Search in sources :

Example 1 with PhpLanguageLevel

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

the class ReturnTypeCanBeDeclaredInspector method buildVisitor.

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

        /* TODO: support functions - see https://github.com/kalessil/phpinspectionsea/pull/320 */
        @Override
        public void visitPhpMethod(@NotNull Method method) {
            final PhpLanguageLevel php = PhpLanguageLevel.get(holder.getProject());
            if (php.atLeast(PhpLanguageLevel.PHP700) && !magicMethods.contains(method.getName())) {
                final boolean isTarget = OpenapiElementsUtil.getReturnType(method) == null;
                if (isTarget) {
                    final PsiElement methodNameNode = NamedElementUtil.getNameIdentifier(method);
                    if (methodNameNode != null) {
                        final boolean supportNullableTypes = php.atLeast(PhpLanguageLevel.PHP710);
                        if (method.isAbstract()) {
                            this.handleAbstractMethod(method, methodNameNode, supportNullableTypes);
                        } else {
                            this.handleMethod(method, methodNameNode, supportNullableTypes);
                        }
                    }
                }
            }
        }

        private void handleAbstractMethod(@NotNull Method method, @NotNull PsiElement target, boolean supportNullableTypes) {
            final PhpDocComment docBlock = method.getDocComment();
            if (docBlock != null && docBlock.getReturnTag() != null) {
                this.handleMethod(method, target, supportNullableTypes);
            }
        }

        private void handleMethod(@NotNull Method method, @NotNull PsiElement target, boolean supportNullableTypes) {
            /* suggest nothing when the type is only partially resolved */
            final PhpType resolvedReturnType = OpenapiResolveUtil.resolveType(method, holder.getProject());
            if (resolvedReturnType == null) {
                return;
            } else if (resolvedReturnType.hasUnknown()) {
                /* adding class interface leading to promise-type for interface method */
                boolean isInfluencedByInterface = resolvedReturnType.size() == 2 && resolvedReturnType.filterUnknown().size() == 1;
                if (!isInfluencedByInterface) {
                    return;
                }
            }
            /* ignore DocBlock, resolve and normalize types instead (DocBlock is involved, but nevertheless) */
            final Set<String> normalizedTypes = resolvedReturnType.filterUnknown().getTypes().stream().map(Types::getType).collect(Collectors.toSet());
            this.checkUnrecognizedGenerator(method, normalizedTypes);
            this.checkReturnStatements(method, normalizedTypes);
            final boolean isVoidAvailable = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP710);
            final int typesCount = normalizedTypes.size();
            /* case 1: offer using void */
            if (supportNullableTypes && typesCount == 0 && isVoidAvailable) {
                final PsiElement firstReturn = PsiTreeUtil.findChildOfType(method, PhpReturn.class);
                if (firstReturn == null || ExpressionSemanticUtil.getScope(firstReturn) != method) {
                    final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(Types.strVoid);
                    final String message = messagePattern.replace("%t%", Types.strVoid).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                    holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                }
            }
            /* case 2: offer using type */
            if (1 == typesCount) {
                final String singleType = normalizedTypes.iterator().next();
                final String suggestedType = isVoidAvailable && voidTypes.contains(singleType) ? Types.strVoid : this.compactType(singleType, method);
                final boolean isLegitBasic = singleType.startsWith("\\") || returnTypes.contains(singleType) || suggestedType.equals("self") || suggestedType.equals("static");
                final boolean isLegitVoid = !isLegitBasic && supportNullableTypes && suggestedType.equals(Types.strVoid);
                if (isLegitBasic || isLegitVoid) {
                    /* false-positive: '@return static' which is gets resolved into current class since 2019.2 */
                    final PhpDocComment docBlock = method.getDocComment();
                    final PhpDocReturnTag tag = docBlock == null ? null : docBlock.getReturnTag();
                    final boolean isStatic = tag != null && Arrays.stream(tag.getChildren()).map(PsiElement::getText).filter(t -> !t.isEmpty()).allMatch(t -> t.equals("static"));
                    final boolean isLegitStatic = isStatic && PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP800);
                    if (!isStatic || isLegitStatic) {
                        final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(isLegitStatic ? "static" : suggestedType);
                        final String message = messagePattern.replace("%t%", isLegitStatic ? "static" : suggestedType).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                        holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                    }
                }
            }
            /* case 3: offer using nullable type */
            if (supportNullableTypes && 2 == typesCount && normalizedTypes.contains(Types.strNull)) {
                normalizedTypes.remove(Types.strNull);
                final String nullableType = normalizedTypes.iterator().next();
                final String suggestedType = isVoidAvailable && voidTypes.contains(nullableType) ? Types.strVoid : compactType(nullableType, method);
                final boolean isLegitNullable = nullableType.startsWith("\\") || returnTypes.contains(nullableType) || suggestedType.equals("self");
                final boolean isLegitVoid = !isLegitNullable && suggestedType.equals(Types.strVoid);
                if (isLegitNullable || isLegitVoid) {
                    final String typeHint = isLegitVoid ? suggestedType : '?' + suggestedType;
                    final LocalQuickFix fixer = this.isMethodOverridden(method) ? null : new DeclareReturnTypeFix(typeHint);
                    final String message = messagePattern.replace("%t%", typeHint).replace("%n%", fixer == null ? " (please use change signature intention to fix this)" : "");
                    holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), fixer);
                }
            }
        }

        /* use change signature intention promoter */
        private boolean isMethodOverridden(@NotNull Method method) {
            boolean result = false;
            final PhpClass clazz = method.getContainingClass();
            if (clazz != null && !clazz.isFinal() && !method.isFinal() && !method.getAccess().isPrivate()) {
                final String methodName = method.getName();
                result = InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).stream().anyMatch(c -> c != clazz && c.findOwnMethodByName(methodName) != null) || OpenapiResolveUtil.resolveChildClasses(clazz.getFQN(), PhpIndex.getInstance(holder.getProject())).stream().anyMatch(c -> c.findOwnMethodByName(methodName) != null);
            }
            return result;
        }

        @NotNull
        private String compactType(@NotNull String type, @NotNull Method method) {
            String result = null;
            if (type.startsWith("\\") || type.equals("static")) {
                /* Strategy 1: respect `@return self` */
                if (LOOKUP_PHPDOC_RETURN_DECLARATIONS) {
                    final PhpDocComment phpDoc = method.getDocComment();
                    final PhpDocReturnTag phpReturn = phpDoc == null ? null : phpDoc.getReturnTag();
                    if (phpReturn != null) {
                        final boolean hasSelfReference = PsiTreeUtil.findChildrenOfType(phpReturn, PhpDocType.class).stream().anyMatch(t -> {
                            final String text = t.getText();
                            return text.equals("self") || text.equals("$this");
                        });
                        if (hasSelfReference) {
                            result = "self";
                        }
                    }
                }
                /* be sure to send back static for avoiding false-positives */
                result = (result == null && type.equals("static")) ? type : result;
                /* Strategy 2: scan imports */
                if (result == null) {
                    PsiElement groupCandidate = method.getContainingClass();
                    groups: while (groupCandidate != null && !(groupCandidate instanceof PsiFile)) {
                        if (groupCandidate instanceof GroupStatement) {
                            final List<PhpUse> imports = new ArrayList<>();
                            /* scan for imports in current group statement */
                            for (final PsiElement child : groupCandidate.getChildren()) {
                                if (child instanceof PhpUseList) {
                                    Collections.addAll(imports, ((PhpUseList) child).getDeclarations());
                                }
                            }
                            /* iterate imports and search for targets */
                            for (final PhpUse imported : imports) {
                                final PhpReference useReference = imported.getTargetReference();
                                if (useReference instanceof ClassReference && type.equals(useReference.getFQN())) {
                                    final String useAlias = imported.getAliasName();
                                    result = useAlias == null ? useReference.getName() : useAlias;
                                    imports.clear();
                                    break groups;
                                }
                            }
                            imports.clear();
                        }
                        groupCandidate = groupCandidate.getParent();
                    }
                }
                /* Strategy 3: relative QN for classes in sub-namespace */
                if (result == null || result.isEmpty()) {
                    final PhpClass clazz = method.getContainingClass();
                    final String nameSpace = null == clazz ? null : clazz.getNamespaceName();
                    if (nameSpace != null && nameSpace.length() > 1 && type.startsWith(nameSpace)) {
                        result = type.replace(nameSpace, "");
                    }
                }
            }
            return result == null ? type : result;
        }

        private void checkUnrecognizedGenerator(@NotNull Method method, @NotNull Set<String> types) {
            if (!types.contains("\\Generator")) {
                final PhpYield yield = PsiTreeUtil.findChildOfType(method, PhpYield.class);
                if (yield != null && ExpressionSemanticUtil.getScope(yield) == method) {
                    types.add("\\Generator");
                    if (PsiTreeUtil.findChildOfType(method, PhpReturn.class) == null) {
                        types.remove(Types.strNull);
                    }
                }
            }
        }

        private void checkReturnStatements(@NotNull Method method, @NotNull Set<String> types) {
            if (!types.isEmpty() && !method.isAbstract()) {
                /* non-implicit null return: omitted last return statement */
                if (!types.contains(Types.strNull) && !types.contains(Types.strVoid)) {
                    final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
                    final PsiElement last = body == null ? null : ExpressionSemanticUtil.getLastStatement(body);
                    if (!(last instanceof PhpReturn) && !OpenapiTypesUtil.isThrowExpression(last)) {
                        types.add(Types.strNull);
                    }
                }
                /* buggy parameter type resolving: no type, but null as default value */
                if (types.size() == 1 && types.contains(Types.strNull)) {
                    final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(method);
                    if (body != null) {
                        final PhpReturn expression = PsiTreeUtil.findChildOfType(body, PhpReturn.class);
                        if (expression != null) {
                            final PsiElement value = ExpressionSemanticUtil.getReturnValue(expression);
                            if (value != null && !PhpLanguageUtil.isNull(value)) {
                                types.remove(Types.strNull);
                            }
                        }
                    }
                }
            }
        }
    };
}
Also used : PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) java.util(java.util) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) PhpDocType(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType) PhpIndex(com.jetbrains.php.PhpIndex) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) PhpDocReturnTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag) ProblemDescriptor(com.intellij.codeInspection.ProblemDescriptor) PsiElement(com.intellij.psi.PsiElement) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) Project(com.intellij.openapi.project.Project) PsiFile(com.intellij.psi.PsiFile) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpPsiElementFactory(com.jetbrains.php.lang.psi.PhpPsiElementFactory) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) Collectors(java.util.stream.Collectors) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) NotNull(org.jetbrains.annotations.NotNull) javax.swing(javax.swing) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PhpDocReturnTag(com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag) PsiFile(com.intellij.psi.PsiFile) PsiElement(com.intellij.psi.PsiElement) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) NotNull(org.jetbrains.annotations.NotNull)

