use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class ClassMockingCorrectnessInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent != null && parent.getFQN().equals("\\PhpSpec\\ObjectBehavior")) {
for (final Method method : clazz.getOwnMethods()) {
for (final Parameter parameter : method.getParameters()) {
/* Since PS 2020.2 union types are introduced (BC-incompatible PSI-changes). Hence traversing node. */
final Collection<ClassReference> references = PsiTreeUtil.findChildrenOfType(parameter, ClassReference.class);
if (!references.isEmpty()) {
for (final ClassReference reference : references) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpClass && ((PhpClass) resolved).isFinal()) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageFinal));
break;
}
}
references.clear();
}
}
}
}
}
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
final String methodName = reference.getName();
final PsiElement[] arguments = reference.getParameters();
if (methodName != null && arguments.length > 0 && methods.containsValue(methodName)) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Method && methods.get(((Method) resolved).getFQN()) != null) {
final PhpClass referencedClass = this.getClass(arguments[0]);
if (referencedClass != null) {
if (methodName.equals("createMock")) {
if (referencedClass.isTrait()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageTrait));
} else if (referencedClass.isFinal()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageFinal));
}
} else if (methodName.equals("getMockBuilder")) {
final PsiElement parent = reference.getParent();
String parentName = null;
if (parent instanceof MethodReference) {
parentName = ((MethodReference) parent).getName();
}
/* classes might need different mocking methods usage */
if (referencedClass.isAbstract() && !referencedClass.isInterface()) {
if (parentName == null) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageMockAbstract));
}
} else if (referencedClass.isTrait()) {
if (parentName == null) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageMockTrait));
}
} else if (referencedClass.isFinal()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageFinal));
}
/* constructor might require arguments */
if (parentName != null && parentName.equals("getMock")) {
final Method constructor = referencedClass.getConstructor();
if (constructor != null) {
final boolean needsArguments = Arrays.stream(constructor.getParameters()).anyMatch(parameter -> parameter.getDefaultValue() == null);
if (needsArguments) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageMockConstructor));
}
}
}
} else if (methodName.equals("getMockForTrait")) {
if (!referencedClass.isTrait()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageNeedsTrait));
}
} else if (methodName.equals("getMockForAbstractClass")) {
if (!referencedClass.isAbstract()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageNeedsAbstract));
}
} else {
if (referencedClass.isFinal()) {
holder.registerProblem(arguments[0], MessagesPresentationUtil.prefixWithEa(messageFinal));
}
}
}
}
}
}
@Nullable
private PhpClass getClass(@NotNull PsiElement expression) {
PhpClass result = null;
if (expression instanceof ClassConstantReference) {
final ClassConstantReference reference = (ClassConstantReference) expression;
final String constantName = reference.getName();
if (constantName != null && constantName.equals("class")) {
final PhpExpression classReference = reference.getClassReference();
if (classReference instanceof ClassReference) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference((ClassReference) classReference);
result = resolved instanceof PhpClass ? (PhpClass) resolved : null;
}
}
} else if (expression instanceof StringLiteralExpression) {
final StringLiteralExpression string = (StringLiteralExpression) expression;
final String contents = string.getContents();
if (string.getFirstPsiChild() == null && contents.length() > 3) {
String fqn = contents.replaceAll("\\\\\\\\", "\\\\");
fqn = fqn.charAt(0) == '\\' ? fqn : '\\' + fqn;
final PhpIndex index = PhpIndex.getInstance(holder.getProject());
for (final PhpClass clazz : OpenapiResolveUtil.resolveClassesByFQN(fqn, index)) {
if (clazz.isFinal()) {
result = clazz;
break;
}
}
}
}
/* TODO: handle __NAMESPACE__.'\Class' */
return result;
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class SelfClassReferencingInspector method buildVisitor.
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder problemsHolder, final boolean onTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
final PhpClass clazz = method.getContainingClass();
if (clazz != null && !clazz.isAnonymous() && !clazz.isTrait() && !method.isAbstract()) {
final String targetReference = PREFER_CLASS_NAMES ? "self" : clazz.getName();
final String targetReplacement = PREFER_CLASS_NAMES ? clazz.getName() : "self";
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
PsiTreeUtil.findChildrenOfType(body, ClassReference.class).stream().filter(reference -> targetReference.equals(reference.getName()) && method == PsiTreeUtil.getParentOfType(reference, Function.class) && clazz == OpenapiResolveUtil.resolveReference(reference)).forEach(reference -> {
final PsiElement parent = reference.getParent();
if (!PREFER_CLASS_NAMES && parent instanceof ClassConstantReference) {
final String constantName = ((ClassConstantReference) parent).getName();
if (constantName != null && constantName.equals("class")) {
final String replacement = "__CLASS__";
problemsHolder.registerProblem(parent, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, parent.getText(), replacement)), new NormalizeReferenceFix(replacement));
return;
}
}
if (!(parent instanceof ExtendsList)) {
problemsHolder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, targetReference, targetReplacement)), new NormalizeReferenceFix(targetReplacement));
}
});
if (PREFER_CLASS_NAMES) {
PsiTreeUtil.findChildrenOfType(body, ConstantReference.class).stream().filter(reference -> "__CLASS__".equals(reference.getName()) && method == PsiTreeUtil.getParentOfType(reference, Function.class)).forEach(reference -> {
final String replacement = targetReplacement + "::class";
problemsHolder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, reference.getText(), replacement)), new NormalizeReferenceFix(replacement));
});
}
}
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class SlowArrayOperationsInLoopInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
final String functionName = reference.getName();
if (functionName != null && greedyFunctions.contains(functionName)) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 1 && !(arguments[0] instanceof ArrayAccessExpression)) {
PsiElement parent = reference.getParent();
if (parent instanceof AssignmentExpression) {
/* false-positives: return/break as last group statement expression */
boolean canLoop = true;
if (OpenapiTypesUtil.isStatementImpl(parent = parent.getParent())) {
final PsiElement grandParent = parent.getParent();
if (grandParent instanceof GroupStatement) {
final PsiElement last = ExpressionSemanticUtil.getLastStatement((GroupStatement) grandParent);
canLoop = !(last instanceof PhpBreak) && !(last instanceof PhpReturn);
}
}
while (canLoop && parent != null && !(parent instanceof PhpFile) && !(parent instanceof Function)) {
if (OpenapiTypesUtil.isLoop(parent)) {
final PsiElement container = ((AssignmentExpression) reference.getParent()).getVariable();
if (container != null) {
for (final PsiElement argument : arguments) {
if (OpenapiEquivalenceUtil.areEqual(container, argument)) {
holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(messageGreedyPattern), functionName));
return;
}
}
}
}
parent = parent.getParent();
}
}
}
}
}
@Override
public void visitPhpFor(@NotNull For forStatement) {
final Set<FunctionReference> references = new HashSet<>();
Arrays.stream(forStatement.getConditionalExpressions()).forEach(c -> {
if (c instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) c;
Stream.of(binary.getLeftOperand(), binary.getRightOperand()).filter(p -> p instanceof FunctionReference).forEach(p -> references.add((FunctionReference) p));
}
});
references.stream().filter(OpenapiTypesUtil::isFunctionReference).forEach(r -> {
final String functionName = r.getName();
if (functionName != null && slowFunctions.contains(functionName)) {
final BinaryExpression condition = (BinaryExpression) r.getParent();
holder.registerProblem(condition, String.format(MessagesPresentationUtil.prefixWithEa(messageSlowPattern), functionName), ProblemHighlightType.GENERIC_ERROR, new ReduceRepetitiveCallsInForFix(holder.getProject(), forStatement, condition));
}
});
references.clear();
}
};
}
use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.
the class SummerTimeUnsafeTimeManipulationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
if (targetOperations.contains(expression.getOperationType())) {
final PsiElement left = expression.getLeftOperand();
final PsiElement right = expression.getRightOperand();
if (right != null && this.isTargetMagicNumber(right) && this.isTargetContext(right)) {
if (!this.isTestContext(expression)) {
holder.registerProblem(expression, message);
}
} else if (left != null && this.isTargetMagicNumber(left) && this.isTargetContext(left)) {
if (!this.isTestContext(expression)) {
holder.registerProblem(expression, message);
}
}
}
}
@Override
public void visitPhpSelfAssignmentExpression(@NotNull SelfAssignmentExpression expression) {
if (targetAssignments.contains(expression.getOperationType())) {
final PsiElement value = expression.getValue();
if (value != null && this.isTargetMagicNumber(value) && !this.isTestContext(expression)) {
holder.registerProblem(expression, message);
}
}
}
private boolean isTargetContext(@NotNull PsiElement magicNumber) {
boolean result = magicNumber.textMatches("86400");
if (!result) {
PsiElement expression = magicNumber.getParent();
while (expression instanceof ParenthesizedExpression || expression instanceof BinaryExpression) {
expression = expression.getParent();
}
result = PsiTreeUtil.findChildrenOfType(expression, PhpExpression.class).stream().filter(OpenapiTypesUtil::isNumber).anyMatch(candidate -> {
final String text = candidate.getText();
return text.equals("60") || text.endsWith("3600");
});
}
return result;
}
private boolean isTargetMagicNumber(@NotNull PsiElement candidate) {
boolean result = false;
if (OpenapiTypesUtil.isNumber(candidate)) {
result = candidate.textMatches("24") || candidate.textMatches("86400");
}
return result;
}
};
}
use of com.intellij.psi.PsiElementVisitor 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();
}
}
};
}
Aggregations