From 1ff37a0c307a13c99d7f560c0cbef7991c5aa457 Mon Sep 17 00:00:00 2001 From: "Anna.Kozlova" Date: Wed, 15 Mar 2017 17:24:03 +0100 Subject: [PATCH] usage of inaccessible api: show language level where the api appeared; quickfix to increase language level (IDEA-168695) --- .../Java15APIUsageInspectionBase.java | 50 +++++++++++-------- .../FunctionalInterfaceSuggester.java | 2 +- .../completion/JavaCompletionUtil.java | 2 +- .../analysis/IncreaseLanguageLevelFix.java | 22 ++++++-- .../usage1.5/overrideAnnotation/expected.xml | 2 +- .../inspections/Java15FormInspection.java | 2 +- 6 files changed, 50 insertions(+), 30 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java index 7d3f82a2212b..1e1edf8938d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java15api/Java15APIUsageInspectionBase.java @@ -21,6 +21,7 @@ import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.EffectiveLanguageLevelUtil; @@ -233,7 +234,8 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement()); if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - if (isForbiddenApiUsage((PsiMember)resolved, languageLevel)) { + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember)resolved, languageLevel); + if (sinceLanguageLevel != null) { PsiClass psiClass = null; final PsiElement qualifier = reference.getQualifier(); if (qualifier != null) { @@ -250,7 +252,7 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo if (isIgnored(superClass)) return; } } - registerError(reference, languageLevel); + registerError(reference, sinceLanguageLevel); } else if (resolved instanceof PsiClass && isInProject(reference)&& !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { final PsiReferenceParameterList parameterList = reference.getParameterList(); if (parameterList != null && parameterList.getTypeParameterElements().length > 0) { @@ -296,8 +298,9 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); if (constructor instanceof PsiCompiledElement) { - if (isForbiddenApiUsage(constructor, languageLevel)) { - registerError(expression.getClassReference(), languageLevel); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); + if (sinceLanguageLevel != null) { + registerError(expression.getClassReference(), sinceLanguageLevel); } } } @@ -309,12 +312,14 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo PsiAnnotation annotation = !method.isConstructor() ? AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) : null; if (annotation != null) { final Module module = ModuleUtilCore.findModuleForPsiElement(annotation); + LanguageLevel sinceLanguageLevel = null; if (module != null) { final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); final PsiMethod[] methods = method.findSuperMethods(); for (PsiMethod superMethod : methods) { if (superMethod instanceof PsiCompiledElement) { - if (!isForbiddenApiUsage(superMethod, languageLevel)) { + sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); + if (sinceLanguageLevel == null) { return; } } @@ -323,7 +328,7 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo } } if (methods.length > 0) { - registerError(annotation.getNameReferenceElement(), languageLevel); + registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); } } } @@ -337,7 +342,9 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo private void registerError(PsiJavaCodeReferenceElement reference, LanguageLevel api) { if (reference != null && isInProject(reference)) { //noinspection DialogTitleCapitalization - myHolder.registerProblem(reference, InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api))); + myHolder.registerProblem(reference, + InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api)), + myOnTheFly ? new LocalQuickFix[] {(LocalQuickFix)QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(LanguageLevel.values()[api.ordinal() + 1])} : null); } } } @@ -347,33 +354,32 @@ public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTo return presentableText.substring(0, presentableText.indexOf(' ')); } - public static boolean isForbiddenApiUsage(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { - if (member instanceof PsiAnonymousClass) return false; + public static LanguageLevel getLastIncompatibleLanguageLevel(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { + if (member instanceof PsiAnonymousClass) return null; PsiClass containingClass = member.getContainingClass(); - if (containingClass instanceof PsiAnonymousClass) return false; - if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return false; + if (containingClass instanceof PsiAnonymousClass) return null; + if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return null; - return isForbiddenSignature(member, languageLevel) || - containingClass != null && isForbiddenApiUsage(containingClass, languageLevel); - - } - - private static boolean isForbiddenSignature(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) { Set forbiddenApi = getForbiddenApi(languageLevel); String signature = getSignature(member); - return forbiddenApi != null && signature != null && isForbiddenSignature(signature, languageLevel, forbiddenApi); + if (forbiddenApi != null && signature != null) { + LanguageLevel lastIncompatibleLanguageLevel = getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); + if (lastIncompatibleLanguageLevel != null) return lastIncompatibleLanguageLevel; + } + return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; + } - private static boolean isForbiddenSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set forbiddenApi) { + private static LanguageLevel getLastIncompatibleLanguageLevelForSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set forbiddenApi) { if (forbiddenApi.contains(signature)) { - return true; + return languageLevel; } if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { - return false; + return null; } LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); - return nextForbiddenApi != null && isForbiddenSignature(signature, nextLanguageLevel, nextForbiddenApi); + return nextForbiddenApi != null ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) : null; } /** diff --git a/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java index 27c06e42df1c..be15abead8b3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java +++ b/java/java-impl/src/com/intellij/codeInsight/FunctionalInterfaceSuggester.java @@ -144,7 +144,7 @@ public class FunctionalInterfaceSuggester { final Project project = element.getProject(); final Set types = new HashSet<>(); final Processor consumer = member -> { - if (member instanceof PsiClass && !Java15APIUsageInspectionBase.isForbiddenApiUsage(member, PsiUtil.getLanguageLevel(element))) { + if (member instanceof PsiClass && Java15APIUsageInspectionBase.getLastIncompatibleLanguageLevel(member, PsiUtil.getLanguageLevel(element)) == null) { if (!JavaResolveUtil.isAccessible(member, null, member.getModifierList(), element, null, null)) { return true; } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java index 0f3714a768a1..a450028e9869 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java @@ -481,7 +481,7 @@ public class JavaCompletionUtil { private static boolean shouldMarkRed(@NotNull Object object, @NotNull PsiElement place) { if (!(object instanceof PsiMember)) return false; - if (Java15APIUsageInspectionBase.isForbiddenApiUsage((PsiMember)object, PsiUtil.getLanguageLevel(place))) return true; + if (Java15APIUsageInspectionBase.getLastIncompatibleLanguageLevel((PsiMember)object, PsiUtil.getLanguageLevel(place)) != null) return true; if (object instanceof PsiEnumConstant) { return findConstantsUsedInSwitch(place).contains(CompletionUtil.getOriginalOrSelf((PsiEnumConstant)object)); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java index 760ad0fa4959..bc8ccb263837 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java @@ -17,6 +17,8 @@ package com.intellij.codeInsight.daemon.impl.analysis; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; @@ -29,13 +31,14 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author cdr */ -public class IncreaseLanguageLevelFix implements IntentionAction { +public class IncreaseLanguageLevelFix implements IntentionAction, LocalQuickFix { private static final Logger LOG = Logger.getInstance("#" + IncreaseLanguageLevelFix.class.getName()); private final LanguageLevel myLevel; @@ -50,12 +53,25 @@ public class IncreaseLanguageLevelFix implements IntentionAction { return CodeInsightBundle.message("set.language.level.to.0", myLevel.getPresentableText()); } + @Nls + @NotNull + @Override + public String getName() { + return getText(); + } + @Override @NotNull public String getFamilyName() { return CodeInsightBundle.message("set.language.level"); } + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + invoke(project, null, element.getContainingFile()); + } + @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { final VirtualFile virtualFile = file.getVirtualFile(); @@ -67,9 +83,7 @@ public class IncreaseLanguageLevelFix implements IntentionAction { @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - final VirtualFile virtualFile = file.getVirtualFile(); - LOG.assertTrue(virtualFile != null); - final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + final Module module = ModuleUtilCore.findModuleForPsiElement(file); if (module == null) return; JavaProjectModelModificationService.getInstance(project).changeLanguageLevel(module, myLevel); diff --git a/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml b/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml index bd67b3f7a82c..6502e2c42b21 100644 --- a/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml +++ b/java/java-tests/testData/inspection/usage1.5/overrideAnnotation/expected.xml @@ -4,7 +4,7 @@ Test.java 4 Usages of API documented as @since 1.5 (1.6|1.7) - Usage of API documented as @since 1.7+ + Usage of API documented as @since 1.8+ \ No newline at end of file diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java b/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java index 53ea8102d5b6..ffeab244f131 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/inspections/Java15FormInspection.java @@ -58,7 +58,7 @@ public class Java15FormInspection extends BaseFormInspection { final PsiMethod getter = PropertyUtil.findPropertyGetter(aClass, prop.getName(), false, true); if (getter == null) continue; final LanguageLevel languageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); - if (Java15APIUsageInspection.isForbiddenApiUsage(getter, languageLevel)) { + if (Java15APIUsageInspection.getLastIncompatibleLanguageLevel(getter, languageLevel) != null) { registerError(component, collector, prop, "@since " + Java15APIUsageInspection.getShortName(languageLevel)); } }