Search in sources :

Example 16 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class ClassInStringContextStrategy method apply.

public static boolean apply(@Nullable PsiElement nonStringOperand, @NotNull ProblemsHolder holder, @NotNull PsiElement expression, @NotNull String classHasNoToStringMessage) {
    if (null == nonStringOperand) {
        return false;
    }
    final Set<String> resolvedTypes = new HashSet<>();
    if (nonStringOperand instanceof PhpTypedElement) {
        final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) nonStringOperand, holder.getProject());
        if (resolved != null) {
            resolved.filterUnknown().getTypes().forEach(t -> resolvedTypes.add(Types.getType(t)));
        }
    }
    if (!TypesSemanticsUtil.isNullableObjectInterface(resolvedTypes)) {
        resolvedTypes.clear();
        return false;
    }
    /* collect classes to check if __toString() is there */
    final PhpIndex index = PhpIndex.getInstance(holder.getProject());
    final List<PhpClass> listClasses = new ArrayList<>();
    resolvedTypes.stream().filter(fqn -> fqn.charAt(0) == '\\').forEach(fqn -> listClasses.addAll(OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(fqn, index)));
    resolvedTypes.clear();
    /* check methods, error on first one violated requirements */
    for (final PhpClass clazz : listClasses) {
        if (OpenapiResolveUtil.resolveMethod(clazz, "__toString") == null) {
            holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(classHasNoToStringMessage.replace("%class%", clazz.getFQN())), ProblemHighlightType.ERROR);
            break;
        }
    }
    /* terminate inspection, php will call __toString() */
    listClasses.clear();
    return true;
}
Also used : PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) Types(com.kalessil.phpStorm.phpInspectionsEA.utils.Types) TypesSemanticsUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.TypesSemanticsUtil) Set(java.util.Set) PhpIndex(com.jetbrains.php.PhpIndex) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) Nullable(org.jetbrains.annotations.Nullable) List(java.util.List) OpenapiResolveUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiResolveUtil) MessagesPresentationUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil) PsiElement(com.intellij.psi.PsiElement) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpIndex(com.jetbrains.php.PhpIndex) PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) ArrayList(java.util.ArrayList) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) HashSet(java.util.HashSet)

Example 17 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class IsEmptyFunctionUsageInspector method buildVisitor.

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

        @Override
        public void visitPhpEmpty(@NotNull PhpEmpty emptyExpression) {
            final PhpExpression[] values = emptyExpression.getVariables();
            if (values.length == 1) {
                final PsiElement subject = ExpressionSemanticUtil.getExpressionTroughParenthesis(values[0]);
                if (subject == null || subject instanceof ArrayAccessExpression) {
                    /* currently, php docs lacks of array structure notations, skip it */
                    return;
                }
                final PsiElement parent = emptyExpression.getParent();
                final PsiElement operation = parent instanceof UnaryExpression ? ((UnaryExpression) parent).getOperation() : null;
                final boolean isInverted = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
                /* extract types */
                final Set<String> resolvedTypes = new HashSet<>();
                if (subject instanceof PhpTypedElement) {
                    final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) subject, holder.getProject());
                    if (resolved != null) {
                        resolved.filterUnknown().getTypes().forEach(t -> resolvedTypes.add(Types.getType(t)));
                    }
                }
                /* Case 1: empty(array) - hidden logic - empty array */
                if (SUGGEST_TO_USE_COUNT_CHECK && this.isCountableType(resolvedTypes)) {
                    final String comparison = isInverted ? "!==" : "===";
                    final String replacement = ComparisonStyle.isRegular() ? String.format("count(%s) %s 0", subject.getText(), comparison) : String.format("0 %s count(%s)", comparison, subject.getText());
                    final PsiElement target = isInverted ? parent : emptyExpression;
                    holder.registerProblem(target, String.format(MessagesPresentationUtil.prefixWithEa(patternAlternative), replacement), new UseCountFix(replacement));
                    resolvedTypes.clear();
                    return;
                }
                /* case 2: nullable classes, nullable target core types */
                if (SUGGEST_TO_USE_NULL_COMPARISON && ((SUGGEST_NULL_COMPARISON_FOR_SCALARS && this.isNullableCoreType(resolvedTypes)) || TypesSemanticsUtil.isNullableObjectInterface(resolvedTypes))) {
                    /* false-positive: a field reference used in the subject expression */
                    PsiElement base = subject;
                    while (base instanceof PhpPsiElement) {
                        if (base instanceof FieldReference) {
                            break;
                        }
                        base = ((PhpPsiElement) base).getFirstPsiChild();
                    }
                    if (!(base instanceof FieldReference)) {
                        final String comparison = isInverted ? "!==" : "===";
                        final String replacement = ComparisonStyle.isRegular() ? String.format("%s %s null", subject.getText(), comparison) : String.format("null %s %s", comparison, subject.getText());
                        holder.registerProblem(isInverted ? parent : emptyExpression, String.format(MessagesPresentationUtil.prefixWithEa(patternAlternative), replacement), new CompareToNullFix(replacement));
                    }
                    resolvedTypes.clear();
                    return;
                }
                resolvedTypes.clear();
            }
            if (REPORT_EMPTY_USAGE) {
                holder.registerProblem(emptyExpression, MessagesPresentationUtil.prefixWithEa(messageDoNotUse));
            }
        }

        private boolean isCountableType(@NotNull Set<String> resolvedTypesSet) {
            if (!resolvedTypesSet.isEmpty()) {
                return resolvedTypesSet.stream().allMatch(t -> {
                    boolean isIterable = false;
                    if (t.equals(Types.strArray)) {
                        isIterable = true;
                    } else if (t.startsWith("\\")) {
                        final List<PhpClass> resolved = OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(t, PhpIndex.getInstance(holder.getProject()));
                        isIterable = resolved.stream().anyMatch(r -> InterfacesExtractUtil.getCrawlInheritanceTree(r, true).stream().anyMatch(c -> c.getFQN().equals("\\Countable")));
                    }
                    return isIterable;
                });
            }
            return false;
        }

        private boolean isNullableCoreType(@NotNull Set<String> resolvedTypesSet) {
            boolean result = false;
            if (resolvedTypesSet.size() == 2 && resolvedTypesSet.contains(Types.strNull)) {
                result = resolvedTypesSet.contains(Types.strInteger) || resolvedTypesSet.contains(Types.strFloat) || resolvedTypesSet.contains(Types.strBoolean) || resolvedTypesSet.contains(Types.strResource);
            }
            return result;
        }
    };
}
Also used : 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) UseSuggestedReplacementFixer(com.kalessil.phpStorm.phpInspectionsEA.fixers.UseSuggestedReplacementFixer) Set(java.util.Set) PhpIndex(com.jetbrains.php.PhpIndex) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) ComparisonStyle(com.kalessil.phpStorm.phpInspectionsEA.settings.ComparisonStyle) HashSet(java.util.HashSet) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) List(java.util.List) PsiElement(com.intellij.psi.PsiElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) javax.swing(javax.swing) Set(java.util.Set) HashSet(java.util.HashSet) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) List(java.util.List) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) NotNull(org.jetbrains.annotations.NotNull)

