Search in sources :

Example 56 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class NotOptimalRegularExpressionsInspector method buildVisitor.

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

        @Override
        public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
            final String functionName = reference.getName();
            if (functionName != null && functions.contains(functionName)) {
                final PsiElement[] params = reference.getParameters();
                if (params.length > 0) {
                    final boolean checkCall = !(params[0] instanceof ArrayCreationExpression);
                    final Set<StringLiteralExpression> patterns = this.extractPatterns(params[0]);
                    for (final StringLiteralExpression pattern : patterns) {
                        if (pattern.getContainingFile() == params[0].getContainingFile()) {
                            final String regex = pattern.getContents();
                            if (!regex.isEmpty() && pattern.getFirstPsiChild() == null) {
                                boolean hasDelimiters = false;
                                for (final Pattern matchPattern : matchers) {
                                    final Matcher matcher = matchPattern.matcher(regex);
                                    if (hasDelimiters = matcher.find()) {
                                        final String phpRegexPattern = matcher.group(2);
                                        final String phpRegexModifiers = matcher.group(4);
                                        this.checkRegex(functionName, reference, pattern, phpRegexPattern, phpRegexModifiers);
                                        if (checkCall) {
                                            this.checkCall(functionName, reference, phpRegexPattern, phpRegexModifiers);
                                        }
                                        break;
                                    }
                                }
                                if (!hasDelimiters && !functionName.equals("preg_quote")) {
                                    holder.registerProblem(pattern, MessagesPresentationUtil.prefixWithEa(messageNoDelimiters));
                                }
                            }
                        }
                        patterns.clear();
                    }
                }
            }
        }

        private Set<StringLiteralExpression> extractPatterns(@NotNull PsiElement candidate) {
            final Set<StringLiteralExpression> result = new HashSet<>();
            if (candidate instanceof ArrayCreationExpression) {
                for (final PsiElement child : candidate.getChildren()) {
                    /* extract element */
                    final StringLiteralExpression element;
                    if (child instanceof ArrayHashElement) {
                        element = ExpressionSemanticUtil.resolveAsStringLiteral(((ArrayHashElement) child).getValue());
                    } else if (child instanceof PhpPsiElement) {
                        element = ExpressionSemanticUtil.resolveAsStringLiteral(child);
                    } else {
                        element = null;
                    }
                    /* resolve element */
                    if (element != null) {
                        result.add(element);
                    }
                }
            } else {
                final StringLiteralExpression literal = ExpressionSemanticUtil.resolveAsStringLiteral(candidate);
                if (literal != null) {
                    result.add(literal);
                }
            }
            return result;
        }

        private void checkRegex(String functionName, FunctionReference reference, StringLiteralExpression target, String regex, String modifiers) {
            /* Modifiers validity (done):
                 * + /no-az-chars/i => /no-az-chars/
                 * + /no-dot-char/s => /no-dot-char/
                 * + /no-$/D => /no-$/
                 * + /no-^-or-$-occurrences/m => /no-^-or-$-occurrences/
                 * + /regexp/e => mark as deprecated, use preg_replace_callback instead
                 * + Check allowed PHP modifiers: eimsuxADJSUX
                 */
            DeprecatedModifiersCheckStrategy.apply(modifiers, target, holder);
            AllowedModifierCheckStrategy.apply(functionName, modifiers, target, holder);
            UselessDollarEndOnlyModifierStrategy.apply(modifiers, regex, target, holder);
            UselessDotAllModifierCheckStrategy.apply(modifiers, regex, target, holder);
            UselessIgnoreCaseModifierCheckStrategy.apply(modifiers, regex, target, holder);
            /* Classes shortening (done):
                 * + [0-9] => \d
                 * + [^0-9] => \D
                 * + [:digit:] => \d
                 * + [:word:] => \w
                 * + [^\w] => \W
                 * + [^\s] => \S
                 */
            ShortClassDefinitionStrategy.apply(modifiers, regex, target, holder);
            /* Optimizations:
                 * (...) => (?:...) (if there is no back-reference)
                 *
                 * + .*?[symbol] at the end of regexp => [^symbol]*[symbol] (e.g. xml/html parsing using <.*?> vs <[^>]*>)
                 * + .+?[symbol] at the end of regexp => [^symbol]+[symbol]
                 * + / .* ··· /, / ···.* / => /···/ (remove leading and trailing .* without ^ or $, note: if no back-reference to \0)
                 * + [seq][seq]... => [seq]{N}
                 * + [seq][seq]+ => [seq]{2,}
                 * + [seq][seq]* => [seq]+
                 * + [seq][seq]? => [seq]{1,2}
                 *
                 * + greedy character classes [\d\w][\D\W]
                 * + dangerous (a+)+ pattern
                 */
            SequentialClassesCollapseCheckStrategy.apply(regex, target, holder);
            AmbiguousAnythingTrimCheckStrategy.apply(functionName, reference, regex, target, holder);
            GreedyCharactersSetCheckStrategy.apply(regex, target, holder);
            QuantifierCompoundsQuantifierCheckStrategy.apply(regex, target, holder);
            /*
                 * Probably bugs:
                 *  - nested tags check without /s
                 *  - unicode characters without /u
                 */
            MissingDotAllCheckStrategy.apply(modifiers, regex, target, holder);
            MissingUnicodeModifierStrategy.apply(functionName, modifiers, regex, target, holder);
        }

        private void checkCall(String functionName, FunctionReference reference, String regex, String modifiers) {
            /* Plain API simplification (done):
                 * + /^text/ => 0 === strpos(...) (match)
                 * + /text/ => false !== strpos(...) (match) / str_replace (replace)
                 * + /^text/i => 0 === stripos(...) (match)
                 * + /text/i => false !== stripos(...) (match) / str_ireplace (replace)
                 * + preg_quote => warning if second argument is not presented
                 * + preg_match_all without match argument preg_match
                 */
            FunctionCallCheckStrategy.apply(functionName, reference, holder);
            PlainApiUseCheckStrategy.apply(functionName, reference, modifiers, regex, holder);
            /* source checks */
            UnnecessaryCaseManipulationCheckStrategy.apply(functionName, reference, modifiers, holder);
        }
    };
}
Also used : Pattern(java.util.regex.Pattern) Matcher(java.util.regex.Matcher) NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) NotNull(org.jetbrains.annotations.NotNull)