Example 2 with PhpLanguageLevel

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

the class DeprecatedIniOptionsInspector method buildVisitor.

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

        @Override
        public void visitPhpFunctionCall(@NotNull final FunctionReference reference) {
            final String functionName = reference.getName();
            if (functionName != null && targetFunctions.contains(functionName)) {
                final PsiElement[] arguments = reference.getParameters();
                if (arguments.length > 0 && arguments[0] instanceof StringLiteralExpression) {
                    final String directive = ((StringLiteralExpression) arguments[0]).getContents().toLowerCase();
                    if (options.containsKey(directive)) {
                        final PhpLanguageLevel php = PhpLanguageLevel.get(holder.getProject());
                        final Triple<PhpLanguageLevel, PhpLanguageLevel, String> details = options.get(directive);
                        final PhpLanguageLevel removalVersion = details.getMiddle();
                        final PhpLanguageLevel deprecationVersion = details.getLeft();
                        if (removalVersion != null && php.atLeast(removalVersion)) {
                            final String alternative = details.getRight();
                            holder.registerProblem(arguments[0], String.format(MessagesPresentationUtil.prefixWithEa(alternative == null ? patternRemoved : patternRemovedWithAlternative), directive, removalVersion.getVersion(), alternative));
                        } else if (deprecationVersion != null && php.atLeast(deprecationVersion)) {
                            final String alternative = details.getRight();
                            holder.registerProblem(arguments[0], String.format(MessagesPresentationUtil.prefixWithEa(alternative == null ? patternDeprecated : patternDeprecatedWithAlternative), directive, deprecationVersion.getVersion(), alternative), ProblemHighlightType.LIKE_DEPRECATED);
                        }
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) StringLiteralExpression(com.jetbrains.php.lang.psi.elements.StringLiteralExpression) FunctionReference(com.jetbrains.php.lang.psi.elements.FunctionReference) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) PhpLanguageLevel(com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

PsiElement (com.intellij.psi.PsiElement)2 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)2 PhpLanguageLevel (com.kalessil.phpStorm.phpInspectionsEA.openApi.PhpLanguageLevel)2 NotNull (org.jetbrains.annotations.NotNull)2 LocalQuickFix (com.intellij.codeInspection.LocalQuickFix)1 ProblemDescriptor (com.intellij.codeInspection.ProblemDescriptor)1 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)1 Project (com.intellij.openapi.project.Project)1 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)1 PsiFile (com.intellij.psi.PsiFile)1 PsiWhiteSpace (com.intellij.psi.PsiWhiteSpace)1 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)1 PhpIndex (com.jetbrains.php.PhpIndex)1 PhpDocComment (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment)1 PhpDocType (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType)1 PhpDocReturnTag (com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocReturnTag)1 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)1 PhpPsiElementFactory (com.jetbrains.php.lang.psi.PhpPsiElementFactory)1 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)1 FunctionReference (com.jetbrains.php.lang.psi.elements.FunctionReference)1