Search in sources :

Example 1 with DropMethodFix

use of com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix in project phpinspectionsea by kalessil.

the class SenselessProxyMethodInspector 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.isTrait()) {
                return;
            }
            for (final Method method : clazz.getOwnMethods()) {
                final PsiElement methodNameNode = NamedElementUtil.getNameIdentifier(method);
                if (null == methodNameNode || method.isAbstract() || method.getAccess().isPrivate()) {
                    continue;
                }
                /* we expect the method to have just one expression - parent invocation */
                final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
                if (null == body || 1 != ExpressionSemanticUtil.countExpressionsInGroup(body)) {
                    continue;
                }
                final PsiElement lastStatement = ExpressionSemanticUtil.getLastStatement(body);
                if (null == lastStatement) {
                    continue;
                }
                /* parent invocation can be both direct or via return */
                final PsiElement parentReferenceCandidate;
                if (lastStatement instanceof PhpReturn) {
                    parentReferenceCandidate = ExpressionSemanticUtil.getReturnValue((PhpReturn) lastStatement);
                } else {
                    parentReferenceCandidate = lastStatement.getFirstChild();
                }
                if (!(parentReferenceCandidate instanceof MethodReference)) {
                    continue;
                }
                final MethodReference reference = (MethodReference) parentReferenceCandidate;
                final String referenceVariable = reference.getFirstChild().getText().trim();
                final String referenceName = reference.getName();
                if (null == referenceName || !referenceVariable.equals("parent") || !referenceName.equals(method.getName())) {
                    continue;
                }
                final Parameter[] methodParameters = method.getParameters();
                /* ensure no transformations/reordering happens when dispatching parameters */
                final PsiElement[] givenParams = reference.getParameters();
                boolean isDispatchingWithoutModifications = (givenParams.length == methodParameters.length);
                if (isDispatchingWithoutModifications) {
                    /* ensure parameters re-dispatched in the same order and state */
                    for (int index = 0; index < givenParams.length; ++index) {
                        if (!(givenParams[index] instanceof Variable) || !((Variable) givenParams[index]).getName().equals(methodParameters[index].getName())) {
                            isDispatchingWithoutModifications = false;
                            break;
                        }
                    }
                }
                /* ensure no signature changes took place */
                boolean isChangingSignature = false;
                final PsiReference referenceToMethod = reference.getReference();
                if (null != referenceToMethod && isDispatchingWithoutModifications) {
                    final PsiElement referenceResolved = OpenapiResolveUtil.resolveReference(referenceToMethod);
                    if (referenceResolved instanceof Method) {
                        final Method nestedMethod = (Method) referenceResolved;
                        final Parameter[] parentParameters = nestedMethod.getParameters();
                        /* verify amount of parameters, visibility, static, abstract, final */
                        if (parentParameters.length == methodParameters.length && nestedMethod.isAbstract() == method.isAbstract() && nestedMethod.isStatic() == method.isStatic() && nestedMethod.isFinal() == method.isFinal() && nestedMethod.getAccess().equals(method.getAccess())) {
                            /* analyze if parameters definition has been changed (only ignore naming changes) */
                            if (methodParameters.length > 0) {
                                for (int index = 0; index < parentParameters.length; ++index) {
                                    /* by-reference declaration changes: not allowed by PHP, hence not checked */
                                    /* default values changes */
                                    final PsiElement parentDefault = parentParameters[index].getDefaultValue();
                                    final PsiElement methodDefault = methodParameters[index].getDefaultValue();
                                    if ((parentDefault == null || methodDefault == null) && parentDefault != methodDefault) {
                                        isChangingSignature = true;
                                        break;
                                    }
                                    if (methodDefault != null && !OpenapiEquivalenceUtil.areEqual(parentDefault, methodDefault)) {
                                        isChangingSignature = true;
                                        break;
                                    }
                                    /* false-positive: magic constants ARE changing signature  */
                                    if (methodDefault instanceof ConstantReference) {
                                        final String constant = ((ConstantReference) methodDefault).getName();
                                        if (constants.contains(constant)) {
                                            isChangingSignature = true;
                                            break;
                                        }
                                    }
                                    /* type definition changes */
                                    final PhpType parentType = OpenapiResolveUtil.resolveDeclaredType(parentParameters[index]);
                                    final PhpType methodType = OpenapiResolveUtil.resolveDeclaredType(methodParameters[index]);
                                    if (!parentType.equals(methodType)) {
                                        isChangingSignature = true;
                                        break;
                                    }
                                }
                            }
                            /* verify returned type declaration */
                            if (!isChangingSignature) {
                                final PsiElement methodReturn = OpenapiElementsUtil.getReturnType(method);
                                final PsiElement parentReturn = OpenapiElementsUtil.getReturnType(nestedMethod);
                                if (methodReturn != parentReturn) {
                                    isChangingSignature = methodReturn == null || parentReturn == null || !OpenapiEquivalenceUtil.areEqual(methodReturn, parentReturn);
                                }
                            }
                        } else {
                            /* okay obviously changed */
                            isChangingSignature = true;
                        }
                    } else {
                        /* we couldn't resolve parent, so we can't report anything */
                        isChangingSignature = true;
                    }
                }
                /* decide if need to report any issues */
                if (isDispatchingWithoutModifications && !isChangingSignature) {
                    holder.registerProblem(methodNameNode, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%s%", method.getName())), ProblemHighlightType.WEAK_WARNING, new DropMethodFix());
                }
            }
        }
    };
}
Also used : PsiReference(com.intellij.psi.PsiReference) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) DropMethodFix(com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 2 with DropMethodFix

use of com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix 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

PsiElement (com.intellij.psi.PsiElement)2 DropMethodFix (com.kalessil.phpStorm.phpInspectionsEA.fixers.DropMethodFix)2 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)2 NotNull (org.jetbrains.annotations.NotNull)2 PsiReference (com.intellij.psi.PsiReference)1 PhpDocComment (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment)1 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)1