use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class StrlenInEmptyStringCheckContextInspection 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 && (functionName.equals("strlen") || functionName.equals("mb_strlen"))) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0 && ExpressionSemanticUtil.getBlockScope(reference) != null) {
boolean isMatchedPattern = false;
boolean isEmptyString = false;
PsiElement target = null;
/* check explicit numbers comparisons */
final PsiElement parent = reference.getParent();
if (parent instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) parent;
final PsiElement left = binary.getLeftOperand();
final PsiElement secondOperand = OpenapiElementsUtil.getSecondOperand(binary, reference);
/* second operand should be a number */
if (OpenapiTypesUtil.isNumber(secondOperand)) {
final String number = secondOperand.getText();
/* check cases when comparing with 1 */
final IElementType operator = binary.getOperationType();
if (operator == PhpTokenTypes.opGREATER) {
isMatchedPattern = left == reference && number.equals("0");
target = binary;
isEmptyString = false;
} else if (operator == PhpTokenTypes.opLESS || operator == PhpTokenTypes.opGREATER_OR_EQUAL) {
isMatchedPattern = left == reference && number.equals("1");
target = binary;
isEmptyString = operator == PhpTokenTypes.opLESS;
}
/* check cases when checking equality to 0 */
if (!isMatchedPattern && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
isMatchedPattern = number.equals("0");
target = binary;
isEmptyString = operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opEQUAL;
}
}
}
/* checks NON-implicit boolean comparison patterns */
if (!isMatchedPattern && ExpressionSemanticUtil.isUsedAsLogicalOperand(reference)) {
isMatchedPattern = true;
target = reference;
final PsiElement operation = parent instanceof UnaryExpression ? ((UnaryExpression) parent).getOperation() : null;
if (operation != null) {
isEmptyString = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
target = parent;
}
}
/* investigate possible issues */
if (isMatchedPattern) {
final boolean isRegular = ComparisonStyle.isRegular();
final String operator = (isEmptyString ? "=" : "!") + (this.canApplyIdentityOperator(arguments[0]) ? "==" : "=");
final String replacement = String.format(isRegular ? "%s %s ''" : "'' %s %s", isRegular ? arguments[0].getText() : operator, isRegular ? operator : arguments[0].getText());
holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new CompareToEmptyStringFix(replacement));
}
}
}
}
private boolean canApplyIdentityOperator(@NotNull PsiElement value) {
if (value instanceof PhpTypedElement) {
final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) value, holder.getProject());
if (resolved != null && resolved.size() == 1) {
return Types.strString.equals(Types.getType(resolved.getTypes().iterator().next()));
}
}
return false;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class EmptyClassInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
if (!clazz.isInterface() && !clazz.isDeprecated() && !clazz.isAnonymous()) {
final boolean isEmpty = clazz.getOwnFields().length == 0 && clazz.getOwnMethods().length == 0 && clazz.getTraits().length == 0;
if (isEmpty) {
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent != null) {
/* we can be forced to introduce an empty class: abstract parent, exception classes */
final boolean skip = parent.isAbstract() || InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).stream().anyMatch(c -> c.getFQN().equals("\\Exception"));
if (skip) {
return;
}
}
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(clazz);
if (nameNode != null) {
holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class MissingIssetImplementationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpEmpty(@NotNull PhpEmpty expression) {
this.analyzeDispatchedExpressions(expression.getVariables());
}
@Override
public void visitPhpIsset(@NotNull PhpIsset expression) {
this.analyzeDispatchedExpressions(expression.getVariables());
}
private void analyzeDispatchedExpressions(@NotNull PhpExpression[] parameters) {
final Project project = holder.getProject();
final PhpIndex projectIndex = PhpIndex.getInstance(project);
for (final PhpExpression parameter : parameters) {
if (parameter instanceof FieldReference) {
final FieldReference reference = (FieldReference) parameter;
final ASTNode nameNode = reference.getNameNode();
final String parameterName = parameter.getName();
/* if the field name is not implicit or the field resolved, continue */
if ((nameNode == null || nameNode instanceof Variable) || (parameterName == null || parameterName.isEmpty()) || !OpenapiTypesUtil.is(OpenapiPsiSearchUtil.findResolutionOperator(reference), PhpTokenTypes.ARROW) || OpenapiResolveUtil.resolveReference(reference) != null) {
continue;
}
/* false-positives: in the $this context we are dealing with dynamic properties */
final PhpExpression variable = reference.getClassReference();
if (null == variable || variable.getText().equals("$this")) {
continue;
}
/* long way around: identify an lookup classes */
final Set<String> resolvedTypes = new HashSet<>();
final PhpType resolved = OpenapiResolveUtil.resolveType(variable, project);
if (resolved != null) {
resolved.filterUnknown().getTypes().forEach(t -> resolvedTypes.add(Types.getType(t)));
}
for (final String type : resolvedTypes) {
/* false-positives: SimpleXMLElement, stdClass */
if (type.startsWith("\\") && !magicClasses.contains(type)) {
final Collection<PhpClass> classes = projectIndex.getClassesByFQN(type);
final PhpClass clazz = classes.isEmpty() ? null : classes.iterator().next();
/* resolved class FQN might differ from what type states */
if (clazz != null && !magicClasses.contains(clazz.getFQN())) {
final boolean hasField = OpenapiResolveUtil.resolveField(clazz, parameterName) != null;
if (!hasField && OpenapiResolveUtil.resolveMethod(clazz, "__isset") == null) {
holder.registerProblem(parameter, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%c%", type)), ProblemHighlightType.GENERIC_ERROR);
break;
}
}
}
}
resolvedTypes.clear();
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class SenselessMethodDuplicationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
/* process only real classes and methods */
if (method.isAbstract() || method.isDeprecated() || method.getModifier().isPrivate() || this.isTestContext(method)) {
return;
}
final PhpClass clazz = method.getContainingClass();
if (clazz == null || clazz.isTrait() || clazz.isInterface()) {
return;
}
/* don't take too heavy work */
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
final int countExpressions = body == null ? 0 : ExpressionSemanticUtil.countExpressionsInGroup(body);
if (countExpressions == 0 || countExpressions > MAX_METHOD_SIZE) {
return;
}
/* ensure parent, parent methods are existing and contains the same amount of expressions */
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
final Method parentMethod = null == parent ? null : OpenapiResolveUtil.resolveMethod(parent, method.getName());
if (parentMethod == null || parentMethod.isAbstract() || parentMethod.isDeprecated() || parentMethod.getModifier().isPrivate()) {
return;
}
final GroupStatement parentBody = ExpressionSemanticUtil.getGroupStatement(parentMethod);
if (parentBody == null || ExpressionSemanticUtil.countExpressionsInGroup(parentBody) != countExpressions) {
return;
}
/* iterate and compare expressions */
PhpPsiElement ownExpression = body.getFirstPsiChild();
PhpPsiElement parentExpression = parentBody.getFirstPsiChild();
for (int index = 0; index <= countExpressions; ++index) {
/* skip doc-blocks */
while (ownExpression instanceof PhpDocComment) {
ownExpression = ownExpression.getNextPsiSibling();
}
while (parentExpression instanceof PhpDocComment) {
parentExpression = parentExpression.getNextPsiSibling();
}
if (null == ownExpression || null == parentExpression) {
break;
}
/* process comparing 2 nodes */
if (!OpenapiEquivalenceUtil.areEqual(ownExpression, parentExpression)) {
return;
}
ownExpression = ownExpression.getNextPsiSibling();
parentExpression = parentExpression.getNextPsiSibling();
}
/* methods seem to be identical: resolve used classes to avoid ns/imports magic */
boolean areReferencesMatching = true;
final Collection<String> ownReferences = this.getUsedReferences(body);
if (!ownReferences.isEmpty()) {
final Collection<String> parentReferences = this.getUsedReferences(parentBody);
areReferencesMatching = !ownReferences.contains(null) && ownReferences.equals(parentReferences);
parentReferences.clear();
}
ownReferences.clear();
if (!areReferencesMatching) {
return;
}
final PsiElement methodName = NamedElementUtil.getNameIdentifier(method);
if (methodName != null && !this.isOperatingOnPrivateMembers(parentMethod)) {
final boolean canFix = !parentMethod.getAccess().isPrivate();
if (method.getAccess().equals(parentMethod.getAccess())) {
holder.registerProblem(methodName, String.format(MessagesPresentationUtil.prefixWithEa(messagePatternIdentical), method.getName()), canFix ? new DropMethodFix() : null);
} else {
holder.registerProblem(methodName, String.format(MessagesPresentationUtil.prefixWithEa(messagePatternProxy), method.getName()), canFix ? new ProxyCallFix() : null);
}
}
}
private Collection<String> getUsedReferences(@NotNull GroupStatement body) {
final Set<String> fqns = new HashSet<>();
for (final PhpReference reference : PsiTreeUtil.findChildrenOfAnyType(body, ClassReference.class, ConstantReference.class, FunctionReference.class)) {
if (!(reference instanceof MethodReference)) {
final PsiElement entry = OpenapiResolveUtil.resolveReference(reference);
if (entry instanceof PhpNamedElement) {
// We have to use this over resolved entry FQN as some of PhpStorm versions might not resolve the proper symbol
fqns.add(reference.getFQN());
} else {
fqns.add(null);
}
}
}
return fqns;
}
private boolean isOperatingOnPrivateMembers(@NotNull Method method) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
if (body != null) {
for (final MemberReference reference : PsiTreeUtil.findChildrenOfType(body, MemberReference.class)) {
final PsiElement base = reference.getFirstChild();
final boolean resolve = (base instanceof Variable && ((Variable) base).getName().equals("this")) || (base instanceof ClassReference && base.getText().equals("self"));
if (resolve) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpClassMember && ((PhpClassMember) resolved).getModifier().isPrivate()) {
return true;
}
}
}
}
return false;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class SuspiciousLoopInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpForeach(@NotNull ForeachStatement statement) {
if (VERIFY_VARIABLES_OVERRIDE) {
this.inspectVariables(statement);
}
}
@Override
public void visitPhpFor(@NotNull For statement) {
if (statement.getConditionalExpressions().length > 1) {
holder.registerProblem(statement.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messageMultipleConditions));
}
if (VERIFY_VARIABLES_OVERRIDE) {
this.inspectVariables(statement);
}
}
private void inspectVariables(@NotNull PhpPsiElement loop) {
final Set<String> loopVariables = this.getLoopVariables(loop);
final Function function = ExpressionSemanticUtil.getScope(loop);
if (null != function) {
final HashSet<String> parameters = new HashSet<>();
for (final Parameter param : function.getParameters()) {
parameters.add(param.getName());
}
loopVariables.forEach(variable -> {
if (parameters.contains(variable)) {
final String message = patternOverridesParameter.replace("%v%", variable).replace("%t%", function instanceof Method ? "method" : "function");
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
}
});
parameters.clear();
}
/* scan parents until reached file/callable */
PsiElement parent = loop.getParent();
while (null != parent && !(parent instanceof Function) && !(parent instanceof PhpFile)) {
/* inspect parent loops for conflicted variables */
if (parent instanceof For || parent instanceof ForeachStatement) {
final Set<String> parentVariables = this.getLoopVariables((PhpPsiElement) parent);
loopVariables.forEach(variable -> {
if (parentVariables.contains(variable)) {
holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(patternOverridesLoopVars.replace("%v%", variable)));
}
});
parentVariables.clear();
}
parent = parent.getParent();
}
loopVariables.clear();
}
@NotNull
private Set<String> getLoopVariables(@NotNull PhpPsiElement loop) {
final Set<String> variables = new HashSet<>();
if (loop instanceof For) {
/* get variables from assignments */
Stream.of(((For) loop).getInitialExpressions()).forEach(init -> {
if (init instanceof AssignmentExpression) {
final PhpPsiElement variable = ((AssignmentExpression) init).getVariable();
if (variable instanceof Variable) {
final String variableName = variable.getName();
if (variableName != null) {
variables.add(variableName);
}
}
}
});
} else if (loop instanceof ForeachStatement) {
((ForeachStatement) loop).getVariables().forEach(variable -> variables.add(variable.getName()));
}
return variables;
}
};
}
Aggregations