Search in sources :

Example 16 with BasePhpElementVisitor

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

the class SubStrShortHandUsageInspector 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 && substringFunctions.contains(functionName)) {
                final PsiElement[] arguments = reference.getParameters();
                if ((arguments.length == 3 || arguments.length == 4) && arguments[2] instanceof BinaryExpression) {
                    /* check if 3rd argument is "strlen($search) - strlen(...)": "strlen($search)" is not needed */
                    final BinaryExpression binary = (BinaryExpression) arguments[2];
                    if (binary.getOperationType() == PhpTokenTypes.opMINUS) {
                        final PsiElement left = binary.getLeftOperand();
                        final PsiElement right = binary.getRightOperand();
                        if (right != null && OpenapiTypesUtil.isFunctionReference(left)) {
                            final FunctionReference leftCall = (FunctionReference) left;
                            final String leftName = leftCall.getName();
                            if (leftName != null && lengthFunctions.contains(leftName)) {
                                final PsiElement[] leftArguments = leftCall.getParameters();
                                if (leftArguments.length == 1 && OpenapiEquivalenceUtil.areEqual(leftArguments[0], arguments[0])) {
                                    final PsiElement startOffset = arguments[1];
                                    if (OpenapiEquivalenceUtil.areEqual(right, startOffset)) {
                                        /* case: third parameter is not needed at all */
                                        holder.registerProblem(arguments[2], String.format(MessagesPresentationUtil.prefixWithEa(patternDropLength), arguments[2].getText()), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropThirdParameterFix(holder.getProject(), reference));
                                    } else if (OpenapiTypesUtil.isNumber(startOffset) && OpenapiTypesUtil.isNumber(right)) {
                                        try {
                                            int offset = Integer.parseInt(startOffset.getText()) - Integer.parseInt(right.getText());
                                            if (offset < 0) {
                                                /* case: third parameter can be simplified */
                                                holder.registerProblem(binary, String.format(MessagesPresentationUtil.prefixWithEa(patternSimplifyLength), offset), new SimplifyFix(String.valueOf(offset)));
                                            } else {
                                                /* case: third parameter is not needed at all */
                                                holder.registerProblem(arguments[2], String.format(MessagesPresentationUtil.prefixWithEa(patternDropLength), arguments[2].getText()), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropThirdParameterFix(holder.getProject(), reference));
                                            }
                                        } catch (final NumberFormatException expected) {
                                        // return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) FunctionReference(com.jetbrains.php.lang.psi.elements.FunctionReference) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 17 with BasePhpElementVisitor

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

the class BadExceptionsProcessingInspector method buildVisitor.

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

        @Override
        public void visitPhpTry(@NotNull Try tryStatement) {
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(tryStatement);
            final int expressionsCount = body == null ? 0 : ExpressionSemanticUtil.countExpressionsInGroup(body);
            if (expressionsCount > 3) {
                holder.registerProblem(tryStatement.getFirstChild(), MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%c%", String.valueOf(expressionsCount))));
            }
        }

        @Override
        public void visitPhpCatch(@NotNull Catch catchStatement) {
            final Variable variable = catchStatement.getException();
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(catchStatement);
            if (variable != null && body != null) {
                final String variableName = variable.getName();
                if (!StringUtils.isEmpty(variableName)) {
                    /* incomplete catch statement */
                    boolean isVariableUsed = false;
                    for (final Variable usedVariable : PsiTreeUtil.findChildrenOfType(body, Variable.class)) {
                        if (usedVariable.getName().equals(variableName)) {
                            isVariableUsed = true;
                            break;
                        }
                    }
                    if (!isVariableUsed) {
                        if (ExpressionSemanticUtil.countExpressionsInGroup(body) == 0) {
                            holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageFailSilently));
                        } else {
                            holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageChainedException));
                        }
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) GroupStatement(com.jetbrains.php.lang.psi.elements.GroupStatement) Variable(com.jetbrains.php.lang.psi.elements.Variable) Catch(com.jetbrains.php.lang.psi.elements.Catch) Try(com.jetbrains.php.lang.psi.elements.Try) NotNull(org.jetbrains.annotations.NotNull) NotNull(org.jetbrains.annotations.NotNull)

Example 18 with BasePhpElementVisitor

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

the class UnSafeIsSetOverArrayInspector method buildVisitor.

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

        @Override
        public void visitPhpIsset(@NotNull PhpIsset issetExpression) {
            /*
                 * if no parameters, we don't check;
                 * if multiple parameters, perhaps if-inspection fulfilled and isset's were merged
                 *
                 * TODO: still needs analysis regarding concatenations in indexes
                 */
            final PhpExpression[] arguments = issetExpression.getVariables();
            if (arguments.length != 1) {
                return;
            }
            /* gather context information */
            PsiElement issetParent = issetExpression.getParent();
            boolean issetInverted = false;
            if (issetParent instanceof UnaryExpression) {
                final PsiElement operator = ((UnaryExpression) issetParent).getOperation();
                if (OpenapiTypesUtil.is(operator, PhpTokenTypes.opNOT)) {
                    issetInverted = true;
                    issetParent = issetParent.getParent();
                }
            }
            boolean isResultStored = (issetParent instanceof AssignmentExpression || issetParent instanceof PhpReturn);
            /* false-positives:  ternaries using isset-or-null semantics, there array_key_exist can introduce bugs */
            final PsiElement conditionCandidate = issetInverted ? issetExpression.getParent() : issetExpression;
            boolean isTernaryCondition = issetParent instanceof TernaryExpression && conditionCandidate == ((TernaryExpression) issetParent).getCondition();
            if (isTernaryCondition) {
                final TernaryExpression ternary = (TernaryExpression) issetParent;
                final PsiElement nullCandidate = issetInverted ? ternary.getTrueVariant() : ternary.getFalseVariant();
                if (PhpLanguageUtil.isNull(nullCandidate)) {
                    return;
                }
            }
            /* do analyze  */
            final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(arguments[0]);
            if (argument == null) {
                return;
            }
            /* false positives: variables in template/global context - too unreliable */
            if (argument instanceof Variable && ExpressionSemanticUtil.getBlockScope(argument) == null) {
                return;
            }
            if (!(argument instanceof ArrayAccessExpression)) {
                if (argument instanceof FieldReference) {
                    /* if field is not resolved, it's probably dynamic and isset has a purpose */
                    final PsiReference referencedField = argument.getReference();
                    final PsiElement resolvedField = referencedField == null ? null : OpenapiResolveUtil.resolveReference(referencedField);
                    if (resolvedField == null || !(ExpressionSemanticUtil.getBlockScope(resolvedField) instanceof PhpClass)) {
                        return;
                    }
                }
                if (SUGGEST_TO_USE_NULL_COMPARISON) {
                    /* false-positives: finally, perhaps fallback to initialization in try */
                    if (PsiTreeUtil.getParentOfType(issetExpression, Finally.class) == null) {
                        final List<String> fragments = Arrays.asList(argument.getText(), issetInverted ? "===" : "!==", "null");
                        if (!ComparisonStyle.isRegular()) {
                            Collections.reverse(fragments);
                        }
                        final String replacement = String.join(" ", fragments);
                        holder.registerProblem(issetInverted ? issetExpression.getParent() : issetExpression, String.format(MessagesPresentationUtil.prefixWithEa(patternUseNullComparison), replacement), ProblemHighlightType.WEAK_WARNING, new CompareToNullFix(replacement));
                    }
                }
                return;
            }
            /* TODO: has method/function reference as index */
            if (REPORT_CONCATENATION_IN_INDEXES && !isResultStored && this.hasConcatenationAsIndex((ArrayAccessExpression) argument)) {
                holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(messageConcatenationInIndex));
                return;
            }
            if (SUGGEST_TO_USE_ARRAY_KEY_EXISTS && !isArrayAccess((ArrayAccessExpression) argument)) {
                holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(messageUseArrayKeyExists), ProblemHighlightType.WEAK_WARNING);
            }
        }

        /* checks if any of indexes is concatenation expression */
        /* TODO: iterator for array access expression */
        private boolean hasConcatenationAsIndex(@NotNull ArrayAccessExpression expression) {
            PsiElement expressionToInspect = expression;
            while (expressionToInspect instanceof ArrayAccessExpression) {
                final ArrayIndex index = ((ArrayAccessExpression) expressionToInspect).getIndex();
                if (index != null && index.getValue() instanceof BinaryExpression) {
                    final PsiElement operation = ((BinaryExpression) index.getValue()).getOperation();
                    if (operation != null && operation.getNode().getElementType() == PhpTokenTypes.opCONCAT) {
                        return true;
                    }
                }
                expressionToInspect = expressionToInspect.getParent();
            }
            return false;
        }

        // TODO: partially duplicates semanticalAnalysis.OffsetOperationsInspector.isContainerSupportsArrayAccess()
        private boolean isArrayAccess(@NotNull ArrayAccessExpression expression) {
            /* ok JB parses `$var[]= ...` always as array, lets make it working properly and report them later */
            final PsiElement container = expression.getValue();
            if (!(container instanceof PhpTypedElement)) {
                return false;
            }
            final Set<String> containerTypes = new HashSet<>();
            final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) container, holder.getProject());
            if (resolved != null) {
                resolved.filterUnknown().getTypes().forEach(t -> containerTypes.add(Types.getType(t)));
            }
            /* failed to resolve, don't try to guess anything */
            if (containerTypes.isEmpty()) {
                return false;
            }
            boolean supportsOffsets = false;
            for (final String typeToCheck : containerTypes) {
                /* assume is just null-ble declaration or we shall just rust to mixed */
                if (typeToCheck.equals(Types.strNull)) {
                    continue;
                }
                if (typeToCheck.equals(Types.strMixed)) {
                    supportsOffsets = true;
                    continue;
                }
                /* some of possible types are scalars, what's wrong */
                if (!StringUtils.isEmpty(typeToCheck) && typeToCheck.charAt(0) != '\\') {
                    supportsOffsets = false;
                    break;
                }
                /* assume class has what is needed, OffsetOperationsInspector should report if not */
                supportsOffsets = true;
            }
            containerTypes.clear();
            return supportsOffsets;
        }
    };
}
Also used : 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) PsiReference(com.intellij.psi.PsiReference) NotNull(org.jetbrains.annotations.NotNull)

Example 19 with BasePhpElementVisitor

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

the class ArrayIsListCanBeUsedInspector method buildVisitor.

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

        @Override
        public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
            final String functionName = reference.getName();
            if (functionName != null && (functionName.equals("array_keys") || functionName.equals("array_values"))) {
                final boolean isTargetVersion = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP800);
                if (isTargetVersion) {
                    final PsiElement[] arguments = reference.getParameters();
                    if (arguments.length == 1) {
                        final PsiElement context = reference.getParent();
                        if (context instanceof BinaryExpression) {
                            final BinaryExpression binary = (BinaryExpression) context;
                            final IElementType operator = binary.getOperationType();
                            if (OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(operator)) {
                                final PsiElement second = OpenapiElementsUtil.getSecondOperand(binary, reference);
                                if (functionName.equals("array_values")) {
                                    if (second != null && OpenapiEquivalenceUtil.areEqual(arguments[0], second)) {
                                        this.report(reference, operator, arguments[0]);
                                    }
                                } else if (functionName.equals("array_keys")) {
                                    if (OpenapiTypesUtil.isFunctionReference(second)) {
                                        final FunctionReference rightReference = (FunctionReference) second;
                                        final String rightFunctionName = rightReference.getName();
                                        final PsiElement[] rightArguments = rightReference.getParameters();
                                        if ((rightFunctionName != null && rightFunctionName.equals("range")) && rightArguments.length == 2) {
                                            final boolean rangeFromZero = OpenapiTypesUtil.isNumber(rightArguments[0]) && rightArguments[0].getText().equals("0");
                                            if (rangeFromZero && rightArguments[1] instanceof BinaryExpression) {
                                                final BinaryExpression rangeToBinary = (BinaryExpression) rightArguments[1];
                                                if (rangeToBinary.getOperationType() == PhpTokenTypes.opMINUS) {
                                                    final PsiElement right = rangeToBinary.getRightOperand();
                                                    final PsiElement left = rangeToBinary.getLeftOperand();
                                                    if (OpenapiTypesUtil.isNumber(right) && right.getText().equals("1") && OpenapiTypesUtil.isFunctionReference(left)) {
                                                        final FunctionReference leftReference = (FunctionReference) left;
                                                        final String leftName = leftReference.getName();
                                                        final PsiElement[] leftArguments = leftReference.getParameters();
                                                        if (leftName != null && leftName.equals("count") && leftArguments.length == 1) {
                                                            if (OpenapiEquivalenceUtil.areEqual(arguments[0], leftArguments[0])) {
                                                                this.report(reference, operator, arguments[0]);
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void report(@NotNull FunctionReference reference, @NotNull IElementType operator, @NotNull PsiElement argument) {
            final boolean listExpected = operator == PhpTokenTypes.opIDENTICAL || operator == PhpTokenTypes.opEQUAL;
            final String replacement = String.format("%s%sarray_is_list(%s)", listExpected ? "" : "!", reference.getImmediateNamespaceName(), argument.getText());
            holder.registerProblem(reference.getParent(), String.format(MessagesPresentationUtil.prefixWithEa(message), replacement), new UseArrayIsListFix(replacement));
        }
    };
}
Also used : IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) FunctionReference(com.jetbrains.php.lang.psi.elements.FunctionReference) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 20 with BasePhpElementVisitor

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

the class TypeUnsafeArraySearchInspector 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 && targetFunctions.contains(functionName)) {
                final PsiElement[] arguments = reference.getParameters();
                if (arguments.length == 2) {
                    /* false-positives: array of string literals */
                    if (arguments[1] instanceof ArrayCreationExpression) {
                        final PsiElement[] elements = arguments[1].getChildren();
                        if (elements.length > 0) {
                            final long validElementsCount = Arrays.stream(elements).filter(element -> OpenapiTypesUtil.is(element, PhpElementTypes.ARRAY_VALUE)).map(PsiElement::getFirstChild).filter(element -> element instanceof StringLiteralExpression).map(literal -> ((StringLiteralExpression) literal).getContents().trim()).filter(content -> !content.isEmpty() && !content.matches("^\\d+$")).count();
                            if (validElementsCount == elements.length) {
                                return;
                            }
                        }
                    }
                    /* false-positives: array and item types are complimentary */
                    if (arguments[0] instanceof PhpTypedElement && arguments[1] instanceof PhpTypedElement) {
                        final Project project = holder.getProject();
                        final PhpType arrayType = OpenapiResolveUtil.resolveType((PhpTypedElement) arguments[1], project);
                        final Set<String> arrayTypes = arrayType == null ? null : arrayType.filterUnknown().getTypes();
                        if (arrayTypes != null && arrayTypes.size() == 1) {
                            final PhpType itemType = OpenapiResolveUtil.resolveType((PhpTypedElement) arguments[0], project);
                            final Set<String> itemTypes = itemType == null ? null : itemType.filterUnknown().getTypes();
                            if (itemTypes != null && itemTypes.size() == 1) {
                                final boolean matching = this.areTypesMatching(itemTypes.iterator().next(), arrayTypes.iterator().next());
                                if (matching) {
                                    return;
                                }
                            }
                        }
                    }
                    /* general case: we need the third argument */
                    final String replacement = String.format("%s%s(%s, %s, true)", reference.getImmediateNamespaceName(), functionName, arguments[0].getText(), arguments[1].getText());
                    holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message), new MakeSearchTypeSensitiveFix(replacement));
                }
            }
        }

        private boolean areTypesMatching(@NotNull String itemType, @NotNull String arrayType) {
            boolean result = false;
            if (!itemType.isEmpty()) {
                result = arrayType.equals((itemType.charAt(0) == '\\' ? itemType : '\\' + itemType) + "[]");
            }
            return result;
        }
    };
}
Also used : Arrays(java.util.Arrays) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) UseSuggestedReplacementFixer(com.kalessil.phpStorm.phpInspectionsEA.fixers.UseSuggestedReplacementFixer) Set(java.util.Set) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) HashSet(java.util.HashSet) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) OpenapiTypesUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiTypesUtil) OpenapiResolveUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiResolveUtil) MessagesPresentationUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) FunctionReference(com.jetbrains.php.lang.psi.elements.FunctionReference) StringLiteralExpression(com.jetbrains.php.lang.psi.elements.StringLiteralExpression) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) PhpElementTypes(com.jetbrains.php.lang.parser.PhpElementTypes) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) ArrayCreationExpression(com.jetbrains.php.lang.psi.elements.ArrayCreationExpression) ArrayCreationExpression(com.jetbrains.php.lang.psi.elements.ArrayCreationExpression) StringLiteralExpression(com.jetbrains.php.lang.psi.elements.StringLiteralExpression) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) Project(com.intellij.openapi.project.Project) FunctionReference(com.jetbrains.php.lang.psi.elements.FunctionReference) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) 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