Search in sources :

Example 6 with PhpDocComment

use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.

the class PhpUnitTestsInspector method buildVisitor.

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

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            final PhpClass clazz = method.getContainingClass();
            final PsiElement nameNode = NamedElementUtil.getNameIdentifier(method);
            final PhpDocComment phpDoc = method.getDocComment();
            if (null == clazz || null == nameNode || phpDoc == null) {
                return;
            }
            final boolean isMethodNamedAsTest = method.getName().startsWith("test");
            for (final PhpDocTag tag : PsiTreeUtil.findChildrenOfType(phpDoc, PhpDocTag.class)) {
                final String tagName = tag.getName();
                if (tagName.equals("@dataProvider")) {
                    final PsiElement candidate = tag.getFirstPsiChild();
                    if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
                        final List<PsiReference> references = Arrays.asList(candidate.getReferences());
                        if (!references.isEmpty()) {
                            Collections.reverse(references);
                            final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
                            if (resolved instanceof Method) {
                                if (SUGGEST_TO_USE_NAMED_DATASETS && !((Method) resolved).isAbstract()) {
                                    final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(resolved);
                                    final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
                                    if (last instanceof PhpReturn) {
                                        final PsiElement value = ExpressionSemanticUtil.getReturnValue((PhpReturn) last);
                                        if (value instanceof ArrayCreationExpression) {
                                            final PsiElement firstChild = ((ArrayCreationExpression) value).getFirstPsiChild();
                                            boolean isNamedDataset = firstChild == null;
                                            if (firstChild instanceof ArrayHashElement) {
                                                final PsiElement key = ((ArrayHashElement) firstChild).getKey();
                                                isNamedDataset = key instanceof StringLiteralExpression;
                                            }
                                            if (!isNamedDataset) {
                                                holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageNamedProvider));
                                            }
                                        }
                                    }
                                }
                            } else {
                                holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
                            }
                        } else {
                            holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDataProvider), ProblemHighlightType.GENERIC_ERROR);
                        }
                    }
                } else if (tagName.equals("@depends")) {
                    final PsiElement candidate = tag.getFirstPsiChild();
                    if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
                        final List<PsiReference> references = Arrays.asList(candidate.getReferences());
                        if (!references.isEmpty()) {
                            Collections.reverse(references);
                            final PsiElement resolved = OpenapiResolveUtil.resolveReference(references.get(0));
                            if (resolved instanceof Method) {
                                final Method dependency = (Method) resolved;
                                if (!dependency.getName().startsWith("test")) {
                                    final PhpDocComment docBlock = dependency.getDocComment();
                                    if (docBlock == null || docBlock.getTagElementsByName("@test").length == 0) {
                                        holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
                                    }
                                }
                            } else {
                                holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
                            }
                        } else {
                            holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDepends), ProblemHighlightType.GENERIC_ERROR);
                        }
                    }
                } else if (tagName.equals("@covers")) {
                    final PsiElement candidate = tag.getFirstPsiChild();
                    if (candidate instanceof PhpDocRef && this.isAnnotation(tag)) {
                        final PhpDocRef referenceNeeded = (PhpDocRef) candidate;
                        final List<PsiReference> references = Arrays.asList(referenceNeeded.getReferences());
                        Collections.reverse(references);
                        /* resolve references, populate information about provided entries */
                        boolean hasCallableReference = false;
                        boolean hasClassReference = false;
                        final String referenceText = referenceNeeded.getText();
                        for (final PsiReference ref : references) {
                            final PsiElement resolved = OpenapiResolveUtil.resolveReference(ref);
                            if (resolved instanceof PhpClass) {
                                hasClassReference = true;
                                hasCallableReference = referenceText.endsWith("::");
                                break;
                            } else if (resolved instanceof Function) {
                                hasCallableReference = true;
                                hasClassReference = resolved instanceof Method;
                                break;
                            }
                        }
                        final boolean callableNeeded = referenceText.contains("::") && !referenceText.contains("::<");
                        if ((callableNeeded && !hasCallableReference) || (!callableNeeded && !hasClassReference)) {
                            holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(String.format(messageCovers, referenceText)), ProblemHighlightType.GENERIC_ERROR);
                        }
                    }
                } else if (tagName.equals("@test")) {
                    if (isMethodNamedAsTest && this.isAnnotation(tag)) {
                        holder.registerProblem(tag.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messageTest), ProblemHighlightType.LIKE_DEPRECATED, new AmbiguousTestAnnotationLocalFix());
                    }
                }
            }
        }

        private boolean isAnnotation(@NotNull PhpDocTag tag) {
            PsiElement previous = tag.getPrevSibling();
            previous = previous instanceof PsiWhiteSpace ? previous.getPrevSibling() : previous;
            final IElementType start = previous == null ? null : previous.getNode().getElementType();
            return start == PhpTokenTypes.DOC_COMMENT_START || start == PhpTokenTypes.DOC_LEADING_ASTERISK;
        }

        @Override
        public void visitPhpMethodReference(@NotNull MethodReference reference) {
            final String methodName = reference.getName();
            if (methodName != null) {
                if (methodName.startsWith("assert") && !methodName.equals("assert")) {
                    final List<BooleanSupplier> callbacks = new ArrayList<>();
                    callbacks.add(() -> AssertBoolInvertedStrategy.apply(methodName, reference, holder));
                    callbacks.add(() -> AssertBoolOfComparisonStrategy.apply(methodName, reference, holder));
                    if (SUGGEST_TO_USE_ASSERTSAME) {
                        callbacks.add(() -> AssertSameStrategy.apply(methodName, reference, holder));
                    }
                    if (PROMOTE_PHPUNIT_API) {
                        final PhpUnitVersion version = PHP_UNIT_VERSION == null ? PhpUnitVersion.PHPUNIT80 : PHP_UNIT_VERSION;
                        callbacks.add(() -> AssertEmptyStrategy.apply(methodName, reference, holder));
                        callbacks.add(() -> AssertConstantStrategy.apply(methodName, reference, holder));
                        callbacks.add(() -> AssertInternalTypeStrategy.apply(methodName, reference, holder, version));
                        callbacks.add(() -> AssertInstanceOfStrategy.apply(methodName, reference, holder));
                        callbacks.add(() -> AssertResourceExistsStrategy.apply(methodName, reference, holder, version));
                        callbacks.add(() -> AssertCountStrategy.apply(methodName, reference, holder));
                        callbacks.add(() -> AssertContainsStrategy.apply(methodName, reference, holder, version));
                        callbacks.add(() -> AssertRegexStrategy.apply(methodName, reference, holder));
                        /* AssertFileEqualsStrategy and AssertStringEqualsFileStrategy order is important */
                        callbacks.add(() -> AssertFileEqualsStrategy.apply(methodName, reference, holder));
                        callbacks.add(() -> AssertStringEqualsFileStrategy.apply(methodName, reference, holder));
                    }
                    for (final BooleanSupplier callback : callbacks) {
                        if (callback.getAsBoolean()) {
                            break;
                        }
                    }
                    callbacks.clear();
                } else if (methodName.equals("expects")) {
                    if (PROMOTE_MOCKING_ONCE) {
                        ExpectsOnceStrategy.apply(methodName, reference, holder);
                    }
                } else if (methodName.equals("will")) {
                    if (PROMOTE_MOCKING_WILL_RETURN) {
                        WillReturnStrategy.apply(methodName, reference, holder);
                    }
                }
            }
        }
    };
}
Also used : ArrayList(java.util.ArrayList) NotNull(org.jetbrains.annotations.NotNull) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpDocRef(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocRef) ArrayList(java.util.ArrayList) List(java.util.List) BooleanSupplier(java.util.function.BooleanSupplier) PsiElement(com.intellij.psi.PsiElement) LeafPsiElement(com.intellij.psi.impl.source.tree.LeafPsiElement) PsiReference(com.intellij.psi.PsiReference) IElementType(com.intellij.psi.tree.IElementType) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) NotNull(org.jetbrains.annotations.NotNull)

