use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class ArrayCastingEquivalentInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpIf(@NotNull If expression) {
if (!ExpressionSemanticUtil.hasAlternativeBranches(expression)) {
/* body has only assignment, which to be extracted */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(expression);
if (body != null && ExpressionSemanticUtil.countExpressionsInGroup(body) == 1) {
PsiElement candidate = ExpressionSemanticUtil.getLastStatement(body);
candidate = (candidate == null ? null : candidate.getFirstChild());
if (!OpenapiTypesUtil.isAssignment(candidate)) {
return;
}
/* expecting !function(...) in condition */
PsiElement condition = null;
PsiElement operation = null;
if (expression.getCondition() instanceof UnaryExpression) {
final UnaryExpression inversion = (UnaryExpression) expression.getCondition();
operation = inversion.getOperation();
condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(inversion.getValue());
}
if (!OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT) || !OpenapiTypesUtil.isFunctionReference(condition)) {
return;
}
/* inspect expression */
final AssignmentExpression assignment = (AssignmentExpression) candidate;
final PsiElement trueExpression = assignment.getVariable();
final PsiElement falseExpression = assignment.getValue();
if (trueExpression != null && falseExpression != null && this.isArrayCasting((FunctionReference) condition, trueExpression, falseExpression)) {
final String replacement = trueExpression.getText() + " = (array) " + trueExpression.getText();
holder.registerProblem(expression.getFirstChild(), message, new SimplifyFix(replacement));
}
}
}
}
/* expecting !function(...), true and false expressions */
@Override
public void visitPhpTernaryExpression(@NotNull TernaryExpression expression) {
PsiElement trueExpression = expression.getTrueVariant();
PsiElement falseExpression = expression.getFalseVariant();
PsiElement condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getCondition());
if (condition instanceof UnaryExpression) {
final UnaryExpression unary = (UnaryExpression) condition;
if (OpenapiTypesUtil.is(unary.getOperation(), PhpTokenTypes.opNOT)) {
condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(unary.getValue());
trueExpression = expression.getFalseVariant();
falseExpression = expression.getTrueVariant();
}
}
if (trueExpression != null && falseExpression != null && OpenapiTypesUtil.isFunctionReference(condition)) {
if (this.isArrayCasting((FunctionReference) condition, trueExpression, falseExpression)) {
final String replacement = "(array) " + trueExpression.getText();
holder.registerProblem(expression, message, new SimplifyFix(replacement));
}
}
}
private boolean isArrayCasting(@NotNull FunctionReference condition, @NotNull PsiElement trueExpression, @NotNull PsiElement falseExpression) {
/* false variant should be array creation */
if (falseExpression instanceof ArrayCreationExpression) {
/* condition expected to be is_array(arg) */
final String functionName = condition.getName();
final PsiElement[] params = condition.getParameters();
if (params.length == 1 && functionName != null && functionName.equals("is_array")) {
/* extract array values, expected one value only */
final List<PsiElement> valuesSet = new ArrayList<>();
for (final PsiElement child : falseExpression.getChildren()) {
if (child instanceof PhpPsiElement) {
valuesSet.add(child.getFirstChild());
}
}
/* ensure both true/false branches applied to the same subject */
boolean result = valuesSet.size() == 1 && OpeanapiEquivalenceUtil.areEqual(trueExpression, params[0]) && OpeanapiEquivalenceUtil.areEqual(trueExpression, valuesSet.get(0));
valuesSet.clear();
/* ensure the subject type is array casting safe */
if (result) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) trueExpression, trueExpression.getProject());
if (resolved == null || resolved.isEmpty() || resolved.hasUnknown()) {
/* well, types resolved partially - do not report */
result = false;
} else {
/* also, object casting to array "exports" it instead of wrapping */
for (final String type : resolved.getTypes()) {
if (Types.getType(type).startsWith("\\")) {
result = false;
break;
}
}
}
}
return result;
}
}
return false;
}
};
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class ReferencingObjectsInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
/* re-dispatch to inspector */
@Override
public void visitPhpMethod(@NotNull Method method) {
this.inspectCallable(method);
}
@Override
public void visitPhpFunction(@NotNull Function function) {
this.inspectCallable(function);
}
private void inspectCallable(@NotNull Function callable) {
if (NamedElementUtil.getNameIdentifier(callable) != null) {
Arrays.stream(callable.getParameters()).filter(parameter -> {
final PhpType declared = parameter.getDeclaredType();
return !declared.isEmpty() && parameter.isPassByRef() && !PhpType.isSubType(declared, php7Types);
}).filter(parameter -> {
boolean result = true;
final String parameterName = parameter.getName();
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(callable);
for (final Variable variable : PsiTreeUtil.findChildrenOfType(body, Variable.class)) {
final PsiElement parent = variable.getParent();
if (parent instanceof AssignmentExpression && parameterName.equals(variable.getName())) {
final AssignmentExpression assignment = (AssignmentExpression) parent;
if (assignment.getVariable() == variable) {
result = false;
break;
}
}
}
return result;
}).forEach(parameter -> {
final String message = messageParameter.replace("%p%", parameter.getName());
holder.registerProblem(parameter, message, new ParameterLocalFix(parameter));
});
}
}
@Override
public void visitPhpNewExpression(@NotNull NewExpression expression) {
final PsiElement parent = expression.getParent();
if (parent instanceof AssignmentExpression) {
final AssignmentExpression assignment = (AssignmentExpression) parent;
if (assignment.getValue() == expression) {
PsiElement operation = assignment.getValue().getPrevSibling();
if (operation instanceof PsiWhiteSpace) {
operation = operation.getPrevSibling();
}
if (operation != null && operation.getText().replaceAll("\\s+", "").equals("=&")) {
holder.registerProblem(expression, messageAssignment, new InstantiationLocalFix(operation));
}
}
}
}
};
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class ForeachSourceInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpForeach(@NotNull ForeachStatement foreach) {
final PsiElement source = ExpressionSemanticUtil.getExpressionTroughParenthesis(foreach.getArray());
if (source instanceof PhpTypedElement && !isEnsuredByPyParentIf(foreach, source)) {
this.analyseContainer(source);
}
}
/* should cover is_array/is_iterable in direct parent if of the loop, while PS types resolving gets improved */
private boolean isEnsuredByPyParentIf(@NotNull ForeachStatement foreach, @NotNull PsiElement source) {
boolean result = false;
if (foreach.getPrevPsiSibling() == null) {
final PsiElement ifCandidate = foreach.getParent() instanceof GroupStatement ? foreach.getParent().getParent() : null;
final PsiElement conditions;
if (ifCandidate instanceof If) {
conditions = ((If) ifCandidate).getCondition();
} else if (ifCandidate instanceof ElseIf) {
conditions = ((ElseIf) ifCandidate).getCondition();
} else {
conditions = null;
}
if (conditions != null) {
for (final PsiElement candidate : PsiTreeUtil.findChildrenOfType(conditions, source.getClass())) {
if (OpeanapiEquivalenceUtil.areEqual(candidate, source)) {
final PsiElement call = candidate.getParent() instanceof ParameterList ? candidate.getParent().getParent() : null;
if (OpenapiTypesUtil.isFunctionReference(call)) {
final String functionName = ((FunctionReference) call).getName();
if (functionName != null && (functionName.equals("is_array") || functionName.equals("is_iterable"))) {
result = true;
break;
}
}
}
}
}
}
return result;
}
private void analyseContainer(@NotNull PsiElement container) {
final PhpType resolvedType = OpenapiResolveUtil.resolveType((PhpTypedElement) container, container.getProject());
if (resolvedType == null) {
return;
}
final Set<String> types = new HashSet<>();
resolvedType.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
if (types.isEmpty()) {
/* false-positives: pre-defined variables */
if (container instanceof Variable) {
final String variableName = ((Variable) container).getName();
if (ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
return;
}
}
if (REPORT_UNRECOGNIZED_TYPES) {
holder.registerProblem(container, patternNotRecognized, ProblemHighlightType.WEAK_WARNING);
}
return;
}
/* false-positives: multiple return types checked only in function/method; no global context */
final PsiElement scope = ExpressionSemanticUtil.getBlockScope(container);
if (types.size() > 1 && !(scope instanceof Function)) {
types.clear();
return;
}
/* false-positives: mixed parameter type, parameter overridden before foreach */
if (types.size() > 1 && scope instanceof Function && container instanceof Variable) {
final String parameter = ((Variable) container).getName();
final PhpEntryPointInstruction start = ((Function) scope).getControlFlow().getEntryPoint();
final PhpAccessVariableInstruction[] uses = PhpControlFlowUtil.getFollowingVariableAccessInstructions(start, parameter, false);
for (final PhpAccessVariableInstruction instruction : uses) {
final PhpPsiElement expression = instruction.getAnchor();
/* when matched itself, stop processing */
if (expression == container) {
break;
}
final PsiElement parent = expression.getParent();
if (parent instanceof AssignmentExpression) {
final PsiElement matchCandidate = ((AssignmentExpression) parent).getVariable();
if (matchCandidate != null && OpeanapiEquivalenceUtil.areEqual(matchCandidate, container)) {
types.clear();
return;
}
}
}
}
/* false-positives: array type parameter declaration adds mixed */
if (types.size() > 1 && scope instanceof Function && container instanceof ArrayAccessExpression) {
final PsiElement candidate = ((ArrayAccessExpression) container).getValue();
if (candidate instanceof Variable && types.contains(Types.strMixed) && types.contains(Types.strArray)) {
types.remove(Types.strMixed);
}
}
/* gracefully request to specify exact types which can appear (mixed, object) */
if (types.contains(Types.strMixed)) {
/* false-positive: mixed definitions from stub functions */
boolean isStubFunction = false;
if (OpenapiTypesUtil.isFunctionReference(container)) {
final PsiElement function = OpenapiResolveUtil.resolveReference((FunctionReference) container);
final String filePath = function == null ? null : function.getContainingFile().getVirtualFile().getCanonicalPath();
isStubFunction = filePath != null && filePath.contains(".jar!") && filePath.contains("/stubs/");
}
/* false-positive: mixed definition from array type */
if (!isStubFunction && !types.contains(Types.strArray) && REPORT_MIXED_TYPES) {
final String message = String.format(patternMixedTypes, Types.strMixed);
holder.registerProblem(container, message, ProblemHighlightType.WEAK_WARNING);
}
types.remove(Types.strMixed);
}
if (types.contains(Types.strObject)) {
if (REPORT_MIXED_TYPES) {
final String message = String.format(patternMixedTypes, Types.strObject);
holder.registerProblem(container, message, ProblemHighlightType.WEAK_WARNING);
}
types.remove(Types.strObject);
}
/* respect patter when returned array and bool|null for indicating failures*/
if (types.size() == 2 && types.contains(Types.strArray)) {
types.remove(Types.strBoolean);
types.remove(Types.strNull);
}
/* do not process foreach-compatible types */
types.remove(Types.strArray);
types.remove(Types.strIterable);
types.remove("\\Traversable");
types.remove("\\Iterator");
types.remove("\\IteratorAggregate");
/* don't process mysterious empty set type */
types.remove(Types.strEmptySet);
/* iterate rest of types */
if (!types.isEmpty()) {
final PhpIndex index = PhpIndex.getInstance(holder.getProject());
for (final String type : types) {
/* report if scalar type is met */
if (!type.startsWith("\\")) {
holder.registerProblem(container, String.format(patternScalar, type), ProblemHighlightType.GENERIC_ERROR);
continue;
}
/* check classes for the Traversable interface in the inheritance chain */
final List<PhpClass> classes = OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(type, index);
if (!classes.isEmpty()) {
boolean hasTraversable = false;
for (final PhpClass clazz : classes) {
final Set<PhpClass> interfaces = InterfacesExtractUtil.getCrawlInheritanceTree(clazz, false);
if (!interfaces.isEmpty()) {
hasTraversable = interfaces.stream().anyMatch(i -> i.getFQN().equals("\\Traversable"));
interfaces.clear();
if (hasTraversable) {
break;
}
}
}
classes.clear();
if (!hasTraversable) {
holder.registerProblem(container, String.format(patternObject, type));
}
}
}
types.clear();
}
}
};
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.
the class CollectPossibleThrowsUtil method collectNestedAndWorkflowExceptions.
public static HashMap<PhpClass, HashSet<PsiElement>> collectNestedAndWorkflowExceptions(PsiElement scope, HashSet<PsiElement> processed, @NotNull final ProblemsHolder holder) {
final HashMap<PhpClass, HashSet<PsiElement>> exceptions = new HashMap<>();
/* recursively invoke and analyse nested try-catches checks */
final Collection<Try> tryStatements = PsiTreeUtil.findChildrenOfType(scope, Try.class);
if (tryStatements.size() > 0) {
for (Try nestedTry : tryStatements) {
if (!processed.contains(nestedTry)) {
/* process nested workflow */
final HashMap<PhpClass, HashSet<PsiElement>> nestedTryExceptions = collectNestedAndWorkflowExceptions(nestedTry, processed, holder);
// holder.registerProblem(nestedTry.getFirstChild(), "Nested: " + nestedTryExceptions.toString(), ProblemHighlightType.WEAK_WARNING);
if (nestedTryExceptions.size() > 0) {
for (final Map.Entry<PhpClass, HashSet<PsiElement>> nestedTryExceptionsPair : nestedTryExceptions.entrySet()) {
/* extract pairs Exception class => source expressions */
final PhpClass key = nestedTryExceptionsPair.getKey();
final HashSet<PsiElement> expressionsToDispatch = nestedTryExceptionsPair.getValue();
if (exceptions.containsKey(key)) {
/* merge entries and release refs */
exceptions.get(key).addAll(expressionsToDispatch);
expressionsToDispatch.clear();
} else {
/* store as it is */
exceptions.put(key, expressionsToDispatch);
}
}
nestedTryExceptions.clear();
}
}
}
tryStatements.clear();
}
/* process try-catch */
if (scope instanceof Try) {
/* extract workflow exceptions */
HashMap<PhpClass, HashSet<PsiElement>> tryWorkflowExceptions = collectTryWorkflowExceptions((Try) scope, processed, holder);
// holder.registerProblem(scope.getFirstChild(), "Throws: " + tryWorkflowExceptions.toString(), ProblemHighlightType.WEAK_WARNING);
/* mark processed and exit, as try-catch handled in special way */
processed.add(scope);
exceptions.clear();
return tryWorkflowExceptions;
}
/* process new statements: throws, constructors */
Collection<NewExpression> newExpressions = PsiTreeUtil.findChildrenOfType(scope, NewExpression.class);
if (newExpressions.size() > 0) {
for (NewExpression newExpression : newExpressions) {
/* skip processed */
if (processed.contains(newExpression)) {
continue;
}
// holder.registerProblem(newExpression, "New expression wil be analyzed", ProblemHighlightType.WEAK_WARNING);
/* skip what can not be resolved */
ClassReference newClassRef = newExpression.getClassReference();
if (null == newClassRef) {
processed.add(newExpression);
continue;
}
PhpClass newClass;
final PsiElement resolved = OpenapiResolveUtil.resolveReference(newClassRef);
if (resolved instanceof PhpClass) {
newClass = (PhpClass) resolved;
} else if (resolved instanceof Method) {
newClass = ((Method) resolved).getContainingClass();
} else {
processed.add(newExpression);
continue;
}
/* throws processed */
if (newExpression.getParent() instanceof PhpThrow) {
/* put an expression, create container if necessary */
if (!exceptions.containsKey(newClass)) {
exceptions.put(newClass, new HashSet<>());
}
exceptions.get(newClass).add(newExpression.getParent());
processed.add(newExpression);
continue;
}
/* process constructors invocation */
final Method constructor = newClass == null ? null : newClass.getConstructor();
if (constructor != null) {
// holder.registerProblem(newExpression, "Constructor found", ProblemHighlightType.WEAK_WARNING);
/* lookup for annotated exceptions */
final HashSet<PhpClass> constructorExceptions = new HashSet<>();
ThrowsResolveUtil.resolveThrownExceptions(constructor, constructorExceptions);
/* link expression with each possible exception */
if (constructorExceptions.size() > 0) {
for (PhpClass constructorException : constructorExceptions) {
/* put an expression, create container if necessary */
if (!exceptions.containsKey(constructorException)) {
exceptions.put(constructorException, new HashSet<>());
}
exceptions.get(constructorException).add(newExpression.getParent());
}
constructorExceptions.clear();
}
}
processed.add(newExpression);
}
newExpressions.clear();
}
/* process throws - some of them might not use new-expression */
final Project project = holder.getProject();
final PhpIndex objIndex = PhpIndex.getInstance(project);
Collection<PhpThrow> throwExpressions = PsiTreeUtil.findChildrenOfType(scope, PhpThrow.class);
if (!throwExpressions.isEmpty()) {
for (final PhpThrow throwExpression : throwExpressions) {
/* skip processed */
if (processed.contains(throwExpression)) {
continue;
}
/* resolve argument */
final PsiElement argument = throwExpression.getArgument();
if (argument instanceof PhpTypedElement) {
/* resolve argument types */
final HashSet<String> types = new HashSet<>();
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) argument, project);
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
}
if (!types.isEmpty()) {
/* remove extra definition of \Exception unexpectedly added by PhpStorm */
final boolean dropExtraDefinitions = argument instanceof Variable && types.size() > 1 && types.contains("\\Exception");
if (dropExtraDefinitions) {
types.remove("\\Exception");
}
for (final String type : types) {
if (type.startsWith("\\")) {
/* process classes references */
final Collection<PhpClass> classes = OpenapiResolveUtil.resolveClassesByFQN(type, objIndex);
if (!classes.isEmpty()) {
/* put an expression, create container if necessary */
final PhpClass exception = classes.iterator().next();
exceptions.computeIfAbsent(exception, e -> new HashSet<>()).add(throwExpression);
}
}
}
types.clear();
}
}
processed.add(throwExpression);
}
throwExpressions.clear();
}
/* process nested calls */
Collection<MethodReference> calls = PsiTreeUtil.findChildrenOfType(scope, MethodReference.class);
if (calls.size() > 0) {
for (MethodReference call : calls) {
/* skip processed */
if (processed.contains(call)) {
continue;
}
PsiElement methodResolved = OpenapiResolveUtil.resolveReference(call);
if (methodResolved instanceof Method) {
/* lookup for annotated exceptions */
final HashSet<PhpClass> methodExceptions = new HashSet<>();
ThrowsResolveUtil.resolveThrownExceptions((Method) methodResolved, methodExceptions);
/* link expression with each possible exception */
if (methodExceptions.size() > 0) {
for (PhpClass methodException : methodExceptions) {
/* put an expression, create container if necessary */
if (!exceptions.containsKey(methodException)) {
exceptions.put(methodException, new HashSet<>());
}
exceptions.get(methodException).add(call);
}
methodExceptions.clear();
}
}
processed.add(call);
}
calls.clear();
}
return exceptions;
}
use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project yii2support by nvlad.
the class RenderUtil method getViewArguments.
@NotNull
public static Map<String, PhpType> getViewArguments(MethodReference reference) {
final Map<String, PhpType> result = new LinkedHashMap<>();
result.put("this", new PhpType.PhpTypeBuilder().add(Yii2SupportSettings.getInstance(reference.getProject()).defaultViewClass).build());
ParameterList parameterList = reference.getParameterList();
if (parameterList == null) {
return result;
}
if (parameterList.getParameters().length == 1) {
return result;
}
final PsiElement parameter = parameterList.getParameters()[1];
if (parameter instanceof ArrayCreationExpression) {
final ArrayCreationExpression array = (ArrayCreationExpression) parameter;
for (ArrayHashElement item : array.getHashElements()) {
PhpPsiElement keyElement = item.getKey();
String key;
if (keyElement instanceof StringLiteralExpression) {
key = ((StringLiteralExpression) keyElement).getContents();
} else {
continue;
}
PhpType valueType;
final PhpPsiElement valueElement = item.getValue();
if (valueElement instanceof PhpExpression) {
valueType = ((PhpTypedElement) valueElement).getType().global(valueElement.getProject());
} else {
continue;
}
result.put(key, valueType);
}
return result;
}
return result;
}
Aggregations