Search in sources :

Example 46 with PsiElementVisitor

use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.

the class ReturnTypeCanBeDeclaredInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        /* TODO: support functions - see https://github.com/kalessil/phpinspectionsea/pull/320 */
        @Override
        public void visitPhpMethod(@NotNull Method method) {
            final PhpLanguageLevel php = PhpLanguageLevel.get(holder.getProject());
            if (php.atLeast(PhpLanguageLevel.PHP700) && !magicMethods.contains(method.getName())) {
                final boolean isTarget = OpenapiElementsUtil.getReturnType(method) == null;
                if (isTarget) {
                    final PsiElement methodNameNode = NamedElementUtil.getNameIdentifier(method);
                    if (methodNameNode != null) {
                        final boolean supportNullableTypes = php.atLeast(PhpLanguageLevel.PHP710);
                        if (method.isAbstract()) {
                            this.handleAbstractMethod(method, methodNameNode, supportNullableTypes);
                        } else {
                            this.handleMethod(method, methodNameNode, supportNullableTypes);
                        }
                    }
                }
            }
        }

        private void handleAbstractMethod(@NotNull Method method, @NotNull PsiElement target, boolean supportNullableTypes) {
            final PhpDocComment docBlock = method.getDocComment();
            if (docBlock != null && docBlock.getReturnTag() != null) {
                this.handleMethod(method, target, supportNullableTypes);
            }
        }

        private void handleMethod(@NotNull Method method, @NotNull PsiElement target, boolean supportNullableTypes) {
            /* suggest nothing when the type is only partially resolved */
            final PhpType resolvedReturnType = OpenapiResolveUtil.resolveType(method, holder.getProject());
            if (resolvedReturnType == null) {
                return;
            } else if (resolvedReturnType.hasUnknown()) {
                /* adding class interface leading to promise-type for interface method */
                boolean isInfluencedByInterface = resolvedReturnType.size() == 2 && resolvedReturnType.filterUnknown().size() == 1;
                if (!isInfluencedByInterface) {
                    return;
                }
            }
            /* ignore DocBlock, resolve and normalize types instead (DocBlock is involved, but nevertheless) */
            final Set<String> normalizedTypes = resolvedReturnType.filterUnknown().getTypes().stream().map(Types::getType).collect(Collectors.toSet());
            this.checkUnrecognizedGenerator(method, normalizedTypes);
            this.checkReturnStatements(method, normalizedTypes);
            final boolean isVoidAvailable = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP710);
            final int typesCount = normalizedTypes.size();
            /* case 1: offer using void */
            if (supportNullableTypes && typesCount == 0 && isVoidAvailable) {
                final PsiElement firstReturn = PsiTreeUtil.findChildOfType(method, PhpReturn.class);
                if (firstReturn == null || ExpressionSemanticUtil.getScope(firstReturn) != method) {
                    final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(Types.strVoid);
                    final String message = messagePattern.replace("%t%", Types.strVoid).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                    holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                }
            }
            /* case 2: offer using type */
            if (1 == typesCount) {
                final String singleType = normalizedTypes.iterator().next();
                final String suggestedType = isVoidAvailable && voidTypes.contains(singleType) ? Types.strVoid : this.compactType(singleType, method);
                final boolean isLegitBasic = singleType.startsWith("\\") || returnTypes.contains(singleType) || suggestedType.equals("self") || suggestedType.equals("static");
                final boolean isLegitVoid = !isLegitBasic && supportNullableTypes && suggestedType.equals(Types.strVoid);
                if (isLegitBasic || isLegitVoid) {
                    /* false-positive: '@return static' which is gets resolved into current class since 2019.2 */
                    final PhpDocComment docBlock = method.getDocComment();
                    final PhpDocReturnTag tag = docBlock == null ? null : docBlock.getReturnTag();
                    final boolean isStatic = tag != null && Arrays.stream(tag.getChildren()).map(PsiElement::getText).filter(t -> !t.isEmpty()).allMatch(t -> t.equals("static"));
                    final boolean isLegitStatic = isStatic && PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP800);
                    if (!isStatic || isLegitStatic) {
                        final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(isLegitStatic ? "static" : suggestedType);
                        final String message = messagePattern.replace("%t%", isLegitStatic ? "static" : suggestedType).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                        holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                    }
                }
            }
            /* case 3: offer using nullable type */
            if (supportNullableTypes && 2 == typesCount && normalizedTypes.contains(Types.strNull)) {
                normalizedTypes.remove(Types.strNull);
                final String nullableType = normalizedTypes.iterator().next();
                final String suggestedType = isVoidAvailable && voidTypes.contains(nullableType) ? Types.strVoid : compactType(nullableType, method);
                final boolean isLegitNullable = nullableType.startsWith("\\") || returnTypes.contains(nullableType) || suggestedType.equals("self");
                final boolean isLegitVoid = !isLegitNullable && suggestedType.equals(Types.strVoid);
                if (isLegitNullable || isLegitVoid) {
                    final String typeHint = isLegitVoid ? suggestedType : '?' + suggestedType;
                    final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(typeHint);
                    final String message = messagePattern.replace("%t%", typeHint).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                    holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                }
            }
        }

        /* use change signature intention promoter */
        private boolean isMethodOverridden(@NotNull Method method) {
            boolean result = false;
            final PhpClass clazz = method.getContainingClass();
            if (clazz != null && !clazz.isFinal() && !method.isFinal() && !method.getAccess().isPrivate()) {
                final String methodName = method.getName();
                result = InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).stream().anyMatch(c -> c != clazz && c.findOwnMethodByName(methodName) != null) || OpenapiResolveUtil.resolveChildClasses(clazz.getFQN(), PhpIndex.getInstance(holder.getProject())).stream().anyMatch(c -> c.findOwnMethodByName(methodName) != null);
            }
            return result;
        }

        @NotNull
        private String compactType(@NotNull String type, @NotNull Method method) {
            String result = null;
            if (type.startsWith("\\") || type.equals("static")) {
                /* Strategy 1: respect `@return self` */
                if (LOOKUP_PHPDOC_RETURN_DECLARATIONS) {
                    final PhpDocComment phpDoc = method.getDocComment();
                    final PhpDocReturnTag phpReturn = phpDoc == null ? null : phpDoc.getReturnTag();
                    if (phpReturn != null) {
                        final boolean hasSelfReference = PsiTreeUtil.findChildrenOfType(phpReturn, PhpDocType.class).stream().anyMatch(t -> {
                            final String text = t.getText();
                            return text.equals("self") || text.equals("$this");
                        });
                        if (hasSelfReference) {
                            result = "self";
                        }
                    }
                }
                /* be sure to send back static for avoiding false-positives */
                result = (result == null && type.equals("static")) ? type : result;
                /* Strategy 2: scan imports */
                if (result == null) {
                    PsiElement groupCandidate = method.getContainingClass();
                    groups: while (groupCandidate != null && !(groupCandidate instanceof PsiFile)) {
                        if (groupCandidate instanceof GroupStatement) {
                            final List<PhpUse> imports = new ArrayList<>();
                            /* scan for imports in current group statement */
                            for (final PsiElement child : groupCandidate.getChildren()) {
                                if (child instanceof PhpUseList) {
                                    Collections.addAll(imports, ((PhpUseList) child).getDeclarations());
                                }
                            }
                            /* iterate imports and search for targets */
                            for (final PhpUse imported : imports) {
                                final PhpReference useReference = imported.getTargetReference();
                                if (useReference instanceof ClassReference && type.equals(useReference.getFQN())) {
                                    final String useAlias = imported.getAliasName();
                                    result = useAlias == null ? useReference.getName() : useAlias;
                                    imports.clear();
                                    break groups;
                                }
                            }
                            imports.clear();
                        }
                        groupCandidate = groupCandidate.getParent();
                    }
                }
                /* Strategy 3: relative QN for classes in sub-namespace */
                if (result == null || result.isEmpty()) {
                    final PhpClass clazz = method.getContainingClass();
                    final String nameSpace = null == clazz ? null : clazz.getNamespaceName();
                    if (nameSpace != null && nameSpace.length() > 1 && type.startsWith(nameSpace)) {
                        result = type.replace(nameSpace, "");
                    }
                }
            }
            return result == null ? type : result;
        }

        private void checkUnrecognizedGenerator(@NotNull Method method, @NotNull Set<String> types) {
            if (!types.contains("\\Generator")) {
                final PhpYield yield = PsiTreeUtil.findChildOfType(method, PhpYield.class);
                if (yield != null && ExpressionSemanticUtil.getScope(yield) == method) {
                    types.add("\\Generator");
                    if (PsiTreeUtil.findChildOfType(method, PhpReturn.class) == null) {
                        types.remove(Types.strNull);
                    }
                }
            }
        }

        private void checkReturnStatements(@NotNull Method method, @NotNull Set<String> types) {
            if (!types.isEmpty() && !method.isAbstract()) {
                /* non-implicit null return: omitted last return statement */
                if (!types.contains(Types.strNull) && !types.contains(Types.strVoid)) {
                    final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
                    final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
                    if (!(last instanceof PhpReturn) && !OpenapiTypesUtil.isThrowExpression(last)) {
                        types.add(Types.strNull);
                    }
                }
                /* buggy parameter type resolving: no type, but null as default value */
                if (types.size() == 1 && types.contains(Types.strNull)) {
                    final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
                    if (body != null) {
                        final PhpReturn expression = PsiTreeUtil.findChildOfType(body, PhpReturn.class);
                        if (expression != null) {
                            final PsiElement value = ExpressionSemanticUtil.getReturnValue(expression);
                            if (value != null && !PhpLanguageUtil.isNull(value)) {
                                types.remove(Types.strNull);
                            }
                        }
                    }
                }
            }
        }
    };
}
Also used : PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) java.util(java.util) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) PhpDocType(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType) PhpIndex(com.jetbrains.php.PhpIndex) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) PhpDocReturnTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag) ProblemDescriptor(com.intellij.codeInspection.ProblemDescriptor) PsiElement(com.intellij.psi.PsiElement) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) Project(com.intellij.openapi.project.Project) PsiFile(com.intellij.psi.PsiFile) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpPsiElementFactory(com.jetbrains.php.lang.psi.PhpPsiElementFactory) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) Collectors(java.util.stream.Collectors) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) NotNull(org.jetbrains.annotations.NotNull) javax.swing(javax.swing) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpDocReturnTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag) PsiFile(com.intellij.psi.PsiFile) PsiElement(com.intellij.psi.PsiElement) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) NotNull(org.jetbrains.annotations.NotNull)