Example 18 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class PropertyInitializationFlawsInspector method buildVisitor.

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

        @Override
        public void visitPhpField(@NotNull Field field) {
            if (REPORT_DEFAULTS_FLAWS && !field.isConstant()) {
                final PhpClass clazz = field.getContainingClass();
                final PhpClass parentClazz = clazz == null ? null : OpenapiResolveUtil.resolveSuperClass(clazz);
                final Field originField = parentClazz == null ? null : OpenapiResolveUtil.resolveField(parentClazz, field.getName());
                final PsiElement fieldDefault = OpenapiResolveUtil.resolveDefaultValue(field);
                final PsiElement originDefault = originField == null ? null : OpenapiResolveUtil.resolveDefaultValue(originField);
                if (PhpLanguageUtil.isNull(fieldDefault)) {
                    /* false-positives: typed properties PS will take care of them */
                    if (!this.isNullableTypedProperty(field)) {
                        holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageDefaultNull), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new DropFieldDefaultValueFix());
                    }
                } else if (fieldDefault instanceof PhpPsiElement && originDefault instanceof PhpPsiElement) {
                    final boolean isDefaultDuplicate = !originField.getModifier().getAccess().isPrivate() && OpenapiEquivalenceUtil.areEqual(fieldDefault, originDefault);
                    if (isDefaultDuplicate) {
                        boolean report = true;
                        /* false-positives: classes reference are the same, but resolved to different classes */
                        final Set<String> originalClasses = this.findReferencedClasses(originDefault);
                        if (!originalClasses.isEmpty()) {
                            final Set<String> fieldClasses = this.findReferencedClasses(fieldDefault);
                            report = !originalClasses.addAll(fieldClasses);
                            fieldClasses.clear();
                            originalClasses.clear();
                        }
                        if (report) {
                            holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageSenselessWrite), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                        }
                    }
                }
            }
        }

        private boolean isNullableTypedProperty(@Nullable Field field) {
            if (field != null && PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP740)) {
                final PhpType resolved = OpenapiResolveUtil.resolveDeclaredType(field);
                return !resolved.isEmpty() && resolved.getTypes().stream().map(Types::getType).anyMatch(t -> t.equals(Types.strNull) || t.equals(Types.strMixed));
            }
            return false;
        }

        @NotNull
        private Set<String> findReferencedClasses(@NotNull PsiElement where) {
            return PsiTreeUtil.findChildrenOfType(where, ClassReference.class).stream().map(r -> {
                final PsiElement resolved = OpenapiResolveUtil.resolveReference(r);
                return resolved instanceof PhpClass ? ((PhpClass) resolved).getFQN() : null;
            }).collect(Collectors.toSet());
        }

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            /* configuration-based toggle */
            if (!REPORT_INIT_FLAWS) {
                return;
            }
            /* process only constructors with non-empty body */
            final PhpClass clazz = method.getContainingClass();
            if (null == clazz || !method.getName().equals("__construct") || clazz.isInterface() || clazz.isTrait()) {
                return;
            }
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
            if (null == body || 0 == ExpressionSemanticUtil.countExpressionsInGroup(body)) {
                return;
            }
            /* collect private properties with default values; stop inspection if none found */
            /* protected/public properties init in __construct can be bypassed, so defaults might have sense */
            final Map<String, PsiElement> propertiesToCheck = new HashMap<>();
            for (final Field field : clazz.getOwnFields()) {
                if (!field.isConstant()) {
                    final PhpModifier modifiers = field.getModifier();
                    if (modifiers.isPrivate() && !modifiers.isStatic()) {
                        final PsiElement defaultValue = OpenapiResolveUtil.resolveDefaultValue(field);
                        if (defaultValue instanceof PhpPsiElement && !PhpLanguageUtil.isNull(defaultValue)) {
                            propertiesToCheck.put(field.getName(), defaultValue);
                        } else {
                            propertiesToCheck.put(field.getName(), null);
                        }
                    }
                }
            }
            if (propertiesToCheck.isEmpty()) {
                return;
            }
            /* iterate 1st level instructions and analyze overriding properties */
            for (final PsiElement expression : body.getChildren()) {
                final PsiElement assignmentCandidate = expression.getFirstChild();
                if (!OpenapiTypesUtil.isAssignment(assignmentCandidate)) {
                    continue;
                }
                final AssignmentExpression assignment = (AssignmentExpression) assignmentCandidate;
                final PsiElement container = assignment.getVariable();
                final PsiElement value = assignment.getValue();
                if (container instanceof FieldReference && container.getFirstChild().getText().equals("$this")) {
                    final String overriddenProperty = ((FieldReference) container).getName();
                    if (null == value || null == overriddenProperty || !propertiesToCheck.containsKey(overriddenProperty)) {
                        continue;
                    }
                    final PsiElement fieldDefault = propertiesToCheck.get(overriddenProperty);
                    /* Pattern: written and default values are identical */
                    if ((null == fieldDefault && PhpLanguageUtil.isNull(value)) || (null != fieldDefault && OpenapiEquivalenceUtil.areEqual(value, fieldDefault))) {
                        /* false-positives: typed properties */
                        if (!this.isNullableTypedProperty(OpenapiResolveUtil.resolveField(clazz, overriddenProperty))) {
                            holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(messageSenselessWrite), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                        }
                        continue;
                    }
                    if (null == fieldDefault) {
                        continue;
                    }
                    /* false-positive: property is involved into generating new value */
                    boolean isPropertyReused = false;
                    for (final FieldReference candidate : PsiTreeUtil.findChildrenOfType(value, FieldReference.class)) {
                        if (OpenapiEquivalenceUtil.areEqual(container, candidate)) {
                            isPropertyReused = true;
                            break;
                        }
                    }
                    if (!isPropertyReused && REPORT_DEFAULTS_FLAWS) {
                        holder.registerProblem(fieldDefault, MessagesPresentationUtil.prefixWithEa(messageDefaultOverride), new DropFieldDefaultValueFix());
                    }
                }
            }
            propertiesToCheck.clear();
        }
    };
}
Also used : BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) Set(java.util.Set) HashMap(java.util.HashMap) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) Collectors(java.util.stream.Collectors) Nullable(org.jetbrains.annotations.Nullable) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) ProblemDescriptor(com.intellij.codeInspection.ProblemDescriptor) Map(java.util.Map) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) javax.swing(javax.swing) Set(java.util.Set) HashMap(java.util.HashMap) 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) Nullable(org.jetbrains.annotations.Nullable) NotNull(org.jetbrains.annotations.NotNull)

Example 19 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType 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 20 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType 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)

Aggregations

PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)56 PsiElement (com.intellij.psi.PsiElement)46 NotNull (org.jetbrains.annotations.NotNull)41 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)33 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)20 Project (com.intellij.openapi.project.Project)20 HashSet (java.util.HashSet)20 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)16 Nullable (org.jetbrains.annotations.Nullable)16 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)15 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)14 PhpIndex (com.jetbrains.php.PhpIndex)14 com.kalessil.phpStorm.phpInspectionsEA.utils (com.kalessil.phpStorm.phpInspectionsEA.utils)13 IElementType (com.intellij.psi.tree.IElementType)12 Set (java.util.Set)12 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)10 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)9 PhpTypedElement (com.jetbrains.php.lang.psi.elements.PhpTypedElement)9 InterfacesExtractUtil (com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil)8 Collectors (java.util.stream.Collectors)8