Example 57 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class NullCoalescingOperatorCanBeUsedInspector method buildVisitor.

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

        @Override
        public void visitPhpTernaryExpression(@NotNull TernaryExpression expression) {
            if (SUGGEST_SIMPLIFYING_TERNARIES && !expression.isShort() && PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP700)) {
                final PsiElement condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getCondition());
                if (condition != null) {
                    final PsiElement extracted = this.getTargetCondition(condition);
                    if (extracted != null) {
                        final PsiElement firstValue = expression.getTrueVariant();
                        final PsiElement secondValue = expression.getFalseVariant();
                        if (firstValue != null && secondValue != null) {
                            final String replacement = this.generateReplacement(condition, extracted, firstValue, secondValue);
                            if (replacement != null) {
                                holder.registerProblem(expression, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new ReplaceSingleConstructFix(replacement));
                            }
                        }
                    }
                }
            }
        }

        @Override
        public void visitPhpIf(@NotNull If statement) {
            final Project project = holder.getProject();
            if (SUGGEST_SIMPLIFYING_IFS && PhpLanguageLevel.get(project).atLeast(PhpLanguageLevel.PHP700)) {
                final PsiElement condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(statement.getCondition());
                if (condition != null && statement.getElseIfBranches().length == 0) {
                    final PsiElement extracted = this.getTargetCondition(condition);
                    if (extracted != null) {
                        final Couple<Couple<PsiElement>> fragments = this.extract(statement);
                        final PsiElement firstValue = fragments.second.first;
                        final PsiElement secondValue = fragments.second.second;
                        if (firstValue != null) {
                            final String coalescing = this.generateReplacement(condition, extracted, firstValue, secondValue);
                            if (coalescing != null) {
                                final PsiElement context = firstValue.getParent();
                                if (context instanceof PhpReturn) {
                                    final String replacement = String.format("return %s", coalescing);
                                    holder.registerProblem(statement.getFirstChild(), String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new ReplaceMultipleConstructFix(project, fragments.first.first, fragments.first.second, replacement));
                                } else if (context instanceof AssignmentExpression) {
                                    final PsiElement container = ((AssignmentExpression) context).getVariable();
                                    final String replacement = String.format("%s = %s", container.getText(), coalescing);
                                    holder.registerProblem(statement.getFirstChild(), String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new ReplaceMultipleConstructFix(project, fragments.first.first, fragments.first.second, replacement));
                                }
                            }
                        }
                    }
                }
            }
        }

        private boolean wrap(@Nullable PsiElement expression) {
            if (expression instanceof TernaryExpression || expression instanceof AssignmentExpression) {
                return true;
            } else if (expression instanceof BinaryExpression) {
                return ((BinaryExpression) expression).getOperationType() != PhpTokenTypes.opCOALESCE;
            }
            return false;
        }

        @Nullable
        private String generateReplacement(@NotNull PsiElement condition, @NotNull PsiElement extracted, @NotNull PsiElement first, @Nullable PsiElement second) {
            String coalescing = null;
            if (extracted instanceof PhpIsset) {
                coalescing = this.generateReplacementForIsset(condition, (PhpIsset) extracted, first, second);
            } else if (extracted instanceof PhpEmpty) {
                coalescing = this.generateReplacementForPropertyAccess(condition, (PhpEmpty) extracted, first, second);
            } else if (extracted instanceof Variable || extracted instanceof ArrayAccessExpression || extracted instanceof FieldReference) {
                coalescing = this.generateReplacementForPropertyAccess(condition, extracted, first, second);
            } else if (extracted instanceof FunctionReference) {
                if (second != null) {
                    coalescing = this.generateReplacementForExists(condition, (FunctionReference) extracted, first, second);
                }
            } else if (extracted instanceof BinaryExpression) {
                if (second != null) {
                    coalescing = this.generateReplacementForIdentity(condition, (BinaryExpression) extracted, first, second);
                }
            }
            return coalescing;
        }

        @Nullable
        private String generateReplacementForExists(@NotNull PsiElement condition, @NotNull FunctionReference extracted, @NotNull PsiElement first, @NotNull PsiElement second) {
            final PsiElement[] arguments = extracted.getParameters();
            if (arguments.length == 2) {
                final boolean expectsToBeSet = condition == extracted;
                final PsiElement candidate = expectsToBeSet ? first : second;
                final PsiElement alternative = expectsToBeSet ? second : first;
                if (candidate instanceof ArrayAccessExpression && PhpLanguageUtil.isNull(alternative)) {
                    final ArrayAccessExpression access = (ArrayAccessExpression) candidate;
                    final PsiElement container = access.getValue();
                    if (container != null && OpenapiEquivalenceUtil.areEqual(container, arguments[1])) {
                        final ArrayIndex index = access.getIndex();
                        if (index != null) {
                            final PsiElement key = index.getValue();
                            if (key != null && OpenapiEquivalenceUtil.areEqual(key, arguments[0])) {
                                return String.format("%s ?? %s", String.format(this.wrap(candidate) ? "(%s)" : "%s", candidate.getText()), String.format(this.wrap(alternative) ? "(%s)" : "%s", alternative.getText()));
                            }
                        }
                    }
                }
            }
            return null;
        }

        @Nullable
        private String generateReplacementForIdentity(@NotNull PsiElement condition, @NotNull BinaryExpression extracted, @NotNull PsiElement first, @NotNull PsiElement second) {
            PsiElement subject = extracted.getLeftOperand();
            if (PhpLanguageUtil.isNull(subject)) {
                subject = extracted.getRightOperand();
            }
            if (subject != null) {
                final IElementType operator = extracted.getOperationType();
                final boolean expectsToBeSet = (operator == PhpTokenTypes.opNOT_IDENTICAL && condition == extracted) || (operator == PhpTokenTypes.opIDENTICAL && condition != extracted);
                final PsiElement candidate = expectsToBeSet ? first : second;
                if (OpenapiEquivalenceUtil.areEqual(candidate, subject)) {
                    final PsiElement alternative = expectsToBeSet ? second : first;
                    return String.format("%s ?? %s", String.format(this.wrap(candidate) ? "(%s)" : "%s", candidate.getText()), String.format(this.wrap(alternative) ? "(%s)" : "%s", alternative.getText()));
                }
            }
            return null;
        }

        @Nullable
        private String generateReplacementForIsset(@NotNull PsiElement condition, @NotNull PhpIsset extracted, @NotNull PsiElement first, @Nullable PsiElement second) {
            final PsiElement subject = extracted.getVariables()[0];
            if (subject != null) {
                final boolean expectsToBeSet = condition == extracted;
                final PsiElement candidate = expectsToBeSet ? first : second;
                if (candidate != null && OpenapiEquivalenceUtil.areEqual(candidate, subject)) {
                    final PsiElement alternative = expectsToBeSet ? second : first;
                    return String.format("%s ?? %s", String.format(this.wrap(candidate) ? "(%s)" : "%s", candidate.getText()), String.format(this.wrap(alternative) ? "(%s)" : "%s", alternative == null ? "null" : alternative.getText()));
                }
            }
            return null;
        }

        @Nullable
        private String generateReplacementForPropertyAccess(@NotNull PsiElement condition, @NotNull PsiElement extracted, @NotNull PsiElement first, @Nullable PsiElement second) {
            final boolean expectsToBeNotEmpty = condition == extracted;
            final PsiElement candidate = expectsToBeNotEmpty ? first : second;
            if (candidate instanceof FieldReference) {
                final FieldReference fieldReference = (FieldReference) candidate;
                final PsiElement base = fieldReference.getClassReference();
                if (base != null && OpenapiEquivalenceUtil.areEqual(extracted, base)) {
                    final PhpType resolved = OpenapiResolveUtil.resolveType(fieldReference, holder.getProject());
                    if (resolved != null && !resolved.filterUnknown().isEmpty()) {
                        final PsiElement alternative = expectsToBeNotEmpty ? second : first;
                        final boolean isNullable = resolved.filterUnknown().getTypes().stream().map(Types::getType).anyMatch(t -> t.equals(Types.strNull));
                        if (!isNullable || (alternative == null || PhpLanguageUtil.isNull(alternative))) {
                            return String.format("%s ?? %s", String.format(this.wrap(fieldReference) ? "(%s)" : "%s", fieldReference.getText()), String.format(this.wrap(alternative) ? "(%s)" : "%s", alternative == null ? "null" : alternative.getText()));
                        }
                    }
                }
            }
            return null;
        }

        @Nullable
        private String generateReplacementForPropertyAccess(@NotNull PsiElement condition, @NotNull PhpEmpty extracted, @NotNull PsiElement first, @Nullable PsiElement second) {
            final PsiElement subject = extracted.getVariables()[0];
            if (subject != null) {
                final boolean expectsToBeNotEmpty = condition != extracted;
                final PsiElement candidate = expectsToBeNotEmpty ? first : second;
                if (candidate instanceof FieldReference) {
                    final PsiElement reference = ((FieldReference) candidate).getClassReference();
                    if (reference != null && OpenapiEquivalenceUtil.areEqual(subject, reference)) {
                        final PsiElement alternative = expectsToBeNotEmpty ? second : first;
                        return String.format("%s ?? %s", String.format(this.wrap(candidate) ? "(%s)" : "%s", candidate.getText()), String.format(this.wrap(alternative) ? "(%s)" : "%s", alternative == null ? "null" : alternative.getText()));
                    }
                }
            }
            return null;
        }

        @Nullable
        private PsiElement getTargetCondition(@NotNull PsiElement condition) {
            /* un-wrap inverted conditions */
            if (condition instanceof UnaryExpression) {
                final UnaryExpression unary = (UnaryExpression) condition;
                if (OpenapiTypesUtil.is(unary.getOperation(), PhpTokenTypes.opNOT)) {
                    condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(unary.getValue());
                }
            }
            /* do check */
            if (condition instanceof Variable || condition instanceof ArrayAccessExpression || condition instanceof FieldReference) {
                return condition;
            } else if (condition instanceof PhpIsset) {
                final PhpIsset isset = (PhpIsset) condition;
                if (isset.getVariables().length == 1) {
                    return condition;
                }
            } else if (condition instanceof PhpEmpty) {
                final PhpEmpty empty = (PhpEmpty) condition;
                if (empty.getVariables().length == 1) {
                    return condition;
                }
            } else if (condition instanceof BinaryExpression) {
                final BinaryExpression binary = (BinaryExpression) condition;
                final IElementType operator = binary.getOperationType();
                if (operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opNOT_IDENTICAL) {
                    if (PhpLanguageUtil.isNull(binary.getRightOperand())) {
                        return condition;
                    } else if (PhpLanguageUtil.isNull(binary.getLeftOperand())) {
                        return condition;
                    }
                }
            } else if (OpenapiTypesUtil.isFunctionReference(condition)) {
                final String functionName = ((FunctionReference) condition).getName();
                if (functionName != null && functionName.equals("array_key_exists")) {
                    return condition;
                }
            }
            return null;
        }

        /* first pair: what to drop, second positive and negative branching values */
        private Couple<Couple<PsiElement>> extract(@NotNull If statement) {
            Couple<Couple<PsiElement>> result = new Couple<>(new Couple<>(null, null), new Couple<>(null, null));
            final GroupStatement ifBody = ExpressionSemanticUtil.getGroupStatement(statement);
            if (ifBody != null && ExpressionSemanticUtil.countExpressionsInGroup(ifBody) == 1) {
                final PsiElement ifLast = this.extractCandidate(ExpressionSemanticUtil.getLastStatement(ifBody));
                if (ifLast != null) {
                    /* extract all related constructs */
                    final PsiElement ifNext = this.extractCandidate(statement.getNextPsiSibling());
                    final PsiElement ifPrevious = this.extractCandidate(statement.getPrevPsiSibling());
                    if (statement.getElseBranch() != null) {
                        PsiElement elseLast = null;
                        final GroupStatement elseBody = ExpressionSemanticUtil.getGroupStatement(statement.getElseBranch());
                        if (elseBody != null && ExpressionSemanticUtil.countExpressionsInGroup(elseBody) == 1) {
                            elseLast = this.extractCandidate(ExpressionSemanticUtil.getLastStatement(elseBody));
                        }
                        /* if - return - else - return */
                        if (ifLast instanceof PhpReturn && elseLast instanceof PhpReturn) {
                            result = new Couple<>(new Couple<>(statement, statement), new Couple<>(((PhpReturn) ifLast).getArgument(), ((PhpReturn) elseLast).getArgument()));
                        } else /* if - assign - else - assign */
                        if (ifLast instanceof AssignmentExpression && elseLast instanceof AssignmentExpression) {
                            final AssignmentExpression ifAssignment = (AssignmentExpression) ifLast;
                            final AssignmentExpression elseAssignment = (AssignmentExpression) elseLast;
                            final PsiElement ifContainer = ifAssignment.getVariable();
                            final PsiElement elseContainer = elseAssignment.getVariable();
                            if (ifContainer instanceof Variable && elseContainer instanceof Variable) {
                                final boolean isTarget = OpenapiEquivalenceUtil.areEqual(ifContainer, elseContainer);
                                if (isTarget) {
                                    result = new Couple<>(new Couple<>(statement, statement), new Couple<>(ifAssignment.getValue(), elseAssignment.getValue()));
                                }
                            }
                        }
                    } else {
                        /* assign - if - assign */
                        if (ifPrevious instanceof AssignmentExpression && ifLast instanceof AssignmentExpression) {
                            final AssignmentExpression previousAssignment = (AssignmentExpression) ifPrevious;
                            final AssignmentExpression ifAssignment = (AssignmentExpression) ifLast;
                            final PsiElement previousContainer = previousAssignment.getVariable();
                            final PsiElement ifContainer = ifAssignment.getVariable();
                            if (previousContainer instanceof Variable && ifContainer instanceof Variable) {
                                final boolean isTarget = OpenapiEquivalenceUtil.areEqual(previousContainer, ifContainer);
                                /* false-positives: assignment by value */
                                if (isTarget && !OpenapiTypesUtil.isAssignmentByReference(previousAssignment)) {
                                    final PsiElement previousValue = previousAssignment.getValue();
                                    if (!(previousValue instanceof AssignmentExpression)) {
                                        /* false-positives: assignment of processed container value */
                                        final boolean isContainerProcessing = PsiTreeUtil.findChildrenOfType(previousValue, previousContainer.getClass()).stream().anyMatch(c -> OpenapiEquivalenceUtil.areEqual(c, previousContainer));
                                        if (!isContainerProcessing) {
                                            result = new Couple<>(new Couple<>(ifPrevious.getParent(), statement), new Couple<>(ifAssignment.getValue(), previousValue));
                                        }
                                    }
                                }
                            }
                        } else /* if - return - return */
                        if (ifLast instanceof PhpReturn && ifNext instanceof PhpReturn) {
                            result = new Couple<>(new Couple<>(statement, ifNext), new Couple<>(((PhpReturn) ifLast).getArgument(), ((PhpReturn) ifNext).getArgument()));
                        } else /* if - return - [end-of-function] */
                        if (ifLast instanceof PhpReturn && ifNext == null && statement.getNextPsiSibling() == null) {
                            final boolean isInFunction = statement.getParent().getParent() instanceof Function;
                            if (isInFunction) {
                                result = new Couple<>(new Couple<>(statement, statement), new Couple<>(((PhpReturn) ifLast).getArgument(), null));
                            }
                        }
                    }
                }
            }
            return result;
        }

        @Nullable
        private PsiElement extractCandidate(@Nullable PsiElement statement) {
            if (statement instanceof PhpReturn) {
                return statement;
            } else if (OpenapiTypesUtil.isStatementImpl(statement)) {
                final PsiElement possiblyAssignment = statement.getFirstChild();
                if (OpenapiTypesUtil.isAssignment(possiblyAssignment)) {
                    final AssignmentExpression assignment = (AssignmentExpression) possiblyAssignment;
                    final PsiElement container = assignment.getVariable();
                    if (container instanceof Variable) {
                        return assignment;
                    }
                }
            }
            return null;
        }
    };
}
Also used : Couple(com.intellij.openapi.util.Couple) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) IElementType(com.intellij.psi.tree.IElementType) Project(com.intellij.openapi.project.Project) Nullable(org.jetbrains.annotations.Nullable) NotNull(org.jetbrains.annotations.NotNull)

Example 58 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class MagicMethodsValidityInspector method buildVisitor.

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

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            final PhpClass clazz = method.getContainingClass();
            final String methodName = method.getName();
            final PsiElement nameNode = NamedElementUtil.getNameIdentifier(method);
            if (clazz == null || nameNode == null || !methodName.startsWith("_") || method.isAbstract()) {
                return;
            }
            switch(methodName) {
                case "__construct":
                    CanNotBeStaticStrategy.apply(method, holder);
                    CanNotReturnTypeStrategy.apply(method, holder);
                    if (!this.isTestContext(clazz)) {
                        NormallyCallsParentMethodStrategy.apply(method, holder);
                    }
                    break;
                case "__destruct":
                case "__clone":
                    CanNotBeStaticStrategy.apply(method, holder);
                    CanNotReturnTypeStrategy.apply(method, holder);
                    CanNotTakeArgumentsStrategy.apply(method, holder);
                    NormallyCallsParentMethodStrategy.apply(method, holder);
                    break;
                case "__get":
                case "__isset":
                case "__unset":
                    TakesExactAmountOfArgumentsStrategy.apply(1, method, holder);
                    CanNotBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    CanNotTakeArgumentsByReferenceStrategy.apply(method, holder);
                    HasAlsoMethodStrategy.apply(method, "__set", holder);
                    break;
                case "__set":
                case "__call":
                    TakesExactAmountOfArgumentsStrategy.apply(2, method, holder);
                    CanNotBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    CanNotTakeArgumentsByReferenceStrategy.apply(method, holder);
                    if (methodName.equals("__set")) {
                        HasAlsoMethodStrategy.apply(method, "__isset", holder);
                        HasAlsoMethodStrategy.apply(method, "__get", holder);
                    }
                    break;
                case "__callStatic":
                    TakesExactAmountOfArgumentsStrategy.apply(2, method, holder);
                    MustBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    CanNotTakeArgumentsByReferenceStrategy.apply(method, holder);
                    break;
                case "__toString":
                    CanNotBeStaticStrategy.apply(method, holder);
                    CanNotTakeArgumentsStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    MustReturnSpecifiedTypeStrategy.apply(stringType, method, holder);
                    break;
                case "__debugInfo":
                    CanNotBeStaticStrategy.apply(method, holder);
                    CanNotTakeArgumentsStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    MustReturnSpecifiedTypeStrategy.apply(arrayOrNullType, method, holder);
                    MinimalPhpVersionStrategy.apply(method, holder, PhpLanguageLevel.PHP560);
                    break;
                case "__set_state":
                    TakesExactAmountOfArgumentsStrategy.apply(1, method, holder);
                    MustBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    final PhpType returnTypes = (new PhpType()).add(clazz.getFQN()).add(Types.strStatic);
                    MustReturnSpecifiedTypeStrategy.apply(returnTypes, method, holder);
                    break;
                case "__invoke":
                    CanNotBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    break;
                case "__wakeup":
                    CanNotBeStaticStrategy.apply(method, holder);
                    CanNotTakeArgumentsStrategy.apply(method, holder);
                    CanNotReturnTypeStrategy.apply(method, holder);
                    break;
                case "__unserialize":
                    CanNotBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    TakesExactAmountOfArgumentsStrategy.apply(1, method, holder);
                    CanNotReturnTypeStrategy.apply(method, holder);
                    break;
                case "__sleep":
                case "__serialize":
                    CanNotBeStaticStrategy.apply(method, holder);
                    MustBePublicStrategy.apply(method, holder);
                    CanNotTakeArgumentsStrategy.apply(method, holder);
                    MustReturnSpecifiedTypeStrategy.apply(arrayType, method, holder);
                    break;
                case "__autoload":
                    TakesExactAmountOfArgumentsStrategy.apply(1, method, holder);
                    CanNotReturnTypeStrategy.apply(method, holder);
                    holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageUseSplAutoloading), ProblemHighlightType.LIKE_DEPRECATED);
                    break;
                default:
                    if (methodName.startsWith("__") && !knownNonMagic.contains(methodName)) {
                        holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageNotMagic));
                    } else {
                        MissingUnderscoreStrategy.apply(method, holder);
                    }
                    break;
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) Method(com.jetbrains.php.lang.psi.elements.Method) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull)

Example 59 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class IssetConstructsCanBeMergedInspector method buildVisitor.

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

        @Override
        public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
            final IElementType operator = expression.getOperationType();
            if (operator != null && (operator == PhpTokenTypes.opAND || operator == PhpTokenTypes.opOR)) {
                /* false-positives: part of another condition */
                final PsiElement parent = expression.getParent();
                final PsiElement context = parent instanceof ParenthesizedExpression ? parent.getParent() : parent;
                if (context instanceof BinaryExpression && ((BinaryExpression) context).getOperationType() == operator) {
                    return;
                }
                final List<PsiElement> fragments = this.extract(expression, operator);
                if (fragments.size() > 1) {
                    if (operator == PhpTokenTypes.opAND) {
                        /* handle isset && isset ... */
                        PsiElement firstHit = null;
                        int hitsCount = 0;
                        for (final PsiElement fragment : fragments) {
                            if (fragment instanceof PhpIsset) {
                                if (++hitsCount > 1 && firstHit != null) {
                                    fragments.remove(firstHit);
                                    fragments.remove(fragment);
                                    holder.registerProblem(fragment, MessagesPresentationUtil.prefixWithEa(messageIsset), new MergeConstructsFix(holder.getProject(), expression, fragments, (PhpIsset) firstHit, (PhpIsset) fragment, operator));
                                    break;
                                }
                                firstHit = firstHit == null ? fragment : firstHit;
                            }
                        }
                    } else {
                        /* handle !isset || !isset ... */
                        PsiElement firstHit = null;
                        int hitsCount = 0;
                        for (final PsiElement fragment : fragments) {
                            if (fragment instanceof UnaryExpression) {
                                final PsiElement candidate = ((UnaryExpression) fragment).getValue();
                                if (candidate instanceof PhpIsset) {
                                    if (++hitsCount > 1 && firstHit != null) {
                                        fragments.remove(firstHit.getParent());
                                        fragments.remove(fragment);
                                        holder.registerProblem(candidate, MessagesPresentationUtil.prefixWithEa(messageIvertedIsset), new MergeConstructsFix(holder.getProject(), expression, fragments, (PhpIsset) firstHit, (PhpIsset) candidate, operator));
                                        break;
                                    }
                                    firstHit = firstHit == null ? candidate : firstHit;
                                }
                            }
                        }
                    }
                }
                fragments.clear();
            }
        }

        @NotNull
        private List<PsiElement> extract(@NotNull BinaryExpression binary, @Nullable IElementType operator) {
            final List<PsiElement> result = new ArrayList<>();
            if (binary.getOperationType() == operator) {
                Stream.of(binary.getLeftOperand(), binary.getRightOperand()).filter(Objects::nonNull).map(ExpressionSemanticUtil::getExpressionTroughParenthesis).forEach(expression -> {
                    if (expression instanceof BinaryExpression) {
                        result.addAll(this.extract((BinaryExpression) expression, operator));
                    } else {
                        result.add(expression);
                    }
                });
            } else {
                result.add(binary);
            }
            return result;
        }
    };
}
Also used : ParenthesizedExpression(com.jetbrains.php.lang.psi.elements.ParenthesizedExpression) ArrayList(java.util.ArrayList) UnaryExpression(com.jetbrains.php.lang.psi.elements.UnaryExpression) NotNull(org.jetbrains.annotations.NotNull) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) Objects(java.util.Objects) PsiElement(com.intellij.psi.PsiElement) Nullable(org.jetbrains.annotations.Nullable) PhpIsset(com.jetbrains.php.lang.psi.elements.PhpIsset) NotNull(org.jetbrains.annotations.NotNull)

Example 60 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class RedundantElseClauseInspector method buildVisitor.

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

        @Override
        public void visitPhpIf(@NotNull If expression) {
            /* context expectations: not if-else-if-* constructs */
            if (expression.getParent() instanceof Else) {
                return;
            }
            /* general construct expectations: body wrapped into {} and has alternative branches */
            final GroupStatement ifBody = ExpressionSemanticUtil.getGroupStatement(expression);
            if (ifBody == null || !ExpressionSemanticUtil.hasAlternativeBranches(expression)) {
                return;
            }
            if (!OpenapiTypesUtil.is(ifBody.getFirstChild(), PhpTokenTypes.chLBRACE)) {
                return;
            }
            /* collect alternative branches for reporting and QF binding */
            final List<PhpPsiElement> alternativeBranches = new ArrayList<>(Arrays.asList(expression.getElseIfBranches()));
            final Else elseBranch = expression.getElseBranch();
            if (elseBranch != null) {
                alternativeBranches.add(elseBranch);
            }
            /* alternative branch expectations */
            final PhpPsiElement alternative = alternativeBranches.get(0);
            final GroupStatement alternativeBody = ExpressionSemanticUtil.getGroupStatement(alternative);
            if (alternative instanceof ElseIf && alternativeBody == null) {
                alternativeBranches.clear();
                return;
            }
            if (alternative instanceof Else && alternativeBody == null && !(alternative.getFirstPsiChild() instanceof If)) {
                alternativeBranches.clear();
                return;
            }
            /* analyze last statement in if and report if matched inspection pattern */
            final PsiElement lastStatement = ExpressionSemanticUtil.getLastStatement(ifBody);
            if (lastStatement != null) {
                final boolean isExitStatement = lastStatement.getFirstChild() instanceof PhpExit;
                final boolean isReturnPoint = isExitStatement || lastStatement instanceof PhpReturn || lastStatement instanceof PhpContinue || lastStatement instanceof PhpBreak || OpenapiTypesUtil.isThrowExpression(lastStatement);
                if (isReturnPoint) {
                    holder.registerProblem(alternative.getFirstChild(), MessagesPresentationUtil.prefixWithEa(alternative instanceof Else ? messageElse : messageElseif), new UnnecessaryElseFixer());
                }
            }
            alternativeBranches.clear();
        }
    };
}
Also used : ArrayList(java.util.ArrayList) NotNull(org.jetbrains.annotations.NotNull) UnnecessaryElseFixer(com.kalessil.phpStorm.phpInspectionsEA.fixers.UnnecessaryElseFixer) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)169 NotNull (org.jetbrains.annotations.NotNull)169 PsiElement (com.intellij.psi.PsiElement)157 FunctionReference (com.jetbrains.php.lang.psi.elements.FunctionReference)43 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)40 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)39 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)37 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)33 IElementType (com.intellij.psi.tree.IElementType)32 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)29 Project (com.intellij.openapi.project.Project)25 BinaryExpression (com.jetbrains.php.lang.psi.elements.BinaryExpression)25 HashSet (java.util.HashSet)24 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)22 MessagesPresentationUtil (com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil)19 StringLiteralExpression (com.jetbrains.php.lang.psi.elements.StringLiteralExpression)18 ArrayList (java.util.ArrayList)18 Set (java.util.Set)18 Nullable (org.jetbrains.annotations.Nullable)18 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)17