Example 47 with PsiElementVisitor

use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.

the class StaticInvocationViaThisInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpMethodReference(@NotNull MethodReference reference) {
            final String methodName = reference.getName();
            if (methodName != null && !reference.isStatic() && !methodName.startsWith("static")) /* workaround for WI-33569 */
            {
                final PsiElement base = reference.getFirstChild();
                if (base != null && !(base instanceof FunctionReference)) {
                    final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
                    if (OpenapiTypesUtil.is(operator, PhpTokenTypes.ARROW)) {
                        if (base instanceof Variable && ((Variable) base).getName().equals("this")) {
                            /* $this->static() */
                            final Function scope = ExpressionSemanticUtil.getScope(reference);
                            if (scope instanceof Method && !((Method) scope).isStatic()) {
                                final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
                                if (resolved instanceof Method) {
                                    final Method method = (Method) resolved;
                                    if (method.isStatic() && !this.shouldSkip(method)) {
                                        holder.registerProblem(base, String.format(MessagesPresentationUtil.prefixWithEa(messageThisUsed), method.getName()), new TheLocalFix(holder.getProject(), base, operator));
                                    }
                                }
                            }
                        } else {
                            /* <expression>->static() */
                            if (!(base instanceof Variable) || !this.isParameterOrUseVariable((Variable) base)) {
                                final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
                                if (resolved instanceof Method) {
                                    final Method method = (Method) resolved;
                                    if (method.isStatic() && !this.shouldSkip(method)) {
                                        holder.registerProblem(reference, String.format(MessagesPresentationUtil.prefixWithEa(messageExpressionUsed), methodName));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private boolean shouldSkip(@NotNull Method method) {
            final String fqn = method.getFQN();
            if (EXCEPT_PHPUNIT_ASSERTIONS && fqn.startsWith("\\PHPUnit")) {
                final String normalized = fqn.indexOf('_') == -1 ? fqn : fqn.replaceAll("_", "\\\\");
                return normalized.startsWith("\\PHPUnit\\Framework\\");
            }
            if (EXCEPT_ELOQUENT_MODELS && fqn.startsWith("\\Illuminate\\Database\\Eloquent\\Model.")) {
                return true;
            }
            return false;
        }

        private boolean isParameterOrUseVariable(@NotNull Variable variable) {
            boolean result = false;
            final String name = variable.getName();
            if (!name.isEmpty()) {
                final Function scope = ExpressionSemanticUtil.getScope(variable);
                if (scope != null) {
                    result = Stream.of(scope.getParameters()).anyMatch(p -> name.equals(p.getName()));
                    if (!result) {
                        final List<Variable> variables = ExpressionSemanticUtil.getUseListVariables(scope);
                        if (variables != null && !variables.isEmpty()) {
                            result = variables.stream().anyMatch(v -> name.equals(v.getName()));
                            variables.clear();
                        }
                    }
                }
            }
            return result;
        }
    };
}
Also used : PhpPsiElementFactory(com.jetbrains.php.lang.psi.PhpPsiElementFactory) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) LeafPsiElement(com.intellij.psi.impl.source.tree.LeafPsiElement) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) SmartPointerManager(com.intellij.psi.SmartPointerManager) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) List(java.util.List) Stream(java.util.stream.Stream) ProblemDescriptor(com.intellij.codeInspection.ProblemDescriptor) SmartPsiElementPointer(com.intellij.psi.SmartPsiElementPointer) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) NotNull(org.jetbrains.annotations.NotNull) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) javax.swing(javax.swing) NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) LeafPsiElement(com.intellij.psi.impl.source.tree.LeafPsiElement) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 48 with PsiElementVisitor