Example 7 with PhpDocComment

use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.

the class PropertyCanBeStaticInspector method buildVisitor.

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

        @Override
        public void visitPhpField(@NotNull Field field) {
            final PsiElement nameNode = NamedElementUtil.getNameIdentifier(field);
            if (nameNode != null) {
                final PhpModifier modifier = field.getModifier();
                if (!field.isConstant() && !modifier.isStatic() && !modifier.isPublic()) {
                    final PsiElement defaultValue = field.getDefaultValue();
                    if (defaultValue instanceof ArrayCreationExpression) {
                        final PhpClass clazz = field.getContainingClass();
                        if (clazz != null) {
                            final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
                            if (parent == null || OpenapiResolveUtil.resolveField(parent, field.getName()) == null) {
                                int intArrayOrStringCount = 0;
                                for (final PsiElement entry : defaultValue.getChildren()) {
                                    PhpPsiElement item = null;
                                    if (entry instanceof ArrayHashElement) {
                                        item = ((ArrayHashElement) entry).getValue();
                                    } else if (entry instanceof PhpPsiElement) {
                                        item = ((PhpPsiElement) entry).getFirstPsiChild();
                                    }
                                    if (item instanceof ArrayCreationExpression || item instanceof StringLiteralExpression) {
                                        if (++intArrayOrStringCount == 3 && !this.isSuppressed(field)) {
                                            final boolean canUseConstants = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP560);
                                            holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(canUseConstants ? messageWithConstants : messageNoConstants));
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private boolean isSuppressed(@NotNull PsiElement expression) {
            final PsiElement parent = expression.getParent();
            if (parent.getParent() instanceof PhpClass) {
                final PsiElement previous = ((PhpPsiElement) parent).getPrevPsiSibling();
                if (previous instanceof PhpDocComment) {
                    final String candidate = previous.getText();
                    if (candidate.contains("@noinspection") && candidate.contains(getShortName())) {
                        return true;
                    }
                }
            }
            return false;
        }
    };
}
Also used : NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 8 with PhpDocComment

use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.

the class OneTimeUseVariablesInspector method buildVisitor.

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

        void checkOneTimeUse(@NotNull PhpPsiElement construct, @NotNull Variable argument) {
            final String variableName = argument.getName();
            final PsiElement previous = construct.getPrevPsiSibling();
            /* verify preceding expression (assignment needed) */
            if (null != previous && OpenapiTypesUtil.isAssignment(previous.getFirstChild())) {
                final AssignmentExpression assign = (AssignmentExpression) previous.getFirstChild();
                /* ensure variables are the same */
                final PhpPsiElement assignVariable = assign.getVariable();
                final PsiElement assignValue = ExpressionSemanticUtil.getExpressionTroughParenthesis(assign.getValue());
                if (null != assignValue && assignVariable instanceof Variable) {
                    final String assignVariableName = assignVariable.getName();
                    if (assignVariableName == null || !assignVariableName.equals(variableName)) {
                        return;
                    }
                    /* check if variable as a function/use(...) parameter by reference */
                    final Function function = ExpressionSemanticUtil.getScope(construct);
                    if (null != function) {
                        for (final Parameter param : function.getParameters()) {
                            if (param.isPassByRef() && param.getName().equals(variableName)) {
                                return;
                            }
                        }
                        final List<Variable> useList = ExpressionSemanticUtil.getUseListVariables(function);
                        if (useList != null && !useList.isEmpty()) {
                            for (final Variable param : useList) {
                                if (param.getName().equals(variableName)) {
                                    /* detect parameters by reference in use clause */
                                    PsiElement ampersandCandidate = param.getPrevSibling();
                                    if (ampersandCandidate instanceof PsiWhiteSpace) {
                                        ampersandCandidate = ampersandCandidate.getPrevSibling();
                                    }
                                    if (OpenapiTypesUtil.is(ampersandCandidate, PhpTokenTypes.opBIT_AND)) {
                                        return;
                                    }
                                }
                            }
                            useList.clear();
                        }
                    }
                    /* too long return/throw statements can be decoupled as a variable */
                    if (!ALLOW_LONG_STATEMENTS && assign.getText().length() > 80) {
                        return;
                    }
                    /* heavy part, find usage inside function/method to analyze multiple writes */
                    final PhpScopeHolder parentScope = ExpressionSemanticUtil.getScope(assign);
                    if (null != parentScope) {
                        int countWrites = 0;
                        int countReads = 0;
                        for (final PhpAccessVariableInstruction oneCase : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(parentScope.getControlFlow().getEntryPoint(), variableName)) {
                            final boolean isWrite = oneCase.getAccess().isWrite();
                            if (isWrite) {
                                /* false-positives: type specification */
                                final PsiElement context = oneCase.getAnchor().getParent();
                                if (OpenapiTypesUtil.isAssignment(context)) {
                                    final boolean typeAnnotated = this.isTypeAnnotated((PhpPsiElement) context.getParent(), variableName);
                                    if (typeAnnotated) {
                                        return;
                                    }
                                }
                            }
                            countWrites += isWrite ? 1 : 0;
                            countReads += isWrite ? 0 : 1;
                            if (countWrites > 1 || countReads > 1) {
                                return;
                            }
                        }
                    }
                    if (!(assignValue instanceof NewExpression) || PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP540)) {
                        holder.registerProblem(assignVariable, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%v%", variableName)), new TheLocalFix(holder.getProject(), assign.getParent(), argument, assignValue));
                    }
                }
            }
        }

        @Override
        public void visitPhpReturn(@NotNull PhpReturn returnStatement) {
            if (ANALYZE_RETURN_STATEMENTS) {
                /* if function returning reference, do not inspect returns */
                final Function callable = ExpressionSemanticUtil.getScope(returnStatement);
                final PsiElement nameNode = NamedElementUtil.getNameIdentifier(callable);
                if (null != callable && null != nameNode) {
                    /* is defined like returning reference */
                    PsiElement referenceCandidate = nameNode.getPrevSibling();
                    if (referenceCandidate instanceof PsiWhiteSpace) {
                        referenceCandidate = referenceCandidate.getPrevSibling();
                    }
                    if (OpenapiTypesUtil.is(referenceCandidate, PhpTokenTypes.opBIT_AND)) {
                        return;
                    }
                }
                /* regular function, check one-time use variables */
                final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(returnStatement.getArgument());
                if (argument instanceof PhpPsiElement) {
                    final Variable variable = this.getVariable((PhpPsiElement) argument);
                    if (null != variable) {
                        checkOneTimeUse(returnStatement, variable);
                    }
                }
            }
        }

        @Override
        public void visitPhpMultiassignmentExpression(@NotNull MultiassignmentExpression multiassignmentExpression) {
            if (ANALYZE_ARRAY_DESTRUCTURING) {
                final PsiElement firstChild = multiassignmentExpression.getFirstChild();
                final IElementType nodeType = null == firstChild ? null : firstChild.getNode().getElementType();
                if (null != nodeType && (PhpTokenTypes.kwLIST == nodeType || PhpTokenTypes.chLBRACKET == nodeType)) {
                    final Variable variable = this.getVariable(multiassignmentExpression.getValue());
                    final PsiElement parent = multiassignmentExpression.getParent();
                    if (null != variable && OpenapiTypesUtil.isStatementImpl(parent)) {
                        checkOneTimeUse((PhpPsiElement) parent, variable);
                    }
                }
            }
        }

        /* TODO: once got bored, add foreach source pattern here =) I'm naive but nevertheless ^_^ */
        @Override
        public void visitPhpThrowExpression(@NotNull PhpThrowExpression expression) {
            if (ANALYZE_THROW_STATEMENTS) {
                final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getArgument());
                if (argument instanceof PhpPsiElement) {
                    final Variable variable = this.getVariable((PhpPsiElement) argument);
                    if (null != variable) {
                        checkOneTimeUse(expression.getExpression(), variable);
                    }
                }
            }
        }

        @Nullable
        private Variable getVariable(@Nullable PhpPsiElement expression) {
            if (expression != null) {
                if (expression instanceof Variable) {
                    return (Variable) expression;
                } else if (expression instanceof MemberReference) {
                    final MemberReference reference = (MemberReference) expression;
                    final PsiElement candidate = reference.getFirstChild();
                    if (candidate instanceof Variable) {
                        final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
                        if (OpenapiTypesUtil.is(operator, PhpTokenTypes.ARROW)) {
                            return (Variable) candidate;
                        }
                    }
                } else if (OpenapiTypesUtil.isPhpExpressionImpl(expression)) {
                    /* instanceof passes child classes as well, what isn't correct */
                    return getVariable(expression.getFirstPsiChild());
                }
            }
            return null;
        }

        private boolean isTypeAnnotated(@NotNull PhpPsiElement current, @NotNull String variableName) {
            boolean result = false;
            final PsiElement phpdocCandidate = current.getPrevPsiSibling();
            if (phpdocCandidate instanceof PhpDocComment) {
                final PhpDocTag[] hints = ((PhpDocComment) phpdocCandidate).getTagElementsByName("@var");
                if (hints.length == 1) {
                    final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
                    result = specifiedVariable != null && specifiedVariable.getName().equals(variableName);
                }
            }
            return result;
        }
    };
}
Also used : PhpDocVariable(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable) NotNull(org.jetbrains.annotations.NotNull) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) PhpThrowExpression(com.kalessil.phpStorm.phpInspectionsEA.openApi.elements.PhpThrowExpression) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) IElementType(com.intellij.psi.tree.IElementType) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) PhpDocVariable(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable) Nullable(org.jetbrains.annotations.Nullable) NotNull(org.jetbrains.annotations.NotNull)

Example 9 with PhpDocComment

use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment in project phpinspectionsea by kalessil.

the class NullableVariablesStrategy method isNullableResult.

private static boolean isNullableResult(@NotNull AssignmentExpression assignment, @NotNull Project project) {
    boolean result = false;
    final PsiElement assignmentValue = assignment.getValue();
    /* primary strategy: resolve types and check nullability */
    if (assignmentValue instanceof PhpTypedElement) {
        final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) assignmentValue, project);
        if (resolved != null) {
            final Set<String> types = new HashSet<>();
            resolved.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
            if (types.contains(Types.strNull) || types.contains(Types.strVoid)) {
                types.remove(Types.strNull);
                types.remove(Types.strVoid);
                if (!types.isEmpty()) {
                    result = types.stream().noneMatch(t -> !t.startsWith("\\") && !objectTypes.contains(t));
                }
            }
            types.clear();
        }
    }
    /* secondary strategy: support type specification with `@var <type> <variable>` */
    if (result) {
        final PhpPsiElement variable = assignment.getVariable();
        final PsiElement parent = assignment.getParent();
        if (variable != null && OpenapiTypesUtil.isStatementImpl(parent) && OpenapiTypesUtil.isAssignment(assignment)) {
            final PsiElement previous = ((PhpPsiElement) parent).getPrevPsiSibling();
            if (previous instanceof PhpDocComment) {
                final PhpDocTag[] hints = ((PhpDocComment) previous).getTagElementsByName("@var");
                if (hints.length == 1) {
                    final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
                    if (specifiedVariable != null && specifiedVariable.getName().equals(variable.getName())) {
                        result = Arrays.stream(hints[0].getChildren()).anyMatch(t -> t instanceof PhpDocType && Types.getType(t.getText()).equals(Types.strNull));
                    }
                }
            }
        }
    }
    return result;
}
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) IElementType(com.intellij.psi.tree.IElementType) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) PhpDocType(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType) Collectors(java.util.stream.Collectors) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) Nullable(org.jetbrains.annotations.Nullable) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) PhpDocVariable(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) Condition(com.intellij.openapi.util.Condition) PhpDocType(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) PhpDocVariable(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocVariable) PsiElement(com.intellij.psi.PsiElement)

Example 10 with PhpDocComment

use of com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment 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;
        }
    };
}
Also used : NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) DropMethodFix(com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

PhpDocComment (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment)16 PsiElement (com.intellij.psi.PsiElement)14 NotNull (org.jetbrains.annotations.NotNull)11 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)9 PsiWhiteSpace (com.intellij.psi.PsiWhiteSpace)5 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)5 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)4 IElementType (com.intellij.psi.tree.IElementType)4 PhpDocTag (com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag)4 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)4 com.kalessil.phpStorm.phpInspectionsEA.utils (com.kalessil.phpStorm.phpInspectionsEA.utils)4 Project (com.intellij.openapi.project.Project)3 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)3 PhpDocType (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType)3 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)3 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)3 OptionsComponent (com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent)3 java.util (java.util)3 Collectors (java.util.stream.Collectors)3 javax.swing (javax.swing)3