Search in sources :

Example 21 with PsiElementVisitor

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

the class IssetArgumentExistenceInspector method buildVisitor.

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

        @Override
        public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
            final PsiElement candidate = expression.getLeftOperand();
            if (candidate instanceof Variable && PhpTokenTypes.opCOALESCE == expression.getOperationType()) {
                final Variable variable = (Variable) candidate;
                if (!this.getSuppliedVariables(expression).contains(variable.getName())) {
                    analyzeExistence(variable);
                }
            }
        }

        @Override
        public void visitPhpEmpty(@NotNull PhpEmpty expression) {
            this.analyzeArgumentsExistence(expression.getVariables());
        }

        @Override
        public void visitPhpIsset(@NotNull PhpIsset expression) {
            this.analyzeArgumentsExistence(expression.getVariables());
        }

        private void analyzeArgumentsExistence(@NotNull PhpExpression[] arguments) {
            if (arguments.length > 0) {
                final Set<String> parameters = this.getSuppliedVariables(arguments[0]);
                for (final PhpExpression argument : arguments) {
                    if (argument instanceof Variable && !parameters.contains(argument.getName())) {
                        this.analyzeExistence((Variable) argument);
                    }
                }
                parameters.clear();
            }
        }

        private void analyzeExistence(@NotNull Variable variable) {
            final String variableName = variable.getName();
            if (!variableName.isEmpty() && !specialVariables.contains(variableName)) {
                final Function scope = ExpressionSemanticUtil.getScope(variable);
                final GroupStatement body = scope == null ? null : ExpressionSemanticUtil.getGroupStatement(scope);
                if (body != null) {
                    for (final Variable reference : PsiTreeUtil.findChildrenOfType(body, Variable.class)) {
                        if (reference.getName().equals(variableName)) {
                            boolean report = reference == variable;
                            if (!report) {
                                final PsiElement parent = reference.getParent();
                                if (parent instanceof AssignmentExpression) {
                                    report = PsiTreeUtil.findCommonParent(reference, variable) == parent;
                                }
                            }
                            if (report) {
                                /* variable created dynamically in a loop: hacky stuff, but nevertheless */
                                PsiElement loopCandidate = reference.getParent();
                                while (loopCandidate != null && loopCandidate != scope) {
                                    if (OpenapiTypesUtil.isLoop(loopCandidate)) {
                                        report = PsiTreeUtil.findChildrenOfType(loopCandidate, AssignmentExpression.class).stream().noneMatch(assignment -> {
                                            final PsiElement container = assignment.getVariable();
                                            return container instanceof Variable && ((Variable) container).getName().equals(variableName);
                                        });
                                        break;
                                    }
                                    loopCandidate = loopCandidate.getParent();
                                }
                                if (report && (IGNORE_INCLUDES || !this.hasIncludes(scope)) && !this.hasGotos(scope)) {
                                    holder.registerProblem(variable, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), variableName), ProblemHighlightType.GENERIC_ERROR);
                                }
                            }
                            break;
                        }
                    }
                }
            }
        }

        @NotNull
        private Set<String> getSuppliedVariables(@NotNull PsiElement expression) {
            final Set<String> result = new HashSet<>();
            final Function scope = ExpressionSemanticUtil.getScope(expression);
            if (scope != null) {
                for (final Parameter parameter : scope.getParameters()) {
                    result.add(parameter.getName());
                }
                final List<Variable> used = ExpressionSemanticUtil.getUseListVariables(scope);
                if (used != null && !used.isEmpty()) {
                    used.forEach(v -> result.add(v.getName()));
                    used.clear();
                }
            }
            return result;
        }

        private boolean hasIncludes(@NotNull Function function) {
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
            if (body != null) {
                return PsiTreeUtil.findChildOfType(body, Include.class) != null;
            }
            return false;
        }

        private boolean hasGotos(@NotNull Function function) {
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
            if (body != null) {
                return PsiTreeUtil.findChildOfType(body, PhpGoto.class) != null;
            }
            return false;
        }
    };
}
Also used : BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) ExpressionSemanticUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.ExpressionSemanticUtil) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) Set(java.util.Set) ExpressionCostEstimateUtil(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.utils.ExpressionCostEstimateUtil) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) HashSet(java.util.HashSet) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) OpenapiTypesUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiTypesUtil) List(java.util.List) MessagesPresentationUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil) PsiElement(com.intellij.psi.PsiElement) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) javax.swing(javax.swing) 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 22 with PsiElementVisitor

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

the class UnusedConstructorDependenciesInspector method buildVisitor.

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

        @NotNull
        private Map<String, Field> getPrivateFields(@NotNull PhpClass clazz) {
            final Map<String, Field> privateFields = new HashMap<>();
            for (final Field field : clazz.getOwnFields()) {
                if (!field.isConstant()) {
                    final PhpModifier modifiers = field.getModifier();
                    if (modifiers.isPrivate() && !modifiers.isStatic()) {
                        final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(field.getDocComment(), PhpDocTag.class);
                        final boolean annotated = tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
                        if (!annotated) {
                            privateFields.put(field.getName(), field);
                        }
                    }
                }
            }
            return privateFields;
        }

        @NotNull
        private Map<String, List<FieldReference>> getFieldReferences(@NotNull Method method, @NotNull Map<String, Field> privateFields) {
            final Map<String, List<FieldReference>> filteredReferences = new HashMap<>();
            if (!method.isAbstract()) {
                final Collection<FieldReference> references = PsiTreeUtil.findChildrenOfType(method, FieldReference.class);
                for (final FieldReference ref : references) {
                    final String fieldName = ref.getName();
                    if (fieldName != null && privateFields.containsKey(fieldName)) {
                        final PsiElement resolved = OpenapiResolveUtil.resolveReference(ref);
                        if (resolved == null || (resolved instanceof Field && privateFields.containsValue(resolved))) {
                            filteredReferences.computeIfAbsent(fieldName, k -> new ArrayList<>()).add(ref);
                        }
                    }
                }
                references.clear();
            }
            return filteredReferences;
        }

        @NotNull
        private Map<String, List<FieldReference>> getMethodsFieldReferences(@NotNull PhpClass clazz, @NotNull Method constructor, @NotNull Map<String, Field> privateFields) {
            final Map<String, List<FieldReference>> filteredReferences = new HashMap<>();
            /* collect methods to inspect, incl. once from traits */
            final List<Method> methodsToCheck = new ArrayList<>();
            Collections.addAll(methodsToCheck, clazz.getOwnMethods());
            for (final PhpClass trait : clazz.getTraits()) {
                Collections.addAll(methodsToCheck, trait.getOwnMethods());
            }
            /* find references */
            for (final Method method : methodsToCheck) {
                if (method != constructor) {
                    final Map<String, List<FieldReference>> innerReferences = getFieldReferences(method, privateFields);
                    if (!innerReferences.isEmpty()) {
                        /* merge method's scan results into common container */
                        innerReferences.forEach((fieldName, references) -> {
                            filteredReferences.computeIfAbsent(fieldName, name -> new ArrayList<>()).addAll(references);
                            references.clear();
                        });
                    }
                    innerReferences.clear();
                }
            }
            methodsToCheck.clear();
            return filteredReferences;
        }

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            /* filter classes which needs to be analyzed */
            final PhpClass clazz = method.getContainingClass();
            if (null == clazz || clazz.isInterface() || clazz.isTrait() || null == clazz.getOwnConstructor() || 0 == clazz.getOwnFields().length || 0 == clazz.getOwnMethods().length) {
                return;
            }
            /* run inspection only in constructors and if own private fields being defined */
            final Method constructor = clazz.getOwnConstructor();
            if (method != constructor) {
                return;
            }
            final Map<String, Field> clazzPrivateFields = this.getPrivateFields(clazz);
            if (clazzPrivateFields.isEmpty()) {
                return;
            }
            /* === intensive part : extract references === */
            final Map<String, List<FieldReference>> constructorsReferences = getFieldReferences(constructor, clazzPrivateFields);
            if (!constructorsReferences.isEmpty()) {
                /* constructor's references being identified */
                final Map<String, List<FieldReference>> otherReferences = getMethodsFieldReferences(clazz, constructor, clazzPrivateFields);
                /* methods's references being identified, time to re-visit constructor's references */
                constructorsReferences.forEach((fieldName, references) -> {
                    /* field is not used, report in constructor, IDE detects unused fields */
                    if (!otherReferences.containsKey(fieldName)) {
                        /* ensure the field reference in constructor is not bound to closures only */
                        if (references.stream().anyMatch(r -> ExpressionSemanticUtil.getScope(r) == constructor)) {
                            this.doReport(holder, references);
                        }
                    }
                    references.clear();
                });
                /* release references found in the methods */
                otherReferences.values().forEach(List::clear);
                otherReferences.clear();
            }
            constructorsReferences.clear();
        }

        private void doReport(@NotNull ProblemsHolder holder, @NotNull List<FieldReference> fields) {
            fields.stream().filter(reference -> {
                final PsiElement parent = reference.getParent();
                if (OpenapiTypesUtil.isAssignment(parent)) {
                    return reference == ((AssignmentExpression) parent).getVariable();
                }
                return false;
            }).forEach(reference -> holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(message)));
        }
    };
}
Also used : java.util(java.util) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) ExpressionSemanticUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.ExpressionSemanticUtil) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) 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) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) NotNull(org.jetbrains.annotations.NotNull) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpDocTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 23 with PsiElementVisitor

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

the class LoopWhichDoesNotLoopInspector method buildVisitor.

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

        @Override
        public void visitPhpForeach(@NotNull ForeachStatement loop) {
            final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
            if (!isBlade && this.isNotLooping(loop)) {
                /* false-positive: return first element from generator, iterable and co */
                final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(loop);
                final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
                if (last != null && !OpenapiTypesUtil.isThrowExpression(last)) {
                    final PsiElement source = loop.getArray();
                    if (source instanceof PhpTypedElement) {
                        final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) source, holder.getProject());
                        final boolean isValid = resolved != null && resolved.filterUnknown().getTypes().stream().anyMatch(type -> foreachExceptions.contains(Types.getType(type)));
                        if (isValid) {
                            return;
                        }
                    }
                }
                holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
            }
        }

        @Override
        public void visitPhpFor(@NotNull For loop) {
            final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
            if (!isBlade && this.isNotLooping(loop)) {
                holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
            }
        }

        @Override
        public void visitPhpWhile(@NotNull While loop) {
            final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
            if (!isBlade && this.isNotLooping(loop)) {
                holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
            }
        }

        @Override
        public void visitPhpDoWhile(@NotNull DoWhile loop) {
            final boolean isBlade = holder.getFile().getName().endsWith(".blade.php");
            if (!isBlade && this.isNotLooping(loop)) {
                holder.registerProblem(loop.getFirstChild(), MessagesPresentationUtil.prefixWithEa(message));
            }
        }

        private boolean isNotLooping(@NotNull PhpPsiElement loop) {
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(loop);
            if (null == body) {
                return false;
            }
            final PsiElement lastExpression = ExpressionSemanticUtil.getLastStatement(body);
            final boolean isLoopTerminatedWithLastExpression = lastExpression instanceof PhpBreak || lastExpression instanceof PhpReturn || OpenapiTypesUtil.isThrowExpression(lastExpression);
            /* loop is empty or terminates on first iteration */
            if (null != lastExpression && !isLoopTerminatedWithLastExpression) {
                return false;
            }
            /* detect continue statements, which makes the loop looping */
            for (final PhpContinue expression : PsiTreeUtil.findChildrenOfType(body, PhpContinue.class)) {
                int nestingLevel = 0;
                PsiElement parent = expression.getParent();
                while (null != parent && !(parent instanceof Function) && !(parent instanceof PsiFile)) {
                    if (OpenapiTypesUtil.isLoop(parent)) {
                        ++nestingLevel;
                        if (parent == loop) {
                            /* extract level of continuation from the statement */
                            int continueLevel = 1;
                            final PsiElement argument = expression.getArgument();
                            if (null != argument) {
                                try {
                                    continueLevel = Integer.parseInt(argument.getText());
                                } catch (final NumberFormatException notParsed) {
                                    continueLevel = 1;
                                }
                            }
                            /* matched continue for the current loop */
                            if (continueLevel == nestingLevel) {
                                return false;
                            }
                        }
                    }
                    parent = parent.getParent();
                }
            }
            return true;
        }
    };
}
Also used : BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) Set(java.util.Set) HashSet(java.util.HashSet) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) PsiElement(com.intellij.psi.PsiElement) PsiFile(com.intellij.psi.PsiFile) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiFile(com.intellij.psi.PsiFile) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 24 with PsiElementVisitor

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

the class CallableParameterUseCaseInTypeContextInspection method buildVisitor.

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

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            if (!this.isTestContext(method)) {
                this.inspectUsages(method.getParameters(), method);
            }
        }

        @Override
        public void visitPhpFunction(@NotNull Function function) {
            if (!this.isTestContext(function)) {
                this.inspectUsages(function.getParameters(), function);
            }
        }

        private void inspectUsages(@NotNull Parameter[] parameters, @NotNull PhpScopeHolder scopeHolder) {
            final Project project = holder.getProject();
            final PhpIndex index = PhpIndex.getInstance(project);
            final PhpEntryPointInstruction entryPoint = scopeHolder.getControlFlow().getEntryPoint();
            for (final Parameter parameter : parameters) {
                /* normalize parameter types, skip analysis when mixed or object appears */
                final Set<String> paramTypes = new HashSet<>();
                final PhpType parameterType = OpenapiResolveUtil.resolveType(parameter, project);
                if (parameterType != null) {
                    label: for (final String type : parameterType.filterUnknown().getTypes()) {
                        final String typeNormalized = Types.getType(type);
                        switch(typeNormalized) {
                            case Types.strMixed:
                            case Types.strObject:
                                paramTypes.clear();
                                break label;
                            case Types.strCallable:
                                paramTypes.add(Types.strArray);
                                paramTypes.add(Types.strString);
                                paramTypes.add("\\Closure");
                                break;
                            case Types.strIterable:
                                paramTypes.add(Types.strArray);
                                paramTypes.add("\\Traversable");
                                break;
                        }
                        paramTypes.add(typeNormalized);
                    }
                }
                if (paramTypes.isEmpty()) {
                    continue;
                } else {
                    /* in some case PhpStorm is not recognizing default value as parameter type */
                    final PsiElement defaultValue = parameter.getDefaultValue();
                    if (defaultValue instanceof PhpTypedElement) {
                        final PhpType defaultType = OpenapiResolveUtil.resolveType((PhpTypedElement) defaultValue, project);
                        if (defaultType != null) {
                            defaultType.filterUnknown().getTypes().forEach(t -> paramTypes.add(Types.getType(t)));
                        }
                    }
                }
                /* false-positive: type is not resolved correctly, default null is taken */
                if (paramTypes.size() == 1 && paramTypes.contains(Types.strNull)) {
                    final PsiElement defaultValue = parameter.getDefaultValue();
                    if (PhpLanguageUtil.isNull(defaultValue)) {
                        continue;
                    }
                }
                /* now find instructions operating on the parameter and perform analysis */
                final String parameterName = parameter.getName();
                for (final PhpAccessVariableInstruction instruction : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(entryPoint, parameterName)) {
                    final PsiElement parent = instruction.getAnchor().getParent();
                    final PsiElement callCandidate = null == parent ? null : parent.getParent();
                    /* Case 1: check if is_* functions being used according to definitions */
                    if (OpenapiTypesUtil.isFunctionReference(callCandidate)) {
                        final FunctionReference functionCall = (FunctionReference) callCandidate;
                        final String functionName = functionCall.getName();
                        if (functionName == null) {
                            continue;
                        }
                        /* we expect that aliases usage has been fixed already */
                        final boolean isTypeAnnounced;
                        switch(functionName) {
                            case "is_array":
                                isTypeAnnounced = paramTypes.contains(Types.strArray) || paramTypes.contains(Types.strIterable);
                                break;
                            case "is_string":
                                isTypeAnnounced = paramTypes.contains(Types.strString);
                                break;
                            case "is_bool":
                                isTypeAnnounced = paramTypes.contains(Types.strBoolean);
                                break;
                            case "is_int":
                                isTypeAnnounced = paramTypes.contains(Types.strInteger) || paramTypes.contains(Types.strNumber);
                                break;
                            case "is_float":
                                isTypeAnnounced = paramTypes.contains(Types.strFloat) || paramTypes.contains(Types.strNumber);
                                break;
                            case "is_resource":
                                isTypeAnnounced = paramTypes.contains(Types.strResource);
                                break;
                            case "is_numeric":
                                if (paramTypes.contains(Types.strString)) {
                                    continue;
                                }
                                isTypeAnnounced = paramTypes.contains(Types.strNumber) || paramTypes.contains(Types.strFloat) || paramTypes.contains(Types.strInteger);
                                break;
                            case "is_callable":
                                isTypeAnnounced = paramTypes.contains(Types.strCallable) || paramTypes.contains(Types.strArray) || paramTypes.contains(Types.strString) || paramTypes.contains("\\Closure");
                                break;
                            case "is_object":
                                isTypeAnnounced = paramTypes.contains(Types.strObject) || paramTypes.contains(Types.strCallable) || paramTypes.stream().anyMatch(t -> classReferences.contains(t) || (t.startsWith("\\") && !t.equals("\\Closure")));
                                break;
                            case "is_a":
                                isTypeAnnounced = paramTypes.contains(Types.strObject) || paramTypes.contains(Types.strString) || paramTypes.stream().anyMatch(t -> (t.startsWith("\\") && !t.equals("\\Closure")) || classReferences.contains(t));
                                break;
                            default:
                                continue;
                        }
                        /* cases: call makes no sense, violation of defined types set */
                        if (!isTypeAnnounced) {
                            final PsiElement callParent = functionCall.getParent();
                            boolean isReversedCheck = false;
                            if (callParent instanceof UnaryExpression) {
                                final PsiElement operation = ((UnaryExpression) callParent).getOperation();
                                isReversedCheck = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
                            }
                            holder.registerProblem(functionCall, MessagesPresentationUtil.prefixWithEa(isReversedCheck ? messageNoSense : messageViolationInCheck));
                        }
                        continue;
                    }
                    /* Case 2: assignments violating parameter definition */
                    if (OpenapiTypesUtil.isAssignment(parent)) {
                        final AssignmentExpression assignment = (AssignmentExpression) parent;
                        final PhpPsiElement variable = assignment.getVariable();
                        final PhpPsiElement value = assignment.getValue();
                        if (variable instanceof Variable && value instanceof PhpTypedElement) {
                            final String variableName = variable.getName();
                            if (variableName != null && variableName.equals(parameterName)) {
                                final PhpType resolvedType = OpenapiResolveUtil.resolveType((PhpTypedElement) value, project);
                                final Set<String> resolved = new HashSet<>();
                                if (resolvedType != null) {
                                    resolvedType.filterUnknown().getTypes().forEach(t -> resolved.add(Types.getType(t)));
                                }
                                if (resolved.size() >= 2) {
                                    /* false-positives: core functions returning string|array & false|null */
                                    if (resolved.contains(Types.strString) || resolved.contains(Types.strArray)) {
                                        if (resolved.contains(Types.strBoolean)) {
                                            final boolean isFunctionCall = OpenapiTypesUtil.isFunctionReference(value);
                                            if (isFunctionCall) {
                                                resolved.remove(Types.strBoolean);
                                            }
                                        } else if (resolved.contains(Types.strNull)) {
                                            final boolean isFunctionCall = OpenapiTypesUtil.isFunctionReference(value);
                                            if (isFunctionCall) {
                                                resolved.remove(Types.strNull);
                                            }
                                        }
                                    } else /* false-positives: nullable objects */
                                    if (resolved.contains(Types.strNull)) {
                                        final boolean isNullableObject = paramTypes.stream().anyMatch(t -> classReferences.contains(t) || t.startsWith("\\") && !t.equals("\\Closure"));
                                        if (isNullableObject) {
                                            resolved.remove(Types.strNull);
                                        }
                                    }
                                }
                                resolved.remove(Types.strMixed);
                                for (String type : resolved) {
                                    /* translate static/self into FQNs */
                                    if (classReferences.contains(type)) {
                                        PsiElement valueExtract = value;
                                        /* ` = <whatever> ?? <method call>` support */
                                        if (valueExtract instanceof BinaryExpression) {
                                            final BinaryExpression binary = (BinaryExpression) valueExtract;
                                            if (binary.getOperationType() == PhpTokenTypes.opCOALESCE) {
                                                final PsiElement left = binary.getLeftOperand();
                                                if (left != null && OpenapiEquivalenceUtil.areEqual(variable, left)) {
                                                    final PsiElement right = binary.getRightOperand();
                                                    if (right != null) {
                                                        valueExtract = right;
                                                    }
                                                }
                                            }
                                        }
                                        /* method call lookup */
                                        if (valueExtract instanceof MethodReference) {
                                            final PsiElement base = valueExtract.getFirstChild();
                                            if (base instanceof ClassReference) {
                                                final PsiElement resolvedClass = OpenapiResolveUtil.resolveReference((ClassReference) base);
                                                if (resolvedClass instanceof PhpClass) {
                                                    type = ((PhpClass) resolvedClass).getFQN();
                                                }
                                            } else if (base instanceof PhpTypedElement) {
                                                final PhpType clazzTypes = OpenapiResolveUtil.resolveType((PhpTypedElement) base, project);
                                                if (clazzTypes != null) {
                                                    final Set<String> filteredTypes = clazzTypes.filterUnknown().getTypes().stream().map(Types::getType).filter(t -> t.startsWith("\\")).collect(Collectors.toSet());
                                                    final int filteredTypesCount = filteredTypes.size();
                                                    /* clear resolved class or interface + class */
                                                    if (filteredTypesCount == 1 || filteredTypesCount == 2) {
                                                        type = filteredTypes.iterator().next();
                                                    }
                                                    filteredTypes.clear();
                                                }
                                            }
                                        }
                                        /* translate static/self into FQNs didn't work, skip */
                                        if (classReferences.contains(type)) {
                                            continue;
                                        }
                                    }
                                    final boolean isViolation = !this.isTypeCompatibleWith(type, paramTypes, index);
                                    if (isViolation) {
                                        holder.registerProblem(value, String.format(MessagesPresentationUtil.prefixWithEa(patternViolationInAssignment), type));
                                        break;
                                    }
                                }
                                resolved.clear();
                            }
                        }
                    }
                }
                paramTypes.clear();
            }
        }

        private boolean isTypeCompatibleWith(@NotNull String type, @NotNull Collection<String> allowedTypes, @NotNull PhpIndex index) {
            /* first case: implicit match */
            if (allowedTypes.contains(type)) {
                return true;
            }
            /* second case: inherited classes/interfaces */
            final Set<String> possibleTypes = new HashSet<>();
            if (type.startsWith("\\")) {
                index.getAnyByFQN(type).forEach(clazz -> InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).forEach(c -> possibleTypes.add(c.getFQN())));
            }
            return !possibleTypes.isEmpty() && allowedTypes.stream().anyMatch(possibleTypes::contains);
        }
    };
}
Also used : PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) Collection(java.util.Collection) Set(java.util.Set) PhpIndex(com.jetbrains.php.PhpIndex) Collectors(java.util.stream.Collectors) HashSet(java.util.HashSet) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) Set(java.util.Set) HashSet(java.util.HashSet) PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpIndex(com.jetbrains.php.PhpIndex) Project(com.intellij.openapi.project.Project) Collection(java.util.Collection) NotNull(org.jetbrains.annotations.NotNull)

Example 25 with PsiElementVisitor

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

the class AutoloadingIssuesInspector method buildVisitor.

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

        @Override
        public void visitPhpFile(@NotNull PhpFile file) {
            final String fileName = file.getName();
            if (fileName.endsWith(".php") && !ignoredFiles.contains(fileName) && !laravelMigration.matcher(fileName).matches()) {
                final List<PhpClass> classes = new ArrayList<>();
                file.getTopLevelDefs().values().stream().filter(definition -> definition instanceof PhpClass).forEach(definition -> classes.add((PhpClass) definition));
                if (classes.size() == 1) {
                    final PhpClass clazz = classes.get(0);
                    final String className = clazz.getName();
                    /* PSR-0 classloading (Package_Subpackage_Class) naming */
                    String extractedClassName = className;
                    if (clazz.getFQN().lastIndexOf('\\') == 0 && extractedClassName.indexOf('_') != -1) {
                        extractedClassName = extractedClassName.substring(1 + extractedClassName.lastIndexOf('_'));
                    }
                    /* check the file name as per extraction compliant with PSR-0/PSR-4 standards */
                    final String expectedClassName = fileName.substring(0, fileName.indexOf('.'));
                    if (this.isBreakingPsrStandard(className, expectedClassName, extractedClassName) && !this.isWordpressStandard(className, fileName)) {
                        final PsiElement classNameNode = NamedElementUtil.getNameIdentifier(clazz);
                        if (classNameNode != null) {
                            holder.registerProblem(classNameNode, MessagesPresentationUtil.prefixWithEa(message));
                        }
                    }
                }
                classes.clear();
            }
        }

        private boolean isBreakingPsrStandard(@NotNull String className, @NotNull String expectedClassName, @NotNull String extractedClassName) {
            return !expectedClassName.equals(extractedClassName) && !expectedClassName.equals(className);
        }

        private boolean isWordpressStandard(@NotNull String className, @NotNull String fileName) {
            final String wordpressFileName = String.format("class-%s.php", className.toLowerCase().replaceAll("_", "-"));
            return fileName.endsWith(wordpressFileName);
        }
    };
}
Also used : PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) Collection(java.util.Collection) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) List(java.util.List) PhpFile(com.jetbrains.php.lang.psi.PhpFile) MessagesPresentationUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil) PsiElement(com.intellij.psi.PsiElement) NamedElementUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.NamedElementUtil) Pattern(java.util.regex.Pattern) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpFile(com.jetbrains.php.lang.psi.PhpFile) PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) ArrayList(java.util.ArrayList) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

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