use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.

the class TraitsPropertiesConflictsInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpClass(@NotNull PhpClass clazz) {
            /* ensure there are traits being used at all */
            final PhpClass[] traits = clazz.getTraits();
            if (traits.length == 0) {
                return;
            }
            /* check conflict with own fields */
            for (final Field ownField : clazz.getOwnFields()) {
                final String ownFieldName = ownField.getName();
                if (!ownFieldName.isEmpty() && !ownField.isConstant() && !this.isDocBlockProperty(ownField, clazz)) {
                    final PhpModifier modifier = ownField.getModifier();
                    if (!modifier.isAbstract() && !this.isAnnotated(ownField)) {
                        final PsiElement ownFieldDefault = OpenapiResolveUtil.resolveDefaultValue(ownField);
                        for (final PhpClass trait : traits) {
                            final Field traitField = OpenapiResolveUtil.resolveField(trait, ownFieldName);
                            if (traitField != null && !this.isDocBlockProperty(traitField, trait)) {
                                final PsiElement traitFieldDefault = OpenapiResolveUtil.resolveDefaultValue(traitField);
                                final boolean isError;
                                if (ownFieldDefault == null || traitFieldDefault == null) {
                                    isError = traitFieldDefault != ownFieldDefault;
                                } else {
                                    isError = !OpenapiEquivalenceUtil.areEqual(traitFieldDefault, ownFieldDefault);
                                }
                                /* error case already covered by the IDEs */
                                final PsiElement ownFieldNameNode = NamedElementUtil.getNameIdentifier(ownField);
                                if (!isError && ownFieldNameNode != null) {
                                    holder.registerProblem(ownFieldNameNode, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), clazz.getName(), trait.getName(), ownFieldName), ProblemHighlightType.WEAK_WARNING);
                                }
                                break;
                            }
                        }
                    }
                }
            }
            /* check parent class accessibility and map use statements with traits for reporting */
            final Map<PhpClass, PsiElement> useReportTargets = new HashMap<>();
            for (final PsiElement child : clazz.getChildren()) {
                if (child instanceof PhpUseList) {
                    for (final ClassReference reference : PsiTreeUtil.findChildrenOfType(child, ClassReference.class)) {
                        final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
                        if (resolved instanceof PhpClass) {
                            useReportTargets.putIfAbsent((PhpClass) resolved, reference);
                        }
                    }
                }
            }
            final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
            if (parent == null || useReportTargets.isEmpty()) {
                useReportTargets.clear();
                return;
            }
            /* iterate parent non-private fields to find conflicting properties */
            for (final Field parentField : parent.getFields()) {
                final String parentFieldName = parentField.getName();
                if (!parentFieldName.isEmpty() && !parentField.isConstant() && !this.isDocBlockProperty(parentField, parent)) {
                    final PhpModifier modifier = parentField.getModifier();
                    if (!modifier.isPrivate() && !modifier.isAbstract()) {
                        final PsiElement parentFieldDefault = OpenapiResolveUtil.resolveDefaultValue(parentField);
                        for (final PhpClass trait : traits) {
                            final Field traitField = OpenapiResolveUtil.resolveField(trait, parentFieldName);
                            if (traitField != null && !this.isDocBlockProperty(traitField, trait)) {
                                final PsiElement traitFieldDefault = OpenapiResolveUtil.resolveDefaultValue(traitField);
                                final boolean isError;
                                if (parentFieldDefault == null || traitFieldDefault == null) {
                                    isError = traitFieldDefault != parentFieldDefault;
                                } else {
                                    isError = !OpenapiEquivalenceUtil.areEqual(traitFieldDefault, parentFieldDefault);
                                }
                                final PsiElement reportTarget = useReportTargets.get(trait);
                                if (reportTarget != null) {
                                    holder.registerProblem(reportTarget, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), clazz.getName(), trait.getName(), parentFieldName), isError ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.WEAK_WARNING);
                                }
                                break;
                            }
                        }
                    }
                }
            }
            useReportTargets.clear();
        }

        private boolean isAnnotated(@NotNull Field ownField) {
            final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(ownField.getDocComment(), PhpDocTag.class);
            return tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
        }

        private boolean isDocBlockProperty(@NotNull Field field, @NotNull PhpClass clazz) {
            return ExpressionSemanticUtil.getBlockScope(field) != clazz;
        }
    };
}
Also used : Arrays(java.util.Arrays) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) HashMap(java.util.HashMap) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) Map(java.util.Map) PsiElement(com.intellij.psi.PsiElement) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) HashMap(java.util.HashMap) NotNull(org.jetbrains.annotations.NotNull) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 49 with PsiElementVisitor

use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.

the class NotOptimalIfConditionsInspection method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpIf(@NotNull If ifStatement) {
            final List<PsiElement> objAllConditions = new ArrayList<>();
            final IElementType[] arrOperationHolder = { null };
            List<PsiElement> objConditionsFromStatement = this.inspectExpressionsOrder(ifStatement.getCondition(), arrOperationHolder);
            if (null != objConditionsFromStatement) {
                objAllConditions.addAll(objConditionsFromStatement);
                if (REPORT_INSTANCE_OF_FLAWS) {
                    this.inspectConditionsForInstanceOfAndIdentityOperations(objConditionsFromStatement, arrOperationHolder[0]);
                    this.inspectConditionsForAmbiguousInstanceOf(objConditionsFromStatement);
                }
                objConditionsFromStatement.clear();
                if (REPORT_LITERAL_OPERATORS) {
                    AndOrWordsUsageStrategy.apply(ifStatement.getCondition(), holder);
                }
            }
            for (final ElseIf objElseIf : ifStatement.getElseIfBranches()) {
                objConditionsFromStatement = this.inspectExpressionsOrder(objElseIf.getCondition(), arrOperationHolder);
                if (objConditionsFromStatement != null) {
                    objAllConditions.addAll(objConditionsFromStatement);
                    if (REPORT_INSTANCE_OF_FLAWS) {
                        this.inspectConditionsForInstanceOfAndIdentityOperations(objConditionsFromStatement, arrOperationHolder[0]);
                        this.inspectConditionsForAmbiguousInstanceOf(objConditionsFromStatement);
                    }
                    objConditionsFromStatement.clear();
                    if (REPORT_LITERAL_OPERATORS) {
                        AndOrWordsUsageStrategy.apply(objElseIf.getCondition(), holder);
                    }
                }
            }
            /* TODO: If not binary/ternary/assignment/array access expression,  */
            /* TODO: perform types lookup - nullable core types/classes should be compared with null.  */
            /* TODO: Inversion should be un-boxed to get expression. */
            objAllConditions.clear();
        }

        // reports $value instanceof \DateTime OP $value instanceof \DateTimeInterface
        private void inspectConditionsForAmbiguousInstanceOf(@NotNull List<PsiElement> conditions) {
            if (conditions.size() < 2) {
                return;
            }
            // find all instanceof expressions
            final List<BinaryExpression> instanceOfExpressions = new ArrayList<>();
            conditions.stream().filter(expression -> expression instanceof BinaryExpression).forEach(expression -> {
                final BinaryExpression binary = (BinaryExpression) expression;
                if (PhpTokenTypes.kwINSTANCEOF == binary.getOperationType()) {
                    instanceOfExpressions.add(binary);
                }
            });
            // terminate processing if not enough entries for analysis
            if (instanceOfExpressions.size() < 2) {
                instanceOfExpressions.clear();
                return;
            }
            // now we need to build up following structure:
            /* 'subject' => [ condition => class, ... ] */
            final Map<PsiElement, Map<PsiElement, PhpClass>> mappedChecks = new HashMap<>();
            for (final BinaryExpression instanceOfExpression : instanceOfExpressions) {
                // ensure expression is well-formed
                final PsiElement subject = instanceOfExpression.getLeftOperand();
                if (null == subject || !(instanceOfExpression.getRightOperand() instanceof ClassReference)) {
                    continue;
                }
                // ensure resolvable
                final ClassReference reference = (ClassReference) instanceOfExpression.getRightOperand();
                final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
                if (!(resolved instanceof PhpClass)) {
                    continue;
                }
                final PhpClass clazz = (PhpClass) resolved;
                // push subject properly, as expressions can be different objects with the same semantics
                PsiElement registeredSubject = null;
                for (final PsiElement testSubject : mappedChecks.keySet()) {
                    if (OpenapiEquivalenceUtil.areEqual(subject, testSubject)) {
                        registeredSubject = testSubject;
                        break;
                    }
                }
                // put empty container if it's not known
                if (null == registeredSubject) {
                    mappedChecks.put(subject, new HashMap<>());
                    registeredSubject = subject;
                }
                // register condition for further analysis
                mappedChecks.get(registeredSubject).put(instanceOfExpression, clazz);
            }
            // release references in the raw list
            instanceOfExpressions.clear();
            final boolean isDateTimeInterfaceAvailable = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP550);
            // process entries, perform subject container clean up on each iteration
            final Map<PhpClass, Set<PhpClass>> resolvedInheritanceChains = new HashMap<>();
            for (final Map<PsiElement, PhpClass> subjectContainer : mappedChecks.values()) {
                // investigate one subject when it has multiple instanceof-expressions
                if (subjectContainer.size() > 1) {
                    // walk through conditions
                    for (Map.Entry<PsiElement, PhpClass> instanceOf2class : subjectContainer.entrySet()) {
                        /* unpack the pair */
                        final PhpClass clazz = instanceOf2class.getValue();
                        final PsiElement instanceOfExpression = instanceOf2class.getKey();
                        // extract current condition details
                        final Set<PhpClass> clazzParents = resolvedInheritanceChains.computeIfAbsent(clazz, c -> InterfacesExtractUtil.getCrawlInheritanceTree(c, true));
                        // inner loop for verification
                        for (Map.Entry<PsiElement, PhpClass> instanceOf2classInner : subjectContainer.entrySet()) {
                            // skip itself
                            if (instanceOf2classInner.getKey() == instanceOfExpression) {
                                continue;
                            }
                            // if alternative references to base class current check is ambiguous
                            final PhpClass secondClass = instanceOf2classInner.getValue();
                            if (clazzParents.contains(secondClass)) {
                                /* false-positive: the interface in stubs but accessible in php 5.5+ only */
                                if (secondClass.getFQN().equals("\\DateTimeInterface") && !isDateTimeInterfaceAvailable) {
                                    continue;
                                }
                                holder.registerProblem(instanceOfExpression, MessagesPresentationUtil.prefixWithEa(messageInstanceOfAmbiguous), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                                break;
                            }
                        }
                    }
                }
                subjectContainer.clear();
            }
            // release inheritance cache as well
            resolvedInheritanceChains.values().forEach(Set::clear);
            resolvedInheritanceChains.clear();
            // release mapping as well
            mappedChecks.clear();
        }

        /* TODO: is_* functions */
        private void inspectConditionsForInstanceOfAndIdentityOperations(@NotNull List<PsiElement> conditions, @Nullable IElementType operationType) {
            PsiElement testSubject = null;
            if (operationType == PhpTokenTypes.opAND && conditions.size() > 1) {
                for (final PsiElement expression : conditions) {
                    if (expression instanceof BinaryExpression) {
                        final BinaryExpression instanceOfExpression = (BinaryExpression) expression;
                        if (instanceOfExpression.getOperationType() == PhpTokenTypes.kwINSTANCEOF) {
                            testSubject = instanceOfExpression.getLeftOperand();
                            break;
                        }
                    }
                }
            }
            if (testSubject != null) {
                for (final PsiElement expression : conditions) {
                    if (expression instanceof BinaryExpression) {
                        final BinaryExpression binaryExpression = (BinaryExpression) expression;
                        final PsiElement left = binaryExpression.getLeftOperand();
                        final PsiElement right = binaryExpression.getRightOperand();
                        if (left != null && right != null && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(binaryExpression.getOperationType())) {
                            if (OpenapiEquivalenceUtil.areEqual(testSubject, left) || OpenapiEquivalenceUtil.areEqual(testSubject, right)) {
                                holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(messageInstanceOfComplementarity), ProblemHighlightType.WEAK_WARNING);
                            }
                        }
                    }
                }
            }
        }

        private void inspectConditionsForDuplicatedCalls(@NotNull List<PsiElement> conditions) {
            if (conditions.size() < 2) {
                return;
            }
            /* extract calls */
            final List<PsiElement> callsExtracted = new ArrayList<>();
            for (final PsiElement condition : conditions) {
                if (condition instanceof BinaryExpression) {
                    final PsiElement left = ((BinaryExpression) condition).getLeftOperand();
                    if (left instanceof FunctionReference) {
                        callsExtracted.add(left);
                    }
                    final PsiElement right = ((BinaryExpression) condition).getRightOperand();
                    if (right instanceof FunctionReference) {
                        callsExtracted.add(right);
                    }
                }
            }
            /* scan for duplicates */
            for (final PsiElement expression : callsExtracted) {
                if (expression != null) {
                    /* put a stub */
                    callsExtracted.set(callsExtracted.indexOf(expression), null);
                    /* search duplicates in current scope */
                    for (final PsiElement innerLoopExpression : callsExtracted) {
                        if (innerLoopExpression != null && OpenapiEquivalenceUtil.areEqual(innerLoopExpression, expression)) {
                            holder.registerProblem(innerLoopExpression, MessagesPresentationUtil.prefixWithEa(messageDuplicateConditionPart));
                            callsExtracted.set(callsExtracted.indexOf(innerLoopExpression), null);
                        }
                    }
                }
            }
        }

        private List<String> getPreviouslyModifiedVariables(@NotNull If ifStatement) {
            final List<String> result = new ArrayList<>();
            PsiElement previous = ifStatement.getPrevPsiSibling();
            while (previous != null) {
                if (OpenapiTypesUtil.isStatementImpl(previous)) {
                    final PsiElement candidate = previous.getFirstChild();
                    if (OpenapiTypesUtil.isAssignment(candidate)) {
                        final PsiElement container = ((AssignmentExpression) candidate).getVariable();
                        if (container instanceof Variable) {
                            result.add(((Variable) container).getName());
                        }
                    }
                }
                previous = previous.getPrevSibling();
            }
            return result;
        }

        /**
         * @param objCondition to inspect
         */
        @Nullable
        private List<PsiElement> inspectExpressionsOrder(PsiElement objCondition, @Nullable IElementType[] arrOperationHolder) {
            final List<PsiElement> conditions = ExpressionSemanticUtil.getConditions(objCondition, arrOperationHolder);
            if (null == conditions) {
                return null;
            }
            /* one item only, skip costs estimation */
            if (!SUGGEST_OPTIMIZING_CONDITIONS || conditions.size() < 2) {
                return conditions;
            }
            /* verify if costs estimated are optimal */
            int intPreviousCost = 0;
            PsiElement previousCond = null;
            for (final PsiElement condition : conditions) {
                int intLoopCurrentCost = ExpressionCostEstimateUtil.getExpressionCost(condition, functionsSet);
                if (null != previousCond && intLoopCurrentCost < intPreviousCost && !ExpressionsCouplingCheckUtil.isSecondCoupledWithFirst(previousCond, condition)) {
                    holder.registerProblem(condition, MessagesPresentationUtil.prefixWithEa(messageOrdering), ProblemHighlightType.WEAK_WARNING);
                }
                intPreviousCost = intLoopCurrentCost;
                previousCond = condition;
            }
            return conditions;
        }
    };
}
Also used : java.util(java.util) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) IElementType(com.intellij.psi.tree.IElementType) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) ExpressionCostEstimateUtil(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.utils.ExpressionCostEstimateUtil) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) AndOrWordsUsageStrategy(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.strategy.AndOrWordsUsageStrategy) Nullable(org.jetbrains.annotations.Nullable) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) PsiElement(com.intellij.psi.PsiElement) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ExpressionsCouplingCheckUtil(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.utils.ExpressionsCouplingCheckUtil) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) javax.swing(javax.swing) NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) IElementType(com.intellij.psi.tree.IElementType) Nullable(org.jetbrains.annotations.Nullable) NotNull(org.jetbrains.annotations.NotNull)

Example 50 with PsiElementVisitor

use of com.intellij.psi.PsiElementVisitor in project phpinspectionsea by kalessil.

the class OpAssignShortSyntaxInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignment) {
            final PsiElement value = ExpressionSemanticUtil.getExpressionTroughParenthesis(assignment.getValue());
            if (value instanceof BinaryExpression) {
                final BinaryExpression binary = (BinaryExpression) value;
                final PsiElement operator = binary.getOperation();
                if (operator != null) {
                    final PsiElement left = binary.getLeftOperand();
                    final PsiElement right = binary.getRightOperand();
                    final PsiElement variable = assignment.getVariable();
                    if (variable != null && left != null && right != null) {
                        final IElementType operation = operator.getNode().getElementType();
                        if (mapping.containsKey(operation)) {
                            final LinkedList<PsiElement> fragments = new LinkedList<>();
                            fragments.addLast(right);
                            PsiElement candidate = left;
                            while (candidate instanceof BinaryExpression) {
                                final BinaryExpression current = (BinaryExpression) candidate;
                                final PsiElement rightPart = current.getRightOperand();
                                if (rightPart != null) {
                                    fragments.addLast(rightPart);
                                }
                                if (current.getOperationType() != operation) {
                                    break;
                                }
                                candidate = current.getLeftOperand();
                            }
                            if (candidate != null && OpenapiEquivalenceUtil.areEqual(variable, candidate)) {
                                final boolean canShorten = (fragments.size() == 1 || chainingSafeOperators.contains(operation)) && fragments.stream().noneMatch(f -> f instanceof BinaryExpression);
                                if (canShorten) {
                                    /* false-positives: string elements manipulation, causes a fatal error */
                                    boolean isStringManipulation = false;
                                    if (variable instanceof ArrayAccessExpression) {
                                        final PsiElement stringCandidate = ((ArrayAccessExpression) variable).getValue();
                                        if (stringCandidate instanceof PhpTypedElement) {
                                            final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) stringCandidate, holder.getProject());
                                            if (resolved != null && !resolved.hasUnknown()) {
                                                isStringManipulation = resolved.getTypes().stream().anyMatch(t -> Types.getType(t).equals(Types.strString));
                                            }
                                        }
                                    }
                                    if (!isStringManipulation) {
                                        Collections.reverse(fragments);
                                        final String replacement = String.format("%s %s= %s", candidate.getText(), operator.getText(), fragments.stream().map(PsiElement::getText).collect(Collectors.joining(" " + operator.getText() + " ")));
                                        holder.registerProblem(assignment, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new UseShorthandOperatorFix(replacement));
                                    }
                                }
                            }
                            fragments.clear();
                        }
                    }
                }
            }
        }
    };
}
Also used : java.util(java.util) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) IElementType(com.intellij.psi.tree.IElementType) ArrayAccessExpression(com.jetbrains.php.lang.psi.elements.ArrayAccessExpression) AssignmentExpression(com.jetbrains.php.lang.psi.elements.AssignmentExpression) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) UseSuggestedReplacementFixer(com.kalessil.phpStorm.phpInspectionsEA.fixers.UseSuggestedReplacementFixer) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) Collectors(java.util.stream.Collectors) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) PsiElement(com.intellij.psi.PsiElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) AssignmentExpression(com.jetbrains.php.lang.psi.elements.AssignmentExpression) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) ArrayAccessExpression(com.jetbrains.php.lang.psi.elements.ArrayAccessExpression) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

PsiElementVisitor (com.intellij.psi.PsiElementVisitor)60 PsiElement (com.intellij.psi.PsiElement)54 NotNull (org.jetbrains.annotations.NotNull)49 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)39 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)37 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)37 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)27 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)21 Project (com.intellij.openapi.project.Project)19 MessagesPresentationUtil (com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil)19 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)17 Set (java.util.Set)17 com.kalessil.phpStorm.phpInspectionsEA.utils (com.kalessil.phpStorm.phpInspectionsEA.utils)16 HashSet (java.util.HashSet)16 OptionsComponent (com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent)15 javax.swing (javax.swing)15 LocalQuickFix (com.intellij.codeInspection.LocalQuickFix)14 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)14 ProblemDescriptor (com.intellij.codeInspection.ProblemDescriptor)12 ProblemHighlightType (com.intellij.codeInspection.ProblemHighlightType)12