diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..aa47d44963 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,10 @@ +{ + "extraKnownMarketplaces": { + "consulo-skills": { + "source": { + "source": "github", + "repo": "consulo/claude-skills" + } + } + } +} diff --git a/.consulo/copyright/apache_2_license.xml b/.consulo/copyright/apache_2_license.xml new file mode 100644 index 0000000000..ea57dc4f82 --- /dev/null +++ b/.consulo/copyright/apache_2_license.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.consulo/copyright/profiles_settings.xml b/.consulo/copyright/profiles_settings.xml new file mode 100644 index 0000000000..fca50fe6e1 --- /dev/null +++ b/.consulo/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.consulo/runConfigurations/consulo_run_desktop_awt_fork.xml b/.consulo/runConfigurations/consulo_run_desktop_awt_fork.xml new file mode 100644 index 0000000000..7f3db43a1f --- /dev/null +++ b/.consulo/runConfigurations/consulo_run_desktop_awt_fork.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml new file mode 100644 index 0000000000..def6c87702 --- /dev/null +++ b/.github/workflows/build-plugin.yml @@ -0,0 +1,11 @@ +name: build-plugin + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + uses: consulo/shared-workflows/.github/workflows/build-plugin.yml@master diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml new file mode 100644 index 0000000000..01aa5e49a9 --- /dev/null +++ b/.github/workflows/claude-pr-review.yml @@ -0,0 +1,16 @@ +name: Claude PR Review + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + claude: + permissions: + contents: read + pull-requests: write + issues: write + uses: consulo/shared-workflows/.github/workflows/claude-pr-review.yml@master + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/source-sync.yml b/.github/workflows/source-sync.yml new file mode 100644 index 0000000000..199d57a556 --- /dev/null +++ b/.github/workflows/source-sync.yml @@ -0,0 +1,15 @@ +name: Source sync + +on: + push: + branches: [master] + workflow_dispatch: {} + +jobs: + sync: + if: ${{ github.repository_owner == 'consulo' }} + permissions: + contents: write + pull-requests: write + uses: consulo/shared-workflows/.github/workflows/source-sync.yml@master + secrets: inherit diff --git a/.gitignore b/.gitignore index c5f9d5aa97..9596f603dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ /.consulo !/.consulo/codeStyleSettings.xml +!/.consulo/copyright +!/.consulo/runConfigurations /out /target target/ -dependency-reduced-pom.xml \ No newline at end of file +dependency-reduced-pom.xml +plugin/sandbox +.claude/settings.local.json \ No newline at end of file diff --git a/extract/gson-impl/pom.xml b/extract/gson-impl/pom.xml index 843dcb7583..86adf53559 100644 --- a/extract/gson-impl/pom.xml +++ b/extract/gson-impl/pom.xml @@ -17,44 +17,74 @@ --> - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - consulo - arch.ide-api-provided - 3-SNAPSHOT - - + + consulo + arch.bind.java + 3-SNAPSHOT + + - - - consulo - https://maven.consulo.io/repository/snapshots/ - - true - interval:60 - - - + + + consulo + https://maven.consulo.dev/repository/snapshots/ + + true + interval:60 + + + - consulo.plugin - consulo.java-gson.impl - 3-SNAPSHOT - jar + consulo.plugin + consulo.java-gson.impl + 3-SNAPSHOT + jar - - - ${project.groupId} - consulo.java-java.language.impl - ${project.version} - + + + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + + consulo + consulo-language-impl + ${project.version} + provided + + + consulo + consulo-language-editor-ui-api + ${project.version} + provided + + + consulo + consulo-ide-api + ${project.version} + provided + + + ${project.groupId} + consulo.java-java.language.impl + ${project.version} + - - ${project.groupId} - consulo.javascript - ${project.version} - provided - - + + ${project.groupId} + consulo.json.jom.api + ${project.version} + provided + + \ No newline at end of file diff --git a/extract/gson-impl/src/main/java/consulo/java/impl/gson/GsonDescriptionByAnotherPsiElementProvider.java b/extract/gson-impl/src/main/java/consulo/java/impl/gson/GsonDescriptionByAnotherPsiElementProvider.java index fa5ab026e3..1b78a62ed6 100644 --- a/extract/gson-impl/src/main/java/consulo/java/impl/gson/GsonDescriptionByAnotherPsiElementProvider.java +++ b/extract/gson-impl/src/main/java/consulo/java/impl/gson/GsonDescriptionByAnotherPsiElementProvider.java @@ -27,19 +27,17 @@ import consulo.annotation.component.ExtensionImpl; import consulo.application.util.RecursionManager; import consulo.java.language.module.extension.JavaModuleExtension; -import consulo.java.language.module.util.JavaClassNames; -import consulo.json.validation.NativeArray; -import consulo.json.validation.descriptionByAnotherPsiElement.DescriptionByAnotherPsiElementProvider; -import consulo.json.validation.descriptor.JsonObjectDescriptor; -import consulo.json.validation.descriptor.JsonPropertyDescriptor; +import consulo.json.jom.validation.NativeArray; +import consulo.json.jom.validation.descriptionByAnotherPsiElement.DescriptionByAnotherPsiElementProvider; +import consulo.json.jom.validation.descriptor.JsonObjectDescriptor; +import consulo.json.jom.validation.descriptor.JsonPropertyDescriptor; import consulo.language.psi.scope.GlobalSearchScope; import consulo.module.extension.ModuleExtensionHelper; import consulo.project.Project; import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Map; @@ -63,36 +61,33 @@ public PropertyType(boolean nullable, Object value) { } } - @Nonnull @Override public String getId() { return "GSON"; } - @Nonnull @Override public String getPsiElementName() { return "Class"; } @RequiredReadAction - @Nonnull @Override - public String getIdFromPsiElement(@Nonnull PsiClass psiClass) { + public String getIdFromPsiElement(PsiClass psiClass) { return psiClass.getQualifiedName(); } @RequiredReadAction @Nullable @Override - public PsiClass getPsiElementById(@Nonnull String s, @Nonnull Project project) { + public PsiClass getPsiElementById(String s, Project project) { return JavaPsiFacade.getInstance(project).findClass(s, GlobalSearchScope.allScope(project)); } @RequiredUIAccess @Nullable @Override - public PsiClass chooseElement(@Nonnull Project project) { + public PsiClass chooseElement(Project project) { TreeClassChooser classChooser = TreeClassChooserFactory.getInstance(project).createAllProjectScopeChooser("Choose class"); classChooser.showDialog(); return classChooser.getSelected(); @@ -100,12 +95,12 @@ public PsiClass chooseElement(@Nonnull Project project) { @RequiredReadAction @Override - public boolean isAvailable(@Nonnull Project project) { + public boolean isAvailable(Project project) { return ModuleExtensionHelper.getInstance(project).hasModuleExtension(JavaModuleExtension.class) && getPsiElementById("com.google.gson.Gson", project) != null; } @Override - public void fillRootObject(@Nonnull PsiClass psiClass, @Nonnull JsonObjectDescriptor jsonObjectDescriptor) { + public void fillRootObject(PsiClass psiClass, JsonObjectDescriptor jsonObjectDescriptor) { PropertyType type = toType(psiClass.getProject(), null, new PsiImmediateClassType(psiClass, PsiSubstitutor.EMPTY)); if (type != null && type.myValue instanceof JsonObjectDescriptor) { @@ -116,7 +111,7 @@ public void fillRootObject(@Nonnull PsiClass psiClass, @Nonnull JsonObjectDescri } @Nullable - private static PropertyType toType(@Nonnull Project project, @Nullable PsiField field, @Nonnull PsiType type) { + private static PropertyType toType(Project project, @Nullable PsiField field, PsiType type) { if (PsiType.BYTE.equals(type)) { return new PropertyType(false, Number.class); } else if (PsiType.SHORT.equals(type)) { @@ -136,21 +131,21 @@ private static PropertyType toType(@Nonnull Project project, @Nullable PsiField PsiClass psiClass = classResolveResult.getElement(); if (psiClass != null) { String qualifiedName = psiClass.getQualifiedName(); - if (JavaClassNames.JAVA_LANG_STRING.equals(qualifiedName)) { + if (CommonClassNames.JAVA_LANG_STRING.equals(qualifiedName)) { return new PropertyType(String.class); - } else if (JavaClassNames.JAVA_LANG_BOOLEAN.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicBoolean".equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_BOOLEAN.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicBoolean".equals(qualifiedName)) { return new PropertyType(Boolean.class); - } else if (JavaClassNames.JAVA_LANG_BYTE.equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_BYTE.equals(qualifiedName)) { return new PropertyType(Number.class); - } else if (JavaClassNames.JAVA_LANG_SHORT.equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_SHORT.equals(qualifiedName)) { return new PropertyType(Number.class); - } else if (JavaClassNames.JAVA_LANG_INTEGER.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicInteger".equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_INTEGER.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicInteger".equals(qualifiedName)) { return new PropertyType(Number.class); - } else if (JavaClassNames.JAVA_LANG_LONG.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicLong".equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_LONG.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicLong".equals(qualifiedName)) { return new PropertyType(Number.class); - } else if (JavaClassNames.JAVA_LANG_FLOAT.equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_FLOAT.equals(qualifiedName)) { return new PropertyType(Number.class); - } else if (JavaClassNames.JAVA_LANG_DOUBLE.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicDouble".equals(qualifiedName)) { + } else if (CommonClassNames.JAVA_LANG_DOUBLE.equals(qualifiedName) || "java.util.concurrent.atomic.AtomicDouble".equals(qualifiedName)) { return new PropertyType(Number.class); } else if ("java.util.concurrent.atomic.AtomicIntegerArray".equals(qualifiedName)) { return new PropertyType(new NativeArray(Number.class)); @@ -160,7 +155,7 @@ private static PropertyType toType(@Nonnull Project project, @Nullable PsiField return new PropertyType(new NativeArray(Number.class)); } - PsiClass collectionClass = JavaPsiFacade.getInstance(project).findClass(JavaClassNames.JAVA_UTIL_COLLECTION, GlobalSearchScope.allScope(project)); + PsiClass collectionClass = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, GlobalSearchScope.allScope(project)); if (collectionClass != null) { if (InheritanceUtil.isInheritorOrSelf(psiClass, collectionClass, true)) { PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(collectionClass, psiClass, classResolveResult.getSubstitutor()); @@ -176,7 +171,7 @@ private static PropertyType toType(@Nonnull Project project, @Nullable PsiField } } - PsiClass mapClass = JavaPsiFacade.getInstance(project).findClass(JavaClassNames.JAVA_UTIL_MAP, GlobalSearchScope.allScope(project)); + PsiClass mapClass = JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_UTIL_MAP, GlobalSearchScope.allScope(project)); if (mapClass != null) { if (InheritanceUtil.isInheritorOrSelf(psiClass, mapClass, true)) { PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(mapClass, psiClass, classResolveResult.getSubstitutor()); @@ -222,7 +217,7 @@ private static PropertyType toType(@Nonnull Project project, @Nullable PsiField return null; } - private static void addIfNotNull(@Nonnull JsonObjectDescriptor objectDescriptor, @Nullable PropertyType propertyType, @Nullable PsiField navElement) { + private static void addIfNotNull(JsonObjectDescriptor objectDescriptor, @Nullable PropertyType propertyType, @Nullable PsiField navElement) { if (propertyType == null) { return; } @@ -250,8 +245,7 @@ private static void addIfNotNull(@Nonnull JsonObjectDescriptor objectDescriptor, } } - @Nonnull - private static String getPropertyNameFromField(@Nonnull PsiField field) { + private static String getPropertyNameFromField(PsiField field) { PsiAnnotation annotation = AnnotationUtil.findAnnotation(field, "com.google.gson.annotations.SerializedName"); if (annotation != null) { String value = AnnotationUtil.getStringAttributeValue(annotation, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); diff --git a/extract/gson-impl/src/main/java/module-info.java b/extract/gson-impl/src/main/java/module-info.java index addb330d8b..18b9033ea2 100644 --- a/extract/gson-impl/src/main/java/module-info.java +++ b/extract/gson-impl/src/main/java/module-info.java @@ -4,5 +4,9 @@ */ module consulo.java.gson.impl { requires consulo.java.language.impl; - requires consulo.javascript.json.javascript.impl; + requires consulo.json.jom.api; + requires consulo.language.impl; + requires consulo.language.editor.ui.api; + requires consulo.json.api; + requires consulo.ide.api; } \ No newline at end of file diff --git a/java-spellchecker/src/main/resources/META-INF/plugin-requires.xml b/extract/gson-impl/src/main/resources/META-INF/plugin-requires.xml similarity index 61% rename from java-spellchecker/src/main/resources/META-INF/plugin-requires.xml rename to extract/gson-impl/src/main/resources/META-INF/plugin-requires.xml index 9f2236b8ac..6d06d86838 100644 --- a/java-spellchecker/src/main/resources/META-INF/plugin-requires.xml +++ b/extract/gson-impl/src/main/resources/META-INF/plugin-requires.xml @@ -1,5 +1,5 @@ - com.intellij.spellchecker + consulo.json \ No newline at end of file diff --git a/extract/guava-impl/pom.xml b/extract/guava-impl/pom.xml index b0c4026a34..3da8fcc572 100644 --- a/extract/guava-impl/pom.xml +++ b/extract/guava-impl/pom.xml @@ -22,7 +22,7 @@ consulo - arch.ide-api-provided + arch.bind.java 3-SNAPSHOT @@ -30,7 +30,7 @@ consulo - https://maven.consulo.io/repository/snapshots/ + https://maven.consulo.dev/repository/snapshots/ true interval:60 @@ -45,6 +45,42 @@ + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + + consulo + consulo-code-editor-api + ${project.version} + provided + + + consulo + consulo-language-editor-api + ${project.version} + provided + + + consulo + consulo-language-editor-ui-api + ${project.version} + provided + + + consulo + consulo-language-impl + ${project.version} + provided + + ${project.groupId} consulo.java-java.language.impl ${project.version} diff --git a/extract/guava-impl/src/main/java/consulo/java/guava/GuavaLineMarkerProvider.java b/extract/guava-impl/src/main/java/consulo/java/guava/GuavaLineMarkerProvider.java index 32581c535a..c1d343782f 100644 --- a/extract/guava-impl/src/main/java/consulo/java/guava/GuavaLineMarkerProvider.java +++ b/extract/guava-impl/src/main/java/consulo/java/guava/GuavaLineMarkerProvider.java @@ -46,8 +46,7 @@ import consulo.module.content.ProjectRootManager; import consulo.util.lang.function.Functions; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import javax.swing.*; import java.util.Collection; @@ -103,7 +102,7 @@ public class GuavaLineMarkerProvider implements LineMarkerProvider { @RequiredReadAction @Nullable @Override - public LineMarkerInfo getLineMarkerInfo(@Nonnull PsiElement element) { + public LineMarkerInfo getLineMarkerInfo(PsiElement element) { PsiElement parent; if (element instanceof PsiIdentifier && (parent = element.getParent()) instanceof PsiClass) { PsiClass annotation = findSubscribeAnnotation(element); @@ -134,8 +133,7 @@ public LineMarkerInfo getLineMarkerInfo(@Nonnull PsiElement element) { return null; } - @Nonnull - private static Query createQuery(@Nonnull PsiClass target, @Nonnull PsiClass annClass) { + private static Query createQuery(PsiClass target, PsiClass annClass) { PsiImmediateClassType type = new PsiImmediateClassType(target, PsiSubstitutor.EMPTY); return new FilteredQuery<>(AnnotatedMembersSearch.search(annClass), psiMember -> ReadAction.compute(() -> { @@ -151,7 +149,7 @@ private static Query createQuery(@Nonnull PsiClass target, @Nonnull P } @Nullable - private static PsiClass findSubscribeAnnotation(@Nonnull PsiElement element) { + private static PsiClass findSubscribeAnnotation(PsiElement element) { return LanguageCachedValueUtil.getCachedValue(element, () -> { PsiClass javaClass = JavaPsiFacade.getInstance(element.getProject()).findClass(GuavaLibrary.Subscribe, element.getResolveScope()); @@ -159,7 +157,6 @@ private static PsiClass findSubscribeAnnotation(@Nonnull PsiElement element) { }); } - @Nonnull @Override public Language getLanguage() { return JavaLanguage.INSTANCE; diff --git a/extract/guava-impl/src/main/java/module-info.java b/extract/guava-impl/src/main/java/module-info.java index b2514b84a6..612aa550de 100644 --- a/extract/guava-impl/src/main/java/module-info.java +++ b/extract/guava-impl/src/main/java/module-info.java @@ -4,6 +4,13 @@ */ module consulo.java.guava.impl { requires consulo.java.language.impl; + requires consulo.language.impl; + requires consulo.code.editor.api; + requires consulo.language.editor.api; + requires consulo.language.editor.ui.api; // TODO remove in future requires java.desktop; + + // need open for CacheValueManager analyze + opens consulo.java.guava to consulo.application.impl; } \ No newline at end of file diff --git a/jam-api/pom.xml b/jam-api/pom.xml index e7de31017e..19fb472812 100644 --- a/jam-api/pom.xml +++ b/jam-api/pom.xml @@ -15,50 +15,99 @@ - limitations under the License. --> - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - consulo - arch.ide-api-provided - 3-SNAPSHOT - - + + consulo + arch.bind.java + 3-SNAPSHOT + + - - - consulo - https://maven.consulo.io/repository/snapshots/ - - true - interval:60 - - - + + + consulo + https://maven.consulo.dev/repository/snapshots/ + + true + interval:60 + + + - consulo.plugin - consulo.java-jam.api - 3-SNAPSHOT - jar + consulo.plugin + consulo.java-jam.api + 3-SNAPSHOT + jar - - - ${project.groupId} - consulo.java-java.language.api - ${project.version} - + + + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + + consulo + consulo-language-impl + ${project.version} + provided + + + consulo + consulo-ui-ex-awt-api + ${project.version} + provided + + + consulo + consulo-ui-ex-api + ${project.version} + provided + + + consulo + consulo-language-editor-api + ${project.version} + provided + + + consulo + consulo-language-editor-refactoring-api + ${project.version} + provided + + + ${project.groupId} + consulo.java-java.language.api + ${project.version} + - - ${project.groupId} - consulo.java-java.indexing.api - ${project.version} - + + ${project.groupId} + consulo.java-java.indexing.api + ${project.version} + - - ${project.groupId} - com.intellij.xml - ${project.version} - provided - - + + ${project.groupId} + com.intellij.xml.api + ${project.version} + provided + + + + ${project.groupId} + com.intellij.xml.dom.api + ${project.version} + provided + + \ No newline at end of file diff --git a/jam-api/src/main/java/com/intellij/jam/JamAttributeElement.java b/jam-api/src/main/java/com/intellij/jam/JamAttributeElement.java index 7c466db0f5..e3839a1e50 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamAttributeElement.java +++ b/jam-api/src/main/java/com/intellij/jam/JamAttributeElement.java @@ -21,10 +21,9 @@ import consulo.language.psi.PsiElementRef; import consulo.language.psi.PsiManager; import consulo.language.psi.util.PsiTreeUtil; -import consulo.xml.util.xml.GenericValue; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.xml.dom.GenericValue; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -36,7 +35,7 @@ public abstract class JamAttributeElement implements JamElement, GenericValue @Nullable private final PsiAnnotationMemberValue myExactValue; - public JamAttributeElement(String attributeName, @Nonnull PsiElementRef parent) { + public JamAttributeElement(String attributeName, PsiElementRef parent) { myAttributeLink = new AnnotationAttributeChildLink(attributeName); myExactValue = null; myParent = parent; diff --git a/jam-api/src/main/java/com/intellij/jam/JamClassAttributeElement.java b/jam-api/src/main/java/com/intellij/jam/JamClassAttributeElement.java index 1e2897f87b..4a93087c09 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamClassAttributeElement.java +++ b/jam-api/src/main/java/com/intellij/jam/JamClassAttributeElement.java @@ -21,14 +21,12 @@ import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; - /** * @author peter */ public class JamClassAttributeElement extends JamAttributeElement { - public JamClassAttributeElement(@Nonnull PsiElementRef parent, String attributeName) { + public JamClassAttributeElement(PsiElementRef parent, String attributeName) { super(attributeName, parent); } diff --git a/jam-api/src/main/java/com/intellij/jam/JamClassGenerator.java b/jam-api/src/main/java/com/intellij/jam/JamClassGenerator.java index ef33e2dac0..4df7d76746 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamClassGenerator.java +++ b/jam-api/src/main/java/com/intellij/jam/JamClassGenerator.java @@ -17,7 +17,7 @@ import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.psi.PsiElementRef; import java.util.function.Function; @@ -26,12 +26,11 @@ * @author peter */ @ServiceAPI(ComponentScope.APPLICATION) -public abstract class JamClassGenerator { +public interface JamClassGenerator { - public static JamClassGenerator getInstance() { - return ServiceManager.getService(JamClassGenerator.class); + static JamClassGenerator getInstance() { + return Application.get().getInstance(JamClassGenerator.class); } - public abstract Function generateJamElementFactory(Class aClass); - + Function generateJamElementFactory(Class aClass); } diff --git a/jam-api/src/main/java/com/intellij/jam/JamConverter.java b/jam-api/src/main/java/com/intellij/jam/JamConverter.java index 2a85b04f5a..22ff9bd274 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamConverter.java +++ b/jam-api/src/main/java/com/intellij/jam/JamConverter.java @@ -15,8 +15,7 @@ */ package com.intellij.jam; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import consulo.language.psi.PsiReference; @@ -45,7 +44,6 @@ public String toString(@Nullable T s, JamElement context) { throw new UnsupportedOperationException("toString() not supported for " + getClass()); } - @Nonnull public PsiReference[] createReferences(JamStringAttributeElement context) { return PsiReference.EMPTY_ARRAY; } diff --git a/jam-api/src/main/java/com/intellij/jam/JamEnumAttributeElement.java b/jam-api/src/main/java/com/intellij/jam/JamEnumAttributeElement.java index 775847c225..481e52ab33 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamEnumAttributeElement.java +++ b/jam-api/src/main/java/com/intellij/jam/JamEnumAttributeElement.java @@ -22,8 +22,7 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -31,7 +30,7 @@ public class JamEnumAttributeElement> extends JamAttributeElement { private final Class myModelEnum; - public JamEnumAttributeElement(@Nonnull PsiElementRef parent, String attributeName, Class modelEnum) { + public JamEnumAttributeElement(PsiElementRef parent, String attributeName, Class modelEnum) { super(attributeName, parent); myModelEnum = modelEnum; } diff --git a/jam-api/src/main/java/com/intellij/jam/JamMessages.java b/jam-api/src/main/java/com/intellij/jam/JamMessages.java index d4263931f3..2ff290a6e8 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamMessages.java +++ b/jam-api/src/main/java/com/intellij/jam/JamMessages.java @@ -17,7 +17,6 @@ package com.intellij.jam; import consulo.component.util.localize.AbstractBundle; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.PropertyKey; /** @@ -25,7 +24,7 @@ */ public class JamMessages extends AbstractBundle { - @NonNls protected static final String PATH_TO_BUNDLE = "messages.JamBundle"; + protected static final String PATH_TO_BUNDLE = "messages.JamBundle"; private static final JamMessages ourInstance = new JamMessages(); diff --git a/jam-api/src/main/java/com/intellij/jam/JamPomTarget.java b/jam-api/src/main/java/com/intellij/jam/JamPomTarget.java index 1e8c1d4e41..dd1064ad16 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamPomTarget.java +++ b/jam-api/src/main/java/com/intellij/jam/JamPomTarget.java @@ -15,7 +15,6 @@ */ package com.intellij.jam; -import javax.annotation.Nonnull; import consulo.document.util.TextRange; import consulo.language.pom.PomRenameableTarget; @@ -25,7 +24,7 @@ import com.intellij.java.language.psi.PsiLiteral; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -40,7 +39,6 @@ public JamPomTarget(JamElement element, JamStringAttributeElement nameAttr) { myNameAttr = nameAttr; } - @Nonnull public String getName() { String value = myNameAttr.getStringValue(); if (value == null) { @@ -49,7 +47,7 @@ public String getName() { return value; } - public JamPomTarget setName(@Nonnull String newName) { + public JamPomTarget setName(String newName) { myNameAttr.setStringValue(newName); return this; } diff --git a/jam-api/src/main/java/com/intellij/jam/JamReferenceContributor.java b/jam-api/src/main/java/com/intellij/jam/JamReferenceContributor.java index 3f23d1f233..966ad208d5 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamReferenceContributor.java +++ b/jam-api/src/main/java/com/intellij/jam/JamReferenceContributor.java @@ -31,7 +31,6 @@ import consulo.language.util.ProcessingContext; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; import java.util.List; import static com.intellij.java.language.patterns.PsiJavaPatterns.psiLiteral; @@ -50,9 +49,8 @@ public class JamReferenceContributor extends PsiReferenceContributor { public void registerReferenceProviders(PsiReferenceRegistrar registrar) { registrar.registerReferenceProvider(STRING_IN_ANNO, new PsiReferenceProvider() { - @Nonnull @Override - public PsiReference[] getReferencesByElement(@Nonnull PsiElement element, @Nonnull ProcessingContext context) { + public PsiReference[] getReferencesByElement(PsiElement element, ProcessingContext context) { final PsiNameValuePair pair = PsiTreeUtil.getParentOfType(element, PsiNameValuePair.class); final PsiAnnotation anno = (PsiAnnotation)pair.getParent().getParent(); final PsiAnnotation originalAnno = anno == null ? null : CompletionUtilCore.getOriginalElement(anno); @@ -83,7 +81,6 @@ public PsiReference[] getReferencesByElement(@Nonnull PsiElement element, @Nonnu }); } - @Nonnull @Override public Language getLanguage() { return JavaLanguage.INSTANCE; diff --git a/jam-api/src/main/java/com/intellij/jam/JamService.java b/jam-api/src/main/java/com/intellij/jam/JamService.java index 7d4c3f3055..c525a1a6ae 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamService.java +++ b/jam-api/src/main/java/com/intellij/jam/JamService.java @@ -22,7 +22,6 @@ import consulo.annotation.component.ServiceAPI; import consulo.annotation.component.ServiceImpl; import consulo.application.util.function.Processor; -import consulo.ide.ServiceManager; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiInvalidElementAccessException; import consulo.language.psi.PsiManager; @@ -33,10 +32,8 @@ import consulo.util.collection.ContainerUtil; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.List; @@ -64,7 +61,7 @@ protected JamService(PsiManager psiManager, SemService semService) { } public static JamService getJamService(Project p) { - return ServiceManager.getService(p, JamService.class); + return p.getInstance(JamService.class); } public static void processMembers(PsiClass psiClass, @@ -103,7 +100,7 @@ public T getJamElement(Class key, PsiElement psi) { } @Nullable - public T getJamElement(@Nonnull PsiElement psi, + public T getJamElement(PsiElement psi, JamMemberMeta... metas) { for (JamMemberMeta meta : metas) { final JamElement element = mySemService.getSemElement(meta.getJamKey(), psi); @@ -113,7 +110,7 @@ public T getJamElement(@Nonnull PsiElement psi, } @Nullable - public T getJamElement(SemKey key, @Nonnull PsiElement psi) { + public T getJamElement(SemKey key, PsiElement psi) { if (!psi.isValid()) { throw new PsiInvalidElementAccessException(psi); } @@ -125,41 +122,40 @@ public T getJamElement(SemKey key, @Nonnull PsiElement } } - @Nonnull - public List getMetas(@Nonnull T psi) { + public List getMetas(T psi) { return mySemService.getSemElements(JamService.MEMBER_META_KEY, psi); } @Nullable - public JamMemberMeta getMeta(@Nonnull T psi, SemKey> key) { + public JamMemberMeta getMeta(T psi, SemKey> key) { return mySemService.getSemElement(key, psi); } @Nullable - public JamAnnotationMeta getMeta(@Nonnull PsiAnnotation anno) { + public JamAnnotationMeta getMeta(PsiAnnotation anno) { return mySemService.getSemElement(JamService.ANNO_META_KEY, anno); } public List getJamClassElements(final JamMemberMeta meta, - @NonNls final String anno, + final String anno, final GlobalSearchScope scope) { return getJamClassElements(meta.getJamKey(), anno, scope); } public List getJamMethodElements(final JamMemberMeta meta, - @NonNls final String anno, + final String anno, final GlobalSearchScope scope) { return getJamMethodElements(meta.getJamKey(), anno, scope); } public List getJamFieldElements(final JamMemberMeta meta, - @NonNls final String anno, + final String anno, final GlobalSearchScope scope) { return getJamFieldElements(meta.getJamKey(), anno, scope); } - public List getAnnotatedMembersList(@Nonnull final PsiClass psiClass, + public List getAnnotatedMembersList(final PsiClass psiClass, final boolean checkClass, final boolean checkMethods, final boolean checkFields, @@ -177,7 +173,7 @@ public boolean process(PsiMember member) { return result; } - public List getJamClassElements(final SemKey c, @NonNls final String anno, final GlobalSearchScope scope) { + public List getJamClassElements(final SemKey c, final String anno, final GlobalSearchScope scope) { final List result = ContainerUtil.newArrayList(); findAnnotatedElements(PsiClass.class, anno, myPsiManager, scope, new Processor() { public boolean process(final PsiClass psiMember) { @@ -188,7 +184,7 @@ public boolean process(final PsiClass psiMember) { return result; } - public List getJamMethodElements(final SemKey c, @NonNls final String anno, final GlobalSearchScope scope) { + public List getJamMethodElements(final SemKey c, final String anno, final GlobalSearchScope scope) { final List result = ContainerUtil.newArrayList(); findAnnotatedElements(PsiMethod.class, anno, myPsiManager, scope, new Processor() { public boolean process(final PsiMethod psiMember) { @@ -199,7 +195,7 @@ public boolean process(final PsiMethod psiMember) { return result; } - public List getJamFieldElements(final SemKey c, @NonNls final String anno, final GlobalSearchScope scope) { + public List getJamFieldElements(final SemKey c, final String anno, final GlobalSearchScope scope) { final List result = ContainerUtil.newArrayList(); findAnnotatedElements(PsiField.class, anno, myPsiManager, scope, new Processor() { public boolean process(final PsiField psiMember) { @@ -211,7 +207,7 @@ public boolean process(final PsiField psiMember) { } public List getJamParameterElements(final SemKey c, - @NonNls final String anno, + final String anno, final GlobalSearchScope scope) { final List result = ContainerUtil.newArrayList(); findAnnotatedElements(PsiParameter.class, anno, myPsiManager, scope, new Processor() { @@ -223,7 +219,7 @@ public boolean process(final PsiParameter psiParameter) { return result; } - public List getAnnotatedMembersList(@Nonnull final PsiClass psiClass, + public List getAnnotatedMembersList(final PsiClass psiClass, final SemKey clazz, final boolean checkClass, final boolean checkMethods, diff --git a/jam-api/src/main/java/com/intellij/jam/JamSimpleReference.java b/jam-api/src/main/java/com/intellij/jam/JamSimpleReference.java index 8508686854..1fddb9ce42 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamSimpleReference.java +++ b/jam-api/src/main/java/com/intellij/jam/JamSimpleReference.java @@ -20,7 +20,6 @@ import consulo.language.psi.PsiReferenceBase; import consulo.language.util.IncorrectOperationException; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; /** * @author peter @@ -45,13 +44,12 @@ public PsiElement resolve() { return element == null? myContext.getPsiLiteral() : element; } - @Nonnull public Object[] getVariants() { return myConverter.getLookupVariants(myContext); } @Override - public PsiElement bindToElement(@Nonnull PsiElement element) throws IncorrectOperationException { + public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException { return myConverter.bindReference(myContext, element); } } diff --git a/jam-api/src/main/java/com/intellij/jam/JamSimpleReferenceConverter.java b/jam-api/src/main/java/com/intellij/jam/JamSimpleReferenceConverter.java index 64cdf856a5..ec4ca0e133 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamSimpleReferenceConverter.java +++ b/jam-api/src/main/java/com/intellij/jam/JamSimpleReferenceConverter.java @@ -23,10 +23,9 @@ import consulo.language.psi.PsiNamedElement; import consulo.language.psi.PsiReference; import consulo.util.collection.ContainerUtil; -import consulo.xml.util.xml.ElementPresentationManager; +import consulo.xml.dom.ElementPresentationManager; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.function.Function; @@ -35,7 +34,6 @@ * @author peter */ public abstract class JamSimpleReferenceConverter extends JamConverter{ - @Nonnull @Override public PsiReference[] createReferences(JamStringAttributeElement context) { final PsiLiteral literal = context.getPsiLiteral(); @@ -49,7 +47,7 @@ protected JamSimpleReference createReference(JamStringAttributeElement con } @Nullable - protected PsiElement getPsiElementFor(@Nonnull T target) { + protected PsiElement getPsiElementFor(T target) { if (target instanceof PsiElement) { return (PsiElement)target; } else if (target instanceof CommonModelElement) { @@ -58,8 +56,7 @@ protected PsiElement getPsiElementFor(@Nonnull T target) { return null; } - @Nonnull - protected LookupElement createLookupElementFor(@Nonnull T target) { + protected LookupElement createLookupElementFor(T target) { String name = ElementPresentationManager.getElementName(target); if (name != null) { return LookupElementBuilder.create(name); @@ -73,7 +70,6 @@ protected LookupElement createLookupElementFor(@Nonnull T target) { public LookupElement[] getLookupVariants(JamStringAttributeElement context) { return ContainerUtil.map2Array(getVariants(context), LookupElement.class, new Function() { - @Nonnull public LookupElement apply(T t) { return createLookupElementFor(t); } diff --git a/jam-api/src/main/java/com/intellij/jam/JamStringAttributeElement.java b/jam-api/src/main/java/com/intellij/jam/JamStringAttributeElement.java index 2124f272cb..23730a3511 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamStringAttributeElement.java +++ b/jam-api/src/main/java/com/intellij/jam/JamStringAttributeElement.java @@ -20,10 +20,8 @@ import com.intellij.java.language.psi.ref.AnnotationAttributeChildLink; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementRef; -import consulo.xml.util.xml.MutableGenericValue; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.xml.dom.MutableGenericValue; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -31,12 +29,12 @@ public class JamStringAttributeElement extends JamAttributeElement implements MutableGenericValue { private final JamConverter myConverter; - public JamStringAttributeElement(@Nonnull PsiElementRef parent, String attributeName, JamConverter converter) { + public JamStringAttributeElement(PsiElementRef parent, String attributeName, JamConverter converter) { super(attributeName, parent); myConverter = converter; } - public JamStringAttributeElement(@Nonnull PsiAnnotationMemberValue exactValue, JamConverter converter) { + public JamStringAttributeElement(PsiAnnotationMemberValue exactValue, JamConverter converter) { super(exactValue); myConverter = converter; } diff --git a/jam-api/src/main/java/com/intellij/jam/JamTypeAttributeElement.java b/jam-api/src/main/java/com/intellij/jam/JamTypeAttributeElement.java index 52fb0474cb..83d6970fe7 100644 --- a/jam-api/src/main/java/com/intellij/jam/JamTypeAttributeElement.java +++ b/jam-api/src/main/java/com/intellij/jam/JamTypeAttributeElement.java @@ -15,21 +15,15 @@ */ package com.intellij.jam; -import com.intellij.java.language.psi.PsiAnnotation; -import com.intellij.java.language.psi.PsiAnnotationMemberValue; -import com.intellij.java.language.psi.PsiClassObjectAccessExpression; -import com.intellij.java.language.psi.PsiType; -import consulo.java.language.module.util.JavaClassNames; +import com.intellij.java.language.psi.*; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; - /** * @author peter */ public class JamTypeAttributeElement extends JamAttributeElement { - public JamTypeAttributeElement(@Nonnull PsiElementRef parent, String attributeName) { + public JamTypeAttributeElement(PsiElementRef parent, String attributeName) { super(attributeName, parent); } @@ -48,7 +42,7 @@ public PsiType getValue() { if (psiAnnotationMemberValue instanceof PsiClassObjectAccessExpression) { psiType = ((PsiClassObjectAccessExpression) psiAnnotationMemberValue).getOperand().getType(); } - if (psiType != null && JavaClassNames.JAVA_LANG_OBJECT.equals(psiType.getCanonicalText())) { + if (psiType != null && CommonClassNames.JAVA_LANG_OBJECT.equals(psiType.getCanonicalText())) { return null; } return psiType; diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/BaseImpl.java b/jam-api/src/main/java/com/intellij/jam/model/common/BaseImpl.java index 8742a20ae1..0c209b710a 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/BaseImpl.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/BaseImpl.java @@ -24,12 +24,8 @@ import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.util.ModuleUtilCore; import consulo.module.Module; -import consulo.xml.util.xml.DomElement; -import consulo.xml.util.xml.DomManager; -import consulo.xml.util.xml.DomTarget; -import consulo.xml.util.xml.DomUtil; - -import javax.annotation.Nullable; +import consulo.xml.dom.*; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -41,7 +37,7 @@ public PsiManager getPsiManager() { } public PsiElement getIdentifyingPsiElement() { - final DomTarget target = DomTarget.getTarget(this); + final DomTarget target = DomService.getInstance().getTarget(this); return target == null? getXmlElement() : PomService.convertToPsi(target); } diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/CommonDomModelElement.java b/jam-api/src/main/java/com/intellij/jam/model/common/CommonDomModelElement.java index 898bbc2dc0..b0b1ef83b2 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/CommonDomModelElement.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/CommonDomModelElement.java @@ -16,7 +16,7 @@ package com.intellij.jam.model.common; -import consulo.xml.util.xml.DomElement; +import consulo.xml.dom.DomElement; public interface CommonDomModelElement extends CommonModelElement, DomElement { } diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelElement.java b/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelElement.java index ef93b509fe..dce872e594 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelElement.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelElement.java @@ -22,10 +22,9 @@ import consulo.language.psi.PsiManager; import consulo.language.util.ModuleUtilCore; import consulo.module.Module; -import consulo.xml.psi.xml.XmlTag; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.xml.language.psi.XmlTag; +import org.jspecify.annotations.Nullable; public interface CommonModelElement { boolean isValid(); @@ -44,7 +43,6 @@ public interface CommonModelElement { PsiFile getContainingFile(); abstract class PsiBase implements CommonModelElement { - @Nonnull public abstract PsiElement getPsiElement(); public boolean isValid() { @@ -75,7 +73,6 @@ public PsiFile getContainingFile() { abstract class ModuleBase implements CommonModelElement{ - @Nonnull public abstract Module getModule(); public boolean isValid() { diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelManager.java b/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelManager.java index d2cd3ac275..169a662875 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelManager.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/CommonModelManager.java @@ -18,10 +18,10 @@ import com.intellij.jam.view.DeleteHandler; import com.intellij.jam.view.JamDeleteHandler; import com.intellij.jam.view.JamUserResponse; -import consulo.ide.ServiceManager; -import consulo.xml.util.xml.DomElement; +import consulo.application.Application; -import javax.annotation.Nullable; +import consulo.xml.dom.DomElement; +import org.jspecify.annotations.Nullable; import java.util.Collection; /** @@ -29,7 +29,7 @@ */ public abstract class CommonModelManager { public static CommonModelManager getInstance() { - return ServiceManager.getService(CommonModelManager.class); + return Application.get().getInstance(CommonModelManager.class); } public abstract void deleteModelElement(CommonModelElement element, JamUserResponse response); diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/DefaultCommonModelTarget.java b/jam-api/src/main/java/com/intellij/jam/model/common/DefaultCommonModelTarget.java index 269fe5bf5f..da0a674c53 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/DefaultCommonModelTarget.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/DefaultCommonModelTarget.java @@ -20,9 +20,7 @@ import consulo.language.pom.PomRenameableTarget; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; -import consulo.xml.util.xml.ElementPresentationManager; - -import javax.annotation.Nonnull; +import consulo.xml.dom.ElementPresentationManager; /** * @author Gregory.Shrago @@ -30,7 +28,7 @@ public class DefaultCommonModelTarget extends DelegatePsiTarget implements PomRenameableTarget, CommonModelTarget { private final CommonModelElement.PsiBase myElement; - public DefaultCommonModelTarget(@Nonnull CommonModelElement.PsiBase element) { + public DefaultCommonModelTarget(CommonModelElement.PsiBase element) { super(element.getPsiElement()); myElement = element; } @@ -43,7 +41,7 @@ public boolean isWritable() { return getNavigationElement().isWritable(); } - public Object setName(@Nonnull final String newName) { + public Object setName(final String newName) { final PsiElement element = getNavigationElement(); if (element instanceof PomRenameableTarget) { return ((PomRenameableTarget)element).setName(newName); diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/JamSupportMetaData.java b/jam-api/src/main/java/com/intellij/jam/model/common/JamSupportMetaData.java index 8a6995d6d7..d23b7769e8 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/JamSupportMetaData.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/JamSupportMetaData.java @@ -24,11 +24,10 @@ import consulo.ui.image.Image; import consulo.util.collection.SmartList; import consulo.util.lang.StringUtil; -import consulo.xml.util.xml.DomElement; -import consulo.xml.util.xml.DomUtil; -import consulo.xml.util.xml.ElementPresentationManager; -import consulo.xml.util.xml.ModelMergerUtil; -import org.jetbrains.annotations.NonNls; +import consulo.xml.dom.DomElement; +import consulo.xml.dom.DomUtil; +import consulo.xml.dom.ElementPresentationManager; +import consulo.xml.dom.ModelMergerUtil; import java.util.List; @@ -59,7 +58,7 @@ public Object[] getDependences() { if (myElement != null) { final List deps = new SmartList(); deps.add(getDeclaration()); - deps.add(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); + deps.add(PsiModificationTracker.MODIFICATION_COUNT); for (final DomElement domElement : ModelMergerUtil.getImplementations(myElement, DomElement.class)) { if (domElement.isValid()) { deps.add(DomUtil.getFileElement(domElement)); @@ -68,15 +67,13 @@ public Object[] getDependences() { return deps.toArray(); } - return new Object[]{getDeclaration(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT}; + return new Object[]{getDeclaration(), PsiModificationTracker.MODIFICATION_COUNT}; } - @NonNls public final String getName() { return StringUtil.notNullize(ElementPresentationManager.getElementName(myElement)); } - @NonNls public String getName(PsiElement context) { return getName(); } diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/ManualModelMergerUtil.java b/jam-api/src/main/java/com/intellij/jam/model/common/ManualModelMergerUtil.java index 46f99c8b1e..dba07d58fe 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/ManualModelMergerUtil.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/ManualModelMergerUtil.java @@ -29,14 +29,13 @@ import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.util.collection.ContainerUtil; -import consulo.xml.psi.xml.XmlTag; -import consulo.xml.util.xml.DomElement; -import consulo.xml.util.xml.DomUtil; -import consulo.xml.util.xml.GenericValue; -import consulo.xml.util.xml.MergedObject; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.xml.dom.DomElement; +import consulo.xml.dom.DomUtil; +import consulo.xml.dom.GenericValue; +import consulo.xml.dom.MergedObject; +import consulo.xml.language.psi.XmlTag; + +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Function; @@ -79,7 +78,6 @@ public Object key(final GenericValue value) { } @Override - @Nonnull public GenericValue join(@Nullable final GenericValue prev, final GenericValue next, final Collection> notToBeMergedSet) { return prev == null? next : prev instanceof MyGenericValue? ((MyGenericValue)prev).addImplementation(next) : new MyGenericValue(prev, next); @@ -131,24 +129,20 @@ public interface Joiner { Collection map(V v); @Nullable Object key(T t); - @Nonnull T join(@Nullable T prev, T next, Collection notToBeMergedSet); } public static abstract class SimpleJoiner implements Joiner { @Override - @Nonnull public final T join(@Nullable T prev, T next, final Collection notToBeMergedSet) { return prev == null ? next : prev instanceof MyMergedObject ? ((MyMergedObject)prev).addImplementation(next) : createMergedImplementation(prev, next); } - @Nonnull protected abstract T createMergedImplementation(T prev, T next); } public static abstract class NextJoiner implements Joiner { @Override - @Nonnull public final T join(@Nullable T prev, T next, final Collection notToBeMergedSet) { return next; } @@ -156,7 +150,6 @@ public final T join(@Nullable T prev, T next, final Collection notToBeMergedS public static abstract class AnnoJoiner implements Joiner { @Override - @Nonnull public final T join(@Nullable T prev, T next, final Collection notToBeMergedSet) { if (shouldNotBeMerged(next)) { notToBeMergedSet.add(next); @@ -165,7 +158,6 @@ public final T join(@Nullable T prev, T next, final Collection notToBeMergedS return joinInner(prev, next); } - @Nonnull protected T joinInner(@Nullable T prev, T next) { if (prev instanceof MyMergedObject) return ((MyMergedObject)prev).addImplementation(next); if (prev != null) return createMergedImplementation(prev, next); @@ -179,7 +171,6 @@ protected T joinInner(@Nullable T prev, T next) { @Nullable protected abstract T getCurrentJam(final T next, final Psi psiMember); - @Nonnull protected abstract T createMergedImplementation(T prev, T next); @Nullable protected abstract Psi getPsiMember(T element); @@ -376,7 +367,6 @@ public int hashCode() { } @Override - @Nonnull public PsiElement getNavigationElement() { return findLast(myTargets, new Function() { public PsiElement apply(final T t) { @@ -405,7 +395,7 @@ public boolean isWritable() { } @Override - public MyRenameableTarget setName(@Nonnull final String newName) { + public MyRenameableTarget setName(final String newName) { final ArrayList list = new ArrayList(myTargets.size()); for (PomRenameableTarget target : myTargets) { final Object result = target.setName(newName); diff --git a/jam-api/src/main/java/com/intellij/jam/model/common/ReadOnlyGenericValue.java b/jam-api/src/main/java/com/intellij/jam/model/common/ReadOnlyGenericValue.java index 57ee342732..e72f030669 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/common/ReadOnlyGenericValue.java +++ b/jam-api/src/main/java/com/intellij/jam/model/common/ReadOnlyGenericValue.java @@ -18,13 +18,12 @@ import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiNamedElement; import consulo.language.psi.PsiPackage; -import consulo.xml.util.xml.GenericValue; +import consulo.xml.dom.GenericValue; /** * @author peter */ -public abstract class ReadOnlyGenericValue implements GenericValue -{ +public abstract class ReadOnlyGenericValue implements GenericValue { public static GenericValue NULL = getInstance(null); public static GenericValue nullInstance() { return (GenericValue)NULL; } diff --git a/jam-api/src/main/java/com/intellij/jam/model/util/JamCommonUtil.java b/jam-api/src/main/java/com/intellij/jam/model/util/JamCommonUtil.java index 658922ec4a..2e09ffcbcf 100644 --- a/jam-api/src/main/java/com/intellij/jam/model/util/JamCommonUtil.java +++ b/jam-api/src/main/java/com/intellij/jam/model/util/JamCommonUtil.java @@ -30,6 +30,7 @@ import com.intellij.java.indexing.search.searches.AnnotatedMembersSearch; import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.application.progress.ProgressManager; import consulo.application.util.CachedValue; import consulo.application.util.CachedValueProvider; @@ -37,10 +38,7 @@ import consulo.application.util.function.Processor; import consulo.dataContext.DataContext; import consulo.java.jam.util.JamCommonService; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.PlatformDataKeys; import consulo.language.editor.util.PsiUtilBase; -import consulo.language.impl.ast.Factory; import consulo.language.pom.PomTarget; import consulo.language.pom.PomTargetPsiElement; import consulo.language.psi.*; @@ -65,15 +63,13 @@ import consulo.util.lang.StringUtil; import consulo.util.lang.reflect.ReflectionUtil; import consulo.virtualFileSystem.VirtualFile; -import consulo.xml.psi.xml.XmlAttribute; -import consulo.xml.psi.xml.XmlAttributeValue; -import consulo.xml.psi.xml.XmlFile; -import consulo.xml.psi.xml.XmlTag; -import consulo.xml.util.xml.*; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.xml.dom.*; +import consulo.xml.language.psi.XmlAttribute; +import consulo.xml.language.psi.XmlAttributeValue; +import consulo.xml.language.psi.XmlFile; +import consulo.xml.language.psi.XmlTag; +import org.jspecify.annotations.Nullable; + import java.lang.annotation.ElementType; import java.lang.annotation.Target; import java.util.*; @@ -83,10 +79,9 @@ * @author Gregory.Shrago */ public class JamCommonUtil { - @NonNls public static final String VALUE_PARAMETER = "value"; - private static final HashingStrategy HASHING_STRATEGY = new HashingStrategy() { + private static final HashingStrategy HASHING_STRATEGY = new HashingStrategy<>() { public int hashCode(final PsiClass object) { final String qualifiedName = object.getQualifiedName(); return qualifiedName == null ? 0 : qualifiedName.hashCode(); @@ -102,9 +97,10 @@ public static Object computeMemberValue(@Nullable final PsiElement value) { if (value == null) { return null; } - if (value instanceof XmlAttributeValue) { - final GenericAttributeValue genericValue = DomManager.getDomManager(value.getProject()).getDomElement((XmlAttribute) value.getParent()); - return genericValue != null ? genericValue.getValue() : ((XmlAttributeValue) value).getValue(); + if (value instanceof XmlAttributeValue xmlAttributeValue) { + final GenericAttributeValue genericValue = + DomManager.getDomManager(value.getProject()).getDomElement((XmlAttribute) value.getParent()); + return genericValue != null ? genericValue.getValue() : xmlAttributeValue.getValue(); } try { @@ -117,26 +113,25 @@ public static Object computeMemberValue(@Nullable final PsiElement value) { } public static boolean isSuperClass(@Nullable final PsiClass firstClass, final String superClassQName) { - return !processSuperClassList(firstClass, new SmartList(), new Processor() { - public boolean process(final PsiClass superClass) { - return !Comparing.equal(superClass.getQualifiedName(), superClassQName); - } - }); + return !processSuperClassList( + firstClass, + new SmartList<>(), + superClass -> !Comparing.equal(superClass.getQualifiedName(), superClassQName) + ); } - @Nonnull public static List getSuperClassList(@Nullable final PsiClass firstClass) { - final SmartList list = new SmartList(); - processSuperClassList(firstClass, list, new Processor() { - public boolean process(final PsiClass psiClass) { - return true; - } - }); + final SmartList list = new SmartList<>(); + processSuperClassList(firstClass, list, psiClass -> true); return list; } - public static boolean processSuperClassList(@Nullable final PsiClass firstClass, @Nonnull final Collection supers, final Processor processor) { - for (PsiClass curClass = firstClass; curClass != null && !JavaClassNames.JAVA_LANG_OBJECT.equals(curClass.getQualifiedName()) && !supers.contains(curClass); curClass = curClass.getSuperClass()) { + public static boolean processSuperClassList( + @Nullable final PsiClass firstClass, + final Collection supers, + final Processor processor + ) { + for (PsiClass curClass = firstClass; curClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(curClass.getQualifiedName()) && !supers.contains(curClass); curClass = curClass.getSuperClass()) { ProgressManager.checkCanceled(); if (!processor.process(curClass)) { return false; @@ -160,14 +155,14 @@ public static T getRootElement(final PsiFile file, final Class domClass, if (!ReflectionUtil.isAssignable(domClass, root.getClass())) { return null; } + //noinspection unchecked return (T) root; } @Nullable public static String getDisplayName(final Object element) { - if (element instanceof CommonModelElement) { - final CommonModelElement modelElement = (CommonModelElement) element; + if (element instanceof CommonModelElement modelElement) { String name = getElementName(modelElement); if (name == null) { name = ""; @@ -187,6 +182,7 @@ public static String getClassName(final CommonModelElement element) { } @Nullable + @RequiredReadAction public static Module findModuleForPsiElement(final PsiElement element) { PsiFile psiFile = element.getContainingFile(); if (psiFile == null) { @@ -195,8 +191,9 @@ public static Module findModuleForPsiElement(final PsiElement element) { } psiFile = psiFile.getOriginalFile(); - if (psiFile instanceof XmlFile) { - final DomFileElement domFileElement = DomManager.getDomManager(element.getProject()).getFileElement((XmlFile) psiFile, CommonDomModelElement.class); + if (psiFile instanceof XmlFile xmlFile) { + final DomFileElement domFileElement = + DomManager.getDomManager(element.getProject()).getFileElement(xmlFile, CommonDomModelElement.class); if (domFileElement != null) { Module module = domFileElement.getRootElement().getModule(); if (module != null) { @@ -208,24 +205,22 @@ public static Module findModuleForPsiElement(final PsiElement element) { return ModuleUtilCore.findModuleForPsiElement(psiFile); } - @Nonnull public static PsiElement[] getTargetPsiElements(final CommonModelElement element) { - final ArrayList list = new ArrayList(); + final ArrayList list = new ArrayList<>(); // todo add new JAM or drop this functionality for (CommonModelElement modelElement : ModelMergerUtil.getFilteredImplementations(element)) { - if (modelElement instanceof DomElement) { - final DomElement domModelElement = (DomElement) modelElement; + if (modelElement instanceof DomElement domModelElement) { if (domModelElement.getXmlTag() != null) { list.add(domModelElement.getXmlTag()); } - } else if (modelElement instanceof JamChief) { - list.add(((JamChief) modelElement).getPsiElement()); + } else if (modelElement instanceof JamChief jamChief) { + list.add(jamChief.getPsiElement()); } else { ContainerUtil.addIfNotNull(list, modelElement.getIdentifyingPsiElement()); } } final PsiElement[] result = list.isEmpty() ? PsiElement.EMPTY_ARRAY : PsiUtilCore.toPsiElementArray(list); - Arrays.sort(result, new Comparator() { + Arrays.sort(result, new Comparator<>() { public int compare(final PsiElement o1, final PsiElement o2) { return getWeight(o1) - getWeight(o2); } @@ -265,14 +260,17 @@ public static boolean isInLibrary(final CommonModelElement modelElement) { return ProjectRootManager.getInstance(modelElement.getPsiManager().getProject()).getFileIndex().isInLibraryClasses(virtualFile); } + @RequiredReadAction public static boolean isKindOfJavaFile(final PsiElement element) { return element instanceof PsiJavaFile && element.getLanguage().isKindOf(JavaLanguage.INSTANCE); } + @RequiredReadAction public static boolean isPlainJavaFile(final PsiElement element) { return JamCommonService.getInstance().isPlainJavaFile(element); } + @RequiredReadAction public static boolean isPlainXmlFile(final PsiElement element) { return JamCommonService.getInstance().isPlainXmlFile(element); } @@ -280,30 +278,24 @@ public static boolean isPlainXmlFile(final PsiElement element) { private static final Key> MODULE_DEPENDENCIES = Key.create("MODULE_DEPENDENCIES"); private static final Key> MODULE_DEPENDENTS = Key.create("MODULE_DEPENDENTS"); - @Nonnull - public static Module[] getAllModuleDependencies(@Nonnull final Module module) { + public static Module[] getAllModuleDependencies(final Module module) { CachedValue value = module.getUserData(MODULE_DEPENDENCIES); if (value == null) { - module.putUserData(MODULE_DEPENDENCIES, value = CachedValuesManager.getManager(module.getProject()).createCachedValue(new CachedValueProvider() { - public Result compute() { - final Set result = addModuleDependencies(module, new HashSet(), false); - return new Result(result.toArray(new Module[result.size()]), ProjectRootManager.getInstance(module.getProject())); - } + module.putUserData(MODULE_DEPENDENCIES, value = CachedValuesManager.getManager(module.getProject()).createCachedValue(() -> { + final Set result = addModuleDependencies(module, new HashSet<>(), false); + return new CachedValueProvider.Result<>(result.toArray(new Module[result.size()]), ProjectRootManager.getInstance(module.getProject())); }, false)); } return value.getValue(); } - @Nonnull - public static Module[] getAllDependentModules(@Nonnull final Module module) { + public static Module[] getAllDependentModules(final Module module) { CachedValue value = module.getUserData(MODULE_DEPENDENTS); if (value == null) { - module.putUserData(MODULE_DEPENDENTS, value = CachedValuesManager.getManager(module.getProject()).createCachedValue(new CachedValueProvider() { - public Result compute() { - final Module[] modules = ModuleManager.getInstance(module.getProject()).getModules(); - final Set result = addModuleDependents(module, new HashSet(), modules); - return new Result(result.toArray(new Module[result.size()]), ProjectRootManager.getInstance(module.getProject())); - } + module.putUserData(MODULE_DEPENDENTS, value = CachedValuesManager.getManager(module.getProject()).createCachedValue(() -> { + final Module[] modules = ModuleManager.getInstance(module.getProject()).getModules(); + final Set result = addModuleDependents(module, new HashSet<>(), modules); + return new CachedValueProvider.Result<>(result.toArray(new Module[result.size()]), ProjectRootManager.getInstance(module.getProject())); }, false)); } return value.getValue(); @@ -315,8 +307,8 @@ public static Set addModuleDependencies(final Module module, final Set addModuleDependents(final Module module, final Set addModuleDependents(final Module module, final Set getAnnotationTypesWithChildren(final String annotationName, final Module module) { final GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); final PsiClass psiClass = JavaPsiFacade.getInstance(module.getProject()).findClass(annotationName, scope); @@ -361,15 +352,20 @@ public static Collection getAnnotationTypesWithChildren(final String a return classes; } - public static Collection getAnnotationTypesWithChildren(final Module module, final Key>> key, final String annotationName) { + public static Collection getAnnotationTypesWithChildren( + final Module module, + final Key>> key, + final String annotationName + ) { CachedValue> cachedValue = module.getUserData(key); if (cachedValue == null) { - cachedValue = CachedValuesManager.getManager(module.getProject()).createCachedValue(new CachedValueProvider>() { - public Result> compute() { + cachedValue = CachedValuesManager.getManager(module.getProject()).createCachedValue( + () -> { final Collection classes = getAnnotationTypesWithChildren(annotationName, module); - return new Result>(classes, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); - } - }, false); + return new CachedValueProvider.Result<>(classes, PsiModificationTracker.MODIFICATION_COUNT); + }, + false + ); module.putUserData(key, cachedValue); } @@ -388,23 +384,24 @@ private static void collectClassWithChildren(final PsiClass psiClass, final Set< } } - public static Collection getAnnotatedTypes(final Module module, final Key>> key, final String annotationName) { - + public static Collection getAnnotatedTypes( + final Module module, + final Key>> key, + final String annotationName + ) { CachedValue> cachedValue = module.getUserData(key); if (cachedValue == null) { - cachedValue = CachedValuesManager.getManager(module.getProject()).createCachedValue(new CachedValueProvider>() { - public Result> compute() { - final GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); - final PsiClass psiClass = JavaPsiFacade.getInstance(module.getProject()).findClass(annotationName, scope); - - final Collection classes; - if (psiClass == null || !psiClass.isAnnotationType()) { - classes = Collections.emptyList(); - } else { - classes = getChildren(psiClass, scope); - } - return new Result>(classes, PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); + cachedValue = CachedValuesManager.getManager(module.getProject()).createCachedValue(() -> { + final GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); + final PsiClass psiClass = JavaPsiFacade.getInstance(module.getProject()).findClass(annotationName, scope); + + final Collection classes; + if (psiClass == null || !psiClass.isAnnotationType()) { + classes = Collections.emptyList(); + } else { + classes = getChildren(psiClass, scope); } + return new CachedValueProvider.Result<>(classes, PsiModificationTracker.MODIFICATION_COUNT); }, false); module.putUserData(key, cachedValue); @@ -426,13 +423,11 @@ public static Set getChildren(final PsiClass psiClass, final GlobalSea final Set result = Sets.newHashSet(HASHING_STRATEGY); - AnnotatedMembersSearch.search(psiClass, scope).forEach(new Processor() { - public boolean process(final PsiMember psiMember) { - if (psiMember instanceof PsiClass && ((PsiClass) psiMember).isAnnotationType()) { - result.add((PsiClass) psiMember); - } - return true; + AnnotatedMembersSearch.search(psiClass, scope).forEach(psiMember -> { + if (psiMember instanceof PsiClass aClass && aClass.isAnnotationType()) { + result.add(aClass); } + return true; }); return result; @@ -445,20 +440,17 @@ public static boolean isAcceptedFor(final PsiClass psiClass, final ElementType.. return false; } - return !processObjectArrayValue(psiAnnotation, "value", new Processor() { - @Override - public boolean process(PsiAnnotationMemberValue value) { - ElementType obj = getObjectValue(value, ElementType.class); - if (obj == null) { - return true; - } - for (ElementType type : elementTypes) { - if (type.equals(obj)) { - return false; - } - } + return !processObjectArrayValue(psiAnnotation, "value", value -> { + ElementType obj = getObjectValue(value, ElementType.class); + if (obj == null) { return true; } + for (ElementType type : elementTypes) { + if (type.equals(obj)) { + return false; + } + } + return true; }); } @@ -468,10 +460,15 @@ public static XmlTag getXmlTag(final CommonModelElement object) { return dom != null ? dom.getXmlTag() : null; } - public static List getElementsIncludingSingle(final M member, final JamAnnotationMeta multiMeta, final JamAnnotationAttributeMeta> multiAttrMeta) { + public static List getElementsIncludingSingle( + final M member, + final JamAnnotationMeta multiMeta, + final JamAnnotationAttributeMeta> multiAttrMeta + ) { final List multiValue = multiMeta.getAttribute(member, multiAttrMeta); final PsiElementRef singleAnno = multiAttrMeta.getAnnotationMeta().getAnnotationRef(member); - return singleAnno.isImaginary() ? multiValue : ContainerUtil.concat(Collections.singletonList(multiAttrMeta.getInstantiator().instantiate(singleAnno)), multiValue); + return singleAnno.isImaginary() ? multiValue + : ContainerUtil.concat(Collections.singletonList(multiAttrMeta.getInstantiator().instantiate(singleAnno)), multiValue); } public static boolean isClassAvailable(final Project project, final String qName) { @@ -489,17 +486,19 @@ public static Object getModelObject(PsiElement element) { return null; } final PomTarget target = ((PomTargetPsiElement) element).getTarget(); - return target instanceof CommonModelTarget ? ((CommonModelTarget) target).getCommonElement() : target instanceof JamPomTarget ? ((JamPomTarget) target).getJamElement() : target instanceof DomTarget ? ((DomTarget) target).getDomElement() : null; + return target instanceof CommonModelTarget modelTarget ? modelTarget.getCommonElement() + : target instanceof JamPomTarget jamPomTarget ? jamPomTarget.getJamElement() + : target instanceof DomTarget domTarget ? domTarget.getDomElement() : null; } @Nullable public static DeleteProvider createDeleteProvider(final AbstractTreeBuilder builder) { - final List toRun = new ArrayList(); - final List jamProviders = new ArrayList(); + final List toRun = new ArrayList<>(); + final List jamProviders = new ArrayList<>(); for (JamNodeDescriptor descriptor : builder.getSelectedElements(JamNodeDescriptor.class)) { - final DeleteProvider provider = descriptor.isValid() ? (DeleteProvider) descriptor.getDataForElement(PlatformDataKeys.DELETE_ELEMENT_PROVIDER) : null; - if (provider instanceof JamDeleteProvider) { - jamProviders.add((JamDeleteProvider) provider); + final DeleteProvider provider = descriptor.isValid() ? (DeleteProvider) descriptor.getDataForElement(DeleteProvider.KEY) : null; + if (provider instanceof JamDeleteProvider jamDeleteProvider) { + jamProviders.add(jamDeleteProvider); } else if (provider != null) { toRun.add(provider); } @@ -517,14 +516,14 @@ public static DeleteProvider createDeleteProvider(final AbstractTreeBuilder buil } return new DeleteProvider() { @Override - public void deleteElement(@Nonnull DataContext dataContext) { + public void deleteElement(DataContext dataContext) { for (DeleteProvider provider : toRun) { provider.deleteElement(dataContext); } } @Override - public boolean canDeleteElement(@Nonnull DataContext dataContext) { + public boolean canDeleteElement(DataContext dataContext) { for (DeleteProvider provider : toRun) { if (!provider.canDeleteElement(dataContext)) { return false; @@ -535,41 +534,52 @@ public boolean canDeleteElement(@Nonnull DataContext dataContext) { }; } - public static CachedValue> createClassCachedValue(final Project project, final Supplier scope, final JamClassMeta... meta) { - return CachedValuesManager.getManager(project).createCachedValue(new CachedValueProvider>() { - @Override - public Result> compute() { - GlobalSearchScope searchScope = scope.get(); - final JamService jamService = JamService.getJamService(project); - List result = new ArrayList(); - if (!DumbService.isDumb(project)) { - for (JamClassMeta classMeta : meta) { - for (JamAnnotationMeta annotationMeta : classMeta.getRootAnnotations()) { - result.addAll(jamService.getJamClassElements(classMeta, annotationMeta.getAnnoName(), searchScope)); - } + public static CachedValue> createClassCachedValue( + final Project project, + final Supplier scope, + final JamClassMeta... meta + ) { + return CachedValuesManager.getManager(project).createCachedValue(() -> { + GlobalSearchScope searchScope = scope.get(); + final JamService jamService = JamService.getJamService(project); + List result = new ArrayList<>(); + if (!DumbService.isDumb(project)) { + for (JamClassMeta classMeta : meta) { + for (JamAnnotationMeta annotationMeta : classMeta.getRootAnnotations()) { + result.addAll(jamService.getJamClassElements(classMeta, annotationMeta.getAnnoName(), searchScope)); } } - return new Result>(result, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); } + return new CachedValueProvider.Result<>(result, PsiModificationTracker.MODIFICATION_COUNT); }, false); } public static void setAnnotationAttributeValue(PsiAnnotation annotation, String attribute, String value) { - new JamStringAttributeElement(PsiElementRef.real(annotation), attribute, JamConverter.DUMMY_CONVERTER).setStringValue(value); - } - - public static void findAnnotatedElements(Class elementClass, String annotationClass, PsiManager psiManager, GlobalSearchScope scope, Processor processor) { - final PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()).findClass(annotationClass, GlobalSearchScope.allScope(psiManager.getProject())); + new JamStringAttributeElement<>(PsiElementRef.real(annotation), attribute, JamConverter.DUMMY_CONVERTER).setStringValue(value); + } + + public static void findAnnotatedElements( + Class elementClass, + String annotationClass, + PsiManager psiManager, + GlobalSearchScope scope, + Processor processor + ) { + final PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()) + .findClass(annotationClass, GlobalSearchScope.allScope(psiManager.getProject())); if (aClass != null) { AnnotatedElementsSearch.searchElements(aClass, scope, elementClass).forEach(processor); } } - public static boolean processObjectArrayValue(PsiAnnotation annotation, String attributeName, Processor processor) { + public static boolean processObjectArrayValue( + PsiAnnotation annotation, + String attributeName, + Processor processor + ) { if (annotation != null) { final PsiAnnotationMemberValue memberValue = annotation.findAttributeValue(attributeName); - if (memberValue instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue arrayValue = (PsiArrayInitializerMemberValue) memberValue; + if (memberValue instanceof PsiArrayInitializerMemberValue arrayValue) { for (PsiAnnotationMemberValue value : arrayValue.getInitializers()) { if (!processor.process(value)) { return false; @@ -587,19 +597,20 @@ public static boolean processObjectArrayValue(PsiAnnotation annotation, String a @Nullable public static PsiClass getPsiClass(@Nullable PsiAnnotationMemberValue psiAnnotationMemberValue) { PsiClass psiClass = null; - if (psiAnnotationMemberValue instanceof PsiClassObjectAccessExpression) { - final PsiType type = ((PsiClassObjectAccessExpression) psiAnnotationMemberValue).getOperand().getType(); - if (type instanceof PsiClassType) { - psiClass = ((PsiClassType) type).resolve(); + if (psiAnnotationMemberValue instanceof PsiClassObjectAccessExpression classObjectAccessExpression) { + final PsiType type = classObjectAccessExpression.getOperand().getType(); + if (type instanceof PsiClassType classType) { + psiClass = classType.resolve(); } } else if (psiAnnotationMemberValue instanceof PsiExpression) { final Object value = computeMemberValue(psiAnnotationMemberValue); - if (value instanceof String) { - String className = StringUtil.stripQuotesAroundValue((String) value); - psiClass = JavaPsiFacade.getInstance(psiAnnotationMemberValue.getProject()).findClass(className, psiAnnotationMemberValue.getResolveScope()); + if (value instanceof String stringValue) { + String className = StringUtil.stripQuotesAroundValue(stringValue); + psiClass = JavaPsiFacade.getInstance(psiAnnotationMemberValue.getProject()) + .findClass(className, psiAnnotationMemberValue.getResolveScope()); } } - if (psiClass != null && JavaClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) { + if (psiClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) { return null; } return psiClass; @@ -607,6 +618,7 @@ public static PsiClass getPsiClass(@Nullable PsiAnnotationMemberValue psiAnnotat // we can consider using AnnotationUtil methods instead @Nullable + @SuppressWarnings("unchecked") public static T getObjectValue(@Nullable PsiAnnotationMemberValue value, Class clazz) { boolean isString = clazz == String.class; if (ReflectionUtil.isAssignable(Enum.class, clazz)) { @@ -622,13 +634,13 @@ public static T getObjectValue(@Nullable PsiAnnotationMemberValue value, Cla } @Nullable + @RequiredReadAction + @SuppressWarnings("unchecked") public static T getEnumValue(PsiAnnotationMemberValue memberValue, Class clazz) { assert ReflectionUtil.isAssignable(Enum.class, clazz); - if (memberValue instanceof PsiReferenceExpression) { - final PsiReferenceExpression psiReferenceExpression = (PsiReferenceExpression) memberValue; + if (memberValue instanceof PsiReferenceExpression psiReferenceExpression) { final PsiElement psiElement = psiReferenceExpression.resolve(); - if (psiElement instanceof PsiField) { - final PsiField psiField = (PsiField) psiElement; + if (psiElement instanceof PsiField psiField) { return (T) Enum.valueOf(clazz, psiField.getName()); } } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotatedChildrenQuery.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotatedChildrenQuery.java index 4cae834c67..cb18cec918 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotatedChildrenQuery.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotatedChildrenQuery.java @@ -19,9 +19,7 @@ import com.intellij.java.language.psi.PsiMember; import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -32,15 +30,15 @@ public abstract class JamAnnotatedChildrenQuery extends JamChildrenQuery { private final String myAnnoName; - public JamAnnotatedChildrenQuery(@NonNls JamAnnotationMeta meta) { + public JamAnnotatedChildrenQuery(JamAnnotationMeta meta) { myAnnoName = meta.getAnnoName(); } @Nullable - protected abstract JamMemberMeta getMemberMeta(@Nonnull PsiModifierListOwner member); + protected abstract JamMemberMeta getMemberMeta(PsiModifierListOwner member); @Override - public JamMemberMeta getMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta getMeta(PsiModifierListOwner member) { final JamMemberMeta memberMeta = getMemberMeta(member); return memberMeta != null && isAnnotated(member, myAnnoName) ? memberMeta : null; } @@ -49,9 +47,9 @@ public String getAnnoName() { return myAnnoName; } - protected abstract PsiModifierListOwner[] getAllChildren(@Nonnull PsiMember parent); + protected abstract PsiModifierListOwner[] getAllChildren(PsiMember parent); - public List findChildren(@Nonnull PsiMember parent) { + public List findChildren(PsiMember parent) { final ArrayList list = ContainerUtil.newArrayList(); for (final PsiModifierListOwner child : getAllChildren(parent)) { if (isAnnotated(child, myAnnoName)) { diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationArchetype.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationArchetype.java index 17f868912f..ddf61a44d4 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationArchetype.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationArchetype.java @@ -16,8 +16,7 @@ package com.intellij.jam.reflect; import consulo.util.collection.SmartList; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -52,7 +51,7 @@ public JamAnnotationArchetype getArchetype() { } @Nullable - public JamAttributeMeta findAttribute(@Nullable @NonNls String name) { + public JamAttributeMeta findAttribute(@Nullable String name) { if (name == null) name = "value"; for (final JamAttributeMeta attribute : myAttributes) { if (attribute.getAttributeLink().getAttributeName().equals(name)) { diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationAttributeMeta.java index 83e0552ede..6e6447a887 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationAttributeMeta.java @@ -33,8 +33,7 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.function.PairConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -58,12 +57,12 @@ private JamAnnotationAttributeMeta(String attrName, JamAnnotationMeta annoMeta, myJamKey = JamService.JAM_ELEMENT_KEY.subKey(attrName); } - public JamAnnotationAttributeMeta addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamAnnotationAttributeMeta addPomTargetProducer(PairConsumer> producer) { myPomTargetProducers.add(producer); return this; } - public List getAssociatedTargets(@Nonnull T element) { + public List getAssociatedTargets(T element) { final ArrayList list = ContainerUtil.newArrayList(); final Consumer targetConsumer = target -> list.add(target); for (final PairConsumer> function : myPomTargetProducers) { @@ -107,7 +106,6 @@ public Single(String attrName, JamAnnotationMeta annoMeta, JamInstantiator anno) { final PsiAnnotation psiElement = anno.getPsiElement(); assert psiElement != null; @@ -126,11 +124,10 @@ public void registerSem(SemRegistrar registrar, ElementPattern an } public static final class Collection extends JamAnnotationAttributeMeta> { - public Collection(String attrName, @Nonnull JamAnnotationMeta annoMeta, JamInstantiator instantiator) { + public Collection(String attrName, JamAnnotationMeta annoMeta, JamInstantiator instantiator) { super(attrName, annoMeta, instantiator); } - @Nonnull public List getJam(final PsiElementRef anno) { return getCollectionJam(anno, new Function() { public T apply(PsiAnnotationMemberValue psiAnnotationMemberValue) { @@ -147,13 +144,12 @@ private T getJam(PsiAnnotationMemberValue element) { return null; } - @Nonnull public T addAttribute(PsiElementRef annoRef) { return ObjectUtil.assertNotNull(getJam(addAttribute(annoRef, "@" + myAnnoMeta.getAnnoName(), getAttributeLink()))); } @Override - public JamAnnotationAttributeMeta.Collection addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamAnnotationAttributeMeta.Collection addPomTargetProducer(PairConsumer> producer) { super.addPomTargetProducer(producer); return this; } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationMeta.java index 4e44e59902..9ba5fd676b 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamAnnotationMeta.java @@ -32,10 +32,8 @@ import consulo.language.sem.SemRegistrar; import consulo.language.sem.SemService; import consulo.language.util.ProcessingContext; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.function.Function; @@ -48,15 +46,15 @@ public class JamAnnotationMeta extends JamAnnotationArchetype implements SemElem private final AnnotationChildLink myAnnotationChildLink; private final SemKey myMetaKey; - public JamAnnotationMeta(@Nonnull @NonNls String annoName) { + public JamAnnotationMeta(String annoName) { this(annoName, null); } - public JamAnnotationMeta(@Nonnull @NonNls String annoName, @Nullable JamAnnotationArchetype archetype) { + public JamAnnotationMeta(String annoName, @Nullable JamAnnotationArchetype archetype) { this(annoName, archetype, JamService.ANNO_META_KEY.subKey("AnnoMeta:" + annoName)); } - public JamAnnotationMeta(@Nonnull @NonNls String annoName, @Nullable JamAnnotationArchetype archetype, + public JamAnnotationMeta(String annoName, @Nullable JamAnnotationArchetype archetype, final SemKey metaKey) { super(archetype); myAnnotationChildLink = new AnnotationChildLink(annoName); @@ -77,12 +75,10 @@ public String getAnnoName() { return myAnnotationChildLink.getAnnotationQualifiedName(); } - @Nonnull public T getAttribute(PsiModifierListOwner member, JamAttributeMeta meta) { return getAttribute(PsiElementRef.real(member), meta); } - @Nonnull public T getAttribute(PsiElementRef member, JamAttributeMeta meta) { return meta.getJam(getAnnotationRef(member)); } @@ -92,12 +88,10 @@ public PsiAnnotation getAnnotation(PsiModifierListOwner owner) { return myAnnotationChildLink.findLinkedChild(owner); } - @Nonnull public PsiElementRef getAnnotationRef(PsiModifierListOwner owner) { return myAnnotationChildLink.createChildRef(owner); } - @Nonnull public PsiElementRef getAnnotationRef(PsiElementRef member) { return myAnnotationChildLink.createChildRef(member); } @@ -113,7 +107,7 @@ public void registerTopLevelSem(SemRegistrar re psiAnnotation().qName(myAnnotationChildLink.getAnnotationQualifiedName()).withSuperParent( 2, isPackage ? PsiJavaPatterns.psiJavaElement(PsiPackageStatement.class).with(new PatternCondition("package") { @Override - public boolean accepts(@Nonnull PsiPackageStatement psiPackageStatement, ProcessingContext context) { + public boolean accepts(PsiPackageStatement psiPackageStatement, ProcessingContext context) { return parentPattern.accepts(psiPackageStatement.getPackageReference().resolve(), context); } }) : parentPattern); diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamAttributeMeta.java index 69de8b7db5..86834cdd09 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamAttributeMeta.java @@ -21,9 +21,7 @@ import com.intellij.java.language.psi.ref.AnnotationAttributeChildLink; import consulo.language.psi.PsiElementRef; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,7 +37,7 @@ public abstract class JamAttributeMeta { private final AnnotationAttributeChildLink myAttributeLink; - protected JamAttributeMeta(@NonNls String attrName) { + protected JamAttributeMeta(String attrName) { myAttributeLink = new AnnotationAttributeChildLink(attrName); } @@ -64,7 +62,6 @@ public int hashCode() { return myAttributeLink.hashCode(); } - @Nonnull protected List getCollectionJam(PsiElementRef annoRef, Function producer) { final PsiAnnotationMemberValue attr = getAttributeLink().findLinkedChild(annoRef.getPsiElement()); if (attr == null) { @@ -83,22 +80,21 @@ protected List getCollectionJam(PsiElementRef annoRef, Fun } - @Nonnull public abstract JamType getJam(PsiElementRef anno); - public static JamStringAttributeMeta.Single singleString(@Nonnull @NonNls String attrName) { + public static JamStringAttributeMeta.Single singleString(String attrName) { return singleString(attrName, JamConverter.DUMMY_CONVERTER); } - public static JamStringAttributeMeta.Single singleString(@Nonnull @NonNls String attrName, JamConverter converter) { + public static JamStringAttributeMeta.Single singleString(String attrName, JamConverter converter) { return new JamStringAttributeMeta.Single(attrName, converter); } - public static > JamEnumAttributeMeta.Single singleEnum(@Nonnull @NonNls String attrName, Class modelEnum) { + public static > JamEnumAttributeMeta.Single singleEnum(String attrName, Class modelEnum) { return new JamEnumAttributeMeta.Single(attrName, modelEnum); } - public static JamStringAttributeMeta.Collection collectionString(@Nonnull @NonNls String attrName) { + public static JamStringAttributeMeta.Collection collectionString(String attrName) { return collectionString(attrName, JamConverter.DUMMY_CONVERTER); } @@ -106,12 +102,12 @@ public static JamStringAttributeMeta.Collection collectionString(String a return new JamStringAttributeMeta.Collection(attrName, converter); } - public static JamAnnotationAttributeMeta.Single singleAnno(@Nonnull @NonNls String attrName, JamAnnotationMeta annoMeta, Class jamClass) { + public static JamAnnotationAttributeMeta.Single singleAnno(String attrName, JamAnnotationMeta annoMeta, Class jamClass) { final JamInstantiator instantiator = JamInstantiator.proxied(jamClass); return new JamAnnotationAttributeMeta.Single(attrName, annoMeta, instantiator); } - public static JamAnnotationAttributeMeta.Collection annoCollection(@Nonnull @NonNls String attrName, @Nonnull JamAnnotationMeta annoMeta, Class jamClass) { + public static JamAnnotationAttributeMeta.Collection annoCollection(String attrName, JamAnnotationMeta annoMeta, Class jamClass) { final JamInstantiator instantiator = JamInstantiator.proxied(jamClass); return new JamAnnotationAttributeMeta.Collection(attrName, annoMeta, instantiator); } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamChildrenQuery.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamChildrenQuery.java index 4c794debf2..68426cf91e 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamChildrenQuery.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamChildrenQuery.java @@ -29,10 +29,8 @@ import consulo.language.sem.SemRegistrar; import consulo.util.collection.ContainerUtil; import consulo.util.dataholder.Key; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -48,18 +46,18 @@ protected JamChildrenQuery() { myCacheKey = Key.create(getClass().getName()); } - protected JamChildrenQuery(@NonNls String debugName) { + protected JamChildrenQuery(String debugName) { myCacheKey = Key.create(debugName); } @Nullable - public abstract JamMemberMeta getMeta(@Nonnull PsiModifierListOwner member); + public abstract JamMemberMeta getMeta(PsiModifierListOwner member); - protected abstract List findChildren(@Nonnull PsiMember parent); + protected abstract List findChildren(PsiMember parent); - public final List findChildren(@Nonnull PsiElementRef parentRef) { + public final List findChildren(PsiElementRef parentRef) { final PsiMember parent = parentRef.getPsiElement(); if (parent == null) { return Collections.emptyList(); @@ -69,7 +67,7 @@ public final List findChildren(@Nonnull PsiElementRef if (data == null) { parent.putUserData(myCacheKey, data = CachedValuesManager.getManager(parent.getProject()).createCachedValue(new CachedValueProvider>() { public Result> compute() { - return Result.create(findChildren(parent), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); + return Result.create(findChildren(parent), PsiModificationTracker.MODIFICATION_COUNT); } }, false)); } @@ -93,7 +91,7 @@ public static JamChildrenQuery annotatedMethods(fi return new AnnotatedMethodsQuery(annotationMeta, methodMeta); } - public static JamChildrenQuery annotatedFields(@NonNls final String annotation, final Class jamClass) { + public static JamChildrenQuery annotatedFields(final String annotation, final Class jamClass) { return annotatedFields(new JamAnnotationMeta(annotation), jamClass); } @@ -121,11 +119,11 @@ public AnnotatedMethodsQuery(JamAnnotationMeta annotationMeta, JamMemberMeta getMemberMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta getMemberMeta(PsiModifierListOwner member) { return member instanceof PsiMethod ? myMethodMeta : null; } - public PsiModifierListOwner[] getAllChildren(@Nonnull PsiMember parent) { + public PsiModifierListOwner[] getAllChildren(PsiMember parent) { return ((PsiClass) parent).getMethods(); } @@ -142,11 +140,11 @@ public AnnotatedFieldsQuery(JamAnnotationMeta annotationMeta, JamMemberMeta getMemberMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta getMemberMeta(PsiModifierListOwner member) { return member instanceof PsiField ? myFieldMeta : null; } - public PsiModifierListOwner[] getAllChildren(@Nonnull PsiMember parent) { + public PsiModifierListOwner[] getAllChildren(PsiMember parent) { return ((PsiClass) parent).getFields(); } @@ -163,7 +161,7 @@ public CompositeQuery(JamChildrenQuery... components) { } @Override - public JamMemberMeta getMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta getMeta(PsiModifierListOwner member) { for (final JamChildrenQuery component : myComponents) { final JamMemberMeta meta = component.getMeta(member); if (meta != null) { @@ -173,7 +171,7 @@ public CompositeQuery(JamChildrenQuery... components) { return null; } - public List findChildren(@Nonnull final PsiMember parent) { + public List findChildren(final PsiMember parent) { return ContainerUtil.concat(myComponents, new Function, Collection>() { public Collection apply(JamChildrenQuery jamChildrenQuery) { return jamChildrenQuery.findChildren(parent); @@ -199,11 +197,11 @@ public AnnotatedParametersQuery(JamAnnotationMeta annotationMeta, JamMemberMeta< } @Nullable - public JamMemberMeta getMemberMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta getMemberMeta(PsiModifierListOwner member) { return member instanceof PsiParameter ? myParamMeta : null; } - public PsiModifierListOwner[] getAllChildren(@Nonnull PsiMember parent) { + public PsiModifierListOwner[] getAllChildren(PsiMember parent) { return ((PsiMethod) parent).getParameterList().getParameters(); } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamClassAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamClassAttributeMeta.java index e68c411a89..e7574fba9b 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamClassAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamClassAttributeMeta.java @@ -21,7 +21,6 @@ import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -40,7 +39,6 @@ public Collection(String attrName) { super(attrName); } - @Nonnull public List getJam(PsiElementRef anno) { return getCollectionJam(anno, new Function() { public JamClassAttributeElement apply(PsiAnnotationMemberValue psiAnnotationMemberValue) { @@ -56,12 +54,10 @@ public Single(String attrName) { super(attrName); } - @Nonnull public JamClassAttributeElement getJam(PsiElementRef anno) { return new JamClassAttributeElement(anno, getAttributeLink().getAttributeName()); } - @Nonnull public JamClassAttributeElement getJam(PsiElementRef anno, final Supplier defaultValue) { return new JamClassAttributeElement(anno, getAttributeLink().getAttributeName()) { @Override diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamClassMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamClassMeta.java index 515e3c5a7d..3974a279fa 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamClassMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamClassMeta.java @@ -21,8 +21,7 @@ import consulo.language.sem.SemKey; import consulo.util.lang.function.PairConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.function.Consumer; /** @@ -48,7 +47,7 @@ public JamClassMeta addChildrenQuery(JamChildrenQuery childrenQuery) { } @Override - public JamClassMeta addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamClassMeta addPomTargetProducer(PairConsumer> producer) { super.addPomTargetProducer(producer); return this; } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamEnumAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamEnumAttributeMeta.java index 8f8f6c8616..120027fc5d 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamEnumAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamEnumAttributeMeta.java @@ -20,7 +20,6 @@ import com.intellij.java.language.psi.PsiAnnotationMemberValue; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; import java.util.List; import java.util.function.Function; @@ -60,7 +59,6 @@ public Collection(String attrName, Class modelEnum) { super(attrName, modelEnum); } - @Nonnull public List> getJam(PsiElementRef anno) { return getCollectionJam(anno, new Function>() { public JamEnumAttributeElement apply(PsiAnnotationMemberValue psiAnnotationMemberValue) { @@ -75,12 +73,10 @@ public Single(String attrName, Class modelEnum) { super(attrName, modelEnum); } - @Nonnull public JamEnumAttributeElement getJam(PsiElementRef anno) { return new JamEnumAttributeElement(anno, getAttributeLink().getAttributeName(), myModelEnum); } - @Nonnull public JamEnumAttributeElement getJam(PsiElementRef anno, final T defaultValue) { return new JamEnumAttributeElement(anno, getAttributeLink().getAttributeName(), myModelEnum) { @Override diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamFieldMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamFieldMeta.java index d4c62ad245..6bb1524156 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamFieldMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamFieldMeta.java @@ -18,7 +18,7 @@ import com.intellij.jam.JamElement; import com.intellij.java.language.psi.PsiField; import consulo.language.sem.SemKey; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author peter diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamInstantiator.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamInstantiator.java index a43903f775..b1f1137e57 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamInstantiator.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamInstantiator.java @@ -20,7 +20,6 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; import java.util.function.Function; /** @@ -28,15 +27,13 @@ */ public abstract class JamInstantiator { - @Nonnull - public abstract Jam instantiate(@Nonnull PsiElementRef ref); + public abstract Jam instantiate(PsiElementRef ref); public static JamInstantiator proxied(final Class jamClass) { final Function function = JamClassGenerator.getInstance().generateJamElementFactory(jamClass); return new JamInstantiator() { - @Nonnull @Override - public Jam instantiate(@Nonnull PsiElementRef psiPsiRef) { + public Jam instantiate(PsiElementRef psiPsiRef) { return function.apply(psiPsiRef); } }; diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberArchetype.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberArchetype.java index 75ce5c2bb2..7d8f2538bb 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberArchetype.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberArchetype.java @@ -24,8 +24,7 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.function.PairConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -58,7 +57,7 @@ protected final void registerChildren(SemRegistrar registrar, ElementPattern addChildrenQuery(JamChildrenQuery childre return this; } - public JamMemberArchetype addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamMemberArchetype addPomTargetProducer(PairConsumer> producer) { myPomTargetProducers.add(producer); return this; } - public List getAssociatedTargets(@Nonnull Jam element) { + public List getAssociatedTargets(Jam element) { final ArrayList list = ContainerUtil.newArrayList(); final Consumer targetConsumer = new Consumer() { public void accept(PomTarget target) { @@ -113,7 +112,7 @@ public void accept(PomTarget target) { } @Nullable - public JamMemberMeta findChildMeta(@Nonnull PsiModifierListOwner member) { + public JamMemberMeta findChildMeta(PsiModifierListOwner member) { for (final JamChildrenQuery child : myChildren) { final JamMemberMeta meta = ((JamChildrenQuery)child).getMeta(member); if (meta != null) { diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberMeta.java index ff51c48711..e840dacfbb 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamMemberMeta.java @@ -28,8 +28,7 @@ import consulo.language.sem.SemService; import consulo.util.lang.function.PairConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -93,13 +92,13 @@ public Jam apply(Psi psi) { } @Override - public JamMemberMeta addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamMemberMeta addPomTargetProducer(PairConsumer> producer) { super.addPomTargetProducer(producer); return this; } @Nullable - public final Jam getJamElement(@Nonnull Psi member) { + public final Jam getJamElement(Psi member) { return SemService.getSemService(member.getProject()).getSemElement(myJamKey, member); } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamMethodMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamMethodMeta.java index 629937b04a..12f4f2f187 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamMethodMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamMethodMeta.java @@ -20,9 +20,8 @@ import consulo.language.pom.PomTarget; import consulo.language.sem.SemKey; import consulo.util.lang.function.PairConsumer; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.function.Consumer; /** @@ -66,7 +65,7 @@ public JamChildrenQuery addAnnotatedParametersQuery(Ja } @Override - public JamMethodMeta addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamMethodMeta addPomTargetProducer(PairConsumer> producer) { super.addPomTargetProducer(producer); return this; } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamPackageMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamPackageMeta.java index f79f53c6f0..b70c8c53e9 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamPackageMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamPackageMeta.java @@ -21,8 +21,7 @@ import consulo.language.sem.SemKey; import consulo.util.lang.function.PairConsumer; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.function.Consumer; /** @@ -48,7 +47,7 @@ public JamPackageMeta addChildrenQuery(JamChildrenQuery childrenQuery) { } @Override - public JamPackageMeta addPomTargetProducer(@Nonnull PairConsumer> producer) { + public JamPackageMeta addPomTargetProducer(PairConsumer> producer) { super.addPomTargetProducer(producer); return this; } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamParameterMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamParameterMeta.java index c34f312e99..a8c63985ba 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamParameterMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamParameterMeta.java @@ -15,7 +15,7 @@ */ package com.intellij.jam.reflect; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import com.intellij.jam.JamElement; import com.intellij.java.language.psi.PsiParameter; diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamStringAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamStringAttributeMeta.java index 066eaff218..c7180cb6f0 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamStringAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamStringAttributeMeta.java @@ -21,7 +21,6 @@ import com.intellij.java.language.psi.PsiAnnotationMemberValue; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -66,7 +65,6 @@ public Collection(String attrName, JamConverter converter) { super(attrName, converter); } - @Nonnull public List> getJam(PsiElementRef anno) { return getCollectionJam(anno, new Function>() { public JamStringAttributeElement apply(PsiAnnotationMemberValue psiAnnotationMemberValue) { @@ -86,8 +84,7 @@ public Single(String attrName, JamConverter converter) { super(attrName, converter); } - @Nonnull - public JamStringAttributeElement getJam(PsiElementRef anno, @Nonnull final Supplier defaultValue) { + public JamStringAttributeElement getJam(PsiElementRef anno, final Supplier defaultValue) { return new JamStringAttributeElement(anno, getAttributeLink().getAttributeName(), myConverter) { @Override public T getValue() { @@ -97,7 +94,6 @@ public T getValue() { }; } - @Nonnull public JamStringAttributeElement getJam(PsiElementRef anno) { return new JamStringAttributeElement(anno, getAttributeLink().getAttributeName(), myConverter); } diff --git a/jam-api/src/main/java/com/intellij/jam/reflect/JamTypeAttributeMeta.java b/jam-api/src/main/java/com/intellij/jam/reflect/JamTypeAttributeMeta.java index e0ae2579dd..d3fefbde0a 100644 --- a/jam-api/src/main/java/com/intellij/jam/reflect/JamTypeAttributeMeta.java +++ b/jam-api/src/main/java/com/intellij/jam/reflect/JamTypeAttributeMeta.java @@ -21,7 +21,6 @@ import com.intellij.java.language.psi.PsiType; import consulo.language.psi.PsiElementRef; -import javax.annotation.Nonnull; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -40,7 +39,6 @@ public Collection(String attrName) { super(attrName); } - @Nonnull public List getJam(PsiElementRef anno) { return getCollectionJam(anno, new Function() { public JamTypeAttributeElement apply(PsiAnnotationMemberValue psiAnnotationMemberValue) { @@ -56,12 +54,10 @@ public Single(String attrName) { super(attrName); } - @Nonnull public JamTypeAttributeElement getJam(PsiElementRef anno) { return new JamTypeAttributeElement(anno, getAttributeLink().getAttributeName()); } - @Nonnull public JamTypeAttributeElement getJam(PsiElementRef anno, final Supplier defaultValue) { return new JamTypeAttributeElement(anno, getAttributeLink().getAttributeName()) { @Override diff --git a/jam-api/src/main/java/com/intellij/jam/view/JamDeleteHandler.java b/jam-api/src/main/java/com/intellij/jam/view/JamDeleteHandler.java index 8aab3116fe..fd86a6cc4e 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/JamDeleteHandler.java +++ b/jam-api/src/main/java/com/intellij/jam/view/JamDeleteHandler.java @@ -17,7 +17,7 @@ import com.intellij.jam.model.common.CommonModelElement; import consulo.language.psi.PsiElement; -import consulo.xml.util.xml.DomElement; +import consulo.xml.dom.DomElement; import java.util.Collection; @@ -32,7 +32,6 @@ public void addModelElements(final CommonModelElement element, final Collection< public void addPsiElements(final CommonModelElement element, final Collection result) { } - public boolean onChildrenDelete(final DomElement domElement) { return false; } diff --git a/jam-api/src/main/java/com/intellij/jam/view/JamDeleteProvider.java b/jam-api/src/main/java/com/intellij/jam/view/JamDeleteProvider.java index 8fa67c5508..402d677683 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/JamDeleteProvider.java +++ b/jam-api/src/main/java/com/intellij/jam/view/JamDeleteProvider.java @@ -20,7 +20,6 @@ import com.intellij.jam.model.common.CommonModelManager; import consulo.dataContext.DataContext; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -51,11 +50,11 @@ public JamDeleteProvider(final CommonModelElement element, final JamUserResponse this(response, Collections.singletonList(element)); } - public void deleteElement(@Nonnull DataContext dataContext) { + public void deleteElement(DataContext dataContext) { CommonModelManager.getInstance().deleteModelElements(myElements, myResponse); } - public boolean canDeleteElement(@Nonnull DataContext dataContext) { + public boolean canDeleteElement(DataContext dataContext) { for (CommonModelElement element : myElements) { if (element != null && !element.isValid()) return false; } diff --git a/jam-api/src/main/java/com/intellij/jam/view/tree/JamAbstractTreeBuilder.java b/jam-api/src/main/java/com/intellij/jam/view/tree/JamAbstractTreeBuilder.java index fe01f63ba0..f99325ba0e 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/tree/JamAbstractTreeBuilder.java +++ b/jam-api/src/main/java/com/intellij/jam/view/tree/JamAbstractTreeBuilder.java @@ -25,7 +25,6 @@ import consulo.ui.ex.tree.NodeDescriptor; import consulo.virtualFileSystem.VirtualFile; -import javax.annotation.Nonnull; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; @@ -56,7 +55,7 @@ public boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { return super.isAutoExpandNode(nodeDescriptor) || ((SimpleNode)nodeDescriptor).isAutoExpandNode(); } - protected final void expandNodeChildren(@Nonnull final DefaultMutableTreeNode node) { + protected final void expandNodeChildren(final DefaultMutableTreeNode node) { Object element = ((NodeDescriptor)node.getUserObject()).getElement(); VirtualFile virtualFile = null; if (element instanceof PsiDirectory){ diff --git a/jam-api/src/main/java/com/intellij/jam/view/tree/JamNodeDescriptor.java b/jam-api/src/main/java/com/intellij/jam/view/tree/JamNodeDescriptor.java index 7a02e0296a..a8675e0df2 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/tree/JamNodeDescriptor.java +++ b/jam-api/src/main/java/com/intellij/jam/view/tree/JamNodeDescriptor.java @@ -16,23 +16,21 @@ package com.intellij.jam.view.tree; -import consulo.dataContext.DataManager; -import consulo.language.editor.PlatformDataKeys; -import consulo.ui.ex.DeleteProvider; -import consulo.ui.ex.awt.tree.SimpleTree; -import consulo.ui.ex.tree.NodeDescriptor; -import consulo.ui.ex.awt.tree.ValidateableNode; import com.intellij.jam.JamMessages; +import consulo.dataContext.DataManager; import consulo.dataContext.DataProvider; import consulo.project.Project; import consulo.ui.color.ColorValue; -import consulo.util.dataholder.Key; -import consulo.util.lang.StringUtil; -import consulo.ui.ex.awt.tree.SimpleNode; +import consulo.ui.ex.DeleteProvider; import consulo.ui.ex.OpenSourceUtil; +import consulo.ui.ex.awt.tree.SimpleNode; +import consulo.ui.ex.awt.tree.SimpleTree; +import consulo.ui.ex.awt.tree.ValidateableNode; +import consulo.ui.ex.tree.NodeDescriptor; import consulo.ui.image.Image; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.util.dataholder.Key; +import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import java.awt.event.InputEvent; @@ -52,7 +50,6 @@ protected JamNodeDescriptor(Project project, NodeDescriptor parentDescriptor, Ob myElement = element; } - @Nonnull public Object[] getEqualityObjects() { return new Object[] { myElement }; } @@ -113,10 +110,7 @@ protected Object getParameters() { @Nullable public final Object getDataForElement(Key dataId) { - if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER == dataId) { - return getDeleteProvider(); - } - return getData(dataId); + return DeleteProvider.KEY == dataId ? getDeleteProvider() : getData(dataId); } public final P getElement() { diff --git a/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsDialog.java b/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsDialog.java index c771fb9b4b..c3aaa02b27 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsDialog.java +++ b/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsDialog.java @@ -34,9 +34,9 @@ import consulo.ui.ex.awt.util.TableUtil; import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; -import consulo.xml.psi.xml.XmlTag; +import consulo.xml.language.psi.XmlTag; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import javax.swing.*; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; diff --git a/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsToDeleteDialog.java b/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsToDeleteDialog.java index 099a30d174..c475e85a8f 100644 --- a/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsToDeleteDialog.java +++ b/jam-api/src/main/java/com/intellij/jam/view/ui/SelectElementsToDeleteDialog.java @@ -18,7 +18,6 @@ import com.intellij.jam.JamMessages; import consulo.project.Project; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; import javax.swing.*; import java.util.List; @@ -30,7 +29,6 @@ public SelectElementsToDeleteDialog(final List elements, final Proje getSelectedItems().addAll(elements); } - @Nonnull protected Action[] createActions() { getOKAction().putValue(Action.NAME, JamMessages.message("button.delete")); getCancelAction().putValue(Action.NAME, JamMessages.message("button.do.not.delete")); diff --git a/jam-api/src/main/java/consulo/java/jam/util/JamCommonService.java b/jam-api/src/main/java/consulo/java/jam/util/JamCommonService.java index 348f80b0a3..e2390e8773 100644 --- a/jam-api/src/main/java/consulo/java/jam/util/JamCommonService.java +++ b/jam-api/src/main/java/consulo/java/jam/util/JamCommonService.java @@ -19,7 +19,7 @@ import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.psi.PsiElement; /** @@ -29,7 +29,7 @@ @ServiceAPI(ComponentScope.APPLICATION) public interface JamCommonService { static JamCommonService getInstance() { - return ServiceManager.getService(JamCommonService.class); + return Application.get().getInstance(JamCommonService.class); } @RequiredReadAction diff --git a/jam-api/src/main/java/module-info.java b/jam-api/src/main/java/module-info.java index cbc92b0967..e3df51630d 100644 --- a/jam-api/src/main/java/module-info.java +++ b/jam-api/src/main/java/module-info.java @@ -3,20 +3,27 @@ * @since 02/12/2022 */ module consulo.java.jam.api { - requires transitive consulo.java.language.api; - requires transitive consulo.java.indexing.api; - requires transitive com.intellij.xml; + requires transitive consulo.java.language.api; + requires transitive consulo.java.indexing.api; + requires transitive com.intellij.xml.api; + requires transitive com.intellij.xml.dom.api; - // TODO remove in future - requires java.desktop; + requires consulo.language.impl; + requires consulo.ui.ex.awt.api; + requires consulo.ui.ex.api; + requires consulo.language.editor.api; + requires consulo.language.editor.refactoring.api; - exports com.intellij.jam; - exports com.intellij.jam.annotations; - exports com.intellij.jam.model.common; - exports com.intellij.jam.model.util; - exports com.intellij.jam.reflect; - exports com.intellij.jam.view; - exports com.intellij.jam.view.tree; - exports com.intellij.jam.view.ui; - exports consulo.java.jam.util; + // TODO remove in future + requires java.desktop; + + exports com.intellij.jam; + exports com.intellij.jam.annotations; + exports com.intellij.jam.model.common; + exports com.intellij.jam.model.util; + exports com.intellij.jam.reflect; + exports com.intellij.jam.view; + exports com.intellij.jam.view.tree; + exports com.intellij.jam.view.ui; + exports consulo.java.jam.util; } \ No newline at end of file diff --git a/jam-impl/pom.xml b/jam-impl/pom.xml index 4c64009598..3c2f342a02 100644 --- a/jam-impl/pom.xml +++ b/jam-impl/pom.xml @@ -21,7 +21,7 @@ consulo - arch.ide-api-provided + arch.bind.java 3-SNAPSHOT @@ -29,7 +29,7 @@ consulo - https://maven.consulo.io/repository/snapshots/ + https://maven.consulo.dev/repository/snapshots/ true interval:60 @@ -44,6 +44,18 @@ + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + consulo consulo-proxy ${project.version} diff --git a/jam-impl/src/main/java/consulo/java/jam/impl/JamClassGeneratorImpl.java b/jam-impl/src/main/java/consulo/java/jam/impl/JamClassGeneratorImpl.java index 38e26f76ae..d3bcb21171 100644 --- a/jam-impl/src/main/java/consulo/java/jam/impl/JamClassGeneratorImpl.java +++ b/jam-impl/src/main/java/consulo/java/jam/impl/JamClassGeneratorImpl.java @@ -1,14 +1,10 @@ package consulo.java.jam.impl; import com.intellij.jam.JamClassGenerator; -import com.intellij.jam.annotations.JamPsiConnector; import consulo.annotation.component.ServiceImpl; import consulo.language.psi.PsiElementRef; -import consulo.proxy.advanced.AdvancedProxyBuilder; import jakarta.inject.Singleton; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; import java.util.function.Function; /** @@ -17,36 +13,10 @@ */ @Singleton @ServiceImpl -public class JamClassGeneratorImpl extends JamClassGenerator { - private static class InvocationHandlerImpl implements InvocationHandler { - private Class myClass; - private PsiElementRef myPsiElementRef; - - public InvocationHandlerImpl(Class clazz, PsiElementRef psiElementRef) { - myPsiElementRef = psiElementRef; - myClass = clazz; - } - - @Override - public Object invoke(Object o, Method method, Object[] objects) throws Throwable { - String name = method.getName(); - switch (name) { - case "hashCode": - return myClass.hashCode(); - case "equals": - Object object = objects[0]; - return myClass.hashCode() == object.hashCode(); - default: - if(method.isAnnotationPresent(JamPsiConnector.class)) { - return myPsiElementRef.getPsiElement(); - } - throw new UnsupportedOperationException(method.getName() + " is not supported in " + myClass.getName()); - } - } - } +public class JamClassGeneratorImpl implements JamClassGenerator { @Override public Function generateJamElementFactory(Class aClass) { - return psiElementRef -> AdvancedProxyBuilder.create(aClass).withInvocationHandler(new InvocationHandlerImpl<>(aClass, psiElementRef)).build(); + return new JamElementFactory<>(aClass); } } diff --git a/jam-impl/src/main/java/consulo/java/jam/impl/JamCommonServiceImpl.java b/jam-impl/src/main/java/consulo/java/jam/impl/JamCommonServiceImpl.java index b8ddb74507..fec7918200 100644 --- a/jam-impl/src/main/java/consulo/java/jam/impl/JamCommonServiceImpl.java +++ b/jam-impl/src/main/java/consulo/java/jam/impl/JamCommonServiceImpl.java @@ -24,7 +24,7 @@ import consulo.java.jam.util.JamCommonService; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; -import consulo.xml.psi.xml.XmlFile; +import consulo.xml.language.psi.XmlFile; import jakarta.inject.Singleton; /** diff --git a/jam-impl/src/main/java/consulo/java/jam/impl/JamElementFactory.java b/jam-impl/src/main/java/consulo/java/jam/impl/JamElementFactory.java new file mode 100644 index 0000000000..4519b57c4a --- /dev/null +++ b/jam-impl/src/main/java/consulo/java/jam/impl/JamElementFactory.java @@ -0,0 +1,71 @@ +package consulo.java.jam.impl; + +import com.intellij.jam.annotations.JamPsiConnector; +import consulo.language.psi.PsiElementRef; +import consulo.proxy.advanced.AdvancedProxyBuilder; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.function.Function; + +/** + * @author VISTALL + * @since 2024-04-14 + */ +public class JamElementFactory implements Function { + private static class InvocationHandlerImpl implements InvocationHandler { + private Class myClass; + private PsiElementRef myPsiElementRef; + + public InvocationHandlerImpl(Class clazz, PsiElementRef psiElementRef) { + myPsiElementRef = psiElementRef; + myClass = clazz; + } + + @Override + public Object invoke(Object o, Method method, Object[] objects) throws Throwable { + String name = method.getName(); + switch (name) { + case "hashCode": + return myClass.hashCode(); + case "equals": + Object object = objects[0]; + return myClass.hashCode() == object.hashCode(); + default: + if (method.isAnnotationPresent(JamPsiConnector.class)) { + return myPsiElementRef.getPsiElement(); + } + throw new UnsupportedOperationException(method.getName() + " is not supported in " + myClass.getName()); + } + } + } + + private final Class myTargetClass; + + private final boolean myHasDefaultConstructor; + + public JamElementFactory(Class targetClass) { + this.myTargetClass = targetClass; + myHasDefaultConstructor = findDefaultConstructor(targetClass) != null; + } + + @Override + public T apply(PsiElementRef psiElementRef) { + AdvancedProxyBuilder builder = AdvancedProxyBuilder.create(myTargetClass); + if (!myHasDefaultConstructor) { + builder.withSuperConstructorArguments(psiElementRef.getPsiElement()); + } + builder.withInvocationHandler(new InvocationHandlerImpl<>(myTargetClass, psiElementRef)); + return builder.build(); + } + + private Constructor findDefaultConstructor(Class aClass) { + try { + return aClass.getConstructor(); + } + catch (NoSuchMethodException ignored) { + } + return null; + } +} diff --git a/java-analysis-api/pom.xml b/java-analysis-api/pom.xml index 839f1d743a..db8c4fd519 100644 --- a/java-analysis-api/pom.xml +++ b/java-analysis-api/pom.xml @@ -1,6 +1,6 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - consulo - arch.ide-api-provided - 3-SNAPSHOT - - + + consulo + arch.bind.java + 3-SNAPSHOT + + - - - consulo - https://maven.consulo.io/repository/snapshots/ - - true - interval:60 - - - + + + consulo + https://maven.consulo.dev/repository/snapshots/ + + true + interval:60 + + + - consulo.plugin - consulo.java-java.analysis.api - 3-SNAPSHOT - jar + consulo.plugin + consulo.java-java.analysis.api + 3-SNAPSHOT + jar - - - ${project.groupId} - consulo.java-java.language.api - ${project.version} - - + + + + consulo.maven + maven-consulo-plugin + true + + + generate-sources + + generate-localize + + + + + + + + + + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + + consulo + consulo-language-editor-api + ${project.version} + provided + + + consulo + consulo-datacontext-api + ${project.version} + provided + + + consulo + consulo-language-editor-refactoring-api + ${project.version} + provided + + + ${project.groupId} + consulo.java-java.language.api + ${project.version} + + \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/JavaAnalysisBundle.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/JavaAnalysisBundle.java index 9c0950a538..4256b48275 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/JavaAnalysisBundle.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/JavaAnalysisBundle.java @@ -1,16 +1,19 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.internal.MigratedExtensionsTo; import consulo.component.util.localize.AbstractBundle; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import org.jetbrains.annotations.PropertyKey; import java.util.function.Supplier; +@Deprecated +@DeprecationInfo("Use JavaAnalysisLocalize") +@MigratedExtensionsTo(JavaAnalysisLocalize.class) public final class JavaAnalysisBundle extends AbstractBundle { - @NonNls public static final String BUNDLE = "messages.JavaAnalysisBundle"; private static final JavaAnalysisBundle INSTANCE = new JavaAnalysisBundle(); @@ -19,14 +22,12 @@ private JavaAnalysisBundle() super(BUNDLE); } - @Nonnull - public static String message(@Nonnull @PropertyKey(resourceBundle = BUNDLE) String key, @Nonnull Object ...params) + public static String message(@PropertyKey(resourceBundle = BUNDLE) String key, Object ...params) { return INSTANCE.getMessage(key, params); } - @Nonnull - public static Supplier messagePointer(@Nonnull @PropertyKey(resourceBundle = BUNDLE) String key, Object... params) + public static Supplier messagePointer(@PropertyKey(resourceBundle = BUNDLE) String key, Object... params) { return () -> INSTANCE.getMessage(key, params); } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/JavaImplicitUsageProvider.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/JavaImplicitUsageProvider.java index d91d3e66ea..b3f1b7db90 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/JavaImplicitUsageProvider.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/JavaImplicitUsageProvider.java @@ -3,31 +3,30 @@ import consulo.language.editor.ImplicitUsageProvider; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; +public interface JavaImplicitUsageProvider extends ImplicitUsageProvider { + /** + * @return true if the given element is implicitly initialized to a non-null value + */ + default boolean isImplicitlyNotNullInitialized(PsiElement element) { + return false; + } -public interface JavaImplicitUsageProvider extends ImplicitUsageProvider -{ - /** - * @return true if the given element is implicitly initialized to a non-null value - */ - @Override - default boolean isImplicitlyNotNullInitialized(@Nonnull PsiElement element) - { - return false; - } + /** + * @return true if given element is represents a class (or another data structure declaration depending on language) + * which instances may have implicit initialization steps not directly available in the source code + * (e.g. Java class initializer is processed via annotation processor and custom steps added) + */ + default boolean isClassWithCustomizedInitialization(PsiElement element) { + return false; + } - /** - * @return true if given element is represents a class (or another data structure declaration depending on language) - * which instances may have implicit initialization steps not directly available in the source code - * (e.g. Java class initializer is processed via annotation processor and custom steps added) - */ - default boolean isClassWithCustomizedInitialization(@Nonnull PsiElement element) - { - return false; - } + // is "unused import" warning can be shown in this file + default boolean isUnusedImportEnabled(PsiElement element) { + return false; + } - static boolean isClassWithCustomizedInitialization(ImplicitUsageProvider provider, PsiElement element) - { - return provider instanceof JavaImplicitUsageProvider && ((JavaImplicitUsageProvider) provider).isClassWithCustomizedInitialization(element); - } + static boolean isClassWithCustomizedInitialization(ImplicitUsageProvider provider, PsiElement element) { + return provider instanceof JavaImplicitUsageProvider && ((JavaImplicitUsageProvider)provider).isClassWithCustomizedInitialization( + element); + } } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/UnusedImportProvider.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/UnusedImportProvider.java deleted file mode 100644 index 7bad759d53..0000000000 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/daemon/UnusedImportProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.java.analysis.codeInsight.daemon; - -import consulo.language.psi.PsiElement; - -import javax.annotation.Nonnull; - -public interface UnusedImportProvider -{ - // is "unused import" warning can be shown in this file - boolean isUnusedImportEnabled(@Nonnull PsiElement element); -} diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/guess/GuessManager.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/guess/GuessManager.java index 1b3802b227..002af3769d 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/guess/GuessManager.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/guess/GuessManager.java @@ -21,33 +21,26 @@ import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; import consulo.document.util.TextRange; -import consulo.ide.ServiceManager; import consulo.project.Project; import consulo.util.collection.MultiMap; -import javax.annotation.Nonnull; import java.util.List; @ServiceAPI(ComponentScope.PROJECT) public abstract class GuessManager { public static GuessManager getInstance(Project project) { - return ServiceManager.getService(project, GuessManager.class); + return project.getInstance(GuessManager.class); } - @Nonnull public abstract PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore); - @Nonnull public abstract PsiType[] guessTypeToCast(PsiExpression expr); - @Nonnull - public abstract MultiMap getControlFlowExpressionTypes(@Nonnull PsiExpression forPlace, boolean honorAssignments); + public abstract MultiMap getControlFlowExpressionTypes(PsiExpression forPlace, boolean honorAssignments); - @Nonnull - public List getControlFlowExpressionTypeConjuncts(@Nonnull PsiExpression expr) { + public List getControlFlowExpressionTypeConjuncts(PsiExpression expr) { return getControlFlowExpressionTypeConjuncts(expr, true); } - @Nonnull - public abstract List getControlFlowExpressionTypeConjuncts(@Nonnull PsiExpression expr, boolean honorAssignments); + public abstract List getControlFlowExpressionTypeConjuncts(PsiExpression expr, boolean honorAssignments); } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/intention/QuickFixFactory.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/intention/QuickFixFactory.java index 4d04a288cd..a9eec90f4b 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/intention/QuickFixFactory.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInsight/intention/QuickFixFactory.java @@ -18,9 +18,11 @@ import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PropertyMemberType; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.ast.IElementType; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; @@ -30,12 +32,12 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiNamedElement; import consulo.language.psi.PsiReference; +import consulo.localize.LocalizeValue; import consulo.module.Module; import consulo.project.Project; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.Collection; import java.util.List; import java.util.Set; @@ -45,373 +47,395 @@ */ @ServiceAPI(ComponentScope.APPLICATION) public abstract class QuickFixFactory { - public static QuickFixFactory getInstance() { - return ServiceManager.getService(QuickFixFactory.class); - } + public interface ModifierFixBuilder { + default ModifierFixBuilder add(@PsiModifier.ModifierConstant String modifier) { + return toggle(modifier, true); + } + + default ModifierFixBuilder remove(@PsiModifier.ModifierConstant String modifier) { + return toggle(modifier, false); + } + + ModifierFixBuilder toggle(@PsiModifier.ModifierConstant String modifier, boolean shouldHave); + + ModifierFixBuilder showContainingClass(); + + @RequiredReadAction + public abstract LocalQuickFixAndIntentionActionOnPsiElement create(); + } + + public static QuickFixFactory getInstance() { + return Application.get().getInstance(QuickFixFactory.class); + } + + public abstract ModifierFixBuilder createModifierFixBuilder(PsiModifierList modifierList); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createModifierListFix(@Nonnull PsiModifierList modifierList, - @PsiModifier.ModifierConstant @Nonnull String modifier, - boolean shouldHave, - final boolean showContainingClass); + public abstract ModifierFixBuilder createModifierFixBuilder(PsiModifierListOwner owner); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createModifierListFix(@Nonnull PsiModifierListOwner owner, - @PsiModifier.ModifierConstant @Nonnull String modifier, - boolean shouldHave, - final boolean showContainingClass); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createMethodReturnFix( + PsiMethod method, + PsiType toReturn, + boolean fixWholeHierarchy + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createMethodReturnFix(@Nonnull PsiMethod method, @Nonnull PsiType toReturn, boolean fixWholeHierarchy); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddMethodFix(PsiMethod method, PsiClass toClass); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddMethodFix(@Nonnull PsiMethod method, @Nonnull PsiClass toClass); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddMethodFix( + String methodText, + PsiClass toClass, + String... exceptions + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddMethodFix(@Nonnull String methodText, @Nonnull PsiClass toClass, @Nonnull String... exceptions); + /** + * @param psiElement psiClass or enum constant without class initializer + */ + public abstract LocalQuickFixAndIntentionActionOnPsiElement createImplementMethodsFix(PsiElement psiElement); - /** - * @param psiElement psiClass or enum constant without class initializer - */ - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createImplementMethodsFix(@Nonnull PsiElement psiElement); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAssignmentToComparisonFix(PsiAssignmentExpression expr); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAssignmentToComparisonFix(PsiAssignmentExpression expr); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createImplementMethodsFix(PsiClass psiElement); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createImplementMethodsFix(@Nonnull PsiClass psiElement); + public abstract LocalQuickFixOnPsiElement createMethodThrowsFix( + PsiMethod method, + PsiClassType exceptionClass, + boolean shouldThrow, + boolean showContainingClass + ); - @Nonnull - public abstract LocalQuickFixOnPsiElement createMethodThrowsFix(@Nonnull PsiMethod method, @Nonnull PsiClassType exceptionClass, boolean shouldThrow, boolean showContainingClass); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddDefaultConstructorFix(PsiClass aClass); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddDefaultConstructorFix(@Nonnull PsiClass aClass); + @Nullable + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddConstructorFix( + PsiClass aClass, + @PsiModifier.ModifierConstant String modifier + ); - @Nullable - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAddConstructorFix(@Nonnull PsiClass aClass, @PsiModifier.ModifierConstant @Nonnull String modifier); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createMethodParameterTypeFix( + PsiMethod method, + int index, + PsiType newType, + boolean fixWholeHierarchy + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createMethodParameterTypeFix(@Nonnull PsiMethod method, int index, @Nonnull PsiType newType, boolean fixWholeHierarchy); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createMakeClassInterfaceFix(PsiClass aClass); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createMakeClassInterfaceFix(@Nonnull PsiClass aClass); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createMakeClassInterfaceFix( + PsiClass aClass, + final boolean makeInterface + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createMakeClassInterfaceFix(@Nonnull PsiClass aClass, final boolean makeInterface); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createExtendsListFix( + PsiClass aClass, + PsiClassType typeToExtendFrom, + boolean toAdd + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createExtendsListFix(@Nonnull PsiClass aClass, @Nonnull PsiClassType typeToExtendFrom, boolean toAdd); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createRemoveUnusedParameterFix(PsiParameter parameter); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createRemoveUnusedParameterFix(@Nonnull PsiParameter parameter); + public abstract IntentionAction createRemoveUnusedVariableFix(PsiVariable variable); - @Nonnull - public abstract IntentionAction createRemoveUnusedVariableFix(@Nonnull PsiVariable variable); + @Nullable + public abstract IntentionAction createCreateClassOrPackageFix( + PsiElement context, + String qualifiedName, + final boolean createClass, + final String superClass + ); - @Nullable - public abstract IntentionAction createCreateClassOrPackageFix(@Nonnull PsiElement context, @Nonnull String qualifiedName, final boolean createClass, final String superClass); + @Nullable + public abstract IntentionAction createCreateClassOrInterfaceFix( + PsiElement context, + String qualifiedName, + final boolean createClass, + final String superClass + ); - @Nullable - public abstract IntentionAction createCreateClassOrInterfaceFix(@Nonnull PsiElement context, @Nonnull String qualifiedName, final boolean createClass, final String superClass); + public abstract IntentionAction createCreateFieldOrPropertyFix( + PsiClass aClass, + String name, + PsiType type, + PropertyMemberType targetMember, + PsiAnnotation... annotations + ); - @Nonnull - public abstract IntentionAction createCreateFieldOrPropertyFix(@Nonnull PsiClass aClass, - @Nonnull String name, - @Nonnull PsiType type, - @Nonnull PropertyMemberType targetMember, - @Nonnull PsiAnnotation... annotations); + public abstract IntentionAction createSetupJDKFix(); - @Nonnull - public abstract IntentionAction createSetupJDKFix(); + public abstract IntentionAction createAddExceptionToCatchFix(); - @Nonnull - public abstract IntentionAction createAddExceptionToCatchFix(); + public abstract IntentionAction createAddExceptionToThrowsFix(PsiElement element); - @Nonnull - public abstract IntentionAction createAddExceptionToThrowsFix(@Nonnull PsiElement element); + public abstract IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(PsiElement element); - @Nonnull - public abstract IntentionAction createAddExceptionFromFieldInitializerToConstructorThrowsFix(@Nonnull PsiElement element); + public abstract IntentionAction createSurroundWithTryCatchFix(PsiElement element); - @Nonnull - public abstract IntentionAction createSurroundWithTryCatchFix(@Nonnull PsiElement element); + public abstract IntentionAction createGeneralizeCatchFix(PsiElement element, PsiClassType type); - @Nonnull - public abstract IntentionAction createGeneralizeCatchFix(@Nonnull PsiElement element, @Nonnull PsiClassType type); + public abstract IntentionAction createChangeToAppendFix( + IElementType sign, + PsiType type, + PsiAssignmentExpression assignment + ); - @Nonnull - public abstract IntentionAction createChangeToAppendFix(@Nonnull IElementType sign, @Nonnull PsiType type, @Nonnull PsiAssignmentExpression assignment); + public abstract IntentionAction createAddTypeCastFix(PsiType type, PsiExpression expression); - @Nonnull - public abstract IntentionAction createAddTypeCastFix(@Nonnull PsiType type, @Nonnull PsiExpression expression); + public abstract IntentionAction createWrapExpressionFix(PsiType type, PsiExpression expression); - @Nonnull - public abstract IntentionAction createWrapExpressionFix(@Nonnull PsiType type, @Nonnull PsiExpression expression); + public abstract IntentionAction createReuseVariableDeclarationFix(PsiLocalVariable variable); - @Nonnull - public abstract IntentionAction createReuseVariableDeclarationFix(@Nonnull PsiLocalVariable variable); + public abstract IntentionAction createConvertToStringLiteralAction(); - @Nonnull - public abstract IntentionAction createConvertToStringLiteralAction(); + public abstract IntentionAction createDeleteCatchFix(PsiParameter parameter); - @Nonnull - public abstract IntentionAction createDeleteCatchFix(@Nonnull PsiParameter parameter); + public abstract IntentionAction createDeleteMultiCatchFix(PsiTypeElement element); - @Nonnull - public abstract IntentionAction createDeleteMultiCatchFix(@Nonnull PsiTypeElement element); + public abstract IntentionAction createConvertSwitchToIfIntention(PsiSwitchStatement statement); - @Nonnull - public abstract IntentionAction createConvertSwitchToIfIntention(@Nonnull PsiSwitchStatement statement); + public abstract IntentionAction createNegationBroadScopeFix(PsiPrefixExpression expr); - @Nonnull - public abstract IntentionAction createNegationBroadScopeFix(@Nonnull PsiPrefixExpression expr); + public abstract IntentionAction createCreateFieldFromUsageFix(PsiReferenceExpression place); - @Nonnull - public abstract IntentionAction createCreateFieldFromUsageFix(@Nonnull PsiReferenceExpression place); + public abstract IntentionAction createReplaceWithListAccessFix(PsiArrayAccessExpression expression); - @Nonnull - public abstract IntentionAction createReplaceWithListAccessFix(@Nonnull PsiArrayAccessExpression expression); + public abstract IntentionAction createAddNewArrayExpressionFix(PsiArrayInitializerExpression expression); - @Nonnull - public abstract IntentionAction createAddNewArrayExpressionFix(@Nonnull PsiArrayInitializerExpression expression); + public abstract IntentionAction createMoveCatchUpFix(PsiCatchSection section, PsiCatchSection section1); - @Nonnull - public abstract IntentionAction createMoveCatchUpFix(@Nonnull PsiCatchSection section, @Nonnull PsiCatchSection section1); + public abstract IntentionAction createRenameWrongRefFix(PsiReferenceExpression ref); - @Nonnull - public abstract IntentionAction createRenameWrongRefFix(@Nonnull PsiReferenceExpression ref); + public abstract IntentionAction createRemoveQualifierFix( + PsiExpression qualifier, + PsiReferenceExpression expression, + PsiClass resolved + ); - @Nonnull - public abstract IntentionAction createRemoveQualifierFix(@Nonnull PsiExpression qualifier, @Nonnull PsiReferenceExpression expression, @Nonnull PsiClass resolved); + public abstract IntentionAction createRemoveParameterListFix(PsiMethod parent); - @Nonnull - public abstract IntentionAction createRemoveParameterListFix(@Nonnull PsiMethod parent); + public abstract IntentionAction createShowModulePropertiesFix(PsiElement element); - @Nonnull - public abstract IntentionAction createShowModulePropertiesFix(@Nonnull PsiElement element); + public abstract IntentionAction createShowModulePropertiesFix(Module module); - @Nonnull - public abstract IntentionAction createShowModulePropertiesFix(@Nonnull Module module); + public abstract IntentionAction createIncreaseLanguageLevelFix(LanguageLevel level); - @Nonnull - public abstract IntentionAction createIncreaseLanguageLevelFix(@Nonnull LanguageLevel level); + public abstract IntentionAction createChangeParameterClassFix(PsiClass aClass, PsiClassType type); - @Nonnull - public abstract IntentionAction createChangeParameterClassFix(@Nonnull PsiClass aClass, @Nonnull PsiClassType type); + public abstract IntentionAction createReplaceInaccessibleFieldWithGetterSetterFix( + PsiElement element, + PsiMethod getter, + boolean isSetter + ); - @Nonnull - public abstract IntentionAction createReplaceInaccessibleFieldWithGetterSetterFix(@Nonnull PsiElement element, @Nonnull PsiMethod getter, boolean isSetter); + public abstract IntentionAction createSurroundWithArrayFix(@Nullable PsiCall methodCall, @Nullable PsiExpression expression); - @Nonnull - public abstract IntentionAction createSurroundWithArrayFix(@Nullable PsiCall methodCall, @Nullable PsiExpression expression); + public abstract IntentionAction createImplementAbstractClassMethodsFix(PsiElement elementToHighlight); - @Nonnull - public abstract IntentionAction createImplementAbstractClassMethodsFix(@Nonnull PsiElement elementToHighlight); + public abstract IntentionAction createMoveClassToSeparateFileFix(PsiClass aClass); - @Nonnull - public abstract IntentionAction createMoveClassToSeparateFileFix(@Nonnull PsiClass aClass); + public abstract IntentionAction createRenameFileFix(String newName); - @Nonnull - public abstract IntentionAction createRenameFileFix(@Nonnull String newName); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createRenameElementFix(PsiNamedElement element); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createRenameElementFix(@Nonnull PsiNamedElement element); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createRenameElementFix( + PsiNamedElement element, + String newName + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createRenameElementFix(@Nonnull PsiNamedElement element, @Nonnull String newName); + public abstract IntentionAction createChangeExtendsToImplementsFix(PsiClass aClass, PsiClassType classToExtendFrom); - @Nonnull - public abstract IntentionAction createChangeExtendsToImplementsFix(@Nonnull PsiClass aClass, @Nonnull PsiClassType classToExtendFrom); + public abstract IntentionAction createCreateConstructorMatchingSuperFix(PsiClass aClass); - @Nonnull - public abstract IntentionAction createCreateConstructorMatchingSuperFix(@Nonnull PsiClass aClass); + public abstract IntentionAction createRemoveNewQualifierFix(PsiNewExpression expression, @Nullable PsiClass aClass); - @Nonnull - public abstract IntentionAction createRemoveNewQualifierFix(@Nonnull PsiNewExpression expression, @Nullable PsiClass aClass); + public abstract IntentionAction createSuperMethodReturnFix(PsiMethod superMethod, PsiType superMethodType); - @Nonnull - public abstract IntentionAction createSuperMethodReturnFix(@Nonnull PsiMethod superMethod, @Nonnull PsiType superMethodType); + public abstract IntentionAction createInsertNewFix(PsiMethodCallExpression call, PsiClass aClass); - @Nonnull - public abstract IntentionAction createInsertNewFix(@Nonnull PsiMethodCallExpression call, @Nonnull PsiClass aClass); + public abstract IntentionAction createAddMethodBodyFix(PsiMethod method); - @Nonnull - public abstract IntentionAction createAddMethodBodyFix(@Nonnull PsiMethod method); + public abstract IntentionAction createDeleteMethodBodyFix(PsiMethod method); - @Nonnull - public abstract IntentionAction createDeleteMethodBodyFix(@Nonnull PsiMethod method); + public abstract IntentionAction createInsertSuperFix(PsiMethod constructor); - @Nonnull - public abstract IntentionAction createInsertSuperFix(@Nonnull PsiMethod constructor); + public abstract IntentionAction createInsertThisFix(PsiMethod constructor); - @Nonnull - public abstract IntentionAction createInsertThisFix(@Nonnull PsiMethod constructor); + public abstract IntentionAction createChangeMethodSignatureFromUsageFix( + PsiMethod targetMethod, + PsiExpression[] expressions, + PsiSubstitutor substitutor, + PsiElement context, + boolean changeAllUsages, + int minUsagesNumberToShowDialog + ); - @Nonnull - public abstract IntentionAction createChangeMethodSignatureFromUsageFix(@Nonnull PsiMethod targetMethod, - @Nonnull PsiExpression[] expressions, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiElement context, - boolean changeAllUsages, - int minUsagesNumberToShowDialog); + public abstract IntentionAction createChangeMethodSignatureFromUsageReverseOrderFix( + PsiMethod targetMethod, + PsiExpression[] expressions, + PsiSubstitutor substitutor, + PsiElement context, + boolean changeAllUsages, + int minUsagesNumberToShowDialog + ); - @Nonnull - public abstract IntentionAction createChangeMethodSignatureFromUsageReverseOrderFix(@Nonnull PsiMethod targetMethod, - @Nonnull PsiExpression[] expressions, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiElement context, - boolean changeAllUsages, - int minUsagesNumberToShowDialog); + public abstract IntentionAction createCreateMethodFromUsageFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreateMethodFromUsageFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreateMethodFromUsageFix(PsiMethodReferenceExpression methodReferenceExpression); - @Nonnull - public abstract IntentionAction createCreateMethodFromUsageFix(PsiMethodReferenceExpression methodReferenceExpression); + public abstract IntentionAction createCreateAbstractMethodFromUsageFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreateAbstractMethodFromUsageFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreatePropertyFromUsageFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreatePropertyFromUsageFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreateConstructorFromSuperFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreateConstructorFromSuperFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreateConstructorFromThisFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreateConstructorFromThisFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreateGetterSetterPropertyFromUsageFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createCreateGetterSetterPropertyFromUsageFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createStaticImportMethodFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createStaticImportMethodFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createReplaceAddAllArrayToCollectionFix(PsiMethodCallExpression call); - @Nonnull - public abstract IntentionAction createReplaceAddAllArrayToCollectionFix(@Nonnull PsiMethodCallExpression call); + public abstract IntentionAction createCreateConstructorFromCallFix(PsiConstructorCall call); - @Nonnull - public abstract IntentionAction createCreateConstructorFromCallFix(@Nonnull PsiConstructorCall call); + public abstract List getVariableTypeFromCallFixes( + PsiMethodCallExpression call, + PsiExpressionList list + ); - @Nonnull - public abstract List getVariableTypeFromCallFixes(@Nonnull PsiMethodCallExpression call, @Nonnull PsiExpressionList list); + public abstract IntentionAction createAddReturnFix(PsiMethod method); - @Nonnull - public abstract IntentionAction createAddReturnFix(@Nonnull PsiMethod method); + public abstract IntentionAction createAddVariableInitializerFix(PsiVariable variable); - @Nonnull - public abstract IntentionAction createAddVariableInitializerFix(@Nonnull PsiVariable variable); + public abstract IntentionAction createDeferFinalAssignmentFix( + PsiVariable variable, + PsiReferenceExpression expression + ); - @Nonnull - public abstract IntentionAction createDeferFinalAssignmentFix(@Nonnull PsiVariable variable, @Nonnull PsiReferenceExpression expression); + public abstract IntentionAction createVariableAccessFromInnerClassFix(PsiVariable variable, PsiElement scope); - @Nonnull - public abstract IntentionAction createVariableAccessFromInnerClassFix(@Nonnull PsiVariable variable, @Nonnull PsiElement scope); + public abstract IntentionAction createCreateConstructorParameterFromFieldFix(PsiField field); - @Nonnull - public abstract IntentionAction createCreateConstructorParameterFromFieldFix(@Nonnull PsiField field); + public abstract IntentionAction createInitializeFinalFieldInConstructorFix(PsiField field); - @Nonnull - public abstract IntentionAction createInitializeFinalFieldInConstructorFix(@Nonnull PsiField field); + public abstract IntentionAction createRemoveTypeArgumentsFix(PsiElement variable); - @Nonnull - public abstract IntentionAction createRemoveTypeArgumentsFix(@Nonnull PsiElement variable); + public abstract IntentionAction createChangeClassSignatureFromUsageFix( + PsiClass owner, + PsiReferenceParameterList parameterList + ); - @Nonnull - public abstract IntentionAction createChangeClassSignatureFromUsageFix(@Nonnull PsiClass owner, @Nonnull PsiReferenceParameterList parameterList); + public abstract IntentionAction createReplacePrimitiveWithBoxedTypeAction( + PsiTypeElement element, + String typeName, + String boxedTypeName + ); - @Nonnull - public abstract IntentionAction createReplacePrimitiveWithBoxedTypeAction(@Nonnull PsiTypeElement element, @Nonnull String typeName, @Nonnull String boxedTypeName); + public abstract IntentionAction createMakeVarargParameterLastFix(PsiParameter parameter); - @Nonnull - public abstract IntentionAction createMakeVarargParameterLastFix(@Nonnull PsiParameter parameter); + public abstract IntentionAction createMoveBoundClassToFrontFix(PsiClass aClass, PsiClassType type); - @Nonnull - public abstract IntentionAction createMoveBoundClassToFrontFix(@Nonnull PsiClass aClass, @Nonnull PsiClassType type); + public abstract void registerPullAsAbstractUpFixes(PsiMethod method, QuickFixActionRegistrar registrar); - public abstract void registerPullAsAbstractUpFixes(@Nonnull PsiMethod method, @Nonnull QuickFixActionRegistrar registrar); + public abstract IntentionAction createCreateAnnotationMethodFromUsageFix(PsiNameValuePair pair); - @Nonnull - public abstract IntentionAction createCreateAnnotationMethodFromUsageFix(@Nonnull PsiNameValuePair pair); + public abstract IntentionAction createOptimizeImportsFix(boolean onTheFly); - @Nonnull - public abstract IntentionAction createOptimizeImportsFix(boolean onTheFly); + public abstract void registerFixesForUnusedParameter(PsiParameter parameter, Object highlightInfo); - public abstract void registerFixesForUnusedParameter(@Nonnull PsiParameter parameter, @Nonnull Object highlightInfo); + public abstract IntentionAction createAddToDependencyInjectionAnnotationsFix( + Project project, + String qualifiedName, + String element + ); - @Nonnull - public abstract IntentionAction createAddToDependencyInjectionAnnotationsFix(@Nonnull Project project, @Nonnull String qualifiedName, @Nonnull String element); + public abstract IntentionAction createAddToImplicitlyWrittenFieldsFix(Project project, String qualifiedName); - @Nonnull - public abstract IntentionAction createAddToImplicitlyWrittenFieldsFix(Project project, @Nonnull String qualifiedName); + public abstract IntentionAction createCreateGetterOrSetterFix(boolean createGetter, boolean createSetter, PsiField field); - @Nonnull - public abstract IntentionAction createCreateGetterOrSetterFix(boolean createGetter, boolean createSetter, @Nonnull PsiField field); + public abstract IntentionAction createRenameToIgnoredFix(PsiNamedElement namedElement); - @Nonnull - public abstract IntentionAction createRenameToIgnoredFix(@Nonnull PsiNamedElement namedElement); + public abstract IntentionAction createEnableOptimizeImportsOnTheFlyFix(); - @Nonnull - public abstract IntentionAction createEnableOptimizeImportsOnTheFlyFix(); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createDeleteFix(PsiElement element); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createDeleteFix(@Nonnull PsiElement element); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createDeleteFix(PsiElement element, LocalizeValue text); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createDeleteFix(@Nonnull PsiElement element, @Nonnull @Nls String text); + public abstract IntentionAction createDeleteSideEffectAwareFix(PsiExpressionStatement statement); - @Nonnull - public abstract IntentionAction createDeleteSideEffectAwareFix(@Nonnull PsiExpressionStatement statement); + public abstract IntentionAction createSafeDeleteFix(PsiElement element); - @Nonnull - public abstract IntentionAction createSafeDeleteFix(@Nonnull PsiElement element); + @Nullable + public abstract List registerOrderEntryFixes(PsiReference reference); - @Nullable - public abstract List registerOrderEntryFixes(@Nonnull QuickFixActionRegistrar registrar, @Nonnull PsiReference reference); + public abstract IntentionAction createAddMissingRequiredAnnotationParametersFix( + PsiAnnotation annotation, + PsiMethod[] annotationMethods, + Collection missedElements + ); - @Nonnull - public abstract IntentionAction createAddMissingRequiredAnnotationParametersFix(@Nonnull PsiAnnotation annotation, - @Nonnull PsiMethod[] annotationMethods, - @Nonnull Collection missedElements); + public abstract IntentionAction createSurroundWithQuotesAnnotationParameterValueFix( + PsiAnnotationMemberValue value, + PsiType expectedType + ); - @Nonnull - public abstract IntentionAction createSurroundWithQuotesAnnotationParameterValueFix(@Nonnull PsiAnnotationMemberValue value, @Nonnull PsiType expectedType); + public abstract IntentionAction addMethodQualifierFix(PsiMethodCallExpression methodCall); - @Nonnull - public abstract IntentionAction addMethodQualifierFix(@Nonnull PsiMethodCallExpression methodCall); + public abstract IntentionAction createWrapWithAdapterFix(@Nullable PsiType type, PsiExpression expression); - @Nonnull - public abstract IntentionAction createWrapWithAdapterFix(@Nullable PsiType type, @Nonnull PsiExpression expression); + public abstract IntentionAction createWrapWithOptionalFix(@Nullable PsiType type, PsiExpression expression); - @Nonnull - public abstract IntentionAction createWrapWithOptionalFix(@Nullable PsiType type, @Nonnull PsiExpression expression); + @Nullable + public abstract IntentionAction createNotIterableForEachLoopFix(PsiExpression expression); - @Nullable - public abstract IntentionAction createNotIterableForEachLoopFix(@Nonnull PsiExpression expression); + public abstract List createAddAnnotationAttributeNameFixes(PsiNameValuePair pair); - @Nonnull - public abstract List createAddAnnotationAttributeNameFixes(@Nonnull PsiNameValuePair pair); + public abstract IntentionAction createCollectionToArrayFix( + PsiExpression collectionExpression, + PsiArrayType arrayType + ); - @Nonnull - public abstract IntentionAction createCollectionToArrayFix(@Nonnull PsiExpression collectionExpression, @Nonnull PsiArrayType arrayType); + public abstract IntentionAction createInsertMethodCallFix(PsiMethodCallExpression call, PsiMethod method); - @Nonnull - public abstract IntentionAction createInsertMethodCallFix(@Nonnull PsiMethodCallExpression call, PsiMethod method); + public abstract LocalQuickFixAndIntentionActionOnPsiElement createAccessStaticViaInstanceFix( + PsiReferenceExpression methodRef, + JavaResolveResult result + ); - @Nonnull - public abstract LocalQuickFixAndIntentionActionOnPsiElement createAccessStaticViaInstanceFix(PsiReferenceExpression methodRef, JavaResolveResult result); + public abstract IntentionAction createWrapStringWithFileFix(@Nullable PsiType type, PsiExpression expression); - @Nonnull - public abstract IntentionAction createWrapStringWithFileFix(@Nullable PsiType type, @Nonnull PsiExpression expression); + public abstract IntentionAction createAddMissingEnumBranchesFix(PsiSwitchBlock switchBlock, Set missingCases); - @Nonnull - public abstract IntentionAction createAddMissingEnumBranchesFix(@Nonnull PsiSwitchBlock switchBlock, @Nonnull Set missingCases); + public abstract IntentionAction createAddSwitchDefaultFix(PsiSwitchBlock switchBlock, LocalizeValue message); - @Nonnull - public abstract IntentionAction createAddSwitchDefaultFix(@Nonnull PsiSwitchBlock switchBlock, @Nullable String message); + public abstract IntentionAction createWrapSwitchRuleStatementsIntoBlockFix(PsiSwitchLabeledRuleStatement rule); - @Nonnull - public abstract IntentionAction createWrapSwitchRuleStatementsIntoBlockFix(PsiSwitchLabeledRuleStatement rule); + @Deprecated + @DeprecationInfo("Use #fixModifiers()...#build()") + @RequiredReadAction + public LocalQuickFixAndIntentionActionOnPsiElement createModifierListFix( + PsiModifierList modifierList, + @PsiModifier.ModifierConstant String modifier, + boolean shouldHave, + boolean showContainingClass + ) { + ModifierFixBuilder builder = createModifierFixBuilder(modifierList).toggle(modifier, shouldHave); + if (showContainingClass) { + builder.showContainingClass(); + } + return builder.create(); + } + @Deprecated + @DeprecationInfo("Use #fixModifiers()...#build()") + @RequiredReadAction + public LocalQuickFixAndIntentionActionOnPsiElement createModifierListFix( + PsiModifierListOwner owner, + @PsiModifier.ModifierConstant String modifier, + boolean shouldHave, + boolean showContainingClass + ) { + ModifierFixBuilder builder = createModifierFixBuilder(owner).toggle(modifier, shouldHave); + if (showContainingClass) { + builder.showContainingClass(); + } + return builder.create(); + } } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/AbstractBaseJavaLocalInspectionTool.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/AbstractBaseJavaLocalInspectionTool.java index 3135d1fe0e..b6f967b47a 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/AbstractBaseJavaLocalInspectionTool.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/AbstractBaseJavaLocalInspectionTool.java @@ -15,15 +15,18 @@ */ package com.intellij.java.analysis.codeInspection; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.psi.JavaElementVisitor; import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiField; import com.intellij.java.language.psi.PsiMethod; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.deadCodeNotWorking.OldStyleInspection; import consulo.language.Language; -import consulo.language.editor.inspection.LocalInspectionTool; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemsHolder; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionManager; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; @@ -31,123 +34,179 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.PsiNamedElement; import consulo.language.psi.util.PsiTreeUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public abstract class AbstractBaseJavaLocalInspectionTool extends LocalInspectionTool { - /** - * Override this to report problems at method level. - * - * @param method to check. - * @param manager InspectionManager to ask for ProblemDescriptors from. - * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. - * @return null if no problems found or not applicable at method level. - */ - @Nullable - public ProblemDescriptor[] checkMethod(@Nonnull PsiMethod method, @Nonnull InspectionManager manager, boolean isOnTheFly) { - return null; - } - - /** - * Override this to report problems at class level. - * - * @param aClass to check. - * @param manager InspectionManager to ask for ProblemDescriptors from. - * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. - * @return null if no problems found or not applicable at class level. - */ - @Nullable - public ProblemDescriptor[] checkClass(@Nonnull PsiClass aClass, @Nonnull InspectionManager manager, boolean isOnTheFly) { - return null; - } - - /** - * Override this to report problems at field level. - * - * @param field to check. - * @param manager InspectionManager to ask for ProblemDescriptors from. - * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. - * @return null if no problems found or not applicable at field level. - */ - @Nullable - public ProblemDescriptor[] checkField(@Nonnull PsiField field, @Nonnull InspectionManager manager, boolean isOnTheFly) { - return null; - } - - /** - * Override this to report problems at file level. - * - * @param file to check. - * @param manager InspectionManager to ask for ProblemDescriptors from. - * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. - * @return null if no problems found or not applicable at file level. - */ - @Override - @Nullable - public ProblemDescriptor[] checkFile(@Nonnull PsiFile file, @Nonnull InspectionManager manager, boolean isOnTheFly) { - return null; - } - - @Override - @Nonnull - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, final boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitMethod(PsiMethod method) { - addDescriptors(checkMethod(method, holder.getManager(), isOnTheFly)); - } - - @Override - public void visitClass(PsiClass aClass) { - addDescriptors(checkClass(aClass, holder.getManager(), isOnTheFly)); - } - - @Override - public void visitField(PsiField field) { - addDescriptors(checkField(field, holder.getManager(), isOnTheFly)); - } - - @Override - public void visitFile(PsiFile file) { - addDescriptors(checkFile(file, holder.getManager(), isOnTheFly)); - } - - private void addDescriptors(final ProblemDescriptor[] descriptors) { - if (descriptors != null) { - for (ProblemDescriptor descriptor : descriptors) { - holder.registerProblem(descriptor); - } +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; + +import java.util.Set; + +public abstract class AbstractBaseJavaLocalInspectionTool extends LocalInspectionTool implements OldStyleInspection { + /** + * @return set of the features required for a given inspection. The inspection will not be launched on the files where + * the corresponding features are not available. + */ + public Set requiredFeatures() { + return Set.of(); + } + + @Override + @RequiredReadAction + public boolean isAvailableForFile(PsiFile file) { + for (JavaFeature feature : requiredFeatures()) { + if (!PsiUtil.isAvailable(feature, file)) { + return false; + } } - } - }; - } - - @Override - public PsiNamedElement getProblemElement(final PsiElement psiElement) { - return PsiTreeUtil.getNonStrictParentOfType(psiElement, PsiFile.class, PsiClass.class, PsiMethod.class, PsiField.class); - } - - @Override - public boolean isEnabledByDefault() { - return false; - } - - @Nullable - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } - - @Nonnull - @Override - public String getGroupDisplayName() { - return "General"; - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } + return true; + } + + /** + * Override this to report problems at method level. + * + * @param method to check. + * @param manager InspectionManager to ask for ProblemDescriptors from. + * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. + * @param state + * @return null if no problems found or not applicable at method level. + */ + @Nullable + public ProblemDescriptor[] checkMethod(PsiMethod method, InspectionManager manager, boolean isOnTheFly, State state) { + return null; + } + + /** + * Override this to report problems at class level. + * + * @param aClass to check. + * @param manager InspectionManager to ask for ProblemDescriptors from. + * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. + * @param state + * @return null if no problems found or not applicable at class level. + */ + @Nullable + public ProblemDescriptor[] checkClass(PsiClass aClass, InspectionManager manager, boolean isOnTheFly, State state) { + return null; + } + + /** + * Override this to report problems at field level. + * + * @param field to check. + * @param manager InspectionManager to ask for ProblemDescriptors from. + * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. + * @param state + * @return null if no problems found or not applicable at field level. + */ + @Nullable + public ProblemDescriptor[] checkField(PsiField field, InspectionManager manager, boolean isOnTheFly, State state) { + return null; + } + + @Override + @Nullable + public final ProblemDescriptor[] checkFile(PsiFile file, InspectionManager manager, boolean isOnTheFly) { + return null; + } + + /** + * Override this to report problems at file level. + * + * @param file to check. + * @param manager InspectionManager to ask for ProblemDescriptors from. + * @param isOnTheFly true if called during on the fly editor highlighting. Called from Inspect Code action otherwise. + * @return null if no problems found or not applicable at file level. + */ + @Nullable + public ProblemDescriptor[] checkFile(PsiFile file, InspectionManager manager, boolean isOnTheFly, State state) { + return null; + } + + @Override + @SuppressWarnings("unchecked") + public InspectionToolState createStateProvider() { + return (InspectionToolState) super.createStateProvider(); + } + + @Override + public final PsiElementVisitor buildVisitor(ProblemsHolder holder, boolean isOnTheFly) { + return super.buildVisitor(holder, isOnTheFly); + } + + @Override + @SuppressWarnings("unchecked") + public final PsiElementVisitor buildVisitor( + ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + return buildVisitorImpl(holder, isOnTheFly, session, (State) state); + } + + public PsiElementVisitor buildVisitorImpl( + final ProblemsHolder holder, + final boolean isOnTheFly, + LocalInspectionToolSession session, + State state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + addDescriptors(checkMethod(method, holder.getManager(), isOnTheFly, state)); + } + + @Override + @RequiredReadAction + public void visitClass(PsiClass aClass) { + addDescriptors(checkClass(aClass, holder.getManager(), isOnTheFly, state)); + } + + @Override + @RequiredReadAction + public void visitField(PsiField field) { + addDescriptors(checkField(field, holder.getManager(), isOnTheFly, state)); + } + + @Override + @RequiredReadAction + public void visitFile(PsiFile file) { + addDescriptors(checkFile(file, holder.getManager(), isOnTheFly, state)); + } + + @RequiredReadAction + private void addDescriptors(ProblemDescriptor[] descriptors) { + if (descriptors != null) { + for (ProblemDescriptor descriptor : descriptors) { + holder.registerProblem(descriptor); + } + } + } + }; + } + + @Override + public PsiNamedElement getProblemElement(PsiElement psiElement) { + return PsiTreeUtil.getNonStrictParentOfType(psiElement, PsiFile.class, PsiClass.class, PsiMethod.class, PsiField.class); + } + + @Override + public boolean isEnabledByDefault() { + return false; + } + + @Nullable + @Override + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } + + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.inspectionGeneralToolsGroupName(); + } + + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BaseJavaBatchLocalInspectionTool.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BaseJavaBatchLocalInspectionTool.java index 250018d54a..3b46163d6b 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BaseJavaBatchLocalInspectionTool.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BaseJavaBatchLocalInspectionTool.java @@ -21,23 +21,21 @@ import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; -public abstract class BaseJavaBatchLocalInspectionTool extends AbstractBaseJavaLocalInspectionTool implements BatchSuppressableTool { - @Nonnull +public abstract class BaseJavaBatchLocalInspectionTool extends AbstractBaseJavaLocalInspectionTool implements BatchSuppressableTool { @Override public SuppressQuickFix[] getBatchSuppressActions(@Nullable PsiElement element) { - return BatchSuppressManager.SERVICE.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName())); + return BatchSuppressManager.getInstance().createBatchSuppressActions(HighlightDisplayKey.find(getShortName())); } @Override - public boolean isSuppressedFor(@Nonnull PsiElement element) { + public boolean isSuppressedFor(PsiElement element) { return isSuppressedFor(element, this); } - public static boolean isSuppressedFor(@Nonnull PsiElement element, @Nonnull LocalInspectionTool tool) { - BatchSuppressManager manager = BatchSuppressManager.SERVICE.getInstance(); + public static boolean isSuppressedFor(PsiElement element, LocalInspectionTool tool) { + BatchSuppressManager manager = BatchSuppressManager.getInstance(); String alternativeId; String toolId = tool.getID(); return manager.isSuppressedFor(element, toolId) || diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BatchSuppressManager.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BatchSuppressManager.java index 36ff8453a7..ba54b09375 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BatchSuppressManager.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/BatchSuppressManager.java @@ -19,13 +19,12 @@ import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.editor.inspection.SuppressQuickFix; import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; @ServiceAPI(ComponentScope.APPLICATION) @@ -33,7 +32,7 @@ public interface BatchSuppressManager { String SUPPRESS_INSPECTIONS_ANNOTATION_NAME = "java.lang.SuppressWarnings"; public static BatchSuppressManager getInstance() { - return ServiceManager.getService(BatchSuppressManager.class); + return Application.get().getInstance(BatchSuppressManager.class); } @Deprecated @@ -43,29 +42,27 @@ public static BatchSuppressManager getInstance() { } } - @Nonnull - SuppressQuickFix[] createBatchSuppressActions(@Nonnull HighlightDisplayKey key); + SuppressQuickFix[] createBatchSuppressActions(HighlightDisplayKey key); - boolean isSuppressedFor(@Nonnull PsiElement element, String toolId); + boolean isSuppressedFor(PsiElement element, String toolId); - PsiElement getElementMemberSuppressedIn(@Nonnull PsiDocCommentOwner owner, String inspectionToolID); + PsiElement getElementMemberSuppressedIn(PsiDocCommentOwner owner, String inspectionToolID); @Nullable - PsiElement getAnnotationMemberSuppressedIn(@Nonnull PsiModifierListOwner owner, String inspectionToolID); + PsiElement getAnnotationMemberSuppressedIn(PsiModifierListOwner owner, String inspectionToolID); @Nullable - PsiElement getDocCommentToolSuppressedIn(@Nonnull PsiDocCommentOwner owner, String inspectionToolID); + PsiElement getDocCommentToolSuppressedIn(PsiDocCommentOwner owner, String inspectionToolID); - @Nonnull - Collection getInspectionIdsSuppressedInAnnotation(@Nonnull PsiModifierListOwner owner); + Collection getInspectionIdsSuppressedInAnnotation(PsiModifierListOwner owner); @Nullable - String getSuppressedInspectionIdsIn(@Nonnull PsiElement element); + String getSuppressedInspectionIdsIn(PsiElement element); @Nullable - PsiElement getElementToolSuppressedIn(@Nonnull PsiElement place, String toolId); + PsiElement getElementToolSuppressedIn(PsiElement place, String toolId); - boolean canHave15Suppressions(@Nonnull PsiElement file); + boolean canHave15Suppressions(PsiElement file); - boolean alreadyHas14Suppressions(@Nonnull PsiDocCommentOwner commentOwner); + boolean alreadyHas14Suppressions(PsiDocCommentOwner commentOwner); } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionContext.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionContext.java index d51301ffe1..16a13199e9 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionContext.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionContext.java @@ -13,14 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * User: anna - * Date: 18-Dec-2007 - */ package com.intellij.java.analysis.codeInspection; import com.intellij.java.analysis.codeInspection.ex.EntryPointsManager; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.inspection.GlobalInspectionContextExtension; import com.intellij.java.analysis.codeInspection.reference.RefClass; import com.intellij.java.analysis.codeInspection.reference.RefField; @@ -32,67 +28,71 @@ import consulo.language.psi.PsiReference; import consulo.application.util.function.Processor; +/** + * @author anna + * @since 2007-12-18 + */ public abstract class GlobalJavaInspectionContext implements GlobalInspectionContextExtension { - public static final Key CONTEXT = Key.create("GlobalJavaInspectionContext"); + public static final Key CONTEXT = Key.create("GlobalJavaInspectionContext"); - public interface DerivedClassesProcessor extends Processor { - } + public interface DerivedClassesProcessor extends Processor { + } - public interface DerivedMethodsProcessor extends Processor { - } + public interface DerivedMethodsProcessor extends Processor { + } - public interface UsagesProcessor extends Processor { - } + public interface UsagesProcessor extends Processor { + } - /** - * Requests that usages of the specified class outside the current analysis - * scope be passed to the specified processor. - * - * @param refClass the reference graph node for the class whose usages should be processed. - * @param p the processor to pass the usages to. - */ - public abstract void enqueueClassUsagesProcessor(RefClass refClass, UsagesProcessor p); + /** + * Requests that usages of the specified class outside the current analysis + * scope be passed to the specified processor. + * + * @param refClass the reference graph node for the class whose usages should be processed. + * @param p the processor to pass the usages to. + */ + public abstract void enqueueClassUsagesProcessor(RefClass refClass, UsagesProcessor p); - /** - * Requests that derived classes of the specified class outside the current analysis - * scope be passed to the specified processor. - * - * @param refClass the reference graph node for the class whose derived classes should be processed. - * @param p the processor to pass the classes to. - */ - public abstract void enqueueDerivedClassesProcessor(RefClass refClass, DerivedClassesProcessor p); + /** + * Requests that derived classes of the specified class outside the current analysis + * scope be passed to the specified processor. + * + * @param refClass the reference graph node for the class whose derived classes should be processed. + * @param p the processor to pass the classes to. + */ + public abstract void enqueueDerivedClassesProcessor(RefClass refClass, DerivedClassesProcessor p); - /** - * Requests that implementing or overriding methods of the specified method outside - * the current analysis scope be passed to the specified processor. - * - * @param refMethod the reference graph node for the method whose derived methods should be processed. - * @param p the processor to pass the methods to. - */ - public abstract void enqueueDerivedMethodsProcessor(RefMethod refMethod, DerivedMethodsProcessor p); + /** + * Requests that implementing or overriding methods of the specified method outside + * the current analysis scope be passed to the specified processor. + * + * @param refMethod the reference graph node for the method whose derived methods should be processed. + * @param p the processor to pass the methods to. + */ + public abstract void enqueueDerivedMethodsProcessor(RefMethod refMethod, DerivedMethodsProcessor p); - /** - * Requests that usages of the specified field outside the current analysis - * scope be passed to the specified processor. - * - * @param refField the reference graph node for the field whose usages should be processed. - * @param p the processor to pass the usages to. - */ - public abstract void enqueueFieldUsagesProcessor(RefField refField, UsagesProcessor p); + /** + * Requests that usages of the specified field outside the current analysis + * scope be passed to the specified processor. + * + * @param refField the reference graph node for the field whose usages should be processed. + * @param p the processor to pass the usages to. + */ + public abstract void enqueueFieldUsagesProcessor(RefField refField, UsagesProcessor p); - /** - * Requests that usages of the specified method outside the current analysis - * scope be passed to the specified processor. - * - * @param refMethod the reference graph node for the method whose usages should be processed. - * @param p the processor to pass the usages to. - */ - public abstract void enqueueMethodUsagesProcessor(RefMethod refMethod, UsagesProcessor p); + /** + * Requests that usages of the specified method outside the current analysis + * scope be passed to the specified processor. + * + * @param refMethod the reference graph node for the method whose usages should be processed. + * @param p the processor to pass the usages to. + */ + public abstract void enqueueMethodUsagesProcessor(RefMethod refMethod, @RequiredReadAction UsagesProcessor p); - public abstract EntryPointsManager getEntryPointsManager(RefManager manager); + public abstract EntryPointsManager getEntryPointsManager(RefManager manager); - @Override - public Key getID() { - return CONTEXT; - } + @Override + public Key getID() { + return CONTEXT; + } } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionTool.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionTool.java index c067858daa..80e3733cd4 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionTool.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GlobalJavaInspectionTool.java @@ -33,18 +33,19 @@ import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public abstract class GlobalJavaInspectionTool extends GlobalInspectionTool implements CustomSuppressableInspectionTool { @Override public boolean queryExternalUsagesRequests(final InspectionManager manager, final GlobalInspectionContext globalContext, - final ProblemDescriptionsProcessor problemDescriptionsProcessor) { - return queryExternalUsagesRequests(globalContext.getRefManager(), globalContext.getExtension(GlobalJavaInspectionContext.CONTEXT), problemDescriptionsProcessor); + final ProblemDescriptionsProcessor problemDescriptionsProcessor, + Object state) { + return queryExternalUsagesRequests(globalContext.getRefManager(), globalContext.getExtension(GlobalJavaInspectionContext.CONTEXT), problemDescriptionsProcessor, + state); } - protected boolean queryExternalUsagesRequests(RefManager manager, GlobalJavaInspectionContext globalContext, ProblemDescriptionsProcessor processor) { + protected boolean queryExternalUsagesRequests(RefManager manager, GlobalJavaInspectionContext globalContext, ProblemDescriptionsProcessor processor, Object state) { return false; } @@ -55,11 +56,10 @@ public SuppressIntentionAction[] getSuppressActions(final PsiElement element) { } @Override - public boolean isSuppressedFor(@Nonnull final PsiElement element) { + public boolean isSuppressedFor(final PsiElement element) { return BatchSuppressManager.getInstance().isSuppressedFor(element, getShortName()); } - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GroupNames.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GroupNames.java index 46b1bc8a2a..b4db5f2632 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GroupNames.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/GroupNames.java @@ -16,26 +16,23 @@ package com.intellij.java.analysis.codeInspection; import consulo.language.editor.inspection.InspectionsBundle; +import consulo.language.editor.inspection.localize.InspectionLocalize; /** * @author anna * Date: Jun 22, 2005 */ +// TODO: seem to be unused. Delete? public interface GroupNames { String ABSTRACTION_GROUP_NAME = InspectionsBundle.message("group.names.abstraction.issues"); String ASSIGNMENT_GROUP_NAME = InspectionsBundle.message("group.names.assignment.issues"); - String BUGS_GROUP_NAME = InspectionsBundle.message("group.names.probable.bugs"); String BITWISE_GROUP_NAME = InspectionsBundle.message("group.names.bitwise.operation.issues"); - String CLASS_LAYOUT_GROUP_NAME = InspectionsBundle.message("group.names.class.structure"); String CLASS_METRICS_GROUP_NAME = InspectionsBundle.message("group.names.class.metrics"); - String COMPILER_ISSUES = InspectionsBundle.message("group.names.compiler.issues"); String CONFUSING_GROUP_NAME = InspectionsBundle.message("group.names.potentially.confusing.code.constructs"); String ENCAPSULATION_GROUP_NAME = InspectionsBundle.message("group.names.encapsulation.issues"); String ERROR_HANDLING_GROUP_NAME = InspectionsBundle.message("group.names.error.handling"); String FINALIZATION_GROUP_NAME = InspectionsBundle.message("group.names.finalization.issues"); - String IMPORTS_GROUP_NAME = InspectionsBundle.message("group.names.imports"); String INITIALIZATION_GROUP_NAME = InspectionsBundle.message("group.names.initialization.issues"); - String INTERNATIONALIZATION_GROUP_NAME = InspectionsBundle.message("group.names.internationalization.issues"); String JUNIT_GROUP_NAME = InspectionsBundle.message("group.names.junit.issues"); String LOGGING_GROUP_NAME = InspectionsBundle.message("group.names.logging.issues"); String MATURITY_GROUP_NAME = InspectionsBundle.message("group.names.code.maturity.issues"); @@ -47,25 +44,15 @@ public interface GroupNames { String PORTABILITY_GROUP_NAME = InspectionsBundle.message("group.names.portability.issues"); String SECURITY_GROUP_NAME = InspectionsBundle.message("group.names.security.issues"); String SERIALIZATION_GROUP_NAME = InspectionsBundle.message("group.names.serialization.issues"); - String STYLE_GROUP_NAME = InspectionsBundle.message("group.names.code.style.issues"); String THREADING_GROUP_NAME = InspectionsBundle.message("group.names.threading.issues"); - String VERBOSE_GROUP_NAME = InspectionsBundle.message("group.names.verbose.or.redundant.code.constructs"); String VISIBILITY_GROUP_NAME = InspectionsBundle.message("group.names.visibility.issues"); String CLONEABLE_GROUP_NAME = InspectionsBundle.message("group.names.cloning.issues"); String RESOURCE_GROUP_NAME = InspectionsBundle.message("group.names.resource.management.issues"); String J2ME_GROUP_NAME = InspectionsBundle.message("group.names.j2me.issues"); String CONTROL_FLOW_GROUP_NAME = InspectionsBundle.message("group.names.control.flow.issues"); - String NUMERIC_GROUP_NAME = InspectionsBundle.message("group.names.numeric.issues"); - String LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME = InspectionsBundle.message("group.names.language.level.specific.issues.and.migration.aids"); String JAVABEANS_GROUP_NAME = InspectionsBundle.message("group.names.javabeans.issues"); - String INHERITANCE_GROUP_NAME = InspectionsBundle.message("group.names.inheritance.issues"); - String DATA_FLOW_ISSUES = InspectionsBundle.message("group.names.data.flow.issues"); - String DECLARATION_REDUNDANCY = InspectionsBundle.message("group.names.declaration.redundancy"); String PACKAGING_GROUP_NAME = InspectionsBundle.message("group.names.packaging.issues"); String DEPENDENCY_GROUP_NAME = InspectionsBundle.message("group.names.dependency.issues"); - String MODULARIZATION_GROUP_NAME = InspectionsBundle.message("group.names.modularization.issues"); String JAVAEE_GROUP_NAME = InspectionsBundle.message("group.names.javaee.issues"); - String CONCURRENCY_ANNOTATION_ISSUES = InspectionsBundle.message("group.names.concurrency.annotation.issues"); String JAVADOC_GROUP_NAME = InspectionsBundle.message("group.names.javadoc.issues"); - String PROPERTIES_GROUP_NAME = InspectionsBundle.message("group.names.properties.files"); } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ParenthesesUtils.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ParenthesesUtils.java similarity index 90% rename from java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ParenthesesUtils.java rename to java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ParenthesesUtils.java index 635896c595..f0dea29e0e 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ParenthesesUtils.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ParenthesesUtils.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.siyeh.ig.psiutils; +package com.intellij.java.analysis.codeInspection; import com.intellij.java.language.psi.*; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; @@ -93,7 +91,7 @@ private ParenthesesUtils() { s_binaryOperatorPrecedence.put(JavaTokenType.NE, EQUALITY_PRECEDENCE); } - public static String getText(@Nonnull PsiExpression expression, int precedence) { + public static String getText(PsiExpression expression, int precedence) { if (getPrecedence(expression) >= precedence) { return '(' + expression.getText() + ')'; } @@ -118,7 +116,7 @@ public static PsiExpression stripParentheses(@Nullable PsiExpression expression) return expression; } - public static boolean isCommutativeOperator(@Nonnull IElementType token) { + public static boolean isCommutativeOperator(IElementType token) { return !(token.equals(JavaTokenType.MINUS) || token.equals(JavaTokenType.DIV) || token.equals(JavaTokenType.PERC) || token.equals(JavaTokenType.LTLT) || token.equals(JavaTokenType.GTGT) || token.equals(JavaTokenType.GTGTGT)); } @@ -193,7 +191,7 @@ public static int getPrecedence(PsiExpression expression) { return -1; } - public static int getPrecedenceForOperator(@Nonnull IElementType operator) { + public static int getPrecedenceForOperator(IElementType operator) { final Integer precedence = s_binaryOperatorPrecedence.get(operator); if (precedence == null) { throw new IllegalArgumentException("unknown operator: " + operator); @@ -201,7 +199,7 @@ public static int getPrecedenceForOperator(@Nonnull IElementType operator) { return precedence.intValue(); } - public static void removeParentheses(@Nonnull PsiExpression expression, boolean ignoreClarifyingParentheses) { + public static void removeParentheses(PsiExpression expression, boolean ignoreClarifyingParentheses) { if (expression instanceof PsiMethodCallExpression) { final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expression; removeParensFromMethodCallExpression(methodCall, ignoreClarifyingParentheses); @@ -256,14 +254,14 @@ public static void removeParentheses(@Nonnull PsiExpression expression, boolean } } - private static void removeParensFromReferenceExpression(@Nonnull PsiReferenceExpression referenceExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromReferenceExpression(PsiReferenceExpression referenceExpression, boolean ignoreClarifyingParentheses) { final PsiExpression qualifier = referenceExpression.getQualifierExpression(); if (qualifier != null) { removeParentheses(qualifier, ignoreClarifyingParentheses); } } - private static void removeParensFromParenthesizedExpression(@Nonnull PsiParenthesizedExpression parenthesizedExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromParenthesizedExpression(PsiParenthesizedExpression parenthesizedExpression, boolean ignoreClarifyingParentheses) { final PsiExpression body = parenthesizedExpression.getExpression(); if (body == null) { parenthesizedExpression.delete(); @@ -336,7 +334,7 @@ private static void removeParensFromParenthesizedExpression(@Nonnull PsiParenthe } } - private static void removeParensFromConditionalExpression(@Nonnull PsiConditionalExpression conditionalExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromConditionalExpression(PsiConditionalExpression conditionalExpression, boolean ignoreClarifyingParentheses) { final PsiExpression condition = conditionalExpression.getCondition(); removeParentheses(condition, ignoreClarifyingParentheses); final PsiExpression thenBranch = conditionalExpression.getThenExpression(); @@ -349,30 +347,30 @@ private static void removeParensFromConditionalExpression(@Nonnull PsiConditiona } } - private static void removeParensFromInstanceOfExpression(@Nonnull PsiInstanceOfExpression instanceofExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromInstanceOfExpression(PsiInstanceOfExpression instanceofExpression, boolean ignoreClarifyingParentheses) { final PsiExpression operand = instanceofExpression.getOperand(); removeParentheses(operand, ignoreClarifyingParentheses); } - private static void removeParensFromPolyadicExpression(@Nonnull PsiPolyadicExpression polyadicExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromPolyadicExpression(PsiPolyadicExpression polyadicExpression, boolean ignoreClarifyingParentheses) { for (PsiExpression operand : polyadicExpression.getOperands()) { removeParentheses(operand, ignoreClarifyingParentheses); } } - private static void removeParensFromPostfixExpression(@Nonnull PsiPostfixExpression postfixExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromPostfixExpression(PsiPostfixExpression postfixExpression, boolean ignoreClarifyingParentheses) { final PsiExpression operand = postfixExpression.getOperand(); removeParentheses(operand, ignoreClarifyingParentheses); } - private static void removeParensFromPrefixExpression(@Nonnull PsiPrefixExpression prefixExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromPrefixExpression(PsiPrefixExpression prefixExpression, boolean ignoreClarifyingParentheses) { final PsiExpression operand = prefixExpression.getOperand(); if (operand != null) { removeParentheses(operand, ignoreClarifyingParentheses); } } - private static void removeParensFromArrayAccessExpression(@Nonnull PsiArrayAccessExpression arrayAccessExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromArrayAccessExpression(PsiArrayAccessExpression arrayAccessExpression, boolean ignoreClarifyingParentheses) { final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); removeParentheses(arrayExpression, ignoreClarifyingParentheses); final PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); @@ -381,21 +379,21 @@ private static void removeParensFromArrayAccessExpression(@Nonnull PsiArrayAcces } } - private static void removeParensFromTypeCastExpression(@Nonnull PsiTypeCastExpression typeCastExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromTypeCastExpression(PsiTypeCastExpression typeCastExpression, boolean ignoreClarifyingParentheses) { final PsiExpression operand = typeCastExpression.getOperand(); if (operand != null) { removeParentheses(operand, ignoreClarifyingParentheses); } } - private static void removeParensFromArrayInitializerExpression(@Nonnull PsiArrayInitializerExpression arrayInitializerExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializerExpression, boolean ignoreClarifyingParentheses) { final PsiExpression[] initializers = arrayInitializerExpression.getInitializers(); for (final PsiExpression initializer : initializers) { removeParentheses(initializer, ignoreClarifyingParentheses); } } - private static void removeParensFromAssignmentExpression(@Nonnull PsiAssignmentExpression assignment, boolean ignoreClarifyingParentheses) { + private static void removeParensFromAssignmentExpression(PsiAssignmentExpression assignment, boolean ignoreClarifyingParentheses) { final PsiExpression lhs = assignment.getLExpression(); final PsiExpression rhs = assignment.getRExpression(); removeParentheses(lhs, ignoreClarifyingParentheses); @@ -404,7 +402,7 @@ private static void removeParensFromAssignmentExpression(@Nonnull PsiAssignmentE } } - private static void removeParensFromNewExpression(@Nonnull PsiNewExpression newExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromNewExpression(PsiNewExpression newExpression, boolean ignoreClarifyingParentheses) { final PsiExpression[] dimensions = newExpression.getArrayDimensions(); for (PsiExpression dimension : dimensions) { removeParentheses(dimension, ignoreClarifyingParentheses); @@ -426,7 +424,7 @@ private static void removeParensFromNewExpression(@Nonnull PsiNewExpression newE } } - private static void removeParensFromMethodCallExpression(@Nonnull PsiMethodCallExpression methodCallExpression, boolean ignoreClarifyingParentheses) { + private static void removeParensFromMethodCallExpression(PsiMethodCallExpression methodCallExpression, boolean ignoreClarifyingParentheses) { final PsiReferenceExpression target = methodCallExpression.getMethodExpression(); final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); final PsiExpression[] arguments = argumentList.getExpressions(); @@ -487,7 +485,7 @@ public static boolean areParenthesesNeeded(PsiExpression expression, PsiExpressi if (!parentType.equals(childType)) { return true; } - if (childType.equalsToText(JavaClassNames.JAVA_LANG_STRING) && !PsiTreeUtil.isAncestor(parentPolyadicExpression.getOperands()[0], childPolyadicExpression, true)) { + if (childType.equalsToText(CommonClassNames.JAVA_LANG_STRING) && !PsiTreeUtil.isAncestor(parentPolyadicExpression.getOperands()[0], childPolyadicExpression, true)) { final PsiExpression[] operands = childPolyadicExpression.getOperands(); for (PsiExpression operand : operands) { if (!childType.equals(operand.getType())) { diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/SuppressManager.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/SuppressManager.java index d108b724e7..268349e0dd 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/SuppressManager.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/SuppressManager.java @@ -26,19 +26,18 @@ import com.intellij.java.language.psi.PsiLiteralExpression; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.editor.inspection.SuppressQuickFix; import consulo.language.editor.intention.SuppressIntentionAction; import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import javax.annotation.Nonnull; @ServiceAPI(ComponentScope.APPLICATION) public abstract class SuppressManager { public static SuppressManager getInstance() { - return ServiceManager.getService(SuppressManager.class); + return Application.get().getInstance(SuppressManager.class); } public static boolean isSuppressedInspectionName(PsiLiteralExpression expression) { @@ -46,13 +45,11 @@ public static boolean isSuppressedInspectionName(PsiLiteralExpression expression return annotation != null && BatchSuppressManager.SUPPRESS_INSPECTIONS_ANNOTATION_NAME.equals(annotation.getQualifiedName()); } - @Nonnull - public SuppressQuickFix[] createBatchSuppressActions(@Nonnull HighlightDisplayKey key) { + public SuppressQuickFix[] createBatchSuppressActions(HighlightDisplayKey key) { return BatchSuppressManager.getInstance().createBatchSuppressActions(key); } - @Nonnull - public abstract SuppressIntentionAction[] createSuppressActions(@Nonnull HighlightDisplayKey key); + public abstract SuppressIntentionAction[] createSuppressActions(HighlightDisplayKey key); - public abstract boolean isSuppressedFor(@Nonnull PsiElement element, String toolId); + public abstract boolean isSuppressedFor(PsiElement element, String toolId); } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java index 1e1bdd362b..9c2c5d59d6 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java @@ -21,8 +21,6 @@ import consulo.language.editor.intention.IntentionAction; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; - /** * Provides quick fixes for "Unused declaration" inspection * @@ -31,9 +29,7 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface UnusedDeclarationFixProvider { + ExtensionPointName EP_NAME = ExtensionPointName.create(UnusedDeclarationFixProvider.class); - ExtensionPointName EP_NAME = ExtensionPointName.create(UnusedDeclarationFixProvider.class); - - @Nonnull - IntentionAction[] getQuickFixes(PsiElement unusedElement); + IntentionAction[] getQuickFixes(PsiElement unusedElement); } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPoint.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPoint.java index a7df138605..c0976d16ad 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPoint.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPoint.java @@ -24,14 +24,13 @@ import consulo.util.xml.serializer.JDOMExternalizable; import org.jdom.Element; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; @ExtensionAPI(ComponentScope.APPLICATION) +@Deprecated public abstract class EntryPoint implements JDOMExternalizable, Cloneable { private static final Logger LOG = Logger.getInstance(EntryPoint.class); - @Nonnull public abstract String getDisplayName(); public abstract boolean isEntryPoint(RefElement refElement, PsiElement psiElement); diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointProvider.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointProvider.java new file mode 100644 index 0000000000..6c0196fa88 --- /dev/null +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointProvider.java @@ -0,0 +1,50 @@ +package com.intellij.java.analysis.codeInspection.ex; + +import consulo.annotation.component.ComponentScope; +import consulo.annotation.component.ExtensionAPI; +import consulo.language.editor.inspection.reference.RefElement; +import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 25/03/2023 + */ +@ExtensionAPI(ComponentScope.APPLICATION) +public interface EntryPointProvider +{ + String getId(); + + LocalizeValue getDisplayName(); + + State createState(); + + default boolean isEntryPoint(RefElement refElement, PsiElement psiElement, State state) + { + return isEntryPoint(psiElement, state); + } + + boolean isEntryPoint(PsiElement psiElement, State state); + + @Nullable + default String[] getIgnoreAnnotations() + { + return null; + } + + default boolean showInSettings() + { + return true; + } + + default void setEnabled(State state, boolean value) + { + state.enabled = value; + } + + default boolean isEnabled(State state) + { + return state.enabled; + } +} diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointState.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointState.java new file mode 100644 index 0000000000..314230e348 --- /dev/null +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointState.java @@ -0,0 +1,10 @@ +package com.intellij.java.analysis.codeInspection.ex; + +/** + * @author VISTALL + * @since 25/03/2023 + */ +public class EntryPointState +{ + public boolean enabled = true; +} diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointsManager.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointsManager.java index b52cf03307..5a4e71cee8 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointsManager.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/ex/EntryPointsManager.java @@ -23,28 +23,25 @@ import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; import consulo.disposer.Disposable; -import consulo.ide.ServiceManager; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefManager; import consulo.language.psi.PsiElement; import consulo.project.Project; import consulo.ui.Button; -import javax.annotation.Nonnull; @ServiceAPI(ComponentScope.PROJECT) public abstract class EntryPointsManager implements Disposable { public static EntryPointsManager getInstance(Project project) { - return ServiceManager.getService(project, EntryPointsManager.class); + return project.getInstance(EntryPointsManager.class); } - public abstract void resolveEntryPoints(@Nonnull RefManager manager); + public abstract void resolveEntryPoints(RefManager manager); - public abstract void addEntryPoint(@Nonnull RefElement newEntryPoint, boolean isPersistent); + public abstract void addEntryPoint(RefElement newEntryPoint, boolean isPersistent); - public abstract void removeEntryPoint(@Nonnull RefElement anEntryPoint); + public abstract void removeEntryPoint(RefElement anEntryPoint); - @Nonnull public abstract RefElement[] getEntryPoints(); public abstract void cleanup(); @@ -55,5 +52,5 @@ public static EntryPointsManager getInstance(Project project) { public abstract Button createConfigureAnnotationsBtn(); - public abstract boolean isEntryPoint(@Nonnull PsiElement element); + public abstract boolean isEntryPoint(PsiElement element); } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefClass.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefClass.java index f25b28f420..199f4290a1 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefClass.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefClass.java @@ -18,7 +18,6 @@ import consulo.language.editor.inspection.reference.RefElement; import com.intellij.java.language.psi.PsiClass; -import javax.annotation.Nonnull; import java.util.List; import java.util.Set; @@ -28,24 +27,18 @@ */ public interface RefClass extends RefJavaElement { - @Nonnull Set getBaseClasses(); - @Nonnull Set getSubClasses(); - @Nonnull List getConstructors(); - @Nonnull Set getInTypeReferences(); - @Nonnull Set getInstanceReferences(); RefMethod getDefaultConstructor(); - @Nonnull List getLibraryMethods(); boolean isAnonymous(); diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaElement.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaElement.java index 5cca367b31..1bd45ff61d 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaElement.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaElement.java @@ -23,8 +23,7 @@ import consulo.language.editor.inspection.reference.RefElement; import com.intellij.java.language.psi.PsiModifier; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; public interface RefJavaElement extends RefElement @@ -34,7 +33,6 @@ public interface RefJavaElement extends RefElement * * @return the collection of used types */ - @Nonnull Collection getOutTypeReferences(); diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaManager.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaManager.java index 0da685b50f..0be1a40aa5 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaManager.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaManager.java @@ -28,24 +28,17 @@ import com.intellij.java.language.psi.PsiMethod; import com.intellij.java.language.psi.PsiParameter; import consulo.util.dataholder.Key; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Collection; public abstract class RefJavaManager implements RefManagerExtension { - @NonNls public static final String CLASS = "class"; - @NonNls public static final String METHOD = "method"; - @NonNls public static final String FIELD = "field"; - @NonNls public static final String PARAMETER = "parameter"; //used in OfflineProjectDescriptor - @NonNls public static final String PACKAGE = "package"; public static final Key MANAGER = Key.create("RefJavaManager"); @@ -80,21 +73,18 @@ public abstract class RefJavaManager implements RefManagerExtension getLanguages() { return Arrays.asList(JavaLanguage.INSTANCE); } - @Nonnull @Override public Language getLanguage() { return JavaLanguage.INSTANCE; } - @Nonnull @Override public Key getID() { diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaUtil.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaUtil.java index c6d7fea565..bef04ebec0 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaUtil.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaUtil.java @@ -23,13 +23,12 @@ import com.intellij.java.language.psi.*; import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefEntity; import consulo.language.editor.inspection.reference.RefManager; import consulo.language.psi.PsiElement; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; @ServiceAPI(ComponentScope.APPLICATION) public abstract class RefJavaUtil { @@ -72,7 +71,7 @@ public static RefPackage getPackage(RefEntity refEntity) { } public static RefJavaUtil getInstance() { - return ServiceManager.getService(RefJavaUtil.class); + return Application.get().getInstance(RefJavaUtil.class); } public abstract boolean isCallToSuperMethod(PsiExpression expression, PsiMethod method); diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaVisitor.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaVisitor.java index 04e48a4841..def0a87791 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaVisitor.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefJavaVisitor.java @@ -17,7 +17,6 @@ import consulo.language.editor.inspection.reference.RefVisitor; -import javax.annotation.Nonnull; /** * Visitor for reference graph nodes. @@ -28,23 +27,23 @@ */ public class RefJavaVisitor extends RefVisitor { - public void visitField(@Nonnull RefField field) { + public void visitField(RefField field) { visitElement(field); } - public void visitMethod(@Nonnull RefMethod method) { + public void visitMethod(RefMethod method) { visitElement(method); } - public void visitParameter(@Nonnull RefParameter parameter) { + public void visitParameter(RefParameter parameter) { visitElement(parameter); } - public void visitClass(@Nonnull RefClass aClass) { + public void visitClass(RefClass aClass) { visitElement(aClass); } - public void visitPackage(@Nonnull RefPackage aPackage) { + public void visitPackage(RefPackage aPackage) { visitElement(aPackage); } } diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefMethod.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefMethod.java index 9e55d3f332..61508c7bcc 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefMethod.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefMethod.java @@ -19,8 +19,7 @@ import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiModifierListOwner; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; /** @@ -38,7 +37,6 @@ public interface RefMethod extends RefJavaElement { * @see PsiMethod#findSuperMethods() * @see #hasSuperMethods */ - @Nonnull Collection getSuperMethods(); /** @@ -47,7 +45,6 @@ public interface RefMethod extends RefJavaElement { * * @return the collection of overriding methods. */ - @Nonnull Collection getDerivedMethods(); /** @@ -147,7 +144,6 @@ public interface RefMethod extends RefJavaElement { * * @return the method parameters. */ - @Nonnull RefParameter[] getParameters(); /** diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefParameter.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefParameter.java index bff57f7528..723bb72391 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefParameter.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/reference/RefParameter.java @@ -17,7 +17,7 @@ import com.intellij.java.language.psi.PsiParameter; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * A node in the reference graph corresponding to a Java method parameter. diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/property/DefaultPropertyAccessorDetector.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/property/DefaultPropertyAccessorDetector.java new file mode 100644 index 0000000000..24f0f9c466 --- /dev/null +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/property/DefaultPropertyAccessorDetector.java @@ -0,0 +1,25 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.property; + +import com.intellij.java.language.psi.PsiMethod; +import com.intellij.java.language.psi.util.PropertyUtilBase; +import com.intellij.java.language.util.PropertyKind; +import consulo.annotation.access.RequiredReadAction; +import org.jspecify.annotations.Nullable; + +final class DefaultPropertyAccessorDetector { + @RequiredReadAction + static PropertyAccessorDetector.@Nullable PropertyAccessorInfo getDefaultAccessorInfo(PsiMethod method) { + if (PropertyUtilBase.isSimplePropertyGetter(method)) { + return new PropertyAccessorDetector.PropertyAccessorInfo(PropertyUtilBase.getPropertyNameByGetter(method), + method.getReturnType(), + PropertyKind.GETTER); + } + else if (PropertyUtilBase.isSimplePropertySetter(method)) { + return new PropertyAccessorDetector.PropertyAccessorInfo(PropertyUtilBase.getPropertyNameBySetter(method), + method.getParameterList().getParameters()[0].getType(), + PropertyKind.SETTER); + } + return null; + } +} diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/property/PropertyAccessorDetector.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/property/PropertyAccessorDetector.java new file mode 100644 index 0000000000..2902c8b610 --- /dev/null +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/property/PropertyAccessorDetector.java @@ -0,0 +1,40 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.property; + +import com.intellij.java.language.psi.PsiMethod; +import com.intellij.java.language.psi.PsiType; +import com.intellij.java.language.util.PropertyKind; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.component.ComponentScope; +import consulo.annotation.component.ExtensionAPI; +import consulo.application.Application; +import org.jspecify.annotations.Nullable; + +@ExtensionAPI(ComponentScope.APPLICATION) +public interface PropertyAccessorDetector { + @Nullable + @RequiredReadAction + static PropertyAccessorInfo detectFrom(PsiMethod method) { + Application application = Application.get(); + for (PropertyAccessorDetector detector : application.getExtensionList(PropertyAccessorDetector.class)) { + PropertyAccessorInfo accessorInfo = detector.detectPropertyAccessor(method); + if (accessorInfo != null) { + return accessorInfo; + } + } + return DefaultPropertyAccessorDetector.getDefaultAccessorInfo(method); + } + + /** + * Detects property access information if any, or results to null + */ + @Nullable + @RequiredReadAction + PropertyAccessorInfo detectPropertyAccessor(PsiMethod method); + + record PropertyAccessorInfo(String propertyName, PsiType propertyType, PropertyKind kind) { + public boolean isKindOf(PropertyKind other) { + return this.kind == other; + } + } +} \ No newline at end of file diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/refactoring/JavaRefactoringActionHandlerFactory.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/refactoring/JavaRefactoringActionHandlerFactory.java index cce98d6996..5ba64382db 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/refactoring/JavaRefactoringActionHandlerFactory.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/refactoring/JavaRefactoringActionHandlerFactory.java @@ -18,7 +18,7 @@ import consulo.annotation.component.ComponentScope; import consulo.annotation.component.ServiceAPI; import consulo.dataContext.DataContext; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.project.Project; import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiElement; @@ -29,7 +29,7 @@ @ServiceAPI(ComponentScope.APPLICATION) public abstract class JavaRefactoringActionHandlerFactory { public static JavaRefactoringActionHandlerFactory getInstance() { - return ServiceManager.getService(JavaRefactoringActionHandlerFactory.class); + return Application.get().getInstance(JavaRefactoringActionHandlerFactory.class); } /** @@ -80,7 +80,7 @@ public static JavaRefactoringActionHandlerFactory getInstance() { * Creates handler for Introduce Parameter refactoring.

* * {@link RefactoringActionHandler#invoke(Project, PsiElement[], DataContext)} - * accepts either 1 PsiExpression, that will be an initialzier for introduced parameter, + * accepts either 1 PsiExpression, that will be an initializer for introduced parameter, * or 1 PsiLocalVariable, that will be replaced with introduced parameter. */ public abstract RefactoringActionHandler createIntroduceParameterHandler(); @@ -181,7 +181,7 @@ public static JavaRefactoringActionHandlerFactory getInstance() { * Creates handler for Introduce Field refactoring.

* * {@link RefactoringActionHandler#invoke(Project, PsiElement[], DataContext)} - * accepts either 1 PsiExpression, that will be an initialzier for introduced field, + * accepts either 1 PsiExpression, that will be an initializer for introduced field, * or 1 PsiLocalVariable, that will be replaced with introduced field. */ public abstract RefactoringActionHandler createIntroduceFieldHandler(); @@ -190,7 +190,7 @@ public static JavaRefactoringActionHandlerFactory getInstance() { * Creates handler for Introduce Variable refactoring.

* * {@link RefactoringActionHandler#invoke(Project, PsiElement[], DataContext)} - * accepts 1 PsiExpression, that will be an initialzier for introduced variable. + * accepts 1 PsiExpression, that will be an initializer for introduced variable. */ public abstract RefactoringActionHandler createIntroduceVariableHandler(); @@ -198,7 +198,7 @@ public static JavaRefactoringActionHandlerFactory getInstance() { * Creates handler for Introduce Constant refactoring.

* * {@link RefactoringActionHandler#invoke(Project, PsiElement[], DataContext)} - * accepts either 1 PsiExpression, that will be an initialzier for introduced constant, + * accepts either 1 PsiExpression, that will be an initializer for introduced constant, * or 1 PsiLocalVariable, that will be replaced with introduced constant. */ public abstract IntroduceConstantHandler createIntroduceConstantHandler(); diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightBundle.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightBundle.java index 41e48cf659..754bf2c0c4 100644 --- a/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightBundle.java +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightBundle.java @@ -1,5 +1,8 @@ package consulo.java.analysis.codeInsight; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.internal.MigratedExtensionsTo; +import consulo.java.analysis.codeInsight.localize.JavaCodeInsightLocalize; import org.jetbrains.annotations.PropertyKey; import consulo.component.util.localize.AbstractBundle; @@ -7,6 +10,9 @@ * @author VISTALL * @since 13.10.2015 */ +@Deprecated +@DeprecationInfo("Use JavaCodeInsightLocalize") +@MigratedExtensionsTo(JavaCodeInsightLocalize.class) public class JavaCodeInsightBundle extends AbstractBundle { private static final JavaCodeInsightBundle ourInstance = new JavaCodeInsightBundle(); diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightUtilCore.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightUtilCore.java index ea70e8033e..5405dbe825 100644 --- a/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightUtilCore.java +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInsight/JavaCodeInsightUtilCore.java @@ -25,8 +25,7 @@ import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Set; @@ -73,8 +72,7 @@ public static T findElementInRange(PsiFile file, int star return CodeInsightUtilCore.findElementInRange(file, startOffset, endOffset, klass, JavaLanguage.INSTANCE); } - @Nonnull - public static PsiElement[] findStatementsInRange(@Nonnull PsiFile file, int startOffset, int endOffset) { + public static PsiElement[] findStatementsInRange(PsiFile file, int startOffset, int endOffset) { Language language = findJavaOrLikeLanguage(file); if (language == null) { return PsiElement.EMPTY_ARRAY; @@ -174,7 +172,7 @@ public static PsiElement[] findStatementsInRange(@Nonnull PsiFile file, int star } @Nullable - public static Language findJavaOrLikeLanguage(@Nonnull final PsiFile file) { + public static Language findJavaOrLikeLanguage(final PsiFile file) { final Set languages = file.getViewProvider().getLanguages(); for (final Language language : languages) { if (language == JavaLanguage.INSTANCE) { diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/CantBeStaticCondition.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/CantBeStaticCondition.java index d89da31bac..0b4ecc5fb0 100644 --- a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/CantBeStaticCondition.java +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/CantBeStaticCondition.java @@ -4,13 +4,11 @@ import consulo.annotation.component.ExtensionAPI; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; - /** * @author VISTALL * @since 01-Sep-22 */ @ExtensionAPI(ComponentScope.APPLICATION) public interface CantBeStaticCondition { - boolean cantBeStatic(@Nonnull PsiElement element); + boolean cantBeStatic(PsiElement element); } diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/DeprecationUtil.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/DeprecationUtil.java new file mode 100644 index 0000000000..e1ee8bf1c9 --- /dev/null +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/DeprecationUtil.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package consulo.java.analysis.codeInspection; + +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.localize.LocalizeValue; + +public class DeprecationUtil { + public static final String DEPRECATION_SHORT_NAME = "Deprecation"; + public static final LocalizeValue DEPRECATION_DISPLAY_NAME = InspectionLocalize.inspectionDeprecatedDisplayName(); + public static final String DEPRECATION_ID = "deprecation"; + + public static final String FOR_REMOVAL_SHORT_NAME = "MarkedForRemoval"; + public static final LocalizeValue FOR_REMOVAL_DISPLAY_NAME = InspectionLocalize.inspectionMarkedForRemovalDisplayName(); + public static final String FOR_REMOVAL_ID = "removal"; +} diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/ImplicitResourceCloser.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/ImplicitResourceCloser.java new file mode 100644 index 0000000000..e75ff4ceb9 --- /dev/null +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/ImplicitResourceCloser.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + */ +package consulo.java.analysis.codeInspection; + +import com.intellij.java.language.psi.PsiVariable; +import consulo.annotation.component.ComponentScope; +import consulo.annotation.component.ExtensionAPI; +import org.jetbrains.annotations.Contract; + +@ExtensionAPI(ComponentScope.APPLICATION) +public interface ImplicitResourceCloser { + /** + * Method used to understand if {@link AutoCloseable} variable closed properly. + * This extension point may be useful for framework, that provides additional ways to close AutoCloseables, like Lombok. + * + * @param variable {@link AutoCloseable} variable to check + * @return true if variable closed properly + */ + @Contract(pure = true) + boolean isSafelyClosed(PsiVariable variable); +} diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java index 57fe4cdbe6..c7d18e464c 100644 --- a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java @@ -18,7 +18,6 @@ import com.intellij.java.analysis.codeInspection.ex.EntryPoint; import consulo.component.extension.ExtensionPointName; -import javax.annotation.Nonnull; /** * @author VISTALL @@ -26,10 +25,8 @@ */ @Deprecated public interface JavaExtensionPoints { - @Nonnull - @Deprecated - ExtensionPointName DEAD_CODE_EP_NAME = ExtensionPointName.create(EntryPoint.class); + @Deprecated + ExtensionPointName DEAD_CODE_EP_NAME = ExtensionPointName.create(EntryPoint.class); - @Nonnull - ExtensionPointName CANT_BE_STATIC_EP_NAME = ExtensionPointName.create(CantBeStaticCondition.class); + ExtensionPointName CANT_BE_STATIC_EP_NAME = ExtensionPointName.create(CantBeStaticCondition.class); } diff --git a/java-analysis-api/src/main/java/consulo/java/deadCodeNotWorking/OldStyleInspection.java b/java-analysis-api/src/main/java/consulo/java/deadCodeNotWorking/OldStyleInspection.java new file mode 100644 index 0000000000..10004aac01 --- /dev/null +++ b/java-analysis-api/src/main/java/consulo/java/deadCodeNotWorking/OldStyleInspection.java @@ -0,0 +1,30 @@ +package consulo.java.deadCodeNotWorking; + +import consulo.util.xml.serializer.InvalidDataException; +import consulo.util.xml.serializer.WriteExternalException; +import org.jdom.Element; + +/** + * This inspection style not worked - just for compilation + * + * @author VISTALL + * @since 25/03/2023 + */ +@Deprecated +public interface OldStyleInspection +{ + default Object createOptionsPanel() + { + throw new Error(); + } + + default void readSettings(Element node) throws InvalidDataException + { + throw new Error(); + } + + default void writeSettings(Element node) throws WriteExternalException + { + throw new Error(); + } +} diff --git a/java-analysis-api/src/main/java/module-info.java b/java-analysis-api/src/main/java/module-info.java index 91398d73db..cb167951d0 100644 --- a/java-analysis-api/src/main/java/module-info.java +++ b/java-analysis-api/src/main/java/module-info.java @@ -1,18 +1,27 @@ /** * @author VISTALL - * @since 02/12/2022 + * @since 2022-02-12 */ module consulo.java.analysis.api { - requires transitive consulo.java.language.api; - - exports com.intellij.java.analysis; - exports com.intellij.java.analysis.codeInsight.daemon; - exports com.intellij.java.analysis.codeInsight.guess; - exports com.intellij.java.analysis.codeInsight.intention; - exports com.intellij.java.analysis.codeInspection; - exports com.intellij.java.analysis.codeInspection.ex; - exports com.intellij.java.analysis.codeInspection.reference; - exports com.intellij.java.analysis.refactoring; - exports consulo.java.analysis.codeInsight; - exports consulo.java.analysis.codeInspection; + requires transitive consulo.java.language.api; + + requires consulo.language.editor.api; + requires consulo.language.editor.refactoring.api; + requires consulo.datacontext.api; + + exports com.intellij.java.analysis; + exports com.intellij.java.analysis.codeInsight.daemon; + exports com.intellij.java.analysis.codeInsight.guess; + exports com.intellij.java.analysis.codeInsight.intention; + exports com.intellij.java.analysis.codeInspection; + exports com.intellij.java.analysis.codeInspection.ex; + exports com.intellij.java.analysis.codeInspection.reference; + exports com.intellij.java.analysis.property; + exports com.intellij.java.analysis.refactoring; + exports consulo.java.analysis.codeInsight; + exports consulo.java.analysis.codeInspection; + exports consulo.java.analysis.codeInsight.localize; + exports consulo.java.analysis.localize; + + exports consulo.java.deadCodeNotWorking; } \ No newline at end of file diff --git a/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.JavaAnalysisLocalize.yaml b/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.JavaAnalysisLocalize.yaml new file mode 100644 index 0000000000..cab3b77d00 --- /dev/null +++ b/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.JavaAnalysisLocalize.yaml @@ -0,0 +1,1128 @@ +0.field.is.always.initialized.not.null: + text: '@{0} field is always initialized not-null' +access.can.be.0: + text: Access can be {0} +access.to.field.code.ref.code.outside.of.declared.guards.loc: + text: 'Access to field #ref outside of declared guards #loc' +add.explicit.type.arguments: + text: Add explicit type arguments +annotate.as.safevarargs: + text: Annotate as @SafeVarargs +annotate.overridden.methods.parameters: + text: Annotate overridden method parameters as ''@{0}'' +annotate.overridden.methods.parameters.family.name: + text: Annotate overridden method parameters +annotation.on.static.member.qualifying.type.family.name: + text: Move annotation +anonymous.ref.loc.can.be.replaced.with.0: + text: 'Anonymous #ref #loc can be replaced with {0}' +anonymous.ref.loc.can.be.replaced.with.lambda: + text: 'Anonymous #ref #loc can be replaced with lambda' +arguments.count.mismatch: + text: Expected {0} arguments but found {1} +assigning.a.class.with.notnull.elements: + text: Assigning a class with not-null type arguments when a class with nullable type arguments is expected{0} +assigning.a.class.with.nullable.elements: + text: Assigning a class with nullable type arguments when a class with not-null type arguments is expected{0} +assigning.a.collection.of.nullable.elements: + text: Assigning a collection of nullable elements into a collection of non-null elements +call.to.method.code.ref.code.outside.of.declared.guards.loc: + text: 'Call to method #ref() outside of declared guards #loc' +change.type.arguments: + text: Change type arguments +change.type.arguments.to.0: + text: Change type arguments to <{0}> +change.visibility.level: + text: Make {0} {1} +comparision.between.object.and.primitive: + text: Comparision between Object and primitive is illegal and is accepted in java 7 only +complex.problem.with.nullability: + text: Incompatible type arguments due to nullability{0} +conflicting.nullability.annotations: + text: Conflicting nullability annotations +contract.return.validator.incompatible.return.parameter.type: + text: return type ''{0}'' must be convertible from parameter type ''{1}'' +contract.return.validator.method.return.incompatible.with.method.containing.class: + text: method return type should be compatible with method containing class +contract.return.validator.not.applicable.for.constructor: + text: not applicable for constructor +contract.return.validator.not.applicable.primitive: + text: not applicable for primitive return type ''{0}'' +contract.return.validator.not.applicable.static: + text: not applicable for static method +contract.return.validator.return.type.must.be.boolean: + text: method return type must be 'boolean' +contract.return.validator.too.few.parameters: + text: not applicable for method that has {0, choice, 0#no parameters|1#one parameter|2#{0} parameters} +contract.return.value.validation.problem: + text: 'Contract return value ''''{0}'''': {1}' +convert.0.to.float: + text: Convert ''{0}'' to float +custom.exception.class.should.have.a.constructor: + text: Custom exception class should have a constructor with a single message parameter of String type +dataflow.constructor: + text: Class initializer +dataflow.message.array.index.out.of.bounds: + text: Array index is out of bounds +dataflow.message.arraystore: + text: Storing element of type {0} to array of {1} elements will produce ArrayStoreException +dataflow.message.assigning.null: + text: null is assigned to a variable that is annotated with @NotNull +dataflow.message.assigning.null.notannotated: + text: Assigning null value to non-annotated field +dataflow.message.assigning.nullable: + text: Expression #ref might evaluate to null but is assigned to a variable that is annotated with @NotNull +dataflow.message.assigning.nullable.notannotated: + text: 'Expression #ref #loc might be null but is assigned to non-annotated field' +dataflow.message.cce: + text: 'Casting {0} to #ref #loc may produce ClassCastException' +dataflow.message.cce.always: + text: 'Casting {0} to #ref #loc will produce ClassCastException for any non-null value' +dataflow.message.constant.condition: + text: 'Condition #ref #loc is always {0, choice, 0#false|1#true}' +dataflow.message.constant.condition.when.reached: + text: 'Condition #ref #loc is always {0, choice, 0#false|1#true} when reached' +dataflow.message.constant.expression: + text: 'Result of #ref #loc is always ''''{0}''''' +dataflow.message.constant.method.reference: + text: Method reference result is always ''{0}'' +dataflow.message.constant.no.ref: + text: Condition is always {0, choice, 0#false|1#true} +dataflow.message.constant.value: + text: 'Value #ref #loc is always ''''{0}''''' +dataflow.message.contract.fail: + text: The call to '#ref' always fails, according to its method contracts +dataflow.message.contract.fail.index: + text: The call to '#ref' always fails as index is out of bounds +dataflow.message.immutable.modified: + text: Immutable object is modified +dataflow.message.immutable.passed: + text: Immutable object is passed where mutable is expected +dataflow.message.npe.array.access: + text: 'Array access #ref #loc may produce NullPointerException' +dataflow.message.npe.array.access.sure: + text: 'Array access #ref #loc will produce NullPointerException' +dataflow.message.npe.field.access: + text: 'Dereference of #ref #loc may produce NullPointerException' +dataflow.message.npe.field.access.sure: + text: 'Dereference of #ref #loc will produce NullPointerException' +dataflow.message.npe.inner.class.construction: + text: Inner class construction may produce NullPointerException +dataflow.message.npe.inner.class.construction.sure: + text: Inner class construction will produce NullPointerException +dataflow.message.npe.method.invocation: + text: 'Method invocation #ref #loc may produce NullPointerException' +dataflow.message.npe.method.invocation.sure: + text: 'Method invocation #ref #loc will produce NullPointerException' +dataflow.message.npe.methodref.invocation: + text: 'Method reference invocation #ref #loc may produce NullPointerException' +dataflow.message.only.switch.label: + text: 'Switch label #ref #loc is the only reachable in the whole switch' +dataflow.message.passing.non.null.argument.to.optional: + text: Passing a non-null argument to Optional +dataflow.message.passing.null.argument: + text: Passing null argument to parameter annotated as @NotNull +dataflow.message.passing.null.argument.nonannotated: + text: Passing null argument to non-annotated parameter +dataflow.message.passing.null.argument.to.optional: + text: Passing null argument to Optional +dataflow.message.passing.nullable.argument: + text: 'Argument #ref #loc might be null' +dataflow.message.passing.nullable.argument.methodref: + text: Method reference argument might be null +dataflow.message.passing.nullable.argument.methodref.nonannotated: + text: Method reference argument might be null but passed to non-annotated parameter +dataflow.message.passing.nullable.argument.nonannotated: + text: 'Argument #ref #loc might be null but passed to non-annotated parameter' +dataflow.message.pointless.assignment.expression: + text: 'Condition #ref #loc at the left side of assignment expression is always {0}. Can be simplified' +dataflow.message.pointless.same.argument.and.result: + text: Result of '#ref' is the same as the {0,choice,1#first|2#second} argument making the call meaningless +dataflow.message.pointless.same.arguments: + text: Arguments of '#ref' are the same. Calling this method with the same arguments is meaningless +dataflow.message.redundant.assignment: + text: Variable is already assigned to this value +dataflow.message.redundant.instanceof: + text: 'Condition #ref #loc is redundant and can be replaced with a null check' +dataflow.message.redundant.update: + text: Variable update does nothing +dataflow.message.return.notnull.from.nullable: + text: '@{0} method ''''{1}'''' always returns a non-null value' +dataflow.message.return.null.from.notnull: + text: null is returned by the method declared as @{0} +dataflow.message.return.null.from.notnullable: + text: null is returned by the method which is not declared as @{0} +dataflow.message.return.nullable.from.notnull: + text: Expression #ref might evaluate to null but is returned by the method declared as @{0} +dataflow.message.return.nullable.from.notnull.function: + text: Function may return null, but it's not allowed here +dataflow.message.return.nullable.from.notnullable: + text: Expression #ref might evaluate to null but is returned by the method which is not declared as @{0} +dataflow.message.storing.array.null: + text: null is stored to an array of @NotNull elements +dataflow.message.storing.array.nullable: + text: Expression #ref might evaluate to null but is stored to an array of @NotNull elements +dataflow.message.unboxing: + text: 'Unboxing of #ref #loc may produce NullPointerException' +dataflow.message.unboxing.method.reference: + text: 'Use of #ref #loc would need unboxing which may produce NullPointerException' +dataflow.message.unboxing.nullable.argument.methodref: + text: Passing an argument to the method reference requires unboxing which may produce NullPointerException +dataflow.message.unreachable.switch.label: + text: 'Switch label #ref #loc is unreachable' +dataflow.method.fails.with.null.argument: + text: Method will throw an exception when parameter is null +dataflow.method.with.name.template: + text: Method #ref +dataflow.not.precise: + text: '{0} is complex: data flow results could be imprecise' +dataflow.too.complex: + text: '{0} is too complex to analyze by data flow algorithm' +delete.repeated.0: + text: Delete repeated ''{0}'' +delete.repeated.interface: + text: Delete repeated interface +delimiters.argument.contains.duplicated.characters: + text: Delimiters argument contains duplicated characters +deprecated.class.usage.group.xml: + text: XML +deprecated.member.0.is.still.used: + text: Deprecated member ''{0}'' is still used +detach.library.quickfix.name: + text: Detach library +detach.library.roots.quickfix.name: + text: Detach unused library roots +dfa.find.cause.an.execution.might.exist.where: + text: 'an execution might exist where:' +dfa.find.cause.array.length.is.always.non.negative: + text: array length is always non-negative +dfa.find.cause.call.always.fails: + text: call always fails +dfa.find.cause.cast.may.fail: + text: cast may fail +dfa.find.cause.collection.size.is.always.non.negative: + text: collection size is always non-negative +dfa.find.cause.comparison.arguments.are.different.constants: + text: comparison arguments are different constants +dfa.find.cause.comparison.arguments.are.the.same: + text: comparison arguments are the same +dfa.find.cause.compile.time.constant: + text: it''s compile-time constant which evaluates to ''{0}'' +dfa.find.cause.condition.is.known.from.place: + text: it''s known that ''{0}'' from ___PLACE___ +dfa.find.cause.condition.joiner: + text: ' and' +dfa.find.cause.condition.was.checked.before: + text: condition ''{0}'' was checked before +dfa.find.cause.condition.was.deduced: + text: condition ''{0}'' was deduced +dfa.find.cause.contract.kind.explicit: + text: contract +dfa.find.cause.contract.kind.hard.coded: + text: hard-coded contract +dfa.find.cause.contract.kind.inferred: + text: inferred contract +dfa.find.cause.contract.returns.on.condition: + text: according to {0}, method ''{1}'' returns ''{2}'' value when {3} +dfa.find.cause.contract.throws.on.condition: + text: according to {0}, method ''{1}'' throws exception when {2} +dfa.find.cause.contract.trivial: + text: according to {0}, method ''{1}'' always returns ''{2}'' value +dfa.find.cause.equality.established.from.condition: + text: '''''{0}'''' was established from condition' +dfa.find.cause.field.assigned.nullability: + text: field ''{0}'' is known to be always initialized to ''{1}'' value +dfa.find.cause.field.initializer.nullability: + text: field ''{0}'' is initialized to ''{1}'' value +dfa.find.cause.instanceof.implies.non.nullity: + text: the 'instanceof' check implies non-nullity +dfa.find.cause.left.operand.range.template: + text: left operand is %s +dfa.find.cause.may.be.null: + text: may be null +dfa.find.cause.nonnull.expression.kind.concatenation: + text: concatenation +dfa.find.cause.nonnull.expression.kind.literal: + text: literal +dfa.find.cause.nonnull.expression.kind.newly.created.object: + text: newly created object +dfa.find.cause.nonnull.expression.kind.primitive.type: + text: a value of primitive type ''{0}'' +dfa.find.cause.nonnull.expression.kind.this.object: + text: '''this'' object' +dfa.find.cause.nullability.explicitly.annotated: + text: '{0} ''''{1}'''' is annotated as ''''{2}''''' +dfa.find.cause.nullability.externally.annotated: + text: '{0} ''''{1}'''' is externally annotated as ''''{2}''''' +dfa.find.cause.nullability.inferred: + text: '{0} ''''{1}'''' was inferred to be ''''{2}''''' +dfa.find.cause.nullability.inherited.from.class: + text: '{0} ''''{1}'''' inherits annotation from class {2}, thus ''''{3}''''' +dfa.find.cause.nullability.inherited.from.container: + text: '{0} ''''{1}'''' inherits container annotation, thus ''''{2}''''' +dfa.find.cause.nullability.inherited.from.named.element: + text: '{0} ''''{1}'''' inherits from {2}, thus ''''{3}''''' +dfa.find.cause.nullability.inherited.from.package: + text: '{0} ''''{1}'''' inherits annotation from package {2}, thus ''''{3}''''' +dfa.find.cause.numeric.cast.operand.template: + text: cast operand is %s +dfa.find.cause.numeric.range.generic.template: + text: value is %s +dfa.find.cause.object.kind.expression: + text: an expression +dfa.find.cause.object.kind.generic: + text: an object +dfa.find.cause.object.kind.method.return: + text: method return +dfa.find.cause.obviously.non.null.expression: + text: expression cannot be null as it''s {0} +dfa.find.cause.one.of.the.following.happens: + text: 'one of the following happens:' +dfa.find.cause.operand.of.boolean.expression.is.the.same: + text: 'operand #{0} of {1, choice, 0#and|1#or}-chain is {2}' +dfa.find.cause.place.here: + text: here +dfa.find.cause.place.line.number: + text: 'line #{0}' +dfa.find.cause.range.is.known.from.place: + text: range is known from ___PLACE___ +dfa.find.cause.range.is.specified.by.annotation: + text: the range of ''{0}'' is specified by annotation as {1} +dfa.find.cause.result.of.numeric.operation.template: + text: result of ''{0}'' is %s +dfa.find.cause.result.of.primitive.cast.template: + text: result of ''({0})'' cast is %s +dfa.find.cause.right.operand.range.template: + text: right operand is %s +dfa.find.cause.string.length.is.always.non.negative: + text: string length is always non-negative +dfa.find.cause.type.is.known.from.place: + text: type of ''{0}'' is known from ___PLACE___ +dfa.find.cause.type.known: + text: '{0} type is {1}' +dfa.find.cause.unable: + text: Unable to find the cause +dfa.find.cause.value.is.always.the.same: + text: value is always {0} +dfa.find.cause.value.is.known.from.place: + text: '''''{0}'''' is known to be ''''{1}'''' from ___PLACE___' +dfa.find.cause.value.x.is.always.the.same: + text: value ''{0}'' is always ''{1}'' +dfa.find.cause.values.cannot.be.equal.because: + text: values cannot be equal because {0} +dfa.find.cause.variable.is.initialized: + text: '{0} ''''{1}'''' is initialized to {2}' +dfa.find.cause.was.assigned: + text: '''''{0}'''' was assigned' +dfa.find.cause.was.assigned.to: + text: '''''{0}'''' was assigned to ''''{1}''''' +dfa.find.cause.was.dereferenced: + text: '''''{0}'''' was dereferenced' +dfa.find.cause.was.passed.as.non.null.parameter: + text: '''''{0}'''' was passed as an argument to a method accepting non-null parameter' +dftype.presentation.empty.optional: + text: empty Optional +dftype.presentation.present.optional: + text: present Optional +don.t.report.unused.jars.inside.used.library: + text: Don't report unused jars inside used library +duplication.policy.ask: + text: Ask +duplication.policy.generate.duplicate: + text: Generate duplicating method +duplication.policy.replace: + text: Replace existing +element.kind.keys: + text: keys +element.kind.objects: + text: objects +element.kind.values: + text: values +error.class.not.found: + text: Class {0} not found +error.message.invalid.java.type: + text: Invalid Java type +exception.removal.will.break.source.code.proceed.anyway: + text: Exception removal will break source code. Proceed anyway? +explicit.type.argument.ref.loc.can.be.replaced.with: + text: 'Explicit type argument #ref #loc can be replaced with <>' +exports.to.itself.delete.module.ref.fix: + text: Delete reference to module ''{0}'' +exports.to.itself.delete.statement.fix: + text: Delete directive +feature.annotations: + text: Annotations +feature.binary.literals: + text: Binary literals +feature.diamond.types: + text: Diamond types +feature.enhanced.switch: + text: Enhanced 'switch' blocks +feature.extension.methods: + text: Extension methods +feature.for.each: + text: For-each loops +feature.generics: + text: Generics +feature.hex.fp.literals: + text: Hexadecimal floating point literals +feature.intersections.in.casts: + text: Intersection types in casts +feature.lambda.expressions: + text: Lambda expressions +feature.local.enums: + text: Local enums +feature.local.interfaces: + text: Local interfaces +feature.lvti: + text: Local variable type inference +feature.method.references: + text: Method references +feature.modules: + text: Modules +feature.multi.catch: + text: Multi-catches +feature.patterns.instanceof: + text: Patterns in 'instanceof' +feature.records: + text: Records +feature.sealed.classes: + text: Sealed classes +feature.static.imports: + text: Static imports +feature.static.interface.calls: + text: Static interface method calls +feature.switch.expressions: + text: '''switch'' expressions' +feature.text.block.escape.sequences: + text: '''\s'' and ''\'' escape sequences' +feature.text.blocks: + text: Text block literals +feature.try.with.resources: + text: Try-with-resources +feature.try.with.resources.refs: + text: Resource references +feature.type.annotations: + text: Type annotations +feature.type.receivers: + text: Receiver parameters +feature.underscores.in.literals: + text: Underscores in literals +feature.var.lambda.parameter: + text: '''var'' in lambda parameters' +feature.varargs: + text: Variable arity methods +find.searching.for.references.to.class.progress: + text: Searching for references to class {0}... +find.usages.panel.title.derived.classes: + text: Derived Classes +find.usages.panel.title.derived.interfaces: + text: Derived Interfaces +find.usages.panel.title.implementing.classes: + text: Implementing Classes +find.usages.panel.title.implementing.methods: + text: Implementing Methods +find.usages.panel.title.overloaded.methods.usages: + text: Overloaded Methods Usages +find.usages.panel.title.overriding.methods: + text: Overriding Methods +generate.members.position.after.equals.and.hashcode: + text: After equals() and hashCode() +generate.members.position.at.caret: + text: At caret +generate.members.position.at.the.end.of.class: + text: At the end of class +html.classes.exposed.with.code.module.info.code.html: + text: Classes exposed with module-info +html.ignore.overrides.of.deprecated.abstract.methods: + text: Ignore overrides of deprecated abstract methods from non-deprecated supers +ignore.casts.in.suspicious.collections.method.calls: + text: Ignore casts in suspicious collections method calls +ignore.exceptions.thrown.by.entry.points.methods: + text: Ignore exceptions thrown by entry points methods +ignore.in.the.same.outermost.class: + text: Ignore in the same outermost class +ignore.inside.deprecated.members: + text: Ignore inside deprecated members +ignore.inside.non.static.imports: + text: Ignore inside non-static imports +ignore.members.of.deprecated.classes: + text: Ignore members of deprecated classes +ignore.operation.which.results.in.negative.value: + text: Ignore '<<' operation which results in negative value +inspection.annotate.method.quickfix.family.name: + text: Annotate method +inspection.annotate.method.quickfix.name: + text: Annotate method with ''@{0}'' +inspection.annotate.overridden.method.and.self.quickfix.family.name: + text: Annotate overridden methods and self +inspection.annotate.overridden.method.and.self.quickfix.name: + text: Annotate overridden methods and self with ''@{0}'' +inspection.annotate.overridden.method.nullable.quickfix.name: + text: Annotate overridden methods as ''@{0}'' +inspection.annotate.overridden.method.quickfix.family.name: + text: Annotate overridden methods +inspection.annotate.overridden.method.quickfix.name: + text: Annotate overridden methods with ''@{0}'' +inspection.anonymous.has.lambda.alternative.display.name: + text: Anonymous type has shorter lambda alternative +inspection.block.marker.comments.display.name: + text: Block marker comment +inspection.c.style.array.declarations.option: + text: Ignore C-style declarations in variables +inspection.can.be.final.accept.quickfix: + text: Make final +inspection.can.be.final.display.name: + text: Declaration can have final modifier +inspection.can.be.final.option: + text: Report classes +inspection.can.be.final.option1: + text: Report methods +inspection.can.be.final.option2: + text: Report fields +inspection.can.be.local.parameter.problem.descriptor: + text: Parameter #ref can have final modifier +inspection.can.be.local.variable.problem.descriptor: + text: Variable #ref can have final modifier +inspection.can.be.replaced.with.message: + text: Can be replaced with ''{0}'' +inspection.class.getclass.display.name: + text: Class.getClass() call +inspection.class.getclass.fix.remove.name: + text: Remove 'getClass()' call +inspection.class.getclass.fix.replace.name: + text: Replace with 'Class.class' +inspection.class.getclass.message: + text: '''getClass()'' is called on Class instance' +inspection.class.has.no.to.string.method.description: + text: Class ''{0}'' does not override ''toString()'' method +inspection.class.has.no.to.string.method.display.name: + text: Class does not override 'toString()' method +inspection.class.has.no.to.string.method.exclude.classes.reg.exp.option: + text: 'Exclude classes (reg exp):' +inspection.class.has.no.to.string.method.ignore.abstract.classes.option: + text: Ignore abstract classes +inspection.class.has.no.to.string.method.ignore.deprecated.classes.option: + text: Ignore deprecated classes +inspection.class.has.no.to.string.method.ignore.enum.classes.option: + text: Ignore enum classes +inspection.class.has.no.to.string.method.ignore.exception.classes.option: + text: Ignore exception classes +inspection.class.has.no.to.string.method.ignore.inner.classes.option: + text: Ignore inner classes +inspection.class.has.no.to.string.method.ignore.test.classes.option: + text: Ignore test classes +inspection.common.if.parts.disable.highlight.tail.call: + text: Do not highlight common parts, if tail statement is call +inspection.common.if.parts.family: + text: Extract common parts of 'if' statement +inspection.common.if.parts.family.else.if: + text: Merge 'else if' statement +inspection.common.if.parts.family.else.if.description: + text: '''else if'' can be merged' +inspection.common.if.parts.settings.highlight.when.tail.call: + text: Highlight when last common statement is call +inspection.compiler.javac.quirks.anno.array.comma.fix: + text: Remove trailing comma +inspection.compiler.javac.quirks.anno.array.comma.problem: + text: Trailing comma in annotation array initializer may cause compilation error in some Javac versions (e.g. JDK 5 and JDK 6). +inspection.compiler.javac.quirks.name: + text: Javac quirks +inspection.compiler.javac.quirks.qualifier.type.args.fix: + text: Remove generic parameter +inspection.compiler.javac.quirks.qualifier.type.args.problem: + text: Generics in qualifier reference may cause compilation error in some Javac versions (e.g. JDK 5 and JDK 6). +inspection.constant.on.wrong.side.of.a.comparison.side.option: + text: 'Constant should be on this side of a comparison:' +inspection.contract.checker.boolean.condition.for.nonboolean.parameter: + text: Parameter ''{0}'' has ''{1}'' type (expected boolean) +inspection.contract.checker.clause.syntax: + text: A contract clause must be in form arg1, ..., argN -> return-value +inspection.contract.checker.contract.clause.never.satisfied: + text: Contract clause ''{0}'' is never satisfied as its conditions are covered by previous contracts +inspection.contract.checker.contract.violated: + text: Contract clause ''{0}'' is violated +inspection.contract.checker.empty.constraint: + text: Constraint should not be empty +inspection.contract.checker.inferred.notnull.parameter.notnull: + text: Parameter ''{0}'' is inferred to be not-null, so ''!null'' is always satisfied +inspection.contract.checker.inferred.notnull.parameter.null: + text: Parameter ''{0}'' is inferred to be not-null, so ''null'' is not applicable +inspection.contract.checker.method.always.fails.nontrivial: + text: Return value of clause ''{0}'' could be replaced with ''fail'' as method always fails in this case +inspection.contract.checker.method.always.fails.trivial: + text: Return value of clause ''{0}'' could be replaced with ''fail'' as method always fails +inspection.contract.checker.no.exception.thrown: + text: 'Contract clause ''''{0}'''' is violated: no exception is thrown' +inspection.contract.checker.notnull.parameter.notnull: + text: Parameter ''{0}'' is annotated as not-null, so ''!null'' is always satisfied +inspection.contract.checker.notnull.parameter.null: + text: Parameter ''{0}'' is annotated as not-null, so ''null'' is not applicable +inspection.contract.checker.parameter.count.mismatch: + text: Method takes {0} parameters, while contract clause ''{1}'' expects {2} +inspection.contract.checker.primitive.parameter.nullability: + text: Parameter ''{0}'' has primitive type ''{1}'', so ''{2}'' is not applicable +inspection.contract.checker.pure.method.mutation.contract: + text: Pure method cannot have mutation contract +inspection.contract.checker.unknown.constraint: + text: 'Constraint should be one of: {0}. Found: {1}' +inspection.contract.checker.unknown.return.value: + text: 'Return value should be one of: {0}. Found: {1}' +inspection.contract.checker.unreachable.contract.clause: + text: 'Contract clause ''''{0}'''' is unreachable: previous contracts cover all possible cases' +inspection.contract.display.name: + text: Contract issues +inspection.convert.2.diamond.display.name: + text: Explicit type can be replaced with <> +inspection.convert.2.lambda.display.name: + text: Anonymous type can be replaced with lambda +inspection.data.flow.redundant.instanceof.quickfix: + text: Replace with a null check +inspection.data.flow.simplify.boolean.expression.quickfix: + text: Simplify boolean expression +inspection.data.flow.simplify.to.assignment.quickfix.name: + text: Simplify to normal assignment +inspection.data.flow.turn.off.constant.references.quickfix: + text: Don't report values which are guaranteed to be constant +inspection.data.flow.turn.off.nullable.returning.notnull.quickfix: + text: Don't report nullable methods which always return not-null value +inspection.data.flow.turn.off.true.asserts.quickfix: + text: Don't report always true assertions +inspection.deprecated.class.usage.inspection.display.name: + text: Deprecated API usage in XML +inspection.deprecated.is.still.used.display.name: + text: Deprecated member is still used +inspection.duplicate.throws.display.name: + text: Duplicate throws +inspection.duplicate.throws.ignore.subclassing.option: + text: Ignore exceptions subclassing others +inspection.duplicate.throws.more.general.problem: + text: There is a more general exception, ''{0}'', in the throws list already. +inspection.duplicate.throws.problem: + text: Duplicate throws +inspection.equals.hashcode.only.one.defined.problem.descriptor: + text: Class has {0} defined but does not define {1} +inspection.export.results.can.be.final.description: + text: Declaration can have final modifier +inspection.field.access.not.guarded.display.name: + text: Unguarded field access or method call +inspection.field.not.used.in.to.string.description: + text: Method ''{0}'' is not used in ''toString()'' method +inspection.field.not.used.in.to.string.description2: + text: Field ''{0}'' is not used in ''toString()'' method +inspection.field.not.used.in.to.string.display.name: + text: Field not used in 'toString()' method +inspection.i18n.quickfix.annotate: + text: Annotate... +inspection.i18n.quickfix.annotate.as: + text: Annotate as @{0} +inspection.i18n.quickfix.annotate.element: + text: Annotate {0} ''{1}''... +inspection.i18n.quickfix.annotate.element.as: + text: Annotate {0} ''{1}'' as @{2} +inspection.implicit.subclass.display.forClass: + text: Class ''{0}'' could be implicitly subclassed and must not be final +inspection.implicit.subclass.display.name: + text: Final declaration can't be overridden at runtime +inspection.implicit.subclass.extendable: + text: Make ''{0}'' overridable +inspection.implicit.subclass.make.class.extendable: + text: Make class ''{0}'' {1,choice,0#|1#and method {2} |1 +inspection.reference.implicit.constructor.name: + text: implicit constructor of {0} +inspection.reference.jsp.holder.method.anonymous.name: + text: <% page content %> +inspection.requires.auto.module: + text: Dependencies on automatic modules +inspection.requires.auto.module.message: + text: '''requires'' directive for an automatic module' +inspection.requires.auto.module.option: + text: Highlight only transitive dependencies +inspection.requires.auto.module.transitive: + text: '''requires transitive'' directive for an automatic module' +inspection.safe.varargs.detector.display.name: + text: Possible heap pollution from parameterized vararg type +inspection.same.return.value.display.name: + text: Method returns the same value +inspection.same.return.value.problem.descriptor: + text: Method always returns {0} +inspection.same.return.value.problem.descriptor1: + text: Method and all its derivables always return {0} +inspection.same.return.value.problem.descriptor2: + text: All implementations of this method always return {0} +inspection.static.guarded.by.instance.display.name: + text: Static member guarded by instance field or this +inspection.string.tokenizer.delimiter.display.name: + text: Duplicated delimiters in java.util.StringTokenizer +inspection.surround.requirenonnull.quickfix: + text: Replace with ''Objects.requireNonNull({0})'' +inspection.suspicious.array.method.call.display.name: + text: Suspicious Arrays method calls +inspection.suspicious.array.method.call.problem.arrays: + text: 'Array types are incompatible: arrays are always different' +inspection.suspicious.array.method.call.problem.element: + text: Element type is not compatible with array type +inspection.suspicious.collections.method.calls.display.name: + text: Suspicious collections method calls +inspection.suspicious.collections.method.calls.problem.descriptor: + text: '''''{0}'''' may not contain {2} of type ''''{1}''''' +inspection.suspicious.collections.method.calls.problem.descriptor1: + text: Suspicious call to ''{0}'' +inspection.suspicious.getter.setter.field.option: + text: Only warn when field matching getter/setter name is present +inspection.suspicious.integer.div.assignment.option: + text: Report suspicious but possibly exact divisions +inspection.test.only.problems.display.name: + text: Test-only class or method call in production code +inspection.test.only.problems.test.only.class.reference: + text: Test-only class is referenced in production code +inspection.test.only.problems.test.only.field.reference: + text: Test-only field is referenced in production code +inspection.test.only.problems.test.only.method.call: + text: Test-only method is called in production code +inspection.unary.plus.unary.binary.option: + text: Only report in confusing binary or unary expression context +inspection.unknown.guard.display.name: + text: Unknown @GuardedBy field +inspection.unnecessary.super.qualifier.option: + text: Ignore clarification 'super' qualifier +inspection.use.compare.method.display.name: + text: '''compare()'' method can be used to compare numbers' +inspection.use.compare.method.fix.family.name: + text: Replace with single comparison method +inspection.visibility.accept.quickfix: + text: Accept suggested access level +inspection.visibility.compose.suggestion: + text: Can be {0} +inspection.visibility.option.constants: + text: Suggest weaker visibility for constants +inspection.visibility.option.package.private.members: + text: Suggest package-private visibility level for class members +inspection.visibility.package.private.top.level.classes: + text: Suggest package-private visibility level for top-level classes +inspection.visibility.private.inner.members: + text: Suggest private for inner class members when referenced from outer class only +inspection.weaker.access.display.name: + text: Declaration access can be weaker +instance.member.guarded.by.static.0.loc: + text: 'Instance member guarded by static "{0}" #loc' +instance.member.guarded.by.static.ref.loc: + text: 'Instance member guarded by static #ref #loc' +intention.add.annotation.family: + text: Add annotation +intention.add.type.annotation.family: + text: Add type annotation +intention.family.name.move.annotation.to.array: + text: Move annotation to array type +intention.family.name.move.annotation.to.upper.bound: + text: Move annotation to upper bound +intention.name.qualify.expression: + text: Qualify {0} expression with ''{1}'' +intention.text.remove.annotation: + text: Remove +junit.rule.classrule.option: + text: Report @ClassRule problems +junit.rule.rule.option: + text: Report @Rule problems +long.range.set.presentation.any: + text: any value +long.range.set.presentation.divisible.by: + text: divisible by {0} +long.range.set.presentation.empty: + text: unknown +long.range.set.presentation.even: + text: even +long.range.set.presentation.odd: + text: odd +long.range.set.presentation.range: + text: in {0} +long.range.set.presentation.range.with.mod: + text: '{0}; {1}' +long.range.set.presentation.two.values: + text: '{0} or {1}' +make.0.default.annotation: + text: Make "{0}" default annotation +make.default.the.last.case.family.name: + text: Make 'default' the last case +make.final.and.annotate.as.safevarargs: + text: Make final and annotate as @SafeVarargs +message.class.inaccessible: + text: Class ''{0}'' is not accessible here +message.class.inaccessible.from.module: + text: Class ''{0}'' is not accessible from module ''{1}'' +method.reference.mapped.to.comparator: + text: Method reference mapped to Comparator interface does not fulfill the Comparator contract +module.0.with.language.level.1.depends.on.module.2.with.language.level.3: + text: Module {0} with language level {1} depends on module {2} with language level {3} +move.0.to.the.beginning: + text: Move ''{0}'' to the beginning +move.to.front: + text: Move to front +mutability.modifiable: + text: modifiable +mutability.unknown: + text: unknown +mutability.unmodifiable: + text: unmodifiable +mutability.unmodifiable.view: + text: unmodifiable view +mutation.signature.problem.invalid.token: + text: 'Invalid token: {0}; supported are ''''this'''', ''''param1'''', ''''param2'''', etc.' +mutation.signature.problem.parameter.has.immutable.type: + text: 'Parameter #{0} has immutable type ''''{1}''''' +mutation.signature.problem.reference.to.parameter.invalid: + text: 'Reference to parameter #{0} is invalid' +mutation.signature.problem.static.method.cannot.mutate.this: + text: Static method cannot mutate 'this' +navigate.to.overridden.methods.title: + text: Overriding methods of {0} +non.final.field.code.ref.code.in.immutable.class.loc: + text: 'Non-final field #ref in ''@Immutable'' class #loc' +non.final.guarded.by.field.0.loc: + text: 'Non-final ''@GuardedBy'' field "{0}" #loc' +non.final.guarded.by.field.ref.loc: + text: 'Non-final ''@GuardedBy'' field #ref #loc' +non.null.type.argument.is.expected: + text: Non-null type argument is expected +not.annotated.method.is.used.as.an.override.for.a.method.annotated.with.0: + text: Not annotated method is used as an override for a method annotated with {0} +nullability.non.null: + text: non-null +nullability.null: + text: 'null' +nullability.nullable: + text: nullable +nullable.stuff.error.overriding.notnull.with.nullable: + text: Overriding a collection of not-null elements with a collection of nullable elements +nullable.stuff.error.overriding.nullable.with.notnull: + text: Overriding a collection of nullable elements with a collection of non-null elements +nullable.stuff.problems.overridden.method.parameters.are.not.annotated: + text: Overridden method parameters are not annotated +nullable.stuff.problems.overridden.methods.are.not.annotated: + text: Overridden methods are not annotated +overriding.a.class.with.notnull.elements: + text: Overriding a class with not-null type arguments when a class with nullable type arguments is expected{0} +overriding.a.class.with.nullable.elements: + text: Overriding a class with nullable type arguments when a class with not-null type arguments is expected{0} +parameter.can.be.null: + text: Parameter can be null +parameter.is.always.not.null: + text: Parameter is always not-null +possible.heap.pollution.from.parameterized.vararg.type.loc: + text: 'Possible heap pollution from parameterized vararg type #loc' +processing.method.usages: + text: Processing Method Usages... +progress.title.searching.for.overridden.methods: + text: Searching for Overridden Methods +provided.type: + text: Provided +qualify.0: + text: Qualify {0} +qualify.with.0.this: + text: Qualify with {0}.this +redundant.block.marker: + text: Redundant block marker +remove.annotation: + text: Remove annotation +remove.block.marker.comments: + text: Remove block marker comments +remove.dependency: + text: Remove dependency +remove.left.side.of.assignment: + text: Remove left side of assignment +remove.switch.branch.0: + text: Remove switch branch ''{0}'' +remove.switch.label: + text: Remove switch label +remove.switch.label.0: + text: Remove switch label ''{0}'' +replace.0.with: + text: Replace ''''{0}'''' with ''''='''' +replace.anonymous.class.with.lambda.alternative: + text: Replace anonymous class with lambda alternative +replace.get.class.with.class.literal: + text: Replace getClass() with .class literal +replace.operator.assignment.with.assignment: + text: Replace Operator Assignment with Assignment +replace.stringtokenizer.delimiters.parameter.with.unique.symbols: + text: Replace StringTokenizer delimiters parameter with unique symbols +replace.var.with.explicit.type: + text: Replace 'var' with explicit type +replace.with.0: + text: Replace with {0} +replace.with.comparator: + text: Replace with comparator +replace.with.constant.value: + text: Replace with constant value +replace.with.expression.lambda: + text: Replace with expression lambda +replace.with.lambda: + text: Replace with lambda +report.suspicious.but.possibly.correct.method.calls: + text: '&Report suspicious but possibly correct method calls' +report.when.interface.is.not.annotated.with.functional.interface: + text: Report when interface is not annotated with @FunctionalInterface +required.type: + text: Required type +returning.a.class.with.notnull.arguments: + text: Returning a class with not-null type arguments when a class with nullable type arguments is expected{0} +returning.a.class.with.nullable.arguments: + text: Returning a class with nullable type arguments when a class with not-null type arguments is expected{0} +scope.package: + text: Package {0} +searching.for.overriding.methods: + text: Searching for Overriding Methods +service.provides: + text: 'Provides service {0}

Click to navigate
' +service.uses: + text: 'Uses service {0}
Click to navigate
' +special.field.array.length: + text: Array length +special.field.collection.size: + text: Size +special.field.optional.value: + text: Optional value +special.field.string.length: + text: String length +special.field.unboxed.value: + text: Unboxed value +statement.lambda.can.be.replaced.with.expression.lambda: + text: Statement lambda can be replaced with expression lambda +static.inheritrance.fix.replace.progress: + text: Replacing usages of {0} +static.member.guarded.by.instance.0.loc: + text: 'Static member guarded by instance "{0}" #loc' +static.member.guarded.by.instance.ref.loc: + text: 'Static member guarded by instance #ref #loc' +subclasses.search.progress.title: + text: Searching for Overridden Methods +suggest.package.private.visibility.level.for.classes.in.exported.packages.java.9: + text: Suggest package-private visibility level for classes in exported packages (Java 9+) +suppress.all.for.class: + text: Suppress all inspections for class +suppress.for.parameter: + text: Suppress for parameter +suppress.for.statement.with.comment: + text: Suppress for statement with comment +suppress.inspection.class: + text: Suppress for class +suppress.inspection.field: + text: Suppress for field +suppress.inspection.member: + text: Suppress for member +suppress.inspection.method: + text: Suppress for method +suppress.inspection.module: + text: Suppress for module declaration +suspected.module.dependency.problem.descriptor: + text: Module ''{0}'' does not depend on module ''{1}''. Though ''{1}'' was not inspected for exported dependencies needed for scope ''{2}'' +suspicious.invocation.handler.implementation.display.name: + text: Suspicious InvocationHandler implementation +suspicious.invocation.handler.implementation.method.unused.message: + text: 'Method is never used in ''invoke'': it''s unlikely that ''hashCode'', ''equals'' and ''toString'' are implemented correctly' +suspicious.invocation.handler.implementation.null.returned.for.toString.message: + text: 'Null might be returned when proxying method ''toString()'': this is discouraged' +suspicious.invocation.handler.implementation.null.returned.message: + text: 'Null might be returned when proxying method ''''{0}()'''': this may cause NullPointerException' +suspicious.invocation.handler.implementation.type.mismatch.message: + text: 'Incompatible type might be returned when proxying method ''''{0}()'''': required: {1}; got: {2}' +text.raw.ctor.reference.with.type.parameters: + text: Raw constructor reference with explicit type parameters for constructor +text.shebang.mechanism.in.java.files.not.permitted: + text: Shebang mechanism in .java files is not permitted +text.unused.import.in.template: + text: Unused import (specified in template) +type.constraint.assignability.explanation.definitely.inconvertible: + text: '{0} is known to be {1} which is definitely incompatible with {2}' +type.constraint.assignability.explanation.exact: + text: '{0} is already known to be {1}' +type.constraint.assignability.explanation.exact.not.subtype: + text: '{0} type is exactly {1} which is not a subtype of {2}' +type.constraint.assignability.explanation.exact.subtype: + text: '{0} type is exactly {1} which is a subtype of {2}' +type.constraint.assignability.explanation.not.instance.of: + text: '{0} is known to be not {1}' +type.constraint.assignability.explanation.not.instance.of.supertype: + text: '{0} is known to be not {1} which is a supertype of {2}' +type.constraint.assignability.explanation.subtype.of.subtype: + text: '{0} is already known to be {1} which is a subtype of {2}' +type.information.local.object: + text: local object +type.mismatch.reason: + text: 'reason: {0}' +type.presentation.except.values: + text: '{0}, not in {1}' +unknown.guardedby.reference.0.loc: + text: 'Unknown @GuardedBy reference "{0}" #loc' +unknown.guardedby.reference.ref.loc: + text: 'Unknown @GuardedBy reference #ref #loc' +unnecessary.module.dependency.display.name: + text: Unnecessary module dependency +unnecessary.module.dependency.problem.descriptor: + text: Module ''{0}'' sources do not depend on module ''{1}'' sources +unused.import.display.name: + text: Unused import +unused.import.statement: + text: Unused import statement +unused.library.display.name: + text: Unused library +unused.library.problem.descriptor: + text: Unused library ''{0}'' +unused.library.roots.problem.descriptor: + text: Unused roots {0} from library ''{1}'' +var.can.be.replaced.with.explicit.type: + text: '''var'' can be replaced with explicit type' +vararg.method.call.with.50.poly.arguments: + text: Vararg method call with 50+ poly arguments may cause compilation and analysis slowdown +visible.for.testing.makes.little.sense.on.test.only.code: + text: '@VisibleForTesting makes little sense on @TestOnly code' diff --git a/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.codeInsight.JavaCodeInsightLocalize.yaml b/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.codeInsight.JavaCodeInsightLocalize.yaml new file mode 100644 index 0000000000..ebabebca9d --- /dev/null +++ b/java-analysis-api/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.codeInsight.JavaCodeInsightLocalize.yaml @@ -0,0 +1,34 @@ +action.structureview.show.non.public: + text: Show non-public +action.structureview.show.properties: + text: Show Properties +file.structure.toggle.show.anonymous.classes: + text: Anonymous Classes +file.structure.toggle.show.collapse.show.lambdas: + text: Lambdas +generate.equals.hashcode.accept.sublcasses: + text: Accept &subclasses as parameter to equals() method +generate.equals.hashcode.accept.sublcasses.explanation: + text: While generally incompliant to Object.equals() specification accepting
subclasses might be necessary for generated method to work correctly
with frameworks, which generate Proxy subclasses like Hibernate. +generate.equals.hashcode.equals.fields.chooser.title: + text: Choose &fields to be included in equals() +generate.equals.hashcode.hashcode.fields.chooser.title: + text: Choose &fields to be included in hashCode() +generate.equals.hashcode.internal.error: + text: Internal error +generate.equals.hashcode.non.null.fields.chooser.title: + text: Select all non-null &fields +generate.equals.hashcode.template: + text: '&Template:' +generate.equals.hashcode.use.getters: + text: Use &getters during code generation +generate.equals.hashcode.warning.hashcode.for.arrays.is.not.supported: + text: hashCode () for arrays is not supported +generate.equals.hashcode.wizard.title: + text: Generate equals() and hashCode() +generate.equals.warning.equals.for.nested.arrays.not.supported: + text: equals() for nested arrays is not supported +generate.equals.warning.generated.equals.could.be.incorrect: + text: Generated equals() for Object[] can be incorrect +static.class.initializer: + text: '{0}class initializer' diff --git a/java-analysis-impl/pom.xml b/java-analysis-impl/pom.xml index 264d2ccb6c..1f79edccb4 100644 --- a/java-analysis-impl/pom.xml +++ b/java-analysis-impl/pom.xml @@ -15,66 +15,126 @@ - limitations under the License. --> - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - consulo - arch.ide-api-provided - 3-SNAPSHOT - - + + consulo + arch.bind.java + 3-SNAPSHOT + + - - - consulo - https://maven.consulo.io/repository/snapshots/ - - true - interval:60 - - - + + + consulo + https://maven.consulo.dev/repository/snapshots/ + + true + interval:60 + + + - consulo.plugin - consulo.java-java.analysis.impl - 3-SNAPSHOT - jar + consulo.plugin + consulo.java-java.analysis.impl + 3-SNAPSHOT + jar - - - ${project.groupId} - consulo.java-java.indexing.impl - ${project.version} - - - ${project.groupId} - consulo.java-java.analysis.api - ${project.version} - - - ${project.groupId} - consulo.java-java.language.impl - ${project.version} - - - one.util - streamex - 0.7.1 - + + + + consulo.maven + maven-consulo-plugin + true + + + generate-sources + + generate-localize + + + + + + - - consulo - consulo-language-editor-impl - ${project.version} - provided - + + + consulo + consulo-language-api + ${project.version} + provided + + + consulo + consulo-application-api + ${project.version} + provided + + + consulo + consulo-language-impl + ${project.version} + provided + + + ${project.groupId} + consulo.java-java.indexing.impl + ${project.version} + + + ${project.groupId} + consulo.java-java.analysis.api + ${project.version} + + + ${project.groupId} + consulo.java-java.language.impl + ${project.version} + + + one.util + streamex + 0.7.1 + - - ${project.groupId} - com.intellij.xml - ${project.version} - provided - - + + consulo + consulo-language-editor-impl + ${project.version} + provided + + + consulo + consulo-ui-ex-awt-api + ${project.version} + provided + + + consulo + consulo-find-api + ${project.version} + provided + + + consulo + consulo-usage-api + ${project.version} + provided + + + consulo + consulo-language-editor-refactoring-api + ${project.version} + provided + + + + ${project.groupId} + com.intellij.xml.api + ${project.version} + provided + + \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/JavaLanguageLevelPusher.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/JavaLanguageLevelPusher.java index fb24b3c940..178460392e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/JavaLanguageLevelPusher.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/JavaLanguageLevelPusher.java @@ -18,12 +18,12 @@ import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.impl.JavaFileType; import consulo.annotation.component.ExtensionImpl; -import consulo.component.messagebus.MessageBus; import consulo.index.io.data.DataInputOutputUtil; import consulo.java.language.module.extension.JavaModuleExtension; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.module.Module; import consulo.module.content.FilePropertyPusher; import consulo.module.content.ModuleRootManager; @@ -34,8 +34,7 @@ import consulo.virtualFileSystem.FileAttribute; import consulo.virtualFileSystem.VirtualFile; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -45,95 +44,97 @@ */ @ExtensionImpl public class JavaLanguageLevelPusher implements FilePropertyPusher { + public static void pushLanguageLevel(final Project project) { + PushedFilePropertiesUpdater.getInstance(project).pushAll(new JavaLanguageLevelPusher()); + } + + @Override + public Key getFileDataKey() { + return LanguageLevel.KEY; + } - public static void pushLanguageLevel(final Project project) { - PushedFilePropertiesUpdater.getInstance(project).pushAll(new JavaLanguageLevelPusher()); - } - - @Override - @Nonnull - public Key getFileDataKey() { - return LanguageLevel.KEY; - } - - @Override - public boolean pushDirectoriesOnly() { - return true; - } - - @Override - @Nonnull - public LanguageLevel getDefaultValue() { - return LanguageLevel.HIGHEST; - } - - @Override - public LanguageLevel getImmediateValue(@Nonnull Project project, VirtualFile file) { - if (file == null) { - return null; + @Override + public boolean pushDirectoriesOnly() { + return true; } - final Module moduleForFile = ModuleUtilCore.findModuleForFile(file, project); - if (moduleForFile == null) { - return null; + + @Override + public LanguageLevel getDefaultValue() { + return LanguageLevel.HIGHEST; } - return getImmediateValue(moduleForFile); - } - - @Override - public LanguageLevel getImmediateValue(@Nonnull Module module) { - ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); - - final JavaModuleExtension extension = moduleRootManager.getExtension(JavaModuleExtension.class); - return extension == null ? null : extension.getLanguageLevel(); - } - - @Override - public boolean acceptsFile(@Nonnull VirtualFile file) { - return false; - } - - @Override - public boolean acceptsDirectory(@Nonnull VirtualFile file, @Nonnull Project project) { - return ProjectFileIndex.getInstance(project).isInSourceContent(file); - } - - private static final FileAttribute PERSISTENCE = new FileAttribute("language_level_persistence", 2, true); - - @Override - public void persistAttribute(@Nonnull Project project, @Nonnull VirtualFile fileOrDir, @Nonnull LanguageLevel level) throws IOException { - final DataInputStream iStream = PERSISTENCE.readAttribute(fileOrDir); - if (iStream != null) { - try { - final int oldLevelOrdinal = DataInputOutputUtil.readINT(iStream); - if (oldLevelOrdinal == level.ordinal()) { - return; + + @Override + public LanguageLevel getImmediateValue(Project project, VirtualFile file) { + if (file == null) { + return null; } - } - finally { - iStream.close(); - } + final Module moduleForFile = ModuleUtilCore.findModuleForFile(file, project); + if (moduleForFile == null) { + return null; + } + return getImmediateValue(moduleForFile); } - final DataOutputStream oStream = PERSISTENCE.writeAttribute(fileOrDir); - DataInputOutputUtil.writeINT(oStream, level.ordinal()); - oStream.close(); + @Override + public LanguageLevel getImmediateValue(Module module) { + ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); + + final JavaModuleExtension extension = moduleRootManager.getExtension(JavaModuleExtension.class); + return extension == null ? null : extension.getLanguageLevel(); + } + + @Override + public boolean acceptsDirectory(VirtualFile file, Project project) { + return ProjectFileIndex.getInstance(project).isInSourceContent(file); + } + + private static final FileAttribute PERSISTENCE = new FileAttribute("language_level_persistence", 2, true); + + @Override + public void persistAttribute( + Project project, + VirtualFile fileOrDir, + LanguageLevel level + ) throws IOException { + final DataInputStream iStream = PERSISTENCE.readAttribute(fileOrDir); + if (iStream != null) { + try { + final int oldLevelOrdinal = DataInputOutputUtil.readINT(iStream); + if (oldLevelOrdinal == level.ordinal()) { + return; + } + } + finally { + iStream.close(); + } + } + + final DataOutputStream oStream = PERSISTENCE.writeAttribute(fileOrDir); + DataInputOutputUtil.writeINT(oStream, level.ordinal()); + oStream.close(); + + for (VirtualFile child : fileOrDir.getChildren()) { + if (!child.isDirectory() && JavaFileType.INSTANCE == child.getFileType()) { + PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child); + } + } + } + + @Override + public void afterRootsChanged(Project project) { + } + + @Override + public boolean acceptsFile(VirtualFile file, Project project) { + return false; + } - for (VirtualFile child : fileOrDir.getChildren()) { - if (!child.isDirectory() && JavaFileType.INSTANCE == child.getFileType()) { - PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child); - } + public LocalizeValue getInconsistencyLanguageLevelMessage( + LocalizeValue message, + PsiElement element, + LanguageLevel level, + PsiFile file + ) { + return LocalizeValue.empty(); } - } - - @Override - public void afterRootsChanged(@Nonnull Project project) { - } - - @Nullable - public String getInconsistencyLanguageLevelMessage(@Nonnull String message, - @Nonnull PsiElement element, - @Nonnull LanguageLevel level, - @Nonnull PsiFile file) { - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/ClassUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/ClassUtil.java index 0518e37487..40ad370194 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/ClassUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/ClassUtil.java @@ -20,10 +20,8 @@ package com.intellij.java.analysis.impl.codeInsight; import com.intellij.java.language.psi.*; -import consulo.java.language.module.util.JavaClassNames; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -32,7 +30,7 @@ public class ClassUtil { private ClassUtil() { } @Nullable - public static PsiMethod getAnyAbstractMethod(@Nonnull PsiClass aClass) { + public static PsiMethod getAnyAbstractMethod(PsiClass aClass) { PsiMethod methodToImplement = getAnyMethodToImplement(aClass); if (methodToImplement != null) { return methodToImplement; @@ -46,7 +44,7 @@ public static PsiMethod getAnyAbstractMethod(@Nonnull PsiClass aClass) { } @Nullable - public static PsiMethod getAnyMethodToImplement(@Nonnull PsiClass aClass) { + public static PsiMethod getAnyMethodToImplement(PsiClass aClass) { Set alreadyImplemented = new HashSet(); for (HierarchicalMethodSignature signatureHierarchical : aClass.getVisibleSignatures()) { for (PsiMethod superS : signatureHierarchical.getMethod().findSuperMethods()) { @@ -80,11 +78,11 @@ public static PsiMethod getAnyMethodToImplement(@Nonnull PsiClass aClass) { } @Nullable - private static PsiMethod checkPackageLocalInSuperClass(@Nonnull PsiClass aClass) { + private static PsiMethod checkPackageLocalInSuperClass(PsiClass aClass) { // super class can have package local abstract methods not accessible for overriding PsiClass superClass = aClass.getSuperClass(); if (superClass == null) return null; - if (JavaClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) return null; + if (CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName())) return null; if (JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, superClass)) return null; for (HierarchicalMethodSignature methodSignature : superClass.getVisibleSignatures()) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java index 7acbb81bba..fcde964577 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/DefaultInferredAnnotationProvider.java @@ -16,13 +16,12 @@ import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import jakarta.inject.Inject; import one.util.streamex.EntryStream; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.JavaMethodContractUtil.ORG_JETBRAINS_ANNOTATIONS_CONTRACT; @@ -30,261 +29,263 @@ @ExtensionImpl public class DefaultInferredAnnotationProvider implements InferredAnnotationProvider { - private static final Set JB_INFERRED_ANNOTATIONS = - Set.of(ORG_JETBRAINS_ANNOTATIONS_CONTRACT, Mutability.UNMODIFIABLE_ANNOTATION, - Mutability.UNMODIFIABLE_VIEW_ANNOTATION); - private static final Set EXPERIMENTAL_INFERRED_ANNOTATIONS = - Set.of(Mutability.UNMODIFIABLE_ANNOTATION, Mutability.UNMODIFIABLE_VIEW_ANNOTATION); - private final Project myProject; - - // Could be added via external annotations, but there are many signatures to handle - // and we have troubles supporting external annotations for JDK 9+ - private static final CallMatcher IMMUTABLE_FACTORY = CallMatcher.anyOf( - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_LIST, "of", "copyOf"), - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_SET, "of", "copyOf"), - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_MAP, "of", "ofEntries", "copyOf", "entry") - ); - private final NullableNotNullManager myNullabilityManager; - - @Inject - public DefaultInferredAnnotationProvider(Project project, NullableNotNullManager nullabilityManager) { - myProject = project; - myNullabilityManager = nullabilityManager; - } - - @Nullable - @Override - public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { - if (!JB_INFERRED_ANNOTATIONS.contains(annotationFQN) && !isDefaultNullabilityAnnotation(annotationFQN)) { - return null; + private static final Set JB_INFERRED_ANNOTATIONS = Set.of( + ORG_JETBRAINS_ANNOTATIONS_CONTRACT, + Mutability.UNMODIFIABLE_ANNOTATION, + Mutability.UNMODIFIABLE_VIEW_ANNOTATION + ); + private static final Set EXPERIMENTAL_INFERRED_ANNOTATIONS = + Set.of(Mutability.UNMODIFIABLE_ANNOTATION, Mutability.UNMODIFIABLE_VIEW_ANNOTATION); + private final Project myProject; + + // Could be added via external annotations, but there are many signatures to handle + // and we have troubles supporting external annotations for JDK 9+ + private static final CallMatcher IMMUTABLE_FACTORY = CallMatcher.anyOf( + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_LIST, "of", "copyOf"), + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_SET, "of", "copyOf"), + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_MAP, "of", "ofEntries", "copyOf", "entry") + ); + private final NullableNotNullManager myNullabilityManager; + + @Inject + public DefaultInferredAnnotationProvider(Project project, NullableNotNullManager nullabilityManager) { + myProject = project; + myNullabilityManager = nullabilityManager; } - listOwner = PsiUtil.preferCompiledElement(listOwner); - - if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && listOwner instanceof PsiMethod) { - PsiAnnotation anno = getHardcodedContractAnnotation((PsiMethod) listOwner); - if (anno != null) { - return anno; - } - } + @Nullable + @Override + public PsiAnnotation findInferredAnnotation(PsiModifierListOwner listOwner, String annotationFQN) { + if (!JB_INFERRED_ANNOTATIONS.contains(annotationFQN) && !isDefaultNullabilityAnnotation(annotationFQN)) { + return null; + } - if (ignoreInference(listOwner, annotationFQN)) { - return null; - } + listOwner = PsiUtil.preferCompiledElement(listOwner); - PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotation(listOwner, annotationFQN); - if (fromBytecode != null) { - return fromBytecode; - } + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && listOwner instanceof PsiMethod method) { + PsiAnnotation anno = getHardcodedContractAnnotation(method); + if (anno != null) { + return anno; + } + } - if (isDefaultNullabilityAnnotation(annotationFQN)) { - PsiAnnotation anno = null; - if (listOwner instanceof PsiMethodImpl) { - anno = getInferredNullabilityAnnotation((PsiMethodImpl) listOwner); - } - if (listOwner instanceof PsiParameter) { - anno = getInferredNullabilityAnnotation((PsiParameter) listOwner); - } - return anno == null ? null : annotationFQN.equals(anno.getQualifiedName()) ? anno : null; - } + if (ignoreInference(listOwner, annotationFQN)) { + return null; + } - if (Mutability.UNMODIFIABLE_ANNOTATION.equals(annotationFQN) || Mutability.UNMODIFIABLE_VIEW_ANNOTATION.equals(annotationFQN)) { - return getInferredMutabilityAnnotation(listOwner); - } + PsiAnnotation fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotation(listOwner, annotationFQN); + if (fromBytecode != null) { + return fromBytecode; + } - if (listOwner instanceof PsiMethodImpl && ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN)) { - return getInferredContractAnnotation((PsiMethodImpl) listOwner); - } + if (isDefaultNullabilityAnnotation(annotationFQN)) { + PsiAnnotation anno = null; + if (listOwner instanceof PsiMethodImpl method) { + anno = getInferredNullabilityAnnotation(method); + } + if (listOwner instanceof PsiParameter parameter) { + anno = getInferredNullabilityAnnotation(parameter); + } + return anno == null ? null : annotationFQN.equals(anno.getQualifiedName()) ? anno : null; + } - return null; - } + if (Mutability.UNMODIFIABLE_ANNOTATION.equals(annotationFQN) || Mutability.UNMODIFIABLE_VIEW_ANNOTATION.equals(annotationFQN)) { + return getInferredMutabilityAnnotation(listOwner); + } - private boolean isDefaultNullabilityAnnotation(String annotationFQN) { - return annotationFQN.equals(myNullabilityManager.getDefaultNullable()) || annotationFQN.equals(myNullabilityManager.getDefaultNotNull()); - } + if (listOwner instanceof PsiMethodImpl method && ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN)) { + return getInferredContractAnnotation(method); + } - @Nullable - private PsiAnnotation getHardcodedContractAnnotation(PsiMethod method) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null && aClass.getQualifiedName() != null && aClass.getQualifiedName().startsWith("org.assertj.core.api.")) { - return createContractAnnotation(Collections.emptyList(), MutationSignature.pure()); - } - List contracts = HardcodedContracts.getHardcodedContracts(method, null); - return contracts.isEmpty() ? null : createContractAnnotation(contracts, HardcodedContracts.getHardcodedMutation(method)); - } - - @Nullable - private PsiAnnotation createContractAnnotation(List contracts, MutationSignature signature) { - return createContractAnnotation(myProject, signature.isPure(), - StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), - signature.isPure() || signature == MutationSignature.unknown() ? "" : signature.toString()); - } - - /** - * There is a number of well-known methods where automatic inference fails (for example, {@link Objects#requireNonNull(Object)}. - * For such methods, contracts are hardcoded, and for their parameters inferred @NotNull are suppressed.

- *

- * {@link Contract} and {@link Nonnull} annotations on methods are not necessarily applicable to the overridden implementations, so they're ignored, too.

- * - * @return whether inference is to be suppressed the given annotation on the given method or parameter - */ - private boolean ignoreInference(@Nonnull PsiModifierListOwner owner, @Nullable String annotationFQN) { - if (annotationFQN == null) - return true; - if (owner instanceof PsiMethod && PsiUtil.canBeOverridden((PsiMethod) owner)) { - return true; - } - if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && HardcodedContracts.hasHardcodedContracts(owner)) { - return true; + return null; } - if (annotationFQN.equals(myNullabilityManager.getDefaultNotNull()) && owner instanceof PsiParameter && owner.getParent() != null) { - List annotations = NullableNotNullManager.getInstance(owner.getProject()).getNullables(); - if (isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_TYPE)) { - return true; - } - if (HardcodedContracts.hasHardcodedContracts(owner)) { - return true; - } - } - return false; - } - @Nullable - private PsiAnnotation getInferredMutabilityAnnotation(@Nonnull PsiModifierListOwner owner) { - if (owner instanceof PsiMethod && IMMUTABLE_FACTORY.methodMatches((PsiMethod) owner)) { - return Mutability.UNMODIFIABLE.asAnnotation(myProject); - } - if (!(owner instanceof PsiMethodImpl)) - return null; - PsiMethodImpl method = (PsiMethodImpl) owner; - PsiModifierList modifiers = method.getModifierList(); - if (modifiers.hasAnnotation(Mutability.UNMODIFIABLE_ANNOTATION) || - modifiers.hasAnnotation(Mutability.UNMODIFIABLE_VIEW_ANNOTATION)) { - return null; + private boolean isDefaultNullabilityAnnotation(String annotationFQN) { + return annotationFQN.equals(myNullabilityManager.getDefaultNullable()) || annotationFQN.equals(myNullabilityManager.getDefaultNotNull()); } - return JavaSourceInference.inferMutability(method).asAnnotation(myProject); - } - @Nullable - private PsiAnnotation getInferredContractAnnotation(PsiMethodImpl method) { - if (method.getModifierList().hasAnnotation(ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { - return null; + @Nullable + private PsiAnnotation getHardcodedContractAnnotation(PsiMethod method) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null && aClass.getQualifiedName() != null && aClass.getQualifiedName().startsWith("org.assertj.core.api.")) { + return createContractAnnotation(Collections.emptyList(), MutationSignature.pure()); + } + List contracts = HardcodedContracts.getHardcodedContracts(method, null); + return contracts.isEmpty() ? null : createContractAnnotation(contracts, HardcodedContracts.getHardcodedMutation(method)); } - return createContractAnnotation(JavaSourceInference.inferContracts(method), JavaSourceInference.inferPurity(method)); - } - - @Nullable - private PsiAnnotation getInferredNullabilityAnnotation(PsiMethodImpl method) { - if (hasExplicitNullability(method)) { - return null; + @Nullable + private PsiAnnotation createContractAnnotation(List contracts, MutationSignature signature) { + return createContractAnnotation(myProject, signature.isPure(), + StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), + signature.isPure() || signature == MutationSignature.unknown() ? "" : signature.toString() + ); } - Nullability nullability = JavaSourceInference.inferNullability(method); - if (nullability == Nullability.NOT_NULL) { - return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); + + /** + * There is a number of well-known methods where automatic inference fails (for example, {@link Objects#requireNonNull(Object)}. + * For such methods, contracts are hardcoded, and for their parameters inferred @NotNull are suppressed.

+ *

+ * {@link Contract} and {@link Nonnull} annotations on methods are not necessarily applicable to the overridden implementations, so they're ignored, too.

+ * + * @return whether inference is to be suppressed the given annotation on the given method or parameter + */ + private boolean ignoreInference(PsiModifierListOwner owner, @Nullable String annotationFQN) { + if (annotationFQN == null) { + return true; + } + if (owner instanceof PsiMethod method && PsiUtil.canBeOverridden(method)) { + return true; + } + if (ORG_JETBRAINS_ANNOTATIONS_CONTRACT.equals(annotationFQN) && HardcodedContracts.hasHardcodedContracts(owner)) { + return true; + } + if (annotationFQN.equals(myNullabilityManager.getDefaultNotNull()) && owner instanceof PsiParameter && owner.getParent() != null) { + List annotations = NullableNotNullManager.getInstance(owner.getProject()).getNullables(); + if (isAnnotated(owner, annotations, CHECK_EXTERNAL | CHECK_TYPE)) { + return true; + } + if (HardcodedContracts.hasHardcodedContracts(owner)) { + return true; + } + } + return false; } - if (nullability == Nullability.NULLABLE) { - return ProjectBytecodeAnalysis.getInstance(myProject).getNullableAnnotation(); + + @Nullable + private PsiAnnotation getInferredMutabilityAnnotation(PsiModifierListOwner owner) { + if (owner instanceof PsiMethod method && IMMUTABLE_FACTORY.methodMatches(method)) { + return Mutability.UNMODIFIABLE.asAnnotation(myProject); + } + if (!(owner instanceof PsiMethodImpl method)) { + return null; + } + PsiModifierList modifiers = method.getModifierList(); + if (modifiers.hasAnnotation(Mutability.UNMODIFIABLE_ANNOTATION) + || modifiers.hasAnnotation(Mutability.UNMODIFIABLE_VIEW_ANNOTATION)) { + return null; + } + return JavaSourceInference.inferMutability(method).asAnnotation(myProject); } - return null; - } - private boolean hasExplicitNullability(PsiModifierListOwner owner) { - return NullableNotNullManager.getInstance(myProject).findExplicitNullability(owner) != null; - } + @Nullable + private PsiAnnotation getInferredContractAnnotation(PsiMethodImpl method) { + if (method.getModifierList().hasAnnotation(ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + return null; + } - @Nullable - private PsiAnnotation getInferredNullabilityAnnotation(PsiParameter parameter) { - if (hasExplicitNullability(parameter)) { - return null; + return createContractAnnotation(JavaSourceInference.inferContracts(method), JavaSourceInference.inferPurity(method)); } - PsiElement parent = parameter.getParent(); - if (!(parent instanceof PsiParameterList)) - return null; - PsiElement scope = parent.getParent(); - if (scope instanceof PsiMethod) { - PsiMethod method = (PsiMethod) scope; - if (method.getName().equals("of")) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - String className = containingClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_LIST.equals(className) || - CommonClassNames.JAVA_UTIL_SET.equals(className) || - CommonClassNames.JAVA_UTIL_MAP.equals(className) || - "java.util.EnumSet".equals(className)) { + + @Nullable + private PsiAnnotation getInferredNullabilityAnnotation(PsiMethodImpl method) { + if (hasExplicitNullability(method)) { + return null; + } + Nullability nullability = JavaSourceInference.inferNullability(method); + if (nullability == Nullability.NOT_NULL) { return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); - } } - } + if (nullability == Nullability.NULLABLE) { + return ProjectBytecodeAnalysis.getInstance(myProject).getNullableAnnotation(); + } + return null; } - Nullability nullability = JavaSourceInference.inferNullability(parameter); - return nullability == Nullability.NOT_NULL ? ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation() : null; - } - - @Nullable - private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { - return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), ""); - } - - @Nullable - public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts, String mutates) { - Map attrMap = new LinkedHashMap<>(); - if (!contracts.isEmpty()) { - attrMap.put("value", StringUtil.wrapWithDoubleQuote(contracts)); + + private boolean hasExplicitNullability(PsiModifierListOwner owner) { + return NullableNotNullManager.getInstance(myProject).findExplicitNullability(owner) != null; } - if (pure) { - attrMap.put("pure", "true"); - } else if (!mutates.trim().isEmpty()) { - attrMap.put("mutates", StringUtil.wrapWithDoubleQuote(mutates)); + + @Nullable + private PsiAnnotation getInferredNullabilityAnnotation(PsiParameter parameter) { + if (hasExplicitNullability(parameter)) { + return null; + } + PsiElement parent = parameter.getParent(); + if (!(parent instanceof PsiParameterList)) { + return null; + } + PsiElement scope = parent.getParent(); + if (scope instanceof PsiMethod method && method.getName().equals("of")) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null) { + String className = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_LIST.equals(className) + || CommonClassNames.JAVA_UTIL_SET.equals(className) + || CommonClassNames.JAVA_UTIL_MAP.equals(className) + || CommonClassNames.JAVA_UTIL_ENUM_SET.equals(className)) { + return ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation(); + } + } + } + Nullability nullability = JavaSourceInference.inferNullability(parameter); + return nullability == Nullability.NOT_NULL ? ProjectBytecodeAnalysis.getInstance(myProject).getNotNullAnnotation() : null; } - if (attrMap.isEmpty()) { - return null; + + @Nullable + private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { + return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "), ""); } - String attrs = attrMap.keySet().equals(Collections.singleton("value")) ? - attrMap.get("value") : EntryStream.of(attrMap).join(" = ").joining(", "); - return ProjectBytecodeAnalysis.getInstance(project).createContractAnnotation(attrs); - } - - @Nonnull - @Override - public List findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { - listOwner = PsiUtil.preferCompiledElement(listOwner); - List result = new ArrayList<>(); - PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner); - for (PsiAnnotation annotation : fromBytecode) { - if (!ignoreInference(listOwner, annotation.getQualifiedName())) { - result.add(annotation); - } + + @Nullable + public static PsiAnnotation createContractAnnotation(Project project, boolean pure, String contracts, String mutates) { + Map attrMap = new LinkedHashMap<>(); + if (!contracts.isEmpty()) { + attrMap.put("value", StringUtil.wrapWithDoubleQuote(contracts)); + } + if (pure) { + attrMap.put("pure", "true"); + } + else if (!mutates.trim().isEmpty()) { + attrMap.put("mutates", StringUtil.wrapWithDoubleQuote(mutates)); + } + if (attrMap.isEmpty()) { + return null; + } + String attrs = attrMap.keySet().equals(Collections.singleton("value")) ? + attrMap.get("value") : EntryStream.of(attrMap).join(" = ").joining(", "); + return ProjectBytecodeAnalysis.getInstance(project).createContractAnnotation(attrs); } - if (listOwner instanceof PsiMethod) { - PsiAnnotation hardcoded = getHardcodedContractAnnotation((PsiMethod) listOwner); - ContainerUtil.addIfNotNull(result, hardcoded); - if (listOwner instanceof PsiMethodImpl) { - if (hardcoded == null && !ignoreInference(listOwner, ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { - ContainerUtil.addIfNotNull(result, getInferredContractAnnotation((PsiMethodImpl) listOwner)); + @Override + public List findInferredAnnotations(PsiModifierListOwner listOwner) { + listOwner = PsiUtil.preferCompiledElement(listOwner); + List result = new ArrayList<>(); + PsiAnnotation[] fromBytecode = ProjectBytecodeAnalysis.getInstance(myProject).findInferredAnnotations(listOwner); + for (PsiAnnotation annotation : fromBytecode) { + if (!ignoreInference(listOwner, annotation.getQualifiedName())) { + result.add(annotation); + } } - if (!ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull()) || - !ignoreInference(listOwner, myNullabilityManager.getDefaultNullable())) { - PsiAnnotation annotation = getInferredNullabilityAnnotation((PsiMethodImpl) listOwner); - if (annotation != null && !ignoreInference(listOwner, annotation.getQualifiedName())) { - result.add(annotation); - } + if (listOwner instanceof PsiMethod method) { + PsiAnnotation hardcoded = getHardcodedContractAnnotation(method); + ContainerUtil.addIfNotNull(result, hardcoded); + if (listOwner instanceof PsiMethodImpl methodImpl) { + if (hardcoded == null && !ignoreInference(listOwner, ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) { + ContainerUtil.addIfNotNull(result, getInferredContractAnnotation(methodImpl)); + } + + if (!ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull()) || + !ignoreInference(listOwner, myNullabilityManager.getDefaultNullable())) { + PsiAnnotation annotation = getInferredNullabilityAnnotation(methodImpl); + if (annotation != null && !ignoreInference(listOwner, annotation.getQualifiedName())) { + result.add(annotation); + } + } + } } - } - } - if (listOwner instanceof PsiParameter && !ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull())) { - ContainerUtil.addIfNotNull(result, getInferredNullabilityAnnotation((PsiParameter) listOwner)); - } + if (listOwner instanceof PsiParameter parameter && !ignoreInference(listOwner, myNullabilityManager.getDefaultNotNull())) { + ContainerUtil.addIfNotNull(result, getInferredNullabilityAnnotation(parameter)); + } - ContainerUtil.addIfNotNull(result, getInferredMutabilityAnnotation(listOwner)); + ContainerUtil.addIfNotNull(result, getInferredMutabilityAnnotation(listOwner)); - return result; - } + return result; + } - public static boolean isExperimentalInferredAnnotation(@Nonnull PsiAnnotation annotation) { - return EXPERIMENTAL_INFERRED_ANNOTATIONS.contains(annotation.getQualifiedName()); - } + public static boolean isExperimentalInferredAnnotation(PsiAnnotation annotation) { + return EXPERIMENTAL_INFERRED_ANNOTATIONS.contains(annotation.getQualifiedName()); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/NullityAnnotationModifier.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/NullityAnnotationModifier.java index 90d0161b0e..03306c38d6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/NullityAnnotationModifier.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/NullityAnnotationModifier.java @@ -24,8 +24,8 @@ import consulo.annotation.component.ExtensionImpl; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.List; /** @@ -35,7 +35,7 @@ public class NullityAnnotationModifier extends TypeAnnotationModifier { @Nullable @Override - public TypeAnnotationProvider modifyAnnotations(@Nonnull PsiType inferenceVariableType, @Nonnull PsiClassType boundType) { + public TypeAnnotationProvider modifyAnnotations(PsiType inferenceVariableType, PsiClassType boundType) { PsiAnnotation[] annotations = inferenceVariableType.getAnnotations(); for (PsiAnnotation annotation : annotations) { String qName = annotation.getQualifiedName(); @@ -47,7 +47,6 @@ public TypeAnnotationProvider modifyAnnotations(@Nonnull PsiType inferenceVariab return null; } - @Nonnull private static TypeAnnotationProvider removeAnnotation(PsiAnnotation[] annotations, PsiAnnotation annotation) { List list = ContainerUtil.newArrayList(annotations); list.remove(annotation); @@ -59,7 +58,7 @@ private static TypeAnnotationProvider removeAnnotation(PsiAnnotation[] annotatio return () -> array; } - private static boolean isMatchingAnnotation(@Nonnull PsiClassType boundType, PsiAnnotation annotation, String qName) { + private static boolean isMatchingAnnotation(PsiClassType boundType, PsiAnnotation annotation, String qName) { NullableNotNullManager manager = NullableNotNullManager.getInstance(annotation.getProject()); return (manager.getNullables().contains(qName) || manager.getNotNulls().contains(qName)) && boundType.findAnnotation(qName) != null; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/DefaultHighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/DefaultHighlightUtil.java index 7212a183fc..cdd0dae533 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/DefaultHighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/DefaultHighlightUtil.java @@ -21,13 +21,11 @@ import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.PsiElement; import consulo.util.lang.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class DefaultHighlightUtil { @Nullable - public static HighlightInfo checkBadCharacter(@Nonnull PsiElement element) { + public static HighlightInfo checkBadCharacter(PsiElement element) { ASTNode node = element.getNode(); if (node != null && node.getElementType() == TokenType.BAD_CHARACTER) { char c = element.textToCharArray()[0]; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/GlobalUsageHelper.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/GlobalUsageHelper.java index 8aa649e9fe..01490511f7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/GlobalUsageHelper.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/GlobalUsageHelper.java @@ -19,7 +19,6 @@ import com.intellij.java.language.psi.PsiMember; import consulo.language.psi.PsiNamedElement; -import javax.annotation.Nonnull; import java.util.HashMap; import java.util.Map; @@ -29,9 +28,9 @@ public abstract class GlobalUsageHelper { final Map unusedClassCache = new HashMap(); - public abstract boolean shouldCheckUsages(@Nonnull PsiMember member); + public abstract boolean shouldCheckUsages(PsiMember member); - public abstract boolean isLocallyUsed(@Nonnull PsiNamedElement member); + public abstract boolean isLocallyUsed(PsiNamedElement member); public abstract boolean isCurrentFileAlreadyChecked(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaHighlightInfoTypes.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaHighlightInfoTypes.java index e3c0b5e8a0..4bf75503a1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaHighlightInfoTypes.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaHighlightInfoTypes.java @@ -20,16 +20,14 @@ import consulo.codeEditor.CodeInsightColors; import consulo.colorScheme.TextAttributesKey; import consulo.language.editor.annotation.HighlightSeverity; +import consulo.language.editor.inspection.HighlightInfoTypeSeverityByKey; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.editor.rawHighlight.HighlightInfoType; - -import javax.annotation.Nonnull; +import consulo.language.editor.rawHighlight.HighlightInfoTypeImpl; public interface JavaHighlightInfoTypes { - HighlightInfoType UNUSED_IMPORT = new HighlightInfoType.HighlightInfoTypeSeverityByKey(HighlightDisplayKey.findOrRegister(UnusedImportLocalInspection.SHORT_NAME, UnusedImportLocalInspection - .DISPLAY_NAME), CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES); - - HighlightInfoType JAVA_KEYWORD = new HighlightInfoType.HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, JavaHighlightingColors.KEYWORD); + HighlightInfoType JAVA_KEYWORD = new HighlightInfoTypeImpl(HighlightSeverity.INFORMATION, JavaHighlightingColors.KEYWORD); HighlightInfoType PACKAGE_NAME = createSymbolTypeInfo(JavaHighlightingColors.PACKAGE_NAME_ATTRIBUTES); HighlightInfoType CLASS_NAME = createSymbolTypeInfo(JavaHighlightingColors.CLASS_NAME_ATTRIBUTES); @@ -50,16 +48,16 @@ public interface JavaHighlightInfoTypes { HighlightInfoType ANONYMOUS_CLASS_NAME = createSymbolTypeInfo(JavaHighlightingColors.ANONYMOUS_CLASS_NAME_ATTRIBUTES); HighlightInfoType INTERFACE_NAME = createSymbolTypeInfo(JavaHighlightingColors.INTERFACE_NAME_ATTRIBUTES); HighlightInfoType ENUM_NAME = createSymbolTypeInfo(JavaHighlightingColors.ENUM_NAME_ATTRIBUTES); - HighlightInfoType TYPE_PARAMETER_NAME = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.TYPE_PARAMETER_NAME_ATTRIBUTES); + HighlightInfoType TYPE_PARAMETER_NAME = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.TYPE_PARAMETER_NAME_ATTRIBUTES); HighlightInfoType ABSTRACT_CLASS_NAME = createSymbolTypeInfo(JavaHighlightingColors.ABSTRACT_CLASS_NAME_ATTRIBUTES); - HighlightInfoType ANNOTATION_NAME = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.ANNOTATION_NAME_ATTRIBUTES); - HighlightInfoType ANNOTATION_ATTRIBUTE_NAME = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES); - HighlightInfoType REASSIGNED_LOCAL_VARIABLE = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES); - HighlightInfoType REASSIGNED_PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.REASSIGNED_PARAMETER_ATTRIBUTES); - HighlightInfoType IMPLICIT_ANONYMOUS_CLASS_PARAMETER = new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors + HighlightInfoType ANNOTATION_NAME = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.ANNOTATION_NAME_ATTRIBUTES); + HighlightInfoType ANNOTATION_ATTRIBUTE_NAME = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.ANNOTATION_ATTRIBUTE_NAME_ATTRIBUTES); + HighlightInfoType REASSIGNED_LOCAL_VARIABLE = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES); + HighlightInfoType REASSIGNED_PARAMETER = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors.REASSIGNED_PARAMETER_ATTRIBUTES); + HighlightInfoType IMPLICIT_ANONYMOUS_CLASS_PARAMETER = new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, JavaHighlightingColors .IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES); - static HighlightInfoType createSymbolTypeInfo(@Nonnull TextAttributesKey attributesKey) { - return new HighlightInfoType.HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, attributesKey, false); + static HighlightInfoType createSymbolTypeInfo(TextAttributesKey attributesKey) { + return new HighlightInfoTypeImpl(HighlightInfoType.SYMBOL_TYPE_SEVERITY, attributesKey, false); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaServiceUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaServiceUtil.java index b590f21a61..9d655a7e22 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaServiceUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaServiceUtil.java @@ -1,201 +1,205 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInsight.daemon.impl; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil; import com.intellij.java.analysis.impl.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.markup.GutterIconRenderer; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.java.language.impl.icon.JavaPsiImplIconGroup; import consulo.language.editor.Pass; import consulo.language.editor.gutter.GutterIconNavigationHandler; import consulo.language.editor.gutter.LineMarkerInfo; import consulo.language.psi.PsiElement; import consulo.navigation.NavigationItem; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.awt.event.MouseEvent; import java.util.Collections; import java.util.List; import java.util.Optional; public final class JavaServiceUtil { - public static final CallMatcher SERVICE_LOADER_LOAD = CallMatcher.staticCall("java.util.ServiceLoader", "load", "loadInstalled"); - - public static boolean isServiceProviderMethod(@Nonnull PsiMethod method) { - return "provider".equals(method.getName()) && - method.getParameterList().isEmpty() && - method.hasModifierProperty(PsiModifier.PUBLIC) && - method.hasModifierProperty(PsiModifier.STATIC); - } - - @Nonnull - public static List> collectServiceProviderMethod(@Nonnull PsiMethod method) { - PsiClass containingClass = method.getContainingClass(); - PsiClass resultClass = PsiUtil.resolveClassInType(method.getReturnType()); - return createJavaServiceLineMarkerInfo(method.getNameIdentifier(), containingClass, resultClass); - } - - @Nonnull - public static List> collectServiceImplementationClass(@Nonnull PsiClass psiClass) { - return createJavaServiceLineMarkerInfo(psiClass.getNameIdentifier(), psiClass, psiClass); - } - - @Nonnull - private static List> createJavaServiceLineMarkerInfo(@Nullable PsiIdentifier identifier, - @Nullable PsiClass implementerClass, - @Nullable PsiClass resultClass) { - if (identifier != null && implementerClass != null && resultClass != null) { - String implementerClassName = implementerClass.getQualifiedName(); - if (implementerClassName != null && PsiUtil.isLanguageLevel9OrHigher(identifier)) { - PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(identifier); - if (javaModule != null) { - for (PsiProvidesStatement providesStatement : javaModule.getProvides()) { - PsiClassType interfaceType = providesStatement.getInterfaceType(); - PsiReferenceList implementationList = providesStatement.getImplementationList(); - if (interfaceType != null && implementationList != null) { - PsiClassType[] implementationTypes = implementationList.getReferencedTypes(); - for (PsiClassType implementationType : implementationTypes) { - if (implementerClass.equals(implementationType.resolve())) { - PsiClass interfaceClass = interfaceType.resolve(); - if (InheritanceUtil.isInheritorOrSelf(resultClass, interfaceClass, true)) { - String interfaceClassName = interfaceClass.getQualifiedName(); - if (interfaceClassName != null) { - LineMarkerInfo info = - new LineMarkerInfo<>(identifier, identifier.getTextRange(), JavaPsiImplIconGroup.gutterJava9service(), - Pass.LINE_MARKERS, - e -> JavaAnalysisBundle.message("service.provides", interfaceClassName), - new ServiceProvidesNavigationHandler(interfaceClassName, implementerClassName), - GutterIconRenderer.Alignment.LEFT); - return Collections.singletonList(info); + public static final CallMatcher SERVICE_LOADER_LOAD = CallMatcher.staticCall("java.util.ServiceLoader", "load", "loadInstalled"); + + public static boolean isServiceProviderMethod(PsiMethod method) { + return "provider".equals(method.getName()) && method.getParameterList().isEmpty() && method.isPublic() && method.isStatic(); + } + + public static List> collectServiceProviderMethod(PsiMethod method) { + PsiClass containingClass = method.getContainingClass(); + PsiClass resultClass = PsiUtil.resolveClassInType(method.getReturnType()); + return createJavaServiceLineMarkerInfo(method.getNameIdentifier(), containingClass, resultClass); + } + + public static List> collectServiceImplementationClass(PsiClass psiClass) { + return createJavaServiceLineMarkerInfo(psiClass.getNameIdentifier(), psiClass, psiClass); + } + + private static List> createJavaServiceLineMarkerInfo( + @Nullable PsiIdentifier identifier, + @Nullable PsiClass implementerClass, + @Nullable PsiClass resultClass + ) { + if (identifier != null && implementerClass != null && resultClass != null) { + String implementerClassName = implementerClass.getQualifiedName(); + if (implementerClassName != null && PsiUtil.isLanguageLevel9OrHigher(identifier)) { + PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(identifier); + if (javaModule != null) { + for (PsiProvidesStatement providesStatement : javaModule.getProvides()) { + PsiClassType interfaceType = providesStatement.getInterfaceType(); + PsiReferenceList implementationList = providesStatement.getImplementationList(); + if (interfaceType != null && implementationList != null) { + PsiClassType[] implementationTypes = implementationList.getReferencedTypes(); + for (PsiClassType implementationType : implementationTypes) { + if (implementerClass.equals(implementationType.resolve())) { + PsiClass interfaceClass = interfaceType.resolve(); + if (InheritanceUtil.isInheritorOrSelf(resultClass, interfaceClass, true)) { + String interfaceClassName = interfaceClass.getQualifiedName(); + if (interfaceClassName != null) { + LineMarkerInfo info = new LineMarkerInfo<>( + identifier, + identifier.getTextRange(), + JavaPsiImplIconGroup.gutterJava9service(), + Pass.LINE_MARKERS, + e -> JavaAnalysisLocalize.serviceProvides(interfaceClassName).get(), + new ServiceProvidesNavigationHandler(interfaceClassName, implementerClassName), + GutterIconRenderer.Alignment.LEFT + ); + return Collections.singletonList(info); + } + } + } + } + } } - } } - } } - } } - } + return Collections.emptyList(); } - return Collections.emptyList(); - } - - public static List> collectServiceLoaderLoadCall(@Nonnull PsiIdentifier identifier, - @Nonnull PsiMethodCallExpression methodCall) { - if (PsiUtil.isLanguageLevel9OrHigher(methodCall)) { - PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); - - JavaReflectionReferenceUtil.ReflectiveType serviceType = null; - for (int i = 0; i < arguments.length && serviceType == null; i++) { - serviceType = JavaReflectionReferenceUtil.getReflectiveType(arguments[i]); - } - - if (serviceType != null && serviceType.isExact()) { - PsiClass psiClass = serviceType.getPsiClass(); - if (psiClass != null) { - String qualifiedName = psiClass.getQualifiedName(); - if (qualifiedName != null) { - PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(methodCall); - if (javaModule != null) { - for (PsiUsesStatement statement : javaModule.getUses()) { - PsiClassType usedClass = statement.getClassType(); - if (usedClass != null && psiClass.equals(usedClass.resolve())) { - LineMarkerInfo info = - new LineMarkerInfo<>(identifier, identifier.getTextRange(), - JavaPsiImplIconGroup.gutterJava9service(), - Pass.LINE_MARKERS, - e -> JavaAnalysisBundle.message("service.uses", qualifiedName), - new ServiceUsesNavigationHandler(qualifiedName), - GutterIconRenderer.Alignment.LEFT); - return Collections.singletonList(info); + + @RequiredReadAction + public static List> collectServiceLoaderLoadCall( + PsiIdentifier identifier, + PsiMethodCallExpression methodCall + ) { + if (PsiUtil.isLanguageLevel9OrHigher(methodCall)) { + PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + + JavaReflectionReferenceUtil.ReflectiveType serviceType = null; + for (int i = 0; i < arguments.length && serviceType == null; i++) { + serviceType = JavaReflectionReferenceUtil.getReflectiveType(arguments[i]); + } + + if (serviceType != null && serviceType.isExact()) { + PsiClass psiClass = serviceType.getPsiClass(); + if (psiClass != null) { + String qualifiedName = psiClass.getQualifiedName(); + if (qualifiedName != null) { + PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(methodCall); + if (javaModule != null) { + for (PsiUsesStatement statement : javaModule.getUses()) { + PsiClassType usedClass = statement.getClassType(); + if (usedClass != null && psiClass.equals(usedClass.resolve())) { + LineMarkerInfo info = new LineMarkerInfo<>( + identifier, + identifier.getTextRange(), + JavaPsiImplIconGroup.gutterJava9service(), + Pass.LINE_MARKERS, + e -> JavaAnalysisLocalize.serviceUses(qualifiedName).get(), + new ServiceUsesNavigationHandler(qualifiedName), + GutterIconRenderer.Alignment.LEFT + ); + return Collections.singletonList(info); + } + } + } + } } - } } - } } - } + return Collections.emptyList(); } - return Collections.emptyList(); - } - abstract static class ServiceNavigationHandler implements GutterIconNavigationHandler { - final String myInterfaceClassName; + abstract static class ServiceNavigationHandler implements GutterIconNavigationHandler { + final String myInterfaceClassName; - ServiceNavigationHandler(@Nonnull String interfaceClassName) { - myInterfaceClassName = interfaceClassName; - } + ServiceNavigationHandler(String interfaceClassName) { + myInterfaceClassName = interfaceClassName; + } - @Override - public void navigate(MouseEvent e, PsiElement element) { - Optional.ofNullable(JavaModuleGraphUtil.findDescriptorByElement(element)) - .map(this::findTargetReference) - .filter(NavigationItem.class::isInstance) - .map(NavigationItem.class::cast) - .ifPresent(item -> item.navigate(true)); - } + @Override + @RequiredUIAccess + public void navigate(MouseEvent e, PsiElement element) { + Optional.ofNullable(JavaModuleGraphUtil.findDescriptorByElement(element)) + .map(this::findTargetReference) + .filter(NavigationItem.class::isInstance) + .map(NavigationItem.class::cast) + .ifPresent(item -> item.navigate(true)); + } - public abstract PsiJavaCodeReferenceElement findTargetReference(@Nonnull PsiJavaModule module); + public abstract PsiJavaCodeReferenceElement findTargetReference(PsiJavaModule module); - @Nonnull - protected String getTargetFQN() { - return myInterfaceClassName; - } + protected String getTargetFQN() { + return myInterfaceClassName; + } - boolean isTargetReference(PsiJavaCodeReferenceElement reference) { - return reference != null && getTargetFQN().equals(reference.getQualifiedName()); + boolean isTargetReference(PsiJavaCodeReferenceElement reference) { + return reference != null && getTargetFQN().equals(reference.getQualifiedName()); + } } - } - private static class ServiceUsesNavigationHandler extends ServiceNavigationHandler { - ServiceUsesNavigationHandler(String interfaceClassName) { - super(interfaceClassName); - } + private static class ServiceUsesNavigationHandler extends ServiceNavigationHandler { + ServiceUsesNavigationHandler(String interfaceClassName) { + super(interfaceClassName); + } - @Override - public PsiJavaCodeReferenceElement findTargetReference(@Nonnull PsiJavaModule module) { - return StreamEx.of(module.getUses().iterator()) - .map(PsiUsesStatement::getClassReference) - .findAny(this::isTargetReference) - .orElse(null); + @Override + public PsiJavaCodeReferenceElement findTargetReference(PsiJavaModule module) { + return StreamEx.of(module.getUses().iterator()) + .map(PsiUsesStatement::getClassReference) + .findAny(this::isTargetReference) + .orElse(null); + } } - } - - private static class ServiceProvidesNavigationHandler extends ServiceNavigationHandler { - private final String myImplementerClassName; - ServiceProvidesNavigationHandler(@Nonnull String interfaceClassName, @Nonnull String implementerClassName) { - super(interfaceClassName); - myImplementerClassName = implementerClassName; - } + private static class ServiceProvidesNavigationHandler extends ServiceNavigationHandler { + private final String myImplementerClassName; - @Override - public PsiJavaCodeReferenceElement findTargetReference(@Nonnull PsiJavaModule module) { - PsiProvidesStatement statement = ContainerUtil.find(module.getProvides(), this::isTargetStatement); - if (statement != null) { - PsiReferenceList list = statement.getImplementationList(); - if (list != null) { - return ContainerUtil.find(list.getReferenceElements(), this::isTargetReference); + ServiceProvidesNavigationHandler(String interfaceClassName, String implementerClassName) { + super(interfaceClassName); + myImplementerClassName = implementerClassName; } - } - return null; - } + @Override + public PsiJavaCodeReferenceElement findTargetReference(PsiJavaModule module) { + PsiProvidesStatement statement = ContainerUtil.find(module.getProvides(), this::isTargetStatement); + if (statement != null) { + PsiReferenceList list = statement.getImplementationList(); + if (list != null) { + return ContainerUtil.find(list.getReferenceElements(), this::isTargetReference); + } + } - @Nonnull - @Override - protected String getTargetFQN() { - return myImplementerClassName; - } + return null; + } - private boolean isTargetStatement(@Nonnull PsiProvidesStatement statement) { - PsiJavaCodeReferenceElement reference = statement.getInterfaceReference(); - return reference != null && myInterfaceClassName.equals(reference.getQualifiedName()); + @Override + protected String getTargetFQN() { + return myImplementerClassName; + } + + private boolean isTargetStatement(PsiProvidesStatement statement) { + PsiJavaCodeReferenceElement reference = statement.getInterfaceReference(); + return reference != null && myInterfaceClassName.equals(reference.getQualifiedName()); + } } - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPass.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPass.java deleted file mode 100644 index c044541aee..0000000000 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPass.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2017 must-be.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.java.analysis.impl.codeInsight.daemon.impl; - -import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.impl.lexer.JavaLexer; -import com.intellij.java.language.psi.JavaRecursiveElementVisitor; -import com.intellij.java.language.psi.PsiJavaFile; -import com.intellij.java.language.psi.PsiKeyword; -import consulo.annotation.access.RequiredReadAction; -import consulo.application.progress.ProgressIndicator; -import consulo.document.Document; -import consulo.language.editor.impl.highlight.TextEditorHighlightingPass; -import consulo.language.editor.impl.highlight.UpdateHighlightersUtil; -import consulo.language.editor.rawHighlight.HighlightInfo; -import consulo.util.collection.ContainerUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - -/** - * @author VISTALL - * @since 09-Jan-17 - */ -public class JavaSoftKeywordHighlightingPass extends TextEditorHighlightingPass { - private final PsiJavaFile myFile; - private final List myResults = new ArrayList<>(); - - public JavaSoftKeywordHighlightingPass(@Nonnull PsiJavaFile file, @Nullable Document document) { - super(file.getProject(), document); - myFile = file; - } - - @RequiredReadAction - @Override - public void doCollectInformation(@Nonnull ProgressIndicator progressIndicator) { - LanguageLevel languageLevel = myFile.getLanguageLevel(); - - myFile.accept(new JavaRecursiveElementVisitor() { - @Override - public void visitKeyword(PsiKeyword keyword) { - if (JavaLexer.isSoftKeyword(keyword.getNode().getChars(), languageLevel)) { - ContainerUtil.addIfNotNull(myResults, HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.JAVA_KEYWORD).range(keyword).create()); - } - } - }); - } - - @Override - public void doApplyInformationToEditor() { - if (!myResults.isEmpty()) { - UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), myResults, getColorsScheme(), getId()); - } - } -} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPassFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPassFactory.java deleted file mode 100644 index e36389d140..0000000000 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/JavaSoftKeywordHighlightingPassFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2017 must-be.org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.java.analysis.impl.codeInsight.daemon.impl; - -import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.psi.PsiJavaFile; -import consulo.annotation.component.ExtensionImpl; -import consulo.codeEditor.Editor; -import consulo.language.editor.impl.highlight.TextEditorHighlightingPass; -import consulo.language.editor.impl.highlight.TextEditorHighlightingPassFactory; -import consulo.language.psi.PsiFile; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * @author VISTALL - * @since 09-Jan-17 - */ -@ExtensionImpl -public class JavaSoftKeywordHighlightingPassFactory implements TextEditorHighlightingPassFactory { - @Override - public void register(@Nonnull Registrar registrar) { - registrar.registerTextEditorHighlightingPass(this, null, null, false, -1); - } - - @Nullable - @Override - public TextEditorHighlightingPass createHighlightingPass(@Nonnull PsiFile file, @Nonnull Editor editor) { - if (file instanceof PsiJavaFile && ((PsiJavaFile) file).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) { - return new JavaSoftKeywordHighlightingPass((PsiJavaFile) file, editor.getDocument()); - } else { - return null; - } - } -} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java index cbad9efb90..81d04d45e7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java @@ -23,15 +23,15 @@ import com.intellij.java.language.impl.psi.impl.source.PsiClassImpl; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PropertyUtil; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.access.RequiredReadAction; import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressManager; -import consulo.application.util.function.Processor; import consulo.content.scope.SearchScope; import consulo.find.FindUsagesOptions; import consulo.java.language.impl.psi.augment.JavaEnumAugmentProvider; import consulo.language.editor.ImplicitUsageProvider; import consulo.language.editor.intention.IntentionAction; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.PsiComment; @@ -41,336 +41,362 @@ import consulo.language.psi.resolve.RefResolveService; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.search.PsiSearchHelper; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.usage.UsageInfo; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import java.util.function.Predicate; + +import static consulo.language.psi.search.PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES; public class UnusedSymbolUtil { - public static boolean isInjected(@Nonnull Project project, @Nonnull PsiModifierListOwner modifierListOwner) { - return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner); - } - - public static boolean isImplicitUsage(@Nonnull Project project, - @Nonnull PsiModifierListOwner element, - @Nonnull ProgressIndicator progress) { - if (isInjected(project, element)) { - return true; - } - for (ImplicitUsageProvider provider : ImplicitUsageProvider.EP_NAME.getExtensionList()) { - progress.checkCanceled(); - if (provider.isImplicitUsage(element)) { - return true; - } + public static boolean isInjected(Project project, PsiModifierListOwner modifierListOwner) { + return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner); } - return false; - } - - public static boolean isImplicitRead(@Nonnull PsiVariable variable) { - return isImplicitRead(variable.getProject(), variable, null); - } - - public static boolean isImplicitRead(@Nonnull Project project, - @Nonnull PsiVariable element, - @Nullable ProgressIndicator progress) { - for (ImplicitUsageProvider provider : ImplicitUsageProvider.EP_NAME.getExtensionList()) { - ProgressManager.checkCanceled(); - if (provider.isImplicitRead(element)) { - return true; - } - } - return isInjected(project, element); - } - - - public static boolean isImplicitWrite(@Nonnull PsiVariable variable) { - return isImplicitWrite(variable.getProject(), variable, null); - } - - public static boolean isImplicitWrite(@Nonnull Project project, - @Nonnull PsiVariable element, - @Nullable ProgressIndicator progress) { - for (ImplicitUsageProvider provider : ImplicitUsageProvider.EP_NAME.getExtensionList()) { - ProgressManager.checkCanceled(); - if (provider.isImplicitWrite(element)) { - return true; - } - } - return isInjected(project, element); - } - - @Nullable - public static HighlightInfo createUnusedSymbolInfo(@Nonnull PsiElement element, - @Nonnull String message, - @Nonnull final HighlightInfoType highlightInfoType) { - HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip - (message).create(); - if (info == null) { - return null; //filtered out + public static boolean isImplicitUsage( + Project project, + PsiModifierListOwner element, + ProgressIndicator progress + ) { + return isInjected(project, element) || project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { + progress.checkCanceled(); + return provider.isImplicitUsage(element); + }) != null; } - for (UnusedDeclarationFixProvider provider : UnusedDeclarationFixProvider.EP_NAME.getExtensionList()) { - IntentionAction[] fixes = provider.getQuickFixes(element); - for (IntentionAction fix : fixes) { - QuickFixAction.registerQuickFixAction(info, fix); - } + public static boolean isImplicitRead(PsiVariable variable) { + return isImplicitRead(variable.getProject(), variable, null); } - return info; - } - - public static boolean isFieldUnused(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiField field, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - if (helper.isLocallyUsed(field)) { - return false; + + public static boolean isImplicitRead(Project project, PsiVariable element, @Nullable ProgressIndicator progress) { + return project.getExtensionPoint(ImplicitUsageProvider.class).anyMatchSafe(provider -> { + ProgressManager.checkCanceled(); + return provider.isImplicitRead(element); + }) || isInjected(project, element); } - if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(project, containingFile, field, progress, - helper)) { - return false; + + public static boolean isImplicitWrite(PsiVariable variable) { + return isImplicitWrite(variable.getProject(), variable, null); } - return weAreSureThereAreNoUsages(project, containingFile, field, progress, helper); - } - - public static boolean isMethodReferenced(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMethod method, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - if (helper.isLocallyUsed(method)) { - return true; + + public static boolean isImplicitWrite(Project project, PsiVariable element, @Nullable ProgressIndicator progress) { + return project.getExtensionPoint(ImplicitUsageProvider.class).anyMatchSafe(provider -> { + ProgressManager.checkCanceled(); + return provider.isImplicitWrite(element); + }) || isInjected(project, element); } - boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); - PsiClass containingClass = method.getContainingClass(); - if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) { - return true; + @RequiredReadAction + public static HighlightInfo.Builder createUnusedSymbolInfo( + PsiElement element, + LocalizeValue message, + HighlightInfoType highlightInfoType + ) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(highlightInfoType) + .range(element) + .descriptionAndTooltip(message); + + element.getApplication().getExtensionPoint(UnusedDeclarationFixProvider.class).forEach(provider -> { + IntentionAction[] fixes = provider.getQuickFixes(element); + for (IntentionAction fix : fixes) { + hlBuilder.registerFix(fix); + } + }); + + return hlBuilder; } - if (isPrivate) { - if (isIntentionalPrivateConstructor(method, containingClass)) { - return true; - } - if (isImplicitUsage(project, method, progress)) { - return true; - } - if (!helper.isCurrentFileAlreadyChecked()) { - return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper); - } - } else { - //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too - boolean isConstructor = method.isConstructor(); - if (containingClass != null && isConstructor && containingClass.getConstructors().length == 1 && - isClassUsed(project, containingFile, containingClass, progress, helper)) { - return true; - } - if (isImplicitUsage(project, method, progress)) { - return true; - } - - if (!isConstructor && FindSuperElementsHelper.findSuperElements(method).length != 0) { - return true; - } - if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) { - return true; - } + + @Deprecated + @DeprecationInfo("Use variant with LocalizeValue") + @Nullable + @RequiredReadAction + public static HighlightInfo createUnusedSymbolInfo( + PsiElement element, + String message, + HighlightInfoType highlightInfoType + ) { + return createUnusedSymbolInfo(element, LocalizeValue.of(message), highlightInfoType).create(); } - return false; - } - - private static boolean weAreSureThereAreNoUsages(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull final PsiMember member, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - log("* " + member.getName() + ": call wearesure"); - if (!helper.shouldCheckUsages(member)) { - log("* " + member.getName() + ": should not check"); - return false; + + @RequiredReadAction + public static boolean isFieldUnused( + Project project, + PsiFile containingFile, + PsiField field, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (helper.isLocallyUsed(field)) { + return false; + } + //noinspection SimplifiableIfStatement + if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(project, containingFile, field, progress, helper)) { + return false; + } + return weAreSureThereAreNoUsages(project, containingFile, field, progress, helper); } - final PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null; + @RequiredReadAction + public static boolean isMethodReferenced( + Project project, + PsiFile containingFile, + PsiMethod method, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (helper.isLocallyUsed(method)) { + return true; + } - boolean sure = processUsages(project, containingFile, member, progress, ignoreFile, new Processor() { - @Override - public boolean process(UsageInfo info) { - PsiFile psiFile = info.getFile(); - if (psiFile == ignoreFile || psiFile == null) { - return true; // ignore usages in containingFile because isLocallyUsed() method would have caught - // that + boolean isPrivate = method.isPrivate(); + PsiClass containingClass = method.getContainingClass(); + if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) { + return true; } - int offset = info.getNavigationOffset(); - if (offset == -1) { - return true; + if (isPrivate) { + if (isIntentionalPrivateConstructor(method, containingClass) || isImplicitUsage(project, method, progress)) { + return true; + } + if (!helper.isCurrentFileAlreadyChecked()) { + return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper); + } + } + else { + //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too + boolean isConstructor = method.isConstructor(); + if (containingClass != null && isConstructor && containingClass.getConstructors().length == 1 && + isClassUsed(project, containingFile, containingClass, progress, helper)) { + return true; + } + if (isImplicitUsage(project, method, progress)) { + return true; + } + + if (!isConstructor && FindSuperElementsHelper.findSuperElements(method).length != 0) { + return true; + } + if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) { + return true; + } } - PsiElement element = psiFile.findElementAt(offset); - boolean inComment = element instanceof PsiComment; - log("* " + member.getName() + ": usage :" + element); - return inComment; // ignore comments - } - }); - log("* " + member.getName() + ": result:" + sure); - return sure; - } - - private static void log(String s) { - //System.out.println(s); - } - - // return false if can't process usages (weird member of too may usages) or processor returned false - public static boolean processUsages(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMember member, - @Nonnull ProgressIndicator progress, - @Nullable PsiFile ignoreFile, - @Nonnull Processor usageInfoProcessor) { - String name = member.getName(); - if (name == null) { - log("* " + member.getName() + " no name; false"); - return false; - } - SearchScope useScope = member.getUseScope(); - PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); - if (useScope instanceof GlobalSearchScope) { - // some classes may have references from within XML outside dependent modules, e.g. our actions - if (member instanceof PsiClass) { - useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope) useScope); - } - - // if we've resolved all references, find usages will be fast - PsiSearchHelper.SearchCostResult cheapEnough = RefResolveService.ENABLED && RefResolveService.getInstance - (project).isUpToDate() ? PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES : searchHelper - .isCheapEnoughToSearch(name, (GlobalSearchScope) useScope, ignoreFile, progress); - if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) { - log("* " + member.getName() + " too many usages; false"); return false; - } - - //search usages if it cheap - //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before - if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames - (member, containingFile)) { - log("* " + member.getName() + " 0 usages; true"); - return true; - } - - if (member instanceof PsiMethod) { - String propertyName = PropertyUtil.getPropertyName(member); - if (propertyName != null) { - SearchScope fileScope = containingFile.getUseScope(); - if (fileScope instanceof GlobalSearchScope && searchHelper.isCheapEnoughToSearch(propertyName, - (GlobalSearchScope) fileScope, ignoreFile, progress) == PsiSearchHelper.SearchCostResult - .TOO_MANY_OCCURRENCES) { - log("* " + member.getName() + " too many prop usages; false"); + } + + @RequiredReadAction + private static boolean weAreSureThereAreNoUsages( + Project project, + PsiFile containingFile, + PsiMember member, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + log("* " + member.getName() + ": call wearesure"); + if (!helper.shouldCheckUsages(member)) { + log("* " + member.getName() + ": should not check"); return false; - } } - } - } - FindUsagesOptions options; - if (member instanceof PsiPackage) { - options = new JavaPackageFindUsagesOptions(project); - options.isSearchForTextOccurrences = true; - } else if (member instanceof PsiClass) { - options = new JavaClassFindUsagesOptions(project); - options.isSearchForTextOccurrences = true; - } else if (member instanceof PsiMethod) { - PsiMethod method = (PsiMethod) member; - options = new JavaMethodFindUsagesOptions(project); - options.isSearchForTextOccurrences = method.isConstructor(); - } else if (member instanceof PsiVariable) { - options = new JavaVariableFindUsagesOptions(project); - options.isSearchForTextOccurrences = false; - } else { - options = new FindUsagesOptions(project); - options.isSearchForTextOccurrences = true; + + PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null; + + boolean sure = processUsages( + project, + containingFile, + member, + progress, + ignoreFile, + info -> { + PsiFile psiFile = info.getFile(); + if (psiFile == ignoreFile || psiFile == null) { + return true; // ignore usages in containingFile because isLocallyUsed() method would have caught + // that + } + int offset = info.getNavigationOffset(); + if (offset == -1) { + return true; + } + PsiElement element = psiFile.findElementAt(offset); + boolean inComment = element instanceof PsiComment; + log("* " + member.getName() + ": usage :" + element); + return inComment; // ignore comments + } + ); + log("* " + member.getName() + ": result:" + sure); + return sure; } - options.isUsages = true; - options.searchScope = useScope; - return JavaFindUsagesHelper.processElementUsages(member, options, usageInfoProcessor); - } - - private static boolean isEnumValuesMethodUsed(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMember member, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - final PsiClass containingClass = member.getContainingClass(); - if (!(containingClass instanceof PsiClassImpl)) { - return true; + + private static void log(String s) { + //System.out.println(s); } - if (containingClass.isEnum()) { - PsiMethod[] methodsByName = containingClass.findMethodsByName(JavaEnumAugmentProvider.VALUES_METHOD_NAME, - false); + // return false if can't process usages (weird member of too may usages) or processor returned false + @RequiredReadAction + public static boolean processUsages( + Project project, + PsiFile containingFile, + PsiMember member, + ProgressIndicator progress, + @Nullable PsiFile ignoreFile, + @RequiredReadAction Predicate usageInfoProcessor + ) { + String name = member.getName(); + if (name == null) { + log("* " + member.getName() + " no name; false"); + return false; + } + SearchScope useScope = member.getUseScope(); + PsiSearchHelper searchHelper = project.getInstance(PsiSearchHelper.class); + if (useScope instanceof GlobalSearchScope globalSearchScope) { + // some classes may have references from within XML outside dependent modules, e.g. our actions + if (member instanceof PsiClass) { + useScope = GlobalSearchScope.projectScope(project).uniteWith(globalSearchScope); + } + + // if we've resolved all references, find usages will be fast + PsiSearchHelper.SearchCostResult cheapEnough = + RefResolveService.ENABLED && RefResolveService.getInstance(project).isUpToDate() + ? PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES + : searchHelper.isCheapEnoughToSearch(name, globalSearchScope, ignoreFile, progress); + if (cheapEnough == TOO_MANY_OCCURRENCES) { + log("* " + member.getName() + " too many usages; false"); + return false; + } - PsiMethod valuesMethod = null; - for (PsiMethod psiMethod : methodsByName) { - if (psiMethod.getParameterList().getParametersCount() == 0 && psiMethod.hasModifierProperty - (PsiModifier.STATIC)) { - valuesMethod = psiMethod; - break; + //search usages if it cheap + //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before + if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames + (member, containingFile)) { + log("* " + member.getName() + " 0 usages; true"); + return true; + } + + if (member instanceof PsiMethod) { + String propertyName = PropertyUtil.getPropertyName(member); + if (propertyName != null + && containingFile.getUseScope() instanceof GlobalSearchScope fileScope + && searchHelper.isCheapEnoughToSearch(propertyName, fileScope, ignoreFile, progress) == TOO_MANY_OCCURRENCES) { + log("* " + member.getName() + " too many prop usages; false"); + return false; + } + } + } + FindUsagesOptions options; + if (member instanceof PsiPackage) { + options = new JavaPackageFindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + else if (member instanceof PsiClass) { + options = new JavaClassFindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + else if (member instanceof PsiMethod method) { + options = new JavaMethodFindUsagesOptions(project); + options.isSearchForTextOccurrences = method.isConstructor(); } - } - return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper); - } else { - return true; + else if (member instanceof PsiVariable) { + options = new JavaVariableFindUsagesOptions(project); + options.isSearchForTextOccurrences = false; + } + else { + options = new FindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + options.isUsages = true; + options.searchScope = useScope; + return JavaFindUsagesHelper.processElementUsages(member, options, usageInfoProcessor); } - } - private static boolean canBeReferencedViaWeirdNames(@Nonnull PsiMember member, @Nonnull PsiFile containingFile) { - if (member instanceof PsiClass) { - return false; - } - if (!(containingFile instanceof PsiJavaFile)) { - return true; // Groovy field can be referenced from Java by getter - } - if (member instanceof PsiField) { - return false; //Java field cannot be referenced by anything but its name + @RequiredReadAction + private static boolean isEnumValuesMethodUsed( + Project project, + PsiFile containingFile, + PsiMember member, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + PsiClass containingClass = member.getContainingClass(); + if (!(containingClass instanceof PsiClassImpl)) { + return true; + } + + if (containingClass.isEnum()) { + PsiMethod[] methodsByName = containingClass.findMethodsByName( + JavaEnumAugmentProvider.VALUES_METHOD_NAME, + false + ); + + PsiMethod valuesMethod = null; + for (PsiMethod psiMethod : methodsByName) { + if (psiMethod.getParameterList().getParametersCount() == 0 && psiMethod.isStatic()) { + valuesMethod = psiMethod; + break; + } + } + return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper); + } + else { + return true; + } } - if (member instanceof PsiMethod) { - return PropertyUtil.isSimplePropertyAccessor((PsiMethod) member); //Java accessors can be referenced by - // field name from Groovy + + private static boolean canBeReferencedViaWeirdNames(PsiMember member, PsiFile containingFile) { + if (member instanceof PsiClass) { + return false; + } + if (!(containingFile instanceof PsiJavaFile)) { + return true; // Groovy field can be referenced from Java by getter + } + if (member instanceof PsiField) { + return false; //Java field cannot be referenced by anything but its name + } + //noinspection SimplifiableIfStatement + if (member instanceof PsiMethod method) { + return PropertyUtil.isSimplePropertyAccessor(method); //Java accessors can be referenced by field name from Groovy + } + return false; } - return false; - } - - public static boolean isClassUsed(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiClass aClass, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - Boolean result = helper.unusedClassCache.get(aClass); - if (result == null) { - result = isReallyUsed(project, containingFile, aClass, progress, helper); - helper.unusedClassCache.put(aClass, result); + + @RequiredReadAction + public static boolean isClassUsed( + Project project, + PsiFile containingFile, + PsiClass aClass, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + Boolean result = helper.unusedClassCache.get(aClass); + if (result == null) { + result = isReallyUsed(project, containingFile, aClass, progress, helper); + helper.unusedClassCache.put(aClass, result); + } + return result; } - return result; - } - - private static boolean isReallyUsed(@Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiClass aClass, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) { - return true; + + @RequiredReadAction + private static boolean isReallyUsed( + Project project, + PsiFile containingFile, + PsiClass aClass, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) { + return true; + } + if (helper.isCurrentFileAlreadyChecked()) { + if (aClass.getContainingClass() != null && aClass.isPrivate() + || aClass.getParent() instanceof PsiDeclarationStatement + || aClass instanceof PsiTypeParameter) { + return false; + } + } + return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper); } - if (helper.isCurrentFileAlreadyChecked()) { - if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) || - aClass.getParent() instanceof PsiDeclarationStatement || - aClass instanceof PsiTypeParameter) { - return false; - } + + private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) { + return method.isConstructor() && + containingClass != null && + containingClass.getConstructors().length == 1; } - return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper); - } - - private static boolean isIntentionalPrivateConstructor(@Nonnull PsiMethod method, PsiClass containingClass) { - return method.isConstructor() && - containingClass != null && - containingClass.getConstructors().length == 1; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressAllForClassFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressAllForClassFix.java index f09cafebd0..cd51690609 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressAllForClassFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressAllForClassFix.java @@ -19,17 +19,17 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.javadoc.PsiDocTag; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.FileModificationService; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.SuppressionUtil; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * User: anna @@ -42,6 +42,7 @@ public SuppressAllForClassFix() { super(SuppressionUtil.ALL); } + @RequiredReadAction @Override @Nullable public PsiDocCommentOwner getContainer(final PsiElement element) { @@ -60,13 +61,13 @@ public PsiDocCommentOwner getContainer(final PsiElement element) { } @Override - @Nonnull - public String getText() { - return InspectionsBundle.message("suppress.all.for.class"); + public LocalizeValue getText() { + return InspectionLocalize.suppressAllForClass(); } + @RequiredReadAction @Override - public void invoke(@Nonnull final Project project, @Nonnull final PsiElement element) throws IncorrectOperationException { + public void invoke(final Project project, final PsiElement element) throws IncorrectOperationException { final PsiDocCommentOwner container = getContainer(element); LOG.assertTrue(container != null); if (!FileModificationService.getInstance().preparePsiElementForWrite(container)) return; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressByJavaCommentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressByJavaCommentFix.java index 23f2c4cbd1..556c77f000 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressByJavaCommentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressByJavaCommentFix.java @@ -27,15 +27,14 @@ import consulo.language.util.IncorrectOperationException; import consulo.project.Project; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author yole */ public class SuppressByJavaCommentFix extends SuppressByCommentFix { - public SuppressByJavaCommentFix(@Nonnull HighlightDisplayKey key) { + public SuppressByJavaCommentFix(HighlightDisplayKey key) { super(key, PsiStatement.class); } @@ -56,9 +55,9 @@ private static boolean hasJspMethodCallAsParent(PsiElement context) { } @Override - protected void createSuppression(@Nonnull final Project project, - @Nonnull final PsiElement element, - @Nonnull final PsiElement container) throws IncorrectOperationException { + protected void createSuppression(final Project project, + final PsiElement element, + final PsiElement container) throws IncorrectOperationException { PsiElement declaredElement = JavaSuppressionUtil.getElementToAnnotate(element, container); if (declaredElement == null) { suppressWithComment(project, element, container); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressFix.java index f63ed2be66..c38cbc7f11 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressFix.java @@ -20,10 +20,11 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.javadoc.PsiDocTag; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.AbstractBatchSuppressByNoInspectionCommentFix; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.SuppressionUtil; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.editor.util.LanguageUndoUtil; import consulo.language.psi.PsiElement; @@ -32,35 +33,31 @@ import consulo.language.psi.SyntheticElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import consulo.undoRedo.util.UndoUtil; -import consulo.util.lang.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author ven */ public class SuppressFix extends AbstractBatchSuppressByNoInspectionCommentFix { - public SuppressFix(@Nonnull HighlightDisplayKey key) { + public SuppressFix(HighlightDisplayKey key) { this(key.getID()); } - public SuppressFix(@Nonnull String ID) { + public SuppressFix(String ID) { super(ID, false); } @Override - @Nonnull - public String getText() { - String myText = super.getText(); - return StringUtil.isEmpty(myText) ? "Suppress for member" : myText; + public LocalizeValue getText() { + return super.getText().orIfEmpty(LocalizeValue.localizeTODO("Suppress for member")); } @Override @Nullable + @RequiredReadAction public PsiDocCommentOwner getContainer(final PsiElement context) { if (context == null || !context.getManager().isInProject(context)) { return null; @@ -84,19 +81,26 @@ public PsiDocCommentOwner getContainer(final PsiElement context) { } @Override - public boolean isAvailable(@Nonnull final Project project, @Nonnull final PsiElement context) { + @RequiredReadAction + public boolean isAvailable(final Project project, final PsiElement context) { PsiDocCommentOwner container = getContainer(context); boolean isValid = container != null && !(container instanceof PsiMethod && container instanceof SyntheticElement); if (!isValid) { return false; } - setText(container instanceof PsiClass ? InspectionsBundle.message("suppress.inspection.class") : container instanceof PsiMethod ? - InspectionsBundle.message("suppress.inspection.method") : InspectionsBundle.message("suppress.inspection.field")); + setText( + container instanceof PsiClass + ? InspectionLocalize.suppressInspectionClass() + : container instanceof PsiMethod + ? InspectionLocalize.suppressInspectionMethod() + : InspectionLocalize.suppressInspectionField() + ); return true; } @Override - public void invoke(@Nonnull final Project project, @Nonnull final PsiElement element) throws IncorrectOperationException { + @RequiredReadAction + public void invoke(final Project project, final PsiElement element) throws IncorrectOperationException { if (doSuppress(project, getContainer(element))) { return; } @@ -105,7 +109,8 @@ public void invoke(@Nonnull final Project project, @Nonnull final PsiElement ele LanguageUndoUtil.markPsiFileForUndo(element.getContainingFile()); } - private boolean doSuppress(@Nonnull Project project, PsiDocCommentOwner container) { + @RequiredReadAction + private boolean doSuppress(Project project, PsiDocCommentOwner container) { assert container != null; if (!FileModificationService.getInstance().preparePsiElementForWrite(container)) { return true; @@ -137,7 +142,7 @@ private boolean doSuppress(@Nonnull Project project, PsiDocCommentOwner containe return false; } - protected boolean use15Suppressions(@Nonnull PsiDocCommentOwner container) { + protected boolean use15Suppressions(PsiDocCommentOwner container) { return JavaSuppressionUtil.canHave15Suppressions(container) && !JavaSuppressionUtil.alreadyHas14Suppressions(container); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressForClassFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressForClassFix.java index 301bdad724..d3f78cf7b2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressForClassFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressForClassFix.java @@ -15,16 +15,16 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.actions; -import consulo.language.editor.rawHighlight.HighlightDisplayKey; -import consulo.language.editor.inspection.InspectionsBundle; import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiDeclarationStatement; import com.intellij.java.language.psi.PsiDocCommentOwner; +import consulo.annotation.access.RequiredReadAction; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; /** * User: anna @@ -39,6 +39,7 @@ public SuppressForClassFix(final String id) { super(id); } + @RequiredReadAction @Override @Nullable public PsiDocCommentOwner getContainer(final PsiElement element) { @@ -48,7 +49,8 @@ public PsiDocCommentOwner getContainer(final PsiElement element) { } while (container != null) { final PsiClass parentClass = PsiTreeUtil.getParentOfType(container, PsiClass.class); - if ((parentClass == null || container.getParent() instanceof PsiDeclarationStatement || container.getParent() instanceof PsiClass) && container instanceof PsiClass) { + if ((parentClass == null || container.getParent() instanceof PsiDeclarationStatement || container.getParent() instanceof PsiClass) + && container instanceof PsiClass) { return container; } container = parentClass; @@ -57,8 +59,7 @@ public PsiDocCommentOwner getContainer(final PsiElement element) { } @Override - @Nonnull - public String getText() { - return InspectionsBundle.message("suppress.inspection.class"); + public LocalizeValue getText() { + return InspectionLocalize.suppressInspectionClass(); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressLocalWithCommentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressLocalWithCommentFix.java index 0446af819c..ea02c0ae54 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressLocalWithCommentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressLocalWithCommentFix.java @@ -17,17 +17,17 @@ import consulo.language.editor.rawHighlight.HighlightDisplayKey; import com.intellij.java.analysis.impl.codeInspection.JavaSuppressionUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * User: anna */ public class SuppressLocalWithCommentFix extends SuppressByJavaCommentFix { - public SuppressLocalWithCommentFix(@Nonnull HighlightDisplayKey key) { + public SuppressLocalWithCommentFix(HighlightDisplayKey key) { super(key); } @@ -43,14 +43,13 @@ public PsiElement getContainer(PsiElement context) { } @Override - protected void createSuppression(@Nonnull Project project, @Nonnull PsiElement element, @Nonnull PsiElement container) + protected void createSuppression(Project project, PsiElement element, PsiElement container) throws IncorrectOperationException { suppressWithComment(project, element, container); } - @Nonnull @Override - public String getText() { - return "Suppress for statement with comment"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Suppress for statement with comment"); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressParameterFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressParameterFix.java index a6a525d084..16cebc3276 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressParameterFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/actions/SuppressParameterFix.java @@ -18,6 +18,7 @@ import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.editor.inspection.AbstractBatchSuppressByNoInspectionCommentFix; import com.intellij.java.analysis.impl.codeInspection.JavaSuppressionUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.language.psi.PsiElement; import com.intellij.java.language.psi.PsiModifierList; @@ -26,14 +27,13 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author ven */ public class SuppressParameterFix extends AbstractBatchSuppressByNoInspectionCommentFix { - public SuppressParameterFix(@Nonnull HighlightDisplayKey key) { + public SuppressParameterFix(HighlightDisplayKey key) { this(key.getID()); } @@ -42,9 +42,8 @@ public SuppressParameterFix(String ID) { } @Override - @Nonnull - public String getText() { - return "Suppress for parameter"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Suppress for parameter"); } @Nullable @@ -60,8 +59,8 @@ protected boolean replaceSuppressionComments(PsiElement container) { } @Override - protected void createSuppression(@Nonnull Project project, @Nonnull PsiElement element, - @Nonnull PsiElement cont) throws IncorrectOperationException { + protected void createSuppression(Project project, PsiElement element, + PsiElement cont) throws IncorrectOperationException { PsiModifierListOwner container = (PsiModifierListOwner) cont; final PsiModifierList modifierList = container.getModifierList(); if (modifierList != null) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java index 3756112085..a6fef34cd1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java @@ -16,9 +16,9 @@ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; import com.intellij.java.analysis.codeInsight.intention.QuickFixFactory; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.codeInsight.AnnotationTargetUtil; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.impl.PsiImplUtil; import com.intellij.java.language.impl.psi.impl.source.PsiClassReferenceType; import com.intellij.java.language.impl.psi.impl.source.PsiImmediateClassType; @@ -26,10 +26,12 @@ import com.intellij.java.language.psi.util.ClassUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.intention.IntentionAction; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; @@ -37,13 +39,13 @@ import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.lang.Comparing; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; @@ -56,820 +58,866 @@ * @author ven */ public class AnnotationsHighlightUtil { - private static final Logger LOG = Logger.getInstance(AnnotationsHighlightUtil.class); + private static final Logger LOG = Logger.getInstance(AnnotationsHighlightUtil.class); - @Nullable - public static HighlightInfo checkNameValuePair(PsiNameValuePair pair) { - PsiReference ref = pair.getReference(); - if (ref == null) { - return null; + @Nullable + @RequiredReadAction + public static HighlightInfo checkNameValuePair(PsiNameValuePair pair) { + PsiReference ref = pair.getReference(); + if (ref == null) { + return null; + } + PsiMethod method = (PsiMethod) ref.resolve(); + if (method == null) { + if (pair.getName() != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(ref.getElement()) + .descriptionAndTooltip(JavaErrorLocalize.annotationUnknownMethod(ref.getCanonicalText())) + .registerFix(QuickFixFactory.getInstance().createCreateAnnotationMethodFromUsageFix(pair)) + .create(); + } + else { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref.getElement()) + .descriptionAndTooltip(JavaErrorLocalize.annotationMissingMethod(ref.getCanonicalText())); + for (IntentionAction action : QuickFixFactory.getInstance().createAddAnnotationAttributeNameFixes(pair)) { + hlBuilder.registerFix(action); + } + return hlBuilder.create(); + } + } + else { + PsiType returnType = method.getReturnType(); + assert returnType != null : method; + PsiAnnotationMemberValue value = pair.getValue(); + HighlightInfo info = checkMemberValueType(value, returnType); + if (info != null) { + return info; + } + + return checkDuplicateAttribute(pair); + } } - PsiMethod method = (PsiMethod) ref.resolve(); - if (method == null) { - if (pair.getName() != null) { - final String description = JavaErrorBundle.message("annotation.unknown.method", ref.getCanonicalText()); - PsiElement element = ref.getElement(); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(element).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateAnnotationMethodFromUsageFix(pair)); - return highlightInfo; - } else { - String description = JavaErrorBundle.message("annotation.missing.method", ref.getCanonicalText()); - PsiElement element = ref.getElement(); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - for (IntentionAction action : QuickFixFactory.getInstance().createAddAnnotationAttributeNameFixes(pair)) { - QuickFixAction.registerQuickFixAction(highlightInfo, action); - } - return highlightInfo; - } - } else { - PsiType returnType = method.getReturnType(); - assert returnType != null : method; - PsiAnnotationMemberValue value = pair.getValue(); - HighlightInfo info = checkMemberValueType(value, returnType); - if (info != null) { - return info; - } - - return checkDuplicateAttribute(pair); + + @Nullable + @RequiredReadAction + private static HighlightInfo checkDuplicateAttribute(PsiNameValuePair pair) { + PsiAnnotationParameterList annotation = (PsiAnnotationParameterList) pair.getParent(); + PsiNameValuePair[] attributes = annotation.getAttributes(); + for (PsiNameValuePair attribute : attributes) { + if (attribute == pair) { + break; + } + String name = pair.getName(); + if (Comparing.equal(attribute.getName(), name)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(pair) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeDuplicate( + name == null ? PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME : name + )) + .create(); + } + } + + return null; } - } - - @Nullable - private static HighlightInfo checkDuplicateAttribute(PsiNameValuePair pair) { - PsiAnnotationParameterList annotation = (PsiAnnotationParameterList) pair.getParent(); - PsiNameValuePair[] attributes = annotation.getAttributes(); - for (PsiNameValuePair attribute : attributes) { - if (attribute == pair) { - break; - } - String name = pair.getName(); - if (Comparing.equal(attribute.getName(), name)) { - String description = JavaErrorBundle.message("annotation.duplicate.attribute", name == null ? PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME : name); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(pair).descriptionAndTooltip(description).create(); - } + + @RequiredReadAction + private static String formatReference(PsiJavaCodeReferenceElement ref) { + return ref.getCanonicalText(); } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkMemberValueType(@Nullable PsiAnnotationMemberValue value, PsiType expectedType) { + if (value == null) { + return null; + } - private static String formatReference(PsiJavaCodeReferenceElement ref) { - return ref.getCanonicalText(); - } + if (expectedType instanceof PsiClassType && expectedType.equalsToText(CommonClassNames.JAVA_LANG_CLASS)) { + if (!(value instanceof PsiClassObjectAccessExpression)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(value) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeNonClassLiteral()) + .create(); + } + } - @Nullable - public static HighlightInfo checkMemberValueType(@Nullable PsiAnnotationMemberValue value, PsiType expectedType) { - if (value == null) { - return null; - } + if (value instanceof PsiAnnotation annotation) { + PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); + if (nameRef == null) { + return null; + } - if (expectedType instanceof PsiClassType && expectedType.equalsToText(JavaClassNames.JAVA_LANG_CLASS)) { - if (!(value instanceof PsiClassObjectAccessExpression)) { - String description = JavaErrorBundle.message("annotation.non.class.literal.attribute.value"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); - } - } + if (expectedType instanceof PsiClassType expectedClassType) { + PsiClass aClass = expectedClassType.resolve(); + if (aClass != null && nameRef.isReferenceTo(aClass)) { + return null; + } + } - if (value instanceof PsiAnnotation) { - PsiJavaCodeReferenceElement nameRef = ((PsiAnnotation) value).getNameReferenceElement(); - if (nameRef == null) { - return null; - } + if (expectedType instanceof PsiArrayType expectedArrayType + && expectedArrayType.getComponentType() instanceof PsiClassType componentClassType) { + PsiClass aClass = componentClassType.resolve(); + if (aClass != null && nameRef.isReferenceTo(aClass)) { + return null; + } + } - if (expectedType instanceof PsiClassType) { - PsiClass aClass = ((PsiClassType) expectedType).resolve(); - if (aClass != null && nameRef.isReferenceTo(aClass)) { - return null; + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(annotation) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeIncompatibleType( + JavaHighlightUtil.formatType(expectedType), + formatReference(nameRef) + )) + .create(); } - } - if (expectedType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) expectedType).getComponentType(); - if (componentType instanceof PsiClassType) { - PsiClass aClass = ((PsiClassType) componentType).resolve(); - if (aClass != null && nameRef.isReferenceTo(aClass)) { - return null; - } + if (value instanceof PsiArrayInitializerMemberValue) { + if (expectedType instanceof PsiArrayType) { + return null; + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(value) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeIllegalArrayInitializer(JavaHighlightUtil.formatType(expectedType))) + .create(); } - } - String description = JavaErrorBundle.message("incompatible.types", JavaHighlightUtil.formatType(expectedType), formatReference(nameRef)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); - } + if (value instanceof PsiExpression expr) { + PsiType type = expr.getType(); - if (value instanceof PsiArrayInitializerMemberValue) { - if (expectedType instanceof PsiArrayType) { - return null; - } - String description = JavaErrorBundle.message("annotation.illegal.array.initializer", JavaHighlightUtil.formatType(expectedType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); - } + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null && psiClass.isEnum() + && !(expr instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiEnumConstant)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeNonEnumConstant()) + .create(); + } - if (value instanceof PsiExpression) { - PsiExpression expr = (PsiExpression) value; - PsiType type = expr.getType(); + if (type != null && TypeConversionUtil.areTypesAssignmentCompatible(expectedType, expr) + || expectedType instanceof PsiArrayType expectedArrayType + && TypeConversionUtil.areTypesAssignmentCompatible(expectedArrayType.getComponentType(), expr)) { + return null; + } - final PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (psiClass != null && psiClass.isEnum() && !(expr instanceof PsiReferenceExpression && ((PsiReferenceExpression) expr).resolve() instanceof PsiEnumConstant)) { - String description = JavaErrorBundle.message("annotation.non.enum.constant.attribute.value"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); - } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(value) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeIncompatibleType( + JavaHighlightUtil.formatType(expectedType), + JavaHighlightUtil.formatType(type) + )) + .registerFix(QuickFixFactory.getInstance().createSurroundWithQuotesAnnotationParameterValueFix(value, expectedType)) + .create(); + } - if (type != null && TypeConversionUtil.areTypesAssignmentCompatible(expectedType, expr) || expectedType instanceof PsiArrayType && TypeConversionUtil.areTypesAssignmentCompatible(( - (PsiArrayType) expectedType).getComponentType(), expr)) { + LOG.error("Unknown annotation member value: " + value); return null; - } - - String description = JavaErrorBundle.message("incompatible.types", JavaHighlightUtil.formatType(expectedType), JavaHighlightUtil.formatType(type)); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createSurroundWithQuotesAnnotationParameterValueFix(value, expectedType)); - return info; } - LOG.error("Unknown annotation member value: " + value); - return null; - } + @RequiredReadAction + public static HighlightInfo.Builder checkDuplicateAnnotations( + PsiAnnotation annotationToCheck, + LanguageLevel languageLevel + ) { + PsiAnnotationOwner owner = annotationToCheck.getOwner(); + if (owner == null) { + return null; + } - public static HighlightInfo checkDuplicateAnnotations(@Nonnull PsiAnnotation annotationToCheck, @Nonnull LanguageLevel languageLevel) { - PsiAnnotationOwner owner = annotationToCheck.getOwner(); - if (owner == null) { - return null; - } + PsiJavaCodeReferenceElement element = annotationToCheck.getNameReferenceElement(); + if (element == null || !(element.resolve() instanceof PsiClass annotationType)) { + return null; + } - PsiJavaCodeReferenceElement element = annotationToCheck.getNameReferenceElement(); - if (element == null) { - return null; - } - PsiElement resolved = element.resolve(); - if (!(resolved instanceof PsiClass)) { - return null; - } + PsiClass contained = contained(annotationType); + String containedElementFQN = contained == null ? null : contained.getQualifiedName(); - PsiClass annotationType = (PsiClass) resolved; - - PsiClass contained = contained(annotationType); - String containedElementFQN = contained == null ? null : contained.getQualifiedName(); - - if (containedElementFQN != null) { - String containerName = annotationType.getQualifiedName(); - if (isAnnotationRepeatedTwice(owner, containedElementFQN)) { - String description = JavaErrorBundle.message("annotation.container.wrong.place", containerName); - return annotationError(annotationToCheck, description); - } - } else if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) { - if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - String description = JavaErrorBundle.message("annotation.duplicate.annotation"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - } - - PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), JavaClassNames.JAVA_LANG_ANNOTATION_REPEATABLE); - if (metaAnno == null) { - String explanation = JavaErrorBundle.message("annotation.non.repeatable", annotationType.getQualifiedName()); - String description = JavaErrorBundle.message("annotation.duplicate.explained", explanation); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - } - - String explanation = doCheckRepeatableAnnotation(metaAnno); - if (explanation != null) { - String description = JavaErrorBundle.message("annotation.duplicate.explained", explanation); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - } - - PsiClass container = getRepeatableContainer(metaAnno); - if (container != null) { - PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner); - PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(container, targets); - if (applicable == null) { - String target = JavaErrorBundle.message("annotation.target." + targets[0]); - String message = JavaErrorBundle.message("annotation.container.not.applicable", container.getName(), target); - return annotationError(annotationToCheck, message); + if (containedElementFQN != null) { + String containerName = annotationType.getQualifiedName(); + if (isAnnotationRepeatedTwice(owner, containedElementFQN)) { + return annotationError(annotationToCheck, JavaCompilationErrorLocalize.annotationContainerWrongPlace(containerName)); + } } - } - } + else if (isAnnotationRepeatedTwice(owner, annotationType.getQualifiedName())) { + if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationDuplicate()); + } - return null; - } + PsiAnnotation metaAnno = + PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE); + if (metaAnno == null) { + LocalizeValue explanation = JavaCompilationErrorLocalize.annotationDuplicateNonRepeatable(annotationType.getQualifiedName()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationDuplicateExplained(explanation)); + } - // returns contained element - private static PsiClass contained(PsiClass annotationType) { - if (!annotationType.isAnnotationType()) { - return null; - } - PsiMethod[] values = annotationType.findMethodsByName("value", false); - if (values.length != 1) { - return null; - } - PsiMethod value = values[0]; - PsiType returnType = value.getReturnType(); - if (!(returnType instanceof PsiArrayType)) { - return null; - } - PsiType type = ((PsiArrayType) returnType).getComponentType(); - if (!(type instanceof PsiClassType)) { - return null; - } - PsiClass contained = ((PsiClassType) type).resolve(); - if (contained == null || !contained.isAnnotationType()) { - return null; - } - if (PsiImplUtil.findAnnotation(contained.getModifierList(), JavaClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) { - return null; - } + LocalizeValue explanation = doCheckRepeatableAnnotation(metaAnno); + if (explanation.isNotEmpty()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationDuplicateExplained(explanation)); + } - return contained; - } - - private static boolean isAnnotationRepeatedTwice(@Nonnull PsiAnnotationOwner owner, @Nullable String qualifiedName) { - int count = 0; - for (PsiAnnotation annotation : owner.getAnnotations()) { - PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); - if (nameRef == null) { - continue; - } - PsiElement resolved = nameRef.resolve(); - if (!(resolved instanceof PsiClass) || !Comparing.equal(qualifiedName, ((PsiClass) resolved).getQualifiedName())) { - continue; - } - if (++count == 2) { - return true; - } - } - return false; - } - - @Nullable - public static HighlightInfo checkMissingAttributes(PsiAnnotation annotation) { - PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); - if (nameRef == null) { - return null; - } - PsiClass aClass = (PsiClass) nameRef.resolve(); - if (aClass != null && aClass.isAnnotationType()) { - Set names = new HashSet<>(); - PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); - for (PsiNameValuePair attribute : attributes) { - final String name = attribute.getName(); - if (name != null) { - names.add(name); - } else { - names.add(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); - } - } - - PsiMethod[] annotationMethods = aClass.getMethods(); - List missed = new ArrayList<>(); - for (PsiMethod method : annotationMethods) { - if (PsiUtil.isAnnotationMethod(method)) { - PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod) method; - if (annotationMethod.getDefaultValue() == null) { - if (!names.contains(annotationMethod.getName())) { - missed.add(annotationMethod.getName()); - } - } - } - } - - if (!missed.isEmpty()) { - StringBuffer buff = new StringBuffer("'" + missed.get(0) + "'"); - for (int i = 1; i < missed.size(); i++) { - buff.append(", "); - buff.append("'").append(missed.get(i)).append("'"); - } - - String description = JavaErrorBundle.message("annotation.missing.attribute", buff); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameRef).descriptionAndTooltip(description).create(); - IntentionAction fix = QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missed); - QuickFixAction.registerQuickFixAction(info, fix); - return info; - } - } + PsiClass container = getRepeatableContainer(metaAnno); + if (container != null) { + PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner); + PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(container, targets); + if (applicable == null) { + return annotationError( + annotationToCheck, + JavaCompilationErrorLocalize.annotationContainerNotApplicable(container.getName(), targets[0].getPresentableText()) + ); + } + } + } - return null; - } - - @Nullable - public static HighlightInfo checkConstantExpression(PsiExpression expression) { - final PsiElement parent = expression.getParent(); - if (PsiUtil.isAnnotationMethod(parent) || parent instanceof PsiNameValuePair || parent instanceof PsiArrayInitializerMemberValue) { - if (!PsiUtil.isConstantExpression(expression)) { - String description = JavaErrorBundle.message("annotation.non.constant.attribute.value"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - } + return null; } - return null; - } + // returns contained element + private static PsiClass contained(PsiClass annotationType) { + if (!annotationType.isAnnotationType()) { + return null; + } + PsiMethod[] values = annotationType.findMethodsByName("value", false); + if (values.length != 1) { + return null; + } + if (!(values[0].getReturnType() instanceof PsiArrayType arrayType)) { + return null; + } + if (!(arrayType.getComponentType() instanceof PsiClassType componentClassType)) { + return null; + } + PsiClass contained = componentClassType.resolve(); + if (contained == null || !contained.isAnnotationType()) { + return null; + } + if (PsiImplUtil.findAnnotation(contained.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE) == null) { + return null; + } - @Nullable - public static HighlightInfo checkValidAnnotationType(PsiType type, final PsiTypeElement typeElement) { - if (type != null && type.accept(AnnotationReturnTypeVisitor.INSTANCE).booleanValue()) { - return null; - } - String description = JavaErrorBundle.message("annotation.invalid.annotation.member.type", type != null ? type.getPresentableText() : null); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } - - private static final ElementPattern ANY_ANNOTATION_ALLOWED = psiElement().andOr(psiElement().withParent(PsiNameValuePair.class), psiElement().withParents - (PsiArrayInitializerMemberValue.class, PsiNameValuePair.class), psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class), psiElement().withParent - (PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT)); - - @Nullable - public static HighlightInfo checkApplicability(@Nonnull PsiAnnotation annotation, @Nonnull LanguageLevel level, @Nonnull PsiFile file) { - if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) { - return null; + return contained; } - PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); - if (nameRef == null) { - return null; + @RequiredReadAction + private static boolean isAnnotationRepeatedTwice(PsiAnnotationOwner owner, @Nullable String qualifiedName) { + int count = 0; + for (PsiAnnotation annotation : owner.getAnnotations()) { + PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); + if (nameRef == null + || !(nameRef.resolve() instanceof PsiClass psiClass) + || !Comparing.equal(qualifiedName, psiClass.getQualifiedName())) { + continue; + } + if (++count == 2) { + return true; + } + } + return false; } - PsiAnnotationOwner owner = annotation.getOwner(); - PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner); - if (owner == null || targets.length == 0) { - String message = JavaErrorBundle.message("annotation.not.allowed.here"); - return annotationError(annotation, message); - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMissingAttributes(PsiAnnotation annotation) { + PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); + if (nameRef == null) { + return null; + } + PsiClass aClass = (PsiClass) nameRef.resolve(); + if (aClass != null && aClass.isAnnotationType()) { + Set names = new HashSet<>(); + PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); + for (PsiNameValuePair attribute : attributes) { + String name = attribute.getName(); + if (name != null) { + names.add(name); + } + else { + names.add(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); + } + } - if (!(owner instanceof PsiModifierList)) { - HighlightInfo info = HighlightUtil.checkFeature(annotation, HighlightUtil.Feature.TYPE_ANNOTATIONS, level, file); - if (info != null) { - return info; - } - } + PsiMethod[] annotationMethods = aClass.getMethods(); + List missed = new ArrayList<>(); + for (PsiMethod method : annotationMethods) { + if (PsiUtil.isAnnotationMethod(method)) { + PsiAnnotationMethod annotationMethod = (PsiAnnotationMethod) method; + if (annotationMethod.getDefaultValue() == null && !names.contains(annotationMethod.getName())) { + missed.add(annotationMethod.getName()); + } + } + } - PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets); - if (applicable == PsiAnnotation.TargetType.UNKNOWN) { - return null; + if (!missed.isEmpty()) { + StringBuffer buff = new StringBuffer("'" + missed.get(0) + "'"); + for (int i = 1; i < missed.size(); i++) { + buff.append(", "); + buff.append("'").append(missed.get(i)).append("'"); + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(nameRef) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMissingAttribute(buff)) + .registerFix( + QuickFixFactory.getInstance().createAddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missed) + ); + } + } + + return null; } - if (applicable == null) { - String target = JavaErrorBundle.message("annotation.target." + targets[0]); - String message = JavaErrorBundle.message("annotation.not.applicable", nameRef.getText(), target); - return annotationError(annotation, message); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkConstantExpression(PsiExpression expression) { + PsiElement parent = expression.getParent(); + if (PsiUtil.isAnnotationMethod(parent) || parent instanceof PsiNameValuePair || parent instanceof PsiArrayInitializerMemberValue) { + if (!PsiUtil.isConstantExpression(expression)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationAttributeNonConstant()); + } + } + + return null; } - if (applicable == PsiAnnotation.TargetType.TYPE_USE) { - if (owner instanceof PsiClassReferenceType) { - PsiJavaCodeReferenceElement ref = ((PsiClassReferenceType) owner).getReference(); - HighlightInfo info = checkReferenceTarget(annotation, ref); - if (info != null) { - return info; - } - } else if (owner instanceof PsiModifierList) { - PsiElement nextElement = PsiTreeUtil.skipSiblingsForward((PsiModifierList) owner, PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class); - if (nextElement instanceof PsiTypeElement) { - PsiTypeElement typeElement = (PsiTypeElement) nextElement; - PsiType type = typeElement.getType(); - if (PsiType.VOID.equals(type)) { - String message = JavaErrorBundle.message("annotation.not.allowed.void"); - return annotationError(annotation, message); - } - if (!(type instanceof PsiPrimitiveType)) { - PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement()); - HighlightInfo info = checkReferenceTarget(annotation, ref); - if (info != null) { - return info; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkValidAnnotationType(PsiType type, PsiTypeElement typeElement) { + if (type != null && type.accept(AnnotationReturnTypeVisitor.INSTANCE)) { + return null; + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMemberInvalidType(type != null ? type.getPresentableText() : "?")); + } + + private static final ElementPattern ANY_ANNOTATION_ALLOWED = psiElement().andOr( + psiElement().withParent(PsiNameValuePair.class), + psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiNameValuePair.class), + psiElement().withParents(PsiArrayInitializerMemberValue.class, PsiAnnotationMethod.class), + psiElement().withParent(PsiAnnotationMethod.class).afterLeaf(PsiKeyword.DEFAULT) + ); + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkApplicability( + PsiAnnotation annotation, + LanguageLevel level, + PsiFile file + ) { + if (ANY_ANNOTATION_ALLOWED.accepts(annotation)) { + return null; + } + + PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); + if (nameRef == null) { + return null; + } + + PsiAnnotationOwner owner = annotation.getOwner(); + PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(owner); + if (owner == null || targets.length == 0) { + return annotationError(annotation, JavaCompilationErrorLocalize.annotationNotAllowedHere()); + } + + if (!(owner instanceof PsiModifierList)) { + HighlightInfo.Builder hlBuilder = HighlightUtil.checkFeature(annotation, JavaFeature.TYPE_ANNOTATIONS, level, file); + if (hlBuilder != null) { + return hlBuilder; } - } } - } else if (owner instanceof PsiTypeElement) { - PsiElement context = PsiTreeUtil.skipParentsOfType((PsiTypeElement) owner, PsiTypeElement.class); - if (context instanceof PsiClassObjectAccessExpression) { - String message = JavaErrorBundle.message("annotation.not.allowed.class"); - return annotationError(annotation, message); + + PsiAnnotation.TargetType applicable = AnnotationTargetUtil.findAnnotationTarget(annotation, targets); + if (applicable == PsiAnnotation.TargetType.UNKNOWN) { + return null; } - } - } - return null; - } + if (applicable == null) { + return annotationError( + annotation, + JavaCompilationErrorLocalize.annotationNotApplicable(nameRef.getText(), targets[0].getPresentableText()) + ); + } - private static HighlightInfo annotationError(PsiAnnotation annotation, String message) { - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, new DeleteAnnotationAction(annotation)); - return info; - } + if (applicable == PsiAnnotation.TargetType.TYPE_USE) { + if (owner instanceof PsiClassReferenceType classRefType) { + PsiJavaCodeReferenceElement ref = classRefType.getReference(); + HighlightInfo.Builder hlBuilder = checkReferenceTarget(annotation, ref); + if (hlBuilder != null) { + return hlBuilder; + } + } + else if (owner instanceof PsiModifierList modifierList) { + PsiElement nextElement = + PsiTreeUtil.skipSiblingsForward(modifierList, PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class); + if (nextElement instanceof PsiTypeElement typeElement) { + PsiType type = typeElement.getType(); + if (PsiType.VOID.equals(type)) { + return annotationError(annotation, JavaCompilationErrorLocalize.annotationNotAllowedVoid()); + } + if (!(type instanceof PsiPrimitiveType)) { + PsiJavaCodeReferenceElement ref = getOutermostReferenceElement(typeElement.getInnermostComponentReferenceElement()); + HighlightInfo.Builder hlBuilder = checkReferenceTarget(annotation, ref); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + } + else if (owner instanceof PsiTypeElement typeElem) { + PsiElement context = PsiTreeUtil.skipParentsOfType(typeElem, PsiTypeElement.class); + if (context instanceof PsiClassObjectAccessExpression) { + return annotationError(annotation, JavaCompilationErrorLocalize.annotationNotAllowedClass()); + } + } + } - @Nullable - private static HighlightInfo checkReferenceTarget(PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) { - if (ref == null) { - return null; - } - PsiElement refTarget = ref.resolve(); - if (refTarget == null) { - return null; + return null; } - String message = null; - if (!(refTarget instanceof PsiClass)) { - message = JavaErrorBundle.message("annotation.not.allowed.ref"); - } else { - PsiElement parent = ref.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement) { - PsiElement qualified = ((PsiJavaCodeReferenceElement) parent).resolve(); - if (qualified instanceof PsiMember && ((PsiMember) qualified).hasModifierProperty(PsiModifier.STATIC)) { - message = JavaErrorBundle.message("annotation.not.allowed.static"); - } - } + @RequiredReadAction + private static HighlightInfo.Builder annotationError(PsiAnnotation annotation, LocalizeValue message) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(annotation) + .descriptionAndTooltip(message) + .registerFix(new DeleteAnnotationAction(annotation)); } - return message != null ? annotationError(annotation, message) : null; - } + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkReferenceTarget(PsiAnnotation annotation, @Nullable PsiJavaCodeReferenceElement ref) { + if (ref == null) { + return null; + } + PsiElement refTarget = ref.resolve(); + if (refTarget == null) { + return null; + } - @Nullable - private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) { - if (ref == null) { - return null; + if (!(refTarget instanceof PsiClass)) { + return annotationError(annotation, JavaCompilationErrorLocalize.annotationNotAllowedRef()); + } + else if (ref.getParent() instanceof PsiJavaCodeReferenceElement javaCodeRef + && javaCodeRef.resolve() instanceof PsiMember member && member.isStatic()) { + return annotationError(annotation, JavaCompilationErrorLocalize.annotationNotAllowedStatic()); + } + return null; } - PsiElement qualifier; - while ((qualifier = ref.getQualifier()) instanceof PsiJavaCodeReferenceElement) { - ref = (PsiJavaCodeReferenceElement) qualifier; - } - return ref; - } - - @Nullable - public static HighlightInfo checkAnnotationType(PsiAnnotation annotation) { - PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement(); - if (nameReferenceElement != null) { - PsiElement resolved = nameReferenceElement.resolve(); - if (!(resolved instanceof PsiClass) || !((PsiClass) resolved).isAnnotationType()) { - String description = JavaErrorBundle.message("annotation.annotation.type.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameReferenceElement).descriptionAndTooltip(description).create(); - } - } - return null; - } - - @Nullable - public static HighlightInfo checkCyclicMemberType(PsiTypeElement typeElement, PsiClass aClass) { - LOG.assertTrue(aClass.isAnnotationType()); - PsiType type = typeElement.getType(); - final Set checked = new HashSet<>(); - if (cyclicDependencies(aClass, type, checked, aClass.getManager())) { - String description = JavaErrorBundle.message("annotation.cyclic.element.type"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } - return null; - } - - private static boolean cyclicDependencies(PsiClass aClass, PsiType type, @Nonnull Set checked, @Nonnull PsiManager manager) { - final PsiClass resolvedClass = PsiUtil.resolveClassInType(type); - if (resolvedClass != null && resolvedClass.isAnnotationType()) { - if (aClass == resolvedClass) { - return true; - } - if (!checked.add(resolvedClass) || !manager.isInProject(resolvedClass)) { - return false; - } - final PsiMethod[] methods = resolvedClass.getMethods(); - for (PsiMethod method : methods) { - if (cyclicDependencies(aClass, method.getReturnType(), checked, manager)) { - return true; + @Nullable + private static PsiJavaCodeReferenceElement getOutermostReferenceElement(@Nullable PsiJavaCodeReferenceElement ref) { + if (ref == null) { + return null; } - } - } - return false; - } - - public static HighlightInfo checkClashesWithSuperMethods(@Nonnull PsiAnnotationMethod psiMethod) { - final PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); - if (nameIdentifier != null) { - final PsiMethod[] methods = psiMethod.findDeepestSuperMethods(); - for (PsiMethod method : methods) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - final String qualifiedName = containingClass.getQualifiedName(); - if (JavaClassNames.JAVA_LANG_OBJECT.equals(qualifiedName) || JavaClassNames.JAVA_LANG_ANNOTATION_ANNOTATION.equals(qualifiedName)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(nameIdentifier).descriptionAndTooltip("@interface member clashes with '" + JavaHighlightUtil.formatMethod - (method) + "' in " + HighlightUtil.formatClass(containingClass)).create(); - } - } - } - } - return null; - } - - @Nullable - public static HighlightInfo checkAnnotationDeclaration(final PsiElement parent, final PsiReferenceList list) { - if (PsiUtil.isAnnotationMethod(parent)) { - PsiAnnotationMethod method = (PsiAnnotationMethod) parent; - if (list == method.getThrowsList()) { - String description = JavaErrorBundle.message("annotation.members.may.not.have.throws.list"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); - } - } else if (parent instanceof PsiClass && ((PsiClass) parent).isAnnotationType()) { - if (PsiKeyword.EXTENDS.equals(list.getFirstChild().getText())) { - String description = JavaErrorBundle.message("annotation.may.not.have.extends.list"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); - } - } - return null; - } - - @Nullable - public static HighlightInfo checkPackageAnnotationContainingFile(PsiPackageStatement statement, PsiFile file) { - PsiModifierList annotationList = statement.getAnnotationList(); - if (annotationList != null && !PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { - String message = JavaErrorBundle.message("invalid.package.annotation.containing.file"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotationList.getTextRange()).descriptionAndTooltip(message).create(); - } - return null; - } - - @Nullable - public static HighlightInfo checkTargetAnnotationDuplicates(PsiAnnotation annotation) { - PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); - if (nameRef == null) { - return null; - } - PsiElement resolved = nameRef.resolve(); - if (!(resolved instanceof PsiClass) || !JavaClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(((PsiClass) resolved).getQualifiedName())) { - return null; + while (ref.getQualifier() instanceof PsiJavaCodeReferenceElement javaCodeRef) { + ref = javaCodeRef; + } + return ref; } - PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); - if (attributes.length < 1) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAnnotationType(PsiAnnotation annotation) { + PsiJavaCodeReferenceElement nameRefElem = annotation.getNameReferenceElement(); + if (nameRefElem != null && (!(nameRefElem.resolve() instanceof PsiClass annotationClass) || !annotationClass.isAnnotationType())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(nameRefElem) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationTypeExpected()); + } + return null; } - PsiAnnotationMemberValue value = attributes[0].getValue(); - if (!(value instanceof PsiArrayInitializerMemberValue)) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCyclicMemberType(PsiTypeElement typeElement, PsiClass aClass) { + LOG.assertTrue(aClass.isAnnotationType()); + PsiType type = typeElement.getType(); + Set checked = new HashSet<>(); + if (cyclicDependencies(aClass, type, checked, aClass.getManager())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationCyclicElementType()); + } + return null; } - PsiAnnotationMemberValue[] arrayInitializers = ((PsiArrayInitializerMemberValue) value).getInitializers(); - Set targets = new HashSet<>(); - for (PsiAnnotationMemberValue initializer : arrayInitializers) { - if (initializer instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) initializer).resolve(); - if (target != null) { - if (targets.contains(target)) { - String description = JavaErrorBundle.message("repeated.annotation.target"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); - } - targets.add(target); - } - } + + private static boolean cyclicDependencies(PsiClass aClass, PsiType type, Set checked, PsiManager manager) { + PsiClass resolvedClass = PsiUtil.resolveClassInType(type); + if (resolvedClass != null && resolvedClass.isAnnotationType()) { + if (aClass == resolvedClass) { + return true; + } + if (!checked.add(resolvedClass) || !manager.isInProject(resolvedClass)) { + return false; + } + PsiMethod[] methods = resolvedClass.getMethods(); + for (PsiMethod method : methods) { + if (cyclicDependencies(aClass, method.getReturnType(), checked, manager)) { + return true; + } + } + } + return false; } - return null; - } - - @Nullable - public static HighlightInfo checkFunctionalInterface(@Nonnull PsiAnnotation annotation, @Nonnull LanguageLevel languageLevel) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && Comparing.strEqual(annotation.getQualifiedName(), JavaClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE)) { - final PsiAnnotationOwner owner = annotation.getOwner(); - if (owner instanceof PsiModifierList) { - final PsiElement parent = ((PsiModifierList) owner).getParent(); - if (parent instanceof PsiClass) { - final String errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional((PsiClass) parent, ((PsiClass) parent).getName() + " is not a functional interface"); - if (errorMessage != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation).descriptionAndTooltip(errorMessage).create(); - } - } - } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClashesWithSuperMethods(PsiAnnotationMethod psiMethod) { + PsiIdentifier nameIdentifier = psiMethod.getNameIdentifier(); + if (nameIdentifier != null) { + PsiMethod[] methods = psiMethod.findDeepestSuperMethods(); + for (PsiMethod method : methods) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null) { + String qualifiedName = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName) + || CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION.equals(qualifiedName)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(nameIdentifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMemberClash( + JavaHighlightUtil.formatMethod(method), + HighlightUtil.formatClass(containingClass) + )); + } + } + } + } + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkRepeatableAnnotation(PsiAnnotation annotation) { - String qualifiedName = annotation.getQualifiedName(); - if (!JavaClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) { - return null; + + @Nullable + @RequiredReadAction + public static HighlightInfo checkAnnotationDeclaration(PsiElement parent, PsiReferenceList list) { + if (PsiUtil.isAnnotationMethod(parent)) { + PsiAnnotationMethod method = (PsiAnnotationMethod) parent; + if (list == method.getThrowsList()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMemberMayNotHaveThrowsList()) + .create(); + } + } + else if (parent instanceof PsiClass annotationClass && annotationClass.isAnnotationType()) { + if (PsiKeyword.EXTENDS.equals(list.getFirstChild().getText())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMayNotHaveExtendsList()) + .create(); + } + } + return null; } - String description = doCheckRepeatableAnnotation(annotation); - if (description != null) { - PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null); - if (containerRef != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(containerRef).descriptionAndTooltip(description).create(); - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkPackageAnnotationContainingFile(PsiPackageStatement statement, PsiFile file) { + PsiModifierList annotationList = statement.getAnnotationList(); + if (annotationList != null && !PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(annotationList.getTextRange()) + .descriptionAndTooltip(JavaErrorLocalize.invalidPackageAnnotationContainingFile()) + .create(); + } + return null; } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkTargetAnnotationDuplicates(PsiAnnotation annotation) { + PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement(); + if (nameRef == null) { + return null; + } - @Nullable - private static String doCheckRepeatableAnnotation(@Nonnull PsiAnnotation annotation) { - PsiAnnotationOwner owner = annotation.getOwner(); - if (!(owner instanceof PsiModifierList)) { - return null; - } - PsiElement target = ((PsiModifierList) owner).getParent(); - if (!(target instanceof PsiClass) || !((PsiClass) target).isAnnotationType()) { - return null; - } - PsiClass container = getRepeatableContainer(annotation); - if (container == null) { - return null; - } + if (!(nameRef.resolve() instanceof PsiClass annotationClass) + || !CommonClassNames.JAVA_LANG_ANNOTATION_TARGET.equals(annotationClass.getQualifiedName())) { + return null; + } - PsiMethod[] methods = container.findMethodsByName("value", false); - if (methods.length == 0) { - return JavaErrorBundle.message("annotation.container.no.value", container.getQualifiedName()); + PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); + if (attributes.length < 1) { + return null; + } + if (!(attributes[0].getValue() instanceof PsiArrayInitializerMemberValue arrayInitializerMemberValue)) { + return null; + } + PsiAnnotationMemberValue[] arrayInitializers = arrayInitializerMemberValue.getInitializers(); + Set targets = new HashSet<>(); + for (PsiAnnotationMemberValue initializer : arrayInitializers) { + if (initializer instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiElement target) { + if (targets.contains(target)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(initializer) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationRepeatedTarget()); + } + targets.add(target); + } + } + return null; } - if (methods.length == 1) { - PsiType expected = new PsiImmediateClassType((PsiClass) target, PsiSubstitutor.EMPTY).createArrayType(); - if (!expected.equals(methods[0].getReturnType())) { - return JavaErrorBundle.message("annotation.container.bad.type", container.getQualifiedName(), JavaHighlightUtil.formatType(expected)); - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkFunctionalInterface(PsiAnnotation annotation, LanguageLevel languageLevel) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) + && CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE.equals(annotation.getQualifiedName()) + && annotation.getOwner() instanceof PsiModifierList modifierList + && modifierList.getParent() instanceof PsiClass psiClass) { + LocalizeValue errorMessage = LambdaHighlightingUtil.checkInterfaceFunctional( + psiClass, + JavaCompilationErrorLocalize.lambdaNotAFunctionalInterface(psiClass.getName()) + ); + if (errorMessage.isNotEmpty()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(annotation) + .descriptionAndTooltip(errorMessage); + } + } + return null; } - RetentionPolicy targetPolicy = getRetentionPolicy((PsiClass) target); - if (targetPolicy != null) { - RetentionPolicy containerPolicy = getRetentionPolicy(container); - if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) { - return JavaErrorBundle.message("annotation.container.low.retention", container.getQualifiedName(), containerPolicy); - } - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkRepeatableAnnotation(PsiAnnotation annotation) { + String qualifiedName = annotation.getQualifiedName(); + if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) { + return null; + } + + LocalizeValue description = doCheckRepeatableAnnotation(annotation); + if (description.isNotEmpty()) { + PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null); + if (containerRef != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(containerRef) + .descriptionAndTooltip(description); + } + } - Set repeatableTargets = AnnotationTargetUtil.getAnnotationTargets((PsiClass) target); - if (repeatableTargets != null) { - Set containerTargets = AnnotationTargetUtil.getAnnotationTargets(container); - if (containerTargets != null && !repeatableTargets.containsAll(containerTargets)) { - return JavaErrorBundle.message("annotation.container.wide.target", container.getQualifiedName()); - } + return null; } - return null; - } + @RequiredReadAction + private static LocalizeValue doCheckRepeatableAnnotation(PsiAnnotation annotation) { + if (!(annotation.getOwner() instanceof PsiModifierList modifierList)) { + return LocalizeValue.empty(); + } + if (!(modifierList.getParent() instanceof PsiClass targetClass) || !targetClass.isAnnotationType()) { + return LocalizeValue.empty(); + } + PsiClass container = getRepeatableContainer(annotation); + if (container == null) { + return LocalizeValue.empty(); + } - @Nullable - private static PsiClass getRepeatableContainer(@Nonnull PsiAnnotation annotation) { - PsiAnnotationMemberValue containerRef = PsiImplUtil.findAttributeValue(annotation, null); - if (!(containerRef instanceof PsiClassObjectAccessExpression)) { - return null; - } - PsiType containerType = ((PsiClassObjectAccessExpression) containerRef).getOperand().getType(); - if (!(containerType instanceof PsiClassType)) { - return null; - } - PsiClass container = ((PsiClassType) containerType).resolve(); - if (container == null || !container.isAnnotationType()) { - return null; - } - return container; - } - - @Nullable - public static HighlightInfo checkReceiverPlacement(PsiReceiverParameter parameter) { - PsiElement owner = parameter.getParent().getParent(); - if (owner == null) { - return null; - } + PsiMethod[] methods = container.findMethodsByName("value", false); + if (methods.length == 0) { + return JavaCompilationErrorLocalize.annotationContainerNoValue(container.getQualifiedName()); + } - if (!(owner instanceof PsiMethod)) { - String text = JavaErrorBundle.message("receiver.wrong.context"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create(); - } + if (methods.length == 1) { + PsiType expected = new PsiImmediateClassType(targetClass, PsiSubstitutor.EMPTY).createArrayType(); + if (!expected.equals(methods[0].getReturnType())) { + return JavaCompilationErrorLocalize.annotationContainerBadType(container.getQualifiedName(), JavaHighlightUtil.formatType(expected)); + } + } + + RetentionPolicy targetPolicy = getRetentionPolicy(targetClass); + if (targetPolicy != null) { + RetentionPolicy containerPolicy = getRetentionPolicy(container); + if (containerPolicy != null && targetPolicy.compareTo(containerPolicy) > 0) { + return JavaCompilationErrorLocalize.annotationContainerLowRetention(container.getQualifiedName(), containerPolicy); + } + } - PsiMethod method = (PsiMethod) owner; - if (isStatic(method) || method.isConstructor() && isStatic(method.getContainingClass())) { - String text = JavaErrorBundle.message("receiver.static.context"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create(); + Set repeatableTargets = AnnotationTargetUtil.getAnnotationTargets(targetClass); + if (repeatableTargets != null) { + Set containerTargets = AnnotationTargetUtil.getAnnotationTargets(container); + if (containerTargets != null && !repeatableTargets.containsAll(containerTargets)) { + return JavaCompilationErrorLocalize.annotationContainerWideTarget(container.getQualifiedName()); + } + } + + return LocalizeValue.empty(); } - PsiElement leftNeighbour = PsiTreeUtil.skipSiblingsBackward(parameter, PsiWhiteSpace.class); - if (leftNeighbour != null && !PsiUtil.isJavaToken(leftNeighbour, JavaTokenType.LPARENTH)) { - String text = JavaErrorBundle.message("receiver.wrong.position"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter.getIdentifier()).descriptionAndTooltip(text).create(); + @Nullable + @RequiredReadAction + private static PsiClass getRepeatableContainer(PsiAnnotation annotation) { + if (!(PsiImplUtil.findAttributeValue(annotation, null) instanceof PsiClassObjectAccessExpression containerRef)) { + return null; + } + if (!(containerRef.getOperand().getType() instanceof PsiClassType containerType)) { + return null; + } + PsiClass container = containerType.resolve(); + return container != null && container.isAnnotationType() ? container : null; } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkReceiverPlacement(PsiReceiverParameter parameter) { + PsiElement owner = parameter.getParent().getParent(); + if (owner == null) { + return null; + } - @Nullable - public static HighlightInfo checkReceiverType(PsiReceiverParameter parameter) { - PsiElement owner = parameter.getParent().getParent(); - if (!(owner instanceof PsiMethod)) { - return null; - } + if (!(owner instanceof PsiMethod method)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameter.getIdentifier()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.receiverWrongContext()) + .create(); + } - PsiMethod method = (PsiMethod) owner; - PsiClass enclosingClass = method.getContainingClass(); - if (method.isConstructor() && enclosingClass != null) { - enclosingClass = enclosingClass.getContainingClass(); - } + if (isStatic(method) || method.isConstructor() && isStatic(method.getContainingClass())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameter.getIdentifier()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.receiverStaticContext()) + .create(); + } - if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(parameter.getType()))) { - PsiElement range = ObjectUtil.notNull(parameter.getTypeElement(), parameter); - String text = JavaErrorBundle.message("receiver.type.mismatch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(text).create(); - } + PsiElement leftNeighbour = PsiTreeUtil.skipSiblingsBackward(parameter, PsiWhiteSpace.class); + if (leftNeighbour != null && !PsiUtil.isJavaToken(leftNeighbour, JavaTokenType.LPARENTH)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameter.getIdentifier()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.receiverWrongPosition()) + .create(); + } - PsiThisExpression identifier = parameter.getIdentifier(); - if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(identifier.getType()))) { - String text = JavaErrorBundle.message("receiver.name.mismatch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); + return null; } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkReceiverType(PsiReceiverParameter parameter) { + PsiElement owner = parameter.getParent().getParent(); + if (!(owner instanceof PsiMethod method)) { + return null; + } - private static boolean isStatic(PsiModifierListOwner owner) { - if (owner == null) { - return false; - } - if (owner instanceof PsiClass && ClassUtil.isTopLevelClass((PsiClass) owner)) { - return true; - } - PsiModifierList modifierList = owner.getModifierList(); - return modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC); - } - - @Nullable - public static RetentionPolicy getRetentionPolicy(@Nonnull PsiClass annotation) { - PsiModifierList modifierList = annotation.getModifierList(); - if (modifierList != null) { - PsiAnnotation retentionAnno = modifierList.findAnnotation(JavaClassNames.JAVA_LANG_ANNOTATION_RETENTION); - if (retentionAnno == null) { - return RetentionPolicy.CLASS; - } - - PsiAnnotationMemberValue policyRef = PsiImplUtil.findAttributeValue(retentionAnno, null); - if (policyRef instanceof PsiReference) { - PsiElement field = ((PsiReference) policyRef).resolve(); - if (field instanceof PsiEnumConstant) { - String name = ((PsiEnumConstant) field).getName(); - try { - //noinspection ConstantConditions - return Enum.valueOf(RetentionPolicy.class, name); - } catch (Exception e) { - LOG.warn("Unknown policy: " + name); - } - } - } - } + PsiClass enclosingClass = method.getContainingClass(); + if (method.isConstructor() && enclosingClass != null) { + enclosingClass = enclosingClass.getContainingClass(); + } - return null; - } + if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(parameter.getType()))) { + PsiElement range = ObjectUtil.notNull(parameter.getTypeElement(), parameter); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(JavaCompilationErrorLocalize.receiverTypeMismatch()) + .create(); + } - public static class AnnotationReturnTypeVisitor extends PsiTypeVisitor { - public static final AnnotationReturnTypeVisitor INSTANCE = new AnnotationReturnTypeVisitor(); + PsiThisExpression identifier = parameter.getIdentifier(); + if (enclosingClass != null && !enclosingClass.equals(PsiUtil.resolveClassInType(identifier.getType()))) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.receiverNameMismatch()) + .create(); + } - @Override - public Boolean visitType(PsiType type) { - return Boolean.FALSE; + return null; } - @Override - public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { - return PsiType.VOID.equals(primitiveType) || PsiType.NULL.equals(primitiveType) ? Boolean.FALSE : Boolean.TRUE; + @RequiredReadAction + @SuppressWarnings("SimplifiableIfStatement") + private static boolean isStatic(PsiModifierListOwner owner) { + if (owner == null) { + return false; + } + if (owner instanceof PsiClass psiClass && ClassUtil.isTopLevelClass(psiClass)) { + return true; + } + return owner.hasModifierProperty(PsiModifier.STATIC); } - @Override - public Boolean visitArrayType(PsiArrayType arrayType) { - if (arrayType.getArrayDimensions() != 1) { - return Boolean.FALSE; - } - PsiType componentType = arrayType.getComponentType(); - return componentType.accept(this); + @Nullable + @RequiredReadAction + public static RetentionPolicy getRetentionPolicy(PsiClass annotation) { + PsiModifierList modifierList = annotation.getModifierList(); + if (modifierList != null) { + PsiAnnotation retentionAnno = modifierList.findAnnotation(CommonClassNames.JAVA_LANG_ANNOTATION_RETENTION); + if (retentionAnno == null) { + return RetentionPolicy.CLASS; + } + + if (PsiImplUtil.findAttributeValue(retentionAnno, null) instanceof PsiReference policyRef + && policyRef.resolve() instanceof PsiEnumConstant enumConst) { + String name = enumConst.getName(); + try { + //noinspection ConstantConditions + return Enum.valueOf(RetentionPolicy.class, name); + } + catch (Exception e) { + LOG.warn("Unknown policy: " + name); + } + } + } + + return null; } - @Override - public Boolean visitClassType(PsiClassType classType) { - if (classType.getParameters().length > 0) { - PsiClassType rawType = classType.rawType(); - return rawType.equalsToText(JavaClassNames.JAVA_LANG_CLASS); - } + public static class AnnotationReturnTypeVisitor extends PsiTypeVisitor { + public static final AnnotationReturnTypeVisitor INSTANCE = new AnnotationReturnTypeVisitor(); - PsiClass aClass = classType.resolve(); - if (aClass != null && (aClass.isAnnotationType() || aClass.isEnum())) { - return Boolean.TRUE; - } + @Override + public Boolean visitType(PsiType type) { + return Boolean.FALSE; + } - return classType.equalsToText(JavaClassNames.JAVA_LANG_CLASS) || classType.equalsToText(JavaClassNames.JAVA_LANG_STRING); - } - } + @Override + public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { + return PsiType.VOID.equals(primitiveType) || PsiType.NULL.equals(primitiveType) ? Boolean.FALSE : Boolean.TRUE; + } + + @Override + public Boolean visitArrayType(PsiArrayType arrayType) { + if (arrayType.getArrayDimensions() != 1) { + return Boolean.FALSE; + } + PsiType componentType = arrayType.getComponentType(); + return componentType.accept(this); + } - private static class DeleteAnnotationAction implements SyntheticIntentionAction { - private final PsiAnnotation myAnnotation; + @Override + public Boolean visitClassType(PsiClassType classType) { + if (classType.getParameters().length > 0) { + PsiClassType rawType = classType.rawType(); + return rawType.equalsToText(CommonClassNames.JAVA_LANG_CLASS); + } - private DeleteAnnotationAction(PsiAnnotation annotation) { - myAnnotation = annotation; - } + PsiClass aClass = classType.resolve(); + if (aClass != null && (aClass.isAnnotationType() || aClass.isEnum())) { + return Boolean.TRUE; + } - @Nonnull - @Override - public String getText() { - return "Remove"; + return classType.equalsToText(CommonClassNames.JAVA_LANG_CLASS) || classType.equalsToText(CommonClassNames.JAVA_LANG_STRING); + } } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return true; - } + private static class DeleteAnnotationAction implements SyntheticIntentionAction { + private final PsiAnnotation myAnnotation; - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - myAnnotation.delete(); - } + private DeleteAnnotationAction(PsiAnnotation annotation) { + myAnnotation = annotation; + } - @Override - public boolean startInWriteAction() { - return true; + @Override + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Remove"); + } + + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return true; + } + + @Override + @RequiredWriteAction + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + myAnnotation.delete(); + } + + @Override + public boolean startInWriteAction() { + return true; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java index 72ef566174..49c2c0a59e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java @@ -28,11 +28,13 @@ import com.intellij.java.language.psi.search.PsiShortNamesCache; import com.intellij.java.language.psi.search.searches.SuperMethodsSearch; import com.intellij.java.language.psi.util.*; +import consulo.annotation.access.RequiredReadAction; import consulo.application.dumb.IndexNotReadyException; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.content.FileIndexFacade; -import consulo.language.editor.intention.QuickFixAction; +import consulo.language.editor.intention.IntentionAction; import consulo.language.editor.intention.QuickFixActionRegistrar; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoHolder; @@ -42,6 +44,7 @@ import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.PsiMatcherImpl; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.DumbService; import consulo.project.Project; @@ -49,1513 +52,1792 @@ import consulo.util.lang.Comparing; import consulo.util.lang.Pair; import consulo.virtualFileSystem.VirtualFile; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; /** * @author cdr */ public class GenericsHighlightUtil { - private static final Logger LOG = Logger.getInstance(GenericsHighlightUtil.class); + private static final Logger LOG = Logger.getInstance(GenericsHighlightUtil.class); + + private GenericsHighlightUtil() { + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkInferredTypeArguments( + PsiTypeParameterListOwner listOwner, + PsiElement call, + PsiSubstitutor substitutor + ) { + return checkInferredTypeArguments(listOwner.getTypeParameters(), call, substitutor); + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkInferredTypeArguments( + PsiTypeParameter[] typeParameters, + PsiElement call, + PsiSubstitutor substitutor + ) { + Pair inferredTypeArgument = + GenericsUtil.findTypeParameterWithBoundError(typeParameters, substitutor, call, false); + if (inferredTypeArgument != null) { + PsiType extendsType = inferredTypeArgument.second; + PsiTypeParameter typeParameter = inferredTypeArgument.first; + PsiClass boundClass = extendsType instanceof PsiClassType extendsClassType ? extendsClassType.resolve() : null; + + String c = HighlightUtil.formatClass(typeParameter); + String t1 = JavaHighlightUtil.formatType(extendsType); + String t2 = JavaHighlightUtil.formatType(substitutor.substitute(typeParameter)); + LocalizeValue description = boundClass == null || typeParameter.isInterface() == boundClass.isInterface() + ? JavaCompilationErrorLocalize.typeParameterInferredTypeNotWithinExtendBound(c, t1, t2) + : JavaCompilationErrorLocalize.typeParameterInferredTypeNotWithinImplementBound(c, t1, t2); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(call) + .descriptionAndTooltip(description); + } - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); + return null; + } - private GenericsHighlightUtil() { - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkParameterizedReferenceTypeArguments( + PsiElement resolved, + PsiJavaCodeReferenceElement referenceElement, + PsiSubstitutor substitutor, + JavaSdkVersion javaSdkVersion + ) { + if (!(resolved instanceof PsiTypeParameterListOwner typeParameterListOwner)) { + return null; + } + return checkReferenceTypeArgumentList( + typeParameterListOwner, + referenceElement.getParameterList(), + substitutor, + true, + javaSdkVersion + ); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkReferenceTypeArgumentList( + PsiTypeParameterListOwner typeParameterListOwner, + PsiReferenceParameterList referenceParameterList, + PsiSubstitutor substitutor, + boolean registerIntentions, + JavaSdkVersion javaSdkVersion + ) { + PsiDiamondType.DiamondInferenceResult inferenceResult = null; + PsiTypeElement[] referenceElements = null; + if (referenceParameterList != null) { + referenceElements = referenceParameterList.getTypeParameterElements(); + if (referenceElements.length == 1 && referenceElements[0].getType() instanceof PsiDiamondType diamondType) { + if (!typeParameterListOwner.hasTypeParameters()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(referenceParameterList) + .descriptionAndTooltip(JavaCompilationErrorLocalize.newExpressionDiamondNotApplicable()); + } + inferenceResult = diamondType.resolveInferredTypes(); + String errorMessage = inferenceResult.getErrorMessage(); + if (errorMessage != null + && !(inferenceResult.failedToInfer() + && detectExpectedType(referenceParameterList) instanceof PsiClassType expectedClassType + && expectedClassType.isRaw())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(referenceParameterList) + .descriptionAndTooltip(errorMessage); + } + } + } - @Nullable - public static HighlightInfo checkInferredTypeArguments(PsiTypeParameterListOwner listOwner, PsiElement call, PsiSubstitutor substitutor) { - return checkInferredTypeArguments(listOwner.getTypeParameters(), call, substitutor); - } + PsiTypeParameter[] typeParameters = typeParameterListOwner.getTypeParameters(); + int targetParametersNum = typeParameters.length; + int refParametersNum = referenceParameterList == null ? 0 : referenceParameterList.getTypeArguments().length; + if (targetParametersNum != refParametersNum && refParametersNum != 0) { + LocalizeValue description; + if (targetParametersNum == 0) { + if (PsiTreeUtil.getParentOfType(referenceParameterList, PsiCall.class) != null + && typeParameterListOwner instanceof PsiMethod method + && (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7) || hasSuperMethodsWithTypeParams(method))) { + description = LocalizeValue.empty(); + } + else { + description = JavaErrorLocalize.genericsTypeOrMethodDoesNotHaveTypeParameters( + typeParameterListOwnerCategoryDescription(typeParameterListOwner), + typeParameterListOwnerDescription(typeParameterListOwner) + ); + } + } + else { + description = JavaCompilationErrorLocalize.typeParameterCountMismatch(refParametersNum, targetParametersNum); + } - @Nullable - private static HighlightInfo checkInferredTypeArguments(PsiTypeParameter[] typeParameters, PsiElement call, PsiSubstitutor substitutor) { - final Pair inferredTypeArgument = GenericsUtil.findTypeParameterWithBoundError(typeParameters, substitutor, call, false); - if (inferredTypeArgument != null) { - final PsiType extendsType = inferredTypeArgument.second; - final PsiTypeParameter typeParameter = inferredTypeArgument.first; - PsiClass boundClass = extendsType instanceof PsiClassType ? ((PsiClassType) extendsType).resolve() : null; + if (description.isNotEmpty()) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(referenceParameterList) + .descriptionAndTooltip(description); + if (registerIntentions) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (typeParameterListOwner instanceof PsiClass psiClass) { + hlBuilder.registerFix(factory.createChangeClassSignatureFromUsageFix(psiClass, referenceParameterList)); + } + + if (referenceParameterList.getParent().getParent() instanceof PsiTypeElement typeElement + && typeElement.getParent() instanceof PsiVariable variable) { + if (targetParametersNum == 0) { + hlBuilder.registerFix(factory.createRemoveTypeArgumentsFix(variable)); + } + registerVariableParameterizedTypeFixes( + hlBuilder, + variable, + referenceParameterList, + javaSdkVersion + ); + } + } + return hlBuilder; + } + } + + // bounds check + if (targetParametersNum > 0 && refParametersNum != 0) { + if (inferenceResult != null) { + PsiType[] types = inferenceResult.getTypes(); + for (int i = 0; i < typeParameters.length; i++) { + PsiType type = types[i]; + HighlightInfo.Builder hlBuilder = checkTypeParameterWithinItsBound( + typeParameters[i], + substitutor, + type, + referenceElements[0], + referenceParameterList + ); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + else { + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeElement typeElement = referenceElements[i]; + HighlightInfo.Builder hlBuilder = checkTypeParameterWithinItsBound( + typeParameters[i], + substitutor, + typeElement.getType(), + typeElement, + referenceParameterList + ); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + } - @NonNls String messageKey = boundClass == null || typeParameter.isInterface() == boundClass.isInterface() ? "generics.inferred.type.for.type.parameter.is.not.within.its.bound.extend" : - "generics.inferred.type.for.type.parameter.is.not.within.its.bound.implement"; + return null; + } - String description = JavaErrorBundle.message(messageKey, HighlightUtil.formatClass(typeParameter), JavaHighlightUtil.formatType(extendsType), JavaHighlightUtil.formatType(substitutor - .substitute(typeParameter))); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(call).descriptionAndTooltip(description).create(); + private static boolean hasSuperMethodsWithTypeParams(PsiMethod method) { + for (PsiMethod superMethod : method.findDeepestSuperMethods()) { + if (superMethod.hasTypeParameters()) { + return true; + } + } + return false; } - return null; - } + private static PsiType detectExpectedType(PsiReferenceParameterList referenceParameterList) { + PsiNewExpression newExpr = PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class); + LOG.assertTrue(newExpr != null); + PsiElement parent = newExpr.getParent(); + PsiType expectedType = null; + if (parent instanceof PsiVariable variable && newExpr.equals(variable.getInitializer())) { + expectedType = variable.getType(); + } + else if (parent instanceof PsiAssignmentExpression assignment && newExpr.equals(assignment.getRExpression())) { + expectedType = assignment.getLExpression().getType(); + } + else if (parent instanceof PsiReturnStatement) { + if (PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class) instanceof PsiMethod method) { + expectedType = method.getReturnType(); + } + } + else if (parent instanceof PsiExpressionList) { + if (parent.getParent() instanceof PsiCallExpression callExpr && parent.equals(callExpr.getArgumentList())) { + PsiMethod method = callExpr.resolveMethod(); + if (method != null) { + PsiExpression[] expressions = callExpr.getArgumentList().getExpressions(); + int idx = ArrayUtil.find(expressions, newExpr); + if (idx > -1) { + PsiParameterList parameterList = method.getParameterList(); + if (idx < parameterList.getParametersCount()) { + expectedType = parameterList.getParameters()[idx].getType(); + } + } + } + } + } + return expectedType; + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkTypeParameterWithinItsBound( + PsiTypeParameter classParameter, + PsiSubstitutor substitutor, + PsiType type, + PsiElement typeElement2Highlight, + PsiReferenceParameterList referenceParameterList + ) { + PsiClass referenceClass = type instanceof PsiClassType classType ? classType.resolve() : null; + PsiType psiType = substitutor.substitute(classParameter); + if (psiType instanceof PsiClassType && !(PsiUtil.resolveClassInType(psiType) instanceof PsiTypeParameter) + && GenericsUtil.checkNotInBounds(type, psiType, referenceParameterList)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement2Highlight) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterActualInferredMismatch()); + } - @Nullable - public static HighlightInfo checkParameterizedReferenceTypeArguments(final PsiElement resolved, - final PsiJavaCodeReferenceElement referenceElement, - final PsiSubstitutor substitutor, - @Nonnull JavaSdkVersion javaSdkVersion) { - if (!(resolved instanceof PsiTypeParameterListOwner)) { - return null; + PsiClassType[] bounds = classParameter.getSuperTypes(); + for (PsiClassType type1 : bounds) { + PsiType bound = substitutor.substitute(type1); + if (!bound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + && GenericsUtil.checkNotInBounds(type, bound, referenceParameterList)) { + PsiClass boundClass = bound instanceof PsiClassType boundClassType ? boundClassType.resolve() : null; + + String c = referenceClass != null ? HighlightUtil.formatClass(referenceClass) : type.getPresentableText(); + String t = JavaHighlightUtil.formatType(bound); + LocalizeValue description = boundClass == null || referenceClass == null + || referenceClass.isInterface() == boundClass.isInterface() + ? JavaCompilationErrorLocalize.typeParameterTypeNotWithinExtendBound(c, t) + : JavaCompilationErrorLocalize.typeParameterTypeNotWithinImplementBound(c, t); + + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement2Highlight) + .descriptionAndTooltip(description); + if (bound instanceof PsiClassType boundClassType && referenceClass != null) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createExtendsListFix(referenceClass, boundClassType, true)); + } + return hlBuilder; + } + } + return null; } - final PsiTypeParameterListOwner typeParameterListOwner = (PsiTypeParameterListOwner) resolved; - return checkReferenceTypeArgumentList(typeParameterListOwner, referenceElement.getParameterList(), substitutor, true, javaSdkVersion); - } - - @Nullable - public static HighlightInfo checkReferenceTypeArgumentList(final PsiTypeParameterListOwner typeParameterListOwner, - final PsiReferenceParameterList referenceParameterList, - final PsiSubstitutor substitutor, - boolean registerIntentions, - @Nonnull JavaSdkVersion javaSdkVersion) { - PsiDiamondType.DiamondInferenceResult inferenceResult = null; - PsiTypeElement[] referenceElements = null; - if (referenceParameterList != null) { - referenceElements = referenceParameterList.getTypeParameterElements(); - if (referenceElements.length == 1 && referenceElements[0].getType() instanceof PsiDiamondType) { - if (!typeParameterListOwner.hasTypeParameters()) { - final String description = JavaErrorBundle.message("generics.diamond.not.applicable"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(referenceParameterList).descriptionAndTooltip(description).create(); - } - inferenceResult = ((PsiDiamondType) referenceElements[0].getType()).resolveInferredTypes(); - final String errorMessage = inferenceResult.getErrorMessage(); - if (errorMessage != null) { - final PsiType expectedType = detectExpectedType(referenceParameterList); - if (!(inferenceResult.failedToInfer() && expectedType instanceof PsiClassType && ((PsiClassType) expectedType).isRaw())) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(referenceParameterList).descriptionAndTooltip(errorMessage).create(); - } - } - } + + private static String typeParameterListOwnerDescription(PsiTypeParameterListOwner typeParameterListOwner) { + return switch (typeParameterListOwner) { + case PsiClass psiClass -> HighlightUtil.formatClass(psiClass); + case PsiMethod method -> JavaHighlightUtil.formatMethod(method); + default -> { + LOG.error("Unknown " + typeParameterListOwner); + yield "?"; + } + }; } - final PsiTypeParameter[] typeParameters = typeParameterListOwner.getTypeParameters(); - final int targetParametersNum = typeParameters.length; - final int refParametersNum = referenceParameterList == null ? 0 : referenceParameterList.getTypeArguments().length; - if (targetParametersNum != refParametersNum && refParametersNum != 0) { - final String description; - if (targetParametersNum == 0) { - if (PsiTreeUtil.getParentOfType(referenceParameterList, PsiCall.class) != null && - typeParameterListOwner instanceof PsiMethod && - (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7) || hasSuperMethodsWithTypeParams((PsiMethod) typeParameterListOwner))) { - description = null; - } else { - description = JavaErrorBundle.message("generics.type.or.method.does.not.have.type.parameters", typeParameterListOwnerCategoryDescription(typeParameterListOwner), - typeParameterListOwnerDescription(typeParameterListOwner)); - } - } else { - description = JavaErrorBundle.message("generics.wrong.number.of.type.arguments", refParametersNum, targetParametersNum); - } - - if (description != null) { - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(referenceParameterList).descriptionAndTooltip(description).create(); - if (registerIntentions) { - if (typeParameterListOwner instanceof PsiClass) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createChangeClassSignatureFromUsageFix((PsiClass) typeParameterListOwner, referenceParameterList)); - } - - PsiElement grandParent = referenceParameterList.getParent().getParent(); - if (grandParent instanceof PsiTypeElement) { - PsiElement variable = grandParent.getParent(); - if (variable instanceof PsiVariable) { - if (targetParametersNum == 0) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createRemoveTypeArgumentsFix(variable)); - } - registerVariableParameterizedTypeFixes(highlightInfo, (PsiVariable) variable, referenceParameterList, javaSdkVersion); - } - } - } - return highlightInfo; - } + private static LocalizeValue typeParameterListOwnerCategoryDescription(PsiTypeParameterListOwner typeParameterListOwner) { + return switch (typeParameterListOwner) { + case PsiClass psiClass -> JavaErrorLocalize.genericsHolderType(); + case PsiMethod method -> JavaErrorLocalize.genericsHolderMethod(); + default -> { + LOG.error("Unknown " + typeParameterListOwner); + yield LocalizeValue.of("?"); + } + }; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkElementInTypeParameterExtendsList( + PsiReferenceList referenceList, + PsiClass aClass, + JavaResolveResult resolveResult, + PsiElement element + ) { + PsiJavaCodeReferenceElement[] referenceElements = referenceList.getReferenceElements(); + PsiClass extendFrom = (PsiClass) resolveResult.getElement(); + if (extendFrom == null) { + return null; + } + if (!extendFrom.isInterface() && referenceElements.length != 0 && element != referenceElements[0]) { + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory() + .createType(extendFrom, resolveResult.getSubstitutor()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaErrorLocalize.interfaceExpected()) + .registerFix(QuickFixFactory.getInstance().createMoveBoundClassToFrontFix(aClass, type)); + } + else if (referenceElements.length != 0 && element != referenceElements[0] + && referenceElements[0].resolve() instanceof PsiTypeParameter) { + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory() + .createType(extendFrom, resolveResult.getSubstitutor()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterCannotBeFollowedByOtherBounds()) + .registerFix(QuickFixFactory.getInstance().createExtendsListFix(aClass, type, false)); + } + return null; } - // bounds check - if (targetParametersNum > 0 && refParametersNum != 0) { - if (inferenceResult != null) { - final PsiType[] types = inferenceResult.getTypes(); - for (int i = 0; i < typeParameters.length; i++) { - final PsiType type = types[i]; - final HighlightInfo highlightInfo = checkTypeParameterWithinItsBound(typeParameters[i], substitutor, type, referenceElements[0], referenceParameterList); - if (highlightInfo != null) { - return highlightInfo; - } - } - } else { - for (int i = 0; i < typeParameters.length; i++) { - final PsiTypeElement typeElement = referenceElements[i]; - final HighlightInfo highlightInfo = checkTypeParameterWithinItsBound(typeParameters[i], substitutor, typeElement.getType(), typeElement, referenceParameterList); - if (highlightInfo != null) { - return highlightInfo; - } - } - } + @RequiredReadAction + public static HighlightInfo checkInterfaceMultipleInheritance(PsiClass aClass) { + PsiClassType[] types = aClass.getSuperTypes(); + if (types.length < 2) { + return null; + } + Map inheritedClasses = new HashMap<>(); + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return checkInterfaceMultipleInheritance(aClass, aClass, PsiSubstitutor.EMPTY, inheritedClasses, new HashSet<>(), textRange); + } + + private static HighlightInfo checkInterfaceMultipleInheritance( + PsiClass aClass, + PsiElement place, + PsiSubstitutor derivedSubstitutor, + Map inheritedClasses, + Set visited, + TextRange textRange + ) { + List superTypes = + PsiClassImplUtil.getScopeCorrectedSuperTypes(aClass, place.getResolveScope()); + for (PsiClassType.ClassResolveResult result : superTypes) { + PsiClass superClass = result.getElement(); + if (superClass == null || visited.contains(superClass)) { + continue; + } + PsiSubstitutor superTypeSubstitutor = result.getSubstitutor(); + superTypeSubstitutor = MethodSignatureUtil.combineSubstitutors(superTypeSubstitutor, derivedSubstitutor); + + PsiSubstitutor inheritedSubstitutor = inheritedClasses.get(superClass); + if (inheritedSubstitutor != null) { + PsiTypeParameter[] typeParameters = superClass.getTypeParameters(); + for (PsiTypeParameter typeParameter : typeParameters) { + PsiType type1 = inheritedSubstitutor.substitute(typeParameter); + PsiType type2 = superTypeSubstitutor.substitute(typeParameter); + + if (!Objects.equals(type1, type2)) { + LocalizeValue description = JavaCompilationErrorLocalize.classInheritanceDifferentTypeArguments( + HighlightUtil.formatClass(superClass), + JavaHighlightUtil.formatType(type1), + JavaHighlightUtil.formatType(type2) + ); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(description) + .create(); + } + } + } + inheritedClasses.put(superClass, superTypeSubstitutor); + visited.add(superClass); + HighlightInfo highlightInfo = + checkInterfaceMultipleInheritance(superClass, place, superTypeSubstitutor, inheritedClasses, visited, textRange); + visited.remove(superClass); + + if (highlightInfo != null) { + return highlightInfo; + } + } + return null; } - return null; - } + @RequiredReadAction + public static Collection checkOverrideEquivalentMethods(PsiClass aClass) { + List result = new ArrayList<>(); + Collection signaturesWithSupers = aClass.getVisibleSignatures(); + PsiManager manager = aClass.getManager(); + Map sameErasureMethods = + Maps.newHashMap(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); + + Set foundProblems = Sets.newHashSet(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); + for (HierarchicalMethodSignature signature : signaturesWithSupers) { + HighlightInfo.Builder hlBuilder = checkSameErasureNotSubSignatureInner(signature, manager, aClass, sameErasureMethods); + if (hlBuilder != null && foundProblems.add(signature)) { + HighlightInfo hlInfo = hlBuilder.create(); + if (hlInfo != null) { + result.add(hlInfo); + } + } + if (aClass instanceof PsiTypeParameter) { + hlBuilder = HighlightMethodUtil.checkMethodIncompatibleReturnType( + signature, + signature.getSuperSignatures(), + true, + HighlightNamesUtil.getClassDeclarationTextRange(aClass) + ); + if (hlBuilder != null) { + HighlightInfo hlInfo = hlBuilder.create(); + if (hlInfo != null) { + result.add(hlInfo); + } + } + } + } - private static boolean hasSuperMethodsWithTypeParams(PsiMethod method) { - for (PsiMethod superMethod : method.findDeepestSuperMethods()) { - if (superMethod.hasTypeParameters()) { - return true; - } - } - return false; - } - - private static PsiType detectExpectedType(PsiReferenceParameterList referenceParameterList) { - final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(referenceParameterList, PsiNewExpression.class); - LOG.assertTrue(newExpression != null); - final PsiElement parent = newExpression.getParent(); - PsiType expectedType = null; - if (parent instanceof PsiVariable && newExpression.equals(((PsiVariable) parent).getInitializer())) { - expectedType = ((PsiVariable) parent).getType(); - } else if (parent instanceof PsiAssignmentExpression && newExpression.equals(((PsiAssignmentExpression) parent).getRExpression())) { - expectedType = ((PsiAssignmentExpression) parent).getLExpression().getType(); - } else if (parent instanceof PsiReturnStatement) { - PsiElement method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class); - if (method instanceof PsiMethod) { - expectedType = ((PsiMethod) method).getReturnType(); - } - } else if (parent instanceof PsiExpressionList) { - final PsiElement pParent = parent.getParent(); - if (pParent instanceof PsiCallExpression && parent.equals(((PsiCallExpression) pParent).getArgumentList())) { - final PsiMethod method = ((PsiCallExpression) pParent).resolveMethod(); - if (method != null) { - final PsiExpression[] expressions = ((PsiCallExpression) pParent).getArgumentList().getExpressions(); - final int idx = ArrayUtil.find(expressions, newExpression); - if (idx > -1) { - final PsiParameterList parameterList = method.getParameterList(); - if (idx < parameterList.getParametersCount()) { - expectedType = parameterList.getParameters()[idx].getType(); - } - } - } - } - } - return expectedType; - } - - @Nullable - private static HighlightInfo checkTypeParameterWithinItsBound(PsiTypeParameter classParameter, - final PsiSubstitutor substitutor, - final PsiType type, - final PsiElement typeElement2Highlight, - PsiReferenceParameterList referenceParameterList) { - final PsiClass referenceClass = type instanceof PsiClassType ? ((PsiClassType) type).resolve() : null; - final PsiType psiType = substitutor.substitute(classParameter); - if (psiType instanceof PsiClassType && !(PsiUtil.resolveClassInType(psiType) instanceof PsiTypeParameter)) { - if (GenericsUtil.checkNotInBounds(type, psiType, referenceParameterList)) { - final String description = "Actual type argument and inferred type contradict each other"; - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement2Highlight).descriptionAndTooltip(description).create(); - } + return result; + } + + @RequiredReadAction + public static HighlightInfo checkDefaultMethodOverrideEquivalentToObjectNonPrivate( + LanguageLevel languageLevel, + PsiClass aClass, + PsiMethod method, + PsiElement methodIdentifier + ) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && aClass.isInterface() && method.hasModifierProperty(PsiModifier.DEFAULT)) { + HierarchicalMethodSignature sig = method.getHierarchicalMethodSignature(); + for (HierarchicalMethodSignature methodSignature : sig.getSuperSignatures()) { + PsiMethod objectMethod = methodSignature.getMethod(); + if (objectMethod.getContainingClass() instanceof PsiClass containingClass + && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName()) + && objectMethod.isPublic()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(methodIdentifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodDefaultOverridesObjectMember(sig.getName())) + .create(); + } + } + } + return null; } - final PsiClassType[] bounds = classParameter.getSuperTypes(); - for (PsiClassType type1 : bounds) { - PsiType bound = substitutor.substitute(type1); - if (!bound.equalsToText(JavaClassNames.JAVA_LANG_OBJECT) && GenericsUtil.checkNotInBounds(type, bound, referenceParameterList)) { - PsiClass boundClass = bound instanceof PsiClassType ? ((PsiClassType) bound).resolve() : null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkUnrelatedDefaultMethods(PsiClass aClass, PsiIdentifier classIdentifier) { + Map> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass); - @NonNls final String messageKey = boundClass == null || referenceClass == null || referenceClass.isInterface() == boundClass.isInterface() ? "generics.type.parameter.is.not.within" + - ".its.bound.extend" : "generics.type.parameter.is.not.within.its.bound.implement"; + boolean isInterface = aClass.isInterface(); + for (Set overrideEquivalentMethods : overrideEquivalent.values()) { + if (overrideEquivalentMethods.size() <= 1) { + continue; + } + List defaults = null; + List abstracts = null; + boolean hasConcrete = false; + for (PsiMethod method : overrideEquivalentMethods) { + boolean isDefault = method.hasModifierProperty(PsiModifier.DEFAULT); + boolean isAbstract = method.isAbstract(); + if (isDefault) { + if (defaults == null) { + defaults = new ArrayList<>(2); + } + defaults.add(method); + } + if (isAbstract) { + if (abstracts == null) { + abstracts = new ArrayList<>(2); + } + abstracts.add(method); + } + hasConcrete |= !isDefault && !isAbstract; + } - String description = JavaErrorBundle.message(messageKey, referenceClass != null ? HighlightUtil.formatClass(referenceClass) : type.getPresentableText(), JavaHighlightUtil - .formatType(bound)); + if (!hasConcrete && defaults != null) { + PsiMethod defaultMethod = defaults.get(0); + if (MethodSignatureUtil.findMethodBySuperMethod(aClass, defaultMethod, false) != null) { + continue; + } + PsiClass defaultMethodContainingClass = defaultMethod.getContainingClass(); + if (defaultMethodContainingClass == null) { + continue; + } + PsiMethod unrelatedMethod = abstracts != null ? abstracts.get(0) : defaults.get(1); + PsiClass unrelatedMethodContainingClass = unrelatedMethod.getContainingClass(); + if (unrelatedMethodContainingClass == null) { + continue; + } + if (!aClass.isAbstract() && !(aClass instanceof PsiTypeParameter) + && abstracts != null && unrelatedMethodContainingClass.isInterface()) { + if (defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) + && MethodSignatureUtil.isSubsignature( + unrelatedMethod.getSignature(TypeConversionUtil.getSuperClassSubstitutor( + unrelatedMethodContainingClass, + defaultMethodContainingClass, + PsiSubstitutor.EMPTY + )), + defaultMethod.getSignature(PsiSubstitutor.EMPTY) + )) { + continue; + } + String c1 = HighlightUtil.formatClass(aClass, false); + String m = JavaHighlightUtil.formatMethod(abstracts.get(0)); + String c2 = HighlightUtil.formatClass(unrelatedMethodContainingClass, false); + LocalizeValue message = aClass instanceof PsiEnumConstantInitializer + ? JavaCompilationErrorLocalize.classMustImplementMethod(c1, m, c2) + : JavaCompilationErrorLocalize.classMustImplementMethodOrAbstract(c1, m, c2); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(classIdentifier) + .descriptionAndTooltip(message) + .registerFix(QuickFixFactory.getInstance().createImplementMethodsFix(aClass)); + } + if (isInterface || abstracts == null || unrelatedMethodContainingClass.isInterface()) { + List defaultContainingClasses = ContainerUtil.mapNotNull(defaults, PsiMethod::getContainingClass); + String unrelatedDefaults = hasUnrelatedDefaults(defaultContainingClasses); + if (unrelatedDefaults == null + && (abstracts == null || !hasNotOverriddenAbstract(defaultContainingClasses, unrelatedMethodContainingClass))) { + continue; + } - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement2Highlight).descriptionAndTooltip(description).create(); - if (bound instanceof PsiClassType && referenceClass != null && info != null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createExtendsListFix(referenceClass, (PsiClassType) bound, true), null); + String c = HighlightUtil.formatClass(aClass); + String m = JavaHighlightUtil.formatMethod(defaultMethod); + LocalizeValue description = unrelatedDefaults != null + ? JavaErrorLocalize.textClassInheritsUnrelatedDefaults(c, m, unrelatedDefaults) + : JavaCompilationErrorLocalize.classInheritsAbstractAndDefault( + c, + m, + HighlightUtil.formatClass(defaultMethodContainingClass), + HighlightUtil.formatClass(unrelatedMethodContainingClass) + ); + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(classIdentifier) + .descriptionAndTooltip(description) + .registerFix(QuickFixFactory.getInstance().createImplementMethodsFix(aClass)); + } + } } - return info; - } - } - return null; - } - - private static String typeParameterListOwnerDescription(final PsiTypeParameterListOwner typeParameterListOwner) { - if (typeParameterListOwner instanceof PsiClass) { - return HighlightUtil.formatClass((PsiClass) typeParameterListOwner); - } else if (typeParameterListOwner instanceof PsiMethod) { - return JavaHighlightUtil.formatMethod((PsiMethod) typeParameterListOwner); - } else { - LOG.error("Unknown " + typeParameterListOwner); - return "?"; - } - } - - private static String typeParameterListOwnerCategoryDescription(final PsiTypeParameterListOwner typeParameterListOwner) { - if (typeParameterListOwner instanceof PsiClass) { - return JavaErrorBundle.message("generics.holder.type"); - } else if (typeParameterListOwner instanceof PsiMethod) { - return JavaErrorBundle.message("generics.holder.method"); - } else { - LOG.error("Unknown " + typeParameterListOwner); - return "?"; - } - } - - @Nullable - public static HighlightInfo checkElementInTypeParameterExtendsList(@Nonnull PsiReferenceList referenceList, - @Nonnull PsiClass aClass, - @Nonnull JavaResolveResult resolveResult, - @Nonnull PsiElement element) { - final PsiJavaCodeReferenceElement[] referenceElements = referenceList.getReferenceElements(); - PsiClass extendFrom = (PsiClass) resolveResult.getElement(); - if (extendFrom == null) { - return null; - } - HighlightInfo errorResult = null; - if (!extendFrom.isInterface() && referenceElements.length != 0 && element != referenceElements[0]) { - String description = JavaErrorBundle.message("interface.expected"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(extendFrom, resolveResult.getSubstitutor()); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMoveBoundClassToFrontFix(aClass, type), null); - } else if (referenceElements.length != 0 && element != referenceElements[0] && referenceElements[0].resolve() instanceof PsiTypeParameter) { - final String description = JavaErrorBundle.message("type.parameter.cannot.be.followed.by.other.bounds"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(extendFrom, resolveResult.getSubstitutor()); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createExtendsListFix(aClass, type, false), null); + return null; } - return errorResult; - } - public static HighlightInfo checkInterfaceMultipleInheritance(PsiClass aClass) { - final PsiClassType[] types = aClass.getSuperTypes(); - if (types.length < 2) { - return null; - } - Map inheritedClasses = new HashMap<>(); - final TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return checkInterfaceMultipleInheritance(aClass, aClass, PsiSubstitutor.EMPTY, inheritedClasses, new HashSet<>(), textRange); - } - - private static HighlightInfo checkInterfaceMultipleInheritance(PsiClass aClass, - PsiElement place, - PsiSubstitutor derivedSubstitutor, - Map inheritedClasses, - Set visited, - TextRange textRange) { - final List superTypes = PsiClassImplUtil.getScopeCorrectedSuperTypes(aClass, place.getResolveScope()); - for (PsiClassType.ClassResolveResult result : superTypes) { - final PsiClass superClass = result.getElement(); - if (superClass == null || visited.contains(superClass)) { - continue; - } - PsiSubstitutor superTypeSubstitutor = result.getSubstitutor(); - superTypeSubstitutor = MethodSignatureUtil.combineSubstitutors(superTypeSubstitutor, derivedSubstitutor); - - final PsiSubstitutor inheritedSubstitutor = inheritedClasses.get(superClass); - if (inheritedSubstitutor != null) { - final PsiTypeParameter[] typeParameters = superClass.getTypeParameters(); - for (PsiTypeParameter typeParameter : typeParameters) { - PsiType type1 = inheritedSubstitutor.substitute(typeParameter); - PsiType type2 = superTypeSubstitutor.substitute(typeParameter); - - if (!Comparing.equal(type1, type2)) { - String description = JavaErrorBundle.message("generics.cannot.be.inherited.with.different.type.arguments", HighlightUtil.formatClass(superClass), JavaHighlightUtil - .formatType(type1), JavaHighlightUtil.formatType(type2)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - } - } - inheritedClasses.put(superClass, superTypeSubstitutor); - visited.add(superClass); - final HighlightInfo highlightInfo = checkInterfaceMultipleInheritance(superClass, place, superTypeSubstitutor, inheritedClasses, visited, textRange); - visited.remove(superClass); - - if (highlightInfo != null) { - return highlightInfo; - } - } - return null; - } - - @Nonnull - public static Collection checkOverrideEquivalentMethods(@Nonnull PsiClass aClass) { - List result = new ArrayList<>(); - final Collection signaturesWithSupers = aClass.getVisibleSignatures(); - PsiManager manager = aClass.getManager(); - Map sameErasureMethods = Maps.newHashMap(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); - - final Set foundProblems = Sets.newHashSet(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); - for (HierarchicalMethodSignature signature : signaturesWithSupers) { - HighlightInfo info = checkSameErasureNotSubSignatureInner(signature, manager, aClass, sameErasureMethods); - if (info != null && foundProblems.add(signature)) { - result.add(info); - } - if (aClass instanceof PsiTypeParameter) { - info = HighlightMethodUtil.checkMethodIncompatibleReturnType(signature, signature.getSuperSignatures(), true, HighlightNamesUtil.getClassDeclarationTextRange(aClass)); - if (info != null) { - result.add(info); - } - } + private static boolean belongToOneHierarchy( + PsiClass defaultMethodContainingClass, + PsiClass unrelatedMethodContainingClass + ) { + return defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) + || unrelatedMethodContainingClass.isInheritor(defaultMethodContainingClass, true); } - return result; - } - - public static HighlightInfo checkDefaultMethodOverrideEquivalentToObjectNonPrivate(@Nonnull LanguageLevel languageLevel, - @Nonnull PsiClass aClass, - @Nonnull PsiMethod method, - @Nonnull PsiElement methodIdentifier) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && aClass.isInterface() && method.hasModifierProperty(PsiModifier.DEFAULT)) { - HierarchicalMethodSignature sig = method.getHierarchicalMethodSignature(); - for (HierarchicalMethodSignature methodSignature : sig.getSuperSignatures()) { - final PsiMethod objectMethod = methodSignature.getMethod(); - final PsiClass containingClass = objectMethod.getContainingClass(); - if (containingClass != null && JavaClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName()) && objectMethod.hasModifierProperty(PsiModifier.PUBLIC)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip("Default method '" + sig.getName() + "' overrides a member of 'java.lang.Object'").range - (methodIdentifier).create(); - } - } - } - return null; - } - - public static HighlightInfo checkUnrelatedDefaultMethods(@Nonnull PsiClass aClass, @Nonnull PsiIdentifier classIdentifier) { - final Map> overrideEquivalent = PsiSuperMethodUtil.collectOverrideEquivalents(aClass); - - final boolean isInterface = aClass.isInterface(); - for (Set overrideEquivalentMethods : overrideEquivalent.values()) { - if (overrideEquivalentMethods.size() <= 1) { - continue; - } - List defaults = null; - List abstracts = null; - boolean hasConcrete = false; - for (PsiMethod method : overrideEquivalentMethods) { - final boolean isDefault = method.hasModifierProperty(PsiModifier.DEFAULT); - final boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); - if (isDefault) { - if (defaults == null) { - defaults = new ArrayList<>(2); - } - defaults.add(method); - } - if (isAbstract) { - if (abstracts == null) { - abstracts = new ArrayList<>(2); - } - abstracts.add(method); - } - hasConcrete |= !isDefault && !isAbstract; - } - - if (!hasConcrete && defaults != null) { - final PsiMethod defaultMethod = defaults.get(0); - if (MethodSignatureUtil.findMethodBySuperMethod(aClass, defaultMethod, false) != null) { - continue; - } - final PsiClass defaultMethodContainingClass = defaultMethod.getContainingClass(); - if (defaultMethodContainingClass == null) { - continue; - } - final PsiMethod unrelatedMethod = abstracts != null ? abstracts.get(0) : defaults.get(1); - final PsiClass unrelatedMethodContainingClass = unrelatedMethod.getContainingClass(); - if (unrelatedMethodContainingClass == null) { - continue; - } - if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter) && abstracts != null && unrelatedMethodContainingClass.isInterface()) { - if (defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) && MethodSignatureUtil.isSubsignature(unrelatedMethod.getSignature(TypeConversionUtil - .getSuperClassSubstitutor(unrelatedMethodContainingClass, defaultMethodContainingClass, PsiSubstitutor.EMPTY)), defaultMethod.getSignature(PsiSubstitutor.EMPTY))) { - continue; - } - final String key = aClass instanceof PsiEnumConstantInitializer ? "enum.constant.should.implement.method" : "class.must.be.abstract"; - final String message = JavaErrorBundle.message(key, HighlightUtil.formatClass(aClass, false), JavaHighlightUtil.formatMethod(abstracts.get(0)), HighlightUtil.formatClass - (unrelatedMethodContainingClass, false)); - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(classIdentifier).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass)); - return info; - } - if (isInterface || abstracts == null || unrelatedMethodContainingClass.isInterface()) { - final List defaultContainingClasses = ContainerUtil.mapNotNull(defaults, PsiMethod::getContainingClass); - final String unrelatedDefaults = hasUnrelatedDefaults(defaultContainingClasses); - if (unrelatedDefaults == null && (abstracts == null || !hasNotOverriddenAbstract(defaultContainingClasses, unrelatedMethodContainingClass))) { - continue; - } - - final String message = unrelatedDefaults != null ? " inherits unrelated defaults for " : " inherits abstract and default for "; - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(classIdentifier).descriptionAndTooltip(HighlightUtil.formatClass(aClass) + - message + - JavaHighlightUtil.formatMethod(defaultMethod) + " from types " + - (unrelatedDefaults != null ? unrelatedDefaults : HighlightUtil.formatClass(defaultMethodContainingClass) + " and " + HighlightUtil.formatClass - (unrelatedMethodContainingClass))).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass)); - return info; - } - } - } - return null; - } - - private static boolean belongToOneHierarchy(@Nonnull PsiClass defaultMethodContainingClass, @Nonnull PsiClass unrelatedMethodContainingClass) { - return defaultMethodContainingClass.isInheritor(unrelatedMethodContainingClass, true) || unrelatedMethodContainingClass.isInheritor(defaultMethodContainingClass, true); - } - - private static boolean hasNotOverriddenAbstract(List defaultContainingClasses, @Nonnull PsiClass abstractMethodContainingClass) { - return defaultContainingClasses.stream().noneMatch(containingClass -> belongToOneHierarchy(containingClass, abstractMethodContainingClass)); - } - - private static String hasUnrelatedDefaults(List defaults) { - if (defaults.size() > 1) { - PsiClass[] defaultClasses = defaults.toArray(PsiClass.EMPTY_ARRAY); - ArrayList classes = new ArrayList<>(defaults); - for (final PsiClass aClass1 : defaultClasses) { - classes.removeIf(aClass2 -> aClass1.isInheritor(aClass2, true)); - } - - if (classes.size() > 1) { - return HighlightUtil.formatClass(classes.get(0)) + " and " + HighlightUtil.formatClass(classes.get(1)); - } + private static boolean hasNotOverriddenAbstract( + List defaultContainingClasses, + PsiClass abstractMethodContainingClass + ) { + return defaultContainingClasses.stream() + .noneMatch(containingClass -> belongToOneHierarchy(containingClass, abstractMethodContainingClass)); } - return null; - } - - public static HighlightInfo checkUnrelatedConcrete(@Nonnull PsiClass psiClass, @Nonnull PsiIdentifier classIdentifier) { - final PsiClass superClass = psiClass.getSuperClass(); - if (superClass != null && superClass.hasTypeParameters()) { - final Collection visibleSignatures = superClass.getVisibleSignatures(); - final Map overrideEquivalent = Maps.newHashMap(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); - for (HierarchicalMethodSignature hms : visibleSignatures) { - final PsiMethod method = hms.getMethod(); - if (method.isConstructor()) { - continue; - } - if (method.hasModifierProperty(PsiModifier.ABSTRACT) || method.hasModifierProperty(PsiModifier.DEFAULT)) { - continue; - } - if (psiClass.findMethodsBySignature(method, false).length > 0) { - continue; - } - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - continue; - } - final PsiSubstitutor containingClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY); - final PsiSubstitutor finalSubstitutor = PsiSuperMethodUtil.obtainFinalSubstitutor(containingClass, containingClassSubstitutor, hms.getSubstitutor(), false); - final MethodSignatureBackedByPsiMethod signature = MethodSignatureBackedByPsiMethod.create(method, finalSubstitutor, false); - final PsiMethod foundMethod = overrideEquivalent.get(signature); - PsiClass foundMethodContainingClass; - if (foundMethod != null && - !foundMethod.hasModifierProperty(PsiModifier.ABSTRACT) && - !foundMethod.hasModifierProperty(PsiModifier.DEFAULT) && - (foundMethodContainingClass = foundMethod.getContainingClass()) != null) { - final String description = "Methods " + - JavaHighlightUtil.formatMethod(foundMethod) + " from " + HighlightUtil.formatClass(foundMethodContainingClass) + - " and " + - JavaHighlightUtil.formatMethod(method) + " from " + HighlightUtil.formatClass(containingClass) + - " are inherited with the same signature"; - - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(classIdentifier).descriptionAndTooltip(description).create(); - //todo override fix - return info; - } - overrideEquivalent.put(signature, method); - } - } - return null; - } - - @Nullable - private static HighlightInfo checkSameErasureNotSubSignatureInner(@Nonnull HierarchicalMethodSignature signature, - @Nonnull PsiManager manager, - @Nonnull PsiClass aClass, - @Nonnull Map sameErasureMethods) { - PsiMethod method = signature.getMethod(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - if (!facade.getResolveHelper().isAccessible(method, aClass, null)) { - return null; - } - MethodSignature signatureToErase = method.getSignature(PsiSubstitutor.EMPTY); - MethodSignatureBackedByPsiMethod sameErasure = sameErasureMethods.get(signatureToErase); - HighlightInfo info; - if (sameErasure != null) { - if (aClass instanceof PsiTypeParameter || - MethodSignatureUtil.findMethodBySuperMethod(aClass, sameErasure.getMethod(), false) != null || - !(InheritanceUtil.isInheritorOrSelf(sameErasure.getMethod().getContainingClass(), method.getContainingClass(), true) || InheritanceUtil.isInheritorOrSelf(method - .getContainingClass(), sameErasure.getMethod().getContainingClass(), true))) { - info = checkSameErasureNotSubSignatureOrSameClass(sameErasure, signature, aClass, method); - if (info != null) { - return info; - } - } - } else { - sameErasureMethods.put(signatureToErase, signature); - } - List supers = signature.getSuperSignatures(); - for (HierarchicalMethodSignature superSignature : supers) { - info = checkSameErasureNotSubSignatureInner(superSignature, manager, aClass, sameErasureMethods); - if (info != null) { - return info; - } - - if (superSignature.isRaw() && !signature.isRaw()) { - final PsiType[] parameterTypes = signature.getParameterTypes(); - PsiType[] erasedTypes = superSignature.getErasedParameterTypes(); - for (int i = 0; i < erasedTypes.length; i++) { - if (!Comparing.equal(parameterTypes[i], erasedTypes[i])) { - return getSameErasureMessage(false, method, superSignature.getMethod(), HighlightNamesUtil.getClassDeclarationTextRange(aClass)); - } - } - } + private static String hasUnrelatedDefaults(List defaults) { + if (defaults.size() > 1) { + PsiClass[] defaultClasses = defaults.toArray(PsiClass.EMPTY_ARRAY); + ArrayList classes = new ArrayList<>(defaults); + for (PsiClass aClass1 : defaultClasses) { + classes.removeIf(aClass2 -> aClass1.isInheritor(aClass2, true)); + } - } - return null; - } - - @Nullable - private static HighlightInfo checkSameErasureNotSubSignatureOrSameClass(final MethodSignatureBackedByPsiMethod signatureToCheck, - final HierarchicalMethodSignature superSignature, - final PsiClass aClass, - final PsiMethod superMethod) { - final PsiMethod checkMethod = signatureToCheck.getMethod(); - if (superMethod.equals(checkMethod)) { - return null; - } - PsiClass checkContainingClass = checkMethod.getContainingClass(); - LOG.assertTrue(checkContainingClass != null); - PsiClass superContainingClass = superMethod.getContainingClass(); - boolean checkEqualsSuper = checkContainingClass.equals(superContainingClass); - if (checkMethod.isConstructor()) { - if (!superMethod.isConstructor() || !checkEqualsSuper) { - return null; - } - } else if (superMethod.isConstructor()) { - return null; - } + if (classes.size() > 1) { + return HighlightUtil.formatClass(classes.get(0)) + " and " + HighlightUtil.formatClass(classes.get(1)); + } + } - final boolean atLeast17 = JavaVersionService.getInstance().isAtLeast(aClass, JavaSdkVersion.JDK_1_7); - if (checkMethod.hasModifierProperty(PsiModifier.STATIC) && !checkEqualsSuper && !atLeast17) { - return null; + return null; } - if (superMethod.hasModifierProperty(PsiModifier.STATIC) && superContainingClass != null && - superContainingClass.isInterface() && !checkEqualsSuper && PsiUtil.isLanguageLevel8OrHigher(superContainingClass)) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkUnrelatedConcrete(PsiClass psiClass, PsiIdentifier classIdentifier) { + PsiClass superClass = psiClass.getSuperClass(); + if (superClass != null && superClass.hasTypeParameters()) { + Collection visibleSignatures = superClass.getVisibleSignatures(); + Map overrideEquivalent = + Maps.newHashMap(MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY); + for (HierarchicalMethodSignature hms : visibleSignatures) { + PsiMethod method = hms.getMethod(); + if (method.isConstructor()) { + continue; + } + if (method.isAbstract() || method.hasModifierProperty(PsiModifier.DEFAULT)) { + continue; + } + if (psiClass.findMethodsBySignature(method, false).length > 0) { + continue; + } + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + continue; + } + PsiSubstitutor containingClassSubstitutor = + TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY); + PsiSubstitutor finalSubstitutor = + PsiSuperMethodUtil.obtainFinalSubstitutor(containingClass, containingClassSubstitutor, hms.getSubstitutor(), false); + MethodSignatureBackedByPsiMethod signature = MethodSignatureBackedByPsiMethod.create(method, finalSubstitutor, false); + PsiMethod foundMethod = overrideEquivalent.get(signature); + PsiClass foundMethodContainingClass; + if (foundMethod != null + && !foundMethod.isAbstract() + && !foundMethod.hasModifierProperty(PsiModifier.DEFAULT) + && (foundMethodContainingClass = foundMethod.getContainingClass()) != null) { + //TODO: override fix + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(classIdentifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classInheritanceMethodClash( + JavaHighlightUtil.formatMethod(foundMethod), + HighlightUtil.formatClass(foundMethodContainingClass), + JavaHighlightUtil.formatMethod(method), + HighlightUtil.formatClass(containingClass) + )); + } + overrideEquivalent.put(signature, method); + } + } + return null; } - final PsiType retErasure1 = TypeConversionUtil.erasure(checkMethod.getReturnType()); - final PsiType retErasure2 = TypeConversionUtil.erasure(superMethod.getReturnType()); + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkSameErasureNotSubSignatureInner( + HierarchicalMethodSignature signature, + PsiManager manager, + PsiClass aClass, + Map sameErasureMethods + ) { + PsiMethod method = signature.getMethod(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); + if (!facade.getResolveHelper().isAccessible(method, aClass, null)) { + return null; + } + MethodSignature signatureToErase = method.getSignature(PsiSubstitutor.EMPTY); + MethodSignatureBackedByPsiMethod sameErasure = sameErasureMethods.get(signatureToErase); + HighlightInfo.Builder hlBuilder; + if (sameErasure != null) { + if (aClass instanceof PsiTypeParameter + || MethodSignatureUtil.findMethodBySuperMethod(aClass, sameErasure.getMethod(), false) != null + || !(InheritanceUtil.isInheritorOrSelf( + sameErasure.getMethod().getContainingClass(), + method.getContainingClass(), + true + ) + || InheritanceUtil.isInheritorOrSelf(method.getContainingClass(), sameErasure.getMethod().getContainingClass(), true))) { + hlBuilder = checkSameErasureNotSubSignatureOrSameClass(sameErasure, signature, aClass, method); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + else { + sameErasureMethods.put(signatureToErase, signature); + } + List supers = signature.getSuperSignatures(); + for (HierarchicalMethodSignature superSignature : supers) { + hlBuilder = checkSameErasureNotSubSignatureInner(superSignature, manager, aClass, sameErasureMethods); + if (hlBuilder != null) { + return hlBuilder; + } - boolean differentReturnTypeErasure = !Comparing.equal(retErasure1, retErasure2); - if (checkEqualsSuper && atLeast17) { - if (retErasure1 != null && retErasure2 != null) { - differentReturnTypeErasure = !TypeConversionUtil.isAssignable(retErasure1, retErasure2); - } else { - differentReturnTypeErasure = !(retErasure1 == null && retErasure2 == null); - } - } + if (superSignature.isRaw() && !signature.isRaw()) { + PsiType[] parameterTypes = signature.getParameterTypes(); + PsiType[] erasedTypes = superSignature.getErasedParameterTypes(); + for (int i = 0; i < erasedTypes.length; i++) { + if (!Comparing.equal(parameterTypes[i], erasedTypes[i])) { + return getSameErasureMessage( + false, + method, + superSignature.getMethod(), + HighlightNamesUtil.getClassDeclarationTextRange(aClass) + ); + } + } + } - if (differentReturnTypeErasure && - !TypeConversionUtil.isVoidType(retErasure1) && - !TypeConversionUtil.isVoidType(retErasure2) && - !(checkEqualsSuper && Arrays.equals(superSignature.getParameterTypes(), signatureToCheck.getParameterTypes())) && - !atLeast17) { - int idx = 0; - final PsiType[] erasedTypes = signatureToCheck.getErasedParameterTypes(); - boolean erasure = erasedTypes.length > 0; - for (PsiType type : superSignature.getParameterTypes()) { - erasure &= Comparing.equal(type, erasedTypes[idx]); - idx++; - } - - if (!erasure) { + } return null; - } } - if (!checkEqualsSuper && MethodSignatureUtil.isSubsignature(superSignature, signatureToCheck)) { - return null; - } - if (superContainingClass != null && !superContainingClass.isInterface() && checkContainingClass.isInterface() && !aClass.equals(superContainingClass)) { - return null; - } - if (aClass.equals(checkContainingClass)) { - boolean sameClass = aClass.equals(superContainingClass); - return getSameErasureMessage(sameClass, checkMethod, superMethod, HighlightNamesUtil.getMethodDeclarationTextRange(checkMethod)); - } else { - return getSameErasureMessage(false, checkMethod, superMethod, HighlightNamesUtil.getClassDeclarationTextRange(aClass)); - } - } - - private static HighlightInfo getSameErasureMessage(final boolean sameClass, @Nonnull PsiMethod method, @Nonnull PsiMethod superMethod, TextRange textRange) { - @NonNls final String key = sameClass ? "generics.methods.have.same.erasure" : method.hasModifierProperty(PsiModifier.STATIC) ? "generics.methods.have.same.erasure.hide" : "generics.methods" + - ".have.same.erasure.override"; - String description = JavaErrorBundle.message(key, HighlightMethodUtil.createClashMethodMessage(method, superMethod, !sameClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - - public static HighlightInfo checkTypeParameterInstantiation(PsiNewExpression expression) { - PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); - if (classReference == null) { - return null; - } - final JavaResolveResult result = classReference.advancedResolve(false); - final PsiElement element = result.getElement(); - if (element instanceof PsiTypeParameter) { - String description = JavaErrorBundle.message("generics.type.parameter.cannot.be.instantiated", HighlightUtil.formatClass((PsiTypeParameter) element)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(classReference).descriptionAndTooltip(description).create(); - } - return null; - } - - public static HighlightInfo checkWildcardUsage(PsiTypeElement typeElement) { - PsiType type = typeElement.getType(); - if (type instanceof PsiWildcardType) { - if (typeElement.getParent() instanceof PsiReferenceParameterList) { - PsiElement parent = typeElement.getParent().getParent(); - LOG.assertTrue(parent instanceof PsiJavaCodeReferenceElement, parent); - PsiElement refParent = parent.getParent(); - if (refParent instanceof PsiAnonymousClass) { - refParent = refParent.getParent(); - } - if (refParent instanceof PsiNewExpression) { - PsiNewExpression newExpression = (PsiNewExpression) refParent; - if (!(newExpression.getType() instanceof PsiArrayType)) { - String description = JavaErrorBundle.message("wildcard.type.cannot.be.instantiated", JavaHighlightUtil.formatType(type)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } - } else if (refParent instanceof PsiReferenceList) { - PsiElement refPParent = refParent.getParent(); - if (!(refPParent instanceof PsiTypeParameter) || refParent != ((PsiTypeParameter) refPParent).getExtendsList()) { - String description = JavaErrorBundle.message("generics.wildcard.not.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } - } - } else { - String description = JavaErrorBundle.message("generics.wildcards.may.be.used.only.as.reference.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } - } + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkSameErasureNotSubSignatureOrSameClass( + MethodSignatureBackedByPsiMethod signatureToCheck, + HierarchicalMethodSignature superSignature, + PsiClass aClass, + PsiMethod superMethod + ) { + PsiMethod checkMethod = signatureToCheck.getMethod(); + if (superMethod.equals(checkMethod)) { + return null; + } + PsiClass checkContainingClass = checkMethod.getContainingClass(); + LOG.assertTrue(checkContainingClass != null); + PsiClass superContainingClass = superMethod.getContainingClass(); + boolean checkEqualsSuper = checkContainingClass.equals(superContainingClass); + if (checkMethod.isConstructor()) { + if (!superMethod.isConstructor() || !checkEqualsSuper) { + return null; + } + } + else if (superMethod.isConstructor()) { + return null; + } - return null; - } + boolean atLeast17 = JavaVersionService.getInstance().isAtLeast(aClass, JavaSdkVersion.JDK_1_7); + if (checkMethod.isStatic() && !checkEqualsSuper && !atLeast17) { + return null; + } - public static HighlightInfo checkReferenceTypeUsedAsTypeArgument(PsiTypeElement typeElement, LanguageLevel level) { - final PsiType type = typeElement.getType(); - if (type != PsiType.NULL && type instanceof PsiPrimitiveType || type instanceof PsiWildcardType && ((PsiWildcardType) type).getBound() instanceof PsiPrimitiveType) { - final PsiElement element = new PsiMatcherImpl(typeElement).parent(PsiMatchers.hasClass(PsiReferenceParameterList.class)).parent(PsiMatchers.hasClass(PsiJavaCodeReferenceElement.class, - PsiNewExpression.class)).getElement(); - if (element == null) { - return null; - } + if (superMethod.isStatic() && superContainingClass != null && + superContainingClass.isInterface() && !checkEqualsSuper && PsiUtil.isLanguageLevel8OrHigher(superContainingClass)) { + return null; + } - if (level.isAtLeast(LanguageLevel.JDK_X)) { - return null; - } - - String text = JavaErrorBundle.message("generics.type.argument.cannot.be.of.primitive.type"); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(text).create(); - - PsiType toConvert = type; - if (type instanceof PsiWildcardType) { - toConvert = ((PsiWildcardType) type).getBound(); - } - if (toConvert instanceof PsiPrimitiveType) { - final PsiClassType boxedType = ((PsiPrimitiveType) toConvert).getBoxedType(typeElement); - if (boxedType != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createReplacePrimitiveWithBoxedTypeAction(typeElement, toConvert.getPresentableText(), ((PsiPrimitiveType) - toConvert).getBoxedTypeName())); - } - } - return highlightInfo; - } + PsiType retErasure1 = TypeConversionUtil.erasure(checkMethod.getReturnType()); + PsiType retErasure2 = TypeConversionUtil.erasure(superMethod.getReturnType()); - return null; - } + boolean differentReturnTypeErasure = !Comparing.equal(retErasure1, retErasure2); + if (checkEqualsSuper && atLeast17) { + if (retErasure1 != null && retErasure2 != null) { + differentReturnTypeErasure = !TypeConversionUtil.isAssignable(retErasure1, retErasure2); + } + else { + differentReturnTypeErasure = !(retErasure1 == null && retErasure2 == null); + } + } - public static HighlightInfo checkForeachExpressionTypeIsIterable(PsiExpression expression) { - if (expression == null || expression.getType() == null) { - return null; - } - final PsiType itemType = JavaGenericsUtil.getCollectionItemType(expression); - if (itemType == null) { - String description = JavaErrorBundle.message("foreach.not.applicable", JavaHighlightUtil.formatType(expression.getType())); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createNotIterableForEachLoopFix(expression)); - return highlightInfo; - } - return null; - } - - public static HighlightInfo checkForEachParameterType(@Nonnull PsiForeachStatement statement, @Nonnull PsiParameter parameter) { - final PsiExpression expression = statement.getIteratedValue(); - final PsiType itemType = expression == null ? null : JavaGenericsUtil.getCollectionItemType(expression); - if (itemType == null) { - return null; - } + if (differentReturnTypeErasure + && !TypeConversionUtil.isVoidType(retErasure1) + && !TypeConversionUtil.isVoidType(retErasure2) + && !(checkEqualsSuper && Arrays.equals(superSignature.getParameterTypes(), signatureToCheck.getParameterTypes())) + && !atLeast17) { + int idx = 0; + PsiType[] erasedTypes = signatureToCheck.getErasedParameterTypes(); + boolean erasure = erasedTypes.length > 0; + for (PsiType type : superSignature.getParameterTypes()) { + erasure &= Objects.equals(type, erasedTypes[idx]); + idx++; + } - final PsiType parameterType = parameter.getType(); - if (TypeConversionUtil.isAssignable(parameterType, itemType)) { - return null; - } - HighlightInfo highlightInfo = HighlightUtil.createIncompatibleTypeHighlightInfo(itemType, parameterType, parameter.getTextRange(), 0); - HighlightUtil.registerChangeVariableTypeFixes(parameter, itemType, expression, highlightInfo); - return highlightInfo; - } - - //http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9.2 - @Nullable - public static HighlightInfo checkAccessStaticFieldFromEnumConstructor(@Nonnull PsiReferenceExpression expr, @Nonnull JavaResolveResult result) { - final PsiElement resolved = result.getElement(); - - if (!(resolved instanceof PsiField)) { - return null; - } - if (!((PsiModifierListOwner) resolved).hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - if (expr.getParent() instanceof PsiSwitchLabelStatement) { - return null; - } - final PsiMember constructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expr); - if (constructorOrInitializer == null) { - return null; - } - if (constructorOrInitializer.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - final PsiClass aClass = constructorOrInitializer instanceof PsiEnumConstantInitializer ? (PsiClass) constructorOrInitializer : constructorOrInitializer.getContainingClass(); - if (aClass == null || !(aClass.isEnum() || aClass instanceof PsiEnumConstantInitializer)) { - return null; + if (!erasure) { + return null; + } + } + + if (!checkEqualsSuper && MethodSignatureUtil.isSubsignature(superSignature, signatureToCheck)) { + return null; + } + if (superContainingClass != null && !superContainingClass.isInterface() && checkContainingClass.isInterface() + && !aClass.equals(superContainingClass)) { + return null; + } + if (aClass.equals(checkContainingClass)) { + boolean sameClass = aClass.equals(superContainingClass); + return getSameErasureMessage( + sameClass, + checkMethod, + superMethod, + HighlightNamesUtil.getMethodDeclarationTextRange(checkMethod) + ); + } + else { + return getSameErasureMessage(false, checkMethod, superMethod, HighlightNamesUtil.getClassDeclarationTextRange(aClass)); + } } - final PsiField field = (PsiField) resolved; - if (aClass instanceof PsiEnumConstantInitializer) { - if (field.getContainingClass() != aClass.getSuperClass()) { + + private static HighlightInfo.Builder getSameErasureMessage( + boolean sameClass, + PsiMethod method, + PsiMethod superMethod, + TextRange textRange + ) { + LocalizeValue clashMethodMessage = HighlightMethodUtil.createClashMethodMessage(method, superMethod, !sameClass); + LocalizeValue description = sameClass + ? JavaCompilationErrorLocalize.methodGenericSameErasure(clashMethodMessage) + : method.isStatic() + ? JavaCompilationErrorLocalize.methodGenericSameErasureHide(clashMethodMessage) + : JavaCompilationErrorLocalize.methodGenericSameErasureOverride(clashMethodMessage); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(description); + } + + @RequiredReadAction + public static HighlightInfo checkTypeParameterInstantiation(PsiNewExpression expression) { + PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + if (classReference == null) { + return null; + } + JavaResolveResult result = classReference.advancedResolve(false); + PsiElement element = result.getElement(); + if (element instanceof PsiTypeParameter typeParam) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(classReference) + .descriptionAndTooltip(JavaCompilationErrorLocalize.newExpressionTypeParameter(HighlightUtil.formatClass(typeParam))) + .create(); + } return null; - } - } else if (field.getContainingClass() != aClass) { - return null; } + @RequiredReadAction + public static HighlightInfo checkWildcardUsage(PsiTypeElement typeElement) { + PsiType type = typeElement.getType(); + if (type instanceof PsiWildcardType) { + if (typeElement.getParent() instanceof PsiReferenceParameterList refParamList) { + PsiElement parent = refParamList.getParent(); + LOG.assertTrue(parent instanceof PsiJavaCodeReferenceElement, parent); + PsiElement refParent = parent.getParent(); + if (refParent instanceof PsiAnonymousClass anonymousClass) { + refParent = anonymousClass.getParent(); + } + if (refParent instanceof PsiNewExpression newExpr) { + if (!(newExpr.getType() instanceof PsiArrayType)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeWildcardCannotBeInstantiated( + JavaHighlightUtil.formatType(type) + )) + .create(); + } + } + else if (refParent instanceof PsiReferenceList refList) { + if (!(refList.getParent() instanceof PsiTypeParameter typeParam) || refList != typeParam.getExtendsList()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeWildcardNotExpected()) + .create(); + } + } + } + else { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaErrorLocalize.genericsWildcardsMayBeUsedOnlyAsReferenceParameters()) + .create(); + } + } - if (!JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_6)) { - final PsiType type = field.getType(); - if (type instanceof PsiClassType && ((PsiClassType) type).resolve() == aClass) { return null; - } } - if (PsiUtil.isCompileTimeConstant((PsiVariable) field)) { - return null; - } + @RequiredReadAction + public static HighlightInfo.Builder checkReferenceTypeUsedAsTypeArgument(PsiTypeElement typeElement, LanguageLevel level) { + PsiType type = typeElement.getType(); + if (type != PsiType.NULL && type instanceof PsiPrimitiveType + || type instanceof PsiWildcardType wildcardType && wildcardType.getBound() instanceof PsiPrimitiveType) { + PsiElement element = new PsiMatcherImpl(typeElement) + .parent(PsiMatchers.hasClass(PsiReferenceParameterList.class)) + .parent(PsiMatchers.hasClass(PsiJavaCodeReferenceElement.class, PsiNewExpression.class)) + .getElement(); + if (element == null) { + return null; + } - String description = JavaErrorBundle.message("illegal.to.access.static.member.from.enum.constructor.or.instance.initializer", HighlightMessageUtil.getSymbolName(resolved, result - .getSubstitutor())); + if (level.isAtLeast(LanguageLevel.JDK_X)) { + return null; + } - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); - } + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaErrorLocalize.genericsTypeArgumentCannotBeOfPrimitiveType()); - @Nullable - public static HighlightInfo checkEnumInstantiation(PsiElement expression, PsiClass aClass) { - if (aClass != null && aClass.isEnum() && - (!(expression instanceof PsiNewExpression) || ((PsiNewExpression) expression).getArrayDimensions().length == 0 && ((PsiNewExpression) expression).getArrayInitializer() == null)) { - String description = JavaErrorBundle.message("enum.types.cannot.be.instantiated"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); + PsiType toConvert = type; + if (type instanceof PsiWildcardType wildcardType) { + toConvert = wildcardType.getBound(); + } + if (toConvert instanceof PsiPrimitiveType primitiveType) { + PsiClassType boxedType = primitiveType.getBoxedType(typeElement); + if (boxedType != null) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createReplacePrimitiveWithBoxedTypeAction( + typeElement, + toConvert.getPresentableText(), + primitiveType.getBoxedTypeName() + )); + } + } + return hlBuilder; + } + + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkGenericArrayCreation(PsiElement element, PsiType type) { - if (type instanceof PsiArrayType) { - if (!JavaGenericsUtil.isReifiableType(((PsiArrayType) type).getComponentType())) { - String description = JavaErrorBundle.message("generic.array.creation"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - } + + @RequiredReadAction + public static HighlightInfo.Builder checkForeachExpressionTypeIsIterable(PsiExpression expression) { + if (expression == null || expression.getType() == null) { + return null; + } + PsiType itemType = JavaGenericsUtil.getCollectionItemType(expression); + if (itemType == null) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.foreachNotApplicable(JavaHighlightUtil.formatType(expression.getType()))); + IntentionAction fix = QuickFixFactory.getInstance().createNotIterableForEachLoopFix(expression); + if (fix != null) { + hlBuilder.registerFix(fix); + } + return hlBuilder; + } + return null; } - return null; - } + @RequiredReadAction + public static HighlightInfo.Builder checkForEachParameterType(PsiForeachStatement statement, PsiParameter parameter) { + PsiExpression expression = statement.getIteratedValue(); + PsiType itemType = expression == null ? null : JavaGenericsUtil.getCollectionItemType(expression); + if (itemType == null) { + return null; + } + + PsiType parameterType = parameter.getType(); + if (TypeConversionUtil.isAssignable(parameterType, itemType)) { + return null; + } + HighlightInfo.Builder hlBuilder = + HighlightUtil.createIncompatibleTypeHighlightInfo(itemType, parameterType, parameter.getTextRange(), 0); + HighlightUtil.registerChangeVariableTypeFixes(parameter, itemType, expression, hlBuilder); + return hlBuilder; + } + + //http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9.2 + @Nullable + @RequiredReadAction + public static HighlightInfo checkAccessStaticFieldFromEnumConstructor( + PsiReferenceExpression expr, + JavaResolveResult result + ) { + PsiElement resolved = result.getElement(); + + if (!(resolved instanceof PsiField field) || !field.isStatic()) { + return null; + } + if (expr.getParent() instanceof PsiSwitchLabelStatement) { + return null; + } + PsiMember constructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expr); + if (constructorOrInitializer == null) { + return null; + } + if (constructorOrInitializer.isStatic()) { + return null; + } + PsiClass aClass = constructorOrInitializer instanceof PsiEnumConstantInitializer enumConstantInitializer + ? enumConstantInitializer + : constructorOrInitializer.getContainingClass(); + if (aClass == null || !(aClass.isEnum() || aClass instanceof PsiEnumConstantInitializer)) { + return null; + } + if (aClass instanceof PsiEnumConstantInitializer) { + if (field.getContainingClass() != aClass.getSuperClass()) { + return null; + } + } + else if (field.getContainingClass() != aClass) { + return null; + } + + if (!JavaVersionService.getInstance().isAtLeast(field, JavaSdkVersion.JDK_1_6) + && field.getType() instanceof PsiClassType classType && classType.resolve() == aClass) { + return null; + } - private static final MethodSignature ourValuesEnumSyntheticMethod = MethodSignatureUtil.createMethodSignature("values", PsiType.EMPTY_ARRAY, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + if (PsiUtil.isCompileTimeConstant((PsiVariable) field)) { + return null; + } - public static boolean isEnumSyntheticMethod(MethodSignature methodSignature, Project project) { - if (methodSignature.equals(ourValuesEnumSyntheticMethod)) { - return true; - } - final PsiType javaLangString = PsiType.getJavaLangString(PsiManager.getInstance(project), GlobalSearchScope.allScope(project)); - final MethodSignature valueOfMethod = MethodSignatureUtil.createMethodSignature("valueOf", new PsiType[]{javaLangString}, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - return valueOfMethod.equals(methodSignature); - } - - @Nullable - public static HighlightInfo checkTypeParametersList(PsiTypeParameterList list, PsiTypeParameter[] parameters, @Nonnull LanguageLevel level) { - final PsiElement parent = list.getParent(); - if (parent instanceof PsiClass && ((PsiClass) parent).isEnum()) { - String description = JavaErrorBundle.message("generics.enum.may.not.have.type.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); - } - if (PsiUtil.isAnnotationMethod(parent)) { - String description = JavaErrorBundle.message("generics.annotation.members.may.not.have.type.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); - } - if (parent instanceof PsiClass && ((PsiClass) parent).isAnnotationType()) { - String description = JavaErrorBundle.message("annotation.may.not.have.type.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); - } + LocalizeValue description = JavaErrorLocalize.illegalToAccessStaticMemberFromEnumConstructorOrInstanceInitializer( + HighlightMessageUtil.getSymbolName(resolved, result.getSubstitutor()) + ); - for (int i = 0; i < parameters.length; i++) { - final PsiTypeParameter typeParameter1 = parameters[i]; - final HighlightInfo cyclicInheritance = HighlightClassUtil.checkCyclicInheritance(typeParameter1); - if (cyclicInheritance != null) { - return cyclicInheritance; - } - String name1 = typeParameter1.getName(); - for (int j = i + 1; j < parameters.length; j++) { - final PsiTypeParameter typeParameter2 = parameters[j]; - String name2 = typeParameter2.getName(); - if (Comparing.strEqual(name1, name2)) { - String message = JavaErrorBundle.message("generics.duplicate.type.parameter", name1); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeParameter2).descriptionAndTooltip(message).create(); - } - } - if (!level.isAtLeast(LanguageLevel.JDK_1_7)) { - for (PsiJavaCodeReferenceElement referenceElement : typeParameter1.getExtendsList().getReferenceElements()) { - final PsiElement resolve = referenceElement.resolve(); - if (resolve instanceof PsiTypeParameter && ArrayUtil.find(parameters, resolve) > i) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(referenceElement.getTextRange()).descriptionAndTooltip("Illegal forward reference").create(); - } - } - } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(description) + .create(); } - return null; - } - @Nonnull - public static Collection checkCatchParameterIsClass(PsiParameter parameter) { - if (!(parameter.getDeclarationScope() instanceof PsiCatchSection)) { - return Collections.emptyList(); + @Nullable + @RequiredReadAction + public static HighlightInfo checkEnumInstantiation(PsiElement expression, PsiClass aClass) { + if (aClass != null && aClass.isEnum() && (!(expression instanceof PsiNewExpression newExpr) + || newExpr.getArrayDimensions().length == 0 && newExpr.getArrayInitializer() == null)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.instantiationEnum()) + .create(); + } + return null; } - final Collection result = ContainerUtil.newArrayList(); - - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - for (PsiTypeElement typeElement : typeElements) { - final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); - if (aClass instanceof PsiTypeParameter) { - final String message = JavaErrorBundle.message("generics.cannot.catch.type.parameters"); - result.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message).create()); - } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkGenericArrayCreation(PsiElement element, PsiType type) { + if (type instanceof PsiArrayType arrayType && !JavaGenericsUtil.isReifiableType(arrayType.getComponentType())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.arrayGeneric()) + .create(); + } + return null; } - return result; - } + private static final MethodSignature ourValuesEnumSyntheticMethod = + MethodSignatureUtil.createMethodSignature("values", PsiType.EMPTY_ARRAY, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - public static HighlightInfo checkInstanceOfGenericType(PsiInstanceOfExpression expression) { - final PsiTypeElement checkTypeElement = expression.getCheckType(); - if (checkTypeElement == null) { - return null; - } - return isIllegalForInstanceOf(checkTypeElement.getType(), checkTypeElement); - } - - /** - * 15.20.2 Type Comparison Operator instanceof - * ReferenceType mentioned after the instanceof operator is reifiable - */ - private static HighlightInfo isIllegalForInstanceOf(PsiType type, final PsiTypeElement typeElement) { - final PsiClass resolved = PsiUtil.resolveClassInClassTypeOnly(type); - if (resolved instanceof PsiTypeParameter) { - String description = JavaErrorBundle.message("generics.cannot.instanceof.type.parameters"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - } + public static boolean isEnumSyntheticMethod(MethodSignature methodSignature, Project project) { + if (methodSignature.equals(ourValuesEnumSyntheticMethod)) { + return true; + } + PsiType javaLangString = PsiType.getJavaLangString(PsiManager.getInstance(project), GlobalSearchScope.allScope(project)); + MethodSignature valueOfMethod = MethodSignatureUtil.createMethodSignature( + "valueOf", + new PsiType[]{javaLangString}, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + return valueOfMethod.equals(methodSignature); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkTypeParametersList( + PsiTypeParameterList list, + PsiTypeParameter[] parameters, + LanguageLevel level + ) { + PsiElement parent = list.getParent(); + if (parent instanceof PsiClass psiClass && psiClass.isEnum()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterOnEnum()); + } + if (PsiUtil.isAnnotationMethod(parent)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterOnAnnotationMember()); + } + if (parent instanceof PsiClass psiClass && psiClass.isAnnotationType()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterOnAnnotation()); + } - if (!JavaGenericsUtil.isReifiableType(type)) { - String description = JavaErrorBundle.message("illegal.generic.type.for.instanceof"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); + for (int i = 0; i < parameters.length; i++) { + PsiTypeParameter typeParameter1 = parameters[i]; + HighlightInfo.Builder cyclicInheritance = HighlightClassUtil.checkCyclicInheritance(typeParameter1); + if (cyclicInheritance != null) { + return cyclicInheritance; + } + String name1 = typeParameter1.getName(); + for (int j = i + 1; j < parameters.length; j++) { + PsiTypeParameter typeParameter2 = parameters[j]; + String name2 = typeParameter2.getName(); + if (Comparing.strEqual(name1, name2)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeParameter2) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeParameterDuplicate(name1)); + } + } + if (!level.isAtLeast(LanguageLevel.JDK_1_7)) { + for (PsiJavaCodeReferenceElement referenceElement : typeParameter1.getExtendsList().getReferenceElements()) { + if (referenceElement.resolve() instanceof PsiTypeParameter typeParam && ArrayUtil.find(parameters, typeParam) > i) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(referenceElement.getTextRange()) + .descriptionAndTooltip(JavaErrorLocalize.illegalForwardReference()); + } + } + } + } + return null; } - return null; - } + @RequiredReadAction + public static Collection checkCatchParameterIsClass(PsiParameter parameter) { + if (!(parameter.getDeclarationScope() instanceof PsiCatchSection)) { + return Collections.emptyList(); + } + Collection result = new ArrayList<>(); + + List typeElements = PsiUtil.getParameterTypeElements(parameter); + for (PsiTypeElement typeElement : typeElements) { + PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); + if (aClass instanceof PsiTypeParameter) { + result.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.catchTypeParameter()) + .create()); + } + } - public static HighlightInfo checkClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { - PsiType type = expression.getOperand().getType(); - if (type instanceof PsiClassType) { - return canSelectFrom((PsiClassType) type, expression.getOperand()); - } - if (type instanceof PsiArrayType) { - final PsiType arrayComponentType = type.getDeepComponentType(); - if (arrayComponentType instanceof PsiClassType) { - return canSelectFrom((PsiClassType) arrayComponentType, expression.getOperand()); - } + return result; } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkInstanceOfGenericType(PsiInstanceOfExpression expression) { + PsiTypeElement checkTypeElement = expression.getCheckType(); + if (checkTypeElement == null) { + return null; + } + return isIllegalForInstanceOf(checkTypeElement.getType(), checkTypeElement); + } + + /** + * 15.20.2 Type Comparison Operator instanceof + * ReferenceType mentioned after the instanceof operator is reifiable + */ + @RequiredReadAction + private static HighlightInfo.@Nullable Builder isIllegalForInstanceOf(PsiType type, PsiTypeElement typeElement) { + if (PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.instanceofTypeParameter()); + } - @Nullable - private static HighlightInfo canSelectFrom(PsiClassType type, PsiTypeElement operand) { - PsiClass aClass = type.resolve(); - if (aClass instanceof PsiTypeParameter) { - String description = JavaErrorBundle.message("cannot.select.dot.class.from.type.variable"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(operand).descriptionAndTooltip(description).create(); - } - if (type.getParameters().length > 0) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(operand).descriptionAndTooltip("Cannot select from parameterized type").create(); + if (!JavaGenericsUtil.isReifiableType(type)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.instanceofIllegalGenericType()); + } + + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkOverrideAnnotation(@Nonnull PsiMethod method, @Nonnull PsiAnnotation overrideAnnotation, @Nonnull LanguageLevel languageLevel) { - try { - MethodSignatureBackedByPsiMethod superMethod = SuperMethodsSearch.search(method, null, true, false).findFirst(); - if (superMethod != null && method.getContainingClass().isInterface()) { - final PsiMethod psiMethod = superMethod.getMethod(); - final PsiClass containingClass = psiMethod.getContainingClass(); - if (containingClass != null && - JavaClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName()) && - psiMethod.hasModifierProperty(PsiModifier.PROTECTED)) { - superMethod = null; - } - } - if (superMethod == null) { - String description = JavaErrorBundle.message("method.does.not.override.super"); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(overrideAnnotation).descriptionAndTooltip(description).create(); - QUICK_FIX_FACTORY.registerPullAsAbstractUpFixes(method, QuickFixActionRegistrar.create(highlightInfo)); - return highlightInfo; - } - PsiClass superClass = superMethod.getMethod().getContainingClass(); - if (languageLevel.equals(LanguageLevel.JDK_1_5) && - superClass != null && - superClass.isInterface()) { - String description = JavaErrorBundle.message("override.not.allowed.in.interfaces"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(overrideAnnotation).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createIncreaseLanguageLevelFix(LanguageLevel.JDK_1_6)); - return info; - } - return null; - } catch (IndexNotReadyException e) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { + PsiType type = expression.getOperand().getType(); + if (type instanceof PsiClassType classType) { + return canSelectFrom(classType, expression.getOperand()); + } + if (type instanceof PsiArrayType && type.getDeepComponentType() instanceof PsiClassType arrayComponentType) { + return canSelectFrom(arrayComponentType, expression.getOperand()); + } + + return null; } - } - - @Nullable - public static HighlightInfo checkSafeVarargsAnnotation(PsiMethod method, LanguageLevel languageLevel) { - PsiModifierList list = method.getModifierList(); - final PsiAnnotation safeVarargsAnnotation = list.findAnnotation("java.lang.SafeVarargs"); - if (safeVarargsAnnotation == null) { - return null; + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder canSelectFrom(PsiClassType type, PsiTypeElement operand) { + if (type.resolve() instanceof PsiTypeParameter) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(operand) + .descriptionAndTooltip(JavaErrorLocalize.cannotSelectDotClassFromTypeVariable()); + } + if (type.getParameters().length > 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(operand) + .descriptionAndTooltip(JavaErrorLocalize.cannotSelectFromParameterizedType()); + } + return null; } - try { - if (!method.isVarArgs()) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(safeVarargsAnnotation).descriptionAndTooltip("@SafeVarargs is not allowed on methods with fixed arity").create(); - } - if (!isSafeVarargsNoOverridingCondition(method, languageLevel)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(safeVarargsAnnotation).descriptionAndTooltip("@SafeVarargs is not allowed on non-final instance methods") - .create(); - } - final PsiParameter varParameter = method.getParameterList().getParameters()[method.getParameterList().getParametersCount() - 1]; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkOverrideAnnotation( + PsiMethod method, + PsiAnnotation overrideAnnotation, + LanguageLevel languageLevel + ) { + try { + MethodSignatureBackedByPsiMethod superMethod = + SuperMethodsSearch.search(method, null, true, false).findFirst(); + if (superMethod != null && method.getContainingClass().isInterface()) { + PsiMethod psiMethod = superMethod.getMethod(); + PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass != null + && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName()) + && psiMethod.isProtected()) { + superMethod = null; + } + } + if (superMethod == null) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(overrideAnnotation) + .descriptionAndTooltip(JavaCompilationErrorLocalize.overrideOnNonOverridingMethod()); + QuickFixFactory.getInstance().registerPullAsAbstractUpFixes(method, QuickFixActionRegistrar.create(hlBuilder)); + return hlBuilder; + } + PsiClass superClass = superMethod.getMethod().getContainingClass(); + if (languageLevel.equals(LanguageLevel.JDK_1_5) + && superClass != null + && superClass.isInterface()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(overrideAnnotation) + .descriptionAndTooltip(JavaErrorBundle.message("override.not.allowed.in.interfaces")) + .registerFix(QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(LanguageLevel.JDK_1_6)); + } + return null; + } + catch (IndexNotReadyException e) { + return null; + } + } - for (PsiReference reference : ReferencesSearch.search(varParameter)) { - final PsiElement element = reference.getElement(); - if (element instanceof PsiExpression && !PsiUtil.isAccessedForReading((PsiExpression) element)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(element).descriptionAndTooltip("@SafeVarargs do not suppress potentially unsafe operations").create(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSafeVarargsAnnotation(PsiMethod method, LanguageLevel languageLevel) { + PsiModifierList list = method.getModifierList(); + PsiAnnotation safeVarargsAnnotation = list.findAnnotation(CommonClassNames.JAVA_LANG_SAFE_VARARGS); + if (safeVarargsAnnotation == null) { + return null; } - } + try { + if (!method.isVarArgs()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(safeVarargsAnnotation) + .descriptionAndTooltip(JavaErrorLocalize.safevarargsNotAllowedOnMethodsWithFixedArity()); + } + if (!isSafeVarargsNoOverridingCondition(method, languageLevel)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(safeVarargsAnnotation) + .descriptionAndTooltip(JavaErrorLocalize.safevarargsNotAllowedNonFinalInstanceMethods()); + } + PsiParameter varParameter = method.getParameterList().getParameters()[method.getParameterList().getParametersCount() - 1]; - LOG.assertTrue(varParameter.isVarArgs()); - final PsiEllipsisType ellipsisType = (PsiEllipsisType) varParameter.getType(); - final PsiType componentType = ellipsisType.getComponentType(); - if (JavaGenericsUtil.isReifiableType(componentType)) { - PsiElement element = varParameter.getTypeElement(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(element).descriptionAndTooltip("@SafeVarargs is not applicable for reifiable types").create(); - } - return null; - } catch (IndexNotReadyException e) { - return null; - } - } - - public static boolean isSafeVarargsNoOverridingCondition(PsiMethod method, LanguageLevel languageLevel) { - return method.hasModifierProperty(PsiModifier.FINAL) || - method.hasModifierProperty(PsiModifier.STATIC) || - method.isConstructor() || - method.hasModifierProperty(PsiModifier.PRIVATE) && languageLevel.isAtLeast(LanguageLevel.JDK_1_9); - } - - public static void checkEnumConstantForConstructorProblems(@Nonnull PsiEnumConstant enumConstant, @Nonnull HighlightInfoHolder holder, @Nonnull JavaSdkVersion javaSdkVersion) { - PsiClass containingClass = enumConstant.getContainingClass(); - if (enumConstant.getInitializingClass() == null) { - HighlightInfo highlightInfo = HighlightClassUtil.checkInstantiationOfAbstractClass(containingClass, enumConstant.getNameIdentifier()); - if (highlightInfo != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createImplementMethodsFix(enumConstant)); - holder.add(highlightInfo); - return; - } - highlightInfo = HighlightClassUtil.checkClassWithAbstractMethods(enumConstant.getContainingClass(), enumConstant, enumConstant.getNameIdentifier().getTextRange()); - if (highlightInfo != null) { - holder.add(highlightInfo); - return; - } - } - PsiClassType type = JavaPsiFacade.getInstance(holder.getProject()).getElementFactory().createType(containingClass); - - HighlightMethodUtil.checkConstructorCall(type.resolveGenerics(), enumConstant, type, null, holder, javaSdkVersion); - } - - @Nullable - public static HighlightInfo checkEnumSuperConstructorCall(PsiMethodCallExpression expr) { - PsiReferenceExpression methodExpression = expr.getMethodExpression(); - final PsiElement refNameElement = methodExpression.getReferenceNameElement(); - if (refNameElement != null && PsiKeyword.SUPER.equals(refNameElement.getText())) { - final PsiMember constructor = PsiUtil.findEnclosingConstructorOrInitializer(expr); - if (constructor instanceof PsiMethod) { - final PsiClass aClass = constructor.getContainingClass(); - if (aClass != null && aClass.isEnum()) { - final String message = JavaErrorBundle.message("call.to.super.is.not.allowed.in.enum.constructor"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(message).create(); - } - } - } - return null; - } - - @Nullable - public static HighlightInfo checkVarArgParameterIsLast(@Nonnull PsiParameter parameter) { - PsiElement declarationScope = parameter.getDeclarationScope(); - if (declarationScope instanceof PsiMethod) { - PsiParameter[] params = ((PsiMethod) declarationScope).getParameterList().getParameters(); - if (params[params.length - 1] != parameter) { - String description = JavaErrorBundle.message("vararg.not.last.parameter"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMakeVarargParameterLastFix(parameter)); - return info; - } + for (PsiReference reference : ReferencesSearch.search(varParameter)) { + if (reference.getElement() instanceof PsiExpression expression && !PsiUtil.isAccessedForReading(expression)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.safevarargsNotSuppressPotentiallyUnsafeOperations()); + } + } + + LOG.assertTrue(varParameter.isVarArgs()); + System.out.println(varParameter.isVarArgs() + " " + varParameter.getType().getClass()); + PsiEllipsisType ellipsisType = (PsiEllipsisType) varParameter.getType(); + PsiType componentType = ellipsisType.getComponentType(); + if (JavaGenericsUtil.isReifiableType(componentType)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(varParameter.getTypeElement()) + .descriptionAndTooltip(JavaErrorLocalize.safevarargsNotApplicableForReifiableTypes()); + } + return null; + } + catch (IndexNotReadyException e) { + return null; + } } - return null; - } - - @Nonnull - public static List checkEnumConstantModifierList(PsiModifierList modifierList) { - List list = null; - PsiElement[] children = modifierList.getChildren(); - for (PsiElement child : children) { - if (child instanceof PsiKeyword) { - if (list == null) { - list = new ArrayList<>(); - } - String description = JavaErrorBundle.message("modifiers.for.enum.constants"); - list.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(child).descriptionAndTooltip(description).create()); - } + + public static boolean isSafeVarargsNoOverridingCondition(PsiMethod method, LanguageLevel languageLevel) { + return method.isFinal() + || method.isStatic() + || method.isConstructor() + || method.isPrivate() && languageLevel.isAtLeast(LanguageLevel.JDK_1_9); + } + + @RequiredReadAction + public static void checkEnumConstantForConstructorProblems( + PsiEnumConstant enumConstant, + HighlightInfoHolder holder, + JavaSdkVersion javaSdkVersion + ) { + PsiClass containingClass = enumConstant.getContainingClass(); + if (enumConstant.getInitializingClass() == null) { + HighlightInfo.Builder highlightInfo = + HighlightClassUtil.checkInstantiationOfAbstractClass(containingClass, enumConstant.getNameIdentifier()); + if (highlightInfo != null) { + highlightInfo.registerFix(QuickFixFactory.getInstance().createImplementMethodsFix(enumConstant)); + holder.add(highlightInfo.create()); + return; + } + highlightInfo = HighlightClassUtil.checkClassWithAbstractMethods( + enumConstant.getContainingClass(), + enumConstant, + enumConstant.getNameIdentifier().getTextRange() + ); + if (highlightInfo != null) { + holder.add(highlightInfo.create()); + return; + } + } + PsiClassType type = JavaPsiFacade.getInstance(holder.getProject()).getElementFactory() + .createType(containingClass); + + HighlightMethodUtil.checkConstructorCall(type.resolveGenerics(), enumConstant, type, null, holder, javaSdkVersion); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkEnumSuperConstructorCall(PsiMethodCallExpression expr) { + PsiReferenceExpression methodExpression = expr.getMethodExpression(); + PsiElement refNameElement = methodExpression.getReferenceNameElement(); + if (refNameElement != null && PsiKeyword.SUPER.equals(refNameElement.getText())) { + if (PsiUtil.findEnclosingConstructorOrInitializer(expr) instanceof PsiMethod constructor) { + PsiClass aClass = constructor.getContainingClass(); + if (aClass != null && aClass.isEnum()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(JavaErrorLocalize.callToSuperIsNotAllowedInEnumConstructor()); + } + } + } + return null; } - return Lists.notNullize(list); - } - - @Nullable - public static HighlightInfo checkParametersAllowed(PsiReferenceParameterList refParamList) { - final PsiElement parent = refParamList.getParent(); - if (parent instanceof PsiReferenceExpression) { - final PsiElement grandParent = parent.getParent(); - if (!(grandParent instanceof PsiMethodCallExpression) && !(parent instanceof PsiMethodReferenceExpression)) { - final String message = JavaErrorBundle.message("generics.reference.parameters.not.allowed"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refParamList).descriptionAndTooltip(message).create(); - } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkVarArgParameterIsLast(PsiParameter parameter) { + if (parameter.getDeclarationScope() instanceof PsiMethod method) { + PsiParameter[] params = method.getParameterList().getParameters(); + if (params[params.length - 1] != parameter) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameter) + .descriptionAndTooltip(JavaCompilationErrorLocalize.varargNotLastParameter()) + .registerFix(QuickFixFactory.getInstance().createMakeVarargParameterLastFix(parameter)); + } + } + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkParametersOnRaw(PsiReferenceParameterList refParamList) { - JavaResolveResult resolveResult = null; - PsiElement parent = refParamList.getParent(); - PsiElement qualifier = null; - if (parent instanceof PsiJavaCodeReferenceElement) { - resolveResult = ((PsiJavaCodeReferenceElement) parent).advancedResolve(false); - qualifier = ((PsiJavaCodeReferenceElement) parent).getQualifier(); - } else if (parent instanceof PsiCallExpression) { - resolveResult = ((PsiCallExpression) parent).resolveMethodGenerics(); - if (parent instanceof PsiMethodCallExpression) { - final PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) parent).getMethodExpression(); - qualifier = methodExpression.getQualifier(); - } + @RequiredReadAction + public static List checkEnumConstantModifierList(PsiModifierList modifierList) { + List list = null; + PsiElement[] children = modifierList.getChildren(); + for (PsiElement child : children) { + if (child instanceof PsiKeyword) { + if (list == null) { + list = new ArrayList<>(); + } + list.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(child) + .descriptionAndTooltip(JavaErrorLocalize.modifiersForEnumConstants()) + .create() + ); + } + } + return Lists.notNullize(list); } - if (resolveResult != null) { - PsiElement element = resolveResult.getElement(); - if (!(element instanceof PsiTypeParameterListOwner)) { - return null; - } - if (((PsiModifierListOwner) element).hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement) qualifier).resolve() instanceof PsiTypeParameter) { - return null; - } - PsiClass containingClass = ((PsiMember) element).getContainingClass(); - if (containingClass != null && PsiUtil.isRawSubstitutor(containingClass, resolveResult.getSubstitutor())) { - if ((parent instanceof PsiCallExpression || parent instanceof PsiMethodReferenceExpression) && PsiUtil.isLanguageLevel7OrHigher(parent)) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkParametersAllowed(PsiReferenceParameterList refParamList) { + if (refParamList.getParent() instanceof PsiReferenceExpression refExpr + && !(refExpr.getParent() instanceof PsiMethodCallExpression) + && !(refExpr instanceof PsiMethodReferenceExpression)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refParamList) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeArgumentNotAllowed()); } - if (element instanceof PsiMethod) { - if (((PsiMethod) element).findSuperMethods().length > 0) { - return null; - } - if (qualifier instanceof PsiReferenceExpression) { - final PsiType type = ((PsiReferenceExpression) qualifier).getType(); - final boolean isJavac7 = JavaVersionService.getInstance().isAtLeast(containingClass, JavaSdkVersion.JDK_1_7); - if (type instanceof PsiClassType && isJavac7 && ((PsiClassType) type).isRaw()) { - return null; - } - final PsiClass typeParameter = PsiUtil.resolveClassInType(type); - if (typeParameter instanceof PsiTypeParameter) { - if (isJavac7) { + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkParametersOnRaw(PsiReferenceParameterList refParamList) { + JavaResolveResult resolveResult = null; + PsiElement parent = refParamList.getParent(); + PsiElement qualifier = null; + if (parent instanceof PsiJavaCodeReferenceElement javaCodeRef) { + resolveResult = javaCodeRef.advancedResolve(false); + qualifier = javaCodeRef.getQualifier(); + } + else if (parent instanceof PsiCallExpression call) { + resolveResult = call.resolveMethodGenerics(); + if (call instanceof PsiMethodCallExpression methodCall) { + PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + qualifier = methodExpression.getQualifier(); + } + } + if (resolveResult != null) { + PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiTypeParameterListOwner typeParamListOwner)) { + return null; + } + if (typeParamListOwner.isStatic()) { return null; - } - for (PsiClassType classType : typeParameter.getExtendsListTypes()) { - final PsiClass resolve = classType.resolve(); - if (resolve != null) { - final PsiMethod[] superMethods = resolve.findMethodsBySignature((PsiMethod) element, true); - for (PsiMethod superMethod : superMethods) { - if (!PsiUtil.isRawSubstitutor(superMethod, resolveResult.getSubstitutor())) { - return null; + } + if (qualifier instanceof PsiJavaCodeReferenceElement javaCodeRef && javaCodeRef.resolve() instanceof PsiTypeParameter) { + return null; + } + PsiClass containingClass = ((PsiMember) element).getContainingClass(); + if (containingClass != null && PsiUtil.isRawSubstitutor(containingClass, resolveResult.getSubstitutor())) { + if ((parent instanceof PsiCallExpression || parent instanceof PsiMethodReferenceExpression) + && PsiUtil.isLanguageLevel7OrHigher(parent)) { + return null; + } + + if (element instanceof PsiMethod method) { + if (method.findSuperMethods().length > 0) { + return null; + } + if (qualifier instanceof PsiReferenceExpression qualifierRefExpr) { + PsiType type = qualifierRefExpr.getType(); + boolean isJavac7 = JavaVersionService.getInstance().isAtLeast(containingClass, JavaSdkVersion.JDK_1_7); + if (type instanceof PsiClassType classType && isJavac7 && classType.isRaw()) { + return null; + } + PsiClass typeParameter = PsiUtil.resolveClassInType(type); + if (typeParameter instanceof PsiTypeParameter) { + if (isJavac7) { + return null; + } + for (PsiClassType classType : typeParameter.getExtendsListTypes()) { + PsiClass resolve = classType.resolve(); + if (resolve != null) { + PsiMethod[] superMethods = resolve.findMethodsBySignature((PsiMethod) element, true); + for (PsiMethod superMethod : superMethods) { + if (!PsiUtil.isRawSubstitutor(superMethod, resolveResult.getSubstitutor())) { + return null; + } + } + } + } + } } - } } - } + LocalizeValue message = element instanceof PsiClass + ? JavaCompilationErrorLocalize.typeArgumentOnRawType() + : JavaCompilationErrorLocalize.typeArgumentOnRawMethod(); + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refParamList) + .descriptionAndTooltip(message); } - } } - final String message = element instanceof PsiClass ? JavaErrorBundle.message("generics.type.arguments.on.raw.type") : JavaErrorBundle.message("generics.type.arguments.on.raw" + - ".method"); - - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refParamList).descriptionAndTooltip(message).create(); - } - } - return null; - } - - public static HighlightInfo checkCannotInheritFromEnum(PsiClass superClass, PsiElement elementToHighlight) { - HighlightInfo errorResult = null; - if (Comparing.strEqual("java.lang.Enum", superClass.getQualifiedName())) { - String message = JavaErrorBundle.message("classes.extends.enum"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message).create(); + return null; } - return errorResult; - } - public static HighlightInfo checkGenericCannotExtendException(PsiReferenceList list) { - PsiElement parent = list.getParent(); - if (!(parent instanceof PsiClass)) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCannotInheritFromEnum(PsiClass superClass, PsiElement elementToHighlight) { + if (CommonClassNames.JAVA_LANG_ENUM.equals(superClass.getQualifiedName())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaErrorLocalize.classesExtendsEnum()); + } + return null; } - PsiClass aClass = (PsiClass) parent; - if (!aClass.hasTypeParameters() || aClass.getExtendsList() != list) { - return null; - } - PsiJavaCodeReferenceElement[] referenceElements = list.getReferenceElements(); - PsiClass throwableClass = null; - for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { - PsiElement resolved = referenceElement.resolve(); - if (!(resolved instanceof PsiClass)) { - continue; - } - if (throwableClass == null) { - throwableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.Throwable", aClass.getResolveScope()); - } - if (InheritanceUtil.isInheritorOrSelf((PsiClass) resolved, throwableClass, true)) { - String message = JavaErrorBundle.message("generic.extend.exception"); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(referenceElement).descriptionAndTooltip(message).create(); - PsiClassType classType = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType((PsiClass) resolved); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createExtendsListFix(aClass, classType, false)); - return highlightInfo; - } - } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkGenericCannotExtendException(PsiReferenceList list) { + PsiElement parent = list.getParent(); + if (!(parent instanceof PsiClass aClass)) { + return null; + } - public static HighlightInfo checkEnumMustNotBeLocal(final PsiClass aClass) { - if (!aClass.isEnum()) { - return null; - } - PsiElement parent = aClass.getParent(); - if (!(parent instanceof PsiClass || parent instanceof PsiFile || parent instanceof PsiClassLevelDeclarationStatement)) { - String description = JavaErrorBundle.message("local.enum"); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); + if (!aClass.hasTypeParameters() || aClass.getExtendsList() != list) { + return null; + } + PsiJavaCodeReferenceElement[] referenceElements = list.getReferenceElements(); + PsiClass throwableClass = null; + for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { + if (!(referenceElement.resolve() instanceof PsiClass psiClass)) { + continue; + } + if (throwableClass == null) { + throwableClass = JavaPsiFacade.getInstance(aClass.getProject()) + .findClass(CommonClassNames.JAVA_LANG_THROWABLE, aClass.getResolveScope()); + } + if (InheritanceUtil.isInheritorOrSelf(psiClass, throwableClass, true)) { + PsiClassType classType = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory() + .createType(psiClass); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(referenceElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classGenericExtendsException()) + .registerFix(QuickFixFactory.getInstance().createExtendsListFix(aClass, classType, false)); + } + } + return null; } - return null; - } - public static HighlightInfo checkEnumWithoutConstantsCantHaveAbstractMethods(final PsiClass aClass) { - if (!aClass.isEnum()) { - return null; - } - for (PsiField field : aClass.getFields()) { - if (field instanceof PsiEnumConstant) { + @Nullable + @RequiredReadAction + public static HighlightInfo checkEnumMustNotBeLocal(PsiClass aClass) { + if (!aClass.isEnum()) { + return null; + } + PsiElement parent = aClass.getParent(); + if (!(parent instanceof PsiClass || parent instanceof PsiFile || parent instanceof PsiClassLevelDeclarationStatement)) { + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(JavaErrorLocalize.localEnum()) + .create(); + } return null; - } - } - for (PsiMethod method : aClass.getMethods()) { - if (method.hasModifierProperty(PsiModifier.ABSTRACT)) { - final String description = "Enum declaration without enum constants cannot have abstract methods"; - final TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - } - return null; - } - - public static HighlightInfo checkSelectStaticClassFromParameterizedType(final PsiElement resolved, final PsiJavaCodeReferenceElement ref) { - if (resolved instanceof PsiClass && ((PsiClass) resolved).hasModifierProperty(PsiModifier.STATIC)) { - final PsiElement qualifier = ref.getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement) { - final PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement) qualifier).getParameterList(); - if (parameterList != null && parameterList.getTypeArguments().length > 0) { - final String message = JavaErrorBundle.message("generics.select.static.class.from.parameterized.type", HighlightUtil.formatClass((PsiClass) resolved)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameterList).descriptionAndTooltip(message).create(); - } - } } - return null; - } - public static HighlightInfo checkCannotInheritFromTypeParameter(final PsiClass superClass, final PsiJavaCodeReferenceElement toHighlight) { - if (superClass instanceof PsiTypeParameter) { - String description = JavaErrorBundle.message("class.cannot.inherit.from.its.type.parameter"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(toHighlight).descriptionAndTooltip(description).create(); - } - return null; - } - - /** - * http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.8 - */ - public static HighlightInfo checkRawOnParameterizedType(@Nonnull PsiJavaCodeReferenceElement parent, PsiElement resolved) { - PsiReferenceParameterList list = parent.getParameterList(); - if (list == null || list.getTypeArguments().length > 0) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkEnumWithoutConstantsCantHaveAbstractMethods(PsiClass aClass) { + if (!aClass.isEnum()) { + return null; + } + for (PsiField field : aClass.getFields()) { + if (field instanceof PsiEnumConstant) { + return null; + } + } + for (PsiMethod method : aClass.getMethods()) { + if (method.isAbstract()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip( + LocalizeValue.localizeTODO("Enum declaration without enum constants cannot have abstract methods") + ); + } + } + return null; } - final PsiElement qualifier = parent.getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement && - ((PsiJavaCodeReferenceElement) qualifier).getTypeParameters().length > 0 && - resolved instanceof PsiTypeParameterListOwner && - ((PsiTypeParameterListOwner) resolved).hasTypeParameters() && - !((PsiTypeParameterListOwner) resolved).hasModifierProperty(PsiModifier.STATIC)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parent).descriptionAndTooltip("Improper formed type; some type parameters are missing").create(); + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSelectStaticClassFromParameterizedType(PsiElement resolved, PsiJavaCodeReferenceElement ref) { + if (resolved instanceof PsiClass psiClass && psiClass.isStatic() + && ref.getQualifier() instanceof PsiJavaCodeReferenceElement javaCodeRef) { + PsiReferenceParameterList parameterList = javaCodeRef.getParameterList(); + if (parameterList != null && parameterList.getTypeArguments().length > 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameterList) + .descriptionAndTooltip(JavaErrorLocalize.genericsSelectStaticClassFromParameterizedType( + HighlightUtil.formatClass(psiClass) + )); + } + } + return null; } - return null; - } - public static HighlightInfo checkCannotPassInner(PsiJavaCodeReferenceElement ref) { - if (ref.getParent() instanceof PsiTypeElement) { - final PsiClass psiClass = PsiTreeUtil.getParentOfType(ref, PsiClass.class); - if (psiClass == null) { + @RequiredReadAction + public static HighlightInfo.Builder checkCannotInheritFromTypeParameter(PsiClass superClass, PsiJavaCodeReferenceElement toHighlight) { + if (superClass instanceof PsiTypeParameter) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(toHighlight) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classInheritsTypeParameter()); + } return null; - } - if (PsiTreeUtil.isAncestor(psiClass.getExtendsList(), ref, false) || PsiTreeUtil.isAncestor(psiClass.getImplementsList(), ref, false)) { - final PsiElement qualifier = ref.getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement) qualifier).resolve() == psiClass) { - final PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.getParentOfType(ref, PsiJavaCodeReferenceElement.class); - if (referenceElement == null) { + } + + /** + * http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.8 + */ + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkRawOnParameterizedType(PsiJavaCodeReferenceElement parent, PsiElement resolved) { + PsiReferenceParameterList list = parent.getParameterList(); + if (list == null || list.getTypeArguments().length > 0) { return null; - } - final PsiElement typeClass = referenceElement.resolve(); - if (!(typeClass instanceof PsiClass)) { + } + if (parent.getQualifier() instanceof PsiJavaCodeReferenceElement ref + && ref.getTypeParameters().length > 0 + && resolved instanceof PsiTypeParameterListOwner typeParamListOwner + && typeParamListOwner.hasTypeParameters() + && !typeParamListOwner.isStatic()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parent) + .descriptionAndTooltip(JavaErrorLocalize.textImproperFormedType()); + } + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCannotPassInner(PsiJavaCodeReferenceElement ref) { + if (!(ref.getParent() instanceof PsiTypeElement)) { return null; - } - final PsiElement resolve = ref.resolve(); - final PsiClass containingClass = resolve != null ? ((PsiClass) resolve).getContainingClass() : null; - if (containingClass == null) { + } + PsiClass psiClass = PsiTreeUtil.getParentOfType(ref, PsiClass.class); + if (psiClass == null) { return null; - } - if (psiClass.isInheritor(containingClass, true) || - unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance((PsiClass) typeClass, ((PsiClass) resolve).getExtendsList()) || - unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance((PsiClass) typeClass, ((PsiClass) resolve).getImplementsList())) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(((PsiClass) resolve).getName() + " is not accessible in current context").range(ref) - .create(); - } } - } - } - return null; - } - - private static boolean unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance(PsiClass containingClass, PsiReferenceList referenceList) { - if (referenceList != null) { - for (PsiJavaCodeReferenceElement referenceElement : referenceList.getReferenceElements()) { - if (!referenceElement.isQualified()) { - final PsiElement superClass = referenceElement.resolve(); - if (superClass instanceof PsiClass) { - final PsiClass superContainingClass = ((PsiClass) superClass).getContainingClass(); - if (superContainingClass != null && - InheritanceUtil.isInheritorOrSelf(containingClass, superContainingClass, true) && - !PsiTreeUtil.isAncestor(superContainingClass, containingClass, true)) { - return true; - } - } - } - } - } - return false; - } - - private static void registerVariableParameterizedTypeFixes(@Nullable HighlightInfo highlightInfo, - @Nonnull PsiVariable variable, - @Nonnull PsiReferenceParameterList parameterList, - @Nonnull JavaSdkVersion version) { - PsiType type = variable.getType(); - if (!(type instanceof PsiClassType) || highlightInfo == null) { - return; + if (PsiTreeUtil.isAncestor(psiClass.getExtendsList(), ref, false) + || PsiTreeUtil.isAncestor(psiClass.getImplementsList(), ref, false)) { + PsiElement qualifier = ref.getQualifier(); + if (qualifier instanceof PsiJavaCodeReferenceElement javaCodeRef && javaCodeRef.resolve() == psiClass) { + PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.getParentOfType(ref, PsiJavaCodeReferenceElement.class); + if (referenceElement == null) { + return null; + } + if (!(referenceElement.resolve() instanceof PsiClass typeClass)) { + return null; + } + PsiClass resolvedClass = (PsiClass) ref.resolve(); + PsiClass containingClass = resolvedClass != null ? resolvedClass.getContainingClass() : null; + if (containingClass == null) { + return null; + } + if (psiClass.isInheritor(containingClass, true) + || unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance(typeClass, resolvedClass.getExtendsList()) + || unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance(typeClass, resolvedClass.getImplementsList())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(LocalizeValue.localizeTODO(resolvedClass.getName() + " is not accessible in current context")) + .range(ref); + } + } + } + return null; } - if (DumbService.getInstance(variable.getProject()).isDumb()) { - return; - } + @RequiredReadAction + private static boolean unqualifiedNestedClassReferenceAccessedViaContainingClassInheritance( + PsiClass containingClass, + PsiReferenceList referenceList + ) { + if (referenceList != null) { + for (PsiJavaCodeReferenceElement referenceElement : referenceList.getReferenceElements()) { + if (!referenceElement.isQualified() && referenceElement.resolve() instanceof PsiClass superClass) { + PsiClass superContainingClass = superClass.getContainingClass(); + if (superContainingClass != null + && InheritanceUtil.isInheritorOrSelf(containingClass, superContainingClass, true) + && !PsiTreeUtil.isAncestor(superContainingClass, containingClass, true)) { + return true; + } + } + } + } + return false; + } + + @RequiredReadAction + private static void registerVariableParameterizedTypeFixes( + HighlightInfo.@Nullable Builder highlightInfo, + PsiVariable variable, + PsiReferenceParameterList parameterList, + JavaSdkVersion version + ) { + PsiType type = variable.getType(); + if (!(type instanceof PsiClassType classType) || highlightInfo == null + || DumbService.getInstance(variable.getProject()).isDumb()) { + return; + } - String shortName = ((PsiClassType) type).getClassName(); - PsiManager manager = parameterList.getManager(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); - PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(parameterList.getProject()); - PsiClass[] classes = shortNamesCache.getClassesByName(shortName, GlobalSearchScope.allScope(manager.getProject())); - PsiElementFactory factory = facade.getElementFactory(); - for (PsiClass aClass : classes) { - if (checkReferenceTypeArgumentList(aClass, parameterList, PsiSubstitutor.EMPTY, false, version) == null) { - PsiType[] actualTypeParameters = parameterList.getTypeArguments(); - PsiTypeParameter[] classTypeParameters = aClass.getTypeParameters(); - Map map = new HashMap<>(); - for (int j = 0; j < classTypeParameters.length; j++) { - PsiTypeParameter classTypeParameter = classTypeParameters[j]; - PsiType actualTypeParameter = actualTypeParameters[j]; - map.put(classTypeParameter, actualTypeParameter); - } - PsiSubstitutor substitutor = factory.createSubstitutor(map); - PsiType suggestedType = factory.createType(aClass, substitutor); - HighlightUtil.registerChangeVariableTypeFixes(variable, suggestedType, variable.getInitializer(), highlightInfo); - } - } - } - - public static HighlightInfo checkInferredIntersections(PsiSubstitutor substitutor, TextRange ref) { - for (Map.Entry typeEntry : substitutor.getSubstitutionMap().entrySet()) { - final String parameterName = typeEntry.getKey().getName(); - final PsiType type = typeEntry.getValue(); - if (type instanceof PsiIntersectionType) { - final String conflictingConjunctsMessage = ((PsiIntersectionType) type).getConflictingConjunctsMessage(); - if (conflictingConjunctsMessage != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip("Type parameter " + parameterName + " has incompatible upper bounds: " + - conflictingConjunctsMessage).range(ref).create(); - } - } - } - return null; - } - - public static HighlightInfo areSupersAccessible(@Nonnull PsiClass aClass) { - return areSupersAccessible(aClass, aClass.getResolveScope(), HighlightNamesUtil.getClassDeclarationTextRange(aClass), true); - } - - public static HighlightInfo areSupersAccessible(@Nonnull PsiClass aClass, PsiReferenceExpression ref) { - final GlobalSearchScope resolveScope = ref.getResolveScope(); - final HighlightInfo info = areSupersAccessible(aClass, resolveScope, ref.getTextRange(), false); - if (info != null) { - return info; + String shortName = classType.getClassName(); + PsiManager manager = parameterList.getManager(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject()); + PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(parameterList.getProject()); + PsiClass[] classes = shortNamesCache.getClassesByName(shortName, GlobalSearchScope.allScope(manager.getProject())); + PsiElementFactory factory = facade.getElementFactory(); + for (PsiClass aClass : classes) { + if (checkReferenceTypeArgumentList(aClass, parameterList, PsiSubstitutor.EMPTY, false, version) == null) { + PsiType[] actualTypeParameters = parameterList.getTypeArguments(); + PsiTypeParameter[] classTypeParameters = aClass.getTypeParameters(); + Map map = new HashMap<>(); + for (int j = 0; j < classTypeParameters.length; j++) { + PsiTypeParameter classTypeParameter = classTypeParameters[j]; + PsiType actualTypeParameter = actualTypeParameters[j]; + map.put(classTypeParameter, actualTypeParameter); + } + PsiSubstitutor substitutor = factory.createSubstitutor(map); + PsiType suggestedType = factory.createType(aClass, substitutor); + HighlightUtil.registerChangeVariableTypeFixes(variable, suggestedType, variable.getInitializer(), highlightInfo); + } + } } - String message = null; - final PsiElement parent = ref.getParent(); - if (parent instanceof PsiMethodCallExpression) { - final JavaResolveResult resolveResult = ((PsiMethodCallExpression) parent).resolveMethodGenerics(); - final PsiMethod method = (PsiMethod) resolveResult.getElement(); - if (method != null) { - final HashSet classes = new HashSet<>(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(aClass.getProject()); - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - - message = isSuperTypeAccessible(substitutor.substitute(method.getReturnType()), classes, false, resolveScope, facade); - if (message == null) { - for (PsiType type : method.getSignature(substitutor).getParameterTypes()) { - - message = isSuperTypeAccessible(type, classes, false, resolveScope, facade); - if (message != null) { - break; - } - } - } - } - } else { - final PsiElement resolve = ref.resolve(); - if (resolve instanceof PsiField) { - message = isSuperTypeAccessible(((PsiField) resolve).getType(), new HashSet<>(), false, resolveScope, JavaPsiFacade.getInstance(aClass.getProject())); - } + @RequiredReadAction + public static HighlightInfo.Builder checkInferredIntersections(PsiSubstitutor substitutor, TextRange ref) { + for (Map.Entry typeEntry : substitutor.getSubstitutionMap().entrySet()) { + String parameterName = typeEntry.getKey().getName(); + if (typeEntry.getValue() instanceof PsiIntersectionType intersectionType) { + String conflictingConjunctsMessage = intersectionType.getConflictingConjunctsMessage(); + if (conflictingConjunctsMessage != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip( + JavaCompilationErrorLocalize.typeParameterIncompatibleUpperBounds(parameterName, conflictingConjunctsMessage) + ) + .range(ref); + } + } + } + return null; } - if (message != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(message).range(ref.getTextRange()).create(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder areSupersAccessible(PsiClass aClass) { + return areSupersAccessible(aClass, aClass.getResolveScope(), HighlightNamesUtil.getClassDeclarationTextRange(aClass), true); } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder areSupersAccessible(PsiClass aClass, PsiReferenceExpression ref) { + GlobalSearchScope resolveScope = ref.getResolveScope(); + HighlightInfo.Builder hlBuilder = areSupersAccessible(aClass, resolveScope, ref.getTextRange(), false); + if (hlBuilder != null) { + return hlBuilder; + } - return null; - } + LocalizeValue message = LocalizeValue.empty(); + PsiElement parent = ref.getParent(); + if (parent instanceof PsiMethodCallExpression methodCall) { + JavaResolveResult resolveResult = methodCall.resolveMethodGenerics(); + PsiMethod method = (PsiMethod) resolveResult.getElement(); + if (method != null) { + Set classes = new HashSet<>(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(aClass.getProject()); + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + + message = isSuperTypeAccessible(substitutor.substitute(method.getReturnType()), classes, false, resolveScope, facade); + if (message.isEmpty()) { + for (PsiType type : method.getSignature(substitutor).getParameterTypes()) { + message = isSuperTypeAccessible(type, classes, false, resolveScope, facade); + if (message.isNotEmpty()) { + break; + } + } + } + } + } + else if (ref.resolve() instanceof PsiField field) { + message = isSuperTypeAccessible( + field.getType(), + new HashSet<>(), + false, + resolveScope, + JavaPsiFacade.getInstance(aClass.getProject()) + ); + } + + if (message.isNotEmpty()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(message) + .range(ref.getTextRange()); + } - private static HighlightInfo areSupersAccessible(@Nonnull PsiClass aClass, GlobalSearchScope resolveScope, TextRange range, boolean checkParameters) { - final JavaPsiFacade factory = JavaPsiFacade.getInstance(aClass.getProject()); - for (PsiClassType superType : aClass.getSuperTypes()) { - final String notAccessibleErrorMessage = isSuperTypeAccessible(superType, new HashSet<>(), checkParameters, resolveScope, factory); - if (notAccessibleErrorMessage != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(notAccessibleErrorMessage).range(range).create(); - } - } - return null; - } - - @Nullable - private static String isSuperTypeAccessible(PsiType superType, HashSet classes, boolean checkParameters, GlobalSearchScope resolveScope, JavaPsiFacade factory) { - final PsiClass aClass = PsiUtil.resolveClassInType(superType); - if (aClass != null && classes.add(aClass)) { - VirtualFile vFile = PsiUtilCore.getVirtualFile(aClass); - if (vFile == null) { return null; - } - FileIndexFacade index = FileIndexFacade.getInstance(aClass.getProject()); - if (!index.isInSource(vFile) && !index.isInLibraryClasses(vFile)) { + } + + @RequiredReadAction + private static HighlightInfo.Builder areSupersAccessible( + PsiClass aClass, + GlobalSearchScope resolveScope, + TextRange range, + boolean checkParameters + ) { + JavaPsiFacade factory = JavaPsiFacade.getInstance(aClass.getProject()); + for (PsiClassType superType : aClass.getSuperTypes()) { + LocalizeValue notAccessibleErrorMessage = + isSuperTypeAccessible(superType, new HashSet<>(), checkParameters, resolveScope, factory); + if (notAccessibleErrorMessage.isNotEmpty()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(notAccessibleErrorMessage) + .range(range); + } + } return null; - } + } + + @RequiredReadAction + private static LocalizeValue isSuperTypeAccessible( + PsiType superType, + Set classes, + boolean checkParameters, + GlobalSearchScope resolveScope, + JavaPsiFacade factory + ) { + PsiClass aClass = PsiUtil.resolveClassInType(superType); + if (aClass != null && classes.add(aClass)) { + VirtualFile vFile = PsiUtilCore.getVirtualFile(aClass); + if (vFile == null) { + return LocalizeValue.empty(); + } + FileIndexFacade index = FileIndexFacade.getInstance(aClass.getProject()); + if (!index.isInSource(vFile) && !index.isInLibraryClasses(vFile)) { + return LocalizeValue.empty(); + } - final String qualifiedName = aClass.getQualifiedName(); - if (qualifiedName != null && factory.findClass(qualifiedName, resolveScope) == null) { - return "Cannot access " + HighlightUtil.formatClass(aClass); - } + String qualifiedName = aClass.getQualifiedName(); + if (qualifiedName != null && factory.findClass(qualifiedName, resolveScope) == null) { + return JavaCompilationErrorLocalize.classNotAccessible(HighlightUtil.formatClass(aClass)); + } - if (checkParameters) { - boolean isInLibrary = !index.isInContent(vFile); - if (superType instanceof PsiClassType) { - for (PsiType psiType : ((PsiClassType) superType).getParameters()) { - final String notAccessibleMessage = isSuperTypeAccessible(psiType, classes, true, resolveScope, factory); - if (notAccessibleMessage != null) { - return notAccessibleMessage; + if (checkParameters) { + boolean isInLibrary = !index.isInContent(vFile); + if (superType instanceof PsiClassType classType) { + for (PsiType psiType : classType.getParameters()) { + LocalizeValue notAccessibleMessage = isSuperTypeAccessible(psiType, classes, true, resolveScope, factory); + if (notAccessibleMessage.isNotEmpty()) { + return notAccessibleMessage; + } + } + } + + for (PsiClassType type : aClass.getSuperTypes()) { + LocalizeValue notAccessibleMessage = isSuperTypeAccessible(type, classes, !isInLibrary, resolveScope, factory); + if (notAccessibleMessage.isNotEmpty()) { + return notAccessibleMessage; + } + } } - } } - - for (PsiClassType type : aClass.getSuperTypes()) { - final String notAccessibleMessage = isSuperTypeAccessible(type, classes, !isInLibrary, resolveScope, factory); - if (notAccessibleMessage != null) { - return notAccessibleMessage; - } + return LocalizeValue.empty(); + } + + @RequiredReadAction + public static HighlightInfo checkTypeParameterOverrideEquivalentMethods(PsiClass aClass, LanguageLevel level) { + if (aClass instanceof PsiTypeParameter && level.isAtLeast(LanguageLevel.JDK_1_7)) { + PsiReferenceList extendsList = aClass.getExtendsList(); + if (extendsList != null && extendsList.getReferenceElements().length > 1) { + //todo suppress erased methods which come from the same class + Collection result = checkOverrideEquivalentMethods(aClass); + if (!result.isEmpty()) { + return result.iterator().next(); + } + } } - } - } - return null; - } - - public static HighlightInfo checkTypeParameterOverrideEquivalentMethods(PsiClass aClass, LanguageLevel level) { - if (aClass instanceof PsiTypeParameter && level.isAtLeast(LanguageLevel.JDK_1_7)) { - final PsiReferenceList extendsList = aClass.getExtendsList(); - if (extendsList != null && extendsList.getReferenceElements().length > 1) { - //todo suppress erased methods which come from the same class - final Collection result = checkOverrideEquivalentMethods(aClass); - if (result != null && !result.isEmpty()) { - return result.iterator().next(); - } - } + return null; } - return null; - } } - diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightClassUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightClassUtil.java index 8b03d41281..91ae49d119 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightClassUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightClassUtil.java @@ -25,1046 +25,1032 @@ import com.intellij.java.analysis.impl.codeInsight.ClassUtil; import com.intellij.java.analysis.impl.psi.util.JavaMatchers; import com.intellij.java.analysis.impl.psi.util.PsiMatchers; +import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.impl.JavaFileType; import com.intellij.java.language.impl.codeInsight.ExceptionUtil; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.intention.QuickFixAction; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.*; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiMatcherImpl; import consulo.language.psi.util.PsiTreeUtil; -import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.module.Module; import consulo.project.Project; import consulo.util.io.FileUtil; import consulo.util.lang.Comparing; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; +import java.util.*; public class HighlightClassUtil { - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - - /** - * new ref(...) or new ref(..) { ... } where ref is abstract class - */ - @Nullable - public static HighlightInfo checkAbstractInstantiation(@Nonnull PsiJavaCodeReferenceElement ref) { - PsiElement parent = ref.getParent(); - HighlightInfo highlightInfo = null; - if (parent instanceof PsiAnonymousClass && parent.getParent() instanceof PsiNewExpression && !PsiUtilCore - .hasErrorElementChild(parent.getParent())) { - PsiAnonymousClass aClass = (PsiAnonymousClass) parent; - highlightInfo = checkClassWithAbstractMethods(aClass, ref.getTextRange()); - } - return highlightInfo; - } - - @Nullable - private static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, TextRange range) { - return checkClassWithAbstractMethods(aClass, aClass, range); - } - - @Nullable - public static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, - PsiElement implementsFixElement, - TextRange range) { - PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); - - if (abstractMethod == null) { - return null; + /** + * new ref(...) or new ref(..) { ... } where ref is abstract class + */ + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAbstractInstantiation(PsiJavaCodeReferenceElement ref) { + if (ref.getParent() instanceof PsiAnonymousClass anonymousClass + && anonymousClass.getParent() instanceof PsiNewExpression newExpr + && !PsiUtilCore.hasErrorElementChild(newExpr)) { + return checkClassWithAbstractMethods(anonymousClass, ref.getTextRange()); + } + return null; } - final PsiClass superClass = abstractMethod.getContainingClass(); - if (superClass == null) { - return null; + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkClassWithAbstractMethods(PsiClass aClass, TextRange range) { + return checkClassWithAbstractMethods(aClass, aClass, range); } - String baseClassName = HighlightUtil.formatClass(aClass, false); - String methodName = JavaHighlightUtil.formatMethod(abstractMethod); - String message = JavaErrorBundle.message(aClass instanceof PsiEnumConstantInitializer || - implementsFixElement instanceof PsiEnumConstant ? "enum.constant.should.implement.method" : "class" + - ".must.be.abstract", baseClassName, methodName, HighlightUtil.formatClass(superClass, false)); - - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range) - .descriptionAndTooltip(message).create(); - final PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass); - if (anyMethodToImplement != null) { - if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || JavaPsiFacade.getInstance - (aClass.getProject()).arePackagesTheSame(aClass, superClass)) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementMethodsFix - (implementsFixElement)); - } else { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix - (anyMethodToImplement, PsiModifier.PROTECTED, true, true)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix - (anyMethodToImplement, PsiModifier.PUBLIC, true, true)); - } - } - if (!(aClass instanceof PsiAnonymousClass) && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, - aClass.getModifierList()) == null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(aClass, - PsiModifier.ABSTRACT, true, false)); - } - return errorResult; - } - - @Nullable - public static HighlightInfo checkClassMustBeAbstract(final PsiClass aClass, final TextRange textRange) { - if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null || aClass.isEnum() && - hasEnumConstants(aClass)) { - return null; - } - return checkClassWithAbstractMethods(aClass, textRange); - } - - @Nullable - public static HighlightInfo checkInstantiationOfAbstractClass(PsiClass aClass, @Nonnull PsiElement highlightElement) { - HighlightInfo errorResult = null; - if (aClass != null && aClass.hasModifierProperty(PsiModifier.ABSTRACT) && (!(highlightElement instanceof - PsiNewExpression) || !(((PsiNewExpression) highlightElement).getType() instanceof PsiArrayType))) { - String baseClassName = aClass.getName(); - String message = JavaErrorBundle.message("abstract.cannot.be.instantiated", baseClassName); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(highlightElement) - .descriptionAndTooltip(message).create(); - final PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass); - if (!aClass.isInterface() && anyAbstractMethod == null) { - // suggest to make not abstract only if possible - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(aClass, - PsiModifier.ABSTRACT, false, false)); - } - if (anyAbstractMethod != null && highlightElement instanceof PsiNewExpression && ((PsiNewExpression) - highlightElement).getClassReference() != null) { - QuickFixAction.registerQuickFixAction(errorResult, - QUICK_FIX_FACTORY.createImplementAbstractClassMethodsFix(highlightElement)); - } - } - return errorResult; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassWithAbstractMethods(PsiClass aClass, PsiElement implementsFixElement, TextRange range) { + PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); - private static boolean hasEnumConstants(PsiClass aClass) { - PsiField[] fields = aClass.getFields(); - for (PsiField field : fields) { - if (field instanceof PsiEnumConstant) { - return true; - } - } - return false; - } + if (abstractMethod == null) { + return null; + } - @Nullable - public static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) { - if (!(aClass.getParent() instanceof PsiFile)) { - return null; - } - String qualifiedName = aClass.getQualifiedName(); - if (qualifiedName == null) { - return null; - } - int numOfClassesToFind = 2; - if (qualifiedName.contains("$")) { - qualifiedName = qualifiedName.replaceAll("\\$", "."); - numOfClassesToFind = 1; - } - PsiManager manager = aClass.getManager(); - Module module = ModuleUtilCore.findModuleForPsiElement(aClass); - if (module == null) { - return null; - } + PsiClass superClass = abstractMethod.getContainingClass(); + if (superClass == null) { + return null; + } - PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()).findClasses(qualifiedName, - GlobalSearchScope.moduleScope(module)); - if (classes.length < numOfClassesToFind) { - return null; + String baseClassName = HighlightUtil.formatClass(aClass, false); + String methodName = JavaHighlightUtil.formatMethod(abstractMethod); + String c = HighlightUtil.formatClass(superClass, false); + LocalizeValue description = aClass instanceof PsiEnumConstantInitializer || implementsFixElement instanceof PsiEnumConstant + ? JavaCompilationErrorLocalize.classMustImplementMethod(baseClassName, methodName, c) + : JavaCompilationErrorLocalize.classMustImplementMethodOrAbstract(baseClassName, methodName, c); + + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(description); + PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass); + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (anyMethodToImplement != null) { + if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) + || JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, superClass)) { + hlBuilder.registerFix(factory.createImplementMethodsFix(implementsFixElement)); + } + else { + hlBuilder.registerFix( + factory.createModifierFixBuilder(anyMethodToImplement).add(PsiModifier.PROTECTED).showContainingClass().create() + ); + hlBuilder.registerFix( + factory.createModifierFixBuilder(anyMethodToImplement).add(PsiModifier.PUBLIC).showContainingClass().create() + ); + } + } + if (!(aClass instanceof PsiAnonymousClass) + && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) { + hlBuilder.registerFix(factory.createModifierFixBuilder(aClass).add(PsiModifier.ABSTRACT).create()); + } + return hlBuilder; } - String dupFileName = null; - for (PsiClass dupClass : classes) { - // do not use equals - if (dupClass != aClass) { - VirtualFile file = dupClass.getContainingFile().getVirtualFile(); - if (file != null && manager.isInProject(dupClass)) { - dupFileName = FileUtil.toSystemDependentName(file.getPath()); - break; - } - } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassMustBeAbstract(PsiClass aClass, TextRange textRange) { + if (aClass.isAbstract() || aClass.getRBrace() == null || aClass.isEnum() && hasEnumConstants(aClass)) { + return null; + } + return checkClassWithAbstractMethods(aClass, textRange); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkInstantiationOfAbstractClass(PsiClass aClass, PsiElement highlightElement) { + if (aClass != null && aClass.isAbstract() + && !(highlightElement instanceof PsiNewExpression newExpr && newExpr.getType() instanceof PsiArrayType)) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(highlightElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.instantiationAbstract(aClass.getName())); + PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass); + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (!aClass.isInterface() && anyAbstractMethod == null) { + // suggest to make not abstract only if possible + hlBuilder.registerFix(factory.createModifierFixBuilder(aClass).remove(PsiModifier.ABSTRACT).create()); + } + if (anyAbstractMethod != null && highlightElement instanceof PsiNewExpression newExpr && newExpr.getClassReference() != null) { + hlBuilder.registerFix(factory.createImplementAbstractClassMethodsFix(highlightElement)); + } + return hlBuilder; + } + return null; } - if (dupFileName == null) { - return null; + + private static boolean hasEnumConstants(PsiClass aClass) { + PsiField[] fields = aClass.getFields(); + for (PsiField field : fields) { + if (field instanceof PsiEnumConstant) { + return true; + } + } + return false; } - String message = JavaErrorBundle.message("duplicate.class.in.other.file", dupFileName); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(message) - .create(); - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) { + if (!(aClass.getParent() instanceof PsiFile)) { + return null; + } + String qualifiedName = aClass.getQualifiedName(); + if (qualifiedName == null) { + return null; + } + int numOfClassesToFind = 2; + if (qualifiedName.contains("$")) { + qualifiedName = qualifiedName.replaceAll("\\$", "."); + numOfClassesToFind = 1; + } + PsiManager manager = aClass.getManager(); + Module module = aClass.getModule(); + if (module == null) { + return null; + } - @Nullable - public static HighlightInfo checkDuplicateNestedClass(PsiClass aClass) { - if (aClass == null) { - return null; - } - PsiElement parent = aClass; - if (aClass.getParent() instanceof PsiDeclarationStatement) { - parent = aClass.getParent(); - } - String name = aClass.getName(); - if (name == null) { - return null; - } - boolean duplicateFound = false; - boolean checkSiblings = true; - while (parent != null) { - if (parent instanceof PsiFile) { - break; - } - PsiElement element = checkSiblings ? parent.getPrevSibling() : null; - if (element == null) { - element = parent.getParent(); - // JLS 14.3: - // The name of a local class C may not be redeclared - // as a local class of the directly enclosing method, constructor, or initializer block within the - // scope of C - // , or a compile-time error occurs. - // However, a local class declaration may be shadowed (?6.3.1) - // anywhere inside a class declaration nested within the local class declaration's scope. - if (element instanceof PsiMethod || element instanceof PsiClass || - element instanceof PsiCodeBlock && element.getParent() instanceof PsiClassInitializer) { - checkSiblings = false; - } - } - parent = element; - - if (element instanceof PsiDeclarationStatement) { - element = PsiTreeUtil.getChildOfType(element, PsiClass.class); - } - if (element instanceof PsiClass && name.equals(((PsiClass) element).getName())) { - duplicateFound = true; - break; - } - } + PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()) + .findClasses(qualifiedName, GlobalSearchScope.moduleScope(module)); + if (classes.length < numOfClassesToFind) { + return null; + } + String dupFileName = null; + for (PsiClass dupClass : classes) { + // do not use equals + if (dupClass != aClass) { + VirtualFile file = dupClass.getContainingFile().getVirtualFile(); + if (file != null && manager.isInProject(dupClass)) { + dupFileName = FileUtil.toSystemDependentName(file.getPath()); + break; + } + } + } + if (dupFileName == null) { + return null; + } - if (duplicateFound) { - String message = JavaErrorBundle.message("duplicate.class", name); - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip - (message).create(); - } - return null; - } - - @Nullable - public static HighlightInfo checkPublicClassInRightFile(PsiClass aClass) { - PsiFile containingFile = aClass.getContainingFile(); - if (aClass.getParent() != containingFile || !aClass.hasModifierProperty(PsiModifier.PUBLIC) || !(containingFile instanceof PsiJavaFile)) { - return null; - } - PsiJavaFile file = (PsiJavaFile) containingFile; - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null || aClass.getName().equals(virtualFile.getNameWithoutExtension())) { - return null; - } - String message = JavaErrorBundle.message("public.class.should.be.named.after.file", aClass.getName()); - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR). - range(aClass, range.getStartOffset(), range.getEndOffset()). - descriptionAndTooltip(message).create(); - PsiModifierList psiModifierList = aClass.getModifierList(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(psiModifierList, - PsiModifier.PUBLIC, false, false)); - PsiClass[] classes = file.getClasses(); - if (classes.length > 1) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMoveClassToSeparateFileFix - (aClass)); - } - for (PsiClass otherClass : classes) { - if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) && - otherClass.hasModifierProperty(PsiModifier.PUBLIC) && - otherClass.getName().equals(virtualFile.getNameWithoutExtension())) { - return errorResult; - } - } - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createRenameFileFix(aClass.getName() + - JavaFileType.DOT_DEFAULT_EXTENSION)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createRenameElementFix(aClass)); - return errorResult; - } - - @Nullable - public static HighlightInfo checkClassAndPackageConflict(@Nonnull PsiClass aClass) { - String name = aClass.getQualifiedName(); - - if (JavaClassNames.DEFAULT_PACKAGE.equals(name)) { - String message = JavaErrorBundle.message("class.clashes.with.package", name); - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message) - .create(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classDuplicateInOtherFile(dupFileName)) + .create(); } - PsiElement file = aClass.getParent(); - if (file instanceof PsiJavaFile && !((PsiJavaFile) file).getPackageName().isEmpty()) { - PsiElement directory = file.getParent(); - if (directory instanceof PsiDirectory) { - String simpleName = aClass.getName(); - PsiDirectory subDirectory = ((PsiDirectory) directory).findSubdirectory(simpleName); - if (subDirectory != null && simpleName.equals(subDirectory.getName())) { - String message = JavaErrorBundle.message("class.clashes.with.package", name); - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip - (message).create(); - } - } - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkDuplicateNestedClass(PsiClass aClass) { + if (aClass == null) { + return null; + } + PsiElement parent = aClass; + if (aClass.getParent() instanceof PsiDeclarationStatement) { + parent = aClass.getParent(); + } + String name = aClass.getName(); + if (name == null) { + return null; + } + boolean duplicateFound = false; + boolean checkSiblings = true; + while (parent != null) { + if (parent instanceof PsiFile) { + break; + } + PsiElement element = checkSiblings ? parent.getPrevSibling() : null; + if (element == null) { + element = parent.getParent(); + // JLS 14.3: + // The name of a local class C may not be redeclared + // as a local class of the directly enclosing method, constructor, or initializer block within the + // scope of C + // , or a compile-time error occurs. + // However, a local class declaration may be shadowed (?6.3.1) + // anywhere inside a class declaration nested within the local class declaration's scope. + if (element instanceof PsiMethod || element instanceof PsiClass || + element instanceof PsiCodeBlock && element.getParent() instanceof PsiClassInitializer) { + checkSiblings = false; + } + } + parent = element; - return null; - } + if (element instanceof PsiDeclarationStatement) { + element = PsiTreeUtil.getChildOfType(element, PsiClass.class); + } + if (element instanceof PsiClass psiClass && name.equals(psiClass.getName())) { + duplicateFound = true; + break; + } + } - @Nullable - private static HighlightInfo checkStaticFieldDeclarationInInnerClass(@Nonnull PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiField.class) == null) { - return null; + if (duplicateFound) { + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classDuplicate(name)); + } + return null; } - PsiField field = (PsiField) keyword.getParent().getParent(); - if (PsiUtilCore.hasErrorElementChild(field) || PsiUtil.isCompileTimeConstant(field)) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkPublicClassInRightFile(PsiClass aClass) { + PsiFile containingFile = aClass.getContainingFile(); + if (aClass.getParent() != containingFile || !aClass.isPublic() || !(containingFile instanceof PsiJavaFile)) { + return null; + } + PsiJavaFile file = (PsiJavaFile)containingFile; + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null || aClass.getName().equals(virtualFile.getNameWithoutExtension())) { + return null; + } + TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + PsiModifierList psiModifierList = aClass.getModifierList(); + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(aClass, range.getStartOffset(), range.getEndOffset()) + .descriptionAndTooltip(JavaErrorLocalize.publicClassShouldBeNamedAfterFile(aClass.getName())) + .registerFix(factory.createModifierFixBuilder(psiModifierList).remove(PsiModifier.PUBLIC).create()); + PsiClass[] classes = file.getClasses(); + if (classes.length > 1) { + errorResult.registerFix(factory.createMoveClassToSeparateFileFix(aClass)); + } + for (PsiClass otherClass : classes) { + if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) + && otherClass.isPublic() + && otherClass.getName().equals(virtualFile.getNameWithoutExtension())) { + return errorResult; + } + } + return errorResult.registerFix(factory.createRenameFileFix(aClass.getName() + JavaFileType.DOT_DEFAULT_EXTENSION)) + .registerFix(factory.createRenameElementFix(aClass)); } - String message = JavaErrorBundle.message("static.declaration.in.inner.class"); - HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword) - .descriptionAndTooltip(message).create(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassAndPackageConflict(PsiClass aClass) { + String name = aClass.getQualifiedName(); + + if (CommonClassNames.DEFAULT_PACKAGE.equals(name)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classClashesWithPackage(name)); + } - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(field, - PsiModifier.STATIC, false, false)); + PsiElement file = aClass.getParent(); + if (file instanceof PsiJavaFile javaFile && !javaFile.getPackageName().isEmpty() + && file.getParent() instanceof PsiDirectory directory) { + String simpleName = aClass.getName(); + PsiDirectory subDirectory = directory.findSubdirectory(simpleName); + if (subDirectory != null && simpleName.equals(subDirectory.getName())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classClashesWithPackage(name)); + } + } - PsiClass aClass = field.getContainingClass(); - if (aClass != null) { - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(aClass, - PsiModifier.STATIC, true, false)); + return null; } - return result; - } + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkStaticFieldDeclarationInInnerClass(PsiKeyword keyword) { + if (getEnclosingStaticClass(keyword, PsiField.class) == null) { + return null; + } - @Nullable - private static HighlightInfo checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) { - return null; - } - PsiMethod method = (PsiMethod) keyword.getParent().getParent(); - if (PsiUtilCore.hasErrorElementChild(method)) { - return null; - } - String message = JavaErrorBundle.message("static.declaration.in.inner.class"); - HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword) - .descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(method, - PsiModifier.STATIC, false, false)); - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix((PsiClass) keyword - .getParent().getParent().getParent(), PsiModifier.STATIC, true, false)); - return result; - } - - @Nullable - private static HighlightInfo checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) { - if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) { - return null; - } - PsiClassInitializer initializer = (PsiClassInitializer) keyword.getParent().getParent(); - if (PsiUtilCore.hasErrorElementChild(initializer)) { - return null; - } - String message = JavaErrorBundle.message("static.declaration.in.inner.class"); - HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword) - .descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(initializer, - PsiModifier.STATIC, false, false)); - PsiClass owner = (PsiClass) keyword.getParent().getParent().getParent(); - QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(owner, - PsiModifier.STATIC, true, false)); - return result; - } - - private static PsiElement getEnclosingStaticClass(@Nonnull PsiKeyword keyword, @Nonnull Class parentClass) { - return new PsiMatcherImpl(keyword).dot(PsiMatchers.hasText(PsiModifier.STATIC)).parent(PsiMatchers.hasClass - (PsiModifierList.class)).parent(PsiMatchers.hasClass(parentClass)).parent(PsiMatchers.hasClass - (PsiClass.class)).dot(JavaMatchers.hasModifier(PsiModifier.STATIC, false)).parent(PsiMatchers.hasClass - (PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, - PsiEnumConstant.class)).getElement(); - } - - @Nullable - private static HighlightInfo checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) { - // keyword points to 'class' or 'interface' or 'enum' - if (new PsiMatcherImpl(keyword).parent(PsiMatchers.hasClass(PsiClass.class)).dot(JavaMatchers.hasModifier - (PsiModifier.STATIC, true)).parent(PsiMatchers.hasClass(PsiClass.class)).dot(JavaMatchers.hasModifier - (PsiModifier.STATIC, false)).parent(PsiMatchers.hasClass(PsiClass.class, - PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class)).getElement() == null) { - return null; - } + PsiField field = (PsiField)keyword.getParent().getParent(); + if (PsiUtilCore.hasErrorElementChild(field) || PsiUtil.isCompileTimeConstant(field)) { + return null; + } - PsiClass aClass = (PsiClass) keyword.getParent(); - if (PsiUtilCore.hasErrorElementChild(aClass)) { - return null; - } + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(keyword) + .descriptionAndTooltip(JavaErrorLocalize.staticDeclarationInInnerClass()) + .registerFix(factory.createModifierFixBuilder(field).remove(PsiModifier.STATIC).create()); - // highlight 'static' keyword if any, or class or interface if not - PsiElement context = null; - PsiModifierList modifierList = aClass.getModifierList(); - if (modifierList != null) { - for (PsiElement element : modifierList.getChildren()) { - if (Comparing.equal(element.getText(), PsiModifier.STATIC)) { - context = element; - break; + PsiClass aClass = field.getContainingClass(); + if (aClass != null) { + hlBuilder.registerFix(factory.createModifierFixBuilder(aClass).add(PsiModifier.STATIC).create()); } - } - } - TextRange range = context != null ? context.getTextRange() : HighlightNamesUtil.getClassDeclarationTextRange - (aClass); - String message = JavaErrorBundle.message("static.declaration.in.inner.class"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range) - .descriptionAndTooltip(message).create(); - if (context != keyword) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(aClass, - PsiModifier.STATIC, false, false)); - } - PsiClass containingClass = aClass.getContainingClass(); - if (containingClass != null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(containingClass, - PsiModifier.STATIC, true, false)); - } - return info; - } - - @Nullable - public static HighlightInfo checkStaticDeclarationInInnerClass(PsiKeyword keyword) { - HighlightInfo errorResult = checkStaticFieldDeclarationInInnerClass(keyword); - if (errorResult != null) { - return errorResult; - } - errorResult = checkStaticMethodDeclarationInInnerClass(keyword); - if (errorResult != null) { - return errorResult; - } - errorResult = checkStaticClassDeclarationInInnerClass(keyword); - if (errorResult != null) { - return errorResult; - } - errorResult = checkStaticInitializerDeclarationInInnerClass(keyword); - if (errorResult != null) { - return errorResult; - } - return null; - } - - @Nullable - public static HighlightInfo checkExtendsAllowed(PsiReferenceList list) { - if (list.getParent() instanceof PsiClass) { - PsiClass aClass = (PsiClass) list.getParent(); - if (aClass.isEnum()) { - boolean isExtends = list.equals(aClass.getExtendsList()); - if (isExtends) { - String description = JavaErrorBundle.message("extends.after.enum"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip - (description).create(); - } - } - } - return null; - } - - @Nullable - public static HighlightInfo checkImplementsAllowed(PsiReferenceList list) { - if (list.getParent() instanceof PsiClass) { - PsiClass aClass = (PsiClass) list.getParent(); - if (aClass.isInterface()) { - boolean isImplements = list.equals(aClass.getImplementsList()); - if (isImplements) { - String description = JavaErrorBundle.message("implements.after.interface"); - HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list) - .descriptionAndTooltip(description).create(); - final PsiClassType[] referencedTypes = list.getReferencedTypes(); - if (referencedTypes.length > 0) { - QuickFixAction.registerQuickFixAction(result, - QUICK_FIX_FACTORY.createChangeExtendsToImplementsFix(aClass, referencedTypes[0])); - } - return result; - } - } - } - return null; - } - - @Nullable - public static HighlightInfo checkExtendsClassAndImplementsInterface(PsiReferenceList referenceList, - JavaResolveResult resolveResult, - PsiJavaCodeReferenceElement ref) { - PsiClass aClass = (PsiClass) referenceList.getParent(); - boolean isImplements = referenceList.equals(aClass.getImplementsList()); - boolean isInterface = aClass.isInterface(); - if (isInterface && isImplements) { - return null; - } - boolean mustBeInterface = isImplements || isInterface; - HighlightInfo errorResult = null; - PsiClass extendFrom = (PsiClass) resolveResult.getElement(); - if (extendFrom.isInterface() != mustBeInterface) { - String message = JavaErrorBundle.message(mustBeInterface ? "interface.expected" : "no.interface" + - ".expected"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip - (message).create(); - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(ref); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createChangeExtendsToImplementsFix - (aClass, type)); - } - return errorResult; - } - - @Nullable - public static HighlightInfo checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) { - HighlightInfo errorResult = null; - if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) { - String message = JavaErrorBundle.message("inheritance.from.final.class", superClass.getQualifiedName()); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight) - .descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(superClass, - PsiModifier.FINAL, false, false)); - } - return errorResult; - } - - @Nullable - public static HighlightInfo checkAnonymousInheritFinal(PsiNewExpression expression) { - PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class); - if (aClass == null) { - return null; - } - PsiClassType baseClassReference = aClass.getBaseClassType(); - PsiClass baseClass = baseClassReference.resolve(); - if (baseClass == null) { - return null; - } - return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference()); - } - - @Nullable - private static String checkDefaultConstructorThrowsException(PsiMethod constructor, - @Nonnull PsiClassType[] handledExceptions) { - PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes(); - List exceptions = new ArrayList(); - for (PsiClassType referencedType : referencedTypes) { - if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, - handledExceptions)) { - exceptions.add(referencedType); - } - } - if (!exceptions.isEmpty()) { - return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions); - } - return null; - } - - @Nullable - public static HighlightInfo checkClassDoesNotCallSuperConstructorOrHandleExceptions(@Nonnull PsiClass aClass, - RefCountHolder refCountHolder, - @Nonnull PsiResolveHelper resolveHelper) { - if (aClass.isEnum()) { - return null; - } - // check only no-ctr classes. Problem with specific constructor will be highlighted inside it - if (aClass.getConstructors().length != 0) { - return null; - } - // find no-args base class ctr - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, - PsiClassType.EMPTY_ARRAY); - } - - public static HighlightInfo checkBaseClassDefaultConstructorProblem(@Nonnull PsiClass aClass, - RefCountHolder refCountHolder, - @Nonnull PsiResolveHelper resolveHelper, - @Nonnull TextRange range, - @Nonnull PsiClassType[] handledExceptions) { - if (aClass instanceof PsiAnonymousClass) { - return null; - } - PsiClass baseClass = aClass.getSuperClass(); - if (baseClass == null) { - return null; - } - PsiMethod[] constructors = baseClass.getConstructors(); - if (constructors.length == 0) { - return null; - } - for (PsiMethod constructor : constructors) { - if (resolveHelper.isAccessible(constructor, aClass, null)) { - if (constructor.getParameterList().getParametersCount() == 0 || constructor.getParameterList() - .getParametersCount() == 1 && constructor.isVarArgs()) { - // it is an error if base ctr throws exceptions - String description = checkDefaultConstructorThrowsException(constructor, handledExceptions); - if (description != null) { - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range) - .descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, - QUICK_FIX_FACTORY.createCreateConstructorMatchingSuperFix(aClass)); - return info; - } - if (refCountHolder != null) { - refCountHolder.registerLocallyReferenced(constructor); - } - return null; - } - } + return hlBuilder; } - String description = JavaErrorBundle.message("no.default.constructor.available", - HighlightUtil.formatClass(baseClass)); + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword, LanguageLevel languageLevel) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_16)) { + return null; + } - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range) - .descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createCreateConstructorMatchingSuperFix(aClass)); + if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) { + return null; + } + PsiMethod method = (PsiMethod)keyword.getParent().getParent(); + if (PsiUtilCore.hasErrorElementChild(method)) { + return null; + } + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(keyword) + .descriptionAndTooltip(JavaErrorLocalize.staticDeclarationInInnerClass()) + .registerFix(factory.createModifierFixBuilder(method).remove(PsiModifier.STATIC).create()) + .registerFix(factory.createModifierFixBuilder((PsiClass)method.getParent()).add(PsiModifier.STATIC).create()); + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) { + if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) { + return null; + } + PsiClassInitializer initializer = (PsiClassInitializer)keyword.getParent().getParent(); + if (PsiUtilCore.hasErrorElementChild(initializer)) { + return null; + } + PsiClass owner = (PsiClass)initializer.getParent(); + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(keyword) + .descriptionAndTooltip(JavaErrorLocalize.staticDeclarationInInnerClass()) + .registerFix(factory.createModifierFixBuilder(initializer).remove(PsiModifier.STATIC).create()) + .registerFix(factory.createModifierFixBuilder(owner).add(PsiModifier.STATIC).create()); + } + + private static PsiElement getEnclosingStaticClass(PsiKeyword keyword, Class parentClass) { + return new PsiMatcherImpl(keyword) + .dot(PsiMatchers.hasText(PsiModifier.STATIC)) + .parent(PsiMatchers.hasClass(PsiModifierList.class)) + .parent(PsiMatchers.hasClass(parentClass)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(JavaMatchers.hasNoModifier(PsiModifier.STATIC)) + .parent(PsiMatchers.hasClass(PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class)) + .getElement(); + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) { + // keyword points to 'class' or 'interface' or 'enum' + if (new PsiMatcherImpl(keyword) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(JavaMatchers.hasModifier(PsiModifier.STATIC)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .dot(JavaMatchers.hasNoModifier(PsiModifier.STATIC)) + .parent(PsiMatchers.hasClass(PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class)) + .getElement() == null) { + return null; + } - return info; - } + PsiClass aClass = (PsiClass)keyword.getParent(); + if (PsiUtilCore.hasErrorElementChild(aClass)) { + return null; + } - @Nullable - public static HighlightInfo checkInterfaceCannotBeLocal(PsiClass aClass) { - if (PsiUtil.isLocalClass(aClass)) { - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - String description = JavaErrorBundle.message("interface.cannot.be.local"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip - (description).create(); - } - return null; - } - - @Nullable - public static HighlightInfo checkCyclicInheritance(PsiClass aClass) { - PsiClass circularClass = getCircularClass(aClass, new HashSet()); - if (circularClass != null) { - String description = JavaErrorBundle.message("cyclic.inheritance", - HighlightUtil.formatClass(circularClass)); - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip - (description).create(); + // highlight 'static' keyword if any, or class or interface if not + PsiElement context = null; + PsiModifierList modifierList = aClass.getModifierList(); + if (modifierList != null) { + for (PsiElement element : modifierList.getChildren()) { + if (Comparing.equal(element.getText(), PsiModifier.STATIC)) { + context = element; + break; + } + } + } + TextRange range = context != null + ? context.getTextRange() + : HighlightNamesUtil.getClassDeclarationTextRange(aClass); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(JavaErrorLocalize.staticDeclarationInInnerClass()); + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (context != keyword) { + hlBuilder.registerFix(factory.createModifierFixBuilder(aClass).remove(PsiModifier.STATIC).create()); + } + PsiClass containingClass = aClass.getContainingClass(); + if (containingClass != null) { + hlBuilder.registerFix(factory.createModifierFixBuilder(containingClass).add(PsiModifier.STATIC).create()); + } + return hlBuilder; } - return null; - } - @Nullable - public static PsiClass getCircularClass(PsiClass aClass, Collection usedClasses) { - if (usedClasses.contains(aClass)) { - return aClass; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkStaticDeclarationInInnerClass(PsiKeyword keyword, LanguageLevel languageLevel) { + HighlightInfo.Builder errorResult = checkStaticFieldDeclarationInInnerClass(keyword); + if (errorResult == null) { + errorResult = checkStaticMethodDeclarationInInnerClass(keyword, languageLevel); + } + if (errorResult == null) { + errorResult = checkStaticClassDeclarationInInnerClass(keyword); + } + if (errorResult == null) { + errorResult = checkStaticInitializerDeclarationInInnerClass(keyword); + } + return errorResult; } - try { - usedClasses.add(aClass); - PsiClass[] superTypes = aClass.getSupers(); - for (PsiElement superType : superTypes) { - while (superType instanceof PsiClass) { - if (!JavaClassNames.JAVA_LANG_OBJECT.equals(((PsiClass) superType).getQualifiedName())) { - PsiClass circularClass = getCircularClass((PsiClass) superType, usedClasses); - if (circularClass != null) { - return circularClass; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkExtendsAllowed(PsiReferenceList list) { + if (list.getParent() instanceof PsiClass aClass && aClass.isEnum()) { + boolean isExtends = list.equals(aClass.getExtendsList()); + if (isExtends) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaErrorLocalize.extendsAfterEnum()); } - } - // check class qualifier - superType = superType.getParent(); } - } - } finally { - usedClasses.remove(aClass); - } - return null; - } - - @Nullable - public static HighlightInfo checkExtendsDuplicate(PsiJavaCodeReferenceElement element, - PsiElement resolved, - @Nonnull PsiFile containingFile) { - if (!(element.getParent() instanceof PsiReferenceList)) { - return null; - } - PsiReferenceList list = (PsiReferenceList) element.getParent(); - if (!(list.getParent() instanceof PsiClass)) { - return null; - } - if (!(resolved instanceof PsiClass)) { - return null; - } - PsiClass aClass = (PsiClass) resolved; - PsiClassType[] referencedTypes = list.getReferencedTypes(); - int dupCount = 0; - PsiManager manager = containingFile.getManager(); - for (PsiClassType referencedType : referencedTypes) { - PsiClass resolvedElement = referencedType.resolve(); - if (resolvedElement != null && manager.areElementsEquivalent(resolvedElement, aClass)) { - dupCount++; - } - } - if (dupCount > 1) { - String description = JavaErrorBundle.message("duplicate.class", HighlightUtil.formatClass(aClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip - (description).create(); - } - return null; - } - - @Nullable - public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) { - PsiFile file = aClass.getContainingFile(); - if (!(file instanceof PsiJavaFile)) { - return null; + return null; } - PsiJavaFile javaFile = (PsiJavaFile) file; - // check only top-level classes conflicts - if (aClass.getParent() != javaFile) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkImplementsAllowed(PsiReferenceList list) { + if (list.getParent() instanceof PsiClass aClass && aClass.isInterface()) { + boolean isImplements = list.equals(aClass.getImplementsList()); + if (isImplements) { + HighlightInfo.Builder result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaErrorLocalize.implementsAfterInterface()); + PsiClassType[] referencedTypes = list.getReferencedTypes(); + if (referencedTypes.length > 0) { + result.registerFix(QuickFixFactory.getInstance().createChangeExtendsToImplementsFix(aClass, referencedTypes[0])); + } + return result; + } + } + return null; } - PsiImportList importList = javaFile.getImportList(); - if (importList == null) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkExtendsClassAndImplementsInterface( + PsiReferenceList referenceList, + JavaResolveResult resolveResult, + PsiJavaCodeReferenceElement ref + ) { + PsiClass aClass = (PsiClass)referenceList.getParent(); + boolean isImplements = referenceList.equals(aClass.getImplementsList()); + boolean isInterface = aClass.isInterface(); + if (isInterface && isImplements) { + return null; + } + boolean mustBeInterface = isImplements || isInterface; + PsiClass extendFrom = (PsiClass)resolveResult.getElement(); + if (extendFrom.isInterface() != mustBeInterface) { + LocalizeValue message = mustBeInterface + ? JavaErrorLocalize.interfaceExpected() + : JavaCompilationErrorLocalize.classExtendsInterface(); + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(ref); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(message) + .registerFix(QuickFixFactory.getInstance().createChangeExtendsToImplementsFix(aClass, type)); + } + return null; } - PsiImportStatementBase[] importStatements = importList.getAllImportStatements(); - for (PsiImportStatementBase importStatement : importStatements) { - if (importStatement.isOnDemand()) { - continue; - } - PsiElement resolved = importStatement.resolve(); - if (resolved instanceof PsiClass && !resolved.equals(aClass) && Comparing.equal(aClass.getName(), - ((PsiClass) resolved).getName(), true)) { - String description = JavaErrorBundle.message("class.already.imported", - HighlightUtil.formatClass(aClass, false)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight) - .descriptionAndTooltip(description).create(); - } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) { + if (superClass.isFinal() || superClass.isEnum()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaErrorLocalize.inheritanceFromFinalClass(superClass.getQualifiedName())) + .registerFix(QuickFixFactory.getInstance().createModifierFixBuilder(superClass).remove(PsiModifier.FINAL).create()); + } + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkClassExtendsOnlyOneClass(PsiReferenceList list) { - PsiClassType[] referencedTypes = list.getReferencedTypes(); - PsiElement parent = list.getParent(); - if (!(parent instanceof PsiClass)) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAnonymousInheritFinal(PsiNewExpression expression) { + PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class); + if (aClass == null) { + return null; + } + PsiClassType baseClassReference = aClass.getBaseClassType(); + PsiClass baseClass = baseClassReference.resolve(); + if (baseClass == null) { + return null; + } + return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference()); } - PsiClass aClass = (PsiClass) parent; - if (!aClass.isInterface() && referencedTypes.length > 1 && aClass.getExtendsList() == list) { - String description = JavaErrorBundle.message("class.cannot.extend.multiple.classes"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip - (description).create(); + private static List collectUnhandledExceptions(PsiMethod constructor, PsiClassType[] handledExceptions) { + PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes(); + List exceptions = new ArrayList<>(); + for (PsiClassType referencedType : referencedTypes) { + if (!ExceptionUtil.isUncheckedException(referencedType) + && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) { + exceptions.add(referencedType); + } + } + return exceptions; } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassDoesNotCallSuperConstructorOrHandleExceptions( + PsiClass aClass, + RefCountHolder refCountHolder, + PsiResolveHelper resolveHelper + ) { + if (aClass.isEnum()) { + return null; + } + // check only no-ctr classes. Problem with specific constructor will be highlighted inside it + if (aClass.getConstructors().length != 0) { + return null; + } + // find no-args base class ctr + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkBaseClassDefaultConstructorProblem( + PsiClass aClass, + RefCountHolder refCountHolder, + PsiResolveHelper resolveHelper, + TextRange range, + PsiClassType[] handledExceptions + ) { + if (aClass instanceof PsiAnonymousClass) { + return null; + } + PsiClass baseClass = aClass.getSuperClass(); + if (baseClass == null) { + return null; + } + PsiMethod[] constructors = baseClass.getConstructors(); + if (constructors.length == 0) { + return null; + } - @Nullable - public static HighlightInfo checkThingNotAllowedInInterface(PsiElement element, PsiClass aClass) { - if (aClass == null || !aClass.isInterface()) { - return null; - } - String description = JavaErrorBundle.message("not.allowed.in.interface"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip - (description).create(); - } - - @Nullable - public static HighlightInfo checkQualifiedNew(PsiNewExpression expression, PsiType type, PsiClass aClass) { - PsiExpression qualifier = expression.getQualifier(); - if (qualifier == null) { - return null; - } - if (type instanceof PsiArrayType) { - String description = JavaErrorBundle.message("invalid.qualified.new"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) - .descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveNewQualifierFix(expression, - null)); - return info; - } - HighlightInfo info = null; - if (aClass != null) { - if (aClass.hasModifierProperty(PsiModifier.STATIC)) { - String description = JavaErrorBundle.message("qualified.new.of.static.class"); - info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip - (description).create(); - if (!aClass.isEnum()) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(aClass, - PsiModifier.STATIC, false, false)); - } - - } else if (aClass instanceof PsiAnonymousClass) { - final PsiClass baseClass = PsiUtil.resolveClassInType(((PsiAnonymousClass) aClass).getBaseClassType()); - if (baseClass != null && baseClass.isInterface()) { - info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) - .descriptionAndTooltip("Anonymous class implements interface; cannot have qualifier for " + "new").create(); - } - } - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveNewQualifierFix(expression, - aClass)); - } - return info; - } - - - /** - * class c extends foreign.inner {} - * - * @param extendRef points to the class in the extends list - * @param resolved extendRef resolved - */ - @Nullable - public static HighlightInfo checkClassExtendsForeignInnerClass(final PsiJavaCodeReferenceElement extendRef, - final PsiElement resolved) { - PsiElement parent = extendRef.getParent(); - if (!(parent instanceof PsiReferenceList)) { - return null; - } - PsiElement grand = parent.getParent(); - if (!(grand instanceof PsiClass)) { - return null; + for (PsiMethod constructor : constructors) { + if (resolveHelper.isAccessible(constructor, aClass, null)) { + int parametersCount = constructor.getParameterList().getParametersCount(); + if (parametersCount == 0 || parametersCount == 1 && constructor.isVarArgs()) { + // it is an error if base ctr throws exceptions + List exceptions = collectUnhandledExceptions(constructor, handledExceptions); + if (!exceptions.isEmpty()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(HighlightUtil.getUnhandledExceptionsDescriptor(exceptions)) + .registerFix(QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(aClass)); + } + if (refCountHolder != null) { + refCountHolder.registerLocallyReferenced(constructor); + } + return null; + } + } + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(JavaErrorLocalize.noDefaultConstructorAvailable(HighlightUtil.formatClass(baseClass))) + .registerFix(QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(aClass)); } - final PsiClass aClass = (PsiClass) grand; - final PsiClass containerClass; - if (aClass instanceof PsiTypeParameter) { - final PsiTypeParameterListOwner owner = ((PsiTypeParameter) aClass).getOwner(); - if (!(owner instanceof PsiClass)) { + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkInterfaceCannotBeLocal(PsiClass aClass) { + if (PsiUtil.isLocalClass(aClass)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(JavaErrorLocalize.interfaceCannotBeLocal()); + } return null; - } - containerClass = (PsiClass) owner; - } else { - containerClass = aClass; - } - if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) { - return null; - } - if (!(resolved instanceof PsiClass)) { - String description = JavaErrorBundle.message("class.name.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip - (description).create(); - } - final HighlightInfo[] infos = new HighlightInfo[1]; - extendRef.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitElement(PsiElement element) { - if (infos[0] != null) { - return; - } - super.visitElement(element); - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - final PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiClass) { - final PsiClass base = (PsiClass) resolve; - final PsiClass baseClass = base.getContainingClass(); - if (baseClass != null && base.hasModifierProperty(PsiModifier.PRIVATE) && baseClass == - containerClass) { - String description = JavaErrorBundle.message("private.symbol", - HighlightUtil.formatClass(base), HighlightUtil.formatClass(baseClass)); - infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef) - .descriptionAndTooltip(description).create(); - return; - } - - // must be inner class - if (!PsiUtil.isInnerClass(base)) { - return; - } - - if (resolve == resolved && baseClass != null && (!PsiTreeUtil.isAncestor(baseClass, extendRef, - true) || aClass.hasModifierProperty(PsiModifier.STATIC)) && - !InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, - !aClass.hasModifierProperty(PsiModifier.STATIC), - true) && !qualifiedNewCalledInConstructors(aClass)) { - String description = JavaErrorBundle.message("no.enclosing.instance.in.scope", - HighlightUtil.formatClass(baseClass)); - infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef) - .descriptionAndTooltip(description).create(); - } - } - } - }); - - return infos[0]; - } - - /** - * 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances - */ - private static boolean qualifiedNewCalledInConstructors(final PsiClass aClass) { - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - return false; } - for (PsiMethod constructor : constructors) { - PsiCodeBlock body = constructor.getBody(); - if (body == null) { - return false; - } - PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) { - return false; - } - PsiStatement firstStatement = statements[0]; - if (!(firstStatement instanceof PsiExpressionStatement)) { - return false; - } - PsiExpression expression = ((PsiExpressionStatement) firstStatement).getExpression(); - if (!RefactoringChangeUtil.isSuperOrThisMethodCall(expression)) { - return false; - } - PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) expression; - if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) { - continue; - } - PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); - PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression - .getQualifierExpression()); - //If the class instance creation expression is qualified, then the immediately - //enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName, - //otherwise aClass needs to be a member of a class enclosing the class in which the class instance - // creation expression appears - //already excluded by InheritanceUtil.hasEnclosingInstanceInScope - if (qualifierExpression == null) { - return false; - } - } - return true; - } - - @Nullable - public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiNewExpression expression, - PsiType type, - PsiClass aClass) { - if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) { - return null; - } - if (aClass == null) { - return null; - } - if (aClass instanceof PsiAnonymousClass) { - aClass = ((PsiAnonymousClass) aClass).getBaseClassType().resolve(); - if (aClass == null) { + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCyclicInheritance(PsiClass aClass) { + PsiClass circularClass = getCircularClass(aClass, new HashSet<>()); + if (circularClass != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classCyclicInheritance(HighlightUtil.formatClass(circularClass))); + } return null; - } } - PsiExpression qualifier = expression.getQualifier(); - return checkCreateInnerClassFromStaticContext(expression, qualifier, aClass); - } - - @Nullable - public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiElement element, - @Nullable PsiExpression qualifier, - PsiClass aClass) { - PsiElement placeToSearchEnclosingFrom; - if (qualifier != null) { - PsiType qType = qualifier.getType(); - placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qType); - } else { - placeToSearchEnclosingFrom = element; + @Nullable + public static PsiClass getCircularClass(PsiClass aClass, Collection usedClasses) { + if (usedClasses.contains(aClass)) { + return aClass; + } + try { + usedClasses.add(aClass); + PsiClass[] superTypes = aClass.getSupers(); + for (PsiElement superType : superTypes) { + while (superType instanceof PsiClass) { + if (!CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)superType).getQualifiedName())) { + PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses); + if (circularClass != null) { + return circularClass; + } + } + // check class qualifier + superType = superType.getParent(); + } + } + } + finally { + usedClasses.remove(aClass); + } + return null; } - return checkCreateInnerClassFromStaticContext(element, placeToSearchEnclosingFrom, aClass); - } - - @Nullable - public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiElement element, - PsiElement placeToSearchEnclosingFrom, - PsiClass aClass) { - if (aClass == null || !PsiUtil.isInnerClass(aClass)) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkExtendsDuplicate( + PsiJavaCodeReferenceElement element, + PsiElement resolved, + PsiFile containingFile + ) { + if (!(element.getParent() instanceof PsiReferenceList)) { + return null; + } + PsiReferenceList list = (PsiReferenceList)element.getParent(); + if (!(list.getParent() instanceof PsiClass)) { + return null; + } + if (!(resolved instanceof PsiClass)) { + return null; + } + PsiClass aClass = (PsiClass)resolved; + PsiClassType[] referencedTypes = list.getReferencedTypes(); + int dupCount = 0; + PsiManager manager = containingFile.getManager(); + for (PsiClassType referencedType : referencedTypes) { + PsiClass resolvedElement = referencedType.resolve(); + if (resolvedElement != null && manager.areElementsEquivalent(resolvedElement, aClass)) { + dupCount++; + } + } + if (dupCount > 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classDuplicate(HighlightUtil.formatClass(aClass))); + } + return null; } - PsiClass outerClass = aClass.getContainingClass(); - if (outerClass == null) { - return null; + + @Nullable + @RequiredReadAction + public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) { + PsiFile file = aClass.getContainingFile(); + if (!(file instanceof PsiJavaFile)) { + return null; + } + PsiJavaFile javaFile = (PsiJavaFile)file; + // check only top-level classes conflicts + if (aClass.getParent() != javaFile) { + return null; + } + PsiImportList importList = javaFile.getImportList(); + if (importList == null) { + return null; + } + PsiImportStatementBase[] importStatements = importList.getAllImportStatements(); + for (PsiImportStatementBase importStatement : importStatements) { + if (importStatement.isOnDemand()) { + continue; + } + if (importStatement.resolve() instanceof PsiClass importClass && !importClass.equals(aClass) + && Objects.equals(aClass.getName(), importClass.getName())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classAlreadyImported(HighlightUtil.formatClass(aClass, false))) + .create(); + } + } + return null; } - if (outerClass instanceof PsiSyntheticClass || InheritanceUtil.hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true, false)) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassExtendsOnlyOneClass(PsiReferenceList list) { + PsiClassType[] referencedTypes = list.getReferencedTypes(); + if (list.getParent() instanceof PsiClass aClass && !aClass.isInterface() + && referencedTypes.length > 1 && aClass.getExtendsList() == list) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classCannotExtendMultipleClasses()); + } + + return null; } - return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, element); - } - @Nullable - public static HighlightInfo checkSuperQualifierType(@Nonnull Project project, @Nonnull PsiMethodCallExpression superCall) { - if (!RefactoringChangeUtil.isSuperMethodCall(superCall)) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkThingNotAllowedInInterface(PsiMember member) { + PsiClass aClass = member.getContainingClass(); + if (aClass == null || !aClass.isInterface()) { + return null; + } + if (member instanceof PsiMethod method && method.isConstructor()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(member) + .descriptionAndTooltip(JavaCompilationErrorLocalize.interfaceConstructor()); + } else if (member instanceof PsiClassInitializer initializer) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(member) + .descriptionAndTooltip(JavaCompilationErrorLocalize.interfaceClassInitializer()); + } + return null; } - PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class); - if (ctr == null) { - return null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkQualifiedNew(PsiNewExpression expression, PsiType type, PsiClass aClass) { + PsiExpression qualifier = expression.getQualifier(); + if (qualifier == null) { + return null; + } + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (type instanceof PsiArrayType) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.newExpressionQualifiedMalformed()) + .registerFix(factory.createRemoveNewQualifierFix(expression, null)); + } + if (aClass != null) { + if (aClass.isStatic()) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.newExpressionQualifiedStaticClass()); + if (!aClass.isEnum()) { + hlBuilder.registerFix(factory.createModifierFixBuilder(aClass).remove(PsiModifier.STATIC).create()); + } + hlBuilder.registerFix(factory.createRemoveNewQualifierFix(expression, aClass)); + } + else if (aClass instanceof PsiAnonymousClass anonymousClass) { + PsiClass baseClass = PsiUtil.resolveClassInType(anonymousClass.getBaseClassType()); + if (baseClass != null && baseClass.isInterface()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(LocalizeValue.localizeTODO( + "Anonymous class implements interface; cannot have qualifier for new" + )) + .registerFix(factory.createRemoveNewQualifierFix(expression, aClass)); + } + } + } + return null; } - final PsiClass aClass = ctr.getContainingClass(); - if (aClass == null) { - return null; + + /** + * class c extends foreign.inner {} + * + * @param extendRef points to the class in the extends list + * @param resolved extendRef resolved + */ + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkClassExtendsForeignInnerClass(PsiJavaCodeReferenceElement extendRef, PsiElement resolved) { + if (!(extendRef.getParent() instanceof PsiReferenceList referenceList + && referenceList.getParent() instanceof PsiClass aClass)) { + return null; + } + PsiClass containerClass; + if (aClass instanceof PsiTypeParameter typeParam) { + if (!(typeParam.getOwner() instanceof PsiClass ownerClass)) { + return null; + } + containerClass = ownerClass; + } + else { + containerClass = aClass; + } + + if (aClass.getExtendsList() != referenceList && aClass.getImplementsList() != referenceList) { + return null; + } + if (!(resolved instanceof PsiClass)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(extendRef) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classReferenceListNameExpected()); + } + SimpleReference infos = SimpleReference.create(); + extendRef.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + if (!infos.isNull()) { + return; + } + super.visitElement(element); + } + + @Override + @RequiredReadAction + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + if (reference.resolve() instanceof PsiClass base) { + PsiClass baseClass = base.getContainingClass(); + if (baseClass != null && base.isPrivate() && baseClass == containerClass) { + infos.set( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(extendRef) + .descriptionAndTooltip(JavaErrorLocalize.privateSymbol( + HighlightUtil.formatClass(base), + HighlightUtil.formatClass(baseClass) + )) + ); + return; + } + + // must be inner class + if (!PsiUtil.isInnerClass(base)) { + return; + } + + if (base == resolved && baseClass != null + && (!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.isStatic()) + && !InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, !aClass.isStatic(), true) + && !qualifiedNewCalledInConstructors(aClass)) { + infos.set( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(extendRef) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classReferenceListNoEnclosingInstance( + HighlightUtil.formatClass(baseClass) + )) + ); + } + } + } + }); + + return infos.get(); } - PsiClass targetClass = aClass.getSuperClass(); - if (targetClass == null) { - return null; + + /** + * 15.9 Class Instance Creation Expressions | 15.9.2 Determining Enclosing Instances + */ + private static boolean qualifiedNewCalledInConstructors(PsiClass aClass) { + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) { + return false; + } + for (PsiMethod constructor : constructors) { + PsiCodeBlock body = constructor.getBody(); + if (body == null) { + return false; + } + PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) { + return false; + } + PsiStatement firstStatement = statements[0]; + if (!(firstStatement instanceof PsiExpressionStatement)) { + return false; + } + PsiExpression expression = ((PsiExpressionStatement)firstStatement).getExpression(); + if (!RefactoringChangeUtil.isSuperOrThisMethodCall(expression)) { + return false; + } + PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) { + continue; + } + PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); + PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression + .getQualifierExpression()); + //If the class instance creation expression is qualified, then the immediately + //enclosing instance of i is the object that is the value of the Primary expression or the ExpressionName, + //otherwise aClass needs to be a member of a class enclosing the class in which the class instance + // creation expression appears + //already excluded by InheritanceUtil.hasEnclosingInstanceInScope + if (qualifierExpression == null) { + return false; + } + } + return true; } - PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression(); - if (qualifier != null) { - if (PsiUtil.isInnerClass(targetClass)) { - PsiClass outerClass = targetClass.getContainingClass(); - if (outerClass != null) { - PsiClassType outerType = JavaPsiFacade.getInstance(project).getElementFactory().createType - (outerClass); - return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier); - } - } else { - String description = "'" + HighlightUtil.formatClass(targetClass) + "' is not an inner class"; - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip - (description).create(); - } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkCreateInnerClassFromStaticContext( + PsiNewExpression expression, + PsiType type, + PsiClass aClass + ) { + if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) { + return null; + } + if (aClass == null) { + return null; + } + if (aClass instanceof PsiAnonymousClass anonymousClass) { + aClass = anonymousClass.getBaseClassType().resolve(); + if (aClass == null) { + return null; + } + } + + PsiExpression qualifier = expression.getQualifier(); + return checkCreateInnerClassFromStaticContext(expression, qualifier, aClass); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkCreateInnerClassFromStaticContext( + PsiElement element, + @Nullable PsiExpression qualifier, + PsiClass aClass + ) { + PsiElement placeToSearchEnclosingFrom; + if (qualifier != null) { + PsiType qType = qualifier.getType(); + placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qType); + } + else { + placeToSearchEnclosingFrom = element; + } + return checkCreateInnerClassFromStaticContext(element, placeToSearchEnclosingFrom, aClass); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkCreateInnerClassFromStaticContext( + PsiElement element, + PsiElement placeToSearchEnclosingFrom, + PsiClass aClass + ) { + if (aClass == null || !PsiUtil.isInnerClass(aClass)) { + return null; + } + PsiClass outerClass = aClass.getContainingClass(); + if (outerClass == null) { + return null; + } + + if (outerClass instanceof PsiSyntheticClass + || InheritanceUtil.hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true, false)) { + return null; + } + return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, element); } - return null; - } - - @Nullable - public static HighlightInfo reportIllegalEnclosingUsage(PsiElement place, - @Nullable PsiClass aClass, - PsiClass outerClass, - PsiElement elementToHighlight) { - if (outerClass != null && !PsiTreeUtil.isContextAncestor(outerClass, place, false)) { - String description = JavaErrorBundle.message("is.not.an.enclosing.class", - HighlightUtil.formatClass(outerClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight) - .descriptionAndTooltip(description).create(); + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSuperQualifierType(Project project, PsiMethodCallExpression superCall) { + if (!RefactoringChangeUtil.isSuperMethodCall(superCall)) { + return null; + } + PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class); + if (ctr == null) { + return null; + } + PsiClass aClass = ctr.getContainingClass(); + if (aClass == null) { + return null; + } + PsiClass targetClass = aClass.getSuperClass(); + if (targetClass == null) { + return null; + } + PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression(); + if (qualifier != null) { + if (PsiUtil.isInnerClass(targetClass)) { + PsiClass outerClass = targetClass.getContainingClass(); + if (outerClass != null) { + PsiClassType outerType = JavaPsiFacade.getInstance(project).getElementFactory().createType(outerClass); + return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier); + } + } + else { + String description = "'" + HighlightUtil.formatClass(targetClass) + "' is not an inner class"; + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(description); + } + } + return null; } - PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass); - if (staticParent != null) { - String element = outerClass == null ? "" : HighlightUtil.formatClass(outerClass) + "." + - (place instanceof PsiSuperExpression ? PsiKeyword.SUPER : PsiKeyword.THIS); - String description = JavaErrorBundle.message("cannot.be.referenced.from.static.context", element); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range - (elementToHighlight).descriptionAndTooltip(description).create(); - // make context not static or referenced class static - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(staticParent, - PsiModifier.STATIC, false, false)); - if (aClass != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, - aClass.getModifierList()) == null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false)); - } - return highlightInfo; + + @Nullable + @RequiredReadAction + public static HighlightInfo reportIllegalEnclosingUsage( + PsiElement place, + @Nullable PsiClass aClass, + PsiClass outerClass, + PsiElement elementToHighlight + ) { + if (outerClass != null && !PsiTreeUtil.isContextAncestor(outerClass, place, false)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaErrorLocalize.isNotAnEnclosingClass(HighlightUtil.formatClass(outerClass))) + .create(); + } + PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass); + if (staticParent != null) { + String element = outerClass == null ? "" : HighlightUtil.formatClass(outerClass) + "." + + (place instanceof PsiSuperExpression ? PsiKeyword.SUPER : PsiKeyword.THIS); + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classCannotBeReferencedFromStaticContext(element)) + // make context not static or referenced class static + .registerFix(factory.createModifierFixBuilder(staticParent).remove(PsiModifier.STATIC).create()); + if (aClass != null + && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, aClass.getModifierList()) == null) { + highlightInfo.registerFix(factory.createModifierFixBuilder(aClass).add(PsiModifier.STATIC).create()); + } + return highlightInfo.create(); + } + return null; } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index eff73e9b8f..7f05e2f5d0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -15,33 +15,35 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; -import consulo.language.editor.rawHighlight.HighlightInfo; -import consulo.language.editor.rawHighlight.HighlightInfoType; -import consulo.language.editor.intention.QuickFixAction; import com.intellij.java.analysis.codeInsight.intention.QuickFixFactory; import com.intellij.java.analysis.impl.psi.controlFlow.AllVariablesControlFlowPolicy; import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.controlFlow.*; import com.intellij.java.language.impl.psi.util.JavaPsiRecordUtil; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.augment.PsiAugmentProvider; import com.intellij.java.language.psi.util.FileTypeUtils; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.application.dumb.IndexNotReadyException; import consulo.document.util.TextRange; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; +import consulo.language.ast.IElementType; +import consulo.language.editor.rawHighlight.HighlightInfo; +import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; +import consulo.language.psi.PsiManager; import consulo.language.psi.PsiReference; import consulo.language.psi.scope.LocalSearchScope; import consulo.language.psi.search.ReferencesSearch; -import consulo.language.ast.IElementType; import consulo.language.psi.util.PsiTreeUtil; -import consulo.application.util.function.Processor; +import consulo.localize.LocalizeValue; import consulo.util.collection.ContainerUtil; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -50,841 +52,935 @@ /** * @author cdr - * @since Aug 8, 2002 + * @since 2022-08-08 */ public class HighlightControlFlowUtil { - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - - private HighlightControlFlowUtil() { - } - - @Nullable - public static HighlightInfo checkMissingReturnStatement(@Nullable PsiCodeBlock body, @Nullable PsiType returnType) { - if (body == null || returnType == null || PsiType.VOID.equals(returnType.getDeepComponentType())) { - return null; - } - - // do not compute constant expressions for if() statement condition - // see JLS 14.20 Unreachable Statements - try { - ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); - if (!ControlFlowUtil.returnPresent(controlFlow)) { - PsiJavaToken rBrace = body.getRBrace(); - PsiElement context = rBrace == null ? body.getLastChild() : rBrace; - String message = JavaErrorBundle.message("missing.return.statement"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(message).create(); - PsiElement parent = body.getParent(); - if (parent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) parent; - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddReturnFix(method)); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true)); - } - return info; - } - } catch (AnalysisCanceledException ignored) { + private HighlightControlFlowUtil() { } - return null; - } - - @Nonnull - public static ControlFlow getControlFlowNoConstantEvaluate(@Nonnull PsiElement body) throws AnalysisCanceledException { - LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); - return ControlFlowFactory.getControlFlow(body, policy, ControlFlowOptions.NO_CONST_EVALUATE); - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkMissingReturnStatement(@Nullable PsiCodeBlock body, @Nullable PsiType returnType) { + if (body == null || returnType == null || PsiType.VOID.equals(returnType.getDeepComponentType())) { + return null; + } - @Nonnull - private static ControlFlow getControlFlow(@Nonnull PsiElement context) throws AnalysisCanceledException { - LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); - return ControlFlowFactory.getInstance(context.getProject()).getControlFlow(context, policy); - } + // do not compute constant expressions for if() statement condition + // see JLS 14.20 Unreachable Statements + try { + ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); + if (!ControlFlowUtil.returnPresent(controlFlow)) { + PsiJavaToken rBrace = body.getRBrace(); + HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(rBrace == null ? body.getLastChild() : rBrace) + .descriptionAndTooltip(JavaCompilationErrorLocalize.returnMissing()); + if (body.getParent() instanceof PsiMethod method) { + info.registerFix(QuickFixFactory.getInstance().createAddReturnFix(method)); + info.registerFix(QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.VOID, true)); + } + return info.create(); + } + } + catch (AnalysisCanceledException ignored) { + } - public static HighlightInfo checkUnreachableStatement(@Nullable PsiCodeBlock codeBlock) { - if (codeBlock == null) { - return null; - } - // do not compute constant expressions for if() statement condition - // see JLS 14.20 Unreachable Statements - try { - AllVariablesControlFlowPolicy policy = AllVariablesControlFlowPolicy.getInstance(); - final ControlFlow controlFlow = ControlFlowFactory.getControlFlow(codeBlock, policy, ControlFlowOptions.NO_CONST_EVALUATE); - final PsiElement unreachableStatement = ControlFlowUtil.getUnreachableStatement(controlFlow); - if (unreachableStatement != null) { - String description = JavaErrorBundle.message("unreachable.statement"); - PsiElement keyword = null; - if (unreachableStatement instanceof PsiIfStatement || - unreachableStatement instanceof PsiSwitchBlock || - unreachableStatement instanceof PsiLoopStatement) { - keyword = unreachableStatement.getFirstChild(); - } - final PsiElement element = keyword != null ? keyword : unreachableStatement; - final HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction( - info, QUICK_FIX_FACTORY.createDeleteFix(unreachableStatement, JavaQuickFixBundle.message("delete.unreachable.statement.fix.text"))); - return info; - } - } catch (AnalysisCanceledException | IndexNotReadyException e) { - // incomplete code + return null; } - return null; - } - public static boolean isFieldInitializedAfterObjectConstruction(@Nonnull PsiField field) { - if (field.hasInitializer()) { - return true; - } - final boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); - final PsiClass aClass = field.getContainingClass(); - if (aClass != null) { - // field might be assigned in the other field initializers - if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic, __ -> true)) { - return true; - } - } - final PsiClassInitializer[] initializers; - if (aClass != null) { - initializers = aClass.getInitializers(); - } else { - return false; + public static ControlFlow getControlFlowNoConstantEvaluate(PsiElement body) throws AnalysisCanceledException { + LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); + return ControlFlowFactory.getControlFlow(body, policy, ControlFlowOptions.NO_CONST_EVALUATE); } - if (isFieldInitializedInClassInitializer(field, isFieldStatic, initializers)) { - return true; + + private static ControlFlow getControlFlow(PsiElement context) throws AnalysisCanceledException { + LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); + return ControlFlowFactory.getInstance(context.getProject()).getControlFlow(context, policy); } - if (isFieldStatic) { - return false; - } else { - // instance field should be initialized at the end of the each constructor - final PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - return false; - } - nextConstructor: - for (PsiMethod constructor : constructors) { - PsiCodeBlock ctrBody = constructor.getBody(); - if (ctrBody == null) { - return false; - } - final List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); - for (PsiMethod redirectedConstructor : redirectedConstructors) { - final PsiCodeBlock body = redirectedConstructor.getBody(); - if (body != null && variableDefinitelyAssignedIn(field, body)) { - continue nextConstructor; - } - } - if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody)) { - continue; + @RequiredReadAction + public static HighlightInfo.Builder checkUnreachableStatement(@Nullable PsiCodeBlock codeBlock) { + if (codeBlock == null) { + return null; } - return false; - } - return true; - } - } - - private static boolean isFieldInitializedInOtherFieldInitializer(@Nonnull PsiClass aClass, - @Nonnull PsiField field, - final boolean fieldStatic, - @Nonnull Predicate condition) { - PsiField[] fields = aClass.getFields(); - for (PsiField psiField : fields) { - if (psiField != field - && psiField.hasModifierProperty(PsiModifier.STATIC) == fieldStatic - && variableDefinitelyAssignedIn(field, psiField) - && condition.test(psiField)) { - return true; - } + // do not compute constant expressions for if() statement condition + // see JLS 14.20 Unreachable Statements + try { + AllVariablesControlFlowPolicy policy = AllVariablesControlFlowPolicy.getInstance(); + ControlFlow controlFlow = ControlFlowFactory.getControlFlow(codeBlock, policy, ControlFlowOptions.NO_CONST_EVALUATE); + PsiElement unreachableStatement = ControlFlowUtil.getUnreachableStatement(controlFlow); + if (unreachableStatement != null) { + PsiElement keyword = null; + if (unreachableStatement instanceof PsiIfStatement || + unreachableStatement instanceof PsiSwitchBlock || + unreachableStatement instanceof PsiLoopStatement) { + keyword = unreachableStatement.getFirstChild(); + } + PsiElement element = keyword != null ? keyword : unreachableStatement; + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.statementUnreachable()) + .registerFix(QuickFixFactory.getInstance().createDeleteFix( + unreachableStatement, + JavaQuickFixLocalize.deleteUnreachableStatementFixText() + )); + } + } + catch (AnalysisCanceledException | IndexNotReadyException e) { + // incomplete code + } + return null; } - return false; - } - - public static boolean isRecursivelyCalledConstructor(@Nonnull PsiMethod constructor) { - final JavaHighlightUtil.ConstructorVisitorInfo info = new JavaHighlightUtil.ConstructorVisitorInfo(); - JavaHighlightUtil.visitConstructorChain(constructor, info); - if (info.recursivelyCalledConstructor == null) { - return false; + + public static boolean isFieldInitializedAfterObjectConstruction(PsiField field) { + if (field.hasInitializer()) { + return true; + } + boolean isFieldStatic = field.isStatic(); + PsiClass aClass = field.getContainingClass(); + if (aClass != null) { + // field might be assigned in the other field initializers + if (isFieldInitializedInOtherFieldInitializer(aClass, field, isFieldStatic, __ -> true)) { + return true; + } + } + PsiClassInitializer[] initializers; + if (aClass != null) { + initializers = aClass.getInitializers(); + } + else { + return false; + } + if (isFieldInitializedInClassInitializer(field, isFieldStatic, initializers)) { + return true; + } + if (isFieldStatic) { + return false; + } + else { + // instance field should be initialized at the end of the each constructor + PsiMethod[] constructors = aClass.getConstructors(); + + if (constructors.length == 0) { + return false; + } + nextConstructor: + for (PsiMethod constructor : constructors) { + PsiCodeBlock ctrBody = constructor.getBody(); + if (ctrBody == null) { + return false; + } + List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); + for (PsiMethod redirectedConstructor : redirectedConstructors) { + PsiCodeBlock body = redirectedConstructor.getBody(); + if (body != null && variableDefinitelyAssignedIn(field, body)) { + continue nextConstructor; + } + } + if (!ctrBody.isValid() || variableDefinitelyAssignedIn(field, ctrBody)) { + continue; + } + return false; + } + return true; + } } - // our constructor is reached from some other constructor by constructor chain - return info.visitedConstructors.indexOf(info.recursivelyCalledConstructor) <= info.visitedConstructors.indexOf(constructor); - } - - public static boolean isAssigned(@Nonnull PsiParameter parameter) { - ParamWriteProcessor processor = new ParamWriteProcessor(); - ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor); - return processor.isWriteRefFound(); - } - - private static class ParamWriteProcessor implements Processor { - private volatile boolean myIsWriteRefFound; - - @Override - public boolean process(PsiReference reference) { - final PsiElement element = reference.getElement(); - if (element instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) element)) { - myIsWriteRefFound = true; + + private static boolean isFieldInitializedInOtherFieldInitializer( + PsiClass aClass, + PsiField field, + boolean fieldStatic, + Predicate condition + ) { + PsiField[] fields = aClass.getFields(); + for (PsiField psiField : fields) { + if (psiField != field + && psiField.isStatic() == fieldStatic + && variableDefinitelyAssignedIn(field, psiField) + && condition.test(psiField)) { + return true; + } + } return false; - } - return true; } - private boolean isWriteRefFound() { - return myIsWriteRefFound; - } - } - - /** - * see JLS chapter 16 - * - * @return true if variable assigned (maybe more than once) - */ - private static boolean variableDefinitelyAssignedIn(@Nonnull PsiVariable variable, @Nonnull PsiElement context) { - try { - ControlFlow controlFlow = getControlFlow(context); - return ControlFlowUtil.isVariableDefinitelyAssigned(variable, controlFlow); - } catch (AnalysisCanceledException e) { - return false; + public static boolean isRecursivelyCalledConstructor(PsiMethod constructor) { + JavaHighlightUtil.ConstructorVisitorInfo info = new JavaHighlightUtil.ConstructorVisitorInfo(); + JavaHighlightUtil.visitConstructorChain(constructor, info); + if (info.recursivelyCalledConstructor == null) { + return false; + } + // our constructor is reached from some other constructor by constructor chain + return info.visitedConstructors.indexOf(info.recursivelyCalledConstructor) <= info.visitedConstructors.indexOf(constructor); } - } - - private static boolean variableDefinitelyNotAssignedIn(@Nonnull PsiVariable variable, @Nonnull PsiElement context) { - try { - ControlFlow controlFlow = getControlFlow(context); - return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, controlFlow); - } catch (AnalysisCanceledException e) { - return false; + + public static boolean isAssigned(PsiParameter parameter) { + ParamWriteProcessor processor = new ParamWriteProcessor(); + ReferencesSearch.search(parameter, new LocalSearchScope(parameter.getDeclarationScope()), true).forEach(processor); + return processor.isWriteRefFound(); } - } + private static class ParamWriteProcessor implements Predicate { + private volatile boolean myIsWriteRefFound; - @Nullable - public static HighlightInfo checkFinalFieldInitialized(@Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - if (isFieldInitializedAfterObjectConstruction(field)) { - return null; - } + @Override + @RequiredReadAction + public boolean test(PsiReference reference) { + if (reference.getElement() instanceof PsiReferenceExpression refExpr && PsiUtil.isAccessedForWriting(refExpr)) { + myIsWriteRefFound = true; + return false; + } + return true; + } - String description = JavaErrorBundle.message("variable.not.initialized", field.getName()); - TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createCreateConstructorParameterFromFieldFix(field)); - QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QUICK_FIX_FACTORY.createInitializeFinalFieldInConstructorFix(field)); - final PsiClass containingClass = field.getContainingClass(); - if (containingClass != null && !containingClass.isInterface()) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.FINAL, false, false)); - } - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(field)); - return highlightInfo; - } - - - public static HighlightInfo checkVariableInitializedBeforeUsage(@Nonnull PsiReferenceExpression expression, - @Nonnull PsiVariable variable, - @Nonnull Map> uninitializedVarProblems, - @Nonnull PsiFile containingFile) { - return checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, containingFile, false); - } - - public static HighlightInfo checkVariableInitializedBeforeUsage(@Nonnull PsiReferenceExpression expression, - @Nonnull PsiVariable variable, - @Nonnull Map> uninitializedVarProblems, - @Nonnull PsiFile containingFile, - boolean ignoreFinality) { - if (variable instanceof ImplicitVariable) { - return null; + private boolean isWriteRefFound() { + return myIsWriteRefFound; + } } - if (!PsiUtil.isAccessedForReading(expression)) { - return null; + + /** + * see JLS chapter 16 + * + * @return true if variable assigned (maybe more than once) + */ + private static boolean variableDefinitelyAssignedIn(PsiVariable variable, PsiElement context) { + try { + ControlFlow controlFlow = getControlFlow(context); + return ControlFlowUtil.isVariableDefinitelyAssigned(variable, controlFlow); + } + catch (AnalysisCanceledException e) { + return false; + } } - int startOffset = expression.getTextRange().getStartOffset(); - final PsiElement topBlock; - if (variable.hasInitializer()) { - topBlock = PsiUtil.getVariableCodeBlock(variable, variable); - if (topBlock == null) { - return null; - } - } else { - PsiElement scope = variable instanceof PsiField - ? ((PsiField) variable).getContainingClass() - : variable.getParent() != null ? variable.getParent().getParent() : null; - while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) { - scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); - } - - topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); - if (variable instanceof PsiField) { - // non final field already initialized with default value - if (!ignoreFinality && !variable.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - // final field may be initialized in ctor or class initializer only - // if we're inside non-ctr method, skip it - if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null - && HighlightUtil.findEnclosingFieldInitializer(expression) == null) { - return null; + + private static boolean variableDefinitelyNotAssignedIn(PsiVariable variable, PsiElement context) { + try { + ControlFlow controlFlow = getControlFlow(context); + return ControlFlowUtil.isVariableDefinitelyNotAssigned(variable, controlFlow); } - if (topBlock == null) { - return null; - } - final PsiElement parent = topBlock.getParent(); - // access to final fields from inner classes always allowed - if (inInnerClass(expression, ((PsiField) variable).getContainingClass())) { - return null; - } - final PsiCodeBlock block; - final PsiClass aClass; - if (parent instanceof PsiMethod) { - PsiMethod constructor = (PsiMethod) parent; - if (!containingFile.getManager().areElementsEquivalent(constructor.getContainingClass(), ((PsiField) variable).getContainingClass())) { - return null; - } - // static variables already initialized in class initializers - if (variable.hasModifierProperty(PsiModifier.STATIC)) { + catch (AnalysisCanceledException e) { + return false; + } + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkFinalFieldInitialized(PsiField field) { + if (!field.isFinal()) { return null; - } - // as a last chance, field may be initialized in this() call - final List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); - for (PsiMethod redirectedConstructor : redirectedConstructors) { - // variable must be initialized before its usage - //??? - //if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; - if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) { - return null; - } - PsiCodeBlock body = redirectedConstructor.getBody(); - if (body != null && variableDefinitelyAssignedIn(variable, body)) { - return null; - } - } - block = constructor.getBody(); - aClass = constructor.getContainingClass(); - } else if (parent instanceof PsiClassInitializer) { - final PsiClassInitializer classInitializer = (PsiClassInitializer) parent; - if (!containingFile.getManager().areElementsEquivalent(classInitializer.getContainingClass(), ((PsiField) variable).getContainingClass())) { + } + if (isFieldInitializedAfterObjectConstruction(field)) { return null; - } - block = classInitializer.getBody(); - aClass = classInitializer.getContainingClass(); + } - if (aClass == null || isFieldInitializedInOtherFieldInitializer(aClass, (PsiField) variable, variable.hasModifierProperty(PsiModifier.STATIC), field -> startOffset > field - .getTextOffset())) { - return null; - } - } else { - // field reference outside code block - // check variable initialized before its usage - final PsiField field = (PsiField) variable; - - aClass = field.getContainingClass(); - final PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class); - if (aClass == null || - isFieldInitializedInOtherFieldInitializer(aClass, field, field.hasModifierProperty(PsiModifier.STATIC), psiField -> startOffset > psiField.getTextOffset())) { + TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field); + QuickFixFactory factory = QuickFixFactory.getInstance(); + TextRange fixRange = HighlightMethodUtil.getFixRange(field); + HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableNotInitialized(field.getName())) + .newFix(factory.createCreateConstructorParameterFromFieldFix(field)).fixRange(fixRange).register() + .newFix(factory.createInitializeFinalFieldInConstructorFix(field)).fixRange(fixRange).register(); + PsiClass containingClass = field.getContainingClass(); + if (containingClass != null && !containingClass.isInterface()) { + highlightInfo.registerFix(factory.createModifierFixBuilder(field).remove(PsiModifier.FINAL).create()); + } + return highlightInfo.registerFix(factory.createAddVariableInitializerFix(field)) + .create(); + } + + @RequiredReadAction + public static HighlightInfo checkVariableInitializedBeforeUsage( + PsiReferenceExpression expression, + PsiVariable variable, + Map> uninitializedVarProblems, + PsiFile containingFile + ) { + return checkVariableInitializedBeforeUsage(expression, variable, uninitializedVarProblems, containingFile, false); + } + + @RequiredReadAction + public static HighlightInfo checkVariableInitializedBeforeUsage( + PsiReferenceExpression expression, + PsiVariable variable, + Map> uninitializedVarProblems, + PsiFile containingFile, + boolean ignoreFinality + ) { + if (variable instanceof ImplicitVariable) { return null; - } - if (anotherField != null && !anotherField.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.STATIC) && - isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) { + } + if (!PsiUtil.isAccessedForReading(expression)) { return null; - } - - int offset = startOffset; - if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) { - offset = 0; - } - block = null; - // initializers will be checked later - final PsiMethod[] constructors = aClass.getConstructors(); - for (PsiMethod constructor : constructors) { - // variable must be initialized before its usage - if (offset < constructor.getTextRange().getStartOffset()) { - continue; - } - PsiCodeBlock body = constructor.getBody(); - if (body != null && variableDefinitelyAssignedIn(variable, body)) { - return null; - } - // as a last chance, field may be initialized in this() call - final List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); - for (PsiMethod redirectedConstructor : redirectedConstructors) { - // variable must be initialized before its usage - if (offset < redirectedConstructor.getTextRange().getStartOffset()) { - continue; - } - PsiCodeBlock redirectedBody = redirectedConstructor.getBody(); - if (redirectedBody != null && variableDefinitelyAssignedIn(variable, redirectedBody)) { + } + int startOffset = expression.getTextRange().getStartOffset(); + PsiElement topBlock; + if (variable.hasInitializer()) { + topBlock = PsiUtil.getVariableCodeBlock(variable, variable); + if (topBlock == null) { return null; - } } - } } + else { + PsiElement scope = variable instanceof PsiField field + ? field.getContainingClass() + : variable.getParent() != null ? variable.getParent().getParent() : null; + while (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) { + scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); + } - if (aClass != null) { - // field may be initialized in class initializer - final PsiClassInitializer[] initializers = aClass.getInitializers(); - for (PsiClassInitializer initializer : initializers) { - PsiCodeBlock body = initializer.getBody(); - if (body == block) { - break; - } - // variable referenced in initializer must be initialized in initializer preceding assignment - // variable referenced in field initializer or in class initializer - boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer; - if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) { - continue; - } - if (initializer.hasModifierProperty(PsiModifier.STATIC) - == variable.hasModifierProperty(PsiModifier.STATIC)) { - if (variableDefinitelyAssignedIn(variable, body)) { - return null; - } + topBlock = FileTypeUtils.isInServerPageFile(scope) && scope instanceof PsiFile + ? scope + : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); + if (variable instanceof PsiField field) { + // non final field already initialized with default value + if (!ignoreFinality && !field.isFinal()) { + return null; + } + // final field may be initialized in ctor or class initializer only + // if we're inside non-ctr method, skip it + if (PsiUtil.findEnclosingConstructorOrInitializer(expression) == null + && HighlightUtil.findEnclosingFieldInitializer(expression) == null) { + return null; + } + if (topBlock == null) { + return null; + } + PsiElement parent = topBlock.getParent(); + // access to final fields from inner classes always allowed + if (inInnerClass(expression, field.getContainingClass())) { + return null; + } + PsiCodeBlock block; + PsiClass aClass; + if (parent instanceof PsiMethod constructor) { + if (!containingFile.getManager() + .areElementsEquivalent(constructor.getContainingClass(), field.getContainingClass())) { + return null; + } + // static variables already initialized in class initializers + if (field.isStatic()) { + return null; + } + // as a last chance, field may be initialized in this() call + List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); + for (PsiMethod redirectedConstructor : redirectedConstructors) { + // variable must be initialized before its usage + //??? + //if (startOffset < redirectedConstructor.getTextRange().getStartOffset()) continue; + if (JavaPsiRecordUtil.isCompactConstructor(redirectedConstructor)) { + return null; + } + PsiCodeBlock body = redirectedConstructor.getBody(); + if (body != null && variableDefinitelyAssignedIn(field, body)) { + return null; + } + } + block = constructor.getBody(); + aClass = constructor.getContainingClass(); + } + else if (parent instanceof PsiClassInitializer classInitializer) { + if (!containingFile.getManager().areElementsEquivalent( + classInitializer.getContainingClass(), + field.getContainingClass() + )) { + return null; + } + block = classInitializer.getBody(); + aClass = classInitializer.getContainingClass(); + + if (aClass == null || isFieldInitializedInOtherFieldInitializer( + aClass, + field, + field.isStatic(), + field1 -> startOffset > field1.getTextOffset() + )) { + return null; + } + } + else { + // field reference outside code block + // check variable initialized before its usage + aClass = field.getContainingClass(); + PsiField anotherField = PsiTreeUtil.getTopmostParentOfType(expression, PsiField.class); + if (aClass == null || + isFieldInitializedInOtherFieldInitializer( + aClass, + field, + field.isStatic(), + psiField -> startOffset > psiField.getTextOffset() + )) { + return null; + } + if (anotherField != null && !anotherField.isStatic() && field.isStatic() + && isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) { + return null; + } + + if (anotherField != null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) { + return null; + } + + int offset = startOffset; + if (anotherField != null && anotherField.getContainingClass() == aClass && !field.isStatic()) { + offset = 0; + } + block = null; + // initializers will be checked later + PsiMethod[] constructors = aClass.getConstructors(); + for (PsiMethod constructor : constructors) { + // variable must be initialized before its usage + if (offset < constructor.getTextRange().getStartOffset()) { + continue; + } + PsiCodeBlock body = constructor.getBody(); + if (body != null && variableDefinitelyAssignedIn(field, body)) { + return null; + } + // as a last chance, field may be initialized in this() call + List redirectedConstructors = JavaHighlightUtil.getChainedConstructors(constructor); + for (PsiMethod redirectedConstructor : redirectedConstructors) { + // variable must be initialized before its usage + if (offset < redirectedConstructor.getTextRange().getStartOffset()) { + continue; + } + PsiCodeBlock redirectedBody = redirectedConstructor.getBody(); + if (redirectedBody != null && variableDefinitelyAssignedIn(field, redirectedBody)) { + return null; + } + } + } + } + + if (aClass != null) { + // field may be initialized in class initializer + PsiClassInitializer[] initializers = aClass.getInitializers(); + for (PsiClassInitializer initializer : initializers) { + PsiCodeBlock body = initializer.getBody(); + if (body == block) { + break; + } + // variable referenced in initializer must be initialized in initializer preceding assignment + // variable referenced in field initializer or in class initializer + boolean shouldCheckInitializerOrder = block == null || block.getParent() instanceof PsiClassInitializer; + if (shouldCheckInitializerOrder && startOffset < initializer.getTextRange().getStartOffset()) { + continue; + } + if (initializer.isStatic() == field.isStatic() && variableDefinitelyAssignedIn(field, body)) { + return null; + } + } + } } - } } - } - } - if (topBlock == null) { - return null; - } - Collection codeBlockProblems = uninitializedVarProblems.get(topBlock); - if (codeBlockProblems == null) { - try { - final ControlFlow controlFlow = getControlFlow(topBlock); - codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); - } catch (AnalysisCanceledException | IndexNotReadyException e) { - codeBlockProblems = Collections.emptyList(); - } - uninitializedVarProblems.put(topBlock, codeBlockProblems); - } - if (codeBlockProblems.contains(expression)) { - final String name = expression.getElement().getText(); - String description = JavaErrorBundle.message("variable.not.initialized", name); - HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddVariableInitializerFix(variable)); - if (variable instanceof PsiLocalVariable) { - //QuickFixAction.registerQuickFixAction(highlightInfo, HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression)); - } - if (variable instanceof PsiField) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); - } - return highlightInfo; + if (topBlock == null) { + return null; + } + Collection codeBlockProblems = uninitializedVarProblems.get(topBlock); + if (codeBlockProblems == null) { + try { + ControlFlow controlFlow = getControlFlow(topBlock); + codeBlockProblems = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); + } + catch (AnalysisCanceledException | IndexNotReadyException e) { + codeBlockProblems = Collections.emptyList(); + } + uninitializedVarProblems.put(topBlock, codeBlockProblems); + } + if (codeBlockProblems.contains(expression)) { + String name = expression.getElement().getText(); + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableNotInitialized(name)) + .registerFix(factory.createAddVariableInitializerFix(variable)); + if (variable instanceof PsiLocalVariable) { + //highlightInfo.registerFix(HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, expression)); + } + if (variable instanceof PsiField) { + hlBuilder.registerFix(factory.createModifierFixBuilder(variable).remove(PsiModifier.FINAL).create()); + } + return hlBuilder.create(); + } + + return null; } - return null; - } - - private static boolean isFieldInitializedInClassInitializer(@Nonnull PsiField field, - boolean isFieldStatic, - @Nonnull PsiClassInitializer[] initializers) { - return ContainerUtil.find(initializers, initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic - && variableDefinitelyAssignedIn(field, initializer.getBody())) != null; - } - - private static boolean inInnerClass(@Nonnull PsiElement psiElement, @Nullable PsiClass containingClass) { - for (PsiElement element = psiElement; element != null; element = element.getParent()) { - if (element instanceof PsiClass) { - final boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass); - if (innerClass) { - if (element instanceof PsiAnonymousClass) { - if (PsiTreeUtil.isAncestor(((PsiAnonymousClass) element).getArgumentList(), psiElement, false)) { - continue; - } - return !insideClassInitialization(containingClass, (PsiClass) element); - } - final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class); - return lambdaExpression == null || !insideClassInitialization(containingClass, (PsiClass) element); + private static boolean isFieldInitializedInClassInitializer( + PsiField field, + boolean isFieldStatic, + PsiClassInitializer[] initializers + ) { + return ContainerUtil.find( + initializers, + initializer -> initializer.isStatic() == isFieldStatic && variableDefinitelyAssignedIn(field, initializer.getBody()) + ) != null; + } + + private static boolean inInnerClass(PsiElement psiElement, @Nullable PsiClass containingClass) { + for (PsiElement element = psiElement; element != null; element = element.getParent()) { + if (element instanceof PsiClass psiClass) { + boolean innerClass = !psiElement.getManager().areElementsEquivalent(element, containingClass); + if (innerClass) { + if (psiClass instanceof PsiAnonymousClass anonymousClass) { + if (PsiTreeUtil.isAncestor(anonymousClass.getArgumentList(), psiElement, false)) { + continue; + } + return !insideClassInitialization(containingClass, anonymousClass); + } + PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class); + return lambdaExpression == null || !insideClassInitialization(containingClass, psiClass); + } + return false; + } } return false; - } - } - return false; - } - - private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) { - PsiMember member = aClass; - while (member != null) { - if (member.getContainingClass() == containingClass) { - return member instanceof PsiField || - member instanceof PsiMethod && ((PsiMethod) member).isConstructor() || - member instanceof PsiClassInitializer; - } - member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true); } - return false; - } - public static boolean isReassigned(@Nonnull PsiVariable variable, @Nonnull Map> finalVarProblems) { - if (variable instanceof PsiLocalVariable) { - final PsiElement parent = variable.getParent(); - if (parent == null) { - return false; - } - final PsiElement declarationScope = parent.getParent(); - if (declarationScope == null) { + private static boolean insideClassInitialization(@Nullable PsiClass containingClass, PsiClass aClass) { + PsiMember member = aClass; + while (member != null) { + if (member.getContainingClass() == containingClass) { + return member instanceof PsiField + || member instanceof PsiMethod method && method.isConstructor() + || member instanceof PsiClassInitializer; + } + member = PsiTreeUtil.getParentOfType(member, PsiMember.class, true); + } return false; - } - Collection codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, declarationScope); - return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null)); } - if (variable instanceof PsiParameter) { - final PsiParameter parameter = (PsiParameter) variable; - return isAssigned(parameter); + + public static boolean isReassigned( + PsiVariable variable, + Map> finalVarProblems + ) { + if (variable instanceof PsiLocalVariable) { + PsiElement parent = variable.getParent(); + if (parent == null) { + return false; + } + PsiElement declarationScope = parent.getParent(); + if (declarationScope == null) { + return false; + } + Collection codeBlockProblems = + getFinalVariableProblemsInBlock(finalVarProblems, declarationScope); + return codeBlockProblems.contains(new ControlFlowUtil.VariableInfo(variable, null)); + } + return variable instanceof PsiParameter parameter && isAssigned(parameter); } - return false; - } - @Nullable - public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo(@Nonnull PsiVariable variable, - @Nonnull PsiReferenceExpression expression, - @Nonnull Map> finalVarProblems) { - if (!PsiUtil.isAccessedForWriting(expression)) { - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkFinalVariableMightAlreadyHaveBeenAssignedTo( + PsiVariable variable, + PsiReferenceExpression expression, + Map> finalVarProblems + ) { + if (!PsiUtil.isAccessedForWriting(expression)) { + return null; + } - final PsiElement scope = variable instanceof PsiField ? variable.getParent() : variable.getParent() == null ? null : variable.getParent().getParent(); - PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); - if (codeBlock == null) { - return null; - } - Collection codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock); - - boolean alreadyAssigned = false; - for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) { - if (variableInfo.expression == expression) { - alreadyAssigned = true; - break; - } - } + PsiElement scope = variable instanceof PsiField ? variable.getParent() + : variable.getParent() == null ? null : variable.getParent().getParent(); + PsiElement codeBlock = PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); + if (codeBlock == null) { + return null; + } + Collection codeBlockProblems = getFinalVariableProblemsInBlock(finalVarProblems, codeBlock); - if (!alreadyAssigned) { - if (!(variable instanceof PsiField)) { - return null; - } - final PsiField field = (PsiField) variable; - final PsiClass aClass = field.getContainingClass(); - if (aClass == null) { - return null; - } - // field can get assigned in other field initializers - final PsiField[] fields = aClass.getFields(); - boolean isFieldStatic = field.hasModifierProperty(PsiModifier.STATIC); - for (PsiField psiField : fields) { - PsiExpression initializer = psiField.getInitializer(); - if (psiField != field && psiField.hasModifierProperty(PsiModifier.STATIC) == isFieldStatic && initializer != null && initializer != codeBlock && !variableDefinitelyNotAssignedIn - (field, initializer)) { - alreadyAssigned = true; - break; - } - } - - if (!alreadyAssigned) { - // field can get assigned in class initializers - final PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); - if (enclosingConstructorOrInitializer == null || !aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) { - return null; - } - final PsiClassInitializer[] initializers = aClass.getInitializers(); - for (PsiClassInitializer initializer : initializers) { - if (initializer.hasModifierProperty(PsiModifier.STATIC) == field.hasModifierProperty(PsiModifier.STATIC)) { - final PsiCodeBlock body = initializer.getBody(); - if (body == codeBlock) { - return null; - } - try { - final ControlFlow controlFlow = getControlFlow(body); - if (!ControlFlowUtil.isVariableDefinitelyNotAssigned(field, controlFlow)) { + boolean alreadyAssigned = false; + for (ControlFlowUtil.VariableInfo variableInfo : codeBlockProblems) { + if (variableInfo.expression == expression) { alreadyAssigned = true; break; - } - } catch (AnalysisCanceledException e) { - // incomplete code - return null; - } - } - } - } - - if (!alreadyAssigned && !field.hasModifierProperty(PsiModifier.STATIC)) { - // then check if instance field already assigned in other constructor - final PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod ? (PsiMethod) codeBlock.getParent() : null; - // assignment to final field in several constructors threatens us only if these are linked (there is this() call in the beginning) - final List redirectedConstructors = ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : null; - for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { - PsiMethod redirectedConstructor = redirectedConstructors.get(j); - PsiCodeBlock body = redirectedConstructor.getBody(); - if (body != null && variableDefinitelyAssignedIn(variable, body)) { - alreadyAssigned = true; - break; - } - } - } - } + } + } - if (alreadyAssigned) { - String description = JavaErrorBundle.message("variable.already.assigned", variable.getName()); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeferFinalAssignmentFix(variable, expression)); - return highlightInfo; - } + if (!alreadyAssigned) { + if (!(variable instanceof PsiField field)) { + return null; + } + PsiClass aClass = field.getContainingClass(); + if (aClass == null) { + return null; + } + // field can get assigned in other field initializers + PsiField[] fields = aClass.getFields(); + boolean isFieldStatic = field.isStatic(); + for (PsiField psiField : fields) { + PsiExpression initializer = psiField.getInitializer(); + if (psiField != field + && psiField.isStatic() == isFieldStatic + && initializer != null && initializer != codeBlock + && !variableDefinitelyNotAssignedIn(field, initializer)) { + alreadyAssigned = true; + break; + } + } - return null; - } - - @Nonnull - private static Collection getFinalVariableProblemsInBlock(@Nonnull Map> finalVarProblems, - @Nonnull PsiElement codeBlock) { - Collection codeBlockProblems = finalVarProblems.get(codeBlock); - if (codeBlockProblems == null) { - try { - final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(codeBlock); - codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow); - } catch (AnalysisCanceledException e) { - codeBlockProblems = Collections.emptyList(); - } - finalVarProblems.put(codeBlock, codeBlockProblems); - } - return codeBlockProblems; - } + if (!alreadyAssigned) { + // field can get assigned in class initializers + PsiMember enclosingConstructorOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); + if (enclosingConstructorOrInitializer == null + || !aClass.getManager().areElementsEquivalent(enclosingConstructorOrInitializer.getContainingClass(), aClass)) { + return null; + } + for (PsiClassInitializer initializer : aClass.getInitializers()) { + if (initializer.isStatic() == field.isStatic()) { + PsiCodeBlock body = initializer.getBody(); + if (body == codeBlock) { + return null; + } + try { + ControlFlow controlFlow = getControlFlow(body); + if (!ControlFlowUtil.isVariableDefinitelyNotAssigned(field, controlFlow)) { + alreadyAssigned = true; + break; + } + } + catch (AnalysisCanceledException e) { + // incomplete code + return null; + } + } + } + } + if (!alreadyAssigned && !field.isStatic()) { + // then check if instance field already assigned in other constructor + PsiMethod ctr = codeBlock.getParent() instanceof PsiMethod method ? method : null; + // assignment to final field in several constructors threatens us only if these are linked + // (there is this() call in the beginning) + List redirectedConstructors = + ctr != null && ctr.isConstructor() ? JavaHighlightUtil.getChainedConstructors(ctr) : null; + for (int j = 0; redirectedConstructors != null && j < redirectedConstructors.size(); j++) { + PsiMethod redirectedConstructor = redirectedConstructors.get(j); + PsiCodeBlock body = redirectedConstructor.getBody(); + if (body != null && variableDefinitelyAssignedIn(variable, body)) { + alreadyAssigned = true; + break; + } + } + } + } - @Nullable - public static HighlightInfo checkFinalVariableInitializedInLoop(@Nonnull PsiReferenceExpression expression, @Nonnull PsiElement resolved) { - if (ControlFlowUtil.isVariableAssignedInLoop(expression, resolved)) { - String description = JavaErrorBundle.message("variable.assigned.in.loop", ((PsiVariable) resolved).getName()); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix((PsiVariable) resolved, PsiModifier.FINAL, false, false)); - return highlightInfo; - } - return null; - } - - - @Nullable - public static HighlightInfo checkCannotWriteToFinal(@Nonnull PsiExpression expression, @Nonnull PsiFile containingFile) { - PsiReferenceExpression reference = null; - boolean readBeforeWrite = false; - if (expression instanceof PsiAssignmentExpression) { - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression) expression; - final PsiExpression left = PsiUtil.skipParenthesizedExprDown(assignmentExpression.getLExpression()); - if (left instanceof PsiReferenceExpression) { - reference = (PsiReferenceExpression) left; - } - readBeforeWrite = assignmentExpression.getOperationTokenType() != JavaTokenType.EQ; - } else if (expression instanceof PsiPostfixExpression) { - final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPostfixExpression) expression).getOperand()); - final IElementType sign = ((PsiPostfixExpression) expression).getOperationTokenType(); - if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { - reference = (PsiReferenceExpression) operand; - } - readBeforeWrite = true; - } else if (expression instanceof PsiPrefixExpression) { - final PsiExpression operand = PsiUtil.skipParenthesizedExprDown(((PsiPrefixExpression) expression).getOperand()); - final IElementType sign = ((PsiPrefixExpression) expression).getOperationTokenType(); - if (operand instanceof PsiReferenceExpression && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { - reference = (PsiReferenceExpression) operand; - } - readBeforeWrite = true; - } - final PsiElement resolved = reference == null ? null : reference.resolve(); - PsiVariable variable = resolved instanceof PsiVariable ? (PsiVariable) resolved : null; - if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) { - return null; + if (alreadyAssigned) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableAlreadyAssigned(variable.getName())) + .registerFix(factory.createModifierFixBuilder(variable).remove(PsiModifier.FINAL).create()) + .registerFix(factory.createDeferFinalAssignmentFix(variable, expression)) + .create(); + } + + return null; } - final boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) && checkWriteToFinalInsideLambda(variable, reference) == null; - if (readBeforeWrite || !canWrite) { - final String name = variable.getName(); - String description = canWrite ? JavaErrorBundle.message("variable.not.initialized", name) : JavaErrorBundle.message("assignment.to.final.variable", name); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(reference.getTextRange()).descriptionAndTooltip(description).create(); - final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); - if (innerClass == null || variable instanceof PsiField) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(variable, PsiModifier.FINAL, false, false)); - } else { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); - } - return highlightInfo; + + private static Collection getFinalVariableProblemsInBlock( + Map> finalVarProblems, + PsiElement codeBlock + ) { + Collection codeBlockProblems = finalVarProblems.get(codeBlock); + if (codeBlockProblems == null) { + try { + ControlFlow controlFlow = getControlFlowNoConstantEvaluate(codeBlock); + codeBlockProblems = ControlFlowUtil.getInitializedTwice(controlFlow); + } + catch (AnalysisCanceledException e) { + codeBlockProblems = Collections.emptyList(); + } + finalVarProblems.put(codeBlock, codeBlockProblems); + } + return codeBlockProblems; + } + + + @Nullable + @RequiredReadAction + public static HighlightInfo checkFinalVariableInitializedInLoop( + PsiReferenceExpression expression, + PsiElement resolved + ) { + if (ControlFlowUtil.isVariableAssignedInLoop(expression, resolved)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableAssignedInLoop(((PsiVariable) resolved).getName())) + .registerFix(QuickFixFactory.getInstance() + .createModifierFixBuilder((PsiVariable) resolved) + .remove(PsiModifier.FINAL) + .create()) + .create(); + } + return null; } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkCannotWriteToFinal(PsiExpression expression, PsiFile containingFile) { + PsiReferenceExpression reference = null; + boolean readBeforeWrite = false; + if (expression instanceof PsiAssignmentExpression assignment) { + if (PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()) instanceof PsiReferenceExpression lRefExpr) { + reference = lRefExpr; + } + readBeforeWrite = assignment.getOperationTokenType() != JavaTokenType.EQ; + } + else if (expression instanceof PsiPostfixExpression postfixExpr) { + IElementType sign = postfixExpr.getOperationTokenType(); + if (PsiUtil.skipParenthesizedExprDown(postfixExpr.getOperand()) instanceof PsiReferenceExpression operandRefExpr + && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { + reference = operandRefExpr; + } + readBeforeWrite = true; + } + else if (expression instanceof PsiPrefixExpression prefixExpr) { + IElementType sign = prefixExpr.getOperationTokenType(); + if (PsiUtil.skipParenthesizedExprDown(prefixExpr.getOperand()) instanceof PsiReferenceExpression operandRefExpr + && (sign == JavaTokenType.PLUSPLUS || sign == JavaTokenType.MINUSMINUS)) { + reference = operandRefExpr; + } + readBeforeWrite = true; + } + PsiElement resolved = reference == null ? null : reference.resolve(); + PsiVariable variable = resolved instanceof PsiVariable resolvedVar ? resolvedVar : null; + if (variable == null || !variable.hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + boolean canWrite = canWriteToFinal(variable, expression, reference, containingFile) + && checkWriteToFinalInsideLambda(variable, reference) == null; + if (readBeforeWrite || !canWrite) { + String name = variable.getName(); + LocalizeValue description = canWrite + ? JavaCompilationErrorLocalize.variableNotInitialized(name) + : JavaCompilationErrorLocalize.assignmentToFinalVariable(name); + PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(reference.getTextRange()) + .descriptionAndTooltip(description) + .registerFix( + innerClass == null || variable instanceof PsiField + ? factory.createModifierFixBuilder(variable).remove(PsiModifier.FINAL).create() + : factory.createVariableAccessFromInnerClassFix(variable, innerClass) + ) + .create(); + } - private static boolean canWriteToFinal(@Nonnull PsiVariable variable, @Nonnull PsiExpression expression, @Nonnull PsiReferenceExpression reference, @Nonnull PsiFile containingFile) { - if (variable.hasInitializer()) { - return false; - } - if (variable instanceof PsiParameter) { - return false; - } - PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); - if (variable instanceof PsiField) { - // if inside some field initializer - if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) { - return true; - } - // assignment from within inner class is illegal always - PsiField field = (PsiField) variable; - if (innerClass != null && !containingFile.getManager().areElementsEquivalent(innerClass, field.getContainingClass())) { - return false; - } - final PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); - return enclosingCtrOrInitializer != null && isSameField(enclosingCtrOrInitializer, field, reference, containingFile); - } - if (variable instanceof PsiLocalVariable) { - boolean isAccessedFromOtherClass = innerClass != null; - if (isAccessedFromOtherClass) { - return false; - } + return null; } - return true; - } - private static boolean isSameField(@Nonnull PsiMember enclosingCtrOrInitializer, @Nonnull PsiField field, @Nonnull PsiReferenceExpression reference, @Nonnull PsiFile containingFile) { - if (!containingFile.getManager().areElementsEquivalent(enclosingCtrOrInitializer.getContainingClass(), field.getContainingClass())) { - return false; + private static boolean canWriteToFinal( + PsiVariable variable, + PsiExpression expression, + PsiReferenceExpression reference, + PsiFile containingFile + ) { + if (variable.hasInitializer()) { + return false; + } + if (variable instanceof PsiParameter) { + return false; + } + PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, expression); + if (variable instanceof PsiField field) { + // if inside some field initializer + if (HighlightUtil.findEnclosingFieldInitializer(expression) != null) { + return true; + } + // assignment from within inner class is illegal always + if (innerClass != null && !containingFile.getManager().areElementsEquivalent(innerClass, field.getContainingClass())) { + return false; + } + PsiMember enclosingCtrOrInitializer = PsiUtil.findEnclosingConstructorOrInitializer(expression); + return enclosingCtrOrInitializer != null && isSameField(enclosingCtrOrInitializer, field, reference, containingFile); + } + if (variable instanceof PsiLocalVariable) { + boolean isAccessedFromOtherClass = innerClass != null; + if (isAccessedFromOtherClass) { + return false; + } + } + return true; } - return LocalsOrMyInstanceFieldsControlFlowPolicy.isLocalOrMyInstanceReference(reference); - } - @Nullable - public static HighlightInfo checkVariableMustBeFinal(@Nonnull PsiVariable variable, @Nonnull PsiJavaCodeReferenceElement context, @Nonnull LanguageLevel languageLevel) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) { - return null; + private static boolean isSameField( + PsiMember enclosingCtrOrInitializer, + PsiField field, + PsiReferenceExpression reference, + PsiFile containingFile + ) { + PsiManager manager = containingFile.getManager(); + if (!manager.areElementsEquivalent(enclosingCtrOrInitializer.getContainingClass(), field.getContainingClass())) { + return false; + } + return LocalsOrMyInstanceFieldsControlFlowPolicy.isLocalOrMyInstanceReference(reference); } - final PsiElement innerClass = getInnerClassVariableReferencedFrom(variable, context); - if (innerClass instanceof PsiClass) { - if (variable instanceof PsiParameter) { - final PsiElement parent = variable.getParent(); - if (parent instanceof PsiParameterList && parent.getParent() instanceof PsiLambdaExpression && - notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter) variable).getDeclarationScope()))) { - return null; - } - } - final boolean isToBeEffectivelyFinal = languageLevel.isAtLeast(LanguageLevel.JDK_1_8); - if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, innerClass, context)) { - return null; - } - final String description = JavaErrorBundle.message(isToBeEffectivelyFinal ? "variable.must.be.final.or.effectively.final" : "variable.must.be.final", context.getText()); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, innerClass)); - return highlightInfo; - } - return checkWriteToFinalInsideLambda(variable, context); - } - - private static HighlightInfo checkWriteToFinalInsideLambda(@Nonnull PsiVariable variable, @Nonnull PsiJavaCodeReferenceElement context) { - final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); - if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { - final PsiElement parent = variable.getParent(); - if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkVariableMustBeFinal( + PsiVariable variable, + PsiJavaCodeReferenceElement context, + LanguageLevel languageLevel + ) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + if (getInnerClassVariableReferencedFrom(variable, context) instanceof PsiClass innerClass) { + if (variable instanceof PsiParameter param + && variable.getParent() instanceof PsiParameterList paramList + && paramList instanceof PsiLambdaExpression + && notAccessedForWriting(variable, new LocalSearchScope(param.getDeclarationScope()))) { + return null; + } + boolean isToBeEffectivelyFinal = languageLevel.isAtLeast(LanguageLevel.JDK_1_8); + if (isToBeEffectivelyFinal && isEffectivelyFinal(variable, innerClass, context)) { + return null; + } + LocalizeValue description = isToBeEffectivelyFinal + ? JavaCompilationErrorLocalize.variableMustBeEffectivelyFinal(context.getText()) + : JavaCompilationErrorLocalize.variableMustBeFinal(context.getText()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(context) + .descriptionAndTooltip(description) + .registerFix(QuickFixFactory.getInstance().createVariableAccessFromInnerClassFix( + variable, + getInnerClassVariableReferencedFrom(variable, context) + )); + } + return checkWriteToFinalInsideLambda(variable, context); + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkWriteToFinalInsideLambda( + PsiVariable variable, + PsiJavaCodeReferenceElement context + ) { + PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(context, PsiLambdaExpression.class); + if (lambdaExpression != null && !PsiTreeUtil.isAncestor(lambdaExpression, variable, true)) { + PsiElement parent = variable.getParent(); + if (parent instanceof PsiParameterList && parent.getParent() == lambdaExpression) { + return null; + } + if (!isEffectivelyFinal(variable, lambdaExpression, context)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(context) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableMustBeEffectivelyFinalLambda()) + .registerFix(QuickFixFactory.getInstance().createVariableAccessFromInnerClassFix(variable, lambdaExpression)); + } + } return null; - } - if (!isEffectivelyFinal(variable, lambdaExpression, context)) { - String text = JavaErrorBundle.message("lambda.variable.must.be.final"); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(context).descriptionAndTooltip(text).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createVariableAccessFromInnerClassFix(variable, lambdaExpression)); - return highlightInfo; - } } - return null; - } - - public static boolean isEffectivelyFinal(@Nonnull PsiVariable variable, @Nonnull PsiElement scope, @Nullable PsiJavaCodeReferenceElement context) { - boolean effectivelyFinal; - if (variable instanceof PsiParameter) { - effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(((PsiParameter) variable).getDeclarationScope())); - } else { - final ControlFlow controlFlow; - try { - PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context); - if (codeBlock == null) { - return true; - } - controlFlow = getControlFlow(codeBlock); - } catch (AnalysisCanceledException e) { - return true; - } - final List readBeforeWriteLocals = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); - for (PsiReferenceExpression expression : readBeforeWriteLocals) { - if (expression.resolve() == variable) { - return PsiUtil.isAccessedForReading(expression); + @RequiredReadAction + public static boolean isEffectivelyFinal( + PsiVariable variable, + PsiElement scope, + @Nullable PsiJavaCodeReferenceElement context + ) { + boolean effectivelyFinal; + if (variable instanceof PsiParameter param) { + effectivelyFinal = notAccessedForWriting(param, new LocalSearchScope(param.getDeclarationScope())); } - } + else { + ControlFlow controlFlow; + try { + PsiElement codeBlock = PsiUtil.getVariableCodeBlock(variable, context); + if (codeBlock == null) { + return true; + } + controlFlow = getControlFlow(codeBlock); + } + catch (AnalysisCanceledException e) { + return true; + } - final Collection initializedTwice = ControlFlowUtil.getInitializedTwice(controlFlow); - effectivelyFinal = !initializedTwice.contains(new ControlFlowUtil.VariableInfo(variable, null)); - if (effectivelyFinal) { - effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(scope)); - } - } - return effectivelyFinal; - } + List readBeforeWriteLocals = ControlFlowUtil.getReadBeforeWriteLocals(controlFlow); + for (PsiReferenceExpression expression : readBeforeWriteLocals) { + if (expression.resolve() == variable) { + return PsiUtil.isAccessedForReading(expression); + } + } - private static boolean notAccessedForWriting(@Nonnull PsiVariable variable, @Nonnull LocalSearchScope searchScope) { - for (PsiReference reference : ReferencesSearch.search(variable, searchScope)) { - final PsiElement element = reference.getElement(); - if (element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression) element)) { - return false; - } - } - return true; - } - - @Nullable - public static PsiElement getInnerClassVariableReferencedFrom(@Nonnull PsiVariable variable, @Nonnull PsiElement context) { - final PsiElement[] scope; - if (variable instanceof PsiResourceVariable) { - scope = ((PsiResourceVariable) variable).getDeclarationScope(); - } else if (variable instanceof PsiLocalVariable) { - final PsiElement parent = variable.getParent(); - scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement - } else if (variable instanceof PsiParameter) { - scope = new PsiElement[]{((PsiParameter) variable).getDeclarationScope()}; - } else { - scope = new PsiElement[]{variable.getParent()}; + Collection initializedTwice = ControlFlowUtil.getInitializedTwice(controlFlow); + effectivelyFinal = !initializedTwice.contains(new ControlFlowUtil.VariableInfo(variable, null)); + if (effectivelyFinal) { + effectivelyFinal = notAccessedForWriting(variable, new LocalSearchScope(scope)); + } + } + return effectivelyFinal; } - if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) { - return null; + + @RequiredReadAction + private static boolean notAccessedForWriting(PsiVariable variable, LocalSearchScope searchScope) { + for (PsiReference reference : ReferencesSearch.search(variable, searchScope)) { + if (reference.getElement() instanceof PsiExpression expr && PsiUtil.isAccessedForWriting(expr)) { + return false; + } + } + return true; } - PsiElement parent = context.getParent(); - PsiElement prevParent = context; - outer: - while (parent != null) { - for (PsiElement scopeElement : scope) { - if (parent.equals(scopeElement)) { - break outer; - } - } - if (parent instanceof PsiClass && !(prevParent instanceof PsiExpressionList && parent instanceof PsiAnonymousClass)) { - return parent; - } - if (parent instanceof PsiLambdaExpression) { - return parent; - } - prevParent = parent; - parent = parent.getParent(); + @Nullable + public static PsiElement getInnerClassVariableReferencedFrom(PsiVariable variable, PsiElement context) { + PsiElement[] scope; + if (variable instanceof PsiResourceVariable resourceVar) { + scope = resourceVar.getDeclarationScope(); + } + else if (variable instanceof PsiLocalVariable) { + PsiElement parent = variable.getParent(); + scope = new PsiElement[]{parent != null ? parent.getParent() : null}; // code block or for statement + } + else if (variable instanceof PsiParameter param) { + scope = new PsiElement[]{param.getDeclarationScope()}; + } + else { + scope = new PsiElement[]{variable.getParent()}; + } + if (scope.length < 1 || scope[0] == null || scope[0].getContainingFile() != context.getContainingFile()) { + return null; + } + + PsiElement parent = context.getParent(); + PsiElement prevParent = context; + outer: + while (parent != null) { + for (PsiElement scopeElement : scope) { + if (parent.equals(scopeElement)) { + break outer; + } + } + if (parent instanceof PsiClass && !(prevParent instanceof PsiExpressionList && parent instanceof PsiAnonymousClass)) { + return parent; + } + if (parent instanceof PsiLambdaExpression) { + return parent; + } + prevParent = parent; + parent = parent.getParent(); + } + return null; } - return null; - } - - - @Nullable - public static HighlightInfo checkInitializerCompleteNormally(@Nonnull PsiClassInitializer initializer) { - final PsiCodeBlock body = initializer.getBody(); - // unhandled exceptions already reported - try { - final ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); - final int completionReasons = ControlFlowUtil.getCompletionReasons(controlFlow, 0, controlFlow.getSize()); - if ((completionReasons & ControlFlowUtil.NORMAL_COMPLETION_REASON) == 0) { - String description = JavaErrorBundle.message("initializer.must.be.able.to.complete.normally"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(body).descriptionAndTooltip(description).create(); - } - } catch (AnalysisCanceledException e) { - // incomplete code + + @Nullable + @RequiredReadAction + public static HighlightInfo checkInitializerCompleteNormally(PsiClassInitializer initializer) { + PsiCodeBlock body = initializer.getBody(); + // unhandled exceptions already reported + try { + ControlFlow controlFlow = getControlFlowNoConstantEvaluate(body); + int completionReasons = ControlFlowUtil.getCompletionReasons(controlFlow, 0, controlFlow.getSize()); + if ((completionReasons & ControlFlowUtil.NORMAL_COMPLETION_REASON) == 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(body) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classInitializerMustCompleteNormally()) + .create(); + } + } + catch (AnalysisCanceledException e) { + // incomplete code + } + return null; } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMessageUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMessageUtil.java index 7c974853f9..428f899c54 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMessageUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMessageUtil.java @@ -18,47 +18,46 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiFormatUtil; import com.intellij.java.language.psi.util.PsiFormatUtilBase; -import consulo.language.LangBundle; +import consulo.annotation.access.RequiredReadAction; +import consulo.language.localize.LanguageLocalize; import consulo.language.psi.PsiDirectory; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.localize.LocalizeValue; public class HighlightMessageUtil { - private HighlightMessageUtil() { - } + private static final LocalizeValue QUESTION_MARK = LocalizeValue.of("?"); - @Nullable - public static String getSymbolName(@Nonnull PsiElement symbol, PsiSubstitutor substitutor) { - String symbolName = null; - - if (symbol instanceof PsiClass) { - if (symbol instanceof PsiAnonymousClass) { - symbolName = LangBundle.message("java.terms.anonymous.class"); - } else { - symbolName = ((PsiClass) symbol).getQualifiedName(); - if (symbolName == null) { - symbolName = ((PsiClass) symbol).getName(); - } - } - } else if (symbol instanceof PsiMethod) { - symbolName = PsiFormatUtil.formatMethod((PsiMethod) symbol, - substitutor, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, - PsiFormatUtilBase.SHOW_TYPE | PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES); - } else if (symbol instanceof PsiVariable) { - symbolName = ((PsiVariable) symbol).getName(); - } else if (symbol instanceof PsiJavaPackage) { - symbolName = ((PsiJavaPackage) symbol).getQualifiedName(); - } else if (symbol instanceof PsiFile) { - PsiDirectory directory = ((PsiFile) symbol).getContainingDirectory(); - PsiJavaPackage aPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); - symbolName = aPackage == null ? null : aPackage.getQualifiedName(); - } else if (symbol instanceof PsiDirectory) { - symbolName = ((PsiDirectory) symbol).getName(); + private HighlightMessageUtil() { } - return symbolName; - } + @RequiredReadAction + public static LocalizeValue getSymbolName(PsiElement symbol, PsiSubstitutor substitutor) { + return switch (symbol) { + case PsiAnonymousClass ac -> LanguageLocalize.javaTermsAnonymousClass(); + case PsiClass c -> { + String n = c.getQualifiedName(); + if (n == null) { + n = c.getName(); + } + yield LocalizeValue.of(n); + } + case PsiMethod m -> LocalizeValue.of(PsiFormatUtil.formatMethod( + m, + substitutor, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE | PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES + )); + case PsiVariable v -> LocalizeValue.of(v.getName()); + case PsiJavaPackage jp -> LocalizeValue.of(jp.getQualifiedName()); + case PsiFile f -> { + PsiDirectory directory = f.getContainingDirectory(); + PsiJavaPackage aPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); + yield aPackage == null ? QUESTION_MARK : LocalizeValue.of(aPackage.getQualifiedName()); + } + case PsiDirectory d -> LocalizeValue.of(d.getName()); + default -> QUESTION_MARK; + }; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 4ae8f5273f..4f98595684 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -18,10 +18,10 @@ import com.intellij.java.analysis.codeInsight.intention.QuickFixFactory; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix.*; import com.intellij.java.analysis.impl.psi.util.PsiMatchers; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.language.impl.codeInsight.ExceptionUtil; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.impl.PsiSuperMethodImplUtil; import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; import com.intellij.java.language.projectRoots.JavaSdkVersion; @@ -30,37 +30,40 @@ import com.intellij.java.language.psi.infos.MethodCandidateInfo; import com.intellij.java.language.psi.util.*; import com.intellij.java.language.util.VisibilityUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.dumb.IndexNotReadyException; import consulo.document.util.TextRange; import consulo.document.util.TextRangeUtil; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.DaemonBundle; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.inspection.LocalQuickFixOnPsiElementAsIntentionAdapter; import consulo.language.editor.intention.IntentionAction; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.QuickFixActionRegistrar; import consulo.language.editor.intention.UnresolvedReferenceQuickFixProvider; +import consulo.language.editor.localize.DaemonLocalize; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoHolder; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.*; import consulo.language.psi.util.PsiMatcherImpl; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; +import consulo.ui.ex.JBColor; import consulo.ui.ex.awt.UIUtil; import consulo.ui.ex.awt.util.ColorUtil; +import consulo.ui.style.StyleManager; import consulo.util.collection.MostlySingularMultiMap; import consulo.util.lang.Comparing; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.util.lang.xml.XmlStringUtil; import consulo.virtualFileSystem.VirtualFile; +import org.jspecify.annotations.Nullable; import org.intellij.lang.annotations.Language; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.text.MessageFormat; import java.util.*; @@ -68,1788 +71,2232 @@ * Highlight method problems * * @author cdr - * Date: Aug 14, 2002 + * @since 2002-08-14 */ public class HighlightMethodUtil { - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - private static final String MISMATCH_COLOR = UIUtil.isUnderDarcula() ? "ff6464" : "red"; - private static final Logger LOG = Logger.getInstance(HighlightMethodUtil.class); - - private HighlightMethodUtil() { - } - - public static String createClashMethodMessage(PsiMethod method1, PsiMethod method2, boolean showContainingClasses) { - @NonNls String pattern = showContainingClasses ? "clash.methods.message.show.classes" : "clash.methods.message"; - return JavaErrorBundle.message(pattern, JavaHighlightUtil.formatMethod(method1), JavaHighlightUtil.formatMethod(method2), HighlightUtil.formatClass(method1.getContainingClass()), - HighlightUtil.formatClass(method2.getContainingClass())); - } - - public static HighlightInfo checkMethodWeakerPrivileges(@Nonnull MethodSignatureBackedByPsiMethod methodSignature, - @Nonnull List superMethodSignatures, - boolean includeRealPositionInfo, - @Nonnull PsiFile containingFile) { - PsiMethod method = methodSignature.getMethod(); - PsiModifierList modifierList = method.getModifierList(); - if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) { - return null; - } - int accessLevel = PsiUtil.getAccessLevel(modifierList); - String accessModifier = PsiUtil.getAccessModifier(accessLevel); - for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { - PsiMethod superMethod = superMethodSignature.getMethod(); - if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(superMethod, method)) { - continue; - } - if (!PsiUtil.isAccessible(containingFile.getProject(), superMethod, method, null)) { - continue; - } - if (!includeRealPositionInfo && MethodSignatureUtil.isSuperMethod(superMethod, method)) { - continue; - } - HighlightInfo info = isWeaker(method, modifierList, accessModifier, accessLevel, superMethod, includeRealPositionInfo); - if (info != null) { - return info; - } - } - return null; - } - - private static HighlightInfo isWeaker(final PsiMethod method, - final PsiModifierList modifierList, - final String accessModifier, - final int accessLevel, - final PsiMethod superMethod, - final boolean includeRealPositionInfo) { - int superAccessLevel = PsiUtil.getAccessLevel(superMethod.getModifierList()); - if (accessLevel < superAccessLevel) { - String description = JavaErrorBundle.message("weaker.privileges", createClashMethodMessage(method, superMethod, true), VisibilityUtil.toPresentableText(accessModifier), PsiUtil - .getAccessModifier(superAccessLevel)); - TextRange textRange; - if (includeRealPositionInfo) { - PsiElement keyword = PsiUtil.findModifierInList(modifierList, accessModifier); - if (keyword == null) { - // in case of package-private or some crazy third-party plugin where some access modifier implied even if it's absent - textRange = method.getNameIdentifier().getTextRange(); - } else { - textRange = keyword.getTextRange(); - } - } else { - textRange = TextRange.EMPTY_RANGE; - } - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(method, PsiUtil.getAccessModifier(superAccessLevel), true, false)); - return highlightInfo; - } - return null; - } - - - public static HighlightInfo checkMethodIncompatibleReturnType(@Nonnull MethodSignatureBackedByPsiMethod methodSignature, - @Nonnull List superMethodSignatures, - boolean includeRealPositionInfo) { - return checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, includeRealPositionInfo, null); - } - - public static HighlightInfo checkMethodIncompatibleReturnType(@Nonnull MethodSignatureBackedByPsiMethod methodSignature, - @Nonnull List superMethodSignatures, - boolean includeRealPositionInfo, - @Nullable TextRange textRange) { - PsiMethod method = methodSignature.getMethod(); - PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType()); - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return null; - } - for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { - PsiMethod superMethod = superMethodSignature.getMethod(); - PsiType declaredReturnType = superMethod.getReturnType(); - PsiType superReturnType = declaredReturnType; - if (superMethodSignature.isRaw()) { - superReturnType = TypeConversionUtil.erasure(declaredReturnType); - } - if (returnType == null || superReturnType == null || method == superMethod) { - continue; - } - PsiClass superClass = superMethod.getContainingClass(); - if (superClass == null) { - continue; - } - TextRange toHighlight = textRange != null ? textRange : includeRealPositionInfo ? method.getReturnTypeElement().getTextRange() : TextRange.EMPTY_RANGE; - HighlightInfo highlightInfo = checkSuperMethodSignature(superMethod, superMethodSignature, superReturnType, method, methodSignature, returnType, JavaErrorBundle.message("incompatible" + - ".return.type"), toHighlight, PsiUtil.getLanguageLevel(aClass)); - if (highlightInfo != null) { - return highlightInfo; - } + private static final Logger LOG = Logger.getInstance(HighlightMethodUtil.class); + + private HighlightMethodUtil() { + } + + public static LocalizeValue createClashMethodMessage(PsiMethod method1, PsiMethod method2, boolean showContainingClasses) { + String m1 = JavaHighlightUtil.formatMethod(method1); + String m2 = JavaHighlightUtil.formatMethod(method2); + return showContainingClasses + ? JavaCompilationErrorLocalize.clashMethodsMessageShowClasses( + m1, + m2, + HighlightUtil.formatClass(method1.getContainingClass()), + HighlightUtil.formatClass(method2.getContainingClass()) + ) + : JavaCompilationErrorLocalize.clashMethodsMessage(m1, m2); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMethodWeakerPrivileges( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean includeRealPositionInfo, + PsiFile containingFile + ) { + PsiMethod method = methodSignature.getMethod(); + PsiModifierList modifierList = method.getModifierList(); + if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) { + return null; + } + int accessLevel = PsiUtil.getAccessLevel(modifierList); + String accessModifier = PsiUtil.getAccessModifier(accessLevel); + for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + if (method.isAbstract() && !MethodSignatureUtil.isSuperMethod(superMethod, method)) { + continue; + } + if (!PsiUtil.isAccessible(containingFile.getProject(), superMethod, method, null)) { + continue; + } + if (!includeRealPositionInfo && MethodSignatureUtil.isSuperMethod(superMethod, method)) { + continue; + } + HighlightInfo.Builder hlBuilder = + isWeaker(method, modifierList, accessModifier, accessLevel, superMethod, includeRealPositionInfo); + if (hlBuilder != null) { + return hlBuilder; + } + } + return null; } - return null; - } - - private static HighlightInfo checkSuperMethodSignature(@Nonnull PsiMethod superMethod, - @Nonnull MethodSignatureBackedByPsiMethod superMethodSignature, - PsiType superReturnType, - @Nonnull PsiMethod method, - @Nonnull MethodSignatureBackedByPsiMethod methodSignature, - @Nonnull PsiType returnType, - @Nonnull String detailMessage, - @Nonnull TextRange range, - @Nonnull LanguageLevel languageLevel) { - if (superReturnType == null) { - return null; - } - final PsiClass superContainingClass = superMethod.getContainingClass(); - if (superContainingClass != null && JavaClassNames.JAVA_LANG_OBJECT.equals(superContainingClass.getQualifiedName()) && !superMethod.hasModifierProperty(PsiModifier.PUBLIC)) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass != null && containingClass.isInterface() && !superContainingClass.isInterface()) { + @RequiredReadAction + private static HighlightInfo.@Nullable Builder isWeaker( + PsiMethod method, + PsiModifierList modifierList, + String accessModifier, + int accessLevel, + PsiMethod superMethod, + boolean includeRealPositionInfo + ) { + int superAccessLevel = PsiUtil.getAccessLevel(superMethod.getModifierList()); + if (accessLevel < superAccessLevel) { + TextRange textRange; + if (includeRealPositionInfo) { + PsiElement keyword = PsiUtil.findModifierInList(modifierList, accessModifier); + if (keyword == null) { + // in case of package-private or some crazy third-party plugin where some access modifier implied even if it's absent + textRange = method.getNameIdentifier().getTextRange(); + } + else { + textRange = keyword.getTextRange(); + } + } + else { + textRange = TextRange.EMPTY_RANGE; + } + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(JavaErrorLocalize.weakerPrivileges( + createClashMethodMessage(method, superMethod, true), + VisibilityUtil.toPresentableText(accessModifier), + PsiUtil.getAccessModifier(superAccessLevel) + )) + .registerFix(factory.createModifierFixBuilder(method).add(PsiUtil.getAccessModifier(superAccessLevel)).create()); + } return null; - } } - PsiType substitutedSuperReturnType; - final boolean isJdk15 = languageLevel.isAtLeast(LanguageLevel.JDK_1_5); - if (isJdk15 && !superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5 - PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superMethodSignature); - substitutedSuperReturnType = unifyingSubstitutor == null ? superReturnType : unifyingSubstitutor.substitute(superReturnType); - } else { - substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType)); - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMethodIncompatibleReturnType( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean includeRealPositionInfo + ) { + return checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, includeRealPositionInfo, null); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMethodIncompatibleReturnType( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean includeRealPositionInfo, + @Nullable TextRange textRange + ) { + PsiMethod method = methodSignature.getMethod(); + PsiType returnType = methodSignature.getSubstitutor().substitute(method.getReturnType()); + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return null; + } + for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + PsiType declaredReturnType = superMethod.getReturnType(); + PsiType superReturnType = declaredReturnType; + if (superMethodSignature.isRaw()) { + superReturnType = TypeConversionUtil.erasure(declaredReturnType); + } + if (returnType == null || superReturnType == null || method == superMethod) { + continue; + } + PsiClass superClass = superMethod.getContainingClass(); + if (superClass == null) { + continue; + } + TextRange toHighlight = textRange != null ? textRange + : includeRealPositionInfo ? method.getReturnTypeElement().getTextRange() : TextRange.EMPTY_RANGE; + HighlightInfo.Builder hlBuilder = checkSuperMethodSignature( + superMethod, + superMethodSignature, + superReturnType, + method, + methodSignature, + returnType, + JavaErrorLocalize.incompatibleReturnType(), + toHighlight, + PsiUtil.getLanguageLevel(aClass) + ); + if (hlBuilder != null) { + return hlBuilder; + } + } - if (returnType.equals(substitutedSuperReturnType)) { - return null; - } - if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) { - if (isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) { return null; - } } - return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType, detailMessage, range); - } - - private static HighlightInfo createIncompatibleReturnTypeMessage(@Nonnull PsiMethod method, - @Nonnull PsiMethod superMethod, - @Nonnull PsiType substitutedSuperReturnType, - @Nonnull PsiType returnType, - @Nonnull String detailMessage, - @Nonnull TextRange textRange) { - String description = MessageFormat.format("{0}; {1}", createClashMethodMessage(method, superMethod, true), detailMessage); - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, substitutedSuperReturnType, false)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSuperMethodReturnFix(superMethod, returnType)); - final PsiClass returnClass = PsiUtil.resolveClassInClassTypeOnly(returnType); - if (returnClass != null && substitutedSuperReturnType instanceof PsiClassType) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createChangeParameterClassFix(returnClass, (PsiClassType) substitutedSuperReturnType)); - } + private static HighlightInfo.@Nullable Builder checkSuperMethodSignature( + PsiMethod superMethod, + MethodSignatureBackedByPsiMethod superMethodSignature, + PsiType superReturnType, + PsiMethod method, + MethodSignatureBackedByPsiMethod methodSignature, + PsiType returnType, + LocalizeValue detailMessage, + TextRange range, + LanguageLevel languageLevel + ) { + if (superReturnType == null) { + return null; + } + PsiClass superContainingClass = superMethod.getContainingClass(); + if (superContainingClass != null + && CommonClassNames.JAVA_LANG_OBJECT.equals(superContainingClass.getQualifiedName()) + && !superMethod.isPublic()) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && containingClass.isInterface() && !superContainingClass.isInterface()) { + return null; + } + } - return errorResult; - } + PsiType substitutedSuperReturnType; + boolean isJdk15 = languageLevel.isAtLeast(LanguageLevel.JDK_1_5); + if (isJdk15 && !superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5 + PsiSubstitutor unifyingSubstitutor = + MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superMethodSignature); + substitutedSuperReturnType = unifyingSubstitutor == null ? superReturnType : unifyingSubstitutor.substitute(superReturnType); + } + else { + substitutedSuperReturnType = TypeConversionUtil.erasure(superMethodSignature.getSubstitutor().substitute(superReturnType)); + } + if (returnType.equals(substitutedSuperReturnType)) { + return null; + } + if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType + && isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) { + return null; + } - public static HighlightInfo checkMethodOverridesFinal(MethodSignatureBackedByPsiMethod methodSignature, List superMethodSignatures) { - PsiMethod method = methodSignature.getMethod(); - for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { - PsiMethod superMethod = superMethodSignature.getMethod(); - HighlightInfo info = checkSuperMethodIsFinal(method, superMethod); - if (info != null) { - return info; - } - } - return null; - } - - private static HighlightInfo checkSuperMethodIsFinal(PsiMethod method, PsiMethod superMethod) { - // strange things happen when super method is from Object and method from interface - if (superMethod.hasModifierProperty(PsiModifier.FINAL)) { - String description = JavaErrorBundle.message("final.method.override", JavaHighlightUtil.formatMethod(method), JavaHighlightUtil.formatMethod(superMethod), HighlightUtil.formatClass - (superMethod.getContainingClass())); - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(superMethod, PsiModifier.FINAL, false, true)); - return errorResult; - } - return null; - } - - public static HighlightInfo checkMethodIncompatibleThrows(MethodSignatureBackedByPsiMethod methodSignature, - List superMethodSignatures, - boolean includeRealPositionInfo, - PsiClass analyzedClass) { - PsiMethod method = methodSignature.getMethod(); - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return null; + return createIncompatibleReturnTypeMessage(method, superMethod, substitutedSuperReturnType, returnType, detailMessage, range); + } + + private static HighlightInfo.Builder createIncompatibleReturnTypeMessage( + PsiMethod method, + PsiMethod superMethod, + PsiType substitutedSuperReturnType, + PsiType returnType, + LocalizeValue detailMessage, + TextRange textRange + ) { + String description = MessageFormat.format("{0}; {1}", createClashMethodMessage(method, superMethod, true), detailMessage); + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(description) + .registerFix(factory.createMethodReturnFix(method, substitutedSuperReturnType, false)) + .registerFix(factory.createSuperMethodReturnFix(superMethod, returnType)); + PsiClass returnClass = PsiUtil.resolveClassInClassTypeOnly(returnType); + if (returnClass != null && substitutedSuperReturnType instanceof PsiClassType classType) { + hlBuilder.registerFix(factory.createChangeParameterClassFix(returnClass, classType)); + } + + return hlBuilder; } - PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(aClass, analyzedClass, PsiSubstitutor.EMPTY); - PsiClassType[] exceptions = method.getThrowsList().getReferencedTypes(); - PsiJavaCodeReferenceElement[] referenceElements; - List exceptionContexts; - if (includeRealPositionInfo) { - exceptionContexts = new ArrayList<>(); - referenceElements = method.getThrowsList().getReferenceElements(); - } else { - exceptionContexts = null; - referenceElements = null; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMethodOverridesFinal( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures + ) { + PsiMethod method = methodSignature.getMethod(); + for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + HighlightInfo.Builder hlBuilder = checkSuperMethodIsFinal(method, superMethod); + if (hlBuilder != null) { + return hlBuilder; + } + } + return null; } - List checkedExceptions = new ArrayList<>(); - for (int i = 0; i < exceptions.length; i++) { - PsiClassType exception = exceptions[i]; - if (exception == null) { - LOG.error("throws: " + method.getThrowsList().getText() + "; method: " + method); - } - if (!ExceptionUtil.isUncheckedException(exception)) { - checkedExceptions.add(exception); - if (includeRealPositionInfo && i < referenceElements.length) { - PsiJavaCodeReferenceElement exceptionRef = referenceElements[i]; - exceptionContexts.add(exceptionRef); - } - } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkSuperMethodIsFinal(PsiMethod method, PsiMethod superMethod) { + // strange things happen when super method is from Object and method from interface + if (superMethod.isFinal()) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getMethodDeclarationTextRange(method)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodOverridesFinal( + JavaHighlightUtil.formatMethod(method), + JavaHighlightUtil.formatMethod(superMethod), + HighlightUtil.formatClass(superMethod.getContainingClass()) + )) + .registerFix(factory.createModifierFixBuilder(superMethod).remove(PsiModifier.FINAL).showContainingClass().create()); + } + return null; } - for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { - PsiMethod superMethod = superMethodSignature.getMethod(); - int index = getExtraExceptionNum(methodSignature, superMethodSignature, checkedExceptions, superSubstitutor); - if (index != -1) { - if (aClass.isInterface()) { - final PsiClass superContainingClass = superMethod.getContainingClass(); - if (superContainingClass != null && !superContainingClass.isInterface()) { - continue; - } - if (superContainingClass != null && !aClass.isInheritor(superContainingClass, true)) { - continue; - } - } - PsiClassType exception = checkedExceptions.get(index); - String description = JavaErrorBundle.message("overridden.method.does.not.throw", createClashMethodMessage(method, superMethod, true), JavaHighlightUtil.formatType(exception)); - TextRange textRange; + + @RequiredReadAction + public static HighlightInfo checkMethodIncompatibleThrows( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean includeRealPositionInfo, + PsiClass analyzedClass + ) { + PsiMethod method = methodSignature.getMethod(); + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return null; + } + PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(aClass, analyzedClass, PsiSubstitutor.EMPTY); + PsiClassType[] exceptions = method.getThrowsList().getReferencedTypes(); + PsiJavaCodeReferenceElement[] referenceElements; + List exceptionContexts; if (includeRealPositionInfo) { - PsiElement exceptionContext = exceptionContexts.get(index); - textRange = exceptionContext.getTextRange(); - } else { - textRange = TextRange.EMPTY_RANGE; - } - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(errorResult, new LocalQuickFixOnPsiElementAsIntentionAdapter(QUICK_FIX_FACTORY.createMethodThrowsFix(method, exception, false, false))); - QuickFixAction.registerQuickFixAction(errorResult, new LocalQuickFixOnPsiElementAsIntentionAdapter(QUICK_FIX_FACTORY.createMethodThrowsFix(superMethod, exception, true, true))); - return errorResult; - } - } - return null; - } - - // return number of exception which was not declared in super method or -1 - private static int getExtraExceptionNum(final MethodSignature methodSignature, - final MethodSignatureBackedByPsiMethod superSignature, - List checkedExceptions, - PsiSubstitutor substitutorForDerivedClass) { - PsiMethod superMethod = superSignature.getMethod(); - PsiSubstitutor substitutorForMethod = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superSignature); - for (int i = 0; i < checkedExceptions.size(); i++) { - final PsiClassType checkedEx = checkedExceptions.get(i); - final PsiType substituted = substitutorForMethod != null ? substitutorForMethod.substitute(checkedEx) : TypeConversionUtil.erasure(checkedEx); - PsiType exception = substitutorForDerivedClass.substitute(substituted); - if (!isMethodThrows(superMethod, substitutorForMethod, exception, substitutorForDerivedClass)) { - return i; - } - } - return -1; - } - - private static boolean isMethodThrows(PsiMethod method, @Nullable PsiSubstitutor substitutorForMethod, PsiType exception, PsiSubstitutor substitutorForDerivedClass) { - PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes(); - for (PsiClassType thrownException1 : thrownExceptions) { - PsiType thrownException = substitutorForMethod != null ? substitutorForMethod.substitute(thrownException1) : TypeConversionUtil.erasure(thrownException1); - thrownException = substitutorForDerivedClass.substitute(thrownException); - if (TypeConversionUtil.isAssignable(thrownException, exception)) { - return true; - } - } - return false; - } - - @Nullable - public static HighlightInfo checkMethodCall(@Nonnull PsiMethodCallExpression methodCall, - @Nonnull PsiResolveHelper resolveHelper, - @Nonnull LanguageLevel languageLevel, - @Nonnull JavaSdkVersion javaSdkVersion, - @Nonnull PsiFile file) { - PsiExpressionList list = methodCall.getArgumentList(); - PsiReferenceExpression referenceToMethod = methodCall.getMethodExpression(); - JavaResolveResult[] results = referenceToMethod.multiResolve(true); - JavaResolveResult resolveResult = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - PsiElement resolved = resolveResult.getElement(); - - boolean isDummy = isDummyConstructorCall(methodCall, resolveHelper, list, referenceToMethod); - if (isDummy) { - return null; - } - HighlightInfo highlightInfo; - - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - if (resolved instanceof PsiMethod && resolveResult.isValidResult()) { - TextRange fixRange = getFixRange(methodCall); - highlightInfo = HighlightUtil.checkUnhandledExceptions(methodCall, fixRange); - - if (highlightInfo == null && ((PsiMethod) resolved).hasModifierProperty(PsiModifier.STATIC)) { - PsiClass containingClass = ((PsiMethod) resolved).getContainingClass(); - if (containingClass != null && containingClass.isInterface()) { - PsiReferenceExpression methodRef = methodCall.getMethodExpression(); - PsiElement element = ObjectUtil.notNull(methodRef.getReferenceNameElement(), methodRef); - highlightInfo = HighlightUtil.checkFeature(element, HighlightUtil.Feature.STATIC_INTERFACE_CALLS, languageLevel, file); - if (highlightInfo == null) { - String message = checkStaticInterfaceMethodCallQualifier(methodRef, resolveResult.getCurrentFileResolveScope(), containingClass); - if (message != null) { - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(message).range(fixRange).create(); - } - } - } - } - - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkInferredIntersections(substitutor, fixRange); - } - - if (highlightInfo == null) { - highlightInfo = checkVarargParameterErasureToBeAccessible((MethodCandidateInfo) resolveResult, methodCall); - } - - if (highlightInfo == null) { - String errorMessage = ((MethodCandidateInfo) resolveResult).getInferenceErrorMessage(); - if (errorMessage != null) { - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip(errorMessage).range(fixRange).create(); - if (highlightInfo != null) { - registerMethodCallIntentions(highlightInfo, methodCall, list, resolveHelper); - registerMethodReturnFixAction(highlightInfo, (MethodCandidateInfo) resolveResult, methodCall); - registerTargetTypeFixesBasedOnApplicabilityInference(methodCall, (MethodCandidateInfo) resolveResult, (PsiMethod) resolved, highlightInfo); - } - } - } - } else { - PsiMethod resolvedMethod = null; - MethodCandidateInfo candidateInfo = null; - if (resolveResult instanceof MethodCandidateInfo) { - candidateInfo = (MethodCandidateInfo) resolveResult; - resolvedMethod = candidateInfo.getElement(); - } - - if (!resolveResult.isAccessible() || !resolveResult.isStaticsScopeCorrect()) { - highlightInfo = null; - } else if (candidateInfo != null && !candidateInfo.isApplicable()) { - if (candidateInfo.isTypeArgumentsApplicable()) { - String methodName = HighlightMessageUtil.getSymbolName(resolved, substitutor); - PsiElement parent = resolved.getParent(); - String containerName = parent == null ? "" : HighlightMessageUtil.getSymbolName(parent, substitutor); - String argTypes = buildArgTypesList(list); - String description = JavaErrorBundle.message("wrong.method.arguments", methodName, containerName, argTypes); - final Ref elementToHighlight = new Ref<>(list); - String toolTip; - if (parent instanceof PsiClass) { - toolTip = buildOneLineMismatchDescription(list, candidateInfo, elementToHighlight); - if (toolTip == null) { - toolTip = createMismatchedArgumentsHtmlTooltip(candidateInfo, list); - } - } else { - toolTip = description; - } - PsiElement element = elementToHighlight.get(); - int navigationShift = element instanceof PsiExpressionList ? +1 : 0; // argument list starts with paren which there is no need to highlight - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).description(description).escapedToolTip(toolTip).navigationShift(navigationShift).create(); - if (highlightInfo != null) { - registerMethodCallIntentions(highlightInfo, methodCall, list, resolveHelper); - registerMethodReturnFixAction(highlightInfo, candidateInfo, methodCall); - registerTargetTypeFixesBasedOnApplicabilityInference(methodCall, candidateInfo, resolvedMethod, highlightInfo); - } - } else { - PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); - PsiReferenceParameterList typeArgumentList = methodCall.getTypeArgumentList(); - PsiSubstitutor applicabilitySubstitutor = candidateInfo.getSubstitutor(false); - if (typeArgumentList.getTypeArguments().length == 0 && resolvedMethod.hasTypeParameters()) { - highlightInfo = GenericsHighlightUtil.checkInferredTypeArguments(resolvedMethod, methodCall, applicabilitySubstitutor); - } else { - highlightInfo = GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, methodExpression, applicabilitySubstitutor, javaSdkVersion); - } - } - } else { - String description = JavaErrorBundle.message("method.call.expected"); - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCall).descriptionAndTooltip(description).create(); - if (resolved instanceof PsiClass) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createInsertNewFix(methodCall, (PsiClass) resolved)); - } else { - TextRange range = getFixRange(methodCall); - QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateMethodFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateAbstractMethodFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createStaticImportMethodFix(methodCall)); - if (resolved instanceof PsiVariable && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(((PsiVariable) resolved).getType()); - if (method != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createInsertMethodCallFix(methodCall, method)); - } - } - } - } - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, substitutor, javaSdkVersion); - } - return highlightInfo; - } - - private static void registerTargetTypeFixesBasedOnApplicabilityInference(@Nonnull PsiMethodCallExpression methodCall, - MethodCandidateInfo resolveResult, - PsiMethod resolved, - HighlightInfo highlightInfo) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent()); - PsiVariable variable = null; - if (parent instanceof PsiVariable) { - variable = (PsiVariable) parent; - } else if (parent instanceof PsiAssignmentExpression) { - PsiExpression lExpression = ((PsiAssignmentExpression) parent).getLExpression(); - if (lExpression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) lExpression).resolve(); - if (resolve instanceof PsiVariable) { - variable = (PsiVariable) resolve; - } - } + exceptionContexts = new ArrayList<>(); + referenceElements = method.getThrowsList().getReferenceElements(); + } + else { + exceptionContexts = null; + referenceElements = null; + } + List checkedExceptions = new ArrayList<>(); + for (int i = 0; i < exceptions.length; i++) { + PsiClassType exception = exceptions[i]; + if (exception == null) { + LOG.error("throws: " + method.getThrowsList().getText() + "; method: " + method); + } + if (!ExceptionUtil.isUncheckedException(exception)) { + checkedExceptions.add(exception); + if (includeRealPositionInfo && i < referenceElements.length) { + PsiJavaCodeReferenceElement exceptionRef = referenceElements[i]; + exceptionContexts.add(exceptionRef); + } + } + } + for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + int index = getExtraExceptionNum(methodSignature, superMethodSignature, checkedExceptions, superSubstitutor); + if (index != -1) { + if (aClass.isInterface()) { + PsiClass superContainingClass = superMethod.getContainingClass(); + if (superContainingClass != null && !superContainingClass.isInterface()) { + continue; + } + if (superContainingClass != null && !aClass.isInheritor(superContainingClass, true)) { + continue; + } + } + PsiClassType exception = checkedExceptions.get(index); + LocalizeValue description = JavaCompilationErrorLocalize.methodInheritanceClashDoesNotThrow( + createClashMethodMessage(method, superMethod, true), + JavaHighlightUtil.formatType(exception) + ); + TextRange textRange; + if (includeRealPositionInfo) { + PsiElement exceptionContext = exceptionContexts.get(index); + textRange = exceptionContext.getTextRange(); + } + else { + textRange = TextRange.EMPTY_RANGE; + } + QuickFixFactory factory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(description) + .registerFix(new LocalQuickFixOnPsiElementAsIntentionAdapter(factory.createMethodThrowsFix( + method, + exception, + false, + false + ))) + .registerFix(new LocalQuickFixOnPsiElementAsIntentionAdapter(factory.createMethodThrowsFix( + superMethod, + exception, + true, + true + ))) + .create(); + } + } + return null; } - if (variable != null) { - PsiType rType = methodCall.getType(); - if (rType != null && !variable.getType().isAssignableFrom(rType)) { - PsiType expectedTypeByApplicabilityConstraints = resolveResult.getSubstitutor(false).substitute(resolved.getReturnType()); - if (expectedTypeByApplicabilityConstraints != null && !expectedTypeByApplicabilityConstraints.equals(rType)) { - HighlightUtil.registerChangeVariableTypeFixes(variable, expectedTypeByApplicabilityConstraints, methodCall, highlightInfo); + // return number of exception which was not declared in super method or -1 + private static int getExtraExceptionNum( + MethodSignature methodSignature, + MethodSignatureBackedByPsiMethod superSignature, + List checkedExceptions, + PsiSubstitutor substitutorForDerivedClass + ) { + PsiMethod superMethod = superSignature.getMethod(); + PsiSubstitutor substitutorForMethod = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature, superSignature); + for (int i = 0; i < checkedExceptions.size(); i++) { + PsiClassType checkedEx = checkedExceptions.get(i); + PsiType substituted = + substitutorForMethod != null ? substitutorForMethod.substitute(checkedEx) : TypeConversionUtil.erasure(checkedEx); + PsiType exception = substitutorForDerivedClass.substitute(substituted); + if (!isMethodThrows(superMethod, substitutorForMethod, exception, substitutorForDerivedClass)) { + return i; + } } - } - } - } - - /* see also PsiReferenceExpressionImpl.hasValidQualifier() */ - @Nullable - private static String checkStaticInterfaceMethodCallQualifier(PsiReferenceExpression ref, PsiElement scope, PsiClass containingClass) { - PsiExpression qualifierExpression = ref.getQualifierExpression(); - if (qualifierExpression == null && (scope instanceof PsiImportStaticStatement || PsiTreeUtil.isAncestor(containingClass, ref, true))) { - return null; + return -1; + } + + private static boolean isMethodThrows( + PsiMethod method, + @Nullable PsiSubstitutor substitutorForMethod, + PsiType exception, + PsiSubstitutor substitutorForDerivedClass + ) { + PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes(); + for (PsiClassType thrownException1 : thrownExceptions) { + PsiType thrownException = substitutorForMethod != null + ? substitutorForMethod.substitute(thrownException1) + : TypeConversionUtil.erasure(thrownException1); + thrownException = substitutorForDerivedClass.substitute(thrownException); + if (TypeConversionUtil.isAssignable(thrownException, exception)) { + return true; + } + } + return false; } - if (qualifierExpression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) qualifierExpression).resolve(); - if (resolve == containingClass) { - return null; - } + @RequiredWriteAction + public static HighlightInfo.@Nullable Builder checkMethodCall( + PsiMethodCallExpression methodCall, + PsiResolveHelper resolveHelper, + LanguageLevel languageLevel, + JavaSdkVersion javaSdkVersion, + PsiFile file + ) { + PsiExpressionList list = methodCall.getArgumentList(); + PsiReferenceExpression referenceToMethod = methodCall.getMethodExpression(); + JavaResolveResult[] results = referenceToMethod.multiResolve(true); + JavaResolveResult resolveResult = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + PsiElement resolved = resolveResult.getElement(); + + boolean isDummy = isDummyConstructorCall(methodCall, resolveHelper, list, referenceToMethod); + if (isDummy) { + return null; + } + HighlightInfo.Builder hlBuilder; + + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + if (resolved instanceof PsiMethod method && resolveResult.isValidResult()) { + TextRange fixRange = getFixRange(methodCall); + hlBuilder = HighlightUtil.checkUnhandledExceptions(methodCall, fixRange); + + if (hlBuilder == null && method.isStatic()) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && containingClass.isInterface()) { + PsiReferenceExpression methodRef = methodCall.getMethodExpression(); + PsiElement element = ObjectUtil.notNull(methodRef.getReferenceNameElement(), methodRef); + hlBuilder = HighlightUtil.checkFeature(element, JavaFeature.STATIC_INTERFACE_CALLS, languageLevel, file); + if (hlBuilder == null) { + LocalizeValue message = + checkStaticInterfaceMethodCallQualifier(methodRef, resolveResult.getCurrentFileResolveScope(), containingClass); + if (message != null) { + hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(message) + .range(fixRange); + } + } + } + } - if (resolve instanceof PsiTypeParameter) { - Set classes = new HashSet<>(); - for (PsiClassType type : ((PsiTypeParameter) resolve).getExtendsListTypes()) { - PsiClass aClass = type.resolve(); - if (aClass != null) { - classes.add(aClass); - } + if (hlBuilder == null) { + hlBuilder = GenericsHighlightUtil.checkInferredIntersections(substitutor, fixRange); + } + + if (hlBuilder == null) { + hlBuilder = checkVarargParameterErasureToBeAccessible((MethodCandidateInfo) resolveResult, methodCall); + } + + if (hlBuilder == null) { + String errorMessage = ((MethodCandidateInfo) resolveResult).getInferenceErrorMessage(); + if (errorMessage != null) { + hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(errorMessage) + .range(fixRange); + registerMethodCallIntentions(hlBuilder, methodCall, list, resolveHelper); + registerMethodReturnFixAction(hlBuilder, (MethodCandidateInfo) resolveResult, methodCall); + registerTargetTypeFixesBasedOnApplicabilityInference( + methodCall, + (MethodCandidateInfo) resolveResult, + method, + hlBuilder + ); + } + } } + else { + PsiMethod resolvedMethod = null; + MethodCandidateInfo candidateInfo = null; + if (resolveResult instanceof MethodCandidateInfo mci) { + candidateInfo = mci; + resolvedMethod = candidateInfo.getElement(); + } - if (classes.size() == 1 && classes.contains(containingClass)) { - return null; + if (!resolveResult.isAccessible() || !resolveResult.isStaticsScopeCorrect()) { + hlBuilder = null; + } + else if (candidateInfo != null && !candidateInfo.isApplicable()) { + if (candidateInfo.isTypeArgumentsApplicable()) { + LocalizeValue methodName = HighlightMessageUtil.getSymbolName(resolved, substitutor); + PsiElement parent = resolved.getParent(); + LocalizeValue containerName = + parent == null ? LocalizeValue.empty() : HighlightMessageUtil.getSymbolName(parent, substitutor); + String argTypes = buildArgTypesList(list); + LocalizeValue description = JavaCompilationErrorLocalize.callWrongArguments(methodName, containerName, argTypes); + SimpleReference elementToHighlight = SimpleReference.create(list); + LocalizeValue toolTip; + if (parent instanceof PsiClass) { + toolTip = buildOneLineMismatchDescription(list, candidateInfo, elementToHighlight); + if (toolTip.isEmpty()) { + toolTip = createMismatchedArgumentsHtmlTooltip(candidateInfo, list); + } + } + else { + toolTip = description; + } + PsiElement element = elementToHighlight.get(); + // argument list starts with paren which there is no need to highlight + int navigationShift = element instanceof PsiExpressionList ? +1 : 0; + hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .description(description) + .escapedToolTip(toolTip) + .navigationShift(navigationShift); + registerMethodCallIntentions(hlBuilder, methodCall, list, resolveHelper); + registerMethodReturnFixAction(hlBuilder, candidateInfo, methodCall); + registerTargetTypeFixesBasedOnApplicabilityInference(methodCall, candidateInfo, resolvedMethod, hlBuilder); + } + else { + PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + PsiReferenceParameterList typeArgumentList = methodCall.getTypeArgumentList(); + PsiSubstitutor applicabilitySubstitutor = candidateInfo.getSubstitutor(false); + if (typeArgumentList.getTypeArguments().length == 0 && resolvedMethod.hasTypeParameters()) { + hlBuilder = + GenericsHighlightUtil.checkInferredTypeArguments(resolvedMethod, methodCall, applicabilitySubstitutor); + } + else { + hlBuilder = GenericsHighlightUtil.checkParameterizedReferenceTypeArguments( + resolved, + methodExpression, + applicabilitySubstitutor, + javaSdkVersion + ); + } + } + } + else { + hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(methodCall) + .descriptionAndTooltip(JavaCompilationErrorLocalize.callExpected()); + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + if (resolved instanceof PsiClass psiClass) { + hlBuilder.registerFix(fixFactory.createInsertNewFix(methodCall, psiClass)); + } + else { + TextRange range = getFixRange(methodCall); + hlBuilder.newFix(fixFactory.createCreateMethodFromUsageFix(methodCall)).fixRange(range).register() + .newFix(fixFactory.createCreateAbstractMethodFromUsageFix(methodCall)).fixRange(range).register() + .newFix(fixFactory.createCreatePropertyFromUsageFix(methodCall)).fixRange(range).register() + .newFix(fixFactory.createStaticImportMethodFix(methodCall)).fixRange(range).register(); + if (resolved instanceof PsiVariable variable && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(variable.getType()); + if (method != null) { + hlBuilder.newFix(fixFactory.createInsertMethodCallFix(methodCall, method)).fixRange(range).register(); + } + } + } + } + } + if (hlBuilder == null) { + hlBuilder = + GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, referenceToMethod, substitutor, javaSdkVersion); + } + return hlBuilder; + } + + @RequiredReadAction + private static void registerTargetTypeFixesBasedOnApplicabilityInference( + PsiMethodCallExpression methodCall, + MethodCandidateInfo resolveResult, + PsiMethod resolved, + HighlightInfo.Builder hlBuilder + ) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent()); + PsiVariable variable = null; + if (parent instanceof PsiVariable var) { + variable = var; + } + else if (parent instanceof PsiAssignmentExpression assignment + && assignment.getLExpression() instanceof PsiReferenceExpression lRefExpr + && lRefExpr.resolve() instanceof PsiVariable lVar) { + variable = lVar; } - } - } - return JavaErrorBundle.message("static.interface.method.call.qualifier"); - } - - private static void registerMethodReturnFixAction(HighlightInfo highlightInfo, MethodCandidateInfo candidate, PsiCall methodCall) { - if (methodCall.getParent() instanceof PsiReturnStatement) { - final PsiMethod containerMethod = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class, true, PsiLambdaExpression.class); - if (containerMethod != null) { - final PsiMethod method = candidate.getElement(); - final PsiExpression methodCallCopy = JavaPsiFacade.getElementFactory(method.getProject()).createExpressionFromText(methodCall.getText(), methodCall); - PsiType methodCallTypeByArgs = methodCallCopy.getType(); - //ensure type params are not included - methodCallTypeByArgs = JavaPsiFacade.getElementFactory(method.getProject()).createRawSubstitutor(method).substitute(methodCallTypeByArgs); - if (methodCallTypeByArgs != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, getFixRange(methodCall), QUICK_FIX_FACTORY.createMethodReturnFix(containerMethod, methodCallTypeByArgs, true)); - } - } - } - } - - private static String buildOneLineMismatchDescription(@Nonnull PsiExpressionList list, @Nonnull MethodCandidateInfo candidateInfo, @Nonnull Ref elementToHighlight) { - final PsiExpression[] expressions = list.getExpressions(); - final PsiMethod resolvedMethod = candidateInfo.getElement(); - final PsiSubstitutor substitutor = candidateInfo.getSubstitutor(); - final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters(); - if (expressions.length == parameters.length && parameters.length > 1) { - int idx = -1; - for (int i = 0; i < expressions.length; i++) { - PsiExpression expression = expressions[i]; - if (expression instanceof PsiMethodCallExpression) { - final JavaResolveResult result = ((PsiCallExpression) expression).resolveMethodGenerics(); - if (result instanceof MethodCandidateInfo && PsiUtil.isLanguageLevel8OrHigher(list) && ((MethodCandidateInfo) result).isToInferApplicability() && ((MethodCandidateInfo) result) - .getInferenceErrorMessage() == null) { - continue; - } - } - if (!TypeConversionUtil.areTypesAssignmentCompatible(substitutor.substitute(parameters[i].getType()), expression)) { - if (idx != -1) { - idx = -1; - break; - } else { - idx = i; - } - } - } - - if (idx > -1) { - final PsiExpression wrongArg = expressions[idx]; - final PsiType argType = wrongArg.getType(); - if (argType != null) { - elementToHighlight.set(wrongArg); - final String message = JavaErrorBundle.message("incompatible.call.types", idx + 1, substitutor.substitute(parameters[idx].getType()).getCanonicalText(), argType - .getCanonicalText()); - - return XmlStringUtil.wrapInHtml("" + XmlStringUtil.escapeString(message) + " " + DaemonBundle.message("inspection.extended.description") + ""); - } - } - } - return null; - } - - public static boolean isDummyConstructorCall(PsiMethodCallExpression methodCall, PsiResolveHelper resolveHelper, PsiExpressionList list, PsiReferenceExpression referenceToMethod) { - boolean isDummy = false; - boolean isThisOrSuper = referenceToMethod.getReferenceNameElement() instanceof PsiKeyword; - if (isThisOrSuper) { - // super(..) or this(..) - if (list.getExpressions().length == 0) { // implicit ctr call - CandidateInfo[] candidates = resolveHelper.getReferencedMethodCandidates(methodCall, true); - if (candidates.length == 1 && !candidates[0].getElement().isPhysical()) { - isDummy = true;// dummy constructor + if (variable != null) { + PsiType rType = methodCall.getType(); + if (rType != null && !variable.getType().isAssignableFrom(rType)) { + PsiType expectedTypeByApplicabilityConstraints = resolveResult.getSubstitutor(false).substitute(resolved.getReturnType()); + if (expectedTypeByApplicabilityConstraints != null && !expectedTypeByApplicabilityConstraints.equals(rType)) { + HighlightUtil.registerChangeVariableTypeFixes( + variable, + expectedTypeByApplicabilityConstraints, + methodCall, + hlBuilder + ); + } + } } - } - } - return isDummy; - } - - @Nullable - public static HighlightInfo checkAmbiguousMethodCallIdentifier(@Nonnull PsiReferenceExpression referenceToMethod, - @Nonnull JavaResolveResult[] resolveResults, - @Nonnull PsiExpressionList list, - final PsiElement element, - @Nonnull JavaResolveResult resolveResult, - @Nonnull PsiMethodCallExpression methodCall, - @Nonnull PsiResolveHelper resolveHelper, - @Nonnull LanguageLevel languageLevel, - @Nonnull PsiFile file) { - MethodCandidateInfo methodCandidate1 = null; - MethodCandidateInfo methodCandidate2 = null; - for (JavaResolveResult result : resolveResults) { - if (!(result instanceof MethodCandidateInfo)) { - continue; - } - MethodCandidateInfo candidate = (MethodCandidateInfo) result; - if (candidate.isApplicable() && !candidate.getElement().isConstructor()) { - if (methodCandidate1 == null) { - methodCandidate1 = candidate; - } else { - methodCandidate2 = candidate; - break; - } - } } - MethodCandidateInfo[] candidates = toMethodCandidates(resolveResults); - HighlightInfoType highlightInfoType = HighlightInfoType.ERROR; - if (methodCandidate2 != null) { - return null; - } - String description; - PsiElement elementToHighlight = ObjectUtil.notNull(referenceToMethod.getReferenceNameElement(), referenceToMethod); - if (element != null && !resolveResult.isAccessible()) { - description = HighlightUtil.buildProblemWithAccessDescription(referenceToMethod, resolveResult); - } else if (element != null && !resolveResult.isStaticsScopeCorrect()) { - if (element instanceof PsiMethod && ((PsiMethod) element).hasModifierProperty(PsiModifier.STATIC)) { - PsiClass containingClass = ((PsiMethod) element).getContainingClass(); - if (containingClass != null && containingClass.isInterface()) { - HighlightInfo info = HighlightUtil.checkFeature(elementToHighlight, HighlightUtil.Feature.STATIC_INTERFACE_CALLS, languageLevel, file); - if (info != null) { - return info; - } - description = checkStaticInterfaceMethodCallQualifier(referenceToMethod, resolveResult.getCurrentFileResolveScope(), containingClass); - if (description != null) { - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(highlightInfoType).range(elementToHighlight).description(description).escapedToolTip(XmlStringUtil.escapeString - (description)).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAccessStaticViaInstanceFix(referenceToMethod, resolveResult)); - return highlightInfo; - } - } - } - - description = HighlightUtil.buildProblemWithStaticDescription(element); - } else { - String methodName = referenceToMethod.getReferenceName() + buildArgTypesList(list); - description = JavaErrorBundle.message("cannot.resolve.method", methodName); - if (candidates.length == 0) { - highlightInfoType = HighlightInfoType.WRONG_REF; - } else { - return null; - } - } + /* see also PsiReferenceExpressionImpl.hasValidQualifier() */ + @Nullable + @RequiredReadAction + private static LocalizeValue checkStaticInterfaceMethodCallQualifier( + PsiReferenceExpression ref, + PsiElement scope, + PsiClass containingClass + ) { + PsiExpression qualifierExpression = ref.getQualifierExpression(); + if (qualifierExpression == null && (scope instanceof PsiImportStaticStatement + || PsiTreeUtil.isAncestor(containingClass, ref, true))) { + return null; + } - String toolTip = XmlStringUtil.escapeString(description); - HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(elementToHighlight).description(description).escapedToolTip(toolTip).create(); - registerMethodCallIntentions(info, methodCall, list, resolveHelper); - if (element != null && !resolveResult.isStaticsScopeCorrect()) { - HighlightUtil.registerStaticProblemQuickFixAction(element, info, referenceToMethod); - } + if (qualifierExpression instanceof PsiReferenceExpression qRefExpr) { + PsiElement resolve = qRefExpr.resolve(); + if (resolve == containingClass) { + return null; + } - TextRange fixRange = getFixRange(elementToHighlight); - CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - PermuteArgumentsFix.registerFix(info, methodCall, candidates, fixRange); - WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), info); - registerChangeParameterClassFix(methodCall, list, info); - if (candidates.length == 0 && info != null) { - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(methodCall.getMethodExpression(), QuickFixActionRegistrar.create(info)); - } - return info; - } - - @Nullable - public static HighlightInfo checkAmbiguousMethodCallArguments(@Nonnull PsiReferenceExpression referenceToMethod, - @Nonnull JavaResolveResult[] resolveResults, - @Nonnull PsiExpressionList list, - final PsiElement element, - @Nonnull JavaResolveResult resolveResult, - @Nonnull PsiMethodCallExpression methodCall, - @Nonnull PsiResolveHelper resolveHelper, - @Nonnull PsiElement elementToHighlight) { - MethodCandidateInfo methodCandidate1 = null; - MethodCandidateInfo methodCandidate2 = null; - for (JavaResolveResult result : resolveResults) { - if (!(result instanceof MethodCandidateInfo)) { - continue; - } - MethodCandidateInfo candidate = (MethodCandidateInfo) result; - if (candidate.isApplicable() && !candidate.getElement().isConstructor()) { - if (methodCandidate1 == null) { - methodCandidate1 = candidate; - } else { - methodCandidate2 = candidate; - break; - } - } - } - MethodCandidateInfo[] candidates = toMethodCandidates(resolveResults); - - String description; - String toolTip; - HighlightInfoType highlightInfoType = HighlightInfoType.ERROR; - if (methodCandidate2 != null) { - PsiMethod element1 = methodCandidate1.getElement(); - String m1 = PsiFormatUtil.formatMethod(element1, methodCandidate1.getSubstitutor(false), PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase - .SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); - PsiMethod element2 = methodCandidate2.getElement(); - String m2 = PsiFormatUtil.formatMethod(element2, methodCandidate2.getSubstitutor(false), PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase - .SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); - VirtualFile virtualFile1 = PsiUtilCore.getVirtualFile(element1); - VirtualFile virtualFile2 = PsiUtilCore.getVirtualFile(element2); - if (!Comparing.equal(virtualFile1, virtualFile2)) { - if (virtualFile1 != null) { - m1 += " (In " + virtualFile1.getPresentableUrl() + ")"; - } - if (virtualFile2 != null) { - m2 += " (In " + virtualFile2.getPresentableUrl() + ")"; - } - } - description = JavaErrorBundle.message("ambiguous.method.call", m1, m2); - toolTip = createAmbiguousMethodHtmlTooltip(new MethodCandidateInfo[]{ - methodCandidate1, - methodCandidate2 - }); - } else { - if (element != null && !resolveResult.isAccessible()) { - return null; - } - if (element != null && !resolveResult.isStaticsScopeCorrect()) { - return null; - } - String methodName = referenceToMethod.getReferenceName() + buildArgTypesList(list); - description = JavaErrorBundle.message("cannot.resolve.method", methodName); - if (candidates.length == 0) { - return null; - } - toolTip = XmlStringUtil.escapeString(description); - } - HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(elementToHighlight).description(description).escapedToolTip(toolTip).create(); - if (methodCandidate2 == null) { - registerMethodCallIntentions(info, methodCall, list, resolveHelper); - } - if (!resolveResult.isAccessible() && resolveResult.isStaticsScopeCorrect() && methodCandidate2 != null) { - HighlightUtil.registerAccessQuickFixAction((PsiMember) element, referenceToMethod, info, resolveResult.getCurrentFileResolveScope()); - } - if (element != null && !resolveResult.isStaticsScopeCorrect()) { - HighlightUtil.registerStaticProblemQuickFixAction(element, info, referenceToMethod); - } + if (resolve instanceof PsiTypeParameter typeParam) { + Set classes = new HashSet<>(); + for (PsiClassType type : typeParam.getExtendsListTypes()) { + PsiClass aClass = type.resolve(); + if (aClass != null) { + classes.add(aClass); + } + } + + if (classes.size() == 1 && classes.contains(containingClass)) { + return null; + } + } + } - TextRange fixRange = getFixRange(elementToHighlight); - CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - WrapStringWithFileFix.REGISTAR.registerCastActions(candidates, methodCall, info, fixRange); - PermuteArgumentsFix.registerFix(info, methodCall, candidates, fixRange); - WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), info); - registerChangeParameterClassFix(methodCall, list, info); - return info; - } - - @Nonnull - private static MethodCandidateInfo[] toMethodCandidates(@Nonnull JavaResolveResult[] resolveResults) { - List candidateList = new ArrayList<>(resolveResults.length); - - for (JavaResolveResult result : resolveResults) { - if (!(result instanceof MethodCandidateInfo)) { - continue; - } - MethodCandidateInfo candidate = (MethodCandidateInfo) result; - if (candidate.isAccessible()) { - candidateList.add(candidate); - } - } - return candidateList.toArray(new MethodCandidateInfo[candidateList.size()]); - } - - private static void registerMethodCallIntentions(@Nullable HighlightInfo highlightInfo, PsiMethodCallExpression methodCall, PsiExpressionList list, PsiResolveHelper resolveHelper) { - TextRange fixRange = getFixRange(methodCall); - final PsiExpression qualifierExpression = methodCall.getMethodExpression().getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression) { - final PsiElement resolve = ((PsiReferenceExpression) qualifierExpression).resolve(); - if (resolve instanceof PsiClass && ((PsiClass) resolve).getContainingClass() != null && !((PsiClass) resolve).hasModifierProperty(PsiModifier.STATIC)) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix((PsiClass) resolve, PsiModifier.STATIC, true, false)); - } + return JavaErrorLocalize.staticInterfaceMethodCallQualifier(); + } + + @RequiredReadAction + private static void registerMethodReturnFixAction( + HighlightInfo.Builder highlightInfo, + MethodCandidateInfo candidate, + PsiCall methodCall + ) { + if (methodCall.getParent() instanceof PsiReturnStatement) { + PsiMethod containerMethod = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class, true, PsiLambdaExpression.class); + if (containerMethod != null) { + PsiMethod method = candidate.getElement(); + PsiExpression methodCallCopy = + JavaPsiFacade.getElementFactory(method.getProject()).createExpressionFromText(methodCall.getText(), methodCall); + PsiType methodCallTypeByArgs = methodCallCopy.getType(); + //ensure type params are not included + methodCallTypeByArgs = JavaPsiFacade.getElementFactory(method.getProject()) + .createRawSubstitutor(method) + .substitute(methodCallTypeByArgs); + if (methodCallTypeByArgs != null) { + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + highlightInfo.newFix(fixFactory.createMethodReturnFix(containerMethod, methodCallTypeByArgs, true)) + .fixRange(getFixRange(methodCall)) + .register(); + } + } + } } - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateMethodFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateAbstractMethodFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromSuperFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromThisFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateGetterSetterPropertyFromUsageFix(methodCall)); - CandidateInfo[] methodCandidates = resolveHelper.getReferencedMethodCandidates(methodCall, false); - CastMethodArgumentFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - PermuteArgumentsFix.registerFix(highlightInfo, methodCall, methodCandidates, fixRange); - AddTypeArgumentsFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - WrapStringWithFileFix.REGISTAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange); - registerMethodAccessLevelIntentions(methodCandidates, methodCall, list, highlightInfo); - registerChangeMethodSignatureFromUsageIntentions(methodCandidates, list, highlightInfo, fixRange); - RemoveRedundantArgumentsFix.registerIntentions(methodCandidates, list, highlightInfo, fixRange); - ConvertDoubleToFloatFix.registerIntentions(methodCandidates, list, highlightInfo, fixRange); - WrapExpressionFix.registerWrapAction(methodCandidates, list.getExpressions(), highlightInfo); - registerChangeParameterClassFix(methodCall, list, highlightInfo); - if (methodCandidates.length == 0) { - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createStaticImportMethodFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.addMethodQualifierFix(methodCall)); - } - for (IntentionAction action : QUICK_FIX_FACTORY.getVariableTypeFromCallFixes(methodCall, list)) { - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, action); - } - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createReplaceAddAllArrayToCollectionFix(methodCall)); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createSurroundWithArrayFix(methodCall, null)); - QualifyThisArgumentFix.registerQuickFixAction(methodCandidates, methodCall, highlightInfo, fixRange); - - CandidateInfo[] candidates = resolveHelper.getReferencedMethodCandidates(methodCall, true); - ChangeStringLiteralToCharInMethodCallFix.registerFixes(candidates, methodCall, highlightInfo); - } - - private static void registerMethodAccessLevelIntentions(CandidateInfo[] methodCandidates, PsiMethodCallExpression methodCall, PsiExpressionList exprList, HighlightInfo highlightInfo) { - for (CandidateInfo methodCandidate : methodCandidates) { - PsiMethod method = (PsiMethod) methodCandidate.getElement(); - if (!methodCandidate.isAccessible() && PsiUtil.isApplicable(method, methodCandidate.getSubstitutor(), exprList)) { - HighlightUtil.registerAccessQuickFixAction(method, methodCall.getMethodExpression(), highlightInfo, methodCandidate.getCurrentFileResolveScope()); - } - } - } - - @Nonnull - private static String createAmbiguousMethodHtmlTooltip(MethodCandidateInfo[] methodCandidates) { - return JavaErrorBundle.message("ambiguous.method.html.tooltip", methodCandidates[0].getElement().getParameterList().getParametersCount() + 2, createAmbiguousMethodHtmlTooltipMethodRow - (methodCandidates[0]), getContainingClassName(methodCandidates[0]), createAmbiguousMethodHtmlTooltipMethodRow(methodCandidates[1]), getContainingClassName(methodCandidates[1])); - } - - private static String getContainingClassName(final MethodCandidateInfo methodCandidate) { - PsiMethod method = methodCandidate.getElement(); - PsiClass containingClass = method.getContainingClass(); - return containingClass == null ? method.getContainingFile().getName() : HighlightUtil.formatClass(containingClass, false); - } - - @Language("HTML") - private static String createAmbiguousMethodHtmlTooltipMethodRow(final MethodCandidateInfo methodCandidate) { - PsiMethod method = methodCandidate.getElement(); - PsiParameter[] parameters = method.getParameterList().getParameters(); - PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); - @NonNls @Language("HTML") String ms = "" + method.getName() + ""; - - for (int j = 0; j < parameters.length; j++) { - PsiParameter parameter = parameters[j]; - PsiType type = substitutor.substitute(parameter.getType()); - ms += "" + (j == 0 ? "(" : "") + XmlStringUtil.escapeString(type.getPresentableText()) + (j == parameters.length - 1 ? ")" : ",") + ""; - } - if (parameters.length == 0) { - ms += "()"; - } - return ms; - } - - private static String createMismatchedArgumentsHtmlTooltip(MethodCandidateInfo info, PsiExpressionList list) { - PsiMethod method = info.getElement(); - PsiSubstitutor substitutor = info.getSubstitutor(); - PsiClass aClass = method.getContainingClass(); - PsiParameter[] parameters = method.getParameterList().getParameters(); - String methodName = method.getName(); - return createMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass); - } - - private static String createShortMismatchedArgumentsHtmlTooltip(PsiExpressionList list, - @Nullable MethodCandidateInfo info, - PsiParameter[] parameters, - String methodName, - PsiSubstitutor substitutor, - PsiClass aClass) { - PsiExpression[] expressions = list.getExpressions(); - int cols = Math.max(parameters.length, expressions.length); - - @Language("HTML") @NonNls String parensizedName = methodName + (parameters.length == 0 ? "( ) " : ""); - String errorMessage = info != null ? info.getParentInferenceErrorMessage(list) : null; - return JavaErrorBundle.message("argument.mismatch.html.tooltip", cols - parameters.length + 1, parensizedName, HighlightUtil.formatClass(aClass, false), - createMismatchedArgsHtmlTooltipParamsRow(parameters, substitutor, expressions), createMismatchedArgsHtmlTooltipArgumentsRow(expressions, parameters, substitutor, cols), errorMessage - != null ? "
reason: " + XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "
") : ""); - } - - private static String esctrim(@Nonnull String s) { - return XmlStringUtil.escapeString(trimNicely(s)); - } - - private static String trimNicely(String s) { - if (s.length() <= 40) { - return s; - } + @RequiredReadAction + private static LocalizeValue buildOneLineMismatchDescription( + PsiExpressionList list, + MethodCandidateInfo candidateInfo, + SimpleReference elementToHighlight + ) { + PsiExpression[] expressions = list.getExpressions(); + PsiMethod resolvedMethod = candidateInfo.getElement(); + PsiSubstitutor substitutor = candidateInfo.getSubstitutor(); + PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters(); + if (expressions.length == parameters.length && parameters.length > 1) { + int idx = -1; + for (int i = 0; i < expressions.length; i++) { + PsiExpression expression = expressions[i]; + if (expression instanceof PsiMethodCallExpression methodCall + && methodCall.resolveMethodGenerics() instanceof MethodCandidateInfo methodCandidateInfo + && PsiUtil.isLanguageLevel8OrHigher(list) + && methodCandidateInfo.isToInferApplicability() + && methodCandidateInfo.getInferenceErrorMessage() == null) { + continue; + } + if (!TypeConversionUtil.areTypesAssignmentCompatible(substitutor.substitute(parameters[i].getType()), expression)) { + if (idx != -1) { + idx = -1; + break; + } + else { + idx = i; + } + } + } + + if (idx > -1) { + PsiExpression wrongArg = expressions[idx]; + PsiType argType = wrongArg.getType(); + if (argType != null) { + elementToHighlight.set(wrongArg); + LocalizeValue message = JavaErrorLocalize.incompatibleCallTypes( + idx + 1, + substitutor.substitute(parameters[idx].getType()).getCanonicalText(), + argType.getCanonicalText() + ); + + return LocalizeValue.of("" + + message.map(s -> XmlStringUtil.escapeString(s)) + + " XmlStringUtil.escapeString(s)) + + "\"" + (StyleManager.get().getCurrentStyle().isDark() ? " color=\"7AB4C9\" " : "") + ">" + + DaemonLocalize.inspectionExtendedDescription() + "" + ); + } + } + } + return LocalizeValue.empty(); + } + + public static boolean isDummyConstructorCall( + PsiMethodCallExpression methodCall, + PsiResolveHelper resolveHelper, + PsiExpressionList list, + PsiReferenceExpression referenceToMethod + ) { + boolean isDummy = false; + boolean isThisOrSuper = referenceToMethod.getReferenceNameElement() instanceof PsiKeyword; + if (isThisOrSuper) { + // super(..) or this(..) + if (list.getExpressions().length == 0) { // implicit ctr call + CandidateInfo[] candidates = resolveHelper.getReferencedMethodCandidates(methodCall, true); + if (candidates.length == 1 && !candidates[0].getElement().isPhysical()) { + isDummy = true;// dummy constructor + } + } + } + return isDummy; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAmbiguousMethodCallIdentifier( + PsiReferenceExpression referenceToMethod, + JavaResolveResult[] resolveResults, + PsiExpressionList list, + PsiElement element, + JavaResolveResult resolveResult, + PsiMethodCallExpression methodCall, + PsiResolveHelper resolveHelper, + LanguageLevel languageLevel, + PsiFile file + ) { + MethodCandidateInfo methodCandidate1 = null; + MethodCandidateInfo methodCandidate2 = null; + for (JavaResolveResult result : resolveResults) { + if (!(result instanceof MethodCandidateInfo candidate)) { + continue; + } + if (candidate.isApplicable() && !candidate.getElement().isConstructor()) { + if (methodCandidate1 == null) { + methodCandidate1 = candidate; + } + else { + methodCandidate2 = candidate; + break; + } + } + } + MethodCandidateInfo[] candidates = toMethodCandidates(resolveResults); - List wordIndices = TextRangeUtil.getWordIndicesIn(s); - if (wordIndices.size() > 2) { - int firstWordEnd = wordIndices.get(0).getEndOffset(); + HighlightInfoType highlightInfoType = HighlightInfoType.ERROR; + if (methodCandidate2 != null) { + return null; + } + LocalizeValue description; + PsiElement elementToHighlight = ObjectUtil.notNull(referenceToMethod.getReferenceNameElement(), referenceToMethod); + if (element != null && !resolveResult.isAccessible()) { + description = HighlightUtil.buildProblemWithAccessDescription(referenceToMethod, resolveResult); + } + else if (element != null && !resolveResult.isStaticsScopeCorrect()) { + if (element instanceof PsiMethod method && method.isStatic()) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && containingClass.isInterface()) { + HighlightInfo.Builder hlBuilder = + HighlightUtil.checkFeature(elementToHighlight, JavaFeature.STATIC_INTERFACE_CALLS, languageLevel, file); + if (hlBuilder != null) { + return hlBuilder; + } + description = checkStaticInterfaceMethodCallQualifier( + referenceToMethod, + resolveResult.getCurrentFileResolveScope(), + containingClass + ); + if (description != null) { + return HighlightInfo.newHighlightInfo(highlightInfoType) + .range(elementToHighlight) + .description(description) + .escapedToolTip(description.map(s -> XmlStringUtil.escapeString(s))) + .registerFix(QuickFixFactory.getInstance().createAccessStaticViaInstanceFix(referenceToMethod, resolveResult)); + } + } + } + + description = HighlightUtil.buildProblemWithStaticDescription(element); + } + else { + String methodName = referenceToMethod.getReferenceName() + buildArgTypesList(list); + description = JavaCompilationErrorLocalize.methodReferenceUnresolvedMethod(methodName); + if (candidates.length == 0) { + highlightInfoType = HighlightInfoType.WRONG_REF; + } + else { + return null; + } + } - // try firstWord...remainder - for (int i = 1; i < wordIndices.size(); i++) { - int stringLength = firstWordEnd + s.length() - wordIndices.get(i).getStartOffset(); - if (stringLength <= 40) { - return s.substring(0, firstWordEnd) + "..." + s.substring(wordIndices.get(i).getStartOffset()); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(highlightInfoType) + .range(elementToHighlight) + .description(description) + .escapedToolTip(description.map(s -> XmlStringUtil.escapeString(s))); + registerMethodCallIntentions(hlBuilder, methodCall, list, resolveHelper); + if (element != null && !resolveResult.isStaticsScopeCorrect()) { + HighlightUtil.registerStaticProblemQuickFixAction(element, hlBuilder, referenceToMethod); + } + + TextRange fixRange = getFixRange(elementToHighlight); + CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + PermuteArgumentsFix.registerFix(hlBuilder, methodCall, candidates, fixRange); + WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), hlBuilder); + registerChangeParameterClassFix(methodCall, list, hlBuilder); + if (candidates.length == 0) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes( + methodCall.getMethodExpression(), + QuickFixActionRegistrar.create(hlBuilder) + ); + } + return hlBuilder; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAmbiguousMethodCallArguments( + PsiReferenceExpression referenceToMethod, + JavaResolveResult[] resolveResults, + PsiExpressionList list, + PsiElement element, + JavaResolveResult resolveResult, + PsiMethodCallExpression methodCall, + PsiResolveHelper resolveHelper, + PsiElement elementToHighlight + ) { + MethodCandidateInfo methodCandidate1 = null; + MethodCandidateInfo methodCandidate2 = null; + for (JavaResolveResult result : resolveResults) { + if (!(result instanceof MethodCandidateInfo candidate)) { + continue; + } + if (candidate.isApplicable() && !candidate.getElement().isConstructor()) { + if (methodCandidate1 == null) { + methodCandidate1 = candidate; + } + else { + methodCandidate2 = candidate; + break; + } + } + } + MethodCandidateInfo[] candidates = toMethodCandidates(resolveResults); + + LocalizeValue description; + LocalizeValue toolTip; + HighlightInfoType highlightInfoType = HighlightInfoType.ERROR; + if (methodCandidate2 != null) { + PsiMethod element1 = methodCandidate1.getElement(); + String m1 = PsiFormatUtil.formatMethod( + element1, + methodCandidate1.getSubstitutor(false), + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE + ); + PsiMethod element2 = methodCandidate2.getElement(); + String m2 = PsiFormatUtil.formatMethod( + element2, + methodCandidate2.getSubstitutor(false), + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE + ); + VirtualFile virtualFile1 = PsiUtilCore.getVirtualFile(element1); + VirtualFile virtualFile2 = PsiUtilCore.getVirtualFile(element2); + if (!Comparing.equal(virtualFile1, virtualFile2)) { + if (virtualFile1 != null) { + m1 += " (In " + virtualFile1.getPresentableUrl() + ")"; + } + if (virtualFile2 != null) { + m2 += " (In " + virtualFile2.getPresentableUrl() + ")"; + } + } + description = JavaCompilationErrorLocalize.callAmbiguous(m1, m2); + toolTip = createAmbiguousMethodHtmlTooltip(new MethodCandidateInfo[]{methodCandidate1, methodCandidate2}); + } + else { + if (element != null && !resolveResult.isAccessible()) { + return null; + } + if (element != null && !resolveResult.isStaticsScopeCorrect()) { + return null; + } + String methodName = referenceToMethod.getReferenceName() + buildArgTypesList(list); + description = JavaCompilationErrorLocalize.methodReferenceUnresolvedMethod(methodName); + if (candidates.length == 0) { + return null; + } + toolTip = description.map(s -> XmlStringUtil.escapeString(s)); + } + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(highlightInfoType) + .range(elementToHighlight) + .description(description) + .escapedToolTip(toolTip); + if (methodCandidate2 == null) { + registerMethodCallIntentions(hlBuilder, methodCall, list, resolveHelper); } - } + if (!resolveResult.isAccessible() && resolveResult.isStaticsScopeCorrect() && methodCandidate2 != null) { + HighlightUtil.registerAccessQuickFixAction( + (PsiMember) element, + referenceToMethod, + hlBuilder, + elementToHighlight.getTextRange(), + resolveResult.getCurrentFileResolveScope() + ); + } + if (element != null && !resolveResult.isStaticsScopeCorrect()) { + HighlightUtil.registerStaticProblemQuickFixAction(element, hlBuilder, referenceToMethod); + } + + TextRange fixRange = getFixRange(elementToHighlight); + CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + WrapStringWithFileFix.REGISTAR.registerCastActions(candidates, methodCall, hlBuilder, fixRange); + PermuteArgumentsFix.registerFix(hlBuilder, methodCall, candidates, fixRange); + WrapExpressionFix.registerWrapAction(candidates, list.getExpressions(), hlBuilder); + registerChangeParameterClassFix(methodCall, list, hlBuilder); + return hlBuilder; } - // maybe one last word will fit? - if (!wordIndices.isEmpty() && s.length() - wordIndices.get(wordIndices.size() - 1).getStartOffset() <= 40) { - return "..." + s.substring(wordIndices.get(wordIndices.size() - 1).getStartOffset()); + + private static MethodCandidateInfo[] toMethodCandidates(JavaResolveResult[] resolveResults) { + List candidateList = new ArrayList<>(resolveResults.length); + + for (JavaResolveResult result : resolveResults) { + if (result instanceof MethodCandidateInfo candidate && candidate.isAccessible()) { + candidateList.add(candidate); + } + } + return candidateList.toArray(new MethodCandidateInfo[candidateList.size()]); + } + + @RequiredReadAction + private static void registerMethodCallIntentions( + HighlightInfo.@Nullable Builder hlBuilder, + PsiMethodCallExpression methodCall, + PsiExpressionList list, + PsiResolveHelper resolveHelper + ) { + if (hlBuilder == null) { + return; + } + TextRange fixRange = getFixRange(methodCall); + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (methodCall.getMethodExpression().getQualifierExpression() instanceof PsiReferenceExpression qualifierExpr + && qualifierExpr.resolve() instanceof PsiClass psiClass && psiClass.getContainingClass() != null && !psiClass.isStatic()) { + hlBuilder.registerFix(factory.createModifierFixBuilder(psiClass).add(PsiModifier.STATIC).create()); + } + + hlBuilder.newFix(factory.createCreateMethodFromUsageFix(methodCall)).fixRange(fixRange).register() + .newFix(factory.createCreateAbstractMethodFromUsageFix(methodCall)).fixRange(fixRange).register() + .newFix(factory.createCreateConstructorFromSuperFix(methodCall)).fixRange(fixRange).register() + .newFix(factory.createCreateConstructorFromThisFix(methodCall)).fixRange(fixRange).register() + .newFix(factory.createCreatePropertyFromUsageFix(methodCall)).fixRange(fixRange).register() + .newFix(factory.createCreateGetterSetterPropertyFromUsageFix(methodCall)).fixRange(fixRange).register(); + + CandidateInfo[] methodCandidates = resolveHelper.getReferencedMethodCandidates(methodCall, false); + CastMethodArgumentFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + PermuteArgumentsFix.registerFix(hlBuilder, methodCall, methodCandidates, fixRange); + AddTypeArgumentsFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + WrapArrayToArraysAsListFix.REGISTAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + WrapLongWithMathToIntExactFix.REGISTAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + WrapObjectWithOptionalOfNullableFix.REGISTAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + WrapStringWithFileFix.REGISTAR.registerCastActions(methodCandidates, methodCall, hlBuilder, fixRange); + registerMethodAccessLevelIntentions(methodCandidates, methodCall, list, hlBuilder, fixRange); + registerChangeMethodSignatureFromUsageIntentions(methodCandidates, list, hlBuilder, fixRange); + RemoveRedundantArgumentsFix.registerIntentions(methodCandidates, list, hlBuilder, fixRange); + ConvertDoubleToFloatFix.registerIntentions(methodCandidates, list, hlBuilder, fixRange); + WrapExpressionFix.registerWrapAction(methodCandidates, list.getExpressions(), hlBuilder); + registerChangeParameterClassFix(methodCall, list, hlBuilder); + if (methodCandidates.length == 0) { + hlBuilder.newFix(factory.createStaticImportMethodFix(methodCall)).fixRange(fixRange).register(); + hlBuilder.newFix(factory.addMethodQualifierFix(methodCall)).fixRange(fixRange).register(); + } + for (IntentionAction action : factory.getVariableTypeFromCallFixes(methodCall, list)) { + hlBuilder.newFix(action).fixRange(fixRange).register(); + } + hlBuilder.newFix(factory.createReplaceAddAllArrayToCollectionFix(methodCall)).fixRange(fixRange).register(); + hlBuilder.newFix(factory.createSurroundWithArrayFix(methodCall, null)).fixRange(fixRange).register(); + QualifyThisArgumentFix.registerQuickFixAction(methodCandidates, methodCall, hlBuilder, fixRange); + + CandidateInfo[] candidates = resolveHelper.getReferencedMethodCandidates(methodCall, true); + ChangeStringLiteralToCharInMethodCallFix.registerFixes(candidates, methodCall, hlBuilder); + } + + @RequiredReadAction + private static void registerMethodAccessLevelIntentions( + CandidateInfo[] methodCandidates, + PsiMethodCallExpression methodCall, + PsiExpressionList exprList, + HighlightInfo.Builder highlightInfo, + TextRange fixRange + ) { + for (CandidateInfo methodCandidate : methodCandidates) { + PsiMethod method = (PsiMethod) methodCandidate.getElement(); + if (!methodCandidate.isAccessible() && PsiUtil.isApplicable(method, methodCandidate.getSubstitutor(), exprList)) { + HighlightUtil.registerAccessQuickFixAction( + method, + methodCall.getMethodExpression(), + highlightInfo, + fixRange, + methodCandidate.getCurrentFileResolveScope() + ); + } + } } - return StringUtil.last(s, 40, true).toString(); - } - - private static String createMismatchedArgumentsHtmlTooltip(PsiExpressionList list, - MethodCandidateInfo info, - PsiParameter[] parameters, - String methodName, - PsiSubstitutor substitutor, - PsiClass aClass) { - return Math.max(parameters.length, list.getExpressions().length) <= 2 ? createShortMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass) : - createLongMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass); - } - - @SuppressWarnings("StringContatenationInLoop") - @Language("HTML") - private static String createLongMismatchedArgumentsHtmlTooltip(PsiExpressionList list, - @Nullable MethodCandidateInfo info, - PsiParameter[] parameters, - String methodName, - PsiSubstitutor substitutor, - PsiClass aClass) { - PsiExpression[] expressions = list.getExpressions(); - - @SuppressWarnings("NonConstantStringShouldBeStringBuffer") @NonNls String s = "" + "" + "" + ""; - - for (int i = 0; i < Math.max(parameters.length, expressions.length); i++) { - PsiParameter parameter = i < parameters.length ? parameters[i] : null; - PsiExpression expression = i < expressions.length ? expressions[i] : null; - boolean showShort = showShortType(i, parameters, expressions, substitutor); - @NonNls String mismatchColor = showShort ? null : UIUtil.isUnderDarcula() ? "FF6B68" : "red"; - - s += ""; - s += ""; - - s += ""; - - s += ""; - - s += ""; + @RequiredReadAction + private static LocalizeValue createAmbiguousMethodHtmlTooltip(MethodCandidateInfo[] methodCandidates) { + return JavaErrorLocalize.ambiguousMethodHtmlTooltip( + methodCandidates[0].getElement().getParameterList().getParametersCount() + 2, + createAmbiguousMethodHtmlTooltipMethodRow(methodCandidates[0]), + getContainingClassName(methodCandidates[0]), + createAmbiguousMethodHtmlTooltipMethodRow(methodCandidates[1]), + getContainingClassName(methodCandidates[1]) + ); } - s += "
" + "" + methodName + "() in " + - HighlightUtil.formatClass(aClass, false) + " cannot be applied to:" + "
Expected
Parameters:
Actual
Arguments:

"; - if (parameter != null) { - String name = parameter.getName(); - if (name != null) { - s += esctrim(name) + ":"; - } - } - s += ""; - if (parameter != null) { - PsiType type = substitutor.substitute(parameter.getType()); - s += "" + esctrim(showShort ? type.getPresentableText() : JavaHighlightUtil.formatType(type)) + ""; - } - s += ""; - if (expression != null) { - PsiType type = expression.getType(); - s += "" + esctrim(expression.getText()) + "  " + (mismatchColor == null || type == null || type - == PsiType.NULL ? "" : "(" + esctrim(JavaHighlightUtil.formatType(type)) + ")") + ""; - - } - s += "
"; - final String errorMessage = info != null ? info.getParentInferenceErrorMessage(list) : null; - if (errorMessage != null) { - s += "reason: "; - s += XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "
"); + @RequiredReadAction + private static String getContainingClassName(MethodCandidateInfo methodCandidate) { + PsiMethod method = methodCandidate.getElement(); + PsiClass containingClass = method.getContainingClass(); + return containingClass == null ? method.getContainingFile().getName() : HighlightUtil.formatClass(containingClass, false); } - s += ""; - return s; - } - @SuppressWarnings("StringContatenationInLoop") - @Language("HTML") - private static String createMismatchedArgsHtmlTooltipArgumentsRow(final PsiExpression[] expressions, final PsiParameter[] parameters, final PsiSubstitutor substitutor, final int cols) { @Language("HTML") + private static String createAmbiguousMethodHtmlTooltipMethodRow(MethodCandidateInfo methodCandidate) { + PsiMethod method = methodCandidate.getElement(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); + @Language("HTML") String ms = "" + method.getName() + ""; + + for (int j = 0; j < parameters.length; j++) { + PsiParameter parameter = parameters[j]; + PsiType type = substitutor.substitute(parameter.getType()); + ms += + "" + (j == 0 ? "(" : "") + XmlStringUtil.escapeString(type.getPresentableText()) + (j == parameters.length - 1 ? ")" : ",") + ""; + } + if (parameters.length == 0) { + ms += "()"; + } + return ms; + } + + @RequiredReadAction + private static LocalizeValue createMismatchedArgumentsHtmlTooltip(MethodCandidateInfo info, PsiExpressionList list) { + PsiMethod method = info.getElement(); + PsiSubstitutor substitutor = info.getSubstitutor(); + PsiClass aClass = method.getContainingClass(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + String methodName = method.getName(); + return createMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass); + } + + private static LocalizeValue createShortMismatchedArgumentsHtmlTooltip( + PsiExpressionList list, + @Nullable MethodCandidateInfo info, + PsiParameter[] parameters, + String methodName, + PsiSubstitutor substitutor, + PsiClass aClass + ) { + PsiExpression[] expressions = list.getExpressions(); + int cols = Math.max(parameters.length, expressions.length); + + @Language("HTML") String parenthesizedName = methodName + (parameters.length == 0 ? "( ) " : ""); + String errorMessage = info != null ? info.getParentInferenceErrorMessage(list) : null; + return JavaErrorLocalize.argumentMismatchHtmlTooltip( + cols - parameters.length + 1, + parenthesizedName, + HighlightUtil.formatClass(aClass, false), + createMismatchedArgsHtmlTooltipParamsRow(parameters, substitutor, expressions), + createMismatchedArgsHtmlTooltipArgumentsRow(expressions, parameters, substitutor, cols)//, + /*errorMessage != null + ? "
reason: " + XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "
") + : ""*/ + ); + } + + private static String esctrim(String s) { + return XmlStringUtil.escapeString(trimNicely(s)); + } + + private static String trimNicely(String s) { + if (s.length() <= 40) { + return s; + } - @NonNls String ms = ""; - for (int i = 0; i < expressions.length; i++) { - PsiExpression expression = expressions[i]; - PsiType type = expression.getType(); + List wordIndices = TextRangeUtil.getWordIndicesIn(s); + if (wordIndices.size() > 2) { + int firstWordEnd = wordIndices.get(0).getEndOffset(); - boolean showShort = showShortType(i, parameters, expressions, substitutor); - @NonNls String mismatchColor = showShort ? null : MISMATCH_COLOR; - ms += " " + "" + (i == 0 ? "(" : "") + "" + XmlStringUtil.escapeString(showShort ? type.getPresentableText() : - JavaHighlightUtil.formatType(type)) + "" + (i == expressions.length - 1 ? ")" : ",") + ""; - } - for (int i = expressions.length; i < cols + 1; i++) { - ms += "" + (i == 0 ? "()" : "") + " "; - } - return ms; - } - - @SuppressWarnings("StringContatenationInLoop") - @Language("HTML") - private static String createMismatchedArgsHtmlTooltipParamsRow(final PsiParameter[] parameters, final PsiSubstitutor substitutor, final PsiExpression[] expressions) { - @NonNls String ms = ""; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - PsiType type = substitutor.substitute(parameter.getType()); - ms += "" + (i == 0 ? "(" : "") + XmlStringUtil.escapeString(showShortType(i, parameters, expressions, substitutor) ? type.getPresentableText() : JavaHighlightUtil.formatType - (type)) + (i == parameters.length - 1 ? ")" : ",") + ""; - } - return ms; - } + // try firstWord...remainder + for (int i = 1; i < wordIndices.size(); i++) { + int stringLength = firstWordEnd + s.length() - wordIndices.get(i).getStartOffset(); + if (stringLength <= 40) { + return s.substring(0, firstWordEnd) + "..." + s.substring(wordIndices.get(i).getStartOffset()); + } + } + } + // maybe one last word will fit? + if (!wordIndices.isEmpty() && s.length() - wordIndices.get(wordIndices.size() - 1).getStartOffset() <= 40) { + return "..." + s.substring(wordIndices.get(wordIndices.size() - 1).getStartOffset()); + } - private static boolean showShortType(int i, PsiParameter[] parameters, PsiExpression[] expressions, PsiSubstitutor substitutor) { - PsiExpression expression = i < expressions.length ? expressions[i] : null; - if (expression == null) { - return true; - } - PsiType paramType = i < parameters.length && parameters[i] != null ? substitutor.substitute(parameters[i].getType()) : null; - PsiType expressionType = expression.getType(); - return paramType != null && expressionType != null && TypeConversionUtil.isAssignable(paramType, expressionType); - } - - - public static HighlightInfo checkMethodMustHaveBody(PsiMethod method, PsiClass aClass) { - HighlightInfo errorResult = null; - if (method.getBody() == null && !method.hasModifierProperty(PsiModifier.ABSTRACT) && !method.hasModifierProperty(PsiModifier.NATIVE) && aClass != null && !aClass.isInterface() && !PsiUtilCore - .hasErrorElementChild(method)) { - int start = method.getModifierList().getTextRange().getStartOffset(); - int end = method.getTextRange().getEndOffset(); - - String description = JavaErrorBundle.message("missing.method.body"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description).create(); - if (HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, method.getModifierList()) == null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.ABSTRACT, true, false)); - } - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddMethodBodyFix(method)); - } - return errorResult; - } - - - public static HighlightInfo checkAbstractMethodInConcreteClass(PsiMethod method, PsiElement elementToHighlight) { - HighlightInfo errorResult = null; - PsiClass aClass = method.getContainingClass(); - if (method.hasModifierProperty(PsiModifier.ABSTRACT) && aClass != null && !aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !aClass.isEnum() && !PsiUtilCore.hasErrorElementChild(method)) { - String description = JavaErrorBundle.message("abstract.method.in.non.abstract.class"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description).create(); - if (method.getBody() != null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.ABSTRACT, false, false)); - } - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddMethodBodyFix(method)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false)); - } - return errorResult; - } + return StringUtil.last(s, 40, true).toString(); + } + + @RequiredReadAction + private static LocalizeValue createMismatchedArgumentsHtmlTooltip( + PsiExpressionList list, + MethodCandidateInfo info, + PsiParameter[] parameters, + String methodName, + PsiSubstitutor substitutor, + PsiClass aClass + ) { + return Math.max(parameters.length, list.getExpressions().length) <= 2 + ? createShortMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass) + : createLongMismatchedArgumentsHtmlTooltip(list, info, parameters, methodName, substitutor, aClass); + } + + @RequiredReadAction + @SuppressWarnings("StringContatenationInLoop") + private static LocalizeValue createLongMismatchedArgumentsHtmlTooltip( + PsiExpressionList list, + @Nullable MethodCandidateInfo info, + PsiParameter[] parameters, + String methodName, + PsiSubstitutor substitutor, + PsiClass aClass + ) { + PsiExpression[] expressions = list.getExpressions(); + + @SuppressWarnings("NonConstantStringShouldBeStringBuffer") String s = + "" + "" + "" + ""; + + for (int i = 0; i < Math.max(parameters.length, expressions.length); i++) { + PsiParameter parameter = i < parameters.length ? parameters[i] : null; + PsiExpression expression = i < expressions.length ? expressions[i] : null; + boolean showShort = showShortType(i, parameters, expressions, substitutor); + String mismatchColor = showShort ? null : UIUtil.isUnderDarcula() ? "FF6B68" : "red"; + + s += ""; + s += ""; + s += ""; + + s += ""; - if (aClass != null) { - String className = aClass instanceof PsiAnonymousClass ? null : aClass.getName(); - if (className == null || !Comparing.strEqual(methodName, className)) { - PsiElement element = method.getNameIdentifier(); - String description = JavaErrorBundle.message("missing.return.type"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - if (className != null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createRenameElementFix(method, className)); + s += ""; } - } - } - return errorResult; - } - @Nullable - public static HighlightInfo checkDuplicateMethod(PsiClass aClass, @Nonnull PsiMethod method, @Nonnull MostlySingularMultiMap duplicateMethods) { - if (aClass == null || method instanceof ExternallyDefinedPsiElement) { - return null; - } - MethodSignature methodSignature = method.getSignature(PsiSubstitutor.EMPTY); - int methodCount = 1; - List methods = (List) duplicateMethods.get(methodSignature); - if (methods.size() > 1) { - methodCount++; + s += "
" + "" + methodName + "() in " + + HighlightUtil.formatClass( + aClass, + false + ) + " cannot be applied to:" + "
Expected
Parameters:
Actual
Arguments:

"; + if (parameter != null) { + s += esctrim(parameter.getName()) + ":"; + } + s += ""; + if (parameter != null) { + PsiType type = substitutor.substitute(parameter.getType()); + s += "" + + esctrim(showShort ? type.getPresentableText() : JavaHighlightUtil.formatType(type)) + ""; + } + s += ""; + if (expression != null) { + PsiType type = expression.getType(); + s += "" + esctrim(expression.getText()) + + "  " + ( + mismatchColor == null || type == null || type == PsiType.NULL + ? "" + : "(" + esctrim(JavaHighlightUtil.formatType(type)) + ")" + ) + ""; - public static HighlightInfo checkConstructorName(PsiMethod method) { - String methodName = method.getName(); - PsiClass aClass = method.getContainingClass(); - HighlightInfo errorResult = null; + } + s += "
"; + String errorMessage = info != null ? info.getParentInferenceErrorMessage(list) : null; + if (errorMessage != null) { + s += "reason: "; + s += XmlStringUtil.escapeString(errorMessage).replaceAll("\n", "
"); + } + s += ""; + return LocalizeValue.localizeTODO(s); } - if (methodCount == 1 && aClass.isEnum() && GenericsHighlightUtil.isEnumSyntheticMethod(methodSignature, aClass.getProject())) { - methodCount++; - } - if (methodCount > 1) { - String description = JavaErrorBundle.message("duplicate.method", JavaHighlightUtil.formatMethod(method), HighlightUtil.formatClass(aClass)); - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR). - range(method, textRange.getStartOffset(), textRange.getEndOffset()). - descriptionAndTooltip(description).create(); - } - return null; - } - - @Nullable - public static HighlightInfo checkMethodCanHaveBody(@Nonnull PsiMethod method, @Nonnull LanguageLevel languageLevel) { - PsiClass aClass = method.getContainingClass(); - boolean hasNoBody = method.getBody() == null; - boolean isInterface = aClass != null && aClass.isInterface(); - boolean isExtension = method.hasModifierProperty(PsiModifier.DEFAULT); - boolean isStatic = method.hasModifierProperty(PsiModifier.STATIC); - boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); - - final List additionalFixes = new ArrayList<>(); - String description = null; - if (hasNoBody) { - if (isExtension) { - description = JavaErrorBundle.message("extension.method.should.have.a.body"); - additionalFixes.add(QUICK_FIX_FACTORY.createAddMethodBodyFix(method)); - } else if (isInterface) { - if (isStatic && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - description = "Static methods in interfaces should have a body"; - } else if (isPrivate && languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - description = "Private methods in interfaces should have a body"; - } - } - } else if (isInterface) { - if (!isExtension && !isStatic && !isPrivate) { - description = JavaErrorBundle.message("interface.methods.cannot.have.body"); - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - additionalFixes.add(QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.DEFAULT, true, false)); - additionalFixes.add(QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, true, false)); - } - } - } else if (isExtension) { - description = JavaErrorBundle.message("extension.method.in.class"); - } else if (method.hasModifierProperty(PsiModifier.ABSTRACT)) { - description = JavaErrorBundle.message("abstract.methods.cannot.have.a.body"); - } else if (method.hasModifierProperty(PsiModifier.NATIVE)) { - description = JavaErrorBundle.message("native.methods.cannot.have.a.body"); - } - if (description == null) { - return null; + @SuppressWarnings("StringContatenationInLoop") + @Language("HTML") + private static String createMismatchedArgsHtmlTooltipArgumentsRow( + PsiExpression[] expressions, + PsiParameter[] parameters, + PsiSubstitutor substitutor, + int cols + ) { + @Language("HTML") + + String ms = ""; + for (int i = 0; i < expressions.length; i++) { + PsiExpression expression = expressions[i]; + PsiType type = expression.getType(); + + boolean showShort = showShortType(i, parameters, expressions, substitutor); + String mismatchColor = showShort ? null : ColorUtil.toHex(JBColor.RED); + ms += " " + "" + (i == 0 ? "(" : "") + "" + + XmlStringUtil.escapeString( + showShort + ? type.getPresentableText() + : JavaHighlightUtil.formatType(type) + ) + "" + (i == expressions.length - 1 ? ")" : ",") + ""; + } + for (int i = expressions.length; i < cols + 1; i++) { + ms += "" + (i == 0 ? "()" : "") + " "; + } + return ms; } - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - if (!hasNoBody) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createDeleteMethodBodyFix(method)); - } - if (method.hasModifierProperty(PsiModifier.ABSTRACT) && !isInterface) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.ABSTRACT, false, false)); + @SuppressWarnings("StringContatenationInLoop") + @Language("HTML") + private static String createMismatchedArgsHtmlTooltipParamsRow( + PsiParameter[] parameters, + PsiSubstitutor substitutor, + PsiExpression[] expressions + ) { + String ms = ""; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + PsiType type = substitutor.substitute(parameter.getType()); + ms += "" + (i == 0 ? "(" : "") + XmlStringUtil.escapeString( + showShortType(i, parameters, expressions, substitutor) + ? type.getPresentableText() + : JavaHighlightUtil.formatType(type) + ) + (i == parameters.length - 1 ? ")" : ",") + ""; + } + return ms; } - for (IntentionAction intentionAction : additionalFixes) { - QuickFixAction.registerQuickFixAction(info, intentionAction); + + private static boolean showShortType(int i, PsiParameter[] parameters, PsiExpression[] expressions, PsiSubstitutor substitutor) { + PsiExpression expression = i < expressions.length ? expressions[i] : null; + if (expression == null) { + return true; + } + PsiType paramType = i < parameters.length && parameters[i] != null ? substitutor.substitute(parameters[i].getType()) : null; + PsiType expressionType = expression.getType(); + return paramType != null && expressionType != null && TypeConversionUtil.isAssignable(paramType, expressionType); + } + + @RequiredReadAction + public static HighlightInfo.Builder checkMethodMustHaveBody(PsiMethod method, PsiClass aClass) { + if (method.getBody() == null && !method.isAbstract() + && !method.hasModifierProperty(PsiModifier.NATIVE) && aClass != null && !aClass.isInterface() + && !PsiUtilCore.hasErrorElementChild(method)) { + int start = method.getModifierList().getTextRange().getStartOffset(); + int end = method.getTextRange().getEndOffset(); + + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(start, end) + .descriptionAndTooltip(JavaErrorLocalize.missingMethodBody()); + if (HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, method.getModifierList()) == null) { + hlBuilder.registerFix(factory.createModifierFixBuilder(method).add(PsiModifier.ABSTRACT).create()); + } + return hlBuilder.registerFix(factory.createAddMethodBodyFix(method)); + } + return null; } - return info; - } - @Nullable - public static HighlightInfo checkConstructorCallMustBeFirstStatement(@Nonnull PsiMethodCallExpression methodCall) { - if (!RefactoringChangeUtil.isSuperOrThisMethodCall(methodCall)) { - return null; + @RequiredReadAction + public static HighlightInfo checkAbstractMethodInConcreteClass(PsiMethod method, PsiElement elementToHighlight) { + PsiClass aClass = method.getContainingClass(); + if (method.isAbstract() && aClass != null + && !aClass.isAbstract() && !aClass.isEnum() && !PsiUtilCore.hasErrorElementChild(method)) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(elementToHighlight) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodAbstractInNonAbstractClass()); + if (method.getBody() != null) { + errorResult.registerFix(factory.createModifierFixBuilder(method).remove(PsiModifier.ABSTRACT).create()); + } + return errorResult.registerFix(factory.createAddMethodBodyFix(method)) + .registerFix(factory.createModifierFixBuilder(aClass).add(PsiModifier.ABSTRACT).create()) + .create(); + } + return null; } - PsiElement codeBlock = methodCall.getParent().getParent(); - if (codeBlock instanceof PsiCodeBlock && codeBlock.getParent() instanceof PsiMethod && ((PsiMethod) codeBlock.getParent()).isConstructor()) { - PsiElement prevSibling = methodCall.getParent().getPrevSibling(); - while (true) { - if (prevSibling == null) { - return null; - } - if (prevSibling instanceof PsiStatement) { - break; - } - prevSibling = prevSibling.getPrevSibling(); - } + + @RequiredReadAction + public static HighlightInfo checkConstructorName(PsiMethod method) { + String methodName = method.getName(); + PsiClass aClass = method.getContainingClass(); + + if (aClass != null) { + String className = aClass instanceof PsiAnonymousClass ? null : aClass.getName(); + if (className == null || !Comparing.strEqual(methodName, className)) { + HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(method.getNameIdentifier()) + .descriptionAndTooltip(JavaErrorLocalize.missingReturnType()); + if (className != null) { + errorResult.registerFix(QuickFixFactory.getInstance().createRenameElementFix(method, className)); + } + return errorResult.create(); + } + } + return null; } - PsiReferenceExpression expression = methodCall.getMethodExpression(); - String message = JavaErrorBundle.message("constructor.call.must.be.first.statement", expression.getText() + "()"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCall).descriptionAndTooltip(message).create(); - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkDuplicateMethod( + PsiClass aClass, + PsiMethod method, + MostlySingularMultiMap duplicateMethods + ) { + if (aClass == null || method instanceof ExternallyDefinedPsiElement) { + return null; + } + MethodSignature methodSignature = method.getSignature(PsiSubstitutor.EMPTY); + int methodCount = 1; + List methods = (List) duplicateMethods.get(methodSignature); + if (methods.size() > 1) { + methodCount++; + } - public static HighlightInfo checkSuperAbstractMethodDirectCall(@Nonnull PsiMethodCallExpression methodCallExpression) { - PsiReferenceExpression expression = methodCallExpression.getMethodExpression(); - if (!(expression.getQualifierExpression() instanceof PsiSuperExpression)) { - return null; - } - PsiMethod method = methodCallExpression.resolveMethod(); - if (method != null && method.hasModifierProperty(PsiModifier.ABSTRACT)) { - String message = JavaErrorBundle.message("direct.abstract.method.access", JavaHighlightUtil.formatMethod(method)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(methodCallExpression).descriptionAndTooltip(message).create(); + if (methodCount == 1 && aClass.isEnum() && GenericsHighlightUtil.isEnumSyntheticMethod(methodSignature, aClass.getProject())) { + methodCount++; + } + if (methodCount > 1) { + TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(method, textRange.getStartOffset(), textRange.getEndOffset()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodDuplicate( + JavaHighlightUtil.formatMethod(method), + HighlightUtil.formatClass(aClass) + )); + } + return null; } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMethodCanHaveBody(PsiMethod method, LanguageLevel languageLevel) { + PsiClass aClass = method.getContainingClass(); + boolean hasNoBody = method.getBody() == null; + boolean isInterface = aClass != null && aClass.isInterface(); + boolean isExtension = method.hasModifierProperty(PsiModifier.DEFAULT); + boolean isStatic = method.isStatic(); + boolean isPrivate = method.isPrivate(); + + List additionalFixes = new ArrayList<>(); + LocalizeValue description; + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (hasNoBody) { + if (isExtension) { + description = JavaCompilationErrorLocalize.methodDefaultShouldHaveBody(); + additionalFixes.add(factory.createAddMethodBodyFix(method)); + } + else if (isInterface) { + if (isStatic && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + description = JavaCompilationErrorLocalize.methodStaticInInterfaceShouldHaveBody(); + } + else if (isPrivate && languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + description = JavaCompilationErrorLocalize.methodPrivateInInterfaceShouldHaveBody(); + } + else { + return null; + } + } + else { + return null; + } + } + else if (isInterface) { + if (!isExtension && !isStatic && !isPrivate) { + description = JavaErrorLocalize.interfaceMethodsCannotHaveBody(); + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + additionalFixes.add(factory.createModifierFixBuilder(method).add(PsiModifier.DEFAULT).create()); + additionalFixes.add(factory.createModifierFixBuilder(method).add(PsiModifier.STATIC).create()); + } + } + else { + return null; + } + } + else if (isExtension) { + description = JavaCompilationErrorLocalize.methodDefaultInClass(); + } + else if (method.isAbstract()) { + description = JavaCompilationErrorLocalize.methodAbstractBody(); + } + else if (method.hasModifierProperty(PsiModifier.NATIVE)) { + description = JavaCompilationErrorLocalize.methodNativeBody(); + } + else { + return null; + } - public static HighlightInfo checkConstructorCallsBaseClassConstructor(PsiMethod constructor, RefCountHolder refCountHolder, PsiResolveHelper resolveHelper) { - if (!constructor.isConstructor()) { - return null; - } - PsiClass aClass = constructor.getContainingClass(); - if (aClass == null) { - return null; - } - if (aClass.isEnum()) { - return null; - } - PsiCodeBlock body = constructor.getBody(); - if (body == null) { - return null; + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getMethodDeclarationTextRange(method)) + .descriptionAndTooltip(description); + if (!hasNoBody) { + hlBuilder.registerFix(factory.createDeleteMethodBodyFix(method)); + } + if (method.isAbstract() && !isInterface) { + hlBuilder.registerFix(factory.createModifierFixBuilder(method).remove(PsiModifier.ABSTRACT).create()); + } + for (IntentionAction intentionAction : additionalFixes) { + hlBuilder.registerFix(intentionAction); + } + return hlBuilder; } - // check whether constructor call super(...) or this(...) - PsiElement element = new PsiMatcherImpl(body).firstChild(PsiMatchers.hasClass(PsiExpressionStatement.class)).firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)).firstChild - (PsiMatchers.hasClass(PsiReferenceExpression.class)).firstChild(PsiMatchers.hasClass(PsiKeyword.class)).getElement(); - if (element != null) { - return null; - } - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(constructor); - PsiClassType[] handledExceptions = constructor.getThrowsList().getReferencedTypes(); - HighlightInfo info = HighlightClassUtil.checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, handledExceptions); - if (info != null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createInsertSuperFix(constructor)); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddDefaultConstructorFix(aClass.getSuperClass())); - } - return info; - } - - - /** - * @return error if static method overrides instance method or - * instance method overrides static. see JLS 8.4.6.1, 8.4.6.2 - */ - public static HighlightInfo checkStaticMethodOverride(@Nonnull PsiMethod method, @Nonnull PsiFile containingFile) { - // constructors are not members and therefor don't override class methods - if (method.isConstructor()) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkConstructorCallMustBeFirstStatement(PsiMethodCallExpression methodCall) { + if (!RefactoringChangeUtil.isSuperOrThisMethodCall(methodCall)) { + return null; + } + if (methodCall.getParent().getParent() instanceof PsiCodeBlock codeBlock + && codeBlock.getParent() instanceof PsiMethod method && method.isConstructor()) { + PsiElement prevSibling = methodCall.getParent().getPrevSibling(); + while (true) { + if (prevSibling == null) { + return null; + } + if (prevSibling instanceof PsiStatement) { + // JEP 482 Flexible Constructor Bodies: statements are allowed before super()/this() + if (PsiUtil.isAvailable(JavaFeature.STATEMENTS_BEFORE_SUPER, methodCall)) { + return null; + } + break; + } + prevSibling = prevSibling.getPrevSibling(); + } + } + PsiReferenceExpression expression = methodCall.getMethodExpression(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(methodCall) + .descriptionAndTooltip(JavaCompilationErrorLocalize.callConstructorMustBeFirstStatement(expression.getText() + "()")); } - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return null; - } - final HierarchicalMethodSignature methodSignature = PsiSuperMethodImplUtil.getHierarchicalMethodSignature(method); - final List superSignatures = methodSignature.getSuperSignatures(); - if (superSignatures.isEmpty()) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSuperAbstractMethodDirectCall(PsiMethodCallExpression methodCall) { + PsiReferenceExpression expression = methodCall.getMethodExpression(); + if (!(expression.getQualifierExpression() instanceof PsiSuperExpression)) { + return null; + } + PsiMethod method = methodCall.resolveMethod(); + if (method != null && method.isAbstract()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(methodCall) + .descriptionAndTooltip(JavaErrorLocalize.directAbstractMethodAccess(JavaHighlightUtil.formatMethod(method))); + } + return null; } - boolean isStatic = method.hasModifierProperty(PsiModifier.STATIC); - for (HierarchicalMethodSignature signature : superSignatures) { - final PsiMethod superMethod = signature.getMethod(); - final PsiClass superClass = superMethod.getContainingClass(); - if (superClass == null) { - continue; - } - final HighlightInfo highlightInfo = checkStaticMethodOverride(aClass, method, isStatic, superClass, superMethod, containingFile); - if (highlightInfo != null) { - return highlightInfo; - } - } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkConstructorCallsBaseClassConstructor( + PsiMethod constructor, + RefCountHolder refCountHolder, + PsiResolveHelper resolveHelper + ) { + if (!constructor.isConstructor()) { + return null; + } + PsiClass aClass = constructor.getContainingClass(); + if (aClass == null) { + return null; + } + if (aClass.isEnum()) { + return null; + } + PsiCodeBlock body = constructor.getBody(); + if (body == null) { + return null; + } - private static HighlightInfo checkStaticMethodOverride(PsiClass aClass, PsiMethod method, boolean isMethodStatic, PsiClass superClass, PsiMethod superMethod, @Nonnull PsiFile containingFile) { - if (superMethod == null) { - return null; - } - PsiManager manager = containingFile.getManager(); - PsiModifierList superModifierList = superMethod.getModifierList(); - PsiModifierList modifierList = method.getModifierList(); - if (superModifierList.hasModifierProperty(PsiModifier.PRIVATE)) { - return null; - } - if (superModifierList.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && !JavaPsiFacade.getInstance(manager.getProject()).arePackagesTheSame(aClass, superClass)) { - return null; - } - boolean isSuperMethodStatic = superModifierList.hasModifierProperty(PsiModifier.STATIC); - if (isMethodStatic != isSuperMethodStatic) { - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - @NonNls final String messageKey = isMethodStatic ? "static.method.cannot.override.instance.method" : "instance.method.cannot.override.static.method"; - - String description = JavaErrorBundle.message(messageKey, JavaHighlightUtil.formatMethod(method), HighlightUtil.formatClass(aClass), JavaHighlightUtil.formatMethod(superMethod), - HighlightUtil.formatClass(superClass)); - - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - if (!isSuperMethodStatic || HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, modifierList) == null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, isSuperMethodStatic, false)); - } - if (manager.isInProject(superMethod) && (!isMethodStatic || HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, superModifierList) == null)) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(superMethod, PsiModifier.STATIC, isMethodStatic, true)); - } - return info; + // check whether constructor call super(...) or this(...) + PsiElement element = new PsiMatcherImpl(body) + .firstChild(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiReferenceExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) + .getElement(); + if (element != null) { + return null; + } + TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(constructor); + PsiClassType[] handledExceptions = constructor.getThrowsList().getReferencedTypes(); + HighlightInfo.Builder hlBuilder = + HighlightClassUtil.checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, handledExceptions); + if (hlBuilder != null) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createInsertSuperFix(constructor)); + hlBuilder.registerFix(QuickFixFactory.getInstance().createAddDefaultConstructorFix(aClass.getSuperClass())); + } + return hlBuilder; } - if (isMethodStatic) { - if (superClass.isInterface()) { + /** + * @return error if static method overrides instance method or + * instance method overrides static. see JLS 8.4.6.1, 8.4.6.2 + */ + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkStaticMethodOverride(PsiMethod method, PsiFile containingFile) { + // constructors are not members and therefor don't override class methods + if (method.isConstructor()) { + return null; + } + + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return null; + } + HierarchicalMethodSignature methodSignature = PsiSuperMethodImplUtil.getHierarchicalMethodSignature(method); + List superSignatures = methodSignature.getSuperSignatures(); + if (superSignatures.isEmpty()) { + return null; + } + + boolean isStatic = method.isStatic(); + for (HierarchicalMethodSignature signature : superSignatures) { + PsiMethod superMethod = signature.getMethod(); + PsiClass superClass = superMethod.getContainingClass(); + if (superClass == null) { + continue; + } + HighlightInfo.Builder hlBuilder = + checkStaticMethodOverride(aClass, method, isStatic, superClass, superMethod, containingFile); + if (hlBuilder != null) { + return hlBuilder; + } + } return null; - } - int accessLevel = PsiUtil.getAccessLevel(modifierList); - String accessModifier = PsiUtil.getAccessModifier(accessLevel); - HighlightInfo info = isWeaker(method, modifierList, accessModifier, accessLevel, superMethod, true); - if (info != null) { - return info; - } - info = checkSuperMethodIsFinal(method, superMethod); - if (info != null) { - return info; - } } - return null; - } - private static HighlightInfo checkInterfaceInheritedMethodsReturnTypes(@Nonnull List superMethodSignatures, @Nonnull LanguageLevel languageLevel) { - if (superMethodSignatures.size() < 2) { - return null; - } - final MethodSignatureBackedByPsiMethod[] returnTypeSubstitutable = {superMethodSignatures.get(0)}; - for (int i = 1; i < superMethodSignatures.size(); i++) { - PsiMethod currentMethod = returnTypeSubstitutable[0].getMethod(); - PsiType currentType = returnTypeSubstitutable[0].getSubstitutor().substitute(currentMethod.getReturnType()); - - MethodSignatureBackedByPsiMethod otherSuperSignature = superMethodSignatures.get(i); - PsiMethod otherSuperMethod = otherSuperSignature.getMethod(); - PsiSubstitutor otherSubstitutor = otherSuperSignature.getSubstitutor(); - PsiType otherSuperReturnType = otherSubstitutor.substitute(otherSuperMethod.getReturnType()); - PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(returnTypeSubstitutable[0], otherSuperSignature); - if (unifyingSubstitutor != null) { - otherSuperReturnType = unifyingSubstitutor.substitute(otherSuperReturnType); - currentType = unifyingSubstitutor.substitute(currentType); - } - - if (otherSuperReturnType == null || currentType == null || otherSuperReturnType.equals(currentType)) { - continue; - } - PsiType otherReturnType = otherSuperReturnType; - PsiType curType = currentType; - final HighlightInfo info = LambdaUtil.performWithSubstitutedParameterBounds(otherSuperMethod.getTypeParameters(), otherSubstitutor, () -> - { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) { - //http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8 Example 8.1.5-3 - if (!(otherReturnType instanceof PsiPrimitiveType || curType instanceof PsiPrimitiveType)) { - if (otherReturnType.isAssignableFrom(curType)) { - return null; - } - if (curType.isAssignableFrom(otherReturnType)) { - returnTypeSubstitutable[0] = otherSuperSignature; - return null; - } - } - if (otherSuperMethod.getTypeParameters().length > 0 && JavaGenericsUtil.isRawToGeneric(otherReturnType, curType)) { + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkStaticMethodOverride( + PsiClass aClass, + PsiMethod method, + boolean isMethodStatic, + PsiClass superClass, + PsiMethod superMethod, + PsiFile containingFile + ) { + if (superMethod == null) { return null; - } - } - return createIncompatibleReturnTypeMessage(otherSuperMethod, currentMethod, curType, otherReturnType, JavaErrorBundle.message("unrelated.overriding.methods.return.types"), - TextRange.EMPTY_RANGE); - }); - if (info != null) { - return info; - } - } - return null; - } - - public static HighlightInfo checkOverrideEquivalentInheritedMethods(PsiClass aClass, PsiFile containingFile, @Nonnull LanguageLevel languageLevel) { - String description = null; - boolean appendImplementMethodFix = true; - final Collection visibleSignatures = aClass.getVisibleSignatures(); - PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper(); - Ultimate: - for (HierarchicalMethodSignature signature : visibleSignatures) { - PsiMethod method = signature.getMethod(); - if (!resolveHelper.isAccessible(method, aClass, null)) { - continue; - } - List superSignatures = signature.getSuperSignatures(); - - boolean allAbstracts = method.hasModifierProperty(PsiModifier.ABSTRACT); - final PsiClass containingClass = method.getContainingClass(); - if (aClass.equals(containingClass)) { - continue; //to be checked at method level - } - - if (aClass.isInterface() && !containingClass.isInterface()) { - continue; - } - HighlightInfo highlightInfo; - if (allAbstracts) { - superSignatures = new ArrayList<>(superSignatures); - superSignatures.add(0, signature); - highlightInfo = checkInterfaceInheritedMethodsReturnTypes(superSignatures, languageLevel); - } else { - highlightInfo = checkMethodIncompatibleReturnType(signature, superSignatures, false); - } - if (highlightInfo != null) { - description = highlightInfo.getDescription(); - } - - if (method.hasModifierProperty(PsiModifier.STATIC)) { - for (HierarchicalMethodSignature superSignature : superSignatures) { - PsiMethod superMethod = superSignature.getMethod(); - if (!superMethod.hasModifierProperty(PsiModifier.STATIC)) { - description = JavaErrorBundle.message("static.method.cannot.override.instance.method", JavaHighlightUtil.formatMethod(method), HighlightUtil.formatClass(containingClass), - JavaHighlightUtil.formatMethod(superMethod), HighlightUtil.formatClass(superMethod.getContainingClass())); - appendImplementMethodFix = false; - break Ultimate; - } - } - continue; - } - - if (description == null) { - highlightInfo = checkMethodIncompatibleThrows(signature, superSignatures, false, aClass); - if (highlightInfo != null) { - description = highlightInfo.getDescription(); - } - } - - if (description == null) { - highlightInfo = checkMethodWeakerPrivileges(signature, superSignatures, false, containingFile); - if (highlightInfo != null) { - description = highlightInfo.getDescription(); - } - } - - if (description != null) { - break; - } + } + PsiManager manager = containingFile.getManager(); + PsiModifierList superModifierList = superMethod.getModifierList(); + PsiModifierList modifierList = method.getModifierList(); + if (superModifierList.hasModifierProperty(PsiModifier.PRIVATE)) { + return null; + } + if (superModifierList.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) + && !JavaPsiFacade.getInstance(manager.getProject()).arePackagesTheSame(aClass, superClass)) { + return null; + } + boolean isSuperMethodStatic = superModifierList.hasModifierProperty(PsiModifier.STATIC); + if (isMethodStatic != isSuperMethodStatic) { + String m1 = JavaHighlightUtil.formatMethod(method); + String m2 = JavaHighlightUtil.formatMethod(superMethod); + String c1 = HighlightUtil.formatClass(aClass); + String c2 = HighlightUtil.formatClass(superClass); + LocalizeValue description = isMethodStatic + ? JavaCompilationErrorLocalize.methodStaticOverridesInstance(m1, c1, m2, c2) + : JavaCompilationErrorLocalize.methodInstanceOverridesStatic(m1, c1, m2, c2); + + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getMethodDeclarationTextRange(method)) + .descriptionAndTooltip(description); + if (!isSuperMethodStatic || HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, modifierList) == null) { + hlBuilder.registerFix( + QuickFixFactory.getInstance() + .createModifierFixBuilder(method) + .toggle(PsiModifier.STATIC, isSuperMethodStatic) + .create() + ); + } + if (manager.isInProject(superMethod) + && (!isMethodStatic || HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, superModifierList) == null)) { + hlBuilder.registerFix( + QuickFixFactory.getInstance() + .createModifierFixBuilder(superMethod) + .toggle(PsiModifier.STATIC, isMethodStatic) + .showContainingClass() + .create() + ); + } + return hlBuilder; + } + + if (isMethodStatic) { + if (superClass.isInterface()) { + return null; + } + int accessLevel = PsiUtil.getAccessLevel(modifierList); + String accessModifier = PsiUtil.getAccessModifier(accessLevel); + HighlightInfo.Builder info = isWeaker(method, modifierList, accessModifier, accessLevel, superMethod, true); + if (info != null) { + return info; + } + info = checkSuperMethodIsFinal(method, superMethod); + if (info != null) { + return info; + } + } + return null; } + private static HighlightInfo.@Nullable Builder checkInterfaceInheritedMethodsReturnTypes( + List superMethodSignatures, + LanguageLevel languageLevel + ) { + if (superMethodSignatures.size() < 2) { + return null; + } + MethodSignatureBackedByPsiMethod[] returnTypeSubstitutable = {superMethodSignatures.get(0)}; + for (int i = 1; i < superMethodSignatures.size(); i++) { + PsiMethod currentMethod = returnTypeSubstitutable[0].getMethod(); + PsiType currentType = returnTypeSubstitutable[0].getSubstitutor().substitute(currentMethod.getReturnType()); + + MethodSignatureBackedByPsiMethod otherSuperSignature = superMethodSignatures.get(i); + PsiMethod otherSuperMethod = otherSuperSignature.getMethod(); + PsiSubstitutor otherSubstitutor = otherSuperSignature.getSubstitutor(); + PsiType otherSuperReturnType = otherSubstitutor.substitute(otherSuperMethod.getReturnType()); + PsiSubstitutor unifyingSubstitutor = + MethodSignatureUtil.getSuperMethodSignatureSubstitutor(returnTypeSubstitutable[0], otherSuperSignature); + if (unifyingSubstitutor != null) { + otherSuperReturnType = unifyingSubstitutor.substitute(otherSuperReturnType); + currentType = unifyingSubstitutor.substitute(currentType); + } - if (description != null) { - // show error info at the class level - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - if (appendImplementMethodFix) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass)); - } - return highlightInfo; + if (otherSuperReturnType == null || currentType == null || otherSuperReturnType.equals(currentType)) { + continue; + } + PsiType otherReturnType = otherSuperReturnType; + PsiType curType = currentType; + HighlightInfo.Builder hlBuilder = LambdaUtil.performWithSubstitutedParameterBounds( + otherSuperMethod.getTypeParameters(), + otherSubstitutor, + () -> { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) { + //http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8 Example 8.1.5-3 + if (!(otherReturnType instanceof PsiPrimitiveType || curType instanceof PsiPrimitiveType)) { + if (otherReturnType.isAssignableFrom(curType)) { + return null; + } + if (curType.isAssignableFrom(otherReturnType)) { + returnTypeSubstitutable[0] = otherSuperSignature; + return null; + } + } + if (otherSuperMethod.getTypeParameters().length > 0 + && JavaGenericsUtil.isRawToGeneric(otherReturnType, curType)) { + return null; + } + } + return createIncompatibleReturnTypeMessage( + otherSuperMethod, + currentMethod, + curType, + otherReturnType, + JavaErrorLocalize.unrelatedOverridingMethodsReturnTypes(), + TextRange.EMPTY_RANGE + ); + } + ); + if (hlBuilder != null) { + return hlBuilder; + } + } + return null; } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkOverrideEquivalentInheritedMethods( + PsiClass aClass, + PsiFile containingFile, + LanguageLevel languageLevel + ) { + LocalizeValue description = LocalizeValue.empty(); + boolean appendImplementMethodFix = true; + Collection visibleSignatures = aClass.getVisibleSignatures(); + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper(); + Ultimate: + for (HierarchicalMethodSignature signature : visibleSignatures) { + PsiMethod method = signature.getMethod(); + if (!resolveHelper.isAccessible(method, aClass, null)) { + continue; + } + List superSignatures = signature.getSuperSignatures(); - public static HighlightInfo checkConstructorHandleSuperClassExceptions(PsiMethod method) { - if (!method.isConstructor()) { - return null; - } - PsiCodeBlock body = method.getBody(); - PsiStatement[] statements = body == null ? null : body.getStatements(); - if (statements == null) { - return null; - } + boolean allAbstracts = method.isAbstract(); + PsiClass containingClass = method.getContainingClass(); + if (aClass.equals(containingClass)) { + continue; //to be checked at method level + } - // if we have unhandled exception inside method body, we could not have been called here, - // so the only problem it can catch here is with super ctr only - Collection unhandled = ExceptionUtil.collectUnhandledExceptions(method, method.getContainingClass()); - if (unhandled.isEmpty()) { - return null; - } - String description = HighlightUtil.getUnhandledExceptionsDescriptor(unhandled); - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - for (PsiClassType exception : unhandled) { - QuickFixAction.registerQuickFixAction(highlightInfo, new LocalQuickFixOnPsiElementAsIntentionAdapter(QUICK_FIX_FACTORY.createMethodThrowsFix(method, exception, true, false))); - } - return highlightInfo; - } + if (aClass.isInterface() && !containingClass.isInterface()) { + continue; + } + HighlightInfo highlightInfo; + if (allAbstracts) { + superSignatures = new ArrayList<>(superSignatures); + superSignatures.add(0, signature); + HighlightInfo.Builder hlBuilder = checkInterfaceInheritedMethodsReturnTypes(superSignatures, languageLevel); + highlightInfo = hlBuilder != null ? hlBuilder.create() : null; + } + else { + HighlightInfo.Builder hlBuilder = checkMethodIncompatibleReturnType(signature, superSignatures, false); + highlightInfo = hlBuilder != null ? hlBuilder.create() : null; + } + if (highlightInfo != null) { + description = highlightInfo.getDescription(); + } + if (method.isStatic()) { + for (HierarchicalMethodSignature superSignature : superSignatures) { + PsiMethod superMethod = superSignature.getMethod(); + if (!superMethod.isStatic()) { + description = JavaCompilationErrorLocalize.methodStaticOverridesInstance( + JavaHighlightUtil.formatMethod(method), + HighlightUtil.formatClass(containingClass), + JavaHighlightUtil.formatMethod(superMethod), + HighlightUtil.formatClass(superMethod.getContainingClass()) + ); + appendImplementMethodFix = false; + break Ultimate; + } + } + continue; + } - public static HighlightInfo checkRecursiveConstructorInvocation(@Nonnull PsiMethod method) { - if (HighlightControlFlowUtil.isRecursivelyCalledConstructor(method)) { - TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); - String description = JavaErrorBundle.message("recursive.constructor.invocation"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - return null; - } - - @Nonnull - public static TextRange getFixRange(@Nonnull PsiElement element) { - TextRange range = element.getTextRange(); - int start = range.getStartOffset(); - int end = range.getEndOffset(); - - PsiElement nextSibling = element.getNextSibling(); - if (nextSibling instanceof PsiJavaToken && ((PsiJavaToken) nextSibling).getTokenType() == JavaTokenType.SEMICOLON) { - return new TextRange(start, end + 1); - } - return range; - } + if (description.isEmpty()) { + highlightInfo = checkMethodIncompatibleThrows(signature, superSignatures, false, aClass); + if (highlightInfo != null) { + description = highlightInfo.getDescription(); + } + } + + if (description.isEmpty()) { + HighlightInfo.Builder hlBuilder = checkMethodWeakerPrivileges(signature, superSignatures, false, containingFile); + highlightInfo = hlBuilder == null ? null : hlBuilder.create(); + if (highlightInfo != null) { + description = highlightInfo.getDescription(); + } + } + if (description.isNotEmpty()) { + break; + } + } - public static void checkNewExpression(@Nonnull PsiNewExpression expression, PsiType type, @Nonnull HighlightInfoHolder holder, @Nonnull JavaSdkVersion javaSdkVersion) { - if (!(type instanceof PsiClassType)) { - return; + if (description.isNotEmpty()) { + // show error info at the class level + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .descriptionAndTooltip(description); + if (appendImplementMethodFix) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createImplementMethodsFix(aClass)); + } + return hlBuilder; + } + return null; } - PsiClassType.ClassResolveResult typeResult = ((PsiClassType) type).resolveGenerics(); - PsiClass aClass = typeResult.getElement(); - if (aClass == null) { - return; + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkConstructorHandleSuperClassExceptions(PsiMethod method) { + if (!method.isConstructor()) { + return null; + } + PsiCodeBlock body = method.getBody(); + PsiStatement[] statements = body == null ? null : body.getStatements(); + if (statements == null) { + return null; + } + + // if we have unhandled exception inside method body, we could not have been called here, + // so the only problem it can catch here is with super ctr only + Collection unhandled = ExceptionUtil.collectUnhandledExceptions(method, method.getContainingClass()); + if (unhandled.isEmpty()) { + return null; + } + TextRange textRange = HighlightNamesUtil.getMethodDeclarationTextRange(method); + HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(HighlightUtil.getUnhandledExceptionsDescriptor(unhandled)); + for (PsiClassType exception : unhandled) { + highlightInfo.registerFix(new LocalQuickFixOnPsiElementAsIntentionAdapter( + QuickFixFactory.getInstance().createMethodThrowsFix( + method, + exception, + true, + false + ) + )); + } + return highlightInfo; } - if (aClass instanceof PsiAnonymousClass) { - type = ((PsiAnonymousClass) aClass).getBaseClassType(); - typeResult = ((PsiClassType) type).resolveGenerics(); - aClass = typeResult.getElement(); - if (aClass == null) { - return; - } + + @RequiredReadAction + public static HighlightInfo.Builder checkRecursiveConstructorInvocation(PsiMethod method) { + if (!HighlightControlFlowUtil.isRecursivelyCalledConstructor(method)) { + return null; + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(HighlightNamesUtil.getMethodDeclarationTextRange(method)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.callConstructorRecursive()); } - PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); - checkConstructorCall(typeResult, expression, type, classReference, holder, javaSdkVersion); - } + @RequiredReadAction + public static TextRange getFixRange(PsiElement element) { + TextRange range = element.getTextRange(); + int start = range.getStartOffset(); + int end = range.getEndOffset(); + PsiElement nextSibling = element.getNextSibling(); + if (nextSibling instanceof PsiJavaToken javaToken && javaToken.getTokenType() == JavaTokenType.SEMICOLON) { + return new TextRange(start, end + 1); + } + return range; + } + + @RequiredReadAction + public static void checkNewExpression( + PsiNewExpression expression, + PsiType type, + HighlightInfoHolder holder, + JavaSdkVersion javaSdkVersion + ) { + if (!(type instanceof PsiClassType classType)) { + return; + } + PsiClassType.ClassResolveResult typeResult = classType.resolveGenerics(); + PsiClass aClass = typeResult.getElement(); + if (aClass == null) { + return; + } + if (aClass instanceof PsiAnonymousClass anonymousClass) { + type = anonymousClass.getBaseClassType(); + typeResult = ((PsiClassType) type).resolveGenerics(); + aClass = typeResult.getElement(); + if (aClass == null) { + return; + } + } - public static void checkConstructorCall(@Nonnull PsiClassType.ClassResolveResult typeResolveResult, - @Nonnull PsiConstructorCall constructorCall, - @Nonnull PsiType type, - PsiJavaCodeReferenceElement classReference, - @Nonnull HighlightInfoHolder holder, - @Nonnull JavaSdkVersion javaSdkVersion) { - PsiExpressionList list = constructorCall.getArgumentList(); - if (list == null) { - return; - } - PsiClass aClass = typeResolveResult.getElement(); - if (aClass == null) { - return; - } - final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(holder.getProject()).getResolveHelper(); - PsiClass accessObjectClass = null; - if (constructorCall instanceof PsiNewExpression) { - PsiExpression qualifier = ((PsiNewExpression) constructorCall).getQualifier(); - if (qualifier != null) { - accessObjectClass = (PsiClass) PsiUtil.getAccessObjectClass(qualifier).getElement(); - } - } - if (classReference != null && !resolveHelper.isAccessible(aClass, constructorCall, accessObjectClass)) { - String description = HighlightUtil.buildProblemWithAccessDescription(classReference, typeResolveResult); - PsiElement element = classReference.getReferenceNameElement(); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); - HighlightUtil.registerAccessQuickFixAction(aClass, classReference, info, null); - holder.add(info); - return; - } - PsiMethod[] constructors = aClass.getConstructors(); - - if (constructors.length == 0) { - if (list.getExpressions().length != 0) { - String constructorName = aClass.getName(); - String argTypes = buildArgTypesList(list); - String description = JavaErrorBundle.message("wrong.constructor.arguments", constructorName + "()", argTypes); - String tooltip = createMismatchedArgumentsHtmlTooltip(list, null, PsiParameter.EMPTY_ARRAY, constructorName, PsiSubstitutor.EMPTY, aClass); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).description(description).escapedToolTip(tooltip).navigationShift(+1).create(); - QuickFixAction.registerQuickFixAction(info, constructorCall.getTextRange(), QUICK_FIX_FACTORY.createCreateConstructorFromCallFix(constructorCall)); - if (classReference != null) { - ConstructorParametersFixer.registerFixActions(classReference, constructorCall, info, getFixRange(list)); - } - holder.add(info); - return; - } - if (classReference != null && aClass.hasModifierProperty(PsiModifier.PROTECTED) && callingProtectedConstructorFromDerivedClass(constructorCall, aClass)) { - holder.add(buildAccessProblem(classReference, typeResolveResult, aClass)); - } else if (aClass.isInterface() && constructorCall instanceof PsiNewExpression) { - final PsiReferenceParameterList typeArgumentList = ((PsiNewExpression) constructorCall).getTypeArgumentList(); - if (typeArgumentList.getTypeArguments().length > 0) { - holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeArgumentList).descriptionAndTooltip("Anonymous class implements interface; cannot have type " + - "arguments").create()); - } - } - } else { - PsiElement place = list; - if (constructorCall instanceof PsiNewExpression) { - final PsiAnonymousClass anonymousClass = ((PsiNewExpression) constructorCall).getAnonymousClass(); - if (anonymousClass != null) { - place = anonymousClass; - } - } - - JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType) type, list, place); - MethodCandidateInfo result = null; - if (results.length == 1) { - result = (MethodCandidateInfo) results[0]; - } - - PsiMethod constructor = result == null ? null : result.getElement(); - - boolean applicable = true; - try { - final PsiDiamondType diamondType = constructorCall instanceof PsiNewExpression ? PsiDiamondType.getDiamondType((PsiNewExpression) constructorCall) : null; - final JavaResolveResult staticFactory = diamondType != null ? diamondType.getStaticFactory() : null; - applicable = staticFactory instanceof MethodCandidateInfo ? ((MethodCandidateInfo) staticFactory).isApplicable() : result != null && result.isApplicable(); - } catch (IndexNotReadyException e) { - // ignore - } - - PsiElement infoElement = list.getTextLength() > 0 ? list : constructorCall; - if (constructor == null) { - String name = aClass.getName(); - name += buildArgTypesList(list); - String description = JavaErrorBundle.message("cannot.resolve.constructor", name); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).navigationShift(+1).create(); - if (info != null) { - WrapExpressionFix.registerWrapAction(results, list.getExpressions(), info); - registerFixesOnInvalidConstructorCall(constructorCall, classReference, list, aClass, constructors, results, infoElement, info); - holder.add(info); - } - } else { - if (classReference != null && (!result.isAccessible() || constructor.hasModifierProperty(PsiModifier.PROTECTED) && callingProtectedConstructorFromDerivedClass(constructorCall, - aClass))) { - holder.add(buildAccessProblem(classReference, result, constructor)); - } else if (!applicable) { - String constructorName = HighlightMessageUtil.getSymbolName(constructor, result.getSubstitutor()); - String containerName = HighlightMessageUtil.getSymbolName(constructor.getContainingClass(), result.getSubstitutor()); - String argTypes = buildArgTypesList(list); - String description = JavaErrorBundle.message("wrong.method.arguments", constructorName, containerName, argTypes); - String toolTip = createMismatchedArgumentsHtmlTooltip(result, list); - - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(infoElement).description(description).escapedToolTip(toolTip).navigationShift(+1).create(); - if (info != null) { - JavaResolveResult[] methodCandidates = results; - if (constructorCall instanceof PsiNewExpression) { - methodCandidates = resolveHelper.getReferencedMethodCandidates((PsiCallExpression) constructorCall, true); - } - registerFixesOnInvalidConstructorCall(constructorCall, classReference, list, aClass, constructors, methodCandidates, infoElement, info); - registerMethodReturnFixAction(info, result, constructorCall); - holder.add(info); - } - } else { - if (constructorCall instanceof PsiNewExpression) { - PsiReferenceParameterList typeArgumentList = ((PsiNewExpression) constructorCall).getTypeArgumentList(); - HighlightInfo info = GenericsHighlightUtil.checkReferenceTypeArgumentList(constructor, typeArgumentList, result.getSubstitutor(), false, javaSdkVersion); - if (info != null) { - holder.add(info); + PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + checkConstructorCall(typeResult, expression, type, classReference, holder, javaSdkVersion); + } + + @RequiredReadAction + public static void checkConstructorCall( + PsiClassType.ClassResolveResult typeResolveResult, + PsiConstructorCall constructorCall, + PsiType type, + PsiJavaCodeReferenceElement classReference, + HighlightInfoHolder holder, + JavaSdkVersion javaSdkVersion + ) { + PsiExpressionList list = constructorCall.getArgumentList(); + if (list == null) { + return; + } + PsiClass aClass = typeResolveResult.getElement(); + if (aClass == null) { + return; + } + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(holder.getProject()).getResolveHelper(); + PsiClass accessObjectClass = null; + if (constructorCall instanceof PsiNewExpression newExpr) { + PsiExpression qualifier = newExpr.getQualifier(); + if (qualifier != null) { + accessObjectClass = (PsiClass) PsiUtil.getAccessObjectClass(qualifier).getElement(); + } + } + if (classReference != null && !resolveHelper.isAccessible(aClass, constructorCall, accessObjectClass)) { + PsiElement refName = classReference.getReferenceNameElement(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refName) + .descriptionAndTooltip(HighlightUtil.buildProblemWithAccessDescription(classReference, typeResolveResult)); + HighlightUtil.registerAccessQuickFixAction(aClass, classReference, hlBuilder, refName.getTextRange(), null); + holder.add(hlBuilder.create()); + return; + } + PsiMethod[] constructors = aClass.getConstructors(); + + if (constructors.length == 0) { + if (list.getExpressions().length != 0) { + String constructorName = aClass.getName(); + String argTypes = buildArgTypesList(list); + LocalizeValue tooltip = createMismatchedArgumentsHtmlTooltip( + list, + null, + PsiParameter.EMPTY_ARRAY, + constructorName, + PsiSubstitutor.EMPTY, + aClass + ); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .description(JavaErrorLocalize.wrongConstructorArguments(constructorName + "()", argTypes)) + .escapedToolTip(tooltip) + .navigationShift(+1); + hlBuilder.newFix(QuickFixFactory.getInstance().createCreateConstructorFromCallFix(constructorCall)) + .fixRange(constructorCall.getTextRange()) + .register(); + if (classReference != null) { + ConstructorParametersFixer.registerFixActions(classReference, constructorCall, hlBuilder, getFixRange(list)); + } + holder.add(hlBuilder.create()); + return; + } + if (classReference != null && aClass.isProtected() + && callingProtectedConstructorFromDerivedClass(constructorCall, aClass)) { + holder.add(buildAccessProblem(classReference, typeResolveResult, aClass)); + } + else if (aClass.isInterface() && constructorCall instanceof PsiNewExpression newExpr) { + PsiReferenceParameterList typeArgumentList = newExpr.getTypeArgumentList(); + if (typeArgumentList.getTypeArguments().length > 0) { + holder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeArgumentList) + .descriptionAndTooltip("Anonymous class implements interface; cannot have type arguments") + .create()); + } } - } } - } + else { + PsiElement place = list; + if (constructorCall instanceof PsiNewExpression newExpr) { + PsiAnonymousClass anonymousClass = newExpr.getAnonymousClass(); + if (anonymousClass != null) { + place = anonymousClass; + } + } - if (result != null && !holder.hasErrorResults()) { - holder.add(checkVarargParameterErasureToBeAccessible(result, constructorCall)); - } - } - } - - /** - * If the compile-time declaration is applicable by variable arity invocation, - * then where the last formal parameter type of the invocation type of the method is Fn[], - * it is a compile-time error if the type which is the erasure of Fn is not accessible at the point of invocation. - */ - private static HighlightInfo checkVarargParameterErasureToBeAccessible(MethodCandidateInfo info, PsiCall place) { - final PsiMethod method = info.getElement(); - if (info.isVarargs() || method.isVarArgs() && !PsiUtil.isLanguageLevel8OrHigher(place)) { - final PsiParameter[] parameters = method.getParameterList().getParameters(); - final PsiType componentType = ((PsiEllipsisType) parameters[parameters.length - 1].getType()).getComponentType(); - final PsiType substitutedTypeErasure = TypeConversionUtil.erasure(info.getSubstitutor().substitute(componentType)); - final PsiClass targetClass = PsiUtil.resolveClassInClassTypeOnly(substitutedTypeErasure); - if (targetClass != null && !PsiUtil.isAccessible(targetClass, place, null)) { - final PsiExpressionList argumentList = place.getArgumentList(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip("Formal varargs element type " + PsiFormatUtil.formatClass(targetClass, PsiFormatUtilBase - .SHOW_FQ_NAME) + " is inaccessible here").range(argumentList != null ? argumentList : place).create(); - } - } - return null; - } - - private static void registerFixesOnInvalidConstructorCall(PsiConstructorCall constructorCall, - PsiJavaCodeReferenceElement classReference, - PsiExpressionList list, - PsiClass aClass, - PsiMethod[] constructors, - JavaResolveResult[] results, - PsiElement infoElement, - @Nonnull final HighlightInfo info) { - QuickFixAction.registerQuickFixAction(info, constructorCall.getTextRange(), QUICK_FIX_FACTORY.createCreateConstructorFromCallFix(constructorCall)); - if (classReference != null) { - ConstructorParametersFixer.registerFixActions(classReference, constructorCall, info, getFixRange(infoElement)); - ChangeTypeArgumentsFix.registerIntentions(results, list, info, aClass); - ConvertDoubleToFloatFix.registerIntentions(results, list, info, null); - } - registerChangeMethodSignatureFromUsageIntentions(results, list, info, null); - PermuteArgumentsFix.registerFix(info, constructorCall, toMethodCandidates(results), getFixRange(list)); - registerChangeParameterClassFix(constructorCall, list, info); - QuickFixAction.registerQuickFixAction(info, getFixRange(list), QUICK_FIX_FACTORY.createSurroundWithArrayFix(constructorCall, null)); - ChangeStringLiteralToCharInMethodCallFix.registerFixes(constructors, constructorCall, info); - } - - private static HighlightInfo buildAccessProblem(@Nonnull PsiJavaCodeReferenceElement classReference, JavaResolveResult result, PsiMember elementToFix) { - String description = HighlightUtil.buildProblemWithAccessDescription(classReference, result); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(classReference).descriptionAndTooltip(description).navigationShift(+1).create(); - if (result.isStaticsScopeCorrect()) { - HighlightUtil.registerAccessQuickFixAction(elementToFix, classReference, info, result.getCurrentFileResolveScope()); - } - return info; - } + JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType) type, list, place); + MethodCandidateInfo result = null; + if (results.length == 1) { + result = (MethodCandidateInfo) results[0]; + } - private static boolean callingProtectedConstructorFromDerivedClass(PsiConstructorCall place, PsiClass constructorClass) { - if (constructorClass == null) { - return false; - } - // indirect instantiation via anonymous class is ok - if (place instanceof PsiNewExpression && ((PsiNewExpression) place).getAnonymousClass() != null) { - return false; - } - PsiElement curElement = place; - PsiClass containingClass = constructorClass.getContainingClass(); - while (true) { - PsiClass aClass = PsiTreeUtil.getParentOfType(curElement, PsiClass.class); - if (aClass == null) { - return false; - } - curElement = aClass; - if ((aClass.isInheritor(constructorClass, true) || containingClass != null && aClass.isInheritor(containingClass, true)) && !JavaPsiFacade.getInstance(aClass.getProject()) - .arePackagesTheSame(aClass, constructorClass)) { - return true; - } - } - } - - private static String buildArgTypesList(PsiExpressionList list) { - StringBuilder builder = new StringBuilder(); - builder.append("("); - PsiExpression[] args = list.getExpressions(); - for (int i = 0; i < args.length; i++) { - if (i > 0) { - builder.append(", "); - } - PsiType argType = args[i].getType(); - builder.append(argType != null ? JavaHighlightUtil.formatType(argType) : "?"); - } - builder.append(")"); - return builder.toString(); - } - - private static void registerChangeParameterClassFix(@Nonnull PsiCall methodCall, @Nonnull PsiExpressionList list, HighlightInfo highlightInfo) { - final JavaResolveResult result = methodCall.resolveMethodGenerics(); - PsiMethod method = (PsiMethod) result.getElement(); - final PsiSubstitutor substitutor = result.getSubstitutor(); - PsiExpression[] expressions = list.getExpressions(); - if (method == null) { - return; + PsiMethod constructor = result == null ? null : result.getElement(); + + boolean applicable = true; + try { + PsiDiamondType diamondType = + constructorCall instanceof PsiNewExpression newExpr ? PsiDiamondType.getDiamondType(newExpr) : null; + JavaResolveResult staticFactory = diamondType != null ? diamondType.getStaticFactory() : null; + applicable = staticFactory instanceof MethodCandidateInfo info + ? info.isApplicable() + : result != null && result.isApplicable(); + } + catch (IndexNotReadyException e) { + // ignore + } + + PsiElement infoElement = list.getTextLength() > 0 ? list : constructorCall; + if (constructor == null) { + String name = aClass.getName(); + name += buildArgTypesList(list); + HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodReferenceUnresolvedConstructor(name)) + .navigationShift(+1); + WrapExpressionFix.registerWrapAction(results, list.getExpressions(), info); + registerFixesOnInvalidConstructorCall( + constructorCall, + classReference, + list, + aClass, + constructors, + results, + infoElement, + info + ); + holder.add(info.create()); + } + else if (classReference != null && (!result.isAccessible() + || constructor.isProtected() && callingProtectedConstructorFromDerivedClass(constructorCall, aClass))) { + holder.add(buildAccessProblem(classReference, result, constructor)); + } + else if (!applicable) { + LocalizeValue constructorName = HighlightMessageUtil.getSymbolName(constructor, result.getSubstitutor()); + LocalizeValue containerName = HighlightMessageUtil.getSymbolName(constructor.getContainingClass(), result.getSubstitutor()); + String argTypes = buildArgTypesList(list); + LocalizeValue tooltip = createMismatchedArgumentsHtmlTooltip(result, list); + + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(infoElement) + .description(JavaCompilationErrorLocalize.callWrongArguments(constructorName, containerName, argTypes)) + .escapedToolTip(tooltip) + .navigationShift(+1); + JavaResolveResult[] methodCandidates = results; + if (constructorCall instanceof PsiNewExpression newExpr) { + methodCandidates = resolveHelper.getReferencedMethodCandidates(newExpr, true); + } + registerFixesOnInvalidConstructorCall( + constructorCall, + classReference, + list, + aClass, + constructors, + methodCandidates, + infoElement, + hlBuilder + ); + registerMethodReturnFixAction(hlBuilder, result, constructorCall); + holder.add(hlBuilder.create()); + } + else if (constructorCall instanceof PsiNewExpression newExpr) { + HighlightInfo.Builder hlBuilder = GenericsHighlightUtil.checkReferenceTypeArgumentList( + constructor, + newExpr.getTypeArgumentList(), + result.getSubstitutor(), + false, + javaSdkVersion + ); + if (hlBuilder != null) { + holder.add(hlBuilder.create()); + } + } + + if (result != null && !holder.hasErrorResults()) { + HighlightInfo.Builder hlBuilder = checkVarargParameterErasureToBeAccessible(result, constructorCall); + if (hlBuilder != null) { + holder.add(hlBuilder.create()); + } + } + } } - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length != expressions.length) { - return; + + /** + * If the compile-time declaration is applicable by variable arity invocation, + * then where the last formal parameter type of the invocation type of the method is Fn[], + * it is a compile-time error if the type which is the erasure of Fn is not accessible at the point of invocation. + */ + @RequiredReadAction + private static HighlightInfo.Builder checkVarargParameterErasureToBeAccessible(MethodCandidateInfo info, PsiCall place) { + PsiMethod method = info.getElement(); + if (info.isVarargs() || method.isVarArgs() && !PsiUtil.isLanguageLevel8OrHigher(place)) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiType componentType = ((PsiEllipsisType) parameters[parameters.length - 1].getType()).getComponentType(); + PsiType substitutedTypeErasure = TypeConversionUtil.erasure(info.getSubstitutor().substitute(componentType)); + PsiClass targetClass = PsiUtil.resolveClassInClassTypeOnly(substitutedTypeErasure); + if (targetClass != null && !PsiUtil.isAccessible(targetClass, place, null)) { + PsiExpressionList argumentList = place.getArgumentList(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(JavaCompilationErrorLocalize.callFormalVarargsElementTypeInaccessibleHere( + PsiFormatUtil.formatClass(targetClass, PsiFormatUtilBase.SHOW_FQ_NAME) + )) + .range(argumentList != null ? argumentList : place); + } + } + return null; } - for (int i = 0; i < expressions.length; i++) { - final PsiExpression expression = expressions[i]; - final PsiParameter parameter = parameters[i]; - final PsiType expressionType = expression.getType(); - final PsiType parameterType = substitutor.substitute(parameter.getType()); - if (expressionType == null || expressionType instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(expressionType) || expressionType instanceof PsiArrayType) { - continue; - } - if (parameterType instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(parameterType) || parameterType instanceof PsiArrayType) { - continue; - } - if (parameterType.isAssignableFrom(expressionType)) { - continue; - } - PsiClass parameterClass = PsiUtil.resolveClassInType(parameterType); - PsiClass expressionClass = PsiUtil.resolveClassInType(expressionType); - if (parameterClass == null || expressionClass == null) { - continue; - } - if (expressionClass instanceof PsiAnonymousClass) { - continue; - } - if (parameterClass.isInheritor(expressionClass, true)) { - continue; - } - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createChangeParameterClassFix(expressionClass, (PsiClassType) parameterType)); + + @RequiredReadAction + private static void registerFixesOnInvalidConstructorCall( + PsiConstructorCall constructorCall, + PsiJavaCodeReferenceElement classReference, + PsiExpressionList list, + PsiClass aClass, + PsiMethod[] constructors, + JavaResolveResult[] results, + PsiElement infoElement, + HighlightInfo.Builder hlBuilder + ) { + hlBuilder.newFix(QuickFixFactory.getInstance().createCreateConstructorFromCallFix(constructorCall)) + .fixRange(constructorCall.getTextRange()) + .register(); + if (classReference != null) { + ConstructorParametersFixer.registerFixActions(classReference, constructorCall, hlBuilder, getFixRange(infoElement)); + ChangeTypeArgumentsFix.registerIntentions(results, list, hlBuilder, aClass); + ConvertDoubleToFloatFix.registerIntentions(results, list, hlBuilder, null); + } + registerChangeMethodSignatureFromUsageIntentions(results, list, hlBuilder, null); + PermuteArgumentsFix.registerFix(hlBuilder, constructorCall, toMethodCandidates(results), getFixRange(list)); + registerChangeParameterClassFix(constructorCall, list, hlBuilder); + hlBuilder.newFix(QuickFixFactory.getInstance().createSurroundWithArrayFix(constructorCall, null)) + .fixRange(getFixRange(list)) + .register(); + ChangeStringLiteralToCharInMethodCallFix.registerFixes(constructors, constructorCall, hlBuilder); + } + + @RequiredReadAction + private static HighlightInfo buildAccessProblem( + PsiJavaCodeReferenceElement classReference, + JavaResolveResult result, + PsiMember elementToFix + ) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(classReference) + .descriptionAndTooltip(HighlightUtil.buildProblemWithAccessDescription(classReference, result)) + .navigationShift(+1); + if (result.isStaticsScopeCorrect()) { + HighlightUtil.registerAccessQuickFixAction( + elementToFix, + classReference, + hlBuilder, + classReference.getTextRange(), + result.getCurrentFileResolveScope() + ); + } + return hlBuilder.create(); } - } - - private static void registerChangeMethodSignatureFromUsageIntentions(@Nonnull JavaResolveResult[] candidates, - @Nonnull PsiExpressionList list, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange) { - if (candidates.length == 0) { - return; + + private static boolean callingProtectedConstructorFromDerivedClass(PsiConstructorCall place, PsiClass constructorClass) { + if (constructorClass == null) { + return false; + } + // indirect instantiation via anonymous class is ok + if (place instanceof PsiNewExpression newExpr && newExpr.getAnonymousClass() != null) { + return false; + } + PsiElement curElement = place; + PsiClass containingClass = constructorClass.getContainingClass(); + while (true) { + PsiClass aClass = PsiTreeUtil.getParentOfType(curElement, PsiClass.class); + if (aClass == null) { + return false; + } + curElement = aClass; + if ((aClass.isInheritor(constructorClass, true) || containingClass != null + && aClass.isInheritor(containingClass, true)) + && !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, constructorClass)) { + return true; + } + } } - PsiExpression[] expressions = list.getExpressions(); - for (JavaResolveResult candidate : candidates) { - registerChangeMethodSignatureFromUsageIntention(expressions, highlightInfo, fixRange, candidate, list); + + private static String buildArgTypesList(PsiExpressionList list) { + StringBuilder builder = new StringBuilder(); + builder.append("("); + PsiExpression[] args = list.getExpressions(); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + builder.append(", "); + } + PsiType argType = args[i].getType(); + builder.append(argType != null ? JavaHighlightUtil.formatType(argType) : "?"); + } + builder.append(")"); + return builder.toString(); + } + + private static void registerChangeParameterClassFix( + PsiCall methodCall, + PsiExpressionList list, + HighlightInfo.Builder hlBuilder + ) { + JavaResolveResult result = methodCall.resolveMethodGenerics(); + PsiMethod method = (PsiMethod) result.getElement(); + PsiSubstitutor substitutor = result.getSubstitutor(); + PsiExpression[] expressions = list.getExpressions(); + if (method == null) { + return; + } + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != expressions.length) { + return; + } + for (int i = 0; i < expressions.length; i++) { + PsiExpression expression = expressions[i]; + PsiParameter parameter = parameters[i]; + PsiType expressionType = expression.getType(); + PsiType parameterType = substitutor.substitute(parameter.getType()); + if (expressionType == null + || expressionType instanceof PsiPrimitiveType + || TypeConversionUtil.isNullType(expressionType) + || expressionType instanceof PsiArrayType) { + continue; + } + if (parameterType instanceof PsiPrimitiveType + || TypeConversionUtil.isNullType(parameterType) + || parameterType instanceof PsiArrayType) { + continue; + } + if (parameterType.isAssignableFrom(expressionType)) { + continue; + } + PsiClass parameterClass = PsiUtil.resolveClassInType(parameterType); + PsiClass expressionClass = PsiUtil.resolveClassInType(expressionType); + if (parameterClass == null + || expressionClass == null + || expressionClass instanceof PsiAnonymousClass + || parameterClass.isInheritor(expressionClass, true)) { + continue; + } + hlBuilder.registerFix(QuickFixFactory.getInstance() + .createChangeParameterClassFix(expressionClass, (PsiClassType) parameterType)); + } } - } - - private static void registerChangeMethodSignatureFromUsageIntention(@Nonnull PsiExpression[] expressions, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange, - @Nonnull JavaResolveResult candidate, - @Nonnull PsiElement context) { - if (!candidate.isStaticsScopeCorrect()) { - return; + + private static void registerChangeMethodSignatureFromUsageIntentions( + JavaResolveResult[] candidates, + PsiExpressionList list, + HighlightInfo.@Nullable Builder highlightInfo, + @Nullable TextRange fixRange + ) { + if (candidates.length == 0) { + return; + } + PsiExpression[] expressions = list.getExpressions(); + for (JavaResolveResult candidate : candidates) { + registerChangeMethodSignatureFromUsageIntention(expressions, highlightInfo, fixRange, candidate, list); + } } - PsiMethod method = (PsiMethod) candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - if (method != null && context.getManager().isInProject(method)) { - IntentionAction fix = QUICK_FIX_FACTORY.createChangeMethodSignatureFromUsageFix(method, expressions, substitutor, context, false, 2); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, fix); - IntentionAction f2 = QUICK_FIX_FACTORY.createChangeMethodSignatureFromUsageReverseOrderFix(method, expressions, substitutor, context, false, 2); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, f2); + + private static void registerChangeMethodSignatureFromUsageIntention( + PsiExpression[] expressions, + HighlightInfo.@Nullable Builder hlBuilder, + @Nullable TextRange fixRange, + JavaResolveResult candidate, + PsiElement context + ) { + if (hlBuilder == null || !candidate.isStaticsScopeCorrect()) { + return; + } + PsiMethod method = (PsiMethod) candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + if (method != null && context.getManager().isInProject(method)) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + hlBuilder.newFix(factory.createChangeMethodSignatureFromUsageFix(method, expressions, substitutor, context, false, 2)) + .optionalFixRange(fixRange).register() + .newFix(factory.createChangeMethodSignatureFromUsageReverseOrderFix(method, expressions, substitutor, context, false, 2)) + .optionalFixRange(fixRange).register(); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightNamesUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightNamesUtil.java index bc8ebba919..397c66ae93 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightNamesUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightNamesUtil.java @@ -19,6 +19,7 @@ import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.impl.psi.impl.source.tree.ElementType; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.colorScheme.TextAttributes; import consulo.colorScheme.TextAttributesKey; import consulo.colorScheme.TextAttributesScheme; @@ -40,319 +41,370 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.logging.Logger; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; public class HighlightNamesUtil { - private static final Logger LOG = Logger.getInstance(HighlightNamesUtil.class); + private static final Logger LOG = Logger.getInstance(HighlightNamesUtil.class); - @Nullable - public static HighlightInfo highlightMethodName(@Nonnull PsiMethod method, @Nonnull PsiElement elementToHighlight, final boolean isDeclaration, @Nonnull TextAttributesScheme colorsScheme) { - return highlightMethodName(method, elementToHighlight, elementToHighlight.getTextRange(), colorsScheme, isDeclaration); - } - - /** - * @param methodOrClass method to highlight; class is passed instead of implicit constructor - */ - @Nullable - public static HighlightInfo highlightMethodName(@Nonnull PsiMember methodOrClass, - @Nonnull PsiElement elementToHighlight, - @Nonnull TextRange range, - @Nonnull TextAttributesScheme colorsScheme, - final boolean isDeclaration) { - boolean isInherited = false; - - if (!isDeclaration) { - if (isCalledOnThis(elementToHighlight)) { - final PsiClass containingClass = methodOrClass instanceof PsiMethod ? methodOrClass.getContainingClass() : null; - PsiClass enclosingClass = containingClass == null ? null : PsiTreeUtil.getParentOfType(elementToHighlight, PsiClass.class); - while (enclosingClass != null) { - isInherited = enclosingClass.isInheritor(containingClass, true); - if (isInherited) { - break; - } - enclosingClass = PsiTreeUtil.getParentOfType(enclosingClass, PsiClass.class, true); - } - } + @Nullable + @RequiredReadAction + public static HighlightInfo highlightMethodName( + PsiMethod method, + PsiElement elementToHighlight, + boolean isDeclaration, + TextAttributesScheme colorsScheme + ) { + return highlightMethodName(method, elementToHighlight, elementToHighlight.getTextRange(), colorsScheme, isDeclaration); } - LOG.assertTrue(methodOrClass instanceof PsiMethod || !isDeclaration); - HighlightInfoType type = methodOrClass instanceof PsiMethod ? getMethodNameHighlightType((PsiMethod) methodOrClass, isDeclaration, isInherited) : JavaHighlightInfoTypes.CONSTRUCTOR_CALL; - if (type != null) { - TextAttributes attributes = mergeWithScopeAttributes(methodOrClass, type, colorsScheme); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); - if (attributes != null) { - builder.textAttributes(attributes); - } - return builder.createUnconditionally(); - } - return null; - } + /** + * @param methodOrClass method to highlight; class is passed instead of implicit constructor + */ + @Nullable + public static HighlightInfo highlightMethodName( + PsiMember methodOrClass, + PsiElement elementToHighlight, + TextRange range, + TextAttributesScheme colorsScheme, + boolean isDeclaration + ) { + boolean isInherited = false; - private static boolean isCalledOnThis(@Nonnull PsiElement elementToHighlight) { - PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(elementToHighlight, PsiMethodCallExpression.class); - if (methodCallExpression != null) { - PsiElement qualifier = methodCallExpression.getMethodExpression().getQualifier(); - if (qualifier == null || qualifier instanceof PsiThisExpression) { - return true; - } - } - return false; - } + if (!isDeclaration && isCalledOnThis(elementToHighlight)) { + PsiClass containingClass = methodOrClass instanceof PsiMethod ? methodOrClass.getContainingClass() : null; + PsiClass enclosingClass = containingClass == null ? null : PsiTreeUtil.getParentOfType(elementToHighlight, PsiClass.class); + while (enclosingClass != null) { + isInherited = enclosingClass.isInheritor(containingClass, true); + if (isInherited) { + break; + } + enclosingClass = PsiTreeUtil.getParentOfType(enclosingClass, PsiClass.class, true); + } + } - private static TextAttributes mergeWithScopeAttributes(@Nullable PsiElement element, @Nonnull HighlightInfoType type, @Nonnull TextAttributesScheme colorsScheme) { - TextAttributes regularAttributes = SeverityRegistrarUtil.getAttributesByType(element, type, colorsScheme); - if (element == null) { - return regularAttributes; + LOG.assertTrue(methodOrClass instanceof PsiMethod || !isDeclaration); + HighlightInfoType type = methodOrClass instanceof PsiMethod method + ? getMethodNameHighlightType(method, isDeclaration, isInherited) + : JavaHighlightInfoTypes.CONSTRUCTOR_CALL; + if (type != null) { + TextAttributes attributes = mergeWithScopeAttributes(methodOrClass, type, colorsScheme); + HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); + if (attributes != null) { + builder.textAttributes(attributes); + } + return builder.createUnconditionally(); + } + return null; } - TextAttributes scopeAttributes = getScopeAttributes(element, colorsScheme); - return TextAttributes.merge(scopeAttributes, regularAttributes); - } - @Nonnull - public static HighlightInfo highlightClassName(@Nullable PsiClass aClass, @Nonnull PsiElement elementToHighlight, @Nonnull TextAttributesScheme colorsScheme) { - TextRange range = elementToHighlight.getTextRange(); - if (elementToHighlight instanceof PsiJavaCodeReferenceElement) { - final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement) elementToHighlight; - PsiElement identifier = referenceElement.getReferenceNameElement(); - if (identifier != null) { - range = identifier.getTextRange(); - } + private static boolean isCalledOnThis(PsiElement elementToHighlight) { + PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(elementToHighlight, PsiMethodCallExpression.class); + if (methodCallExpression != null) { + PsiElement qualifier = methodCallExpression.getMethodExpression().getQualifier(); + if (qualifier == null || qualifier instanceof PsiThisExpression) { + return true; + } + } + return false; } - // This will highlight @ sign in annotation as well. - final PsiElement parent = elementToHighlight.getParent(); - if (parent instanceof PsiAnnotation) { - final PsiAnnotation psiAnnotation = (PsiAnnotation) parent; - range = new TextRange(psiAnnotation.getTextRange().getStartOffset(), range.getEndOffset()); + private static TextAttributes mergeWithScopeAttributes( + @Nullable PsiElement element, + HighlightInfoType type, + TextAttributesScheme colorsScheme + ) { + TextAttributes regularAttributes = SeverityRegistrarUtil.getAttributesByType(element, type, colorsScheme); + if (element == null) { + return regularAttributes; + } + TextAttributes scopeAttributes = getScopeAttributes(element, colorsScheme); + return TextAttributes.merge(scopeAttributes, regularAttributes); } - HighlightInfoType type = getClassNameHighlightType(aClass, elementToHighlight); - TextAttributes attributes = mergeWithScopeAttributes(aClass, type, colorsScheme); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); - if (attributes != null) { - builder.textAttributes(attributes); - } - return builder.createUnconditionally(); - } + @RequiredReadAction + public static HighlightInfo highlightClassName( + @Nullable PsiClass aClass, + PsiElement elementToHighlight, + TextAttributesScheme colorsScheme + ) { + TextRange range = elementToHighlight.getTextRange(); + if (elementToHighlight instanceof PsiJavaCodeReferenceElement javaCodeRef) { + PsiElement identifier = javaCodeRef.getReferenceNameElement(); + if (identifier != null) { + range = identifier.getTextRange(); + } + } - @Nullable - public static HighlightInfo highlightVariableName(@Nonnull PsiVariable variable, @Nonnull PsiElement elementToHighlight, @Nonnull TextAttributesScheme colorsScheme) { - HighlightInfoType varType = getVariableNameHighlightType(variable); - if (varType == null) { - return null; - } - if (variable instanceof PsiField) { - TextAttributes attributes = mergeWithScopeAttributes(variable, varType, colorsScheme); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(varType).range(elementToHighlight.getTextRange()); - if (attributes != null) { - builder.textAttributes(attributes); - } - return builder.createUnconditionally(); + // This will highlight @ sign in annotation as well. + PsiElement parent = elementToHighlight.getParent(); + if (parent instanceof PsiAnnotation annotation) { + range = new TextRange(annotation.getTextRange().getStartOffset(), range.getEndOffset()); + } + + HighlightInfoType type = getClassNameHighlightType(aClass, elementToHighlight); + TextAttributes attributes = mergeWithScopeAttributes(aClass, type, colorsScheme); + HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); + if (attributes != null) { + builder.textAttributes(attributes); + } + return builder.createUnconditionally(); } - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(varType).range(elementToHighlight); - return RainbowHighlighter.isRainbowEnabledWithInheritance(colorsScheme, JavaLanguage.INSTANCE) ? builder.createUnconditionally() : builder.create(); - } + @Nullable + @RequiredReadAction + public static HighlightInfo highlightVariableName( + PsiVariable variable, + PsiElement elementToHighlight, + TextAttributesScheme colorsScheme + ) { + HighlightInfoType varType = getVariableNameHighlightType(variable); + if (varType == null) { + return null; + } + if (variable instanceof PsiField field) { + TextAttributes attributes = mergeWithScopeAttributes(field, varType, colorsScheme); + HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(varType) + .range(elementToHighlight.getTextRange()); + if (attributes != null) { + builder.textAttributes(attributes); + } + return builder.createUnconditionally(); + } - @Nullable - public static HighlightInfo highlightClassNameInQualifier(@Nonnull PsiJavaCodeReferenceElement element, @Nonnull TextAttributesScheme colorsScheme) { - PsiElement qualifierExpression = element.getQualifier(); - if (qualifierExpression instanceof PsiJavaCodeReferenceElement) { - PsiElement resolved = ((PsiJavaCodeReferenceElement) qualifierExpression).resolve(); - if (resolved instanceof PsiClass) { - return highlightClassName((PsiClass) resolved, qualifierExpression, colorsScheme); - } + HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(varType).range(elementToHighlight); + return RainbowHighlighter.isRainbowEnabledWithInheritance(colorsScheme, JavaLanguage.INSTANCE) + ? builder.createUnconditionally() + : builder.create(); } - return null; - } - private static HighlightInfoType getMethodNameHighlightType(@Nonnull PsiMethod method, boolean isDeclaration, boolean isInheritedMethod) { - if (method.isConstructor()) { - return isDeclaration ? JavaHighlightInfoTypes.CONSTRUCTOR_DECLARATION : JavaHighlightInfoTypes.CONSTRUCTOR_CALL; - } - if (isDeclaration) { - return JavaHighlightInfoTypes.METHOD_DECLARATION; - } - if (method.hasModifierProperty(PsiModifier.STATIC)) { - return JavaHighlightInfoTypes.STATIC_METHOD; - } - if (isInheritedMethod) { - return JavaHighlightInfoTypes.INHERITED_METHOD; - } - if (method.hasModifierProperty(PsiModifier.ABSTRACT)) { - return JavaHighlightInfoTypes.ABSTRACT_METHOD; + @Nullable + @RequiredReadAction + public static HighlightInfo highlightClassNameInQualifier( + PsiJavaCodeReferenceElement element, + TextAttributesScheme colorsScheme + ) { + PsiElement qualifierExpression = element.getQualifier(); + if (qualifierExpression instanceof PsiJavaCodeReferenceElement javaCodeRef + && javaCodeRef.resolve() instanceof PsiClass psiClass) { + return highlightClassName(psiClass, qualifierExpression, colorsScheme); + } + return null; } - return JavaHighlightInfoTypes.METHOD_CALL; - } - @Nullable - private static HighlightInfoType getVariableNameHighlightType(@Nonnull PsiVariable var) { - if (var instanceof PsiLocalVariable || var instanceof PsiParameter && ((PsiParameter) var).getDeclarationScope() instanceof PsiForeachStatement) { - return JavaHighlightInfoTypes.LOCAL_VARIABLE; - } - if (var instanceof PsiField) { - return var.hasModifierProperty(PsiModifier.STATIC) ? var.hasModifierProperty(PsiModifier.FINAL) ? JavaHighlightInfoTypes.STATIC_FINAL_FIELD : JavaHighlightInfoTypes.STATIC_FIELD : var - .hasModifierProperty(PsiModifier.FINAL) ? JavaHighlightInfoTypes.INSTANCE_FINAL_FIELD : JavaHighlightInfoTypes.INSTANCE_FIELD; - } - if (var instanceof PsiParameter) { - return ((PsiParameter) var).getDeclarationScope() instanceof PsiLambdaExpression ? JavaHighlightInfoTypes.LAMBDA_PARAMETER : JavaHighlightInfoTypes.PARAMETER; + private static HighlightInfoType getMethodNameHighlightType( + PsiMethod method, + boolean isDeclaration, + boolean isInheritedMethod + ) { + if (method.isConstructor()) { + return isDeclaration ? JavaHighlightInfoTypes.CONSTRUCTOR_DECLARATION : JavaHighlightInfoTypes.CONSTRUCTOR_CALL; + } + if (isDeclaration) { + return JavaHighlightInfoTypes.METHOD_DECLARATION; + } + if (method.isStatic()) { + return JavaHighlightInfoTypes.STATIC_METHOD; + } + if (isInheritedMethod) { + return JavaHighlightInfoTypes.INHERITED_METHOD; + } + if (method.isAbstract()) { + return JavaHighlightInfoTypes.ABSTRACT_METHOD; + } + return JavaHighlightInfoTypes.METHOD_CALL; } - return null; - } - @Nonnull - private static HighlightInfoType getClassNameHighlightType(@Nullable PsiClass aClass, @Nullable PsiElement element) { - if (element instanceof PsiJavaCodeReferenceElement && element.getParent() instanceof PsiAnonymousClass) { - return JavaHighlightInfoTypes.ANONYMOUS_CLASS_NAME; - } - if (aClass != null) { - if (aClass.isAnnotationType()) { - return JavaHighlightInfoTypes.ANNOTATION_NAME; - } - if (aClass.isInterface()) { - return JavaHighlightInfoTypes.INTERFACE_NAME; - } - if (aClass.isEnum()) { - return JavaHighlightInfoTypes.ENUM_NAME; - } - if (aClass instanceof PsiTypeParameter) { - return JavaHighlightInfoTypes.TYPE_PARAMETER_NAME; - } - final PsiModifierList modList = aClass.getModifierList(); - if (modList != null && modList.hasModifierProperty(PsiModifier.ABSTRACT)) { - return JavaHighlightInfoTypes.ABSTRACT_CLASS_NAME; - } + @Nullable + private static HighlightInfoType getVariableNameHighlightType(PsiVariable var) { + if (var instanceof PsiLocalVariable + || var instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiForeachStatement) { + return JavaHighlightInfoTypes.LOCAL_VARIABLE; + } + if (var instanceof PsiField field) { + return field.isStatic() + ? field.isFinal() ? JavaHighlightInfoTypes.STATIC_FINAL_FIELD : JavaHighlightInfoTypes.STATIC_FIELD + : field.isFinal() ? JavaHighlightInfoTypes.INSTANCE_FINAL_FIELD : JavaHighlightInfoTypes.INSTANCE_FIELD; + } + if (var instanceof PsiParameter parameter) { + return parameter.getDeclarationScope() instanceof PsiLambdaExpression + ? JavaHighlightInfoTypes.LAMBDA_PARAMETER + : JavaHighlightInfoTypes.PARAMETER; + } + return null; } - // use class by default - return JavaHighlightInfoTypes.CLASS_NAME; - } - @Nullable - public static HighlightInfo highlightReassignedVariable(@Nonnull PsiVariable variable, @Nonnull PsiElement elementToHighlight) { - if (variable instanceof PsiLocalVariable) { - return HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.REASSIGNED_LOCAL_VARIABLE).range(elementToHighlight).create(); - } - if (variable instanceof PsiParameter) { - return HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.REASSIGNED_PARAMETER).range(elementToHighlight).create(); + private static HighlightInfoType getClassNameHighlightType(@Nullable PsiClass aClass, @Nullable PsiElement element) { + if (element instanceof PsiJavaCodeReferenceElement && element.getParent() instanceof PsiAnonymousClass) { + return JavaHighlightInfoTypes.ANONYMOUS_CLASS_NAME; + } + if (aClass != null) { + if (aClass.isAnnotationType()) { + return JavaHighlightInfoTypes.ANNOTATION_NAME; + } + if (aClass.isInterface()) { + return JavaHighlightInfoTypes.INTERFACE_NAME; + } + if (aClass.isEnum()) { + return JavaHighlightInfoTypes.ENUM_NAME; + } + if (aClass instanceof PsiTypeParameter) { + return JavaHighlightInfoTypes.TYPE_PARAMETER_NAME; + } + PsiModifierList modList = aClass.getModifierList(); + if (modList != null && modList.hasModifierProperty(PsiModifier.ABSTRACT)) { + return JavaHighlightInfoTypes.ABSTRACT_CLASS_NAME; + } + } + // use class by default + return JavaHighlightInfoTypes.CLASS_NAME; } - return null; - } - private static TextAttributes getScopeAttributes(@Nonnull PsiElement element, @Nonnull TextAttributesScheme colorsScheme) { - PsiFile file = element.getContainingFile(); - if (file == null) { - return null; - } - TextAttributes result = null; - DependencyValidationManager validationManager = DependencyValidationManager.getInstance(file.getProject()); - List> scopes = validationManager.getScopeBasedHighlightingCachedScopes(); - for (Pair scope : scopes) { - final NamedScope namedScope = scope.getFirst(); - final TextAttributesKey scopeKey = ScopeAttributesUtil.getScopeTextAttributeKey(namedScope.getName()); - final TextAttributes attributes = colorsScheme.getAttributes(scopeKey); - if (attributes == null || attributes.isEmpty()) { - continue; - } - final PackageSet packageSet = namedScope.getValue(); - if (packageSet != null && packageSet.contains(file.getVirtualFile(), file.getProject(), scope.getSecond())) { - result = TextAttributes.merge(attributes, result); - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder highlightReassignedVariable(PsiVariable variable, PsiElement elementToHighlight) { + if (variable instanceof PsiLocalVariable) { + return HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.REASSIGNED_LOCAL_VARIABLE) + .range(elementToHighlight); + } + if (variable instanceof PsiParameter) { + return HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.REASSIGNED_PARAMETER) + .range(elementToHighlight); + } + return null; } - return result; - } - @Nonnull - public static TextRange getMethodDeclarationTextRange(@Nonnull PsiMethod method) { - if (method instanceof SyntheticElement) { - return TextRange.EMPTY_RANGE; + private static TextAttributes getScopeAttributes(PsiElement element, TextAttributesScheme colorsScheme) { + PsiFile file = element.getContainingFile(); + if (file == null) { + return null; + } + TextAttributes result = null; + DependencyValidationManager validationManager = DependencyValidationManager.getInstance(file.getProject()); + List> scopes = validationManager.getScopeBasedHighlightingCachedScopes(); + for (Pair scope : scopes) { + NamedScope namedScope = scope.getFirst(); + TextAttributesKey scopeKey = ScopeAttributesUtil.getScopeTextAttributeKey(namedScope); + TextAttributes attributes = colorsScheme.getAttributes(scopeKey); + if (attributes == null || attributes.isEmpty()) { + continue; + } + PackageSet packageSet = namedScope.getValue(); + if (packageSet != null && packageSet.contains(file.getVirtualFile(), file.getProject(), scope.getSecond())) { + result = TextAttributes.merge(attributes, result); + } + } + return result; } - int start = stripAnnotationsFromModifierList(method.getModifierList()); - final TextRange throwsRange = method.getThrowsList().getTextRange(); - LOG.assertTrue(throwsRange != null, method); - int end = throwsRange.getEndOffset(); - return new TextRange(start, end); - } - @Nonnull - public static TextRange getFieldDeclarationTextRange(@Nonnull PsiField field) { - int start = stripAnnotationsFromModifierList(field.getModifierList()); - int end = field.getNameIdentifier().getTextRange().getEndOffset(); - return new TextRange(start, end); - } - - @Nonnull - public static TextRange getClassDeclarationTextRange(@Nonnull PsiClass aClass) { - if (aClass instanceof PsiEnumConstantInitializer) { - return ((PsiEnumConstantInitializer) aClass).getEnumConstant().getNameIdentifier().getTextRange(); - } - final PsiElement psiElement = aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass.getModifierList() == null ? aClass.getNameIdentifier() : - aClass.getModifierList(); - if (psiElement == null) { - return new TextRange(aClass.getTextRange().getStartOffset(), aClass.getTextRange().getStartOffset()); - } - int start = stripAnnotationsFromModifierList(psiElement); - PsiElement endElement = aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass.getImplementsList(); - if (endElement == null) { - endElement = aClass.getNameIdentifier(); + @RequiredReadAction + public static TextRange getMethodDeclarationTextRange(PsiMethod method) { + if (method instanceof SyntheticElement) { + return TextRange.EMPTY_RANGE; + } + int start = stripAnnotationsFromModifierList(method.getModifierList()); + TextRange throwsRange = method.getThrowsList().getTextRange(); + LOG.assertTrue(throwsRange != null, method); + int end = throwsRange.getEndOffset(); + return new TextRange(start, end); } - TextRange endTextRange = endElement == null ? null : endElement.getTextRange(); - int end = endTextRange == null ? start : endTextRange.getEndOffset(); - return new TextRange(start, end); - } - private static int stripAnnotationsFromModifierList(@Nonnull PsiElement element) { - TextRange textRange = element.getTextRange(); - if (textRange == null) { - return 0; - } - PsiAnnotation lastAnnotation = null; - for (PsiElement child : element.getChildren()) { - if (child instanceof PsiAnnotation) { - lastAnnotation = (PsiAnnotation) child; - } + @RequiredReadAction + public static TextRange getFieldDeclarationTextRange(PsiField field) { + int start = stripAnnotationsFromModifierList(field.getModifierList()); + int end = field.getNameIdentifier().getTextRange().getEndOffset(); + return new TextRange(start, end); } - if (lastAnnotation == null) { - return textRange.getStartOffset(); + + @RequiredReadAction + public static TextRange getClassDeclarationTextRange(PsiClass aClass) { + if (aClass instanceof PsiEnumConstantInitializer enumInitializer) { + return enumInitializer.getEnumConstant().getNameIdentifier().getTextRange(); + } + PsiElement psiElement = aClass instanceof PsiAnonymousClass anonymousClass + ? anonymousClass.getBaseClassReference() + : aClass.getModifierList() == null ? aClass.getNameIdentifier() : aClass.getModifierList(); + if (psiElement == null) { + return new TextRange(aClass.getTextRange().getStartOffset(), aClass.getTextRange().getStartOffset()); + } + int start = stripAnnotationsFromModifierList(psiElement); + PsiElement endElement = + aClass instanceof PsiAnonymousClass anonymousClass ? anonymousClass.getBaseClassReference() : aClass.getImplementsList(); + if (endElement == null) { + endElement = aClass.getNameIdentifier(); + } + TextRange endTextRange = endElement == null ? TextRange.EMPTY_RANGE : endElement.getTextRange(); + int end = endTextRange == TextRange.EMPTY_RANGE ? start : endTextRange.getEndOffset(); + return new TextRange(start, end); } - ASTNode node = lastAnnotation.getNode(); - if (node != null) { - do { - node = TreeUtil.nextLeaf(node); - } - while (node != null && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(node.getElementType())); + + @RequiredReadAction + private static int stripAnnotationsFromModifierList(PsiElement element) { + TextRange textRange = element.getTextRange(); + if (textRange == TextRange.EMPTY_RANGE) { + return 0; + } + PsiAnnotation lastAnnotation = null; + for (PsiElement child : element.getChildren()) { + if (child instanceof PsiAnnotation annotation) { + lastAnnotation = annotation; + } + } + if (lastAnnotation == null) { + return textRange.getStartOffset(); + } + ASTNode node = lastAnnotation.getNode(); + if (node != null) { + do { + node = TreeUtil.nextLeaf(node); + } + while (node != null && ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains(node.getElementType())); + } + if (node != null) { + return node.getTextRange().getStartOffset(); + } + return textRange.getStartOffset(); } - if (node != null) { - return node.getTextRange().getStartOffset(); + + @RequiredReadAction + public static HighlightInfo highlightPackage( + PsiElement resolved, + PsiJavaCodeReferenceElement elementToHighlight, + TextAttributesScheme scheme + ) { + PsiElement referenceNameElement = elementToHighlight.getReferenceNameElement(); + TextRange range; + if (referenceNameElement == null) { + range = elementToHighlight.getTextRange(); + } + else { + PsiElement nextSibling = PsiTreeUtil.nextLeaf(referenceNameElement); + if (nextSibling != null && nextSibling.getTextRange().isEmpty()) { + // empty PsiReferenceParameterList + nextSibling = PsiTreeUtil.nextLeaf(nextSibling); + } + if (nextSibling instanceof PsiJavaToken javaToken && javaToken.getTokenType() == JavaTokenType.DOT) { + range = new TextRange(referenceNameElement.getTextRange().getStartOffset(), javaToken.getTextRange().getEndOffset()); + } + else { + range = referenceNameElement.getTextRange(); + } + } + HighlightInfoType type = JavaHighlightInfoTypes.PACKAGE_NAME; + TextAttributes attributes = mergeWithScopeAttributes(resolved, type, scheme); + HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); + if (attributes != null) { + builder.textAttributes(attributes); + } + return builder.createUnconditionally(); } - return textRange.getStartOffset(); - } - public static HighlightInfo highlightPackage(@Nonnull PsiElement resolved, @Nonnull PsiJavaCodeReferenceElement elementToHighlight, @Nonnull TextAttributesScheme scheme) { - PsiElement referenceNameElement = elementToHighlight.getReferenceNameElement(); - TextRange range; - if (referenceNameElement == null) { - range = elementToHighlight.getTextRange(); - } else { - PsiElement nextSibling = PsiTreeUtil.nextLeaf(referenceNameElement); - if (nextSibling != null && nextSibling.getTextRange().isEmpty()) { - // empty PsiReferenceParameterList - nextSibling = PsiTreeUtil.nextLeaf(nextSibling); - } - if (nextSibling instanceof PsiJavaToken && ((PsiJavaToken) nextSibling).getTokenType() == JavaTokenType.DOT) { - range = new TextRange(referenceNameElement.getTextRange().getStartOffset(), nextSibling.getTextRange().getEndOffset()); - } else { - range = referenceNameElement.getTextRange(); - } + private static HighlightInfo.Builder nameBuilder(HighlightInfoType type) { + return HighlightInfo.newHighlightInfo(type)/*.toolId(JavaNamesHighlightVisitor.class)*/; } - HighlightInfoType type = JavaHighlightInfoTypes.PACKAGE_NAME; - TextAttributes attributes = mergeWithScopeAttributes(resolved, type, scheme); - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(type).range(range); - if (attributes != null) { - builder.textAttributes(attributes); + + @RequiredReadAction + static HighlightInfo highlightKeyword(PsiKeyword keyword) { + return nameBuilder(JavaHighlightInfoTypes.JAVA_KEYWORD).range(keyword).create(); } - return builder.createUnconditionally(); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java index 5c06ebbb6f..b91757f6fb 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -23,18 +23,16 @@ import com.intellij.java.analysis.impl.psi.util.EnclosingLoopOrSwitchMatcherExpression; import com.intellij.java.analysis.impl.psi.util.JavaMatchers; import com.intellij.java.analysis.impl.psi.util.PsiMatchers; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.language.impl.codeInsight.ExceptionUtil; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.codeInsight.daemon.impl.analysis.HighlightUtilBase; import com.intellij.java.language.impl.psi.impl.source.resolve.JavaResolveUtil; import com.intellij.java.language.impl.psi.impl.source.resolve.graphInference.InferenceSession; import com.intellij.java.language.impl.psi.impl.source.tree.ElementType; import com.intellij.java.language.impl.psi.impl.source.tree.java.PsiReferenceExpressionImpl; -import com.intellij.java.language.impl.psi.scope.processor.VariablesNotProcessor; -import com.intellij.java.language.impl.psi.scope.util.PsiScopesUtil; import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; import com.intellij.java.language.module.EffectiveLanguageLevelUtil; import com.intellij.java.language.projectRoots.JavaSdkVersion; @@ -44,29 +42,26 @@ import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.util.*; import consulo.annotation.access.RequiredReadAction; -import consulo.component.extension.Extensions; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.ast.IElementType; import consulo.language.editor.CodeInsightUtilCore; import consulo.language.editor.highlight.HighlightUsagesDescriptionLocation; +import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.editor.inspection.LocalQuickFixOnPsiElementAsIntentionAdapter; import consulo.language.editor.inspection.PriorityActionWrapper; -import consulo.language.editor.intention.IntentionAction; -import consulo.language.editor.intention.QuickFixAction; -import consulo.language.editor.intention.QuickFixActionRegistrar; -import consulo.language.editor.intention.UnresolvedReferenceQuickFixProvider; +import consulo.language.editor.intention.*; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.findUsage.FindUsagesProvider; import consulo.language.psi.*; -import consulo.language.psi.resolve.ResolveState; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.search.ContainerProvider; import consulo.language.psi.util.PsiMatcherImpl; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.module.Module; import consulo.module.content.FilePropertyPusher; @@ -80,3304 +75,3776 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.util.lang.xml.XmlStringUtil; import consulo.virtualFileSystem.VirtualFile; +import org.jspecify.annotations.Nullable; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.PropertyKey; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author cdr - * @since Jul 30, 2002 + * @since 2002-07-30 */ public class HighlightUtil extends HighlightUtilBase { - private static final Logger LOG = Logger.getInstance(HighlightUtil.class); - - private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); - - private static final Map> ourInterfaceIncompatibleModifiers = new HashMap<>(7); - private static final Map> ourMethodIncompatibleModifiers = new HashMap<>(11); - private static final Map> ourFieldIncompatibleModifiers = new HashMap<>(8); - private static final Map> ourClassIncompatibleModifiers = new HashMap<>(8); - private static final Map> ourClassInitializerIncompatibleModifiers = new HashMap<>(1); - private static final Map> ourModuleIncompatibleModifiers = new HashMap<>(1); - private static final Map> ourRequiresIncompatibleModifiers = new HashMap<>(2); - - private static final Set ourConstructorNotAllowedModifiers = Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier - .STRICTFP, PsiModifier.SYNCHRONIZED); - - private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; - - static { - ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)); - ourClassIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)); - ourClassIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); - ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - - ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Collections.emptySet()); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.emptySet()); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - - ourMethodIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, - PsiModifier.SYNCHRONIZED, PsiModifier.DEFAULT)); - ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, Set.of(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)); - ourMethodIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, Set.of(PsiModifier.ABSTRACT, PsiModifier.DEFAULT, PsiModifier.FINAL)); - ourMethodIncompatibleModifiers.put(PsiModifier.DEFAULT, Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE)); - ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, Set.of(PsiModifier.ABSTRACT)); - ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of(PsiModifier.ABSTRACT)); - ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)); - - ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.VOLATILE)); - ourFieldIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Collections.emptySet()); - ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL)); - - ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - - ourModuleIncompatibleModifiers.put(PsiModifier.OPEN, Collections.emptySet()); - - ourRequiresIncompatibleModifiers.put(PsiModifier.STATIC, Collections.emptySet()); - ourRequiresIncompatibleModifiers.put(PsiModifier.TRANSITIVE, Collections.emptySet()); - } - - private HighlightUtil() { - } - - @Nullable - private static String getIncompatibleModifier(String modifier, @Nullable PsiModifierList modifierList, @Nonnull Map> incompatibleModifiersHash) { - if (modifierList == null) { - return null; - } - - // modifier is always incompatible with itself - PsiElement[] modifiers = modifierList.getChildren(); - int modifierCount = 0; - for (PsiElement otherModifier : modifiers) { - if (Comparing.equal(modifier, otherModifier.getText(), true)) { - modifierCount++; - } - } - if (modifierCount > 1) { - return modifier; - } - - Set incompatibles = incompatibleModifiersHash.get(modifier); - if (incompatibles == null) { - return null; - } - final PsiElement parent = modifierList.getParent(); - final boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList); - final boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList); - for (@PsiModifier.ModifierConstant String incompatible : incompatibles) { - if (level8OrHigher) { - if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) { - continue; - } - } - if (parent instanceof PsiMethod) { - if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) { - continue; - } - - if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) { - final PsiClass containingClass = ((PsiMethod) parent).getContainingClass(); - if (containingClass == null || !containingClass.isInterface()) { - continue; - } - } - } - if (modifierList.hasModifierProperty(incompatible)) { - return incompatible; - } - if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) { - return incompatible; - } - } - - return null; - } - - /** - * make element protected/package-private/public suggestion - * for private method in the interface it should add default modifier as well - */ - public static void registerAccessQuickFixAction(@Nonnull PsiMember refElement, @Nonnull PsiJavaCodeReferenceElement place, @Nullable HighlightInfo errorResult, final PsiElement fileResolveScope) { - if (errorResult == null) { - return; - } - PsiClass accessObjectClass = null; - PsiElement qualifier = place.getQualifier(); - if (qualifier instanceof PsiExpression) { - accessObjectClass = (PsiClass) PsiUtil.getAccessObjectClass((PsiExpression) qualifier).getElement(); - } - registerReplaceInaccessibleFieldWithGetterSetterFix(refElement, place, accessObjectClass, errorResult); - - if (refElement instanceof PsiCompiledElement) { - return; - } - PsiModifierList modifierList = refElement.getModifierList(); - if (modifierList == null) { - return; - } - - PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); - if (packageLocalClassInTheMiddle != null) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return; - } - - try { - Project project = refElement.getProject(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); - modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); - String minModifier = PsiModifier.PACKAGE_LOCAL; - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { - minModifier = PsiModifier.PROTECTED; - } - if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - minModifier = PsiModifier.PUBLIC; - } - PsiClass containingClass = refElement.getContainingClass(); - if (containingClass != null && containingClass.isInterface()) { - minModifier = PsiModifier.PUBLIC; - } - String[] modifiers = { - PsiModifier.PACKAGE_LOCAL, - PsiModifier.PROTECTED, - PsiModifier.PUBLIC, - }; - for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { - @PsiModifier.ModifierConstant String modifier = modifiers[i]; - modifierListCopy.setModifierProperty(modifier, true); - if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { - IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(refElement, modifier, true, true); - TextRange fixRange = new TextRange(errorResult.getStartOffset(), errorResult.getEndOffset()); - PsiElement ref = place.getReferenceNameElement(); - if (ref != null) { - fixRange = fixRange.union(ref.getTextRange()); - } - QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix); - } - } - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Nullable - private static PsiClass getPackageLocalClassInTheMiddle(@Nonnull PsiElement place) { - if (place instanceof PsiReferenceExpression) { - // check for package-private classes in the middle - PsiReferenceExpression expression = (PsiReferenceExpression) place; - while (true) { - PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; - PsiClass aClass = field.getContainingClass(); - if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, place)) { - - return aClass; - } + private static final Logger LOG = Logger.getInstance(HighlightUtil.class); + + private static final Map> ourInterfaceIncompatibleModifiers = new HashMap<>(7); + private static final Map> ourMethodIncompatibleModifiers = new HashMap<>(11); + private static final Map> ourFieldIncompatibleModifiers = new HashMap<>(8); + private static final Map> ourClassIncompatibleModifiers = new HashMap<>(8); + private static final Map> ourClassInitializerIncompatibleModifiers = new HashMap<>(1); + private static final Map> ourModuleIncompatibleModifiers = new HashMap<>(1); + private static final Map> ourRequiresIncompatibleModifiers = new HashMap<>(2); + + private static final Set ourConstructorNotAllowedModifiers = + Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier + .STRICTFP, PsiModifier.SYNCHRONIZED); + + private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; + + static { + ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)); + ourClassIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED)); + ourClassIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); + ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourClassIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED)); + ourClassIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED)); + + ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of()); + ourInterfaceIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourInterfaceIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourInterfaceIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourInterfaceIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourInterfaceIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.NON_SEALED)); + ourInterfaceIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.SEALED)); + + ourMethodIncompatibleModifiers.put( + PsiModifier.ABSTRACT, + Set.of( + PsiModifier.NATIVE, + PsiModifier.STATIC, + PsiModifier.FINAL, + PsiModifier.PRIVATE, + PsiModifier.STRICTFP, + PsiModifier.SYNCHRONIZED, + PsiModifier.DEFAULT + ) + ); + ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, Set.of(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)); + ourMethodIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, Set.of(PsiModifier.ABSTRACT, PsiModifier.DEFAULT)); + ourMethodIncompatibleModifiers.put(PsiModifier.DEFAULT, Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.PRIVATE)); + ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, Set.of(PsiModifier.ABSTRACT)); + ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of(PsiModifier.ABSTRACT)); + ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)); + + ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.VOLATILE)); + ourFieldIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Set.of()); + ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL)); + + ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + + ourModuleIncompatibleModifiers.put(PsiModifier.OPEN, Set.of()); + + ourRequiresIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourRequiresIncompatibleModifiers.put(PsiModifier.TRANSITIVE, Set.of()); + } + + private HighlightUtil() { + } + + @Nullable + @RequiredReadAction + private static String getIncompatibleModifier( + String modifier, + @Nullable PsiModifierList modifierList, + Map> incompatibleModifiersHash + ) { + if (modifierList == null) { + return null; + } + + // modifier is always incompatible with itself + PsiElement[] modifiers = modifierList.getChildren(); + int modifierCount = 0; + for (PsiElement otherModifier : modifiers) { + if (Comparing.equal(modifier, otherModifier.getText(), true)) { + modifierCount++; + } + } + if (modifierCount > 1) { + return modifier; + } + + Set incompatibles = incompatibleModifiersHash.get(modifier); + if (incompatibles == null) { + return null; + } + boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList); + boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList); + for (@PsiModifier.ModifierConstant String incompatible : incompatibles) { + if (level8OrHigher) { + if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) { + continue; + } + } + if (modifierList.getParent() instanceof PsiMethod method) { + if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) { + continue; + } + + if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null || !containingClass.isInterface()) { + continue; + } + } + } + if (modifierList.hasModifierProperty(incompatible)) { + return incompatible; + } + if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) { + return incompatible; + } + } + + return null; + } + + /** + * make element protected/package-private/public suggestion + * for private method in the interface it should add default modifier as well + */ + @RequiredReadAction + public static void registerAccessQuickFixAction( + PsiMember refElement, + PsiJavaCodeReferenceElement place, + HighlightInfo.@Nullable Builder hlBuilder, + @Nullable TextRange hlRange, + PsiElement fileResolveScope + ) { + if (hlBuilder == null) { + return; + } + PsiClass accessObjectClass = null; + PsiElement qualifier = place.getQualifier(); + if (qualifier instanceof PsiExpression qExpr) { + accessObjectClass = (PsiClass) PsiUtil.getAccessObjectClass(qExpr).getElement(); + } + registerReplaceInaccessibleFieldWithGetterSetterFix(refElement, place, accessObjectClass, hlBuilder); + + if (refElement instanceof PsiCompiledElement) { + return; + } + PsiModifierList modifierList = refElement.getModifierList(); + if (modifierList == null) { + return; + } + + PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); + if (packageLocalClassInTheMiddle != null) { + hlBuilder.registerFix( + QuickFixFactory.getInstance() + .createModifierFixBuilder(packageLocalClassInTheMiddle) + .add(PsiModifier.PUBLIC) + .showContainingClass() + .create() + ); + return; + } + + try { + Project project = refElement.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); + modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); + String minModifier = PsiModifier.PACKAGE_LOCAL; + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + minModifier = PsiModifier.PROTECTED; + } + if (refElement.isProtected()) { + minModifier = PsiModifier.PUBLIC; + } + PsiClass containingClass = refElement.getContainingClass(); + if (containingClass != null && containingClass.isInterface()) { + minModifier = PsiModifier.PUBLIC; + } + String[] modifiers = { + PsiModifier.PACKAGE_LOCAL, + PsiModifier.PROTECTED, + PsiModifier.PUBLIC, + }; + for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { + @PsiModifier.ModifierConstant + String modifier = modifiers[i]; + modifierListCopy.setModifierProperty(modifier, true); + if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { + TextRange fixRange = hlRange; + PsiElement ref = place.getReferenceNameElement(); + if (ref != null) { + fixRange = fixRange.union(ref.getTextRange()); + } + LocalQuickFixAndIntentionActionOnPsiElement action = + QuickFixFactory.getInstance().createModifierFixBuilder(refElement).add(modifier).showContainingClass().create(); + hlBuilder.newFix(action).optionalFixRange(fixRange).register(); + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + @Nullable + @RequiredReadAction + private static PsiClass getPackageLocalClassInTheMiddle(PsiElement place) { + if (place instanceof PsiReferenceExpression refExpr) { + // check for package-private classes in the middle + while (true) { + if (refExpr.resolve() instanceof PsiField field) { + PsiClass aClass = field.getContainingClass(); + if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) + && !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, place)) { + return aClass; + } + } + if (!(refExpr.getQualifierExpression() instanceof PsiReferenceExpression qualifierRefExpr)) { + break; + } + refExpr = qualifierRefExpr; + } + } + return null; + } + + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkInstanceOfApplicable(PsiInstanceOfExpression expression) { + PsiExpression operand = expression.getOperand(); + PsiTypeElement typeElement = expression.getCheckType(); + if (typeElement == null) { + return null; + } + PsiType checkType = typeElement.getType(); + PsiType operandType = operand.getType(); + if (operandType == null) { + return null; + } + if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) + || TypeConversionUtil.isPrimitiveAndNotNull(checkType) + || !TypeConversionUtil.areTypesConvertible(operandType, checkType)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.castInconvertible( + JavaHighlightUtil.formatType(operandType), + JavaHighlightUtil.formatType(checkType) + )); + } + return null; + } + + /** + * 15.16 Cast Expressions + * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true + * - ReferenceType must denote a class or interface type. + * - The erasures of all the listed types must be pairwise different. + * - No two listed types may be subtypes of different parametrization of the same generic interface. + */ + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkIntersectionInTypeCast( + PsiTypeCastExpression expression, + LanguageLevel languageLevel, + PsiFile file + ) { + PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement != null && isIntersection(castTypeElement, castTypeElement.getType())) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.INTERSECTION_CASTS, languageLevel, file); + if (hlBuilder != null) { + return hlBuilder; + } + + PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); + if (conjuncts != null) { + Set erasures = new HashSet<>(conjuncts.length); + erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); + List conjList = new ArrayList<>(Arrays.asList(conjuncts)); + for (int i = 1; i < conjuncts.length; i++) { + PsiTypeElement conjunct = conjuncts[i]; + PsiType conjType = conjunct.getType(); + if (conjType instanceof PsiClassType classType) { + PsiClass aClass = classType.resolve(); + if (aClass != null && !aClass.isInterface()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(conjunct) + .descriptionAndTooltip(JavaErrorLocalize.interfaceExpected()) + .registerFix(new FlipIntersectionSidesFix(aClass.getName(), conjList, conjunct, castTypeElement)); + } + } + else { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(conjunct) + .descriptionAndTooltip(JavaCompilationErrorLocalize.castIntersectionUnexpectedType()); + } + if (!erasures.add(TypeConversionUtil.erasure(conjType))) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(conjunct) + .descriptionAndTooltip(JavaCompilationErrorLocalize.castIntersectionRepeatedInterface()) + .registerFix(new DeleteRepeatedInterfaceFix(conjunct, conjList)); + } + } + + List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); + SimpleReference differentArgumentsMessage = SimpleReference.create(); + PsiClass sameGenericParameterization = InferenceSession.findParameterizationOfTheSameGenericClass( + typeList, + pair -> { + if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { + return true; + } + differentArgumentsMessage.set(pair.first.getPresentableText() + " and " + pair.second.getPresentableText()); + return false; + } + ); + if (sameGenericParameterization != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.classCannotBeInheritedWithDifferentArguments( + formatClass(sameGenericParameterization), + differentArgumentsMessage.get() + )); + } + } + } + + return null; + } + + @RequiredReadAction + private static boolean isIntersection(PsiTypeElement castTypeElement, PsiType castType) { + //noinspection SimplifiableIfStatement + if (castType instanceof PsiIntersectionType) { + return true; + } + return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkInconvertibleTypeCast(PsiTypeCastExpression expression) { + PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement == null) { + return null; + } + PsiType castType = castTypeElement.getType(); + + PsiExpression operand = expression.getOperand(); + if (operand == null) { + return null; + } + PsiType operandType = operand.getType(); + + if (operandType != null + && !TypeConversionUtil.areTypesConvertible(operandType, castType, PsiUtil.getLanguageLevel(expression)) + && !RedundantCastUtil.isInPolymorphicCall(expression)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.castInconvertible( + JavaHighlightUtil.formatType(operandType), + JavaHighlightUtil.formatType(castType) + )) + .create(); + } + + + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkVariableExpected(PsiExpression expression) { + PsiExpression lValue; + if (expression instanceof PsiAssignmentExpression assignment) { + lValue = assignment.getLExpression(); + } + else if (PsiUtil.isIncrementDecrementOperation(expression)) { + lValue = expression instanceof PsiPostfixExpression postfixExpr + ? postfixExpr.getOperand() + : ((PsiPrefixExpression) expression).getOperand(); + } + else { + lValue = null; + } + HighlightInfo errorResult = null; + if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { + errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(lValue) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lvalueVariableExpected()) + .create(); + } + + return errorResult; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkAssignmentOperatorApplicable(PsiAssignmentExpression assignment) { + PsiJavaToken operationSign = assignment.getOperationSign(); + IElementType eqOpSign = operationSign.getTokenType(); + IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); + if (opSign == null) { + return null; + } + PsiType lType = assignment.getLExpression().getType(); + PsiExpression rExpression = assignment.getRExpression(); + if (rExpression == null) { + return null; + } + PsiType rType = rExpression.getType(); + if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, lType, rType, true)) { + String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); + LocalizeValue message = JavaCompilationErrorLocalize.binaryOperatorNotApplicable( + operatorText, + JavaHighlightUtil.formatType(lType), + JavaHighlightUtil.formatType(rType) + ); + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(assignment) + .descriptionAndTooltip(message) + .create(); + } + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAssignmentCompatibleTypes(PsiAssignmentExpression assignment) { + PsiExpression lExpr = assignment.getLExpression(); + PsiExpression rExpr = assignment.getRExpression(); + if (rExpr == null) { + return null; + } + PsiType lType = lExpr.getType(); + PsiType rType = rExpr.getType(); + if (rType == null) { + return null; + } + + IElementType sign = assignment.getOperationTokenType(); + HighlightInfo.Builder hlBuilder; + if (JavaTokenType.EQ.equals(sign)) { + hlBuilder = checkAssignability(lType, rType, rExpr, assignment); + } + else { + // 15.26.2. Compound Assignment Operators + IElementType opSign = TypeConversionUtil.convertEQtoOperation(sign); + PsiType type = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, opSign, true); + if (type == null || lType == null || TypeConversionUtil.areTypesConvertible(type, lType)) { + return null; + } + hlBuilder = createIncompatibleTypeHighlightInfo(lType, type, assignment.getTextRange(), 0); + hlBuilder.registerFix(QuickFixFactory.getInstance().createChangeToAppendFix(sign, lType, assignment)); + } + if (hlBuilder == null) { + return null; + } + registerChangeVariableTypeFixes(lExpr, rType, rExpr, hlBuilder); + if (lType != null) { + registerChangeVariableTypeFixes(rExpr, lType, lExpr, hlBuilder); + } + return hlBuilder; + } + + @RequiredReadAction + private static void registerChangeVariableTypeFixes( + PsiExpression expression, + PsiType type, + @Nullable PsiExpression lExpr, + HighlightInfo.@Nullable Builder highlightInfo + ) { + if (highlightInfo == null || !(expression instanceof PsiReferenceExpression refExpr) + || !(refExpr.resolve() instanceof PsiVariable variable)) { + return; + } + + registerChangeVariableTypeFixes(variable, type, lExpr, highlightInfo); + + if (lExpr instanceof PsiMethodCallExpression methodCall + && methodCall.getParent() instanceof PsiAssignmentExpression assignment + && assignment.getParent() instanceof PsiStatement) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null && PsiType.VOID.equals(method.getReturnType())) { + highlightInfo.registerFix(new ReplaceAssignmentFromVoidWithStatementIntentionAction(assignment, lExpr)); + } + } + } + + private static boolean isCastIntentionApplicable(PsiExpression expression, @Nullable PsiType toType) { + while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { + if (expression instanceof PsiTypeCastExpression typeCast) { + expression = typeCast.getOperand(); + } + if (expression instanceof PsiParenthesizedExpression parenthesized) { + expression = parenthesized.getExpression(); + } + } + if (expression == null) { + return false; + } + PsiType rType = expression.getType(); + return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); + } + + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkVariableInitializerType(PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + // array initializer checked in checkArrayInitializerApplicable + if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { + return null; + } + PsiType lType = variable.getType(); + PsiType rType = initializer.getType(); + PsiTypeElement typeElement = variable.getTypeElement(); + int start = typeElement != null ? typeElement.getTextRange().getStartOffset() : variable.getTextRange().getStartOffset(); + int end = variable.getTextRange().getEndOffset(); + HighlightInfo.Builder hlBuilder = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0); + if (hlBuilder != null) { + registerChangeVariableTypeFixes(variable, rType, variable.getInitializer(), hlBuilder); + registerChangeVariableTypeFixes(initializer, lType, null, hlBuilder); + } + return hlBuilder; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAssignability( + @Nullable PsiType lType, + @Nullable PsiType rType, + @Nullable PsiExpression expression, + PsiElement elementToHighlight + ) { + TextRange textRange = elementToHighlight.getTextRange(); + return checkAssignability(lType, rType, expression, textRange, 0); + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkAssignability( + @Nullable PsiType lType, + @Nullable PsiType rType, + @Nullable PsiExpression expression, + TextRange textRange, + int navigationShift + ) { + if (lType == rType) { + return null; + } + if (expression == null) { + if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) { + return null; + } + } + else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { + return null; + } + if (rType == null) { + rType = expression.getType(); + } + HighlightInfo.Builder hlBuilder = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { + hlBuilder.registerFix(factory.createAddTypeCastFix(lType, expression)); + } + if (expression != null) { + hlBuilder.registerFix(factory.createWrapWithAdapterFix(lType, expression)) + .registerFix(factory.createWrapWithOptionalFix(lType, expression)) + .registerFix(factory.createWrapExpressionFix(lType, expression)) + .registerFix(factory.createWrapStringWithFileFix(lType, expression)); + AddTypeArgumentsConditionalFix.register(hlBuilder, expression, lType); + registerCollectionToArrayFixAction(hlBuilder, rType, lType, expression); + } + ChangeNewOperatorTypeFix.register(hlBuilder, expression, lType); + return hlBuilder; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkReturnStatementType(PsiReturnStatement statement) { + PsiMethod method = null; + PsiLambdaExpression lambda = null; + PsiElement parent = statement.getParent(); + while (true) { + if (parent instanceof PsiFile) { + break; + } + if (parent instanceof PsiClassInitializer) { + break; + } + if (parent instanceof PsiLambdaExpression parentLambda) { + lambda = parentLambda; + break; + } + if (parent instanceof PsiMethod parentMethod) { + method = parentMethod; + break; + } + parent = parent.getParent(); + } + if (parent instanceof PsiCodeFragment) { + return null; + } + if (method == null && lambda != null) { + //todo check return statements type inside lambda + } + else if (method == null && !(parent instanceof ServerPageFile)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.returnOutsideMethod()); + } + else { + PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; + boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); + PsiExpression returnValue = statement.getReturnValue(); + if (returnValue != null) { + PsiType valueType = RefactoringChangeUtil.getTypeByExpression(returnValue); + if (isMethodVoid) { + HighlightInfo.Builder errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.returnFromVoidMethod()); + if (valueType != null) { + errorResult.registerFix(QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)); + } + return errorResult; + } + else { + TextRange textRange = statement.getTextRange(); + HighlightInfo.Builder hlBuilder = + checkAssignability(returnType, valueType, returnValue, textRange, returnValue.getStartOffsetInParent()); + if (hlBuilder != null && valueType != null) { + if (!PsiType.VOID.equals(valueType)) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)); + } + registerChangeParameterClassFix(returnType, valueType, hlBuilder); + if (returnType instanceof PsiArrayType arrayType) { + PsiType erasedValueType = TypeConversionUtil.erasure(valueType); + if (erasedValueType != null + && TypeConversionUtil.isAssignable(arrayType.getComponentType(), erasedValueType)) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createSurroundWithArrayFix(null, returnValue)); + } + } + registerCollectionToArrayFixAction(hlBuilder, valueType, returnType, returnValue); + return hlBuilder; + } + } + } + else if (!isMethodVoid) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.returnValueMissing()) + .navigationShift(PsiKeyword.RETURN.length()) + .registerFix(QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.VOID, true)); + } + } + return null; + } + + private static void registerCollectionToArrayFixAction( + HighlightInfo.@Nullable Builder hlBuilder, + @Nullable PsiType fromType, + @Nullable PsiType toType, + PsiExpression expression + ) { + if (hlBuilder != null && toType instanceof PsiArrayType arrayType) { + PsiType arrayComponentType = arrayType.getComponentType(); + if (!(arrayComponentType instanceof PsiPrimitiveType) + && !(PsiUtil.resolveClassInType(arrayComponentType) instanceof PsiTypeParameter) + && InheritanceUtil.isInheritor(fromType, CommonClassNames.JAVA_UTIL_COLLECTION)) { + PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(fromType, expression.getResolveScope()); + if (collectionItemType != null && arrayComponentType.isAssignableFrom(collectionItemType)) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createCollectionToArrayFix(expression, arrayType)); + } + } + } + } + + public static LocalizeValue getUnhandledExceptionsDescriptor(Collection unhandled) { + return getUnhandledExceptionsDescriptor(unhandled, null); + } + + private static LocalizeValue getUnhandledExceptionsDescriptor(Collection unhandled, @Nullable String source) { + String exceptions = formatTypes(unhandled); + return source != null + ? JavaCompilationErrorLocalize.exceptionUnhandledClose(exceptions, unhandled.size()) + : JavaCompilationErrorLocalize.exceptionUnhandled(exceptions, unhandled.size()); + } + + private static String formatTypes(Collection unhandled) { + return StringUtil.join(unhandled, JavaHighlightUtil::formatType, ", "); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkVariableAlreadyDefined(PsiVariable variable) { + PsiVariable oldVariable = JavaPsiVariableUtil.findPreviousVariableDeclaration(variable); + if (oldVariable != null) { + PsiIdentifier identifier = variable.getNameIdentifier(); + assert identifier != null : variable; + HighlightInfo.Builder highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.variableAlreadyDefined(variable.getName())); + if (variable instanceof PsiLocalVariable localVar) { + highlightInfo.registerFix(QuickFixFactory.getInstance().createReuseVariableDeclarationFix(localVar)); + } + return highlightInfo.create(); + } + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkUnderscore(PsiIdentifier identifier, LanguageLevel languageLevel) { + if ("_".equals(identifier.getText())) { + PsiElement parent = identifier.getParent(); + + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9) && !(parent instanceof PsiUnnamedPattern) && + !(parent instanceof PsiVariable var && var.isUnnamed())) { + if (!JavaFeature.UNNAMED_PATTERNS_AND_VARIABLES.isSufficient(languageLevel)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.underscoreIdentifier()) + .create(); + } + } + else if (JavaFeature.LAMBDA_EXPRESSIONS.isSufficient(languageLevel)) { + if (parent instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiLambdaExpression && + !parameter.isUnnamed()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.underscoreIdentifierLambda()) + .create(); + } + } + } + + return null; + } + + public static String formatClass(PsiClass aClass) { + return formatClass(aClass, true); + } + + public static String formatClass(PsiClass aClass, boolean fqn) { + return PsiFormatUtil.formatClass( + aClass, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0) + ); + } + + private static String formatField(PsiField field) { + return PsiFormatUtil.formatVariable( + field, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + PsiSubstitutor.EMPTY + ); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkUnhandledExceptions(PsiElement element, @Nullable TextRange textRange) { + List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); + if (unhandledExceptions.isEmpty()) { + return null; + } + + HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); + if (highlightType == null) { + return null; + } + + if (textRange == null) { + textRange = element.getTextRange(); + } + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(highlightType) + .range(textRange) + .descriptionAndTooltip(getUnhandledExceptionsDescriptor(unhandledExceptions)); + return registerUnhandledExceptionFixes(element, hlBuilder, unhandledExceptions); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkUnhandledCloserExceptions(PsiResourceListElement resource) { + List unhandled = ExceptionUtil.getUnhandledCloserExceptions(resource, null); + if (unhandled.isEmpty()) { + return null; + } + + HighlightInfoType highlightType = getUnhandledExceptionHighlightType(resource); + if (highlightType == null) { + return null; + } + + HighlightInfo.Builder highlight = HighlightInfo.newHighlightInfo(highlightType) + .range(resource) + .descriptionAndTooltip(getUnhandledExceptionsDescriptor(unhandled, "auto-closeable resource")); + return registerUnhandledExceptionFixes(resource, highlight, unhandled).create(); + } + + private static HighlightInfo.Builder registerUnhandledExceptionFixes( + PsiElement element, + HighlightInfo.Builder hlBuilder, + List unhandled + ) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + hlBuilder.registerFix(factory.createAddExceptionToCatchFix()) + .registerFix(factory.createAddExceptionToThrowsFix(element)) + .registerFix(factory.createAddExceptionFromFieldInitializerToConstructorThrowsFix(element)) + .registerFix(factory.createSurroundWithTryCatchFix(element)); + if (unhandled.size() == 1) { + hlBuilder.registerFix(factory.createGeneralizeCatchFix(element, unhandled.get(0))); + } + return hlBuilder; + } + + @Nullable + private static HighlightInfoType getUnhandledExceptionHighlightType(PsiElement element) { + // JSP top level errors are handled by UnhandledExceptionInJSP inspection + if (FileTypeUtils.isInServerPageFile(element)) { + PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiLambdaExpression.class); + if (targetMethod instanceof SyntheticElement) { + return null; + } + } + + return HighlightInfoType.UNHANDLED_EXCEPTION; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkBreakOutsideLoop(PsiBreakStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.breakOutsideSwitchOrLoop()) + .create(); + } + } + else { + // todo labeled + } + return null; + } + + + @Nullable + @RequiredReadAction + public static HighlightInfo checkContinueOutsideLoop(PsiContinueStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.continueOutsideLoop()) + .create(); + } + } + else { + PsiStatement exitedStatement = statement.findContinuedStatement(); + if (exitedStatement == null) { + return null; + } + if (!(exitedStatement instanceof PsiForStatement) + && !(exitedStatement instanceof PsiWhileStatement) + && !(exitedStatement instanceof PsiDoWhileStatement) + && !(exitedStatement instanceof PsiForeachStatement)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaErrorLocalize.notLoopLabel(statement.getLabelIdentifier().getText())) + .create(); + } + } + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkIllegalModifierCombination(PsiKeyword keyword, PsiModifierList modifierList) { + @PsiModifier.ModifierConstant String modifier = keyword.getText(); + String incompatible = getIncompatibleModifier(modifier, modifierList); + if (incompatible != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(keyword) + .descriptionAndTooltip(JavaCompilationErrorLocalize.modifierIncompatible(modifier, incompatible)) + .registerFix(QuickFixFactory.getInstance().createModifierFixBuilder(modifierList).remove(modifier).create()) + .create(); + } + + return null; + } + + @Contract("null -> null") + @RequiredReadAction + private static Map> getIncompatibleModifierMap(@Nullable PsiElement modifierListOwner) { + if (modifierListOwner == null || PsiUtilCore.hasErrorElementChild(modifierListOwner)) { + return null; + } + if (modifierListOwner instanceof PsiClass psiClass) { + return psiClass.isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiMethod) { + return ourMethodIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiVariable) { + return ourFieldIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiClassInitializer) { + return ourClassInitializerIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiJavaModule) { + return ourModuleIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiRequiresStatement) { + return ourRequiresIncompatibleModifiers; + } + return null; + } + + @Nullable + @RequiredReadAction + public static String getIncompatibleModifier(String modifier, PsiModifierList modifierList) { + Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList.getParent()); + return incompatibleModifierMap != null ? getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap) : null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkNotAllowedModifier(PsiKeyword keyword, PsiModifierList modifierList) { + PsiElement modifierOwner = modifierList.getParent(); + Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierOwner); + if (incompatibleModifierMap == null) { + return null; + } + + @PsiModifier.ModifierConstant String modifier = keyword.getText(); + Set incompatibles = incompatibleModifierMap.get(modifier); + PsiElement modifierOwnerParent = + modifierOwner instanceof PsiMember member ? member.getContainingClass() : modifierOwner.getParent(); + if (modifierOwnerParent == null) { + modifierOwnerParent = modifierOwner.getParent(); + } + boolean isAllowed = true; + if (modifierOwner instanceof PsiClass aClass) { + if (aClass.isInterface()) { + if (PsiModifier.STATIC.equals(modifier) + || PsiModifier.PRIVATE.equals(modifier) + || PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.PACKAGE_LOCAL.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass; + } + } + else { + if (PsiModifier.PUBLIC.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiJavaFile + || modifierOwnerParent instanceof PsiClass psiClass + && (psiClass instanceof PsiSyntheticClass || psiClass.getQualifiedName() != null); + } + else if (PsiModifier.STATIC.equals(modifier) + || PsiModifier.PRIVATE.equals(modifier) + || PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.PACKAGE_LOCAL.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass psiClass && psiClass.getQualifiedName() != null + || FileTypeUtils.isInServerPageFile(modifierOwnerParent); + } + + if (aClass.isEnum()) { + isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); + } + + if (aClass.getContainingClass() instanceof PsiAnonymousClass) { + isAllowed &= !(PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier)); + } + } + } + else if (modifierOwner instanceof PsiMethod method) { + isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); + PsiClass containingClass = method.getContainingClass(); + if ((method.isPublic() || method.isProtected()) && method.isConstructor() + && containingClass != null && containingClass.isEnum()) { + isAllowed = false; + } + + if (PsiModifier.PRIVATE.equals(modifier)) { + isAllowed &= modifierOwnerParent instanceof PsiClass modifierClass && (!modifierClass.isInterface() + || PsiUtil.isLanguageLevel9OrHigher(method) && !modifierClass.isAnnotationType()); + } + else if (PsiModifier.STRICTFP.equals(modifier)) { + isAllowed &= modifierOwnerParent instanceof PsiClass modifierClass + && (!modifierClass.isInterface() || PsiUtil.isLanguageLevel8OrHigher(method)); + } + else if (PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.TRANSIENT.equals(modifier) + || PsiModifier.SYNCHRONIZED.equals(modifier)) { + isAllowed &= modifierOwnerParent instanceof PsiClass modifierClass && !modifierClass.isInterface(); + } + + if (containingClass != null && containingClass.isInterface()) { + isAllowed &= !PsiModifier.NATIVE.equals(modifier); + } + + if (containingClass != null && containingClass.isAnnotationType()) { + isAllowed &= !PsiModifier.STATIC.equals(modifier); + isAllowed &= !PsiModifier.DEFAULT.equals(modifier); + } + } + else if (modifierOwner instanceof PsiField) { + if (PsiModifier.PRIVATE.equals(modifier) + || PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.TRANSIENT.equals(modifier) + || PsiModifier.STRICTFP.equals(modifier) + || PsiModifier.SYNCHRONIZED.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass modifierClass && !modifierClass.isInterface(); + } + } + else if (modifierOwner instanceof PsiClassInitializer) { + isAllowed = PsiModifier.STATIC.equals(modifier); + } + else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { + isAllowed = PsiModifier.FINAL.equals(modifier); + } + else if (modifierOwner instanceof PsiReceiverParameter) { + isAllowed = false; + } + + isAllowed &= incompatibles != null; + if (!isAllowed) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(keyword) + .descriptionAndTooltip(JavaCompilationErrorLocalize.modifierNotAllowed(modifier)) + .registerFix(QuickFixFactory.getInstance().createModifierFixBuilder(modifierList).remove(modifier).create()) + .create(); + } + + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkLiteralExpressionParsingError( + PsiLiteralExpression expression, + LanguageLevel level, + PsiFile file + ) { + PsiElement literal = expression.getFirstChild(); + assert literal instanceof PsiJavaToken : literal; + IElementType type = ((PsiJavaToken) literal).getTokenType(); + if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD || type == JavaTokenType.NULL_KEYWORD) { + return null; + } + + boolean isInt = ElementType.INTEGER_LITERALS.contains(type); + boolean isFP = ElementType.REAL_LITERALS.contains(type); + String text = isInt || isFP ? StringUtil.toLowerCase(literal.getText()) : literal.getText(); + Object value = expression.getValue(); + + if (file != null) { + if (isFP) { + if (text.startsWith(PsiLiteralUtil.HEX_PREFIX)) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.HEX_FP_LITERALS, level, file); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + if (isInt) { + if (text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.BIN_LITERALS, level, file); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + if ((isInt || isFP) && text.contains("_")) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.UNDERSCORES, level, file); + if (hlBuilder == null) { + hlBuilder = checkUnderscores(expression, text, isInt); + } + if (hlBuilder != null) { + return hlBuilder; + } + } + } + + if (type == JavaTokenType.INTEGER_LITERAL) { + String cleanText = StringUtil.replace(text, "_", ""); + //literal 2147483648 may appear only as the operand of the unary negation operator -. + if (!(cleanText.equals(PsiLiteralUtil._2_IN_31) + && expression.getParent() instanceof PsiPrefixExpression prefixExpr + && prefixExpr.getOperationTokenType() == JavaTokenType.MINUS)) { + if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalHexadecimalNoDigits()); + } + if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalBinaryNoDigits()); + } + if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_31)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalIntegerTooLarge()); + } + } + } + else if (type == JavaTokenType.LONG_LITERAL) { + String cleanText = StringUtil.replace(StringUtil.trimEnd(text, 'l'), "_", ""); + //literal 9223372036854775808L may appear only as the operand of the unary negation operator -. + if (!(cleanText.equals(PsiLiteralUtil._2_IN_63) + && expression.getParent() instanceof PsiPrefixExpression prefixExpr + && prefixExpr.getOperationTokenType() == JavaTokenType.MINUS)) { + if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalHexadecimalNoDigits()); + } + if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalBinaryNoDigits()); + } + if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_63)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalLongTooLarge()); + } + } + } + else if (isFP) { + if (value == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalFloatingMalformed()); + } + } + else if (type == JavaTokenType.CHARACTER_LITERAL) { + if (value == null) { + if (!StringUtil.startsWithChar(text, '\'')) { + return null; + } + if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalCharacterUnclosed()); + } + text = text.substring(1, text.length() - 1); + + CharSequence chars = CodeInsightUtilCore.parseStringCharacters(text, null); + if (chars == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalCharacterIllegalEscape()); + } + int length = chars.length(); + if (length > 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalCharacterTooLong()) + .registerFix(QuickFixFactory.getInstance().createConvertToStringLiteralAction()); + } + else if (length == 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalCharacterEmpty()); + } + } + } + else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) { + if (type == JavaTokenType.STRING_LITERAL) { + if (value == null) { + for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) { + if (element instanceof OuterLanguageElement) { + return null; + } + } + + if (!StringUtil.startsWithChar(text, '\"')) { + return null; + } + if (StringUtil.endsWithChar(text, '\"')) { + if (text.length() == 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.illegalLineEndInStringLiteral()); + } + text = text.substring(1, text.length() - 1); + } + else { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.illegalLineEndInStringLiteral()); + } + + if (CodeInsightUtilCore.parseStringCharacters(text, null) == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalStringIllegalEscape()); + } + } + } + else if (value == null) { + if (!text.endsWith("\"\"\"")) { + int p = expression.getTextRange().getEndOffset(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(p, p) + .endOfLine() + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalTextBlockUnclosed()); + } + else { + StringBuilder chars = new StringBuilder(text.length()); + int[] offsets = new int[text.length() + 1]; + boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets); + if (!success) { + TextRange textRange = chars.length() < text.length() - 1 + ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1]) + : expression.getTextRange(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression, textRange) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalStringIllegalEscape()); + } + else { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalTextBlockNoNewLine()); + } + } + } + else { + if (file != null && containsUnescaped(text, "\\\n")) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + if (file != null && containsUnescaped(text, "\\s")) { + HighlightInfo.Builder hlBuilder = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); + if (hlBuilder != null) { + return hlBuilder; + } + } + } + + if (value instanceof Float number) { + if (number.isInfinite()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalFloatingTooLarge()); + } + if (number == 0 && !TypeConversionUtil.isFPZero(text)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalFloatingTooSmall()); + } + } + else if (value instanceof Double number) { + if (number.isInfinite()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalFloatingTooLarge()); + } + if (number == 0 && !TypeConversionUtil.isFPZero(text)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalFloatingTooSmall()); + } + } + + return null; + } + + private static final Pattern FP_LITERAL_PARTS = Pattern.compile( + "(?:" + + "(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" + + "|" + + "(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))" + + ")" + + "[fd]?" + ); + + private static boolean containsUnescaped(String text, String subText) { + int start = 0; + while ((start = StringUtil.indexOf(text, subText, start)) != -1) { + int nSlashes = 0; + for (int pos = start - 1; pos >= 0; pos--) { + if (text.charAt(pos) != '\\') { + break; + } + nSlashes++; + } + if (nSlashes % 2 == 0) { + return true; + } + start += subText.length(); + } + return false; + } + + @RequiredReadAction + private static HighlightInfo.Builder checkUnderscores(PsiElement expression, String text, boolean isInt) { + String[] parts = ArrayUtil.EMPTY_STRING_ARRAY; + + if (isInt) { + int start = 0; + if (text.startsWith(PsiLiteralUtil.HEX_PREFIX) || text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { + start += 2; + } + int end = text.length(); + if (StringUtil.endsWithChar(text, 'l')) { + --end; + } + parts = new String[]{text.substring(start, end)}; + } + else { + Matcher matcher = FP_LITERAL_PARTS.matcher(text); + if (matcher.matches()) { + parts = new String[matcher.groupCount()]; + for (int i = 0; i < matcher.groupCount(); i++) { + parts[i] = matcher.group(i + 1); + } + } + } + + for (String part : parts) { + if (part != null && (StringUtil.startsWithChar(part, '_') || StringUtil.endsWithChar(part, '_'))) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.literalIllegalUnderscore()); + } + } + + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMustBeBoolean(PsiExpression expr, PsiType type) { + PsiElement parent = expr.getParent(); + if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement + || parent instanceof PsiForStatement forStmt && expr.equals(forStmt.getCondition()) + || parent instanceof PsiDoWhileStatement doWhileStmt && expr.equals(doWhileStmt.getCondition())) { + if (expr.getNextSibling() instanceof PsiErrorElement) { + return null; + } + + if (!TypeConversionUtil.isBooleanType(type)) { + HighlightInfo.Builder hlBuilder = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange(), 0); + if (expr instanceof PsiMethodCallExpression methodCall) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null && PsiType.VOID.equals(method.getReturnType())) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.BOOLEAN, true)); + } + } + else if (expr instanceof PsiAssignmentExpression assignment && assignment.getOperationTokenType() == JavaTokenType.EQ) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createAssignmentToComparisonFix(assignment)); + } + return hlBuilder; + } + } + return null; + } + + @RequiredReadAction + public static Set collectUnhandledExceptions(PsiTryStatement statement) { + Set thrownTypes = new HashSet<>(); + + PsiCodeBlock tryBlock = statement.getTryBlock(); + if (tryBlock != null) { + thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock)); + } + + PsiResourceList resources = statement.getResourceList(); + if (resources != null) { + thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(resources, resources)); + } + + return thrownTypes; + } + + @RequiredReadAction + public static List checkExceptionThrownInTry(PsiParameter parameter, Set thrownTypes) { + if (!(parameter.getDeclarationScope() instanceof PsiCatchSection)) { + return Collections.emptyList(); + } + + PsiType caughtType = parameter.getType(); + if (caughtType instanceof PsiClassType classType) { + HighlightInfo info = checkSimpleCatchParameter(parameter, thrownTypes, classType); + return info == null ? Collections.emptyList() : Collections.singletonList(info); + } + if (caughtType instanceof PsiDisjunctionType) { + return checkMultiCatchParameter(parameter, thrownTypes); + } + + return Collections.emptyList(); + } + + @Nullable + @RequiredReadAction + private static HighlightInfo checkSimpleCatchParameter( + PsiParameter parameter, + Collection thrownTypes, + PsiClassType caughtType + ) { + if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtType)) { + return null; + } + + for (PsiClassType exceptionType : thrownTypes) { + if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) { + return null; + } + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parameter) + .descriptionAndTooltip(JavaCompilationErrorLocalize.exceptionNeverThrownTry(JavaHighlightUtil.formatType(caughtType))) + .registerFix(QuickFixFactory.getInstance().createDeleteCatchFix(parameter)) + .create(); + } + + @RequiredReadAction + private static List checkMultiCatchParameter( + PsiParameter parameter, + Collection thrownTypes + ) { + List typeElements = PsiUtil.getParameterTypeElements(parameter); + List highlights = new ArrayList<>(typeElements.size()); + + for (PsiTypeElement typeElement : typeElements) { + PsiType catchType = typeElement.getType(); + if (catchType instanceof PsiClassType classType && ExceptionUtil.isUncheckedExceptionOrSuperclass(classType)) { + continue; + } + + boolean used = false; + for (PsiClassType exceptionType : thrownTypes) { + if (exceptionType.isAssignableFrom(catchType) || catchType.isAssignableFrom(exceptionType)) { + used = true; + break; + } + } + if (!used) { + HighlightInfo highlight = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.exceptionNeverThrownTry(JavaHighlightUtil.formatType(catchType))) + .registerFix(QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement)) + .create(); + highlights.add(highlight); + } + } + + return highlights; + } + + @RequiredReadAction + public static Collection checkWithImprovedCatchAnalysis( + PsiParameter parameter, + Collection thrownInTryStatement, + PsiFile containingFile + ) { + PsiElement scope = parameter.getDeclarationScope(); + if (!(scope instanceof PsiCatchSection catchSection)) { + return Collections.emptyList(); + } + + PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); + int idx = ArrayUtil.find(allCatchSections, catchSection); + if (idx <= 0) { + return Collections.emptyList(); + } + + Collection thrownTypes = new HashSet<>(thrownInTryStatement); + PsiManager manager = containingFile.getManager(); + GlobalSearchScope parameterResolveScope = parameter.getResolveScope(); + thrownTypes.add(PsiType.getJavaLangError(manager, parameterResolveScope)); + thrownTypes.add(PsiType.getJavaLangRuntimeException(manager, parameterResolveScope)); + Collection result = new ArrayList<>(); + + List parameterTypeElements = PsiUtil.getParameterTypeElements(parameter); + boolean isMultiCatch = parameterTypeElements.size() > 1; + for (PsiTypeElement catchTypeElement : parameterTypeElements) { + PsiType catchType = catchTypeElement.getType(); + if (ExceptionUtil.isGeneralExceptionType(catchType)) { + continue; + } + + // collect exceptions which are caught by this type + Collection caught = ContainerUtil.findAll(thrownTypes, catchType::isAssignableFrom); + if (caught.isEmpty()) { + continue; + } + Collection caughtCopy = new HashSet<>(caught); + + // exclude all which are caught by previous catch sections + for (int i = 0; i < idx; i++) { + PsiParameter prevCatchParameter = allCatchSections[i].getParameter(); + if (prevCatchParameter == null) { + continue; + } + for (PsiTypeElement prevCatchTypeElement : PsiUtil.getParameterTypeElements(prevCatchParameter)) { + PsiType prevCatchType = prevCatchTypeElement.getType(); + caught.removeIf(prevCatchType::isAssignableFrom); + if (caught.isEmpty()) { + break; + } + } + } + + // check & warn + if (caught.isEmpty()) { + HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(catchSection) + .descriptionAndTooltip(JavaErrorLocalize.exceptionAlreadyCaughtWarn(formatTypes(caughtCopy), caughtCopy.size())) + .registerFix( + isMultiCatch + ? QuickFixFactory.getInstance().createDeleteMultiCatchFix(catchTypeElement) + : QuickFixFactory.getInstance().createDeleteCatchFix(parameter) + ) + .create(); + result.add(highlightInfo); + } + } + + return result; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkNotAStatement(PsiStatement statement) { + if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) { + boolean isDeclarationNotAllowed = false; + if (statement instanceof PsiDeclarationStatement) { + PsiElement parent = statement.getParent(); + isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement; + } + + LocalizeValue description = isDeclarationNotAllowed + ? JavaCompilationErrorLocalize.statementDeclarationNotAllowed() + : JavaCompilationErrorLocalize.statementBadExpression(); + HighlightInfo.Builder error = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(description); + if (statement instanceof PsiExpressionStatement expressionStmt) { + error.registerFix(QuickFixFactory.getInstance().createDeleteSideEffectAwareFix(expressionStmt)); + } + return error.create(); + } + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSwitchBlockStatements( + PsiSwitchBlock switchBlock, + LanguageLevel languageLevel, + PsiFile file + ) { + PsiCodeBlock body = switchBlock.getBody(); + if (body != null) { + PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); + if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(first) + .descriptionAndTooltip(JavaErrorLocalize.statementMustBePrependedWithCaseLabel()); + } + + PsiElement element = first; + PsiStatement alien = null; + boolean classicLabels = false; + boolean enhancedLabels = false; + boolean levelChecked = false; + while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { + if (element instanceof PsiSwitchLabeledRuleStatement) { + if (!levelChecked) { + HighlightInfo.Builder hlBuilder = checkFeature(element, JavaFeature.ENHANCED_SWITCH, languageLevel, file); + if (hlBuilder != null) { + return hlBuilder; + } + levelChecked = true; + } + if (classicLabels) { + alien = (PsiStatement) element; + break; + } + enhancedLabels = true; + } + else if (element instanceof PsiStatement statement) { + if (enhancedLabels) { + alien = statement; + break; + } + classicLabels = true; + } + + if (!levelChecked && element instanceof PsiSwitchLabelStatementBase switchLabelStatementBase) { + PsiExpressionList values = switchLabelStatementBase.getCaseValues(); + if (values != null && values.getExpressionCount() > 1) { + HighlightInfo.Builder hlBuilder = checkFeature(values, JavaFeature.ENHANCED_SWITCH, languageLevel, file); + if (hlBuilder != null) { + return hlBuilder; + } + levelChecked = true; + } + } + + element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); + } + if (alien != null) { + if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { + PsiSwitchLabeledRuleStatement previousRule = + PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(alien) + .descriptionAndTooltip(JavaErrorLocalize.statementMustBePrependedWithCaseLabel()); + if (previousRule != null) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); + } + return hlBuilder; + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(alien) + .descriptionAndTooltip(JavaErrorLocalize.differentCaseKindsInSwitch()); + } + } + + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkSwitchSelectorType(PsiSwitchBlock switchBlock, LanguageLevel level) { + PsiExpression expression = switchBlock.getExpression(); + if (expression == null) { + return null; + } + PsiType type = expression.getType(); + if (type == null) { + return null; + } + + SelectorKind kind = getSwitchSelectorKind(type); + if (kind == SelectorKind.INT) { + return null; + } + + LanguageLevel requiredLevel = null; + if (kind == SelectorKind.ENUM) { + requiredLevel = LanguageLevel.JDK_1_5; + } + if (kind == SelectorKind.STRING) { + requiredLevel = LanguageLevel.JDK_1_7; + } + + if (kind == null || requiredLevel != null && !level.isAtLeast(requiredLevel)) { + boolean is7 = level.isAtLeast(LanguageLevel.JDK_1_7); + LocalizeValue expected = is7 + ? JavaErrorLocalize.validSwitch1_7SelectorTypes() + : JavaErrorLocalize.validSwitchSelectorTypes(); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeIncompatible(expected, JavaHighlightUtil.formatType(type))) + .create(); + if (switchBlock instanceof PsiSwitchStatement switchStmt) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createConvertSwitchToIfIntention(switchStmt) + ); + } + if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddTypeCastFix(PsiType.INT, expression)); + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createWrapWithAdapterFix(PsiType.INT, expression) + ); + } + if (requiredLevel != null) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(requiredLevel)); + } + return info; + } + + PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); + if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { + String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.inaccessibleType(className).get()) + .create(); + } + + return null; + } + + private enum SelectorKind { + INT, + ENUM, + STRING + } + + private static SelectorKind getSwitchSelectorKind(PsiType type) { + if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) { + return SelectorKind.INT; + } + + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (psiClass != null) { + if (psiClass.isEnum()) { + return SelectorKind.ENUM; + } + if (Comparing.strEqual(psiClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING)) { + return SelectorKind.STRING; + } + } + + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkPolyadicOperatorApplicable(PsiPolyadicExpression expression) { + PsiExpression[] operands = expression.getOperands(); + + PsiType lType = operands[0].getType(); + IElementType operationSign = expression.getOperationTokenType(); + for (int i = 1; i < operands.length; i++) { + PsiExpression operand = operands[i]; + PsiType rType = operand.getType(); + if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign, lType, rType, false)) { + PsiJavaToken token = expression.getTokenBeforeOperand(operand); + LocalizeValue message = JavaCompilationErrorLocalize.binaryOperatorNotApplicable( + token.getText(), + JavaHighlightUtil.formatType(lType), + JavaHighlightUtil.formatType(rType) + ); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, operationSign, true); } - PsiExpression qualifier = expression.getQualifierExpression(); - if (!(qualifier instanceof PsiReferenceExpression)) { - break; - } - expression = (PsiReferenceExpression) qualifier; - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkInstanceOfApplicable(@Nonnull PsiInstanceOfExpression expression) { - PsiExpression operand = expression.getOperand(); - PsiTypeElement typeElement = expression.getCheckType(); - if (typeElement == null) { - return null; - } - PsiType checkType = typeElement.getType(); - PsiType operandType = operand.getType(); - if (operandType == null) { - return null; - } - if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) || TypeConversionUtil.isPrimitiveAndNotNull(checkType) || !TypeConversionUtil.areTypesConvertible(operandType, checkType)) { - String message = JavaErrorBundle.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil.formatType(checkType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - return null; - } - - - /** - * 15.16 Cast Expressions - * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true - * - ReferenceType must denote a class or interface type. - * - The erasures of all the listed types must be pairwise different. - * - No two listed types may be subtypes of different parameterization of the same generic interface. - */ - @Nullable - public static HighlightInfo checkIntersectionInTypeCast(@Nonnull PsiTypeCastExpression expression, @Nonnull LanguageLevel languageLevel, @Nonnull PsiFile file) { - PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement != null && isIntersection(castTypeElement, castTypeElement.getType())) { - HighlightInfo info = checkFeature(expression, Feature.INTERSECTION_CASTS, languageLevel, file); - if (info != null) { - return info; - } - - final PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); - if (conjuncts != null) { - final Set erasures = new HashSet<>(conjuncts.length); - erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); - final List conjList = new ArrayList<>(Arrays.asList(conjuncts)); - for (int i = 1; i < conjuncts.length; i++) { - final PsiTypeElement conjunct = conjuncts[i]; - final PsiType conjType = conjunct.getType(); - if (conjType instanceof PsiClassType) { - final PsiClass aClass = ((PsiClassType) conjType).resolve(); - if (aClass != null && !aClass.isInterface()) { - final HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(conjunct).descriptionAndTooltip(JavaErrorBundle.message("interface" + - ".expected")).create(); - QuickFixAction.registerQuickFixAction(errorResult, new FlipIntersectionSidesFix(aClass.getName(), conjList, conjunct, castTypeElement), null); - return errorResult; - } - } else { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(conjunct).descriptionAndTooltip("Unexpected type: class is expected").create(); - } - if (!erasures.add(TypeConversionUtil.erasure(conjType))) { - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(conjunct).descriptionAndTooltip("Repeated interface").create(); - QuickFixAction.registerQuickFixAction(highlightInfo, new DeleteRepeatedInterfaceFix(conjunct, conjList), null); + + return null; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkUnaryOperatorApplicable(@Nullable PsiJavaToken token, @Nullable PsiExpression expression) { + if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { + PsiType type = expression.getType(); + if (type == null) { + return null; + } + + PsiElement parentExpr = token.getParent(); + HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(parentExpr) + .descriptionAndTooltip(JavaCompilationErrorLocalize.unaryOperatorNotApplicable(token.getText(), JavaHighlightUtil.formatType(type))) + .create(); + if (parentExpr instanceof PsiPrefixExpression prefixExpr && token.getTokenType() == JavaTokenType.EXCL) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createNegationBroadScopeFix(prefixExpr) + ); + } return highlightInfo; - } } + return null; + } - final List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); - final Ref differentArgumentsMessage = new Ref<>(); - final PsiClass sameGenericParameterization = InferenceSession.findParameterizationOfTheSameGenericClass(typeList, pair -> - { - if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { + @Nullable + @RequiredReadAction + public static HighlightInfo checkThisOrSuperExpressionInIllegalContext( + PsiExpression expr, + @Nullable PsiJavaCodeReferenceElement qualifier, + LanguageLevel languageLevel + ) { + if (expr instanceof PsiSuperExpression && !(expr.getParent() instanceof PsiReferenceExpression)) { + // like in 'Object o = super;' + int o = expr.getTextRange().getEndOffset(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(o, o + 1) + .descriptionAndTooltip(JavaCompilationErrorLocalize.expressionSuperDotExpected()) + .create(); + } + + PsiClass aClass = null; + if (qualifier != null) { + PsiElement resolved = qualifier.advancedResolve(true).getElement(); + if (resolved instanceof PsiClass resolvedClass) { + aClass = resolvedClass; + } + else if (resolved != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.expressionQualifiedClassExpected()) + .create(); + } + } + else { + aClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); + if (aClass instanceof PsiAnonymousClass anonymousClass + && PsiTreeUtil.isAncestor(anonymousClass.getArgumentList(), expr, false)) { + aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); + } + } + if (aClass == null) { + return null; + } + + if (!InheritanceUtil.hasEnclosingInstanceInScope(aClass, expr, false, false)) { + if (!resolvesToImmediateSuperInterface(expr, qualifier, aClass, languageLevel)) { + return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); + } + + //15.11.2 + //The form T.super.Identifier refers to the field named Identifier of the lexically enclosing instance corresponding to T, + //but with that instance viewed as an instance of the superclass of T. + if (expr instanceof PsiSuperExpression + && expr.getParent() instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiField) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(JavaErrorLocalize.isNotAnEnclosingClass(formatClass(aClass))) + .create(); + } + } + + if (qualifier != null && aClass.isInterface() && expr instanceof PsiSuperExpression + && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + //15.12.1 for method invocation expressions; 15.13 for method references + //If TypeName denotes an interface, I, then let T be the type declaration immediately enclosing the method reference expression. + //It is a compile-time error if I is not a direct superinterface of T, + //or if there exists some other direct superclass or direct superinterface of T, J, such that J is a subtype of I. + PsiClass classT = PsiTreeUtil.getParentOfType(expr, PsiClass.class); + if (classT != null) { + PsiElement resolved = expr.getParent() instanceof PsiReferenceExpression refExpr ? refExpr.resolve() : null; + + PsiClass containingClass = + ObjectUtil.notNull(resolved instanceof PsiMethod method ? method.getContainingClass() : null, aClass); + for (PsiClass superClass : classT.getSupers()) { + if (superClass.isInheritor(containingClass, true)) { + String cause = null; + if (superClass.isInheritor(aClass, true) && superClass.isInterface()) { + cause = "redundant interface " + format(containingClass) + " is extended by "; + } + else if (resolved instanceof PsiMethod method + && MethodSignatureUtil.findMethodBySuperMethod(superClass, method, true) != method) { + cause = "method " + method.getName() + " is overridden in "; + } + + if (cause != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip( + JavaErrorLocalize.badQualifierInSuperMethodReference(cause + formatClass(superClass)) + ) + .create(); + } + } + } + + if (!classT.isInheritor(aClass, false)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.expressionSuperNoEnclosingInstance(format(aClass))) + .create(); + } + } + } + + if (expr instanceof PsiThisExpression) { + PsiMethod psiMethod = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); + if (psiMethod == null || psiMethod.getContainingClass() != aClass && !isInsideDefaultMethod(psiMethod, aClass)) { + if (aClass.isInterface()) { + return thisNotFoundInInterfaceInfo(expr); + } + + if (aClass instanceof PsiAnonymousClass anonymousClass + && PsiTreeUtil.isAncestor(anonymousClass.getArgumentList(), expr, true)) { + PsiClass parentClass = PsiTreeUtil.getParentOfType(anonymousClass, PsiClass.class, true); + if (parentClass != null && parentClass.isInterface()) { + return thisNotFoundInInterfaceInfo(expr); + } + } + } + } + return null; + } + + @RequiredReadAction + public static HighlightInfo checkUnqualifiedSuperInDefaultMethod( + LanguageLevel languageLevel, + PsiReferenceExpression expr, + PsiExpression qualifier + ) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && qualifier instanceof PsiSuperExpression superExpr) { + PsiMethod method = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); + if (method != null && method.hasModifierProperty(PsiModifier.DEFAULT) && superExpr.getQualifier() == null) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(JavaCompilationErrorLocalize.expressionSuperUnqualifiedDefaultMethod()); + QualifySuperArgumentFix.registerQuickFixAction(superExpr, hlBuilder); + return hlBuilder.create(); + } + } + return null; + } + + private static boolean isInsideDefaultMethod(PsiMethod method, PsiClass aClass) { + while (method != null && method.getContainingClass() != aClass) { + method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); + } + return method != null && method.hasModifierProperty(PsiModifier.DEFAULT); + } + + @RequiredReadAction + private static HighlightInfo thisNotFoundInInterfaceInfo(PsiExpression expr) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(LocalizeValue.localizeTODO("Cannot find symbol variable this")) + .create(); + } + + private static boolean resolvesToImmediateSuperInterface( + PsiExpression expr, + @Nullable PsiJavaCodeReferenceElement qualifier, + PsiClass aClass, + LanguageLevel languageLevel + ) { + return expr instanceof PsiSuperExpression + && qualifier != null + && languageLevel.isAtLeast(LanguageLevel.JDK_1_8) + && expr.getType() instanceof PsiClassType superClassType + && aClass.equals(superClassType.resolve()) + && PsiUtil.getEnclosingStaticElement(expr, PsiTreeUtil.getParentOfType(expr, PsiClass.class)) == null; + } + + @RequiredReadAction + public static LocalizeValue buildProblemWithStaticDescription(PsiElement refElement) { + String type = FindUsagesProvider.forLanguage(JavaLanguage.INSTANCE).getType(refElement); + LocalizeValue name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); + return JavaCompilationErrorLocalize.referenceNonStaticFromStaticContext(type, name); + } + + @RequiredReadAction + public static void registerStaticProblemQuickFixAction( + PsiElement refElement, + HighlightInfo.Builder hlBuilder, + PsiJavaCodeReferenceElement place + ) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + if (refElement instanceof PsiModifierListOwner modifierListOwner) { + hlBuilder.registerFix(factory.createModifierFixBuilder(modifierListOwner).add(PsiModifier.STATIC).create()); + } + // make context non static + PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); + if (staticParent != null && isInstanceReference(place)) { + hlBuilder.registerFix(factory.createModifierFixBuilder(staticParent).remove(PsiModifier.STATIC).create()); + } + if (place instanceof PsiReferenceExpression placeRefExpr && refElement instanceof PsiField) { + hlBuilder.registerFix(factory.createCreateFieldFromUsageFix(placeRefExpr)); + } + } + + @RequiredReadAction + private static boolean isInstanceReference(PsiJavaCodeReferenceElement place) { + PsiElement qualifier = place.getQualifier(); + if (qualifier == null) { + return true; + } + if (!(qualifier instanceof PsiJavaCodeReferenceElement codeReferenceElement)) { + return false; + } + PsiElement q = codeReferenceElement.resolve(); + if (q instanceof PsiClass) { + return false; + } + if (q != null) { return true; - } - differentArgumentsMessage.set(pair.first.getPresentableText() + " and " + pair.second.getPresentableText()); - return false; - }); - if (sameGenericParameterization != null) { - final String message = formatClass(sameGenericParameterization) + " cannot be inherited with different arguments: " + differentArgumentsMessage.get(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); } - } + String qName = codeReferenceElement.getQualifiedName(); + return qName == null || !Character.isLowerCase(qName.charAt(0)); + } + + @RequiredReadAction + public static LocalizeValue buildProblemWithAccessDescription(PsiElement reference, JavaResolveResult result) { + return buildProblemWithAccessDescription(reference, result, ObjectUtil.notNull(result.getElement())); + } + + @RequiredReadAction + private static LocalizeValue buildProblemWithAccessDescription( + PsiElement reference, + JavaResolveResult result, + PsiElement resolved + ) { + assert resolved instanceof PsiModifierListOwner : resolved; + PsiModifierListOwner refElement = (PsiModifierListOwner) resolved; + LocalizeValue symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + + if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { + LocalizeValue containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorLocalize.privateSymbol(symbolName, containerName); + } + else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + LocalizeValue containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorLocalize.protectedSymbol(symbolName, containerName); + } + else { + PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); + if (packageLocalClass != null) { + refElement = packageLocalClass; + symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + } + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { + LocalizeValue containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorLocalize.packageLocalSymbol(symbolName, containerName); + } + else { + LocalizeValue containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorLocalize.visibilityAccessProblem(symbolName, containerName); + } + } + } + + private static PsiElement getContainer(PsiModifierListOwner refElement) { + for (ContainerProvider provider : ContainerProvider.EP_NAME.getExtensions()) { + PsiElement container = provider.getContainer(refElement); + if (container != null) { + return container; + } + } + return refElement.getParent(); + } + + @RequiredReadAction + private static LocalizeValue getContainerName(PsiModifierListOwner refElement, PsiSubstitutor substitutor) { + return HighlightMessageUtil.getSymbolName(getContainer(refElement), substitutor); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkValidArrayAccessExpression(PsiArrayAccessExpression arrayAccessExpression) { + PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); + PsiType arrayExpressionType = arrayExpression.getType(); + + if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(arrayExpression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.arrayTypeExpected(JavaHighlightUtil.formatType(arrayExpressionType))) + .registerFix(QuickFixFactory.getInstance().createReplaceWithListAccessFix(arrayAccessExpression)); + } + + PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); + return indexExpression != null + ? checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression) + : null; + } + + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkCatchParameterIsThrowable(PsiParameter parameter) { + if (parameter.getDeclarationScope() instanceof PsiCatchSection) { + return checkMustBeThrowable(parameter.getType(), parameter, true); + } + return null; + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkTryResourceIsAutoCloseable(PsiResourceListElement resource) { + PsiType type = resource.getType(); + if (type == null) { + return null; + } + + PsiElementFactory factory = JavaPsiFacade.getInstance(resource.getProject()).getElementFactory(); + PsiClassType autoCloseable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope()); + if (TypeConversionUtil.isAssignable(autoCloseable, type)) { + return null; + } + + return createIncompatibleTypeHighlightInfo(autoCloseable, type, resource.getTextRange(), 0); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkResourceVariableIsFinal(PsiResourceExpression resource) { + PsiExpression expression = resource.getExpression(); + + if (expression instanceof PsiThisExpression) { + return null; + } + + if (expression instanceof PsiReferenceExpression refExpr) { + PsiElement target = refExpr.resolve(); + if (target == null) { + return null; + } + + if (target instanceof PsiVariable variable) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + + if (!(variable instanceof PsiField) + && HighlightControlFlowUtil.isEffectivelyFinal(variable, resource, refExpr)) { + return null; + } + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.resourceVariableMustBeFinal()) + .create(); + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.declarationOrVariableExpected()) + .create(); + } + + @RequiredReadAction + public static Collection checkArrayInitializer(PsiExpression initializer, PsiType type) { + if (!(initializer instanceof PsiArrayInitializerExpression arrayInitializer)) { + return Collections.emptyList(); + } + if (!(type instanceof PsiArrayType arrayType)) { + return Collections.emptyList(); + } + + PsiType componentType = arrayType.getComponentType(); + + boolean arrayTypeFixChecked = false; + VariableArrayTypeFix fix = null; + + Collection result = new ArrayList<>(); + PsiExpression[] initializers = arrayInitializer.getInitializers(); + for (PsiExpression expression : initializers) { + HighlightInfo.Builder hlBuilder = checkArrayInitializerCompatibleTypes(expression, componentType); + if (hlBuilder != null) { + if (!arrayTypeFixChecked) { + PsiType checkResult = JavaHighlightUtil.sameType(initializers); + fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; + arrayTypeFixChecked = true; + } + if (fix != null) { + hlBuilder.registerFix(new LocalQuickFixOnPsiElementAsIntentionAdapter(fix)); + } + result.add(hlBuilder.create()); + } + } + return result; + } + + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkArrayInitializerCompatibleTypes(PsiExpression initializer, PsiType componentType) { + PsiType initializerType = initializer.getType(); + if (initializerType == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(initializer) + .descriptionAndTooltip(JavaCompilationErrorLocalize.arrayIllegalInitializer(JavaHighlightUtil.formatType(componentType))); + } + PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; + return checkAssignability(componentType, initializerType, expression, initializer); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkExpressionRequired( + PsiReferenceExpression expression, + JavaResolveResult resultForIncompleteCode + ) { + if (expression.getNextSibling() instanceof PsiErrorElement) { + return null; + } + + PsiElement resolved = resultForIncompleteCode.getElement(); + if (resolved == null || resolved instanceof PsiVariable) { + return null; + } + + PsiElement parent = expression.getParent(); + // String.class or String() are both correct + if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) { + return null; + } + + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaErrorLocalize.expressionExpected()) + .create(); + if (info != null) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(expression, QuickFixActionRegistrar.create(info)); + } + return info; + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkArrayInitializerApplicable(PsiArrayInitializerExpression expression) { + /* + * JLS 10.6 Array Initializers + * An array initializer may be specified in a declaration, or as part of an array creation expression + */ + PsiElement parent = expression.getParent(); + if (parent instanceof PsiVariable variable) { + if (variable.getType() instanceof PsiArrayType) { + return null; + } + } + else if (parent instanceof PsiNewExpression || parent instanceof PsiArrayInitializerExpression) { + return null; + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.arrayInitializerNotAllowed()) + .registerFix(QuickFixFactory.getInstance().createAddNewArrayExpressionFix(expression)) + .create(); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkCaseStatement(PsiSwitchLabelStatementBase statement) { + PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); + if (switchBlock == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.statementCaseOutsideSwitch()) + .create(); + } + + return null; + } + + @RequiredReadAction + public static Collection checkSwitchLabelValues(PsiSwitchBlock switchBlock) { + PsiCodeBlock body = switchBlock.getBody(); + if (body == null) { + return Collections.emptyList(); + } + + PsiExpression selectorExpression = switchBlock.getExpression(); + PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType(); + MultiMap values = new MultiMap<>(); + Object defaultValue = new Object(); + Collection results = new ArrayList<>(); + boolean hasDefaultCase = false; + + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) { + continue; + } + boolean defaultCase = labelStatement.isDefaultCase(); + + if (defaultCase) { + values.putValue(defaultValue, ObjectUtil.notNull(labelStatement.getFirstChild(), labelStatement)); + hasDefaultCase = true; + } + else { + PsiExpressionList expressionList = labelStatement.getCaseValues(); + if (expressionList != null) { + for (PsiExpression expr : expressionList.getExpressions()) { + if (selectorExpression != null) { + HighlightInfo.Builder result = checkAssignability(selectorType, expr.getType(), expr, expr); + if (result != null) { + results.add(result.create()); + continue; + } + } + + Object value = null; + if (expr instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiEnumConstant enumConst) { + value = enumConst.getName(); + if (refExpr.getQualifier() != null) { + results.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refExpr) + .descriptionAndTooltip(JavaErrorLocalize.qualifiedEnumConstantInSwitch()) + .create() + ); + continue; + } + } + if (value == null) { + value = ConstantExpressionUtil.computeCastTo(expr, selectorType); + } + if (value == null) { + results.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(JavaCompilationErrorLocalize.switchLabelConstantExpected()) + .create() + ); + continue; + } + + values.putValue(value, expr); + } + } + } + } + + for (Map.Entry> entry : values.entrySet()) { + if (entry.getValue().size() > 1) { + Object value = entry.getKey(); + LocalizeValue description = value == defaultValue + ? JavaCompilationErrorLocalize.switchLabelDuplicateDefault() + : JavaCompilationErrorLocalize.switchLabelDuplicate(value); + for (PsiElement element : entry.getValue()) { + results.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(description) + .create() + ); + } + } + } + + if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) { + Set missingConstants = new HashSet<>(); + boolean exhaustive = hasDefaultCase; + if (!exhaustive) { + if (!values.isEmpty() && selectorType instanceof PsiClassType classType) { + PsiClass type = classType.resolve(); + if (type != null && type.isEnum()) { + for (PsiField field : type.getFields()) { + if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { + missingConstants.add(field.getName()); + } + } + exhaustive = missingConstants.isEmpty(); + } + } + } + if (!exhaustive) { + LocalizeValue message = values.isEmpty() + ? JavaErrorLocalize.switchExprEmpty() + : JavaErrorLocalize.switchExprIncomplete(); + QuickFixFactory factory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ObjectUtil.notNull(selectorExpression, switchBlock)) + .descriptionAndTooltip(message); + if (!missingConstants.isEmpty()) { + hlBuilder.registerFix(factory.createAddMissingEnumBranchesFix(switchBlock, missingConstants)); + } + hlBuilder.registerFix(factory.createAddSwitchDefaultFix(switchBlock, LocalizeValue.empty())); + results.add(hlBuilder.create()); + } + } + + return results; } - return null; - } + /** + * see JLS 8.3.2.3 + */ + @Nullable + @RequiredReadAction + public static HighlightInfo checkIllegalForwardReferenceToField( + PsiReferenceExpression expression, + PsiField referencedField + ) { + Boolean isIllegalForwardReference = isIllegalForwardReferenceToField(expression, referencedField, false); + if (isIllegalForwardReference == null) { + return null; + } + LocalizeValue description = isIllegalForwardReference + ? JavaErrorLocalize.illegalForwardReference() + : JavaErrorLocalize.illegalSelfReference(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(description) + .create(); + } + + @RequiredReadAction + public static Boolean isIllegalForwardReferenceToField( + PsiReferenceExpression expression, + PsiField referencedField, + boolean acceptQualified + ) { + PsiClass containingClass = referencedField.getContainingClass(); + if (containingClass == null) { + return null; + } + if (expression.getContainingFile() != referencedField.getContainingFile()) { + return null; + } + if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) { + return null; + } + // only simple reference can be illegal + if (!acceptQualified && expression.getQualifierExpression() != null) { + return null; + } + PsiField initField = findEnclosingFieldInitializer(expression); + PsiClassInitializer classInitializer = findParentClassInitializer(expression); + if (initField == null && classInitializer == null) { + return null; + } + // instance initializers may access static fields + boolean isStaticClassInitializer = classInitializer != null && classInitializer.isStatic(); + boolean isStaticInitField = initField != null && initField.isStatic(); + boolean inStaticContext = isStaticInitField || isStaticClassInitializer; + if (!inStaticContext && referencedField.isStatic()) { + return null; + } + if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) { + return null; + } + if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { + return null; + } + return initField != referencedField; + } - private static boolean isIntersection(PsiTypeElement castTypeElement, PsiType castType) { - if (castType instanceof PsiIntersectionType) { - return true; + /** + * @return field that has initializer with this element as subexpression or null if not found + */ + @Nullable + public static PsiField findEnclosingFieldInitializer(@Nullable PsiElement element) { + while (element != null) { + PsiElement parent = element.getParent(); + if (parent instanceof PsiField field) { + if (element == field.getInitializer()) { + return field; + } + if (field instanceof PsiEnumConstant enumConst && element == enumConst.getArgumentList()) { + return field; + } + } + if (element instanceof PsiClass || element instanceof PsiMethod) { + return null; + } + element = parent; + } + return null; } - return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; - } - @Nullable - public static HighlightInfo checkInconvertibleTypeCast(@Nonnull PsiTypeCastExpression expression) { - final PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement == null) { - return null; + @Nullable + private static PsiClassInitializer findParentClassInitializer(@Nullable PsiElement element) { + while (element != null) { + if (element instanceof PsiClassInitializer classInitializer) { + return classInitializer; + } + if (element instanceof PsiClass || element instanceof PsiMethod) { + return null; + } + element = element.getParent(); + } + return null; } - PsiType castType = castTypeElement.getType(); - PsiExpression operand = expression.getOperand(); - if (operand == null) { - return null; + @Nullable + @RequiredReadAction + public static HighlightInfo checkIllegalType(@Nullable PsiTypeElement typeElement) { + if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) { + return null; + } + + if (PsiUtil.isInsideJavadocComment(typeElement)) { + return null; + } + + PsiType type = typeElement.getType(); + PsiType componentType = type.getDeepComponentType(); + if (componentType instanceof PsiClassType) { + PsiClass aClass = PsiUtil.resolveClassInType(componentType); + if (aClass == null) { + String canonicalText = type.getCanonicalText(); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeUnknownClass(canonicalText)) + .create(); + + PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement(); + if (referenceElement != null && info != null) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(referenceElement, QuickFixActionRegistrar.create(info)); + } + return info; + } + } + + return null; } - PsiType operandType = operand.getType(); - if (operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, castType, PsiUtil.getLanguageLevel(expression)) && !RedundantCastUtil.isInPolymorphicCall(expression)) { - String message = JavaErrorBundle.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil.formatType(castType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkIllegalVoidType(PsiKeyword type) { + if (!PsiKeyword.VOID.equals(type.getText())) { + return null; + } + + if (type.getParent() instanceof PsiTypeElement typeElem) { + PsiElement typeOwner = typeElem.getParent(); + if (typeOwner != null) { + // do not highlight incomplete declarations + if (PsiUtilCore.hasErrorElementChild(typeOwner)) { + return null; + } + } + + if (typeOwner instanceof PsiMethod method) { + if (method.getReturnTypeElement() == typeElem && PsiType.VOID.equals(method.getReturnType())) { + return null; + } + } + else if (typeOwner instanceof PsiClassObjectAccessExpression classObjectAccessExpression) { + if (TypeConversionUtil.isVoidType(classObjectAccessExpression.getOperand().getType())) { + return null; + } + } + else if (typeOwner instanceof JavaCodeFragment) { + if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) { + return null; + } + } + } + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(type) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeVoidIllegal()); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMemberReferencedBeforeConstructorCalled( + PsiElement expression, + PsiElement resolved, + PsiFile containingFile + ) { + PsiClass referencedClass; + String resolvedName; + PsiType type; + if (expression instanceof PsiJavaCodeReferenceElement javaCodeRef) { + // redirected ctr + if (PsiKeyword.THIS.equals(javaCodeRef.getReferenceName()) && resolved instanceof PsiMethod method && method.isConstructor()) { + return null; + } + PsiElement qualifier = javaCodeRef.getQualifier(); + type = qualifier instanceof PsiExpression qExpr ? qExpr.getType() : null; + referencedClass = PsiUtil.resolveClassInType(type); + + boolean isSuperCall = RefactoringChangeUtil.isSuperMethodCall(javaCodeRef.getParent()); + if (resolved == null && isSuperCall) { + if (qualifier instanceof PsiReferenceExpression qRefExpr) { + resolved = qRefExpr.resolve(); + expression = qRefExpr; + type = qRefExpr.getType(); + referencedClass = PsiUtil.resolveClassInType(type); + } + else if (qualifier == null) { + PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); + if (method != null) { + referencedClass = method.getContainingClass(); + } + resolved = method; + } + else if (qualifier instanceof PsiThisExpression thisExpr) { + referencedClass = PsiUtil.resolveClassInType(thisExpr.getType()); + } + } + if (resolved instanceof PsiField referencedField) { + if (referencedField.isStatic()) { + return null; + } + resolvedName = PsiFormatUtil.formatVariable( + referencedField, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + PsiSubstitutor.EMPTY + ); + referencedClass = referencedField.getContainingClass(); + } + else if (resolved instanceof PsiMethod method) { + if (method.isStatic()) { + return null; + } + PsiElement nameElement = expression instanceof PsiThisExpression + ? expression + : ((PsiJavaCodeReferenceElement) expression).getReferenceNameElement(); + String name = nameElement == null ? null : nameElement.getText(); + if (isSuperCall) { + if (referencedClass == null) { + return null; + } + if (qualifier == null) { + PsiClass superClass = referencedClass.getSuperClass(); + if (superClass != null && PsiUtil.isInnerClass(superClass) + && InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) { + // by default super() is considered this. - qualified + resolvedName = PsiKeyword.THIS; + } + else { + return null; + } + } + else { + resolvedName = qualifier.getText(); + } + } + else if (PsiKeyword.THIS.equals(name)) { + resolvedName = PsiKeyword.THIS; + } + else { + resolvedName = PsiFormatUtil.formatMethod( + method, + PsiSubstitutor.EMPTY, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + 0 + ); + if (referencedClass == null) { + referencedClass = method.getContainingClass(); + } + } + } + else if (resolved instanceof PsiClass aClass) { + if (aClass.isStatic()) { + return null; + } + referencedClass = aClass.getContainingClass(); + if (referencedClass == null) { + return null; + } + resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); + } + else { + return null; + } + } + else if (expression instanceof PsiThisExpression thisExpr) { + type = thisExpr.getType(); + referencedClass = PsiUtil.resolveClassInType(type); + if (thisExpr.getQualifier() != null) { + resolvedName = + referencedClass == null ? null : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_NAME) + ".this"; + } + else { + resolvedName = "this"; + } + } + else { + return null; + } + + if (referencedClass == null) { + return null; + } + return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName, containingFile); } + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkReferenceToOurInstanceInsideThisOrSuper( + PsiElement expression, + PsiClass referencedClass, + String resolvedName, + PsiFile containingFile + ) { + if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) { + return null; + } + PsiElement element = expression.getParent(); + while (element != null) { + // check if expression inside super()/this() call + if (RefactoringChangeUtil.isSuperOrThisMethodCall(element)) { + PsiElement parentClass = new PsiMatcherImpl(element) + .parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) + .parent(PsiMatchers.hasClass(PsiMethod.class)) + .dot(JavaMatchers.isConstructor(true)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .getElement(); + if (parentClass == null) { + return null; + } - return null; - } + // only this class/superclasses instance methods are not allowed to call + PsiClass aClass = (PsiClass) parentClass; + if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) { + return null; + } + // field or method should be declared in this class or super + if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) { + return null; + } + // and point to our instance + if (expression instanceof PsiReferenceExpression refExpr + && !thisOrSuperReference(refExpr.getQualifierExpression(), aClass)) { + return null; + } - @Nullable - public static HighlightInfo checkVariableExpected(@Nonnull PsiExpression expression) { - PsiExpression lValue; - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression) expression; - lValue = assignment.getLExpression(); - } else if (PsiUtil.isIncrementDecrementOperation(expression)) { - lValue = expression instanceof PsiPostfixExpression ? ((PsiPostfixExpression) expression).getOperand() : ((PsiPrefixExpression) expression).getOperand(); - } else { - lValue = null; - } - HighlightInfo errorResult = null; - if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { - String description = JavaErrorBundle.message("variable.expected"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(lValue).descriptionAndTooltip(description).create(); - } + if (expression instanceof PsiJavaCodeReferenceElement + && !aClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) + && PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) { + return null; + } - return errorResult; - } + if (expression instanceof PsiJavaCodeReferenceElement + && PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) { + return null; + } + // JEP 482 Flexible Constructor Bodies: assigning to an instance field of this class + // (on the left-hand side of a simple assignment) before super()/this() is allowed + if (PsiUtil.isAvailable(JavaFeature.STATEMENTS_BEFORE_SUPER, expression) + && PsiUtil.getLanguageLevel(expression) != LanguageLevel.JDK_22_PREVIEW) { + if (expression instanceof PsiReferenceExpression fieldRef + && isOnSimpleAssignmentLeftHand(expression) + && fieldRef.resolve() instanceof PsiField field + && !field.isStatic() + && field.getContainingClass() == aClass) { + return field.hasInitializer() + ? HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression.getTextRange()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.fieldInitializedBeforeConstructorCall(resolvedName)) + : null; + } + if (expression instanceof PsiThisExpression) { + PsiElement assignTarget = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (assignTarget instanceof PsiReferenceExpression fieldRef + && isOnSimpleAssignmentLeftHand(assignTarget) + && fieldRef.resolve() instanceof PsiField field + && !field.isStatic() + && field.getContainingClass() == aClass) { + return null; + } + } + } - @Nullable - public static HighlightInfo checkAssignmentOperatorApplicable(@Nonnull PsiAssignmentExpression assignment) { - PsiJavaToken operationSign = assignment.getOperationSign(); - IElementType eqOpSign = operationSign.getTokenType(); - IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); - if (opSign == null) { - return null; - } - final PsiType lType = assignment.getLExpression().getType(); - final PsiExpression rExpression = assignment.getRExpression(); - if (rExpression == null) { - return null; - } - final PsiType rType = rExpression.getType(); - HighlightInfo errorResult = null; - if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, lType, rType, true)) { - String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); - String message = JavaErrorBundle.message("binary.operator.not.applicable", operatorText, JavaHighlightUtil.formatType(lType), JavaHighlightUtil.formatType(rType)); + HighlightInfo.Builder hlBuilder = createMemberReferencedError(resolvedName, expression.getTextRange()); + if (expression instanceof PsiReferenceExpression refExpr && PsiUtil.isInnerClass(aClass)) { + String referenceName = refExpr.getReferenceName(); + PsiClass containingClass = aClass.getContainingClass(); + LOG.assertTrue(containingClass != null); + PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true); + if (fieldInContainingClass != null && refExpr.getQualifierExpression() == null) { + hlBuilder.registerFix(new QualifyWithThisFix(containingClass, refExpr)); + } + } - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(assignment).descriptionAndTooltip(message).create(); - } - return errorResult; - } + return hlBuilder; + } + if (element instanceof PsiReferenceExpression refExpr) { + PsiElement resolve; + if (refExpr instanceof PsiReferenceExpressionImpl refExprImpl) { + JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile( + refExprImpl, + PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE, + true, + false, + containingFile + ); + resolve = results.length == 1 ? results[0].getElement() : null; + } + else { + resolve = refExpr.resolve(); + } - @Nullable - public static HighlightInfo checkAssignmentCompatibleTypes(@Nonnull PsiAssignmentExpression assignment) { - PsiExpression lExpr = assignment.getLExpression(); - PsiExpression rExpr = assignment.getRExpression(); - if (rExpr == null) { - return null; - } - PsiType lType = lExpr.getType(); - PsiType rType = rExpr.getType(); - if (rType == null) { - return null; - } + if (resolve instanceof PsiField field && field.isStatic()) { + return null; + } + } - final IElementType sign = assignment.getOperationTokenType(); - HighlightInfo highlightInfo; - if (JavaTokenType.EQ.equals(sign)) { - highlightInfo = checkAssignability(lType, rType, rExpr, assignment); - } else { - // 15.26.2. Compound Assignment Operators - final IElementType opSign = TypeConversionUtil.convertEQtoOperation(sign); - final PsiType type = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, opSign, true); - if (type == null || lType == null || TypeConversionUtil.areTypesConvertible(type, lType)) { + element = element.getParent(); + if (element instanceof PsiClass psiClass && InheritanceUtil.isInheritorOrSelf(psiClass, referencedClass, true)) { + return null; + } + } return null; - } - highlightInfo = createIncompatibleTypeHighlightInfo(lType, type, assignment.getTextRange(), 0); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createChangeToAppendFix(sign, lType, assignment)); } - if (highlightInfo == null) { - return null; - } - registerChangeVariableTypeFixes(lExpr, rType, rExpr, highlightInfo); - if (lType != null) { - registerChangeVariableTypeFixes(rExpr, lType, lExpr, highlightInfo); - } - return highlightInfo; - } - private static void registerChangeVariableTypeFixes(@Nonnull PsiExpression expression, @Nonnull PsiType type, @Nullable final PsiExpression lExpr, @Nullable HighlightInfo highlightInfo) { - if (highlightInfo == null || !(expression instanceof PsiReferenceExpression)) { - return; + private static HighlightInfo.Builder createMemberReferencedError(String resolvedName, TextRange textRange) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .descriptionAndTooltip(JavaErrorLocalize.memberReferencedBeforeConstructorCalled(resolvedName)); } - final PsiElement element = ((PsiReferenceExpression) expression).resolve(); - if (!(element instanceof PsiVariable)) { - return; + private static boolean isOnSimpleAssignmentLeftHand(PsiElement expr) { + PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class); + return parent instanceof PsiAssignmentExpression assignment + && JavaTokenType.EQ == assignment.getOperationTokenType() + && PsiTreeUtil.isAncestor(assignment.getLExpression(), expr, false); } - registerChangeVariableTypeFixes((PsiVariable) element, type, lExpr, highlightInfo); - - if (lExpr instanceof PsiMethodCallExpression && lExpr.getParent() instanceof PsiAssignmentExpression) { - final PsiElement parent = lExpr.getParent(); - if (parent.getParent() instanceof PsiStatement) { - final PsiMethod method = ((PsiMethodCallExpression) lExpr).resolveMethod(); - if (method != null && PsiType.VOID.equals(method.getReturnType())) { - QuickFixAction.registerQuickFixAction(highlightInfo, new ReplaceAssignmentFromVoidWithStatementIntentionAction(parent, lExpr)); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkImplicitThisReferenceBeforeSuper( + PsiClass aClass, + JavaSdkVersion javaSdkVersion + ) { + if (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) { + return null; } - } - } - } - - private static boolean isCastIntentionApplicable(@Nonnull PsiExpression expression, @Nullable PsiType toType) { - while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { - if (expression instanceof PsiTypeCastExpression) { - expression = ((PsiTypeCastExpression) expression).getOperand(); - } - if (expression instanceof PsiParenthesizedExpression) { - expression = ((PsiParenthesizedExpression) expression).getExpression(); - } - } - if (expression == null) { - return false; + if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiTypeParameter) { + return null; + } + PsiClass superClass = aClass.getSuperClass(); + if (superClass == null || !PsiUtil.isInnerClass(superClass)) { + return null; + } + PsiClass outerClass = superClass.getContainingClass(); + if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { + return null; + } + // 'this' can be used as an (implicit) super() qualifier + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) { + TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return createMemberReferencedError(aClass.getName() + ".this", range); + } + for (PsiMethod constructor : constructors) { + if (!isSuperCalledInConstructor(constructor)) { + return createMemberReferencedError( + aClass.getName() + ".this", + HighlightNamesUtil.getMethodDeclarationTextRange(constructor) + ); + } + } + return null; } - PsiType rType = expression.getType(); - return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); - } - - @Nullable - public static HighlightInfo checkVariableInitializerType(@Nonnull PsiVariable variable) { - PsiExpression initializer = variable.getInitializer(); - // array initializer checked in checkArrayInitializerApplicable - if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { - return null; - } - PsiType lType = variable.getType(); - PsiType rType = initializer.getType(); - PsiTypeElement typeElement = variable.getTypeElement(); - int start = typeElement != null ? typeElement.getTextRange().getStartOffset() : variable.getTextRange().getStartOffset(); - int end = variable.getTextRange().getEndOffset(); - HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0); - if (highlightInfo != null) { - registerChangeVariableTypeFixes(variable, rType, variable.getInitializer(), highlightInfo); - registerChangeVariableTypeFixes(initializer, lType, null, highlightInfo); + private static boolean isSuperCalledInConstructor(PsiMethod constructor) { + PsiCodeBlock body = constructor.getBody(); + if (body == null) { + return false; + } + PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) { + return false; + } + PsiStatement statement = statements[0]; + PsiElement element = new PsiMatcherImpl(statement) + .dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiReferenceExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) + .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) + .getElement(); + return element != null; } - return highlightInfo; - } - @Nullable - public static HighlightInfo checkAssignability(@Nullable PsiType lType, @Nullable PsiType rType, @Nullable PsiExpression expression, @Nonnull PsiElement elementToHighlight) { - TextRange textRange = elementToHighlight.getTextRange(); - return checkAssignability(lType, rType, expression, textRange, 0); - } - - @Nullable - private static HighlightInfo checkAssignability(@Nullable PsiType lType, @Nullable PsiType rType, @Nullable PsiExpression expression, @Nonnull TextRange textRange, int navigationShift) { - if (lType == rType) { - return null; - } - if (expression == null) { - if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) { - return null; - } - } else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { - return null; - } - if (rType == null) { - rType = expression.getType(); - } - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); - if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddTypeCastFix(lType, expression)); - } - if (expression != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapWithAdapterFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapWithOptionalFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapExpressionFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapStringWithFileFix(lType, expression)); - AddTypeArgumentsConditionalFix.register(highlightInfo, expression, lType); - registerCollectionToArrayFixAction(highlightInfo, rType, lType, expression); - } - ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); - return highlightInfo; - } - - - @Nullable - public static HighlightInfo checkReturnStatementType(@Nonnull PsiReturnStatement statement) { - PsiMethod method = null; - PsiLambdaExpression lambda = null; - PsiElement parent = statement.getParent(); - while (true) { - if (parent instanceof PsiFile) { - break; - } - if (parent instanceof PsiClassInitializer) { - break; - } - if (parent instanceof PsiLambdaExpression) { - lambda = (PsiLambdaExpression) parent; - break; - } - if (parent instanceof PsiMethod) { - method = (PsiMethod) parent; - break; - } - parent = parent.getParent(); - } - if (parent instanceof PsiCodeFragment) { - return null; - } - String description; - HighlightInfo errorResult = null; - if (method == null && lambda != null) { - //todo check return statements type inside lambda - } else if (method == null && !(parent instanceof ServerPageFile)) { - description = JavaErrorBundle.message("return.outside.method"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } else { - PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; - boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); - final PsiExpression returnValue = statement.getReturnValue(); - if (returnValue != null) { - PsiType valueType = RefactoringChangeUtil.getTypeByExpression(returnValue); - if (isMethodVoid) { - description = JavaErrorBundle.message("return.from.void.method"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - if (valueType != null) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true)); - } - } else { - TextRange textRange = statement.getTextRange(); - errorResult = checkAssignability(returnType, valueType, returnValue, textRange, returnValue.getStartOffsetInParent()); - if (errorResult != null && valueType != null) { - if (!PsiType.VOID.equals(valueType)) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true)); - } - registerChangeParameterClassFix(returnType, valueType, errorResult); - if (returnType instanceof PsiArrayType) { - final PsiType erasedValueType = TypeConversionUtil.erasure(valueType); - if (erasedValueType != null && TypeConversionUtil.isAssignable(((PsiArrayType) returnType).getComponentType(), erasedValueType)) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSurroundWithArrayFix(null, returnValue)); - } - } - registerCollectionToArrayFixAction(errorResult, valueType, returnType, returnValue); - } - } - } else { - if (!isMethodVoid) { - description = JavaErrorBundle.message("missing.return.value"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).navigationShift(PsiKeyword.RETURN.length()).create(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true)); - } - } - } - return errorResult; - } - - private static void registerCollectionToArrayFixAction(@Nullable HighlightInfo info, @Nullable PsiType fromType, @Nullable PsiType toType, @Nonnull PsiExpression expression) { - if (toType instanceof PsiArrayType) { - PsiType arrayComponentType = ((PsiArrayType) toType).getComponentType(); - if (!(arrayComponentType instanceof PsiPrimitiveType) && !(PsiUtil.resolveClassInType(arrayComponentType) instanceof PsiTypeParameter) && InheritanceUtil.isInheritor(fromType, - JavaClassNames.JAVA_UTIL_COLLECTION)) { - PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(fromType, expression.getResolveScope()); - if (collectionItemType != null && arrayComponentType.isAssignableFrom(collectionItemType)) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createCollectionToArrayFix(expression, (PsiArrayType) toType)); - } - } - } - } - - @Nonnull - public static String getUnhandledExceptionsDescriptor(@Nonnull final Collection unhandled) { - return getUnhandledExceptionsDescriptor(unhandled, null); - } - - @Nonnull - private static String getUnhandledExceptionsDescriptor(@Nonnull final Collection unhandled, @Nullable final String source) { - final String exceptions = formatTypes(unhandled); - return source != null ? JavaErrorBundle.message("unhandled.close.exceptions", exceptions, unhandled.size(), source) : JavaErrorBundle.message("unhandled.exceptions", exceptions, - unhandled.size()); - } - - @Nonnull - private static String formatTypes(@Nonnull Collection unhandled) { - return StringUtil.join(unhandled, JavaHighlightUtil::formatType, ", "); - } - - @Nullable - public static HighlightInfo checkVariableAlreadyDefined(@Nonnull PsiVariable variable) { - if (variable instanceof ExternallyDefinedPsiElement) { - return null; - } - boolean isIncorrect = false; - PsiElement declarationScope = null; - if (variable instanceof PsiLocalVariable || variable instanceof PsiParameter && ((declarationScope = ((PsiParameter) variable).getDeclarationScope()) instanceof PsiCatchSection || - declarationScope instanceof PsiForeachStatement || declarationScope instanceof PsiLambdaExpression)) { - @SuppressWarnings("unchecked") PsiElement scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class, PsiResourceList.class); - VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) { - @Override - protected boolean check(final PsiVariable var, final ResolveState state) { - return (var instanceof PsiLocalVariable || var instanceof PsiParameter) && super.check(var, state); - } - }; - PsiIdentifier identifier = variable.getNameIdentifier(); - assert identifier != null : variable; - PsiScopesUtil.treeWalkUp(proc, identifier, scope); - if (scope instanceof PsiResourceList && proc.size() == 0) { - scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class); - PsiScopesUtil.treeWalkUp(proc, identifier, scope); - } - if (proc.size() > 0) { - isIncorrect = true; - } else if (declarationScope instanceof PsiLambdaExpression) { - isIncorrect = checkSameNames(variable); - } - } else if (variable instanceof PsiField) { - PsiField field = (PsiField) variable; - PsiClass aClass = field.getContainingClass(); - if (aClass == null) { - return null; - } - PsiField fieldByName = aClass.findFieldByName(variable.getName(), false); - if (fieldByName != null && fieldByName != field) { - isIncorrect = true; - } - } else { - isIncorrect = checkSameNames(variable); - } - - if (isIncorrect) { - String description = JavaErrorBundle.message("variable.already.defined", variable.getName()); - PsiIdentifier identifier = variable.getNameIdentifier(); - assert identifier != null : variable; - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); - if (variable instanceof PsiLocalVariable) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createReuseVariableDeclarationFix((PsiLocalVariable) variable)); - } - return highlightInfo; - } - return null; - } - - private static boolean checkSameNames(@Nonnull PsiVariable variable) { - PsiElement scope = variable.getParent(); - PsiElement[] children = scope.getChildren(); - for (PsiElement child : children) { - if (child instanceof PsiVariable) { - if (child.equals(variable)) { - continue; - } - if (Objects.equals(variable.getName(), ((PsiVariable) child).getName())) { - return true; - } - } - } - return false; - } - - @Nullable - public static HighlightInfo checkUnderscore(@Nonnull PsiIdentifier identifier, @Nonnull LanguageLevel languageLevel) { - if ("_".equals(identifier.getText())) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - String text = JavaErrorBundle.message("underscore.identifier.error"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); - } else if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - PsiElement parent = identifier.getParent(); - if (parent instanceof PsiParameter && ((PsiParameter) parent).getDeclarationScope() instanceof PsiLambdaExpression) { - String text = JavaErrorBundle.message("underscore.lambda.identifier"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); - } - } - } - - return null; - } - - @Nonnull - public static String formatClass(@Nonnull PsiClass aClass) { - return formatClass(aClass, true); - } - - @Nonnull - public static String formatClass(@Nonnull PsiClass aClass, boolean fqn) { - return PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0)); - } - - @Nonnull - private static String formatField(@Nonnull PsiField field) { - return PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); - } - - @Nullable - public static HighlightInfo checkUnhandledExceptions(@Nonnull final PsiElement element, @Nullable TextRange textRange) { - final List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); - if (unhandledExceptions.isEmpty()) { - return null; - } - - final HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); - if (highlightType == null) { - return null; - } - - if (textRange == null) { - textRange = element.getTextRange(); - } - final String description = getUnhandledExceptionsDescriptor(unhandledExceptions); - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(highlightType).range(textRange).descriptionAndTooltip(description).create(); - registerUnhandledExceptionFixes(element, errorResult, unhandledExceptions); - return errorResult; - } - - @Nullable - public static HighlightInfo checkUnhandledCloserExceptions(@Nonnull PsiResourceListElement resource) { - List unhandled = ExceptionUtil.getUnhandledCloserExceptions(resource, null); - if (unhandled.isEmpty()) { - return null; - } - - HighlightInfoType highlightType = getUnhandledExceptionHighlightType(resource); - if (highlightType == null) { - return null; - } - - String description = getUnhandledExceptionsDescriptor(unhandled, "auto-closeable resource"); - HighlightInfo highlight = HighlightInfo.newHighlightInfo(highlightType).range(resource).descriptionAndTooltip(description).create(); - registerUnhandledExceptionFixes(resource, highlight, unhandled); - return highlight; - } - - private static void registerUnhandledExceptionFixes(PsiElement element, HighlightInfo errorResult, List unhandled) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToCatchFix()); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToThrowsFix(element)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionFromFieldInitializerToConstructorThrowsFix(element)); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSurroundWithTryCatchFix(element)); - if (unhandled.size() == 1) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createGeneralizeCatchFix(element, unhandled.get(0))); - } - } - - @Nullable - private static HighlightInfoType getUnhandledExceptionHighlightType(PsiElement element) { - // JSP top level errors are handled by UnhandledExceptionInJSP inspection - if (FileTypeUtils.isInServerPageFile(element)) { - PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiLambdaExpression.class); - if (targetMethod instanceof SyntheticElement) { - return null; - } - } - - return HighlightInfoType.UNHANDLED_EXCEPTION; - } - - @Nullable - public static HighlightInfo checkBreakOutsideLoop(@Nonnull PsiBreakStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { - String description = JavaErrorBundle.message("break.outside.switch.or.loop"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } else { - // todo labeled - } - return null; - } - - - @Nullable - public static HighlightInfo checkContinueOutsideLoop(@Nonnull PsiContinueStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { - String description = JavaErrorBundle.message("continue.outside.loop"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } else { - PsiStatement exitedStatement = statement.findContinuedStatement(); - if (exitedStatement == null) { - return null; - } - if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof - PsiForeachStatement)) { - String description = JavaErrorBundle.message("not.loop.label", statement.getLabelIdentifier().getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } - return null; - } - - @Nullable - public static HighlightInfo checkIllegalModifierCombination(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { - @PsiModifier.ModifierConstant String modifier = keyword.getText(); - String incompatible = getIncompatibleModifier(modifier, modifierList); - if (incompatible != null) { - String message = JavaErrorBundle.message("incompatible.modifiers", modifier, incompatible); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); - return highlightInfo; - } - - return null; - } - - @Contract("null -> null") - private static Map> getIncompatibleModifierMap(@Nullable PsiElement modifierListOwner) { - if (modifierListOwner == null || PsiUtilCore.hasErrorElementChild(modifierListOwner)) { - return null; - } - if (modifierListOwner instanceof PsiClass) { - return ((PsiClass) modifierListOwner).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiMethod) { - return ourMethodIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiVariable) { - return ourFieldIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiClassInitializer) { - return ourClassInitializerIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiJavaModule) { - return ourModuleIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiRequiresStatement) { - return ourRequiresIncompatibleModifiers; - } - return null; - } - - @Nullable - public static String getIncompatibleModifier(String modifier, @Nonnull PsiModifierList modifierList) { - Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList.getParent()); - return incompatibleModifierMap != null ? getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap) : null; - } - - @Nullable - public static HighlightInfo checkNotAllowedModifier(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { - PsiElement modifierOwner = modifierList.getParent(); - Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierOwner); - if (incompatibleModifierMap == null) { - return null; - } - - @PsiModifier.ModifierConstant String modifier = keyword.getText(); - Set incompatibles = incompatibleModifierMap.get(modifier); - PsiElement modifierOwnerParent = modifierOwner instanceof PsiMember ? ((PsiMember) modifierOwner).getContainingClass() : modifierOwner.getParent(); - if (modifierOwnerParent == null) { - modifierOwnerParent = modifierOwner.getParent(); - } - boolean isAllowed = true; - if (modifierOwner instanceof PsiClass) { - PsiClass aClass = (PsiClass) modifierOwner; - if (aClass.isInterface()) { - if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.PACKAGE_LOCAL.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass; - } - } else { - if (PsiModifier.PUBLIC.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiJavaFile || modifierOwnerParent instanceof PsiClass && (modifierOwnerParent instanceof PsiSyntheticClass || ((PsiClass) - modifierOwnerParent).getQualifiedName() != null); - } else if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.PACKAGE_LOCAL.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass && ((PsiClass) modifierOwnerParent).getQualifiedName() != null || FileTypeUtils.isInServerPageFile(modifierOwnerParent); - } - - if (aClass.isEnum()) { - isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); - } - - if (aClass.getContainingClass() instanceof PsiAnonymousClass) { - isAllowed &= !(PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier)); - } - } - } else if (modifierOwner instanceof PsiMethod) { - PsiMethod method = (PsiMethod) modifierOwner; - isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); - PsiClass containingClass = method.getContainingClass(); - if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && containingClass != null && containingClass.isEnum()) { - isAllowed = false; - } - - if (PsiModifier.PRIVATE.equals(modifier)) { - isAllowed &= modifierOwnerParent instanceof PsiClass && (!((PsiClass) modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel9OrHigher(modifierOwner) && !((PsiClass) - modifierOwnerParent).isAnnotationType()); - } else if (PsiModifier.STRICTFP.equals(modifier)) { - isAllowed &= modifierOwnerParent instanceof PsiClass && (!((PsiClass) modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel8OrHigher(modifierOwner)); - } else if (PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { - isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass) modifierOwnerParent).isInterface(); - } - - if (containingClass != null && containingClass.isInterface()) { - isAllowed &= !PsiModifier.NATIVE.equals(modifier); - } - - if (containingClass != null && containingClass.isAnnotationType()) { - isAllowed &= !PsiModifier.STATIC.equals(modifier); - isAllowed &= !PsiModifier.DEFAULT.equals(modifier); - } - } else if (modifierOwner instanceof PsiField) { - if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || PsiModifier.STRICTFP.equals(modifier) || PsiModifier - .SYNCHRONIZED.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass) modifierOwnerParent).isInterface(); - } - } else if (modifierOwner instanceof PsiClassInitializer) { - isAllowed = PsiModifier.STATIC.equals(modifier); - } else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { - isAllowed = PsiModifier.FINAL.equals(modifier); - } else if (modifierOwner instanceof PsiReceiverParameter) { - isAllowed = false; - } - - isAllowed &= incompatibles != null; - if (!isAllowed) { - String message = JavaErrorBundle.message("modifier.not.allowed", modifier); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); - return highlightInfo; - } - - return null; - } - - @Nullable - public static HighlightInfo checkLiteralExpressionParsingError(@Nonnull PsiLiteralExpression expression, LanguageLevel level, PsiFile file) { - PsiElement literal = expression.getFirstChild(); - assert literal instanceof PsiJavaToken : literal; - IElementType type = ((PsiJavaToken) literal).getTokenType(); - if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD || type == JavaTokenType.NULL_KEYWORD) { - return null; - } - - boolean isInt = ElementType.INTEGER_LITERALS.contains(type); - boolean isFP = ElementType.REAL_LITERALS.contains(type); - String text = isInt || isFP ? StringUtil.toLowerCase(literal.getText()) : literal.getText(); - Object value = expression.getValue(); - - if (file != null) { - if (isFP) { - if (text.startsWith(PsiLiteralUtil.HEX_PREFIX)) { - HighlightInfo info = checkFeature(expression, Feature.HEX_FP_LITERALS, level, file); - if (info != null) { - return info; - } + @RequiredReadAction + private static boolean thisOrSuperReference(@Nullable PsiExpression qualifierExpression, PsiClass aClass) { + if (qualifierExpression == null) { + return true; } - } - if (isInt) { - if (text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { - HighlightInfo info = checkFeature(expression, Feature.BIN_LITERALS, level, file); - if (info != null) { - return info; - } + PsiJavaCodeReferenceElement qualifier; + if (qualifierExpression instanceof PsiThisExpression thisExpr) { + qualifier = thisExpr.getQualifier(); } - } - if (isInt || isFP) { - if (text.contains("_")) { - HighlightInfo info = checkFeature(expression, Feature.UNDERSCORES, level, file); - if (info != null) { - return info; - } - info = checkUnderscores(expression, text, isInt); - if (info != null) { - return info; - } - } - } - } - - PsiElement parent = expression.getParent(); - if (type == JavaTokenType.INTEGER_LITERAL) { - String cleanText = StringUtil.replace(text, "_", ""); - //literal 2147483648 may appear only as the operand of the unary negation operator -. - if (!(cleanText.equals(PsiLiteralUtil._2_IN_31) && - parent instanceof PsiPrefixExpression && - ((PsiPrefixExpression) parent).getOperationTokenType() == JavaTokenType.MINUS)) { - if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { - String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { - String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_31)) { - String message = JavaErrorBundle.message("integer.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } else if (type == JavaTokenType.LONG_LITERAL) { - String cleanText = StringUtil.replace(StringUtil.trimEnd(text, 'l'), "_", ""); - //literal 9223372036854775808L may appear only as the operand of the unary negation operator -. - if (!(cleanText.equals(PsiLiteralUtil._2_IN_63) && - parent instanceof PsiPrefixExpression && - ((PsiPrefixExpression) parent).getOperationTokenType() == JavaTokenType.MINUS)) { - if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { - String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { - String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_63)) { - String message = JavaErrorBundle.message("long.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } else if (isFP) { - if (value == null) { - String message = JavaErrorBundle.message("malformed.floating.point.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } else if (type == JavaTokenType.CHARACTER_LITERAL) { - if (value == null) { - if (!StringUtil.startsWithChar(text, '\'')) { - return null; - } - if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) { - String message = JavaErrorBundle.message("unclosed.char.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - text = text.substring(1, text.length() - 1); - - CharSequence chars = CodeInsightUtilCore.parseStringCharacters(text, null); - if (chars == null) { - String message = JavaErrorBundle.message("illegal.escape.character.in.character.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - int length = chars.length(); - if (length > 1) { - String message = JavaErrorBundle.message("too.many.characters.in.character.literal"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertToStringLiteralAction()); - return info; - } else if (length == 0) { - String message = JavaErrorBundle.message("empty.character.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) { - if (type == JavaTokenType.STRING_LITERAL) { - if (value == null) { - for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) { - if (element instanceof OuterLanguageElement) { - return null; - } - } - - if (!StringUtil.startsWithChar(text, '\"')) { - return null; - } - if (StringUtil.endsWithChar(text, '\"')) { - if (text.length() == 1) { - String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - text = text.substring(1, text.length() - 1); - } else { - String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - - if (CodeInsightUtilCore.parseStringCharacters(text, null) == null) { - String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } else { - if (value == null) { - if (!text.endsWith("\"\"\"")) { - String message = JavaErrorBundle.message("text.block.unclosed"); - int p = expression.getTextRange().getEndOffset(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(p, p).endOfLine().descriptionAndTooltip(message).create(); - } else { - StringBuilder chars = new StringBuilder(text.length()); - int[] offsets = new int[text.length() + 1]; - boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets); - if (!success) { - String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); - TextRange textRange = chars.length() < text.length() - 1 ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1]) - : expression.getTextRange(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression, textRange) - .descriptionAndTooltip(message).create(); - } else { - String message = JavaErrorBundle.message("text.block.new.line"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } else { - if (file != null && containsUnescaped(text, "\\\n")) { - HighlightInfo info = checkFeature(expression, Feature.TEXT_BLOCK_ESCAPES, level, file); - if (info != null) { - return info; - } - } - } - } - if (file != null && containsUnescaped(text, "\\s")) { - HighlightInfo info = checkFeature(expression, Feature.TEXT_BLOCK_ESCAPES, level, file); - if (info != null) { - return info; - } - } - } - - if (value instanceof Float) { - Float number = (Float) value; - if (number.isInfinite()) { - String message = JavaErrorBundle.message("floating.point.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (number.floatValue() == 0 && !TypeConversionUtil.isFPZero(text)) { - String message = JavaErrorBundle.message("floating.point.number.too.small"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } else if (value instanceof Double) { - Double number = (Double) value; - if (number.isInfinite()) { - String message = JavaErrorBundle.message("floating.point.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (number.doubleValue() == 0 && !TypeConversionUtil.isFPZero(text)) { - String message = JavaErrorBundle.message("floating.point.number.too.small"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - - return null; - } - - private static final Pattern FP_LITERAL_PARTS = Pattern.compile("(?:" + "(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" + "|" + "(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))" + ")" + - "[fd]?"); - - private static boolean containsUnescaped(@Nonnull String text, @Nonnull String subText) { - int start = 0; - while ((start = StringUtil.indexOf(text, subText, start)) != -1) { - int nSlashes = 0; - for (int pos = start - 1; pos >= 0; pos--) { - if (text.charAt(pos) != '\\') { - break; - } - nSlashes++; - } - if (nSlashes % 2 == 0) { - return true; - } - start += subText.length(); - } - return false; - } - - private static HighlightInfo checkUnderscores(@Nonnull PsiElement expression, @Nonnull String text, boolean isInt) { - String[] parts = ArrayUtil.EMPTY_STRING_ARRAY; - - if (isInt) { - int start = 0; - if (text.startsWith(PsiLiteralUtil.HEX_PREFIX) || text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { - start += 2; - } - int end = text.length(); - if (StringUtil.endsWithChar(text, 'l')) { - --end; - } - parts = new String[]{text.substring(start, end)}; - } else { - Matcher matcher = FP_LITERAL_PARTS.matcher(text); - if (matcher.matches()) { - parts = new String[matcher.groupCount()]; - for (int i = 0; i < matcher.groupCount(); i++) { - parts[i] = matcher.group(i + 1); - } - } - } - - for (String part : parts) { - if (part != null && (StringUtil.startsWithChar(part, '_') || StringUtil.endsWithChar(part, '_'))) { - String message = JavaErrorBundle.message("illegal.underscore"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkMustBeBoolean(@Nonnull PsiExpression expr, PsiType type) { - PsiElement parent = expr.getParent(); - if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiForStatement && expr.equals(((PsiForStatement) parent).getCondition()) || parent instanceof - PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement) parent).getCondition())) { - if (expr.getNextSibling() instanceof PsiErrorElement) { - return null; - } - - if (!TypeConversionUtil.isBooleanType(type)) { - final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange(), 0); - if (expr instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expr; - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && PsiType.VOID.equals(method.getReturnType())) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.BOOLEAN, true)); - } - } else if (expr instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) expr).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAssignmentToComparisonFix((PsiAssignmentExpression) expr)); + else if (qualifierExpression instanceof PsiSuperExpression superExpr) { + qualifier = superExpr.getQualifier(); } - return info; - } - } - return null; - } - - - @Nonnull - public static Set collectUnhandledExceptions(@Nonnull final PsiTryStatement statement) { - final Set thrownTypes = new HashSet<>(); - - final PsiCodeBlock tryBlock = statement.getTryBlock(); - if (tryBlock != null) { - thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock)); + else { + return false; + } + //noinspection SimplifiableIfStatement + if (qualifier == null) { + return true; + } + return qualifier.resolve() instanceof PsiClass psiClass && InheritanceUtil.isInheritorOrSelf(aClass, psiClass, true); } - final PsiResourceList resources = statement.getResourceList(); - if (resources != null) { - thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(resources, resources)); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkLabelWithoutStatement(PsiLabeledStatement statement) { + if (statement.getStatement() == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.labelWithoutStatement()); + } + return null; } - return thrownTypes; - } - - @Nonnull - public static List checkExceptionThrownInTry(@Nonnull final PsiParameter parameter, @Nonnull final Set thrownTypes) { - final PsiElement declarationScope = parameter.getDeclarationScope(); - if (!(declarationScope instanceof PsiCatchSection)) { - return Collections.emptyList(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkLabelAlreadyInUse(PsiLabeledStatement statement) { + PsiIdentifier identifier = statement.getLabelIdentifier(); + String text = identifier.getText(); + PsiElement element = statement; + while (element != null) { + if (element instanceof PsiMethod || element instanceof PsiClass) { + break; + } + if (element instanceof PsiLabeledStatement labeledStmt + && labeledStmt != statement + && Objects.equals(labeledStmt.getLabelIdentifier().getText(), text)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.labelDuplicate(text)); + } + element = element.getParent(); + } + return null; } - final PsiType caughtType = parameter.getType(); - if (caughtType instanceof PsiClassType) { - HighlightInfo info = checkSimpleCatchParameter(parameter, thrownTypes, (PsiClassType) caughtType); - return info == null ? Collections.emptyList() : Collections.singletonList(info); - } - if (caughtType instanceof PsiDisjunctionType) { - return checkMultiCatchParameter(parameter, thrownTypes); + @Nullable + @RequiredReadAction + public static HighlightInfo checkUnclosedComment(PsiComment comment) { + if (!(comment instanceof PsiDocComment) && comment.getTokenType() != JavaTokenType.C_STYLE_COMMENT) { + return null; + } + if (!comment.getText().endsWith("*/")) { + int start = comment.getTextRange().getEndOffset() - 1; + int end = start + 1; + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(start, end) + .descriptionAndTooltip(JavaCompilationErrorLocalize.commentUnclosed()) + .create(); + } + return null; } - return Collections.emptyList(); - } + @RequiredReadAction + public static Collection checkCatchTypeIsDisjoint(PsiParameter parameter) { + if (!(parameter.getType() instanceof PsiDisjunctionType)) { + return Collections.emptyList(); + } - @Nullable - private static HighlightInfo checkSimpleCatchParameter(@Nonnull final PsiParameter parameter, @Nonnull final Collection thrownTypes, @Nonnull final PsiClassType caughtType) { - if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtType)) { - return null; - } + Collection result = new ArrayList<>(); + List typeElements = PsiUtil.getParameterTypeElements(parameter); + for (int i = 0, size = typeElements.size(); i < size; i++) { + PsiClass class1 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(i).getType()); + if (class1 == null) { + continue; + } + for (int j = i + 1; j < size; j++) { + PsiClass class2 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(j).getType()); + if (class2 == null) { + continue; + } + boolean sub = InheritanceUtil.isInheritorOrSelf(class1, class2, true); + boolean sup = InheritanceUtil.isInheritorOrSelf(class2, class1, true); + if (sub || sup) { + String name1 = PsiFormatUtil.formatClass(class1, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + String name2 = PsiFormatUtil.formatClass(class2, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + PsiTypeElement element = typeElements.get(sub ? i : j); + HighlightInfo highlight = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(JavaCompilationErrorLocalize.exceptionMustBeDisjoint(sub ? name1 : name2, sub ? name2 : name1)) + .registerFix(QuickFixFactory.getInstance().createDeleteMultiCatchFix(element)) + .create(); + result.add(highlight); + break; + } + } + } - for (PsiClassType exceptionType : thrownTypes) { - if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) { - return null; - } - } - - final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(caughtType)); - final HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); - return errorResult; - } - - @Nonnull - private static List checkMultiCatchParameter(@Nonnull final PsiParameter parameter, @Nonnull final Collection thrownTypes) { - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - final List highlights = new ArrayList<>(typeElements.size()); - - for (final PsiTypeElement typeElement : typeElements) { - final PsiType catchType = typeElement.getType(); - if (catchType instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) catchType)) { - continue; - } - - boolean used = false; - for (PsiClassType exceptionType : thrownTypes) { - if (exceptionType.isAssignableFrom(catchType) || catchType.isAssignableFrom(exceptionType)) { - used = true; - break; - } - } - if (!used) { - final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(catchType)); - final HighlightInfo highlight = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlight, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(typeElement)); - highlights.add(highlight); - } - } - - return highlights; - } - - - @Nonnull - public static Collection checkWithImprovedCatchAnalysis(@Nonnull PsiParameter parameter, @Nonnull Collection thrownInTryStatement, @Nonnull PsiFile containingFile) { - final PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiCatchSection)) { - return Collections.emptyList(); - } - - final PsiCatchSection catchSection = (PsiCatchSection) scope; - final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); - final int idx = ArrayUtil.find(allCatchSections, catchSection); - if (idx <= 0) { - return Collections.emptyList(); - } - - final Collection thrownTypes = new HashSet<>(thrownInTryStatement); - final PsiManager manager = containingFile.getManager(); - final GlobalSearchScope parameterResolveScope = parameter.getResolveScope(); - thrownTypes.add(PsiType.getJavaLangError(manager, parameterResolveScope)); - thrownTypes.add(PsiType.getJavaLangRuntimeException(manager, parameterResolveScope)); - final Collection result = ContainerUtil.newArrayList(); - - final List parameterTypeElements = PsiUtil.getParameterTypeElements(parameter); - final boolean isMultiCatch = parameterTypeElements.size() > 1; - for (PsiTypeElement catchTypeElement : parameterTypeElements) { - final PsiType catchType = catchTypeElement.getType(); - if (ExceptionUtil.isGeneralExceptionType(catchType)) { - continue; - } - - // collect exceptions which are caught by this type - Collection caught = ContainerUtil.findAll(thrownTypes, catchType::isAssignableFrom); - if (caught.isEmpty()) { - continue; - } - final Collection caughtCopy = new HashSet<>(caught); - - // exclude all which are caught by previous catch sections - for (int i = 0; i < idx; i++) { - final PsiParameter prevCatchParameter = allCatchSections[i].getParameter(); - if (prevCatchParameter == null) { - continue; - } - for (PsiTypeElement prevCatchTypeElement : PsiUtil.getParameterTypeElements(prevCatchParameter)) { - final PsiType prevCatchType = prevCatchTypeElement.getType(); - caught.removeIf(prevCatchType::isAssignableFrom); - if (caught.isEmpty()) { - break; - } - } - } - - // check & warn - if (caught.isEmpty()) { - final String message = JavaErrorBundle.message("exception.already.caught.warn", formatTypes(caughtCopy), caughtCopy.size()); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(catchSection).descriptionAndTooltip(message).create(); - if (isMultiCatch) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(catchTypeElement)); - } else { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); - } - result.add(highlightInfo); - } - } - - return result; - } - - - @Nullable - public static HighlightInfo checkNotAStatement(@Nonnull PsiStatement statement) { - if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) { - boolean isDeclarationNotAllowed = false; - if (statement instanceof PsiDeclarationStatement) { - final PsiElement parent = statement.getParent(); - isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement; - } - - String description = JavaErrorBundle.message(isDeclarationNotAllowed ? "declaration.not.allowed" : "not.a.statement"); - HighlightInfo error = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - if (statement instanceof PsiExpressionStatement) { - QuickFixAction.registerQuickFixAction(error, QuickFixFactory.getInstance().createDeleteSideEffectAwareFix((PsiExpressionStatement) statement)); - } - return error; - } - return null; - } - - @Nullable - public static HighlightInfo checkSwitchBlockStatements(@Nonnull PsiSwitchBlock switchBlock, - @Nonnull LanguageLevel languageLevel, - @Nonnull PsiFile file) { - PsiCodeBlock body = switchBlock.getBody(); - if (body != null) { - PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); - if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); - } - - PsiElement element = first; - PsiStatement alien = null; - boolean classicLabels = false; - boolean enhancedLabels = false; - boolean levelChecked = false; - while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { - if (element instanceof PsiSwitchLabeledRuleStatement) { - if (!levelChecked) { - HighlightInfo info = checkFeature(element, Feature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) { - return info; - } - levelChecked = true; - } - if (classicLabels) { - alien = (PsiStatement) element; - break; - } - enhancedLabels = true; - } else if (element instanceof PsiStatement) { - if (enhancedLabels) { - alien = (PsiStatement) element; - break; - } - classicLabels = true; - } - - if (!levelChecked && element instanceof PsiSwitchLabelStatementBase) { - PsiExpressionList values = ((PsiSwitchLabelStatementBase) element).getCaseValues(); - if (values != null && values.getExpressionCount() > 1) { - HighlightInfo info = checkFeature(values, Feature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) { - return info; - } - levelChecked = true; - } - } - - element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); - } - if (alien != null) { - if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { - PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - if (previousRule != null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); - } - return info; - } - String description = JavaErrorBundle.message("different.case.kinds.in.switch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkSwitchSelectorType(@Nonnull PsiSwitchBlock switchBlock, @Nonnull LanguageLevel level) { - PsiExpression expression = switchBlock.getExpression(); - if (expression == null) { - return null; - } - PsiType type = expression.getType(); - if (type == null) { - return null; - } - - SelectorKind kind = getSwitchSelectorKind(type); - if (kind == SelectorKind.INT) { - return null; - } - - LanguageLevel requiredLevel = null; - if (kind == SelectorKind.ENUM) { - requiredLevel = LanguageLevel.JDK_1_5; - } - if (kind == SelectorKind.STRING) { - requiredLevel = LanguageLevel.JDK_1_7; - } - - if (kind == null || requiredLevel != null && !level.isAtLeast(requiredLevel)) { - boolean is7 = level.isAtLeast(LanguageLevel.JDK_1_7); - String expected = JavaErrorBundle.message(is7 ? "valid.switch.17.selector.types" : "valid.switch.selector.types"); - String message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(type)); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - if (switchBlock instanceof PsiSwitchStatement) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertSwitchToIfIntention((PsiSwitchStatement) switchBlock)); - } - if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddTypeCastFix(PsiType.INT, expression)); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithAdapterFix(PsiType.INT, expression)); - } - if (requiredLevel != null) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createIncreaseLanguageLevelFix(requiredLevel)); - } - return info; - } - - PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); - if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { - String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - String message = JavaErrorBundle.message("inaccessible.type", className); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - - return null; - } - - private enum SelectorKind { - INT, - ENUM, - STRING - } - - private static SelectorKind getSwitchSelectorKind(@Nonnull PsiType type) { - if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) { - return SelectorKind.INT; - } - - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (psiClass != null) { - if (psiClass.isEnum()) { - return SelectorKind.ENUM; - } - if (Comparing.strEqual(psiClass.getQualifiedName(), JavaClassNames.JAVA_LANG_STRING)) { - return SelectorKind.STRING; - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkPolyadicOperatorApplicable(@Nonnull PsiPolyadicExpression expression) { - PsiExpression[] operands = expression.getOperands(); - - PsiType lType = operands[0].getType(); - IElementType operationSign = expression.getOperationTokenType(); - for (int i = 1; i < operands.length; i++) { - PsiExpression operand = operands[i]; - PsiType rType = operand.getType(); - if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign, lType, rType, false)) { - PsiJavaToken token = expression.getTokenBeforeOperand(operand); - String message = JavaErrorBundle.message("binary.operator.not.applicable", token.getText(), JavaHighlightUtil.formatType(lType), JavaHighlightUtil.formatType(rType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, operationSign, true); + return result; } - return null; - } - + @RequiredReadAction + public static Collection checkExceptionAlreadyCaught(PsiParameter parameter) { + PsiElement scope = parameter.getDeclarationScope(); + if (!(scope instanceof PsiCatchSection catchSection)) { + return Collections.emptyList(); + } - @Nullable - public static HighlightInfo checkUnaryOperatorApplicable(@Nullable PsiJavaToken token, @Nullable PsiExpression expression) { - if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { - PsiType type = expression.getType(); - if (type == null) { - return null; - } - String message = JavaErrorBundle.message("unary.operator.not.applicable", token.getText(), JavaHighlightUtil.formatType(type)); - - PsiElement parentExpr = token.getParent(); - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parentExpr).descriptionAndTooltip(message).create(); - if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createNegationBroadScopeFix((PsiPrefixExpression) parentExpr)); - } - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkThisOrSuperExpressionInIllegalContext(@Nonnull PsiExpression expr, @Nullable PsiJavaCodeReferenceElement qualifier, @Nonnull LanguageLevel languageLevel) { - if (expr instanceof PsiSuperExpression) { - final PsiElement parent = expr.getParent(); - if (!(parent instanceof PsiReferenceExpression)) { - // like in 'Object o = super;' - final int o = expr.getTextRange().getEndOffset(); - String description = JavaErrorBundle.message("dot.expected.after.super.or.this"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(o, o + 1).descriptionAndTooltip(description).create(); - } - } - - PsiClass aClass; - if (qualifier != null) { - PsiElement resolved = qualifier.advancedResolve(true).getElement(); - if (resolved != null && !(resolved instanceof PsiClass)) { - String description = JavaErrorBundle.message("class.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); - } - aClass = (PsiClass) resolved; - } else { - aClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); - if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass) aClass).getArgumentList(), expr, false)) { - aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); - } - } - if (aClass == null) { - return null; - } - - if (!InheritanceUtil.hasEnclosingInstanceInScope(aClass, expr, false, false)) { - if (!resolvesToImmediateSuperInterface(expr, qualifier, aClass, languageLevel)) { - return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); - } - - if (expr instanceof PsiSuperExpression) { - final PsiElement resolved = ((PsiReferenceExpression) expr.getParent()).resolve(); - //15.11.2 - //The form T.super.Identifier refers to the field named Identifier of the lexically enclosing instance corresponding to T, - //but with that instance viewed as an instance of the superclass of T. - if (resolved instanceof PsiField) { - String description = JavaErrorBundle.message("is.not.an.enclosing.class", formatClass(aClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); - } - } - } - - if (qualifier != null && aClass.isInterface() && expr instanceof PsiSuperExpression && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - //15.12.1 for method invocation expressions; 15.13 for method references - //If TypeName denotes an interface, I, then let T be the type declaration immediately enclosing the method reference expression. - //It is a compile-time error if I is not a direct superinterface of T, - //or if there exists some other direct superclass or direct superinterface of T, J, such that J is a subtype of I. - final PsiClass classT = PsiTreeUtil.getParentOfType(expr, PsiClass.class); - if (classT != null) { - final PsiElement parent = expr.getParent(); - final PsiElement resolved = parent instanceof PsiReferenceExpression ? ((PsiReferenceExpression) parent).resolve() : null; - - PsiClass containingClass = ObjectUtil.notNull(resolved instanceof PsiMethod ? ((PsiMethod) resolved).getContainingClass() : null, aClass); - for (PsiClass superClass : classT.getSupers()) { - if (superClass.isInheritor(containingClass, true)) { - String cause = null; - if (superClass.isInheritor(aClass, true) && superClass.isInterface()) { - cause = "redundant interface " + format(containingClass) + " is extended by "; - } else if (resolved instanceof PsiMethod && MethodSignatureUtil.findMethodBySuperMethod(superClass, (PsiMethod) resolved, true) != resolved) { - cause = "method " + ((PsiMethod) resolved).getName() + " is overridden in "; - } - - if (cause != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(JavaErrorBundle.message("bad.qualifier.in.super.method.reference", - cause + formatClass(superClass))).create(); - } - } - } - - if (!classT.isInheritor(aClass, false)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(JavaErrorBundle.message("no.enclosing.instance.in.scope", format(aClass))) - .create(); - } - } - } - - if (expr instanceof PsiThisExpression) { - final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); - if (psiMethod == null || psiMethod.getContainingClass() != aClass && !isInsideDefaultMethod(psiMethod, aClass)) { - if (aClass.isInterface()) { - return thisNotFoundInInterfaceInfo(expr); - } - - if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass) aClass).getArgumentList(), expr, true)) { - final PsiClass parentClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); - if (parentClass != null && parentClass.isInterface()) { - return thisNotFoundInInterfaceInfo(expr); - } - } - } - } - return null; - } - - public static HighlightInfo checkUnqualifiedSuperInDefaultMethod(@Nonnull LanguageLevel languageLevel, @Nonnull PsiReferenceExpression expr, PsiExpression qualifier) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && qualifier instanceof PsiSuperExpression) { - final PsiMethod method = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); - if (method != null && method.hasModifierProperty(PsiModifier.DEFAULT) && ((PsiSuperExpression) qualifier).getQualifier() == null) { - String description = JavaErrorBundle.message("unqualified.super.disallowed"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); - QualifySuperArgumentFix.registerQuickFixAction((PsiSuperExpression) qualifier, info); - return info; - } - } - return null; - } - - private static boolean isInsideDefaultMethod(PsiMethod method, PsiClass aClass) { - while (method != null && method.getContainingClass() != aClass) { - method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); - } - return method != null && method.hasModifierProperty(PsiModifier.DEFAULT); - } - - private static HighlightInfo thisNotFoundInInterfaceInfo(@Nonnull PsiExpression expr) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip("Cannot find symbol variable this").create(); - } - - private static boolean resolvesToImmediateSuperInterface(@Nonnull PsiExpression expr, - @Nullable PsiJavaCodeReferenceElement qualifier, - @Nonnull PsiClass aClass, - @Nonnull LanguageLevel languageLevel) { - if (!(expr instanceof PsiSuperExpression) || qualifier == null || !languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - return false; - } - final PsiType superType = expr.getType(); - if (!(superType instanceof PsiClassType)) { - return false; - } - final PsiClass superClass = ((PsiClassType) superType).resolve(); - return superClass != null && aClass.equals(superClass) && PsiUtil.getEnclosingStaticElement(expr, PsiTreeUtil.getParentOfType(expr, PsiClass.class)) == null; - } - - @Nonnull - public static String buildProblemWithStaticDescription(@Nonnull PsiElement refElement) { - String type = FindUsagesProvider.forLanguage(JavaLanguage.INSTANCE).getType(refElement); - String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); - return JavaErrorBundle.message("non.static.symbol.referenced.from.static.context", type, name); - } - - public static void registerStaticProblemQuickFixAction(@Nonnull PsiElement refElement, HighlightInfo errorResult, @Nonnull PsiJavaCodeReferenceElement place) { - if (refElement instanceof PsiModifierListOwner) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix((PsiModifierListOwner) refElement, PsiModifier.STATIC, true, false)); - } - // make context non static - PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); - if (staticParent != null && isInstanceReference(place)) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); - } - if (place instanceof PsiReferenceExpression && refElement instanceof PsiField) { - QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createCreateFieldFromUsageFix((PsiReferenceExpression) place)); - } - } - - private static boolean isInstanceReference(@Nonnull PsiJavaCodeReferenceElement place) { - PsiElement qualifier = place.getQualifier(); - if (qualifier == null) { - return true; - } - if (!(qualifier instanceof PsiJavaCodeReferenceElement)) { - return false; - } - PsiElement q = ((PsiReference) qualifier).resolve(); - if (q instanceof PsiClass) { - return false; - } - if (q != null) { - return true; - } - String qname = ((PsiJavaCodeReferenceElement) qualifier).getQualifiedName(); - return qname == null || !Character.isLowerCase(qname.charAt(0)); - } - - @Nonnull - public static String buildProblemWithAccessDescription(@Nonnull final PsiElement reference, @Nonnull final JavaResolveResult result) { - return buildProblemWithAccessDescription(reference, result, ObjectUtil.notNull(result.getElement())); - } - - @Nonnull - private static String buildProblemWithAccessDescription(@Nonnull final PsiElement reference, @Nonnull final JavaResolveResult result, @Nonnull final PsiElement resolved) { - assert resolved instanceof PsiModifierListOwner : resolved; - PsiModifierListOwner refElement = (PsiModifierListOwner) resolved; - String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - - if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("private.symbol", symbolName, containerName); - } else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("protected.symbol", symbolName, containerName); - } else { - PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); - if (packageLocalClass != null) { - refElement = packageLocalClass; - symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - } - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("package.local.symbol", symbolName, containerName); - } else { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("visibility.access.problem", symbolName, containerName); - } - } - } - - private static PsiElement getContainer(PsiModifierListOwner refElement) { - for (ContainerProvider provider : ContainerProvider.EP_NAME.getExtensions()) { - final PsiElement container = provider.getContainer(refElement); - if (container != null) { - return container; - } - } - return refElement.getParent(); - } - - private static String getContainerName(PsiModifierListOwner refElement, final PsiSubstitutor substitutor) { - final PsiElement container = getContainer(refElement); - return container == null ? "?" : HighlightMessageUtil.getSymbolName(container, substitutor); - } - - @Nullable - public static HighlightInfo checkValidArrayAccessExpression(@Nonnull PsiArrayAccessExpression arrayAccessExpression) { - final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); - final PsiType arrayExpressionType = arrayExpression.getType(); - - if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { - final String description = JavaErrorBundle.message("array.type.expected", JavaHighlightUtil.formatType(arrayExpressionType)); - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(arrayExpression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createReplaceWithListAccessFix(arrayAccessExpression)); - return info; - } - - final PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); - return indexExpression != null ? checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression) : null; - } - - - @Nullable - public static HighlightInfo checkCatchParameterIsThrowable(@Nonnull final PsiParameter parameter) { - if (parameter.getDeclarationScope() instanceof PsiCatchSection) { - final PsiType type = parameter.getType(); - return checkMustBeThrowable(type, parameter, true); - } - return null; - } - - @Nullable - public static HighlightInfo checkTryResourceIsAutoCloseable(@Nonnull PsiResourceListElement resource) { - PsiType type = resource.getType(); - if (type == null) { - return null; - } - - PsiElementFactory factory = JavaPsiFacade.getInstance(resource.getProject()).getElementFactory(); - PsiClassType autoCloseable = factory.createTypeByFQClassName(JavaClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope()); - if (TypeConversionUtil.isAssignable(autoCloseable, type)) { - return null; - } - - return createIncompatibleTypeHighlightInfo(autoCloseable, type, resource.getTextRange(), 0); - } - - @Nullable - public static HighlightInfo checkResourceVariableIsFinal(@Nonnull PsiResourceExpression resource) { - PsiExpression expression = resource.getExpression(); - - if (expression instanceof PsiThisExpression) { - return null; - } - - if (expression instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) expression).resolve(); - if (target == null) { - return null; - } + PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); + int startFrom = ArrayUtil.find(allCatchSections, catchSection) - 1; + if (startFrom < 0) { + return Collections.emptyList(); + } - if (target instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) target; + List typeElements = PsiUtil.getParameterTypeElements(parameter); + boolean isInMultiCatch = typeElements.size() > 1; + Collection result = new ArrayList<>(); - PsiModifierList modifierList = variable.getModifierList(); - if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } + for (PsiTypeElement typeElement : typeElements) { + PsiClass catchClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); + if (catchClass == null) { + continue; + } - if (!(variable instanceof PsiField) && HighlightControlFlowUtil.isEffectivelyFinal(variable, resource, (PsiJavaCodeReferenceElement) expression)) { - return null; + for (int i = startFrom; i >= 0; i--) { + PsiCatchSection upperCatchSection = allCatchSections[i]; + PsiType upperCatchType = upperCatchSection.getCatchType(); + + boolean highlight = upperCatchType instanceof PsiDisjunctionType disjunctionType + ? checkMultipleTypes(catchClass, disjunctionType.getDisjunctions()) + : checkSingleType(catchClass, upperCatchType); + if (highlight) { + String className = + PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.exceptionAlreadyCaught(className)) + .registerFix(QuickFixFactory.getInstance().createMoveCatchUpFix(catchSection, upperCatchSection)) + .registerFix( + isInMultiCatch + ? QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement) + : QuickFixFactory.getInstance().createDeleteCatchFix(parameter) + ) + .create(); + result.add(highlightInfo); + } + } } - } - String text = JavaErrorBundle.message("resource.variable.must.be.final"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); + return result; } - String text = JavaErrorBundle.message("declaration.or.variable.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); - } - - @Nonnull - public static Collection checkArrayInitializer(final PsiExpression initializer, PsiType type) { - if (!(initializer instanceof PsiArrayInitializerExpression)) { - return Collections.emptyList(); - } - if (!(type instanceof PsiArrayType)) { - return Collections.emptyList(); + private static boolean checkMultipleTypes(PsiClass catchClass, List upperCatchTypes) { + for (int i = upperCatchTypes.size() - 1; i >= 0; i--) { + if (checkSingleType(catchClass, upperCatchTypes.get(i))) { + return true; + } + } + return false; } - final PsiType componentType = ((PsiArrayType) type).getComponentType(); - final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression) initializer; - - boolean arrayTypeFixChecked = false; - VariableArrayTypeFix fix = null; - - final Collection result = ContainerUtil.newArrayList(); - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - for (PsiExpression expression : initializers) { - final HighlightInfo info = checkArrayInitializerCompatibleTypes(expression, componentType); - if (info != null) { - result.add(info); + private static boolean checkSingleType(PsiClass catchClass, PsiType upperCatchType) { + PsiClass upperCatchClass = PsiUtil.resolveClassInType(upperCatchType); + return upperCatchClass != null && InheritanceUtil.isInheritorOrSelf(catchClass, upperCatchClass, true); + } - if (!arrayTypeFixChecked) { - final PsiType checkResult = JavaHighlightUtil.sameType(initializers); - fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; - arrayTypeFixChecked = true; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkTernaryOperatorConditionIsBoolean(PsiExpression expression, PsiType type) { + if (expression.getParent() instanceof PsiConditionalExpression condExpr && condExpr.getCondition() == expression + && !TypeConversionUtil.isBooleanType(type)) { + return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); } - if (fix != null) { - QuickFixAction.registerQuickFixAction(info, new LocalQuickFixOnPsiElementAsIntentionAdapter(fix)); - } - } + return null; } - return result; - } - @Nullable - private static HighlightInfo checkArrayInitializerCompatibleTypes(@Nonnull PsiExpression initializer, final PsiType componentType) { - PsiType initializerType = initializer.getType(); - if (initializerType == null) { - String description = JavaErrorBundle.message("illegal.initializer", JavaHighlightUtil.formatType(componentType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); - } - PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; - return checkAssignability(componentType, initializerType, expression, initializer); - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(PsiSwitchStatement statement) { + PsiCodeBlock body = statement.getBody(); + if (body != null) { + PsiElement first = PsiTreeUtil.skipSiblingsForward(body.getLBrace(), PsiWhiteSpace.class, PsiComment.class); + if (first != null && !(first instanceof PsiSwitchLabelStatement) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(first) + .descriptionAndTooltip(JavaErrorLocalize.statementMustBePrependedWithCaseLabel()) + .create(); + } + } - @Nullable - public static HighlightInfo checkExpressionRequired(@Nonnull PsiReferenceExpression expression, @Nonnull JavaResolveResult resultForIncompleteCode) { - if (expression.getNextSibling() instanceof PsiErrorElement) { - return null; + return null; } - PsiElement resolved = resultForIncompleteCode.getElement(); - if (resolved == null || resolved instanceof PsiVariable) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkAssertOperatorTypes(PsiExpression expression, @Nullable PsiType type) { + if (type == null) { + return null; + } + if (!(expression.getParent() instanceof PsiAssertStatement assertStatement)) { + return null; + } + if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { + // addTypeCast quickfix is not applicable here since no type can be cast to boolean + HighlightInfo.Builder hlBuilder = + createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); + if (expression instanceof PsiAssignmentExpression assignment && assignment.getOperationTokenType() == JavaTokenType.EQ) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createAssignmentToComparisonFix(assignment)); + } + return hlBuilder; + } + if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.typeVoidNotAllowed()); + } + return null; } - PsiElement parent = expression.getParent(); - // String.class or String() are both correct - if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) { - return null; + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSynchronizedExpressionType( + PsiExpression expression, + @Nullable PsiType type, + PsiFile containingFile + ) { + if (type == null) { + return null; + } + if (expression.getParent() instanceof PsiSynchronizedStatement syncStmt + && expression == syncStmt.getLockExpression() && (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { + PsiClassType objectType = PsiType.getJavaLangObject(containingFile.getManager(), expression.getResolveScope()); + return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange(), 0); + } + return null; } - String description = JavaErrorBundle.message("expression.expected"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(expression, QuickFixActionRegistrar.create(info)); - return info; - } - - @Nullable - public static HighlightInfo checkArrayInitializerApplicable(@Nonnull PsiArrayInitializerExpression expression) { - /* - JLS 10.6 Array Initializers - An array initializer may be specified in a declaration, or as part of an array creation expression - */ - PsiElement parent = expression.getParent(); - if (parent instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) parent; - if (variable.getType() instanceof PsiArrayType) { - return null; - } - } else if (parent instanceof PsiNewExpression || parent instanceof PsiArrayInitializerExpression) { - return null; - } - - String description = JavaErrorBundle.message("array.initializer.not.allowed"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddNewArrayExpressionFix(expression)); - return info; - } - - - @Nullable - public static HighlightInfo checkCaseStatement(@Nonnull PsiSwitchLabelStatementBase statement) { - PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); - if (switchBlock == null) { - String description = JavaErrorBundle.message("case.statement.outside.switch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - - return null; - } - - @Nonnull - public static Collection checkSwitchLabelValues(@Nonnull PsiSwitchBlock switchBlock) { - PsiCodeBlock body = switchBlock.getBody(); - if (body == null) { - return Collections.emptyList(); - } - - PsiExpression selectorExpression = switchBlock.getExpression(); - PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType(); - MultiMap values = new MultiMap<>(); - Object defaultValue = new Object(); - Collection results = new ArrayList<>(); - boolean hasDefaultCase = false; - - for (PsiStatement st : body.getStatements()) { - if (!(st instanceof PsiSwitchLabelStatementBase)) { - continue; - } - PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase) st; - boolean defaultCase = labelStatement.isDefaultCase(); - - if (defaultCase) { - values.putValue(defaultValue, ObjectUtil.notNull(labelStatement.getFirstChild(), labelStatement)); - hasDefaultCase = true; - } else { - PsiExpressionList expressionList = labelStatement.getCaseValues(); - if (expressionList != null) { - for (PsiExpression expr : expressionList.getExpressions()) { - if (selectorExpression != null) { - HighlightInfo result = checkAssignability(selectorType, expr.getType(), expr, expr); - if (result != null) { - results.add(result); - continue; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkConditionalExpressionBranchTypesMatch(PsiExpression expression, PsiType type) { + if (!(expression.getParent() instanceof PsiConditionalExpression conditional)) { + return null; + } + // check else branches only + if (conditional.getElseExpression() != expression) { + return null; + } + PsiExpression thenExpression = conditional.getThenExpression(); + assert thenExpression != null; + PsiType thenType = thenExpression.getType(); + if (thenType == null || type == null) { + return null; + } + if (conditional.getType() == null) { + if (PsiUtil.isLanguageLevel8OrHigher(conditional) && PsiPolyExpressionUtil.isPolyExpression(conditional)) { + return null; } + // cannot derive type of conditional expression + // elseType will never be cast-able to thenType, so no quick fix here + return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange(), 0); + } + return null; + } - Object value = null; - if (expr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression) expr).resolve(); - if (element instanceof PsiEnumConstant) { - value = ((PsiEnumConstant) element).getName(); - if (((PsiReferenceExpression) expr).getQualifier() != null) { - String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(message).create()); - continue; - } - } + @SuppressWarnings("StringContatenationInLoop") + public static HighlightInfo.Builder createIncompatibleTypeHighlightInfo( + PsiType lType, + PsiType rType, + TextRange textRange, + int navigationShift + ) { + PsiType lType1 = lType; + PsiType rType1 = rType; + PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; + if (lType1 instanceof PsiClassType lClassType) { + PsiClassType.ClassResolveResult resolveResult = lClassType.resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass anonymousClass) { + lType1 = anonymousClass.getBaseClassType(); + resolveResult = ((PsiClassType) lType1).resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); } - if (value == null) { - value = ConstantExpressionUtil.computeCastTo(expr, selectorType); + lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; + if (rType1 instanceof PsiClassType rClassType) { + PsiClassType.ClassResolveResult resolveResult = rClassType.resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass anonymousClass) { + rType1 = anonymousClass.getBaseClassType(); + resolveResult = ((PsiClassType) rType1).resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); } - if (value == null) { - String description = JavaErrorBundle.message("constant.expression.required"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create()); - continue; + rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + + int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); + @Language("HTML") String requiredRow = ""; + @Language("HTML") String foundRow = ""; + for (int i = 0; i < typeParamColumns; i++) { + PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; + PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; + PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); + PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); + boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); + String openBrace = i == 0 ? "<" : ""; + String closeBrace = i == typeParamColumns - 1 ? ">" : ","; + requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + + redIfNotMatch(lSubstitutedType, matches) + + (i < lTypeParams.length ? closeBrace : "") + ""; + foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + + redIfNotMatch(rSubstitutedType, matches) + + (i < rTypeParams.length ? closeBrace : "") + ""; + } + PsiType lRawType = lType1 instanceof PsiClassType lClassType1 ? lClassType1.rawType() : lType1; + PsiType rRawType = rType1 instanceof PsiClassType rClassType1 ? rClassType1.rawType() : rType1; + boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); + + LocalizeValue toolTip = JavaErrorLocalize.incompatibleTypesHtmlTooltip( + redIfNotMatch(lRawType, assignable), + requiredRow, + redIfNotMatch(rRawType, assignable), + foundRow + ); + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .description(JavaCompilationErrorLocalize.typeIncompatible(JavaHighlightUtil.formatType(lType1), JavaHighlightUtil.formatType(rType1))) + .escapedToolTip(toolTip) + .navigationShift(navigationShift); + } + + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkSingleImportClassConflict( + PsiImportStatement statement, + Map> importedClasses, + PsiFile containingFile + ) { + if (statement.isOnDemand()) { + return null; + } + if (statement.resolve() instanceof PsiClass psiClass) { + String name = psiClass.getName(); + Pair imported = importedClasses.get(name); + PsiClass importedClass = imported == null ? null : imported.getSecond(); + if (importedClass != null && !containingFile.getManager().areElementsEquivalent(importedClass, psiClass)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaErrorLocalize.singleImportClassConflict(formatClass(importedClass))); } - - values.putValue(value, expr); - } - } - } - } - - for (Map.Entry> entry : values.entrySet()) { - if (entry.getValue().size() > 1) { - Object value = entry.getKey(); - String description = value == defaultValue ? JavaErrorBundle.message("duplicate.default.switch.label") : JavaErrorBundle.message("duplicate.switch.label", value); - for (PsiElement element : entry.getValue()) { - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create()); - } - } - } - - if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) { - Set missingConstants = new HashSet<>(); - boolean exhaustive = hasDefaultCase; - if (!exhaustive) { - if (!values.isEmpty() && selectorType instanceof PsiClassType) { - PsiClass type = ((PsiClassType) selectorType).resolve(); - if (type != null && type.isEnum()) { - for (PsiField field : type.getFields()) { - if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { - missingConstants.add(field.getName()); - } - } - exhaustive = missingConstants.isEmpty(); - } - } - } - if (!exhaustive) { - PsiElement range = ObjectUtil.notNull(selectorExpression, switchBlock); - String message = JavaErrorBundle.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); - if (!missingConstants.isEmpty()) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddMissingEnumBranchesFix(switchBlock, missingConstants)); - } - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddSwitchDefaultFix(switchBlock, null)); - results.add(info); - } - } - - return results; - } - - - /** - * see JLS 8.3.2.3 - */ - @Nullable - public static HighlightInfo checkIllegalForwardReferenceToField(@Nonnull PsiReferenceExpression expression, @Nonnull PsiField referencedField) { - Boolean isIllegalForwardReference = isIllegalForwardReferenceToField(expression, referencedField, false); - if (isIllegalForwardReference == null) { - return null; - } - String description = JavaErrorBundle.message(isIllegalForwardReference ? "illegal.forward.reference" : "illegal.self.reference"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - } - - public static Boolean isIllegalForwardReferenceToField(@Nonnull PsiReferenceExpression expression, @Nonnull PsiField referencedField, boolean acceptQualified) { - PsiClass containingClass = referencedField.getContainingClass(); - if (containingClass == null) { - return null; - } - if (expression.getContainingFile() != referencedField.getContainingFile()) { - return null; - } - if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) { - return null; - } - // only simple reference can be illegal - if (!acceptQualified && expression.getQualifierExpression() != null) { - return null; - } - PsiField initField = findEnclosingFieldInitializer(expression); - PsiClassInitializer classInitializer = findParentClassInitializer(expression); - if (initField == null && classInitializer == null) { - return null; - } - // instance initializers may access static fields - boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); - boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); - boolean inStaticContext = isStaticInitField || isStaticClassInitializer; - if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) { - return null; - } - if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { - return null; - } - return initField != referencedField; - } - - /** - * @return field that has initializer with this element as subexpression or null if not found - */ - @Nullable - public static PsiField findEnclosingFieldInitializer(@Nullable PsiElement element) { - while (element != null) { - PsiElement parent = element.getParent(); - if (parent instanceof PsiField) { - PsiField field = (PsiField) parent; - if (element == field.getInitializer()) { - return field; - } - if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant) field).getArgumentList()) { - return field; - } - } - if (element instanceof PsiClass || element instanceof PsiMethod) { - return null; - } - element = parent; - } - return null; - } - - @Nullable - private static PsiClassInitializer findParentClassInitializer(@Nullable PsiElement element) { - while (element != null) { - if (element instanceof PsiClassInitializer) { - return (PsiClassInitializer) element; - } - if (element instanceof PsiClass || element instanceof PsiMethod) { + importedClasses.put(name, Pair.create(null, psiClass)); + } return null; - } - element = element.getParent(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkIllegalType(@Nullable PsiTypeElement typeElement) { - if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) { - return null; } - if (PsiUtil.isInsideJavadocComment(typeElement)) { - return null; + private static String redIfNotMatch(PsiType type, boolean matches) { + if (matches) { + return getFQName(type, false); + } + String color = UIUtil.isUnderDarcula() ? "FF6B68" : "red"; + return "" + getFQName(type, true) + ""; } - PsiType type = typeElement.getType(); - PsiType componentType = type.getDeepComponentType(); - if (componentType instanceof PsiClassType) { - PsiClass aClass = PsiUtil.resolveClassInType(componentType); - if (aClass == null) { - String canonicalText = type.getCanonicalText(); - String description = JavaErrorBundle.message("unknown.class", canonicalText); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - - PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement(); - if (referenceElement != null && info != null) { - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(referenceElement, QuickFixActionRegistrar.create(info)); + private static String getFQName(@Nullable PsiType type, boolean longName) { + if (type == null) { + return ""; } - return info; - } + return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); } - return null; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkMustBeThrowable( + @Nullable PsiType type, + PsiElement context, + boolean addCastIntention + ) { + if (type == null) { + return null; + } + PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); + PsiClassType throwable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_THROWABLE, context.getResolveScope()); + if (!TypeConversionUtil.isAssignable(throwable, type)) { + HighlightInfo.Builder hlBuilder = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0); + if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable) + && context instanceof PsiExpression contextExpr) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createAddTypeCastFix(throwable, contextExpr)); + } - @Nullable - public static HighlightInfo checkIllegalVoidType(@Nonnull PsiKeyword type) { - if (!PsiKeyword.VOID.equals(type.getText())) { - return null; + PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (aClass != null) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createExtendsListFix(aClass, throwable, true)); + } + return hlBuilder; + } + return null; } - PsiElement parent = type.getParent(); - if (parent instanceof PsiTypeElement) { - PsiElement typeOwner = parent.getParent(); - if (typeOwner != null) { - // do not highlight incomplete declarations - if (PsiUtilCore.hasErrorElementChild(typeOwner)) { - return null; + @RequiredReadAction + private static HighlightInfo.@Nullable Builder checkMustBeThrowable(@Nullable PsiClass aClass, PsiElement context) { + if (aClass == null) { + return null; } - } + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); + return checkMustBeThrowable(type, context, false); + } - if (typeOwner instanceof PsiMethod) { - PsiMethod method = (PsiMethod) typeOwner; - if (method.getReturnTypeElement() == parent && PsiType.VOID.equals(method.getReturnType())) { - return null; - } - } else if (typeOwner instanceof PsiClassObjectAccessExpression) { - if (TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression) typeOwner).getOperand().getType())) { - return null; + @Nullable + @RequiredReadAction + public static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) { + if (labelIdentifier == null) { + return null; } - } else if (typeOwner instanceof JavaCodeFragment) { - if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) { - return null; + String label = labelIdentifier.getText(); + if (label == null) { + return null; } - } - } - - String description = JavaErrorBundle.message("illegal.type.void"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(type).descriptionAndTooltip(description).create(); - } - - @Nullable - public static HighlightInfo checkMemberReferencedBeforeConstructorCalled(@Nonnull PsiElement expression, PsiElement resolved, @Nonnull PsiFile containingFile) { - PsiClass referencedClass; - @NonNls String resolvedName; - PsiType type; - if (expression instanceof PsiJavaCodeReferenceElement) { - // redirected ctr - if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement) expression).getReferenceName()) && resolved instanceof PsiMethod && ((PsiMethod) resolved).isConstructor()) { - return null; - } - PsiElement qualifier = ((PsiJavaCodeReferenceElement) expression).getQualifier(); - type = qualifier instanceof PsiExpression ? ((PsiExpression) qualifier).getType() : null; - referencedClass = PsiUtil.resolveClassInType(type); - - boolean isSuperCall = RefactoringChangeUtil.isSuperMethodCall(expression.getParent()); - if (resolved == null && isSuperCall) { - if (qualifier instanceof PsiReferenceExpression) { - resolved = ((PsiReferenceExpression) qualifier).resolve(); - expression = qualifier; - type = ((PsiReferenceExpression) qualifier).getType(); - referencedClass = PsiUtil.resolveClassInType(type); - } else if (qualifier == null) { - resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); - if (resolved != null) { - referencedClass = ((PsiMethod) resolved).getContainingClass(); - } - } else if (qualifier instanceof PsiThisExpression) { - referencedClass = PsiUtil.resolveClassInType(((PsiThisExpression) qualifier).getType()); - } - } - if (resolved instanceof PsiField) { - PsiField referencedField = (PsiField) resolved; - if (referencedField.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - resolvedName = PsiFormatUtil.formatVariable(referencedField, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); - referencedClass = referencedField.getContainingClass(); - } else if (resolved instanceof PsiMethod) { - PsiMethod method = (PsiMethod) resolved; - if (method.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - PsiElement nameElement = expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement) expression).getReferenceNameElement(); - String name = nameElement == null ? null : nameElement.getText(); - if (isSuperCall) { - if (referencedClass == null) { - return null; - } - if (qualifier == null) { - PsiClass superClass = referencedClass.getSuperClass(); - if (superClass != null && PsiUtil.isInnerClass(superClass) && InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) { - // by default super() is considered this. - qualified - resolvedName = PsiKeyword.THIS; - } else { - return null; - } - } else { - resolvedName = qualifier.getText(); - } - } else if (PsiKeyword.THIS.equals(name)) { - resolvedName = PsiKeyword.THIS; - } else { - resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, 0); - if (referencedClass == null) { - referencedClass = method.getContainingClass(); - } - } - } else if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass) resolved; - if (aClass.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - referencedClass = aClass.getContainingClass(); - if (referencedClass == null) { - return null; + if (exitedStatement == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(labelIdentifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.labelUnresolved(label)) + .create(); } - resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); - } else { - return null; - } - } else if (expression instanceof PsiThisExpression) { - PsiThisExpression thisExpression = (PsiThisExpression) expression; - type = thisExpression.getType(); - referencedClass = PsiUtil.resolveClassInType(type); - if (thisExpression.getQualifier() != null) { - resolvedName = referencedClass == null ? null : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_NAME) + ".this"; - } else { - resolvedName = "this"; - } - } else { - return null; - } - if (referencedClass == null) { - return null; - } - return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName, containingFile); - } - - @Nullable - private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper(@Nonnull final PsiElement expression, - @Nonnull PsiClass referencedClass, - final String resolvedName, - @Nonnull PsiFile containingFile) { - if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) { - return null; - } - PsiElement element = expression.getParent(); - while (element != null) { - // check if expression inside super()/this() call - if (RefactoringChangeUtil.isSuperOrThisMethodCall(element)) { - PsiElement parentClass = new PsiMatcherImpl(element).parent(PsiMatchers.hasClass(PsiExpressionStatement.class)).parent(PsiMatchers.hasClass(PsiCodeBlock.class)).parent(PsiMatchers - .hasClass(PsiMethod.class)).dot(JavaMatchers.isConstructor(true)).parent(PsiMatchers.hasClass(PsiClass.class)).getElement(); - if (parentClass == null) { - return null; - } - - // only this class/superclasses instance methods are not allowed to call - PsiClass aClass = (PsiClass) parentClass; - if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) { - return null; - } - // field or method should be declared in this class or super - if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) { - return null; - } - // and point to our instance - if (expression instanceof PsiReferenceExpression && !thisOrSuperReference(((PsiReferenceExpression) expression).getQualifierExpression(), aClass)) { - return null; - } - - if (expression instanceof PsiJavaCodeReferenceElement && !aClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) && PsiTreeUtil.getParentOfType(expression, - PsiTypeElement.class) != null) { - return null; - } - - if (expression instanceof PsiJavaCodeReferenceElement && PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) { - return null; - } - - final HighlightInfo highlightInfo = createMemberReferencedError(resolvedName, expression.getTextRange()); - if (expression instanceof PsiReferenceExpression && PsiUtil.isInnerClass(aClass)) { - final String referenceName = ((PsiReferenceExpression) expression).getReferenceName(); - final PsiClass containingClass = aClass.getContainingClass(); - LOG.assertTrue(containingClass != null); - final PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true); - if (fieldInContainingClass != null && ((PsiReferenceExpression) expression).getQualifierExpression() == null) { - QuickFixAction.registerQuickFixAction(highlightInfo, new QualifyWithThisFix(containingClass, expression)); - } - } - - return highlightInfo; - } - - if (element instanceof PsiReferenceExpression) { - final PsiElement resolve; - if (element instanceof PsiReferenceExpressionImpl) { - PsiReferenceExpressionImpl referenceExpression = (PsiReferenceExpressionImpl) element; - JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(referenceExpression, PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE, true, false, containingFile); - resolve = results.length == 1 ? results[0].getElement() : null; - } else { - resolve = ((PsiReferenceExpression) element).resolve(); - } - if (resolve instanceof PsiField && ((PsiField) resolve).hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - } - - element = element.getParent(); - if (element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass) element, referencedClass, true)) { return null; - } - } - return null; - } - - private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, @Nonnull TextRange textRange) { - String description = JavaErrorBundle.message("member.referenced.before.constructor.called", resolvedName); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - - @Nullable - public static HighlightInfo checkImplicitThisReferenceBeforeSuper(@Nonnull PsiClass aClass, @Nonnull JavaSdkVersion javaSdkVersion) { - if (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) { - return null; - } - if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiTypeParameter) { - return null; - } - PsiClass superClass = aClass.getSuperClass(); - if (superClass == null || !PsiUtil.isInnerClass(superClass)) { - return null; - } - PsiClass outerClass = superClass.getContainingClass(); - if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { - return null; - } - // 'this' can be used as an (implicit) super() qualifier - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return createMemberReferencedError(aClass.getName() + ".this", range); - } - for (PsiMethod constructor : constructors) { - if (!isSuperCalledInConstructor(constructor)) { - return createMemberReferencedError(aClass.getName() + ".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); - } - } - return null; - } - - private static boolean isSuperCalledInConstructor(@Nonnull final PsiMethod constructor) { - final PsiCodeBlock body = constructor.getBody(); - if (body == null) { - return false; - } - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) { - return false; - } - final PsiStatement statement = statements[0]; - final PsiElement element = new PsiMatcherImpl(statement).dot(PsiMatchers.hasClass(PsiExpressionStatement.class)).firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)).firstChild - (PsiMatchers.hasClass(PsiReferenceExpression.class)).firstChild(PsiMatchers.hasClass(PsiKeyword.class)).dot(PsiMatchers.hasText(PsiKeyword.SUPER)).getElement(); - return element != null; - } - - private static boolean thisOrSuperReference(@Nullable PsiExpression qualifierExpression, PsiClass aClass) { - if (qualifierExpression == null) { - return true; - } - PsiJavaCodeReferenceElement qualifier; - if (qualifierExpression instanceof PsiThisExpression) { - qualifier = ((PsiThisExpression) qualifierExpression).getQualifier(); - } else if (qualifierExpression instanceof PsiSuperExpression) { - qualifier = ((PsiSuperExpression) qualifierExpression).getQualifier(); - } else { - return false; - } - if (qualifier == null) { - return true; - } - PsiElement resolved = qualifier.resolve(); - return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass) resolved, true); - } - - - @Nullable - public static HighlightInfo checkLabelWithoutStatement(@Nonnull PsiLabeledStatement statement) { - if (statement.getStatement() == null) { - String description = JavaErrorBundle.message("label.without.statement"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkLabelAlreadyInUse(@Nonnull PsiLabeledStatement statement) { - PsiIdentifier identifier = statement.getLabelIdentifier(); - String text = identifier.getText(); - PsiElement element = statement; - while (element != null) { - if (element instanceof PsiMethod || element instanceof PsiClass) { - break; - } - if (element instanceof PsiLabeledStatement && element != statement && Comparing.equal(((PsiLabeledStatement) element).getLabelIdentifier().getText(), text)) { - String description = JavaErrorBundle.message("duplicate.label", text); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); - } - element = element.getParent(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkUnclosedComment(@Nonnull PsiComment comment) { - if (!(comment instanceof PsiDocComment) && comment.getTokenType() != JavaTokenType.C_STYLE_COMMENT) { - return null; - } - if (!comment.getText().endsWith("*/")) { - int start = comment.getTextRange().getEndOffset() - 1; - int end = start + 1; - String description = JavaErrorBundle.message("unclosed.comment"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description).create(); - } - return null; - } - - - @Nonnull - public static Collection checkCatchTypeIsDisjoint(@Nonnull final PsiParameter parameter) { - if (!(parameter.getType() instanceof PsiDisjunctionType)) { - return Collections.emptyList(); - } - - final Collection result = ContainerUtil.newArrayList(); - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - for (int i = 0, size = typeElements.size(); i < size; i++) { - final PsiClass class1 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(i).getType()); - if (class1 == null) { - continue; - } - for (int j = i + 1; j < size; j++) { - final PsiClass class2 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(j).getType()); - if (class2 == null) { - continue; - } - final boolean sub = InheritanceUtil.isInheritorOrSelf(class1, class2, true); - final boolean sup = InheritanceUtil.isInheritorOrSelf(class2, class1, true); - if (sub || sup) { - final String name1 = PsiFormatUtil.formatClass(class1, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String name2 = PsiFormatUtil.formatClass(class2, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String message = JavaErrorBundle.message("exception.must.be.disjoint", sub ? name1 : name2, sub ? name2 : name1); - final PsiTypeElement element = typeElements.get(sub ? i : j); - final HighlightInfo highlight = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlight, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(element)); - result.add(highlight); - break; - } - } - } - - return result; - } - - - @Nonnull - public static Collection checkExceptionAlreadyCaught(@Nonnull final PsiParameter parameter) { - final PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiCatchSection)) { - return Collections.emptyList(); - } - - final PsiCatchSection catchSection = (PsiCatchSection) scope; - final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); - final int startFrom = ArrayUtil.find(allCatchSections, catchSection) - 1; - if (startFrom < 0) { - return Collections.emptyList(); } - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - final boolean isInMultiCatch = typeElements.size() > 1; - final Collection result = ContainerUtil.newArrayList(); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkReference( + PsiJavaCodeReferenceElement ref, + JavaResolveResult result, + PsiFile containingFile, + LanguageLevel languageLevel + ) { + PsiElement refName = ref.getReferenceNameElement(); + if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) { + return null; + } + PsiElement resolved = result.getElement(); - for (PsiTypeElement typeElement : typeElements) { - final PsiClass catchClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); - if (catchClass == null) { - continue; - } - - for (int i = startFrom; i >= 0; i--) { - final PsiCatchSection upperCatchSection = allCatchSections[i]; - final PsiType upperCatchType = upperCatchSection.getCatchType(); - - final boolean highlight = upperCatchType instanceof PsiDisjunctionType ? checkMultipleTypes(catchClass, ((PsiDisjunctionType) upperCatchType).getDisjunctions()) : checkSingleType - (catchClass, upperCatchType); - if (highlight) { - final String className = PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String description = JavaErrorBundle.message("exception.already.caught", className); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - result.add(highlightInfo); - - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createMoveCatchUpFix(catchSection, upperCatchSection)); - if (isInMultiCatch) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(typeElement)); - } else { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); - } + { + HighlightInfo.Builder hlBuilder = checkMemberReferencedBeforeConstructorCalled(ref, resolved, containingFile); + if (hlBuilder != null) { + return hlBuilder; + } } - } - } - return result; - } - - private static boolean checkMultipleTypes(final PsiClass catchClass, @Nonnull final List upperCatchTypes) { - for (int i = upperCatchTypes.size() - 1; i >= 0; i--) { - if (checkSingleType(catchClass, upperCatchTypes.get(i))) { - return true; - } - } - return false; - } + PsiElement refParent = ref.getParent(); + if (refParent instanceof PsiReferenceExpression refExpr && refExpr.getParent() instanceof PsiMethodCallExpression methodCall) { + PsiReferenceExpression referenceToMethod = methodCall.getMethodExpression(); + PsiExpression qualifierExpression = referenceToMethod.getQualifierExpression(); + if (qualifierExpression == ref && resolved != null && !(resolved instanceof PsiClass) && !(resolved instanceof PsiVariable)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(qualifierExpression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.referenceQualifierNotExpression()); + } + } + else if (refParent instanceof PsiMethodCallExpression) { + return null; // methods checked elsewhere + } - private static boolean checkSingleType(final PsiClass catchClass, final PsiType upperCatchType) { - final PsiClass upperCatchClass = PsiUtil.resolveClassInType(upperCatchType); - return upperCatchClass != null && InheritanceUtil.isInheritorOrSelf(catchClass, upperCatchClass, true); - } - - - @Nullable - public static HighlightInfo checkTernaryOperatorConditionIsBoolean(@Nonnull PsiExpression expression, PsiType type) { - if (expression.getParent() instanceof PsiConditionalExpression && ((PsiConditionalExpression) expression.getParent()).getCondition() == expression && !TypeConversionUtil.isBooleanType(type)) { - return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); - } - return null; - } - - - @Nullable - public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(@Nonnull PsiSwitchStatement statement) { - PsiCodeBlock body = statement.getBody(); - if (body != null) { - PsiElement first = PsiTreeUtil.skipSiblingsForward(body.getLBrace(), PsiWhiteSpace.class, PsiComment.class); - if (first != null && !(first instanceof PsiSwitchLabelStatement) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); - } - } + if (resolved == null) { + // do not highlight unknown packages (javac does not care), Javadoc, and module references (checked elsewhere) + PsiElement outerParent = getOuterReferenceParent(ref); + if (outerParent instanceof PsiPackageStatement + || result.isPackagePrefixPackageReference() + || PsiUtil.isInsideJavadocComment(ref) + || outerParent instanceof PsiPackageAccessibilityStatement) { + return null; + } - return null; - } + JavaResolveResult[] results = ref.multiResolve(true); + LocalizeValue description; + if (results.length > 1) { + String t1 = format(ObjectUtil.notNull(results[0].getElement())); + String t2 = format(ObjectUtil.notNull(results[1].getElement())); + description = JavaCompilationErrorLocalize.referenceAmbiguous(refName.getText(), t1, t2); + } + else { + description = JavaCompilationErrorLocalize.referenceUnresolved(refName.getText()); + } + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refName) + .descriptionAndTooltip(description); + UnresolvedReferenceQuickFixUpdater.getInstance(containingFile.getProject()).registerQuickFixesLater(ref, hlBuilder); + return hlBuilder; + } + + if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { + if (!result.isAccessible()) { + // Module accessibility now participates in resolution (see PsiResolveHelperImpl). For an otherwise-public + // symbol the inaccessibility is a Java module problem, so prefer the specific module diagnostic and fix. + if (resolved instanceof PsiModifierListOwner owner + && !owner.hasModifierProperty(PsiModifier.PRIVATE) + && !owner.hasModifierProperty(PsiModifier.PROTECTED) + && !owner.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) + && getPackageLocalClassInTheMiddle(ref) == null) { + PsiJavaModule refModule = JavaModuleGraphUtil.findDescriptorByElement(ref); + if (refModule != null) { + HighlightInfo.Builder moduleProblem = ModuleHighlightUtil.checkPackageAccessibility(ref, resolved, refModule); + if (moduleProblem != null) { + return moduleProblem; + } + } + } - @Nullable - public static HighlightInfo checkAssertOperatorTypes(@Nonnull PsiExpression expression, @Nullable PsiType type) { - if (type == null) { - return null; - } - if (!(expression.getParent() instanceof PsiAssertStatement)) { - return null; - } - PsiAssertStatement assertStatement = (PsiAssertStatement) expression.getParent(); - if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { - // addTypeCast quickfix is not applicable here since no type can be cast to boolean - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); - if (expression instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) expression).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAssignmentToComparisonFix((PsiAssignmentExpression) expression)); - } - return highlightInfo; - } - if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { - String description = JavaErrorBundle.message("void.type.is.not.allowed"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - } - return null; - } + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refName) + .descriptionAndTooltip(buildProblemWithAccessDescription(ref, result, resolved)); + if (result.isStaticsScopeCorrect()) { + registerAccessQuickFixAction( + (PsiMember) resolved, + ref, + hlBuilder, + refName.getTextRange(), + result.getCurrentFileResolveScope() + ); + if (ref instanceof PsiReferenceExpression refExpr) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createRenameWrongRefFix(refExpr)); + } + } + UnresolvedReferenceQuickFixUpdater.getInstance(containingFile.getProject()).registerQuickFixesLater(ref, hlBuilder); + return hlBuilder; + } + if (!result.isStaticsScopeCorrect()) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refName) + .descriptionAndTooltip(buildProblemWithStaticDescription(resolved)); + registerStaticProblemQuickFixAction(resolved, hlBuilder, ref); + if (ref instanceof PsiReferenceExpression refExpr) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createRenameWrongRefFix(refExpr)); + } + return hlBuilder; + } + } - @Nullable - public static HighlightInfo checkSynchronizedExpressionType(@Nonnull PsiExpression expression, @Nullable PsiType type, @Nonnull PsiFile containingFile) { - if (type == null) { - return null; - } - if (expression.getParent() instanceof PsiSynchronizedStatement) { - PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement) expression.getParent(); - if (expression == synchronizedStatement.getLockExpression() && (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { - PsiClassType objectType = PsiType.getJavaLangObject(containingFile.getManager(), expression.getResolveScope()); - return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange(), 0); - } - } - return null; - } + if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { + return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable) resolved, ref, languageLevel); + } + if (resolved instanceof PsiClass resolvedClass && resolvedClass.getContainingClass() == null + && PsiTreeUtil.getParentOfType(ref, PsiImportStatementBase.class) != null + && PsiUtil.isFromDefaultPackage(resolvedClass)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refName) + .descriptionAndTooltip(JavaCompilationErrorLocalize.referenceUnresolved(refName.getText())); + } - - @Nullable - public static HighlightInfo checkConditionalExpressionBranchTypesMatch(@Nonnull final PsiExpression expression, PsiType type) { - PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiConditionalExpression)) { - return null; - } - PsiConditionalExpression conditionalExpression = (PsiConditionalExpression) parent; - // check else branches only - if (conditionalExpression.getElseExpression() != expression) { - return null; - } - final PsiExpression thenExpression = conditionalExpression.getThenExpression(); - assert thenExpression != null; - PsiType thenType = thenExpression.getType(); - if (thenType == null || type == null) { - return null; - } - if (conditionalExpression.getType() == null) { - if (PsiUtil.isLanguageLevel8OrHigher(conditionalExpression) && PsiPolyExpressionUtil.isPolyExpression(conditionalExpression)) { return null; - } - // cannot derive type of conditional expression - // elseType will never be cast-able to thenType, so no quick fix here - return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange(), 0); - } - return null; - } - - @SuppressWarnings("StringContatenationInLoop") - public static HighlightInfo createIncompatibleTypeHighlightInfo(final PsiType lType, final PsiType rType, @Nonnull final TextRange textRange, int navigationShift) { - PsiType lType1 = lType; - PsiType rType1 = rType; - PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; - if (lType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType) lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - lType1 = ((PsiAnonymousClass) psiClass).getBaseClassType(); - resolveResult = ((PsiClassType) lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; - if (rType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType) rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - rType1 = ((PsiAnonymousClass) psiClass).getBaseClassType(); - resolveResult = ((PsiClassType) rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - - int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); - @Language("HTML") @NonNls String requiredRow = ""; - @Language("HTML") @NonNls String foundRow = ""; - for (int i = 0; i < typeParamColumns; i++) { - PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; - PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; - PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); - PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); - boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); - @NonNls String openBrace = i == 0 ? "<" : ""; - @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; - requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(lSubstitutedType, matches) + (i < lTypeParams.length ? closeBrace : "") + ""; - foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(rSubstitutedType, matches) + (i < rTypeParams.length ? closeBrace : "") + ""; - } - PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType) lType1).rawType() : lType1; - PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType) rType1).rawType() : rType1; - boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); - - String toolTip = JavaErrorBundle.message("incompatible.types.html.tooltip", redIfNotMatch(lRawType, assignable), requiredRow, redIfNotMatch(rRawType, assignable), foundRow); - - String description = JavaErrorBundle.message("incompatible.types", JavaHighlightUtil.formatType(lType1), JavaHighlightUtil.formatType(rType1)); - - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).description(description).escapedToolTip(toolTip).navigationShift(navigationShift).create(); - } - - @Nullable - public static HighlightInfo checkSingleImportClassConflict(@Nonnull PsiImportStatement statement, - @Nonnull Map> importedClasses, - @Nonnull PsiFile containingFile) { - if (statement.isOnDemand()) { - return null; - } - PsiElement element = statement.resolve(); - if (element instanceof PsiClass) { - String name = ((PsiClass) element).getName(); - Pair imported = importedClasses.get(name); - PsiClass importedClass = imported == null ? null : imported.getSecond(); - if (importedClass != null && !containingFile.getManager().areElementsEquivalent(importedClass, element)) { - String description = JavaErrorBundle.message("single.import.class.conflict", formatClass(importedClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - importedClasses.put(name, Pair.create(null, (PsiClass) element)); - } - return null; - } - - - @NonNls - private static String redIfNotMatch(PsiType type, boolean matches) { - if (matches) { - return getFQName(type, false); - } - String color = UIUtil.isUnderDarcula() ? "FF6B68" : "red"; - return "" + getFQName(type, true) + ""; - } - - private static String getFQName(@Nullable PsiType type, boolean longName) { - if (type == null) { - return ""; - } - return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); - } - - - @Nullable - public static HighlightInfo checkMustBeThrowable(@Nullable PsiType type, @Nonnull PsiElement context, boolean addCastIntention) { - if (type == null) { - return null; - } - PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); - PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); - if (!TypeConversionUtil.isAssignable(throwable, type)) { - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0); - if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { - if (context instanceof PsiExpression) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddTypeCastFix(throwable, (PsiExpression) context)); - } - } - - final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (aClass != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createExtendsListFix(aClass, throwable, true)); - } - return highlightInfo; - } - return null; - } - - - @Nullable - private static HighlightInfo checkMustBeThrowable(@Nullable PsiClass aClass, @Nonnull PsiElement context) { - if (aClass == null) { - return null; - } - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); - return checkMustBeThrowable(type, context, false); - } - - - @Nullable - public static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) { - if (labelIdentifier == null) { - return null; - } - String label = labelIdentifier.getText(); - if (label == null) { - return null; - } - if (exitedStatement == null) { - String message = JavaErrorBundle.message("unresolved.label", label); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelIdentifier).descriptionAndTooltip(message).create(); } - return null; - } - - @Nullable - public static HighlightInfo checkReference(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull JavaResolveResult result, @Nonnull PsiFile containingFile, @Nonnull LanguageLevel languageLevel) { - PsiElement refName = ref.getReferenceNameElement(); - if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) { - return null; - } - PsiElement resolved = result.getElement(); - - HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved, containingFile); - if (highlightInfo != null) { - return highlightInfo; + private static String format(PsiElement element) { + if (element instanceof PsiClass psiClass) { + return formatClass(psiClass); + } + if (element instanceof PsiMethod method) { + return JavaHighlightUtil.formatMethod(method); + } + if (element instanceof PsiField field) { + return formatField(field); + } + return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); } - PsiElement refParent = ref.getParent(); - PsiElement granny; - if (refParent instanceof PsiReferenceExpression && (granny = refParent.getParent()) instanceof PsiMethodCallExpression) { - PsiReferenceExpression referenceToMethod = ((PsiMethodCallExpression) granny).getMethodExpression(); - PsiExpression qualifierExpression = referenceToMethod.getQualifierExpression(); - if (qualifierExpression == ref && resolved != null && !(resolved instanceof PsiClass) && !(resolved instanceof PsiVariable)) { - String message = JavaErrorBundle.message("qualifier.must.be.expression"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(qualifierExpression).descriptionAndTooltip(message).create(); - } - } else if (refParent instanceof PsiMethodCallExpression) { - return null; // methods checked elsewhere + private static PsiElement getOuterReferenceParent(PsiJavaCodeReferenceElement ref) { + PsiElement element = ref; + while (element instanceof PsiJavaCodeReferenceElement) { + element = element.getParent(); + } + return element; } - if (resolved == null) { - // do not highlight unknown packages (javac does not care), Javadoc, and module references (checked elsewhere) - PsiElement outerParent = getOuterReferenceParent(ref); - if (outerParent instanceof PsiPackageStatement || result.isPackagePrefixPackageReference() || PsiUtil.isInsideJavadocComment(ref) || outerParent instanceof - PsiPackageAccessibilityStatement) { + @Nullable + @RequiredReadAction + public static HighlightInfo checkPackageAndClassConflict(PsiJavaCodeReferenceElement ref, PsiFile containingFile) { + if (ref.isQualified() && getOuterReferenceParent(ref) instanceof PsiPackageStatement) { + VirtualFile file = containingFile.getVirtualFile(); + if (file != null) { + Module module = ProjectFileIndex.SERVICE.getInstance(ref.getProject()).getModuleForFile(file); + if (module != null) { + GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); + PsiClass aClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(ref.getCanonicalText(), scope); + if (aClass != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(JavaCompilationErrorLocalize.packageClashesWithClass(ref.getText())) + .create(); + } + } + } + } + return null; - } - - JavaResolveResult[] results = ref.multiResolve(true); - String description; - if (results.length > 1) { - String t1 = format(ObjectUtil.notNull(results[0].getElement())); - String t2 = format(ObjectUtil.notNull(results[1].getElement())); - description = JavaErrorBundle.message("ambiguous.reference", refName.getText(), t1, t2); - } else { - description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); - } - - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); - return info; - } - - if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { - if (!result.isAccessible()) { - String message = buildProblemWithAccessDescription(ref, result, resolved); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(message).create(); - if (result.isStaticsScopeCorrect()) { - registerAccessQuickFixAction((PsiMember) resolved, ref, info, result.getCurrentFileResolveScope()); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRenameWrongRefFix((PsiReferenceExpression) ref)); - } - } - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); - return info; - } + } - if (!result.isStaticsScopeCorrect()) { - String description = buildProblemWithStaticDescription(resolved); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); - registerStaticProblemQuickFixAction(resolved, info, ref); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRenameWrongRefFix((PsiReferenceExpression) ref)); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkElementInReferenceList( + PsiJavaCodeReferenceElement ref, + PsiReferenceList referenceList, + JavaResolveResult resolveResult + ) { + PsiElement resolved = resolveResult.getElement(); + PsiElement refGrandParent = referenceList.getParent(); + if (resolved instanceof PsiClass aClass) { + if (refGrandParent instanceof PsiClass parentClass) { + if (refGrandParent instanceof PsiTypeParameter typeParameter) { + return GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, typeParameter, resolveResult, ref); + } + else if (referenceList.equals(parentClass.getImplementsList()) || + referenceList.equals(parentClass.getExtendsList())) { + HighlightInfo.Builder hlBuilder = + HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); + if (hlBuilder == null) { + hlBuilder = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); + } + if (hlBuilder == null) { + // TODO highlightInfo = HighlightClassUtil.checkExtendsProhibitedClass(aClass, parentClass, ref); + } + if (hlBuilder == null) { + hlBuilder = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); + } + if (hlBuilder == null) { + // TODO highlightInfo = HighlightClassUtil.checkExtendsSealedClass(parentClass, aClass, ref); + } + } + } + else if (refGrandParent instanceof PsiMethod method && method.getThrowsList() == referenceList) { + return checkMustBeThrowable(aClass, ref); + } } - return info; - } + else if (refGrandParent instanceof PsiMethod method && referenceList == method.getThrowsList()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classReferenceListNameExpected()); + } + return null; } - if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { - return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable) resolved, ref, languageLevel); - } - if (resolved instanceof PsiClass && ((PsiClass) resolved).getContainingClass() == null && PsiTreeUtil.getParentOfType(ref, PsiImportStatementBase.class) != null && PsiUtil - .isFromDefaultPackage((PsiClass) resolved)) { - String description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); + public static boolean isSerializationImplicitlyUsedField(PsiField field) { + String name = field.getName(); + if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) { + return false; + } + if (!field.isStatic()) { + return false; + } + PsiClass aClass = field.getContainingClass(); + return aClass == null || JavaHighlightUtil.isSerializable(aClass); } - return null; - } - - @Nonnull - private static String format(@Nonnull PsiElement element) { - if (element instanceof PsiClass) { - return formatClass((PsiClass) element); - } - if (element instanceof PsiMethod) { - return JavaHighlightUtil.formatMethod((PsiMethod) element); - } - if (element instanceof PsiField) { - return formatField((PsiField) element); + @Nullable + @RequiredReadAction + public static HighlightInfo checkClassReferenceAfterQualifier(PsiReferenceExpression expression, PsiElement resolved) { + if (!(resolved instanceof PsiClass resolvedClass)) { + return null; + } + PsiExpression qualifier = expression.getQualifierExpression(); + if (qualifier == null) { + return null; + } + if (qualifier instanceof PsiReferenceExpression qRefExpr) { + PsiElement qualifierResolved = qRefExpr.resolve(); + if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) { + return null; + } + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.classOrPackageExpected()) + .registerFix(QuickFixFactory.getInstance().createRemoveQualifierFix(qualifier, expression, resolvedClass)) + .create(); + } + + public static void registerChangeVariableTypeFixes( + PsiVariable parameter, + PsiType itemType, + @Nullable PsiExpression expr, + HighlightInfo.Builder highlightInfo + ) { + for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) { + highlightInfo.registerFix(action); + } + if (expr instanceof PsiMethodCallExpression methodCall) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null) { + highlightInfo.registerFix(PriorityActionWrapper.lowPriority( + method, + QuickFixFactory.getInstance().createMethodReturnFix(method, parameter.getType(), true) + )); + } + } } - return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); - } - private static PsiElement getOuterReferenceParent(PsiJavaCodeReferenceElement ref) { - PsiElement element = ref; - while (element instanceof PsiJavaCodeReferenceElement) { - element = element.getParent(); + public static List getChangeVariableTypeFixes(PsiVariable parameter, PsiType itemType) { + if (itemType instanceof PsiMethodReferenceType) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + if (itemType != null) { + parameter.getApplication().getExtensionPoint(ChangeVariableTypeQuickFixProvider.class) + .forEach(fixProvider -> Collections.addAll(result, fixProvider.getFixes(parameter, itemType))); + } + IntentionAction changeFix = getChangeParameterClassFix(parameter.getType(), itemType); + if (changeFix != null) { + result.add(changeFix); + } + return result; } - return element; - } - @Nullable - public static HighlightInfo checkPackageAndClassConflict(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull PsiFile containingFile) { - if (ref.isQualified() && getOuterReferenceParent(ref) instanceof PsiPackageStatement) { - VirtualFile file = containingFile.getVirtualFile(); - if (file != null) { - Module module = ProjectFileIndex.SERVICE.getInstance(ref.getProject()).getModuleForFile(file); - if (module != null) { - GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); - PsiClass aClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(ref.getCanonicalText(), scope); - if (aClass != null) { - String message = JavaErrorBundle.message("package.clashes.with.class", ref.getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(message).create(); - } - } - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkElementInReferenceList(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull PsiReferenceList referenceList, @Nonnull JavaResolveResult resolveResult) { - PsiElement resolved = resolveResult.getElement(); - HighlightInfo highlightInfo = null; - PsiElement refGrandParent = referenceList.getParent(); - if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass) resolved; - if (refGrandParent instanceof PsiClass) { - if (refGrandParent instanceof PsiTypeParameter) { - highlightInfo = GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, (PsiClass) refGrandParent, resolveResult, ref); - } else { - highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); - if (highlightInfo == null) { - highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkCannotInheritFromEnum(aClass, ref); - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); - } - } - } else if (refGrandParent instanceof PsiMethod && ((PsiMethod) refGrandParent).getThrowsList() == referenceList) { - highlightInfo = checkMustBeThrowable(aClass, ref); - } - } else if (refGrandParent instanceof PsiMethod && referenceList == ((PsiMethod) refGrandParent).getThrowsList()) { - String description = JavaErrorBundle.message("class.name.expected"); - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(description).create(); - } - return highlightInfo; - } - - - public static boolean isSerializationImplicitlyUsedField(@Nonnull PsiField field) { - final String name = field.getName(); - if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) { - return false; - } - if (!field.hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - PsiClass aClass = field.getContainingClass(); - return aClass == null || JavaHighlightUtil.isSerializable(aClass); - } - - @Nullable - public static HighlightInfo checkClassReferenceAfterQualifier(@Nonnull final PsiReferenceExpression expression, final PsiElement resolved) { - if (!(resolved instanceof PsiClass)) { - return null; - } - final PsiExpression qualifier = expression.getQualifierExpression(); - if (qualifier == null) { - return null; - } - if (qualifier instanceof PsiReferenceExpression) { - PsiElement qualifierResolved = ((PsiReferenceExpression) qualifier).resolve(); - if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) { + @Nullable + @RequiredReadAction + public static HighlightInfo checkAnnotationMethodParameters(PsiParameterList list) { + PsiElement parent = list.getParent(); + if (PsiUtil.isAnnotationMethod(parent) && list.getParametersCount() > 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(list) + .descriptionAndTooltip(JavaCompilationErrorLocalize.annotationMemberMayNotHaveParameters()) + .registerFix(QuickFixFactory.getInstance().createRemoveParameterListFix((PsiMethod) parent)) + .create(); + } return null; - } - } - String description = JavaErrorBundle.message("expected.class.or.package"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveQualifierFix(qualifier, expression, (PsiClass) resolved)); - return info; - } - - public static void registerChangeVariableTypeFixes(@Nonnull PsiVariable parameter, PsiType itemType, @Nullable PsiExpression expr, @Nonnull HighlightInfo highlightInfo) { - for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, action); - } - if (expr instanceof PsiMethodCallExpression) { - final PsiMethod method = ((PsiMethodCallExpression) expr).resolveMethod(); - if (method != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, PriorityActionWrapper.lowPriority(method, QUICK_FIX_FACTORY.createMethodReturnFix(method, parameter.getType(), true))); - } - } - } - - @Nonnull - public static List getChangeVariableTypeFixes(@Nonnull PsiVariable parameter, PsiType itemType) { - if (itemType instanceof PsiMethodReferenceType) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - if (itemType != null) { - for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { - Collections.addAll(result, fixProvider.getFixes(parameter, itemType)); - } - } - IntentionAction changeFix = getChangeParameterClassFix(parameter.getType(), itemType); - if (changeFix != null) { - result.add(changeFix); - } - return result; - } - - @Nullable - public static HighlightInfo checkAnnotationMethodParameters(@Nonnull PsiParameterList list) { - final PsiElement parent = list.getParent(); - if (PsiUtil.isAnnotationMethod(parent) && list.getParametersCount() > 0) { - final String message = JavaErrorBundle.message("annotation.interface.members.may.not.have.parameters"); - final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createRemoveParameterListFix((PsiMethod) parent)); - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkForStatement(@Nonnull PsiForStatement statement) { - PsiStatement init = statement.getInitialization(); - if (init == null || init instanceof PsiEmptyStatement || init instanceof PsiDeclarationStatement && ArrayUtil.getFirstElement(((PsiDeclarationStatement) init).getDeclaredElements()) - instanceof PsiLocalVariable || init instanceof PsiExpressionStatement || init instanceof PsiExpressionListStatement) { - return null; - } - - String message = JavaErrorBundle.message("invalid.statement"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(init).descriptionAndTooltip(message).create(); - } - - private static void registerChangeParameterClassFix(PsiType lType, PsiType rType, HighlightInfo info) { - QuickFixAction.registerQuickFixAction(info, getChangeParameterClassFix(lType, rType)); - } - - @Nullable - private static IntentionAction getChangeParameterClassFix(PsiType lType, PsiType rType) { - final PsiClass lClass = PsiUtil.resolveClassInClassTypeOnly(lType); - final PsiClass rClass = PsiUtil.resolveClassInClassTypeOnly(rType); - - if (rClass == null || lClass == null) { - return null; - } - if (rClass instanceof PsiAnonymousClass) { - return null; - } - if (rClass.isInheritor(lClass, true)) { - return null; - } - if (lClass.isInheritor(rClass, true)) { - return null; - } - if (lClass == rClass) { - return null; - } - - return QUICK_FIX_FACTORY.createChangeParameterClassFix(rClass, (PsiClassType) lType); - } - - private static void registerReplaceInaccessibleFieldWithGetterSetterFix(PsiMember refElement, PsiJavaCodeReferenceElement place, PsiClass accessObjectClass, HighlightInfo error) { - if (refElement instanceof PsiField && place instanceof PsiReferenceExpression) { - final PsiField psiField = (PsiField) refElement; - final PsiClass containingClass = psiField.getContainingClass(); - if (containingClass != null) { - if (PsiUtil.isOnAssignmentLeftHand((PsiExpression) place)) { - final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(psiField); - final PsiMethod setter = containingClass.findMethodBySignature(setterPrototype, true); - if (setter != null && PsiUtil.isAccessible(setter, place, accessObjectClass)) { - final PsiElement element = PsiTreeUtil.skipParentsOfType(place, PsiParenthesizedExpression.class); - if (element instanceof PsiAssignmentExpression && ((PsiAssignmentExpression) element).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(error, QUICK_FIX_FACTORY.createReplaceInaccessibleFieldWithGetterSetterFix(place, setter, true)); - } - } - } else if (PsiUtil.isAccessedForReading((PsiExpression) place)) { - final PsiMethod getterPrototype = PropertyUtil.generateGetterPrototype(psiField); - final PsiMethod getter = containingClass.findMethodBySignature(getterPrototype, true); - if (getter != null && PsiUtil.isAccessible(getter, place, accessObjectClass)) { - QuickFixAction.registerQuickFixAction(error, QUICK_FIX_FACTORY.createReplaceInaccessibleFieldWithGetterSetterFix(place, getter, false)); - } - } - } - } - } - - public enum Feature { - GENERICS(LanguageLevel.JDK_1_5, "feature.generics"), - ANNOTATIONS(LanguageLevel.JDK_1_5, "feature.annotations"), - STATIC_IMPORTS(LanguageLevel.JDK_1_5, "feature.static.imports"), - FOR_EACH(LanguageLevel.JDK_1_5, "feature.for.each"), - VARARGS(LanguageLevel.JDK_1_5, "feature.varargs"), - HEX_FP_LITERALS(LanguageLevel.JDK_1_5, "feature.hex.fp.literals"), - DIAMOND_TYPES(LanguageLevel.JDK_1_7, "feature.diamond.types"), - MULTI_CATCH(LanguageLevel.JDK_1_7, "feature.multi.catch"), - TRY_WITH_RESOURCES(LanguageLevel.JDK_1_7, "feature.try.with.resources"), - BIN_LITERALS(LanguageLevel.JDK_1_7, "feature.binary.literals"), - UNDERSCORES(LanguageLevel.JDK_1_7, "feature.underscores.in.literals"), - EXTENSION_METHODS(LanguageLevel.JDK_1_8, "feature.extension.methods"), - METHOD_REFERENCES(LanguageLevel.JDK_1_8, "feature.method.references"), - LAMBDA_EXPRESSIONS(LanguageLevel.JDK_1_8, "feature.lambda.expressions"), - TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations"), - RECEIVERS(LanguageLevel.JDK_1_8, "feature.type.receivers"), - INTERSECTION_CASTS(LanguageLevel.JDK_1_8, "feature.intersections.in.casts"), - STATIC_INTERFACE_CALLS(LanguageLevel.JDK_1_8, "feature.static.interface.calls"), - REFS_AS_RESOURCE(LanguageLevel.JDK_1_9, "feature.try.with.resources.refs"), - MODULES(LanguageLevel.JDK_1_9, "feature.modules"), - LVTI(LanguageLevel.JDK_10, "feature.lvti"), - VAR_LAMBDA_PARAMETER(LanguageLevel.JDK_11, "feature.var.lambda.parameter"), - ENHANCED_SWITCH(LanguageLevel.JDK_14, "feature.enhanced.switch"), - SWITCH_EXPRESSION(LanguageLevel.JDK_14, "feature.switch.expressions"), - RECORDS(LanguageLevel.JDK_16, "feature.records"), - PATTERNS(LanguageLevel.JDK_16, "feature.patterns.instanceof"), - TEXT_BLOCK_ESCAPES(LanguageLevel.JDK_15, "feature.text.block.escape.sequences"), - TEXT_BLOCKS(LanguageLevel.JDK_15, "feature.text.blocks"), - SEALED_CLASSES(LanguageLevel.JDK_17, "feature.sealed.classes"), - LOCAL_INTERFACES(LanguageLevel.JDK_16, "feature.local.interfaces"), - LOCAL_ENUMS(LanguageLevel.JDK_16, "feature.local.enums"), - INNER_STATICS(LanguageLevel.JDK_16, "feature.inner.statics"); - - private final LanguageLevel level; - private final String key; - - Feature(LanguageLevel level, @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key) { - this.level = level; - this.key = key; } - /** - * @param element a valid PsiElement to check (it's better to supply PsiFile if already known; any element is accepted for convenience) - * @return true if this feature is available in the PsiFile the supplied element belongs to - */ - public boolean isAvailable(PsiElement element) { - return isSufficient(PsiUtil.getLanguageLevel(element)); - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkForStatement(PsiForStatement statement) { + PsiStatement init = statement.getInitialization(); + if (init == null + || init instanceof PsiEmptyStatement + || init instanceof PsiDeclarationStatement declaration + && ArrayUtil.getFirstElement(declaration.getDeclaredElements()) instanceof PsiLocalVariable + || init instanceof PsiExpressionStatement + || init instanceof PsiExpressionListStatement) { + return null; + } - private boolean isSufficient(LanguageLevel useSiteLevel) { - return useSiteLevel.isAtLeast(level) && (!level.isPreview() || useSiteLevel.isPreview()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(init) + .descriptionAndTooltip(JavaCompilationErrorLocalize.statementInvalid()) + .create(); } - } - @Nullable - public static HighlightInfo checkFeature(@Nonnull PsiElement element, @Nonnull Feature feature, @Nonnull LanguageLevel level, @Nonnull PsiFile file) { - if (file.getManager().isInProject(file) && !level.isAtLeast(feature.level)) { - String message = getUnsupportedFeatureMessage(element, feature, level, file); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createIncreaseLanguageLevelFix(feature.level)); - QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createShowModulePropertiesFix(element)); - return info; + private static void registerChangeParameterClassFix(PsiType lType, PsiType rType, HighlightInfo.Builder hlBuilder) { + IntentionAction fix = getChangeParameterClassFix(lType, rType); + if (fix != null) { + hlBuilder.registerFix(fix); + } } - return null; - } + @Nullable + private static IntentionAction getChangeParameterClassFix(PsiType lType, PsiType rType) { + PsiClass lClass = PsiUtil.resolveClassInClassTypeOnly(lType); + PsiClass rClass = PsiUtil.resolveClassInClassTypeOnly(rType); - @RequiredReadAction - private static String getUnsupportedFeatureMessage(PsiElement element, Feature feature, LanguageLevel level, PsiFile file) { - String name = JavaErrorBundle.message(feature.key); - String message = JavaErrorBundle.message("insufficient.language.level", name, level.getCompilerComplianceDefaultOption()); + if (rClass == null || lClass == null + || rClass instanceof PsiAnonymousClass + || rClass.isInheritor(lClass, true) + || lClass.isInheritor(rClass, true) + || lClass == rClass) { + return null; + } - Module module = ModuleUtilCore.findModuleForPsiElement(element); - if (module != null) { - LanguageLevel moduleLanguageLevel = EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); - if (moduleLanguageLevel.isAtLeast(feature.level)) { - for (FilePropertyPusher pusher : FilePropertyPusher.EP_NAME.getExtensions()) { - if (pusher instanceof JavaLanguageLevelPusher) { - String newMessage = ((JavaLanguageLevelPusher) pusher).getInconsistencyLanguageLevelMessage(message, element, level, file); - if (newMessage != null) { - return newMessage; + return QuickFixFactory.getInstance().createChangeParameterClassFix(rClass, (PsiClassType) lType); + } + + private static void registerReplaceInaccessibleFieldWithGetterSetterFix( + PsiMember refElement, + PsiJavaCodeReferenceElement place, + PsiClass accessObjectClass, + HighlightInfo.Builder hlBuilder + ) { + if (refElement instanceof PsiField field && place instanceof PsiReferenceExpression placeRefExpr) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass != null) { + if (PsiUtil.isOnAssignmentLeftHand(placeRefExpr)) { + PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(field); + PsiMethod setter = containingClass.findMethodBySignature(setterPrototype, true); + if (setter != null && PsiUtil.isAccessible(setter, placeRefExpr, accessObjectClass)) { + PsiElement element = PsiTreeUtil.skipParentsOfType(placeRefExpr, PsiParenthesizedExpression.class); + if (element instanceof PsiAssignmentExpression assignment && assignment.getOperationTokenType() == JavaTokenType.EQ) { + hlBuilder.registerFix(QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix( + placeRefExpr, + setter, + true + )); + } + } + } + else if (PsiUtil.isAccessedForReading(placeRefExpr)) { + PsiMethod getterPrototype = PropertyUtil.generateGetterPrototype(field); + PsiMethod getter = containingClass.findMethodBySignature(getterPrototype, true); + if (getter != null && PsiUtil.isAccessible(getter, placeRefExpr, accessObjectClass)) { + hlBuilder.registerFix( + QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix(placeRefExpr, getter, false) + ); + } + } } - } } - } } - return message; - } + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkFeature( + PsiElement element, + JavaFeature feature, + LanguageLevel level, + PsiFile file + ) { + if (file.getManager().isInProject(file) && !level.isAtLeast(feature.getMinimumLevel())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(getUnsupportedFeatureMessage(element, feature, level, file)) + .registerFix(QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(feature.getMinimumLevel())) + .registerFix(QuickFixFactory.getInstance().createShowModulePropertiesFix(element)); + } + + return null; + } + + @RequiredReadAction + private static LocalizeValue getUnsupportedFeatureMessage(PsiElement element, JavaFeature feature, LanguageLevel level, PsiFile file) { + String name = feature.getFeatureName(); + LocalizeValue message = JavaCompilationErrorLocalize.insufficientLanguageLevel(name, level.getCompilerComplianceDefaultOption()); + + Module module = element.getModule(); + if (module != null) { + LanguageLevel moduleLanguageLevel = EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); + if (moduleLanguageLevel.isAtLeast(feature.getMinimumLevel())) { + return module.getApplication().getExtensionPoint(FilePropertyPusher.class).computeSafeIfAny( + pusher -> { + if (pusher instanceof JavaLanguageLevelPusher languageLevelPusher) { + LocalizeValue newMessage = + languageLevelPusher.getInconsistencyLanguageLevelMessage(message, element, level, file); + if (newMessage.isNotEmpty()) { + return newMessage; + } + } + return null; + }, + message + ); + } + } + + return message; + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorFactoryImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorFactoryImpl.java new file mode 100644 index 0000000000..c52e29c819 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorFactoryImpl.java @@ -0,0 +1,40 @@ +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; + +import com.intellij.java.language.psi.PsiImportHolder; +import com.intellij.java.language.psi.PsiResolveHelper; +import consulo.annotation.component.ExtensionImpl; +import consulo.language.editor.rawHighlight.HighlightVisitor; +import consulo.language.editor.rawHighlight.HighlightVisitorFactory; +import consulo.language.inject.InjectedLanguageManager; +import consulo.language.psi.PsiFile; +import jakarta.inject.Inject; + + +/** + * @author VISTALL + * @since 25/03/2023 + */ +@ExtensionImpl +public class HighlightVisitorFactoryImpl implements HighlightVisitorFactory +{ + private final PsiResolveHelper myResolveHelper; + + @Inject + public HighlightVisitorFactoryImpl(PsiResolveHelper resolveHelper) + { + myResolveHelper = resolveHelper; + } + + @Override + public boolean suitableForFile(PsiFile file) + { + // both PsiJavaFile and PsiCodeFragment must match + return file instanceof PsiImportHolder && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file); + } + + @Override + public HighlightVisitor createVisitor() + { + return new HighlightVisitorImpl(myResolveHelper); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index f1798fe7fa..4454f364be 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -18,9 +18,8 @@ import com.intellij.java.analysis.codeInsight.intention.QuickFixFactory; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.DefaultHighlightUtil; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.JavaHighlightInfoTypes; -import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.HighlightUtil.Feature; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.controlFlow.ControlFlowUtil; import com.intellij.java.language.impl.psi.impl.source.javadoc.PsiDocMethodOrFieldRef; import com.intellij.java.language.impl.psi.impl.source.resolve.JavaResolveUtil; @@ -34,1982 +33,2235 @@ import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.javadoc.PsiDocTagValue; import com.intellij.java.language.psi.util.*; -import consulo.annotation.component.ExtensionImpl; -import consulo.application.ApplicationManager; -import consulo.application.ApplicationProperties; +import consulo.annotation.access.RequiredReadAction; import consulo.application.dumb.IndexNotReadyException; import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressManager; import consulo.colorScheme.TextAttributesScheme; import consulo.document.Document; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.DaemonCodeAnalyzer; import consulo.language.editor.Pass; -import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; -import consulo.language.editor.intention.QuickFixAction; -import consulo.language.editor.rawHighlight.*; +import consulo.language.editor.rawHighlight.HighlightInfo; +import consulo.language.editor.rawHighlight.HighlightInfoHolder; +import consulo.language.editor.rawHighlight.HighlightInfoType; +import consulo.language.editor.rawHighlight.HighlightVisitor; import consulo.language.inject.InjectedLanguageManager; import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.MostlySingularMultiMap; import consulo.util.collection.primitive.objects.ObjectIntMap; import consulo.util.collection.primitive.objects.ObjectMaps; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; import jakarta.inject.Inject; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static consulo.util.lang.ObjectUtil.notNull; -@ExtensionImpl public class HighlightVisitorImpl extends JavaElementVisitor implements HighlightVisitor { - private final PsiResolveHelper myResolveHelper; - - private HighlightInfoHolder myHolder; - private RefCountHolder myRefCountHolder; - private LanguageLevel myLanguageLevel; - private JavaSdkVersion myJavaSdkVersion; - - @SuppressWarnings("StatefulEp") - private PsiFile myFile; - @SuppressWarnings("StatefulEp") - private PsiJavaModule myJavaModule; - - // map codeBlock->List of PsiReferenceExpression of uninitialized final variables - private final Map> myUninitializedVarProblems = new HashMap<>(); - // map codeBlock->List of PsiReferenceExpression of extra initialization of final variable - private final Map> myFinalVarProblems = new HashMap<>(); - - // value==1: no info if the parameter was reassigned (but the parameter is present in current file), value==2: parameter was reassigned - private final ObjectIntMap myReassignedParameters = ObjectMaps.newObjectIntHashMap(); - - private final Map> mySingleImportedClasses = new HashMap<>(); - private final Map> mySingleImportedFields = new HashMap<>(); - - private final PsiElementVisitor REGISTER_REFERENCES_VISITOR = new PsiRecursiveElementWalkingVisitor() { - @Override - public void visitElement(PsiElement element) { - super.visitElement(element); - for (PsiReference reference : element.getReferences()) { - PsiElement resolved = reference.resolve(); - if (resolved instanceof PsiNamedElement) { - myRefCountHolder.registerLocallyReferenced((PsiNamedElement) resolved); - if (resolved instanceof PsiMember) { - myRefCountHolder.registerReference(reference, new CandidateInfo(resolved, PsiSubstitutor.EMPTY)); - } - } - } - } - }; - private final Map> myDuplicateMethods = new HashMap<>(); - private final Set myOverrideEquivalentMethodsVisitedClasses = new HashSet<>(); - - private static class Holder { - private static final boolean CHECK_ELEMENT_LEVEL = ApplicationManager.getApplication().isUnitTestMode() || ApplicationProperties.isInSandbox(); - } - - @Inject - public HighlightVisitorImpl(@Nonnull PsiResolveHelper resolveHelper) { - myResolveHelper = resolveHelper; - } - - @Nonnull - private MostlySingularMultiMap getDuplicateMethods(@Nonnull PsiClass aClass) { - MostlySingularMultiMap signatures = myDuplicateMethods.get(aClass); - if (signatures == null) { - signatures = new MostlySingularMultiMap<>(); - for (PsiMethod method : aClass.getMethods()) { - if (method instanceof ExternallyDefinedPsiElement) { - continue; // ignore aspectj-weaved methods; they are checked elsewhere - } - MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); - signatures.add(signature, method); - } - - myDuplicateMethods.put(aClass, signatures); - } - return signatures; - } - - @Nonnull - @Override - @SuppressWarnings("MethodDoesntCallSuperMethod") - public HighlightVisitorImpl clone() { - return new HighlightVisitorImpl(myResolveHelper); - } - - @Override - public boolean suitableForFile(@Nonnull PsiFile file) { - // both PsiJavaFile and PsiCodeFragment must match - return file instanceof PsiImportHolder && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file); - } - - @Override - public void visit(@Nonnull PsiElement element) { - if (Holder.CHECK_ELEMENT_LEVEL) { - ((CheckLevelHighlightInfoHolder) myHolder).enterLevel(element); - element.accept(this); - ((CheckLevelHighlightInfoHolder) myHolder).enterLevel(null); - } else { - element.accept(this); - } - } - - private void registerReferencesFromInjectedFragments(@Nonnull PsiElement element) { - InjectedLanguageManager manager = InjectedLanguageManager.getInstance(myFile.getProject()); - manager.enumerateEx(element, myFile, false, (injectedPsi, places) -> injectedPsi.accept(REGISTER_REFERENCES_VISITOR)); - } - - @Override - public boolean analyze(@Nonnull PsiFile file, boolean updateWholeFile, @Nonnull HighlightInfoHolder holder, @Nonnull Runnable highlight) { - try { - prepare(Holder.CHECK_ELEMENT_LEVEL ? new CheckLevelHighlightInfoHolder(file, holder) : holder, file); - if (updateWholeFile) { - ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); - if (progress == null) throw new IllegalStateException("Must be run under progress"); - Project project = file.getProject(); - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - TextRange dirtyScope = document == null ? null : DaemonCodeAnalyzer.getInstance(project).getFileStatusMap().getFileDirtyScope(document, Pass.UPDATE_ALL); - if (dirtyScope == null) dirtyScope = file.getTextRange(); - RefCountHolder refCountHolder = RefCountHolder.get(file, dirtyScope); - if (refCountHolder == null) { - // RefCountHolder was GCed and queried again for some inner code block - // "highlight.run()" can't fill it again because it runs for only a subset of elements, - // so we have to restart the daemon for the whole file - return false; - } - myRefCountHolder = refCountHolder; - - highlight.run(); - ProgressManager.checkCanceled(); - refCountHolder.storeReadyHolder(file); - if (document != null) { - new PostHighlightingVisitor(file, document, refCountHolder).collectHighlights(holder, progress); - } - } else { - myRefCountHolder = null; - highlight.run(); - } - } finally { - myUninitializedVarProblems.clear(); - myFinalVarProblems.clear(); - mySingleImportedClasses.clear(); - mySingleImportedFields.clear(); - myReassignedParameters.clear(); - - myRefCountHolder = null; - myJavaModule = null; - myFile = null; - myHolder = null; - myDuplicateMethods.clear(); - myOverrideEquivalentMethodsVisitedClasses.clear(); - } - - return true; - } - - protected void prepareToRunAsInspection(@Nonnull HighlightInfoHolder holder) { - prepare(holder, holder.getContextFile()); - } - - private void prepare(HighlightInfoHolder holder, PsiFile file) { - myHolder = holder; - myFile = file; - myLanguageLevel = PsiUtil.getLanguageLevel(file); - myJavaSdkVersion = notNull(JavaVersionService.getInstance().getJavaSdkVersion(file), JavaSdkVersion.fromLanguageLevel(myLanguageLevel)); - myJavaModule = myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9) ? ModuleHighlightUtil.getModuleDescriptor(file) : null; - } - - @Override - public void visitElement(PsiElement element) { - if (myRefCountHolder != null && myFile instanceof ServerPageFile) { - // in JSP, XmlAttributeValue may contain java references - try { - for (PsiReference reference : element.getReferences()) { - if (reference instanceof PsiJavaReference) { - PsiJavaReference psiJavaReference = (PsiJavaReference) reference; - myRefCountHolder.registerReference(psiJavaReference, psiJavaReference.advancedResolve(false)); - } - } - } catch (IndexNotReadyException ignored) { - } - } - - if (!(myFile instanceof ServerPageFile)) { - myHolder.add(DefaultHighlightUtil.checkBadCharacter(element)); - } - } - - @Nullable - public static JavaResolveResult resolveJavaReference(@Nonnull PsiReference reference) { - if (reference instanceof PsiJavaReference) { - PsiJavaReference psiJavaReference = (PsiJavaReference) reference; - return psiJavaReference.advancedResolve(false); - } - if (reference instanceof PsiPolyVariantReference && - reference instanceof ResolvingHint && ((ResolvingHint) reference).canResolveTo(PsiClass.class)) { - ResolveResult[] resolve = ((PsiPolyVariantReference) reference).multiResolve(false); - if (resolve.length == 1 && resolve[0] instanceof JavaResolveResult) { - return (JavaResolveResult) resolve[0]; - } - } - return null; - } - - @Override - public void visitAnnotation(PsiAnnotation annotation) { - super.visitAnnotation(annotation); - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(annotation, Feature.ANNOTATIONS)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkApplicability(annotation, myLanguageLevel, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkAnnotationType(annotation)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkMissingAttributes(annotation)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkTargetAnnotationDuplicates(annotation)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkFunctionalInterface(annotation, myLanguageLevel)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation)); - } - if (JavaClassNames.JAVA_LANG_OVERRIDE.equals(annotation.getQualifiedName())) { - PsiAnnotationOwner owner = annotation.getOwner(); - PsiElement parent = owner instanceof PsiModifierList ? ((PsiModifierList) owner).getParent() : null; - if (parent instanceof PsiMethod) { - myHolder.add(GenericsHighlightUtil.checkOverrideAnnotation((PsiMethod) parent, annotation, myLanguageLevel)); - } - } - } - - @Override - public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue initializer) { - PsiMethod method = null; - - PsiElement parent = initializer.getParent(); - if (parent instanceof PsiNameValuePair) { - PsiReference reference = parent.getReference(); - if (reference != null) { - method = (PsiMethod) reference.resolve(); - } - } else if (PsiUtil.isAnnotationMethod(parent)) { - method = (PsiMethod) parent; - } - - if (method != null) { - PsiType type = method.getReturnType(); - if (type instanceof PsiArrayType) { - type = ((PsiArrayType) type).getComponentType(); - PsiAnnotationMemberValue[] initializers = initializer.getInitializers(); - for (PsiAnnotationMemberValue initializer1 : initializers) { - myHolder.add(AnnotationsHighlightUtil.checkMemberValueType(initializer1, type)); - } - } - } - } - - @Override - public void visitAnnotationMethod(PsiAnnotationMethod method) { - PsiType returnType = method.getReturnType(); - PsiAnnotationMemberValue value = method.getDefaultValue(); - if (returnType != null && value != null) { - myHolder.add(AnnotationsHighlightUtil.checkMemberValueType(value, returnType)); - } - - myHolder.add(AnnotationsHighlightUtil.checkValidAnnotationType(method.getReturnType(), method.getReturnTypeElement())); - final PsiClass aClass = method.getContainingClass(); - myHolder.add(AnnotationsHighlightUtil.checkCyclicMemberType(method.getReturnTypeElement(), aClass)); - myHolder.add(AnnotationsHighlightUtil.checkClashesWithSuperMethods(method)); + private final PsiResolveHelper myResolveHelper; + + private HighlightInfoHolder myHolder; + private RefCountHolder myRefCountHolder; + private LanguageLevel myLanguageLevel; + private JavaSdkVersion myJavaSdkVersion; + + @SuppressWarnings("StatefulEp") + private PsiFile myFile; + @SuppressWarnings("StatefulEp") + private PsiJavaModule myJavaModule; + + // map codeBlock->List of PsiReferenceExpression of uninitialized final variables + private final Map> myUninitializedVarProblems = new HashMap<>(); + // map codeBlock->List of PsiReferenceExpression of extra initialization of final variable + private final Map> myFinalVarProblems = new HashMap<>(); + + // value==1: no info if the parameter was reassigned (but the parameter is present in current file), value==2: parameter was reassigned + private final ObjectIntMap myReassignedParameters = ObjectMaps.newObjectIntHashMap(); + + private final Map> mySingleImportedClasses = new HashMap<>(); + private final Map> mySingleImportedFields = new HashMap<>(); + + private final PsiElementVisitor REGISTER_REFERENCES_VISITOR = new PsiRecursiveElementWalkingVisitor() { + @Override + @RequiredReadAction + public void visitElement(PsiElement element) { + super.visitElement(element); + for (PsiReference reference : element.getReferences()) { + if (reference.resolve() instanceof PsiNamedElement namedElem) { + myRefCountHolder.registerLocallyReferenced(namedElem); + if (namedElem instanceof PsiMember member) { + myRefCountHolder.registerReference(reference, new CandidateInfo(member, PsiSubstitutor.EMPTY)); + } + } + } + } + }; + private final Map> myDuplicateMethods = new HashMap<>(); + private final Set myOverrideEquivalentMethodsVisitedClasses = new HashSet<>(); + + @Inject + public HighlightVisitorImpl(PsiResolveHelper resolveHelper) { + myResolveHelper = resolveHelper; + } + + private MostlySingularMultiMap getDuplicateMethods(PsiClass aClass) { + MostlySingularMultiMap signatures = myDuplicateMethods.get(aClass); + if (signatures == null) { + signatures = new MostlySingularMultiMap<>(); + for (PsiMethod method : aClass.getMethods()) { + if (method instanceof ExternallyDefinedPsiElement) { + continue; // ignore aspectj-weaved methods; they are checked elsewhere + } + MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); + signatures.add(signature, method); + } - if (!myHolder.hasErrorResults() && aClass != null) { - myHolder.add(HighlightMethodUtil.checkDuplicateMethod(aClass, method, getDuplicateMethods(aClass))); - } - } - - @Override - public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { - super.visitArrayInitializerExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkArrayInitializerApplicable(expression)); - } - if (!(expression.getParent() instanceof PsiNewExpression)) { - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkGenericArrayCreation(expression, expression.getType())); - } - } - } - - @Override - public void visitAssignmentExpression(PsiAssignmentExpression assignment) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkAssignmentCompatibleTypes(assignment)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkAssignmentOperatorApplicable(assignment)); - } - if (!myHolder.hasErrorResults()) { - visitExpression(assignment); + myDuplicateMethods.put(aClass, signatures); + } + return signatures; } - } - @Override - public void visitPolyadicExpression(PsiPolyadicExpression expression) { - super.visitPolyadicExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkPolyadicOperatorApplicable(expression)); + @Override + public void visit(PsiElement element) { + element.accept(this); } - } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - myHolder.add(checkFeature(expression, Feature.LAMBDA_EXPRESSIONS)); - final PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (parent instanceof PsiExpressionStatement) { - return; - } - if (!myHolder.hasErrorResults() && !LambdaUtil.isValidLambdaContext(parent)) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip("Lambda expression not expected here").create()); + @RequiredReadAction + private void registerReferencesFromInjectedFragments(PsiElement element) { + InjectedLanguageManager manager = InjectedLanguageManager.getInstance(myFile.getProject()); + manager.enumerateEx(element, myFile, false, (injectedPsi, places) -> injectedPsi.accept(REGISTER_REFERENCES_VISITOR)); } - PsiType functionalInterfaceType = null; - if (!myHolder.hasErrorResults()) { - functionalInterfaceType = expression.getFunctionalInterfaceType(); - if (functionalInterfaceType != null) { - final String notFunctionalMessage = LambdaHighlightingUtil.checkInterfaceFunctional(functionalInterfaceType); - if (notFunctionalMessage != null) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(notFunctionalMessage).create()); - } else { - checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType); + @Override + @RequiredReadAction + public boolean analyze( + PsiFile file, + boolean updateWholeFile, + HighlightInfoHolder holder, + Runnable highlight + ) { + try { + prepare(holder, file); + if (updateWholeFile) { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress == null) { + throw new IllegalStateException("Must be run under progress"); + } + Project project = file.getProject(); + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + TextRange dirtyScope = document == null ? null : DaemonCodeAnalyzer.getInstance(project) + .getFileStatusMap() + .getFileDirtyScope(document, Pass.UPDATE_ALL); + if (dirtyScope == null) { + dirtyScope = file.getTextRange(); + } + RefCountHolder refCountHolder = RefCountHolder.get(file, dirtyScope); + if (refCountHolder == null) { + // RefCountHolder was GCed and queried again for some inner code block + // "highlight.run()" can't fill it again because it runs for only a subset of elements, + // so we have to restart the daemon for the whole file + return false; + } + myRefCountHolder = refCountHolder; + + highlight.run(); + ProgressManager.checkCanceled(); + refCountHolder.storeReadyHolder(file); + if (document != null) { + new PostHighlightingVisitor(file, document, refCountHolder).collectHighlights(holder, progress); + } + } + else { + myRefCountHolder = null; + highlight.run(); + } } - } else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip("Cannot infer functional interface type").create()); - } - } - - if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { - String parentInferenceErrorMessage = null; - final PsiCallExpression callExpression = parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCallExpression ? (PsiCallExpression) parent.getParent() : null; - final JavaResolveResult containingCallResolveResult = callExpression != null ? callExpression.resolveMethodGenerics() : null; - if (containingCallResolveResult instanceof MethodCandidateInfo) { - parentInferenceErrorMessage = ((MethodCandidateInfo) containingCallResolveResult).getInferenceErrorMessage(); - } - final Map returnErrors = LambdaUtil.checkReturnTypeCompatible(expression, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType)); - if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(parentInferenceErrorMessage).create()); - } else if (returnErrors != null) { - for (Map.Entry entry : returnErrors.entrySet()) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(entry.getKey()).descriptionAndTooltip(entry.getValue()).create()); + finally { + myUninitializedVarProblems.clear(); + myFinalVarProblems.clear(); + mySingleImportedClasses.clear(); + mySingleImportedFields.clear(); + myReassignedParameters.clear(); + + myRefCountHolder = null; + myJavaModule = null; + myFile = null; + myHolder = null; + myDuplicateMethods.clear(); + myOverrideEquivalentMethodsVisitedClasses.clear(); } - } - } - if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { - final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); - if (interfaceMethod != null) { - final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); - myHolder.add(LambdaHighlightingUtil.checkParametersCompatible(expression, parameters, LambdaUtil.getSubstitutor(interfaceMethod, resolveResult))); - } + return true; } - if (!myHolder.hasErrorResults()) { - final PsiElement body = expression.getBody(); - if (body instanceof PsiCodeBlock) { - myHolder.add(HighlightControlFlowUtil.checkUnreachableStatement((PsiCodeBlock) body)); - } + @RequiredReadAction + protected void prepareToRunAsInspection(HighlightInfoHolder holder) { + prepare(holder, holder.getContextFile()); } - } - @Override - public void visitBreakStatement(PsiBreakStatement statement) { - super.visitBreakStatement(statement); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findExitedStatement())); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkBreakOutsideLoop(statement)); + @RequiredReadAction + private void prepare(HighlightInfoHolder holder, PsiFile file) { + myHolder = holder; + myFile = file; + myLanguageLevel = PsiUtil.getLanguageLevel(file); + myJavaSdkVersion = + notNull(JavaVersionService.getInstance().getJavaSdkVersion(file), JavaSdkVersion.fromLanguageLevel(myLanguageLevel)); + myJavaModule = myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9) ? ModuleHighlightUtil.getModuleDescriptor(file) : null; } - } - @Override - public void visitClass(PsiClass aClass) { - super.visitClass(aClass); - if (aClass instanceof PsiSyntheticClass) { - return; - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkInterfaceMultipleInheritance(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.areSupersAccessible(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkDuplicateTopLevelClass(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkEnumMustNotBeLocal(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkEnumWithoutConstantsCantHaveAbstractMethods(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkImplicitThisReferenceBeforeSuper(aClass, myJavaSdkVersion)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkClassAndPackageConflict(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkPublicClassInRightFile(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel)); - } - } - - @Override - public void visitClassInitializer(PsiClassInitializer initializer) { - super.visitClassInitializer(initializer); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkInitializerCompleteNormally(initializer)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkUnreachableStatement(initializer.getBody())); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkThingNotAllowedInInterface(initializer, initializer.getContainingClass())); - } - } - - @Override - public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { - super.visitClassObjectAccessExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkClassObjectAccessExpression(expression)); - } - } - - @Override - public void visitComment(PsiComment comment) { - super.visitComment(comment); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnclosedComment(comment)); - } - if (myRefCountHolder != null && !myHolder.hasErrorResults()) { - registerReferencesFromInjectedFragments(comment); - } - } + @Override + public void visitElement(PsiElement element) { + if (myRefCountHolder != null && myFile instanceof ServerPageFile) { + // in JSP, XmlAttributeValue may contain java references + try { + for (PsiReference reference : element.getReferences()) { + if (reference instanceof PsiJavaReference javaRef) { + myRefCountHolder.registerReference(javaRef, javaRef.advancedResolve(false)); + } + } + } + catch (IndexNotReadyException ignored) { + } + } - @Override - public void visitContinueStatement(PsiContinueStatement statement) { - super.visitContinueStatement(statement); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findContinuedStatement())); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkContinueOutsideLoop(statement)); + if (!(myFile instanceof ServerPageFile)) { + myHolder.add(DefaultHighlightUtil.checkBadCharacter(element)); + } } - } - @Override - public void visitJavaToken(PsiJavaToken token) { - super.visitJavaToken(token); - if (!myHolder.hasErrorResults() && token.getTokenType() == JavaTokenType.RBRACE && token.getParent() instanceof PsiCodeBlock) { - - final PsiElement gParent = token.getParent().getParent(); - final PsiCodeBlock codeBlock; - final PsiType returnType; - if (gParent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) gParent; - codeBlock = method.getBody(); - returnType = method.getReturnType(); - } else if (gParent instanceof PsiLambdaExpression) { - final PsiElement body = ((PsiLambdaExpression) gParent).getBody(); - if (!(body instanceof PsiCodeBlock)) { - return; + @Nullable + @RequiredReadAction + public static JavaResolveResult resolveJavaReference(PsiReference reference) { + if (reference instanceof PsiJavaReference javaRef) { + return javaRef.advancedResolve(false); + } + if (reference instanceof PsiPolyVariantReference polyRef + && reference instanceof ResolvingHint hint + && hint.canResolveTo(PsiClass.class)) { + ResolveResult[] resolve = polyRef.multiResolve(false); + if (resolve.length == 1 && resolve[0] instanceof JavaResolveResult resolveResult) { + return resolveResult; + } } - codeBlock = (PsiCodeBlock) body; - returnType = LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression) gParent); - } else { - return; - } - myHolder.add(HighlightControlFlowUtil.checkMissingReturnStatement(codeBlock, returnType)); + return null; } - } - @Override - public void visitDocComment(PsiDocComment comment) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnclosedComment(comment)); + @Override + @RequiredReadAction + public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); + if (!myHolder.hasErrorResults()) { + add(checkFeature(annotation, JavaFeature.ANNOTATIONS)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkApplicability(annotation, myLanguageLevel, myFile)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkAnnotationType(annotation)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkMissingAttributes(annotation)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkTargetAnnotationDuplicates(annotation)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkFunctionalInterface(annotation, myLanguageLevel)); + } + if (!myHolder.hasErrorResults()) { + add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation)); + } + if (CommonClassNames.JAVA_LANG_OVERRIDE.equals(annotation.getQualifiedName())) { + if (annotation.getOwner() instanceof PsiModifierList modifierList && modifierList.getParent() instanceof PsiMethod method) { + add(GenericsHighlightUtil.checkOverrideAnnotation(method, annotation, myLanguageLevel)); + } + } } - } - @Override - public void visitDocTagValue(PsiDocTagValue value) { - PsiReference reference = value.getReference(); - if (reference != null) { - PsiElement element = reference.resolve(); - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - if (element instanceof PsiMethod) { - PsiElement nameElement = ((PsiDocMethodOrFieldRef) value).getNameElement(); - if (nameElement != null) { - myHolder.add(HighlightNamesUtil.highlightMethodName((PsiMethod) element, nameElement, false, colorsScheme)); + @Override + @RequiredReadAction + public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue initializer) { + PsiMethod method = null; + + PsiElement parent = initializer.getParent(); + if (parent instanceof PsiNameValuePair) { + PsiReference reference = parent.getReference(); + if (reference != null) { + method = (PsiMethod)reference.resolve(); + } + } + else if (PsiUtil.isAnnotationMethod(parent)) { + method = (PsiMethod)parent; } - } else if (element instanceof PsiParameter) { - myHolder.add(HighlightNamesUtil.highlightVariableName((PsiVariable) element, value.getNavigationElement(), colorsScheme)); - } - } - } - @Override - public void visitEnumConstant(PsiEnumConstant enumConstant) { - super.visitEnumConstant(enumConstant); - if (!myHolder.hasErrorResults()) { - GenericsHighlightUtil.checkEnumConstantForConstructorProblems(enumConstant, myHolder, myJavaSdkVersion); - } - if (!myHolder.hasErrorResults()) { - registerConstructorCall(enumConstant); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnhandledExceptions(enumConstant, null)); + if (method != null) { + PsiType type = method.getReturnType(); + if (type instanceof PsiArrayType arrayType) { + type = arrayType.getComponentType(); + PsiAnnotationMemberValue[] initializers = initializer.getInitializers(); + for (PsiAnnotationMemberValue initializer1 : initializers) { + myHolder.add(AnnotationsHighlightUtil.checkMemberValueType(initializer1, type)); + } + } + } } - } - @Override - public void visitEnumConstantInitializer(PsiEnumConstantInitializer enumConstantInitializer) { - super.visitEnumConstantInitializer(enumConstantInitializer); - if (!myHolder.hasErrorResults()) { - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(enumConstantInitializer); - myHolder.add(HighlightClassUtil.checkClassMustBeAbstract(enumConstantInitializer, textRange)); - } - } + @Override + @RequiredReadAction + public void visitAnnotationMethod(PsiAnnotationMethod method) { + PsiType returnType = method.getReturnType(); + PsiAnnotationMemberValue value = method.getDefaultValue(); + if (returnType != null && value != null) { + myHolder.add(AnnotationsHighlightUtil.checkMemberValueType(value, returnType)); + } - @Override - public void visitExpression(PsiExpression expression) { - ProgressManager.checkCanceled(); // visitLiteralExpression is invoked very often in array initializers + add(AnnotationsHighlightUtil.checkValidAnnotationType(method.getReturnType(), method.getReturnTypeElement())); + PsiClass aClass = method.getContainingClass(); + add(AnnotationsHighlightUtil.checkCyclicMemberType(method.getReturnTypeElement(), aClass)); + add(AnnotationsHighlightUtil.checkClashesWithSuperMethods(method)); - super.visitExpression(expression); - PsiType type = expression.getType(); - if (myHolder.add(HighlightUtil.checkMustBeBoolean(expression, type))) { - return; + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkDuplicateMethod(aClass, method, getDuplicateMethods(aClass))); + } } - if (expression instanceof PsiArrayAccessExpression) { - myHolder.add(HighlightUtil.checkValidArrayAccessExpression((PsiArrayAccessExpression) expression)); + @Override + @RequiredReadAction + public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) { + super.visitArrayInitializerExpression(expression); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkArrayInitializerApplicable(expression)); + } + if (!(expression.getParent() instanceof PsiNewExpression) && !myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkGenericArrayCreation(expression, expression.getType())); + } } - PsiElement parent = expression.getParent(); - if (parent instanceof PsiNewExpression && ((PsiNewExpression) parent).getQualifier() != expression && ((PsiNewExpression) parent).getArrayInitializer() != expression) { - // like in 'new String["s"]' - myHolder.add(HighlightUtil.checkAssignability(PsiType.INT, expression.getType(), expression, expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkCannotWriteToFinal(expression, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkVariableExpected(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(HighlightUtil.checkArrayInitializer(expression, type)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkTernaryOperatorConditionIsBoolean(expression, type)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkAssertOperatorTypes(expression, type)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkSynchronizedExpressionType(expression, type, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkConditionalExpressionBranchTypesMatch(expression, type)); - } - if (!myHolder.hasErrorResults() && parent instanceof PsiThrowStatement && ((PsiThrowStatement) parent).getException() == expression) { - myHolder.add(HighlightUtil.checkMustBeThrowable(type, expression, true)); + @Override + @RequiredReadAction + public void visitAssignmentExpression(PsiAssignmentExpression assignment) { + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkAssignmentCompatibleTypes(assignment)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkAssignmentOperatorApplicable(assignment)); + } + if (!myHolder.hasErrorResults()) { + visitExpression(assignment); + } } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkConstantExpression(expression)); - } - if (!myHolder.hasErrorResults() && parent instanceof PsiForeachStatement && ((PsiForeachStatement) parent).getIteratedValue() == expression) { - myHolder.add(GenericsHighlightUtil.checkForeachExpressionTypeIsIterable(expression)); + @Override + @RequiredReadAction + public void visitPolyadicExpression(PsiPolyadicExpression expression) { + super.visitPolyadicExpression(expression); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkPolyadicOperatorApplicable(expression)); + } } - } - @Override - public void visitExpressionList(PsiExpressionList list) { - super.visitExpressionList(list); - PsiElement parent = list.getParent(); - if (parent instanceof PsiMethodCallExpression) { - PsiMethodCallExpression expression = (PsiMethodCallExpression) parent; - if (expression.getArgumentList() == list) { - PsiReferenceExpression referenceExpression = expression.getMethodExpression(); - JavaResolveResult[] results = resolveOptimised(referenceExpression); - if (results == null) { - return; + @Override + @RequiredReadAction + public void visitLambdaExpression(PsiLambdaExpression expression) { + add(checkFeature(expression, JavaFeature.LAMBDA_EXPRESSIONS)); + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (parent instanceof PsiExpressionStatement) { + return; + } + if (!myHolder.hasErrorResults() && !LambdaUtil.isValidLambdaContext(parent)) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lambdaNotExpected()) + .create()); } - JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - PsiElement resolved = result.getElement(); - if ((!result.isAccessible() || !result.isStaticsScopeCorrect()) && !HighlightMethodUtil.isDummyConstructorCall(expression, myResolveHelper, list, referenceExpression) && - // this check is for fake expression from JspMethodCallImpl - referenceExpression.getParent() == expression) { - try { - if (PsiTreeUtil.findChildrenOfType(expression.getArgumentList(), PsiLambdaExpression.class).isEmpty()) { - myHolder.add(HighlightMethodUtil.checkAmbiguousMethodCallArguments(referenceExpression, results, list, resolved, result, expression, myResolveHelper, list)); + PsiType functionalInterfaceType = null; + if (!myHolder.hasErrorResults()) { + functionalInterfaceType = expression.getFunctionalInterfaceType(); + if (functionalInterfaceType != null) { + LocalizeValue notFunctionalMessage = LambdaHighlightingUtil.checkInterfaceFunctional(functionalInterfaceType); + if (notFunctionalMessage.isNotEmpty()) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(notFunctionalMessage) + .create()); + } + else { + checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType); + } + } + else if (LambdaUtil.getFunctionalInterfaceType(expression, true) != null) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lambdaTypeInferenceFailure()) + .create()); } - } catch (IndexNotReadyException ignored) { - } } - } - } - } - @Override - public void visitField(PsiField field) { - super.visitField(field); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkFinalFieldInitialized(field)); - } - } - - @Override - public void visitForStatement(PsiForStatement statement) { - myHolder.add(HighlightUtil.checkForStatement(statement)); - } + if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { + String parentInferenceErrorMessage = null; + PsiCallExpression callExpression = + parent instanceof PsiExpressionList && parent.getParent() instanceof PsiCallExpression call ? call : null; + JavaResolveResult containingCallResolveResult = callExpression != null ? callExpression.resolveMethodGenerics() : null; + if (containingCallResolveResult instanceof MethodCandidateInfo methodCandidateInfo) { + parentInferenceErrorMessage = methodCandidateInfo.getInferenceErrorMessage(); + } + Map returnErrors = + LambdaUtil.checkReturnTypeCompatible(expression, LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType)); + if (parentInferenceErrorMessage != null && (returnErrors == null || !returnErrors.containsValue(parentInferenceErrorMessage))) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(parentInferenceErrorMessage) + .create()); + } + else if (returnErrors != null) { + for (Map.Entry entry : returnErrors.entrySet()) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(entry.getKey()) + .descriptionAndTooltip(entry.getValue()) + .create()); + } + } + } - @Override - public void visitForeachStatement(PsiForeachStatement statement) { - myHolder.add(checkFeature(statement, Feature.FOR_EACH)); - } + if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { + PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod != null) { + PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); + myHolder.add(LambdaHighlightingUtil.checkParametersCompatible( + expression, + parameters, + LambdaUtil.getSubstitutor(interfaceMethod, resolveResult) + )); + } + } - @Override - public void visitImportStaticStatement(PsiImportStaticStatement statement) { - myHolder.add(checkFeature(statement, Feature.STATIC_IMPORTS)); - if (!myHolder.hasErrorResults()) { - myHolder.add(ImportsHighlightUtil.checkStaticOnDemandImportResolvesToClass(statement)); + if (!myHolder.hasErrorResults() && expression.getBody() instanceof PsiCodeBlock bodyCodeBlock) { + add(HighlightControlFlowUtil.checkUnreachableStatement(bodyCodeBlock)); + } } - } - - @Override - public void visitIdentifier(final PsiIdentifier identifier) { - TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - PsiElement parent = identifier.getParent(); - if (parent instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) parent; - myHolder.add(HighlightUtil.checkVariableAlreadyDefined(variable)); - - if (variable.getInitializer() == null) { - final PsiElement child = variable.getLastChild(); - if (child instanceof PsiErrorElement && child.getPrevSibling() == identifier) { - return; + @Override + @RequiredReadAction + public void visitBreakStatement(PsiBreakStatement statement) { + super.visitBreakStatement(statement); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findExitedStatement())); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkBreakOutsideLoop(statement)); } - } + } - boolean isMethodParameter = variable instanceof PsiParameter && ((PsiParameter) variable).getDeclarationScope() instanceof PsiMethod; - if (isMethodParameter) { - myReassignedParameters.putInt((PsiParameter) variable, 1); // mark param as present in current file - } else { - // method params are highlighted in visitMethod since we should make sure the method body was visited before - if (HighlightControlFlowUtil.isReassigned(variable, myFinalVarProblems)) { - myHolder.add(HighlightNamesUtil.highlightReassignedVariable(variable, identifier)); - } else { - myHolder.add(HighlightNamesUtil.highlightVariableName(variable, identifier, colorsScheme)); - } - } - } else if (parent instanceof PsiClass) { - PsiClass aClass = (PsiClass) parent; - if (aClass.isAnnotationType()) { - myHolder.add(checkFeature(identifier, Feature.ANNOTATIONS)); - } - - myHolder.add(HighlightClassUtil.checkClassAlreadyImported(aClass, identifier)); - if (!(parent instanceof PsiAnonymousClass) && aClass.getNameIdentifier() == identifier) { - myHolder.add(HighlightNamesUtil.highlightClassName(aClass, identifier, colorsScheme)); - } - if (!myHolder.hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - myHolder.add(GenericsHighlightUtil.checkUnrelatedDefaultMethods(aClass, identifier)); - } - - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkUnrelatedConcrete(aClass, identifier)); - } - } else if (parent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) parent; - if (method.isConstructor()) { - myHolder.add(HighlightMethodUtil.checkConstructorName(method)); - } - myHolder.add(HighlightNamesUtil.highlightMethodName(method, identifier, true, colorsScheme)); - final PsiClass aClass = method.getContainingClass(); - if (aClass != null) { - myHolder.add(GenericsHighlightUtil.checkDefaultMethodOverrideEquivalentToObjectNonPrivate(myLanguageLevel, aClass, method, identifier)); - } - } - - myHolder.add(HighlightUtil.checkUnderscore(identifier, myLanguageLevel)); - - super.visitIdentifier(identifier); - } - - @Override - public void visitImportStatement(final PsiImportStatement statement) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkSingleImportClassConflict(statement, mySingleImportedClasses, myFile)); - } - } - - @Override - public void visitImportStaticReferenceElement(@Nonnull PsiImportStaticReferenceElement ref) { - final String refName = ref.getReferenceName(); - final JavaResolveResult[] results = ref.multiResolve(false); - - final PsiElement referenceNameElement = ref.getReferenceNameElement(); - if (results.length == 0) { - final String description = JavaErrorBundle.message("cannot.resolve.symbol", refName); - assert referenceNameElement != null : ref; - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(referenceNameElement).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createSetupJDKFix()); - myHolder.add(info); - } else { - final PsiManager manager = ref.getManager(); - for (JavaResolveResult result : results) { - final PsiElement element = result.getElement(); - - String description = null; - if (element instanceof PsiClass) { - final Pair imported = mySingleImportedClasses.get(refName); - final PsiClass aClass = imported == null ? null : imported.getSecond(); - if (aClass != null && !manager.areElementsEquivalent(aClass, element)) { - //noinspection ConditionalExpressionWithIdenticalBranches - description = imported.first == null ? JavaErrorBundle.message("single.import.class.conflict", refName) : imported.first.equals(ref) ? JavaErrorBundle.message("class.is" + - ".ambiguous.in.single.static.import", refName) : JavaErrorBundle.message("class.is.already.defined.in.single.static.import", refName); - } - mySingleImportedClasses.put(refName, Pair.create(ref, (PsiClass) element)); - } else if (element instanceof PsiField) { - final Pair imported = mySingleImportedFields.get(refName); - final PsiField field = imported == null ? null : imported.getSecond(); - if (field != null && !manager.areElementsEquivalent(field, element)) { - //noinspection ConditionalExpressionWithIdenticalBranches - description = imported.first.equals(ref) ? JavaErrorBundle.message("field.is.ambiguous.in.single.static.import", refName) : JavaErrorBundle.message("field.is.already" + - ".defined.in.single.static.import", refName); - } - mySingleImportedFields.put(refName, Pair.create(ref, (PsiField) element)); - } - - if (description != null) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(description).create()); - } - } - } - if (!myHolder.hasErrorResults()) { - PsiElement resolved = results.length >= 1 ? results[0].getElement() : null; - if (results.length > 1) { - for (int i = 1; i < results.length; i++) { - final PsiElement element = results[i].getElement(); - if (resolved instanceof PsiMethod && !(element instanceof PsiMethod) || resolved instanceof PsiVariable && !(element instanceof PsiVariable) || resolved instanceof PsiClass && ! - (element instanceof PsiClass)) { - resolved = null; - break; - } - } - } - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - if (resolved instanceof PsiClass) { - myHolder.add(HighlightNamesUtil.highlightClassName((PsiClass) resolved, ref, colorsScheme)); - } else { - myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(ref, colorsScheme)); - if (referenceNameElement != null) { - if (resolved instanceof PsiVariable) { - myHolder.add(HighlightNamesUtil.highlightVariableName((PsiVariable) resolved, referenceNameElement, colorsScheme)); - } else if (resolved instanceof PsiMethod) { - myHolder.add(HighlightNamesUtil.highlightMethodName((PsiMethod) resolved, referenceNameElement, false, colorsScheme)); - } - } - } - } - } - - @Override - public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { - super.visitInstanceOfExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkInstanceOfApplicable(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkInstanceOfGenericType(expression)); - } - } - - @Override - public void visitKeyword(PsiKeyword keyword) { - super.visitKeyword(keyword); - PsiElement parent = keyword.getParent(); - String text = keyword.getText(); - if (parent instanceof PsiModifierList) { - PsiModifierList psiModifierList = (PsiModifierList) parent; - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkNotAllowedModifier(keyword, psiModifierList)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkIllegalModifierCombination(keyword, psiModifierList)); - } - if (PsiModifier.ABSTRACT.equals(text) && psiModifierList.getParent() instanceof PsiMethod) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkAbstractMethodInConcreteClass((PsiMethod) psiModifierList.getParent(), keyword)); - } - } - } else if (PsiKeyword.INTERFACE.equals(text) && parent instanceof PsiClass) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkInterfaceCannotBeLocal((PsiClass) parent)); - } - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkStaticDeclarationInInnerClass(keyword)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkIllegalVoidType(keyword)); - } - } - - @Override - public void visitLabeledStatement(PsiLabeledStatement statement) { - super.visitLabeledStatement(statement); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkLabelWithoutStatement(statement)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkLabelAlreadyInUse(statement)); - } - } - - @Override - public void visitLiteralExpression(PsiLiteralExpression expression) { - super.visitLiteralExpression(expression); - if (myHolder.hasErrorResults()) { - return; - } - myHolder.add(HighlightUtil.checkLiteralExpressionParsingError(expression, myLanguageLevel, myFile)); - if (myRefCountHolder != null && !myHolder.hasErrorResults()) { - registerReferencesFromInjectedFragments(expression); - } - - if (myRefCountHolder != null && !myHolder.hasErrorResults()) { - for (PsiReference reference : expression.getReferences()) { - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiMember) { - myRefCountHolder.registerReference(reference, new CandidateInfo(resolve, PsiSubstitutor.EMPTY)); - } - } - } - } - - @Override - public void visitMethod(PsiMethod method) { - super.visitMethod(method); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkUnreachableStatement(method.getBody())); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkConstructorHandleSuperClassExceptions(method)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkRecursiveConstructorInvocation(method)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkSafeVarargsAnnotation(method, myLanguageLevel)); - } - - PsiClass aClass = method.getContainingClass(); - if (!myHolder.hasErrorResults() && method.isConstructor()) { - myHolder.add(HighlightClassUtil.checkThingNotAllowedInInterface(method, aClass)); - } - if (!myHolder.hasErrorResults() && method.hasModifierProperty(PsiModifier.DEFAULT)) { - myHolder.add(checkFeature(method, Feature.EXTENSION_METHODS)); - } - if (!myHolder.hasErrorResults() && aClass != null && aClass.isInterface() && method.hasModifierProperty(PsiModifier.STATIC)) { - myHolder.add(checkFeature(method, Feature.EXTENSION_METHODS)); - } - if (!myHolder.hasErrorResults() && aClass != null) { - myHolder.add(HighlightMethodUtil.checkDuplicateMethod(aClass, method, getDuplicateMethods(aClass))); - } - - // method params are highlighted in visitMethod since we should make sure the method body was visited before - PsiParameter[] parameters = method.getParameterList().getParameters(); - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - - for (PsiParameter parameter : parameters) { - int info = myReassignedParameters.getInt(parameter); - if (info == 0) { - continue; // out of this file - } - - PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); - if (nameIdentifier != null) { - if (info == 2) { // reassigned - myHolder.add(HighlightNamesUtil.highlightReassignedVariable(parameter, nameIdentifier)); - } else { - myHolder.add(HighlightNamesUtil.highlightVariableName(parameter, nameIdentifier, colorsScheme)); - } - } - } - } - - private void highlightReferencedMethodOrClassName(@Nonnull PsiJavaCodeReferenceElement element, PsiElement resolved) { - PsiElement parent = element.getParent(); - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - if (parent instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression) parent).resolveMethod(); - PsiElement methodNameElement = element.getReferenceNameElement(); - if (method != null && methodNameElement != null && !(methodNameElement instanceof PsiKeyword)) { - myHolder.add(HighlightNamesUtil.highlightMethodName(method, methodNameElement, false, colorsScheme)); - myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(element, colorsScheme)); - } - } else if (parent instanceof PsiConstructorCall) { - try { - PsiMethod method = ((PsiConstructorCall) parent).resolveConstructor(); - PsiMember methodOrClass = method != null ? method : resolved instanceof PsiClass ? (PsiClass) resolved : null; - if (methodOrClass != null) { - final PsiElement referenceNameElement = element.getReferenceNameElement(); - if (referenceNameElement != null) { - // exclude type parameters from the highlighted text range - TextRange range = referenceNameElement.getTextRange(); - myHolder.add(HighlightNamesUtil.highlightMethodName(methodOrClass, referenceNameElement, range, colorsScheme, false)); - } - } - } catch (IndexNotReadyException ignored) { - } - } else if (resolved instanceof PsiPackage) { - // highlight package (and following dot) as a class - myHolder.add(HighlightNamesUtil.highlightPackage(resolved, element, colorsScheme)); - } else if (resolved instanceof PsiClass) { - myHolder.add(HighlightNamesUtil.highlightClassName((PsiClass) resolved, element, colorsScheme)); - } - } - - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkEnumSuperConstructorCall(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkSuperQualifierType(myFile.getProject(), expression)); - } - // in case of JSP synthetic method call, do not check - if (myFile.isPhysical() && !myHolder.hasErrorResults()) { - try { - myHolder.add(HighlightMethodUtil.checkMethodCall(expression, myResolveHelper, myLanguageLevel, myJavaSdkVersion, myFile)); - } catch (IndexNotReadyException ignored) { - } - } - - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkConstructorCallMustBeFirstStatement(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression)); - } - - if (!myHolder.hasErrorResults()) { - visitExpression(expression); - } - } - - @Override - public void visitModifierList(PsiModifierList list) { - super.visitModifierList(list); - PsiElement parent = list.getParent(); - if (parent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) parent; - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodCanHaveBody(method, myLanguageLevel)); - } - MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); - if (!method.isConstructor()) { - try { - List superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); - if (!superMethodSignatures.isEmpty()) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodIncompatibleReturnType(methodSignature, superMethodSignatures, true)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodIncompatibleThrows(methodSignature, superMethodSignatures, true, method.getContainingClass())); - } - if (!method.hasModifierProperty(PsiModifier.STATIC)) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodWeakerPrivileges(methodSignature, superMethodSignatures, true, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodOverridesFinal(methodSignature, superMethodSignatures)); - } - } - } - } catch (IndexNotReadyException ignored) { + @Override + @RequiredReadAction + public void visitClass(PsiClass aClass) { + super.visitClass(aClass); + if (aClass instanceof PsiSyntheticClass) { + return; } - } - PsiClass aClass = method.getContainingClass(); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkMethodMustHaveBody(method, aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkConstructorCallsBaseClassConstructor(method, myRefCountHolder, myResolveHelper)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkStaticMethodOverride(method, myFile)); - } - if (!myHolder.hasErrorResults() && aClass != null && myOverrideEquivalentMethodsVisitedClasses.add(aClass)) { - myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(aClass)); - } - } else if (parent instanceof PsiClass) { - PsiClass aClass = (PsiClass) parent; - try { if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkDuplicateNestedClass(aClass)); + myHolder.add(GenericsHighlightUtil.checkInterfaceMultipleInheritance(aClass)); } if (!myHolder.hasErrorResults()) { - TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - myHolder.add(HighlightClassUtil.checkClassMustBeAbstract(aClass, textRange)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkClassDoesNotCallSuperConstructorOrHandleExceptions(aClass, myRefCountHolder, myResolveHelper)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel)); - } - if (!myHolder.hasErrorResults() && myOverrideEquivalentMethodsVisitedClasses.add(aClass)) { - myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkCyclicInheritance(aClass)); - } - } catch (IndexNotReadyException ignored) { - } - } else if (parent instanceof PsiEnumConstant) { - if (!myHolder.hasErrorResults()) { - myHolder.addAll(GenericsHighlightUtil.checkEnumConstantModifierList(list)); - } - } - } - - @Override - public void visitNameValuePair(PsiNameValuePair pair) { - myHolder.add(AnnotationsHighlightUtil.checkNameValuePair(pair)); - if (!myHolder.hasErrorResults()) { - PsiIdentifier nameId = pair.getNameIdentifier(); - if (nameId != null) { - HighlightInfo result = HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.ANNOTATION_ATTRIBUTE_NAME).range(nameId).create(); - myHolder.add(result); - } - } - } - - @Override - public void visitNewExpression(PsiNewExpression expression) { - final PsiType type = expression.getType(); - final PsiClass aClass = PsiUtil.resolveClassInType(type); - myHolder.add(HighlightUtil.checkUnhandledExceptions(expression, null)); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkAnonymousInheritFinal(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkQualifiedNew(expression, type, aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(expression, type, aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkTypeParameterInstantiation(expression)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkInstantiationOfAbstractClass(aClass, expression)); - } - try { - if (!myHolder.hasErrorResults()) { - HighlightMethodUtil.checkNewExpression(expression, type, myHolder, myJavaSdkVersion); - } - } catch (IndexNotReadyException ignored) { - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkEnumInstantiation(expression, aClass)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkGenericArrayCreation(expression, type)); - } - if (!myHolder.hasErrorResults()) { - registerConstructorCall(expression); - } - - if (!myHolder.hasErrorResults()) { - visitExpression(expression); - } - } - - @Override - public void visitPackageStatement(PsiPackageStatement statement) { - super.visitPackageStatement(statement); - myHolder.add(AnnotationsHighlightUtil.checkPackageAnnotationContainingFile(statement, myFile)); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkPackageStatement(statement, myFile, myJavaModule)); - } - } - } - - @Override - public void visitParameter(PsiParameter parameter) { - super.visitParameter(parameter); - - final PsiElement parent = parameter.getParent(); - if (parent instanceof PsiParameterList && parameter.isVarArgs()) { - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(parameter, Feature.VARARGS)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkVarArgParameterIsLast(parameter)); - } - } else if (parent instanceof PsiCatchSection) { - if (!myHolder.hasErrorResults() && parameter.getType() instanceof PsiDisjunctionType) { - myHolder.add(checkFeature(parameter, Feature.MULTI_CATCH)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkCatchParameterIsThrowable(parameter)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(GenericsHighlightUtil.checkCatchParameterIsClass(parameter)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(HighlightUtil.checkCatchTypeIsDisjoint(parameter)); - } - } else if (parent instanceof PsiForeachStatement) { - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkForEachParameterType((PsiForeachStatement) parent, parameter)); - } - } - } - - @Override - public void visitParameterList(PsiParameterList list) { - super.visitParameterList(list); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkAnnotationMethodParameters(list)); - } - } - - @Override - public void visitPostfixExpression(PsiPostfixExpression expression) { - super.visitPostfixExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnaryOperatorApplicable(expression.getOperationSign(), expression.getOperand())); - } - } - - @Override - public void visitPrefixExpression(PsiPrefixExpression expression) { - super.visitPrefixExpression(expression); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnaryOperatorApplicable(expression.getOperationSign(), expression.getOperand())); + add(GenericsHighlightUtil.areSupersAccessible(aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightClassUtil.checkDuplicateTopLevelClass(aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkEnumMustNotBeLocal(aClass)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkEnumWithoutConstantsCantHaveAbstractMethods(aClass)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkImplicitThisReferenceBeforeSuper(aClass, myJavaSdkVersion)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkClassAndPackageConflict(aClass)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkPublicClassInRightFile(aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkTypeParameterOverrideEquivalentMethods(aClass, myLanguageLevel)); + } } - } - private void registerConstructorCall(@Nonnull PsiConstructorCall constructorCall) { - if (myRefCountHolder != null) { - JavaResolveResult resolveResult = constructorCall.resolveMethodGenerics(); - final PsiElement resolved = resolveResult.getElement(); - if (resolved instanceof PsiNamedElement) { - myRefCountHolder.registerLocallyReferenced((PsiNamedElement) resolved); - } - } - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement ref) { - JavaResolveResult result = doVisitReferenceElement(ref); - if (result != null) { - PsiElement resolved = result.getElement(); - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkRawOnParameterizedType(ref, resolved)); - } - if (!myHolder.hasErrorResults() && resolved != null && myJavaModule != null) { - myHolder.add(ModuleHighlightUtil.checkPackageAccessibility(ref, resolved, myJavaModule)); - } + @Override + @RequiredReadAction + public void visitClassInitializer(PsiClassInitializer initializer) { + super.visitClassInitializer(initializer); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightControlFlowUtil.checkInitializerCompleteNormally(initializer)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightControlFlowUtil.checkUnreachableStatement(initializer.getBody())); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkThingNotAllowedInInterface(initializer)); + } } - } - private JavaResolveResult doVisitReferenceElement(@Nonnull PsiJavaCodeReferenceElement ref) { - JavaResolveResult result = resolveOptimised(ref); - if (result == null) { - return null; - } - - PsiElement resolved = result.getElement(); - PsiElement parent = ref.getParent(); - - if (myRefCountHolder != null) { - myRefCountHolder.registerReference(ref, result); - } - - myHolder.add(HighlightUtil.checkReference(ref, result, myFile, myLanguageLevel)); - - if (parent instanceof PsiJavaCodeReferenceElement || ref.isQualified()) { - if (!myHolder.hasErrorResults() && resolved instanceof PsiTypeParameter) { - boolean canSelectFromTypeParameter = myJavaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7); - if (canSelectFromTypeParameter) { - final PsiClass containingClass = PsiTreeUtil.getParentOfType(ref, PsiClass.class); - if (containingClass != null) { - if (PsiTreeUtil.isAncestor(containingClass.getExtendsList(), ref, false) || PsiTreeUtil.isAncestor(containingClass.getImplementsList(), ref, false)) { - canSelectFromTypeParameter = false; - } - } - } - if (!canSelectFromTypeParameter) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).descriptionAndTooltip("Cannot select from a type parameter").range(ref).create()); - } - } + @Override + @RequiredReadAction + public void visitClassObjectAccessExpression(PsiClassObjectAccessExpression expression) { + super.visitClassObjectAccessExpression(expression); + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkClassObjectAccessExpression(expression)); + } } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkAbstractInstantiation(ref)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkExtendsDuplicate(ref, resolved, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkClassExtendsForeignInnerClass(ref, resolved)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkSelectStaticClassFromParameterizedType(resolved, ref)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkParameterizedReferenceTypeArguments(resolved, ref, result.getSubstitutor(), myJavaSdkVersion)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkCannotPassInner(ref)); + @Override + @RequiredReadAction + public void visitComment(PsiComment comment) { + super.visitComment(comment); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnclosedComment(comment)); + } + if (myRefCountHolder != null && !myHolder.hasErrorResults()) { + registerReferencesFromInjectedFragments(comment); + } } - if (resolved != null && parent instanceof PsiReferenceList) { - if (!myHolder.hasErrorResults()) { - PsiReferenceList referenceList = (PsiReferenceList) parent; - myHolder.add(HighlightUtil.checkElementInReferenceList(ref, referenceList, result)); - } + @Override + @RequiredReadAction + public void visitContinueStatement(PsiContinueStatement statement) { + super.visitContinueStatement(statement); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findContinuedStatement())); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkContinueOutsideLoop(statement)); + } } - if (parent instanceof PsiAnonymousClass && ref.equals(((PsiAnonymousClass) parent).getBaseClassReference()) && myOverrideEquivalentMethodsVisitedClasses.add((PsiClass) parent)) { - PsiClass aClass = (PsiClass) parent; - myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(aClass)); + @Override + @RequiredReadAction + public void visitJavaToken(PsiJavaToken token) { + super.visitJavaToken(token); + if (!myHolder.hasErrorResults() + && token.getTokenType() == JavaTokenType.RBRACE + && token.getParent() instanceof PsiCodeBlock tokenCodeBlock) { + PsiElement gParent = tokenCodeBlock.getParent(); + PsiCodeBlock codeBlock; + PsiType returnType; + if (gParent instanceof PsiMethod method) { + codeBlock = method.getBody(); + returnType = method.getReturnType(); + } + else if (gParent instanceof PsiLambdaExpression lambda) { + PsiElement body = lambda.getBody(); + if (!(body instanceof PsiCodeBlock lambdaCodeBlock)) { + return; + } + codeBlock = lambdaCodeBlock; + returnType = LambdaUtil.getFunctionalInterfaceReturnType(lambda); + } + else { + return; + } + myHolder.add(HighlightControlFlowUtil.checkMissingReturnStatement(codeBlock, returnType)); + } } - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - - PsiElement containingClass = PsiTreeUtil.getNonStrictParentOfType(ref, PsiClass.class, PsiLambdaExpression.class); - if ((containingClass instanceof PsiAnonymousClass || containingClass instanceof PsiLambdaExpression) && !PsiTreeUtil.isAncestor(containingClass, variable, false) && !(variable instanceof - PsiField) && (containingClass instanceof PsiLambdaExpression || !PsiTreeUtil.isAncestor(((PsiAnonymousClass) containingClass).getArgumentList(), ref, false))) { - myHolder.add(HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.IMPLICIT_ANONYMOUS_CLASS_PARAMETER).range(ref).create()); - } - - if (variable instanceof PsiParameter && ref instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression) ref)) { - myReassignedParameters.putInt((PsiParameter) variable, 2); - } - - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - if (!variable.hasModifierProperty(PsiModifier.FINAL) && isReassigned(variable)) { - myHolder.add(HighlightNamesUtil.highlightReassignedVariable(variable, ref)); - } else { - PsiElement nameElement = ref.getReferenceNameElement(); - if (nameElement != null) { - myHolder.add(HighlightNamesUtil.highlightVariableName(variable, nameElement, colorsScheme)); + @Override + @RequiredReadAction + public void visitDocComment(PsiDocComment comment) { + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnclosedComment(comment)); } - } - myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(ref, colorsScheme)); - } else { - highlightReferencedMethodOrClassName(ref, resolved); } - if (parent instanceof PsiNewExpression && !(resolved instanceof PsiClass) && resolved instanceof PsiNamedElement && ((PsiNewExpression) parent).getClassOrAnonymousClassReference() == ref) { - String text = JavaErrorBundle.message("cannot.resolve.symbol", ((PsiNamedElement) resolved).getName()); - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(text).create()); + @Override + @RequiredReadAction + public void visitDocTagValue(PsiDocTagValue value) { + PsiReference reference = value.getReference(); + if (reference != null) { + PsiElement element = reference.resolve(); + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + if (element instanceof PsiMethod method) { + PsiElement nameElement = ((PsiDocMethodOrFieldRef)value).getNameElement(); + if (nameElement != null) { + myHolder.add(HighlightNamesUtil.highlightMethodName(method, nameElement, false, colorsScheme)); + } + } + else if (element instanceof PsiParameter parameter) { + myHolder.add(HighlightNamesUtil.highlightVariableName(parameter, value.getNavigationElement(), colorsScheme)); + } + } } - if (!myHolder.hasErrorResults() && resolved instanceof PsiClass) { - final PsiClass aClass = ((PsiClass) resolved).getContainingClass(); - if (aClass != null) { - final PsiElement qualifier = ref.getQualifier(); - final PsiElement place; - if (qualifier instanceof PsiJavaCodeReferenceElement) { - place = ((PsiJavaCodeReferenceElement) qualifier).resolve(); - } else { - if (parent instanceof PsiNewExpression) { - final PsiExpression newQualifier = ((PsiNewExpression) parent).getQualifier(); - place = newQualifier == null ? ref : PsiUtil.resolveClassInType(newQualifier.getType()); - } else { - place = ref; - } + @Override + @RequiredReadAction + public void visitEnumConstant(PsiEnumConstant enumConstant) { + super.visitEnumConstant(enumConstant); + if (!myHolder.hasErrorResults()) { + GenericsHighlightUtil.checkEnumConstantForConstructorProblems(enumConstant, myHolder, myJavaSdkVersion); } - if (place != null && PsiTreeUtil.isAncestor(aClass, place, false) && aClass.hasTypeParameters()) { - myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(ref, place, (PsiClass) resolved)); + if (!myHolder.hasErrorResults()) { + registerConstructorCall(enumConstant); } - } else if (resolved instanceof PsiTypeParameter) { - final PsiTypeParameterListOwner owner = ((PsiTypeParameter) resolved).getOwner(); - if (owner instanceof PsiClass) { - final PsiClass outerClass = (PsiClass) owner; - if (!InheritanceUtil.hasEnclosingInstanceInScope(outerClass, ref, false, false)) { - myHolder.add(HighlightClassUtil.reportIllegalEnclosingUsage(ref, null, (PsiClass) owner, ref)); - } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkUnhandledExceptions(enumConstant, null)); } - } } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkPackageAndClassConflict(ref, myFile)); + @Override + @RequiredReadAction + public void visitEnumConstantInitializer(PsiEnumConstantInitializer enumConstantInitializer) { + super.visitEnumConstantInitializer(enumConstantInitializer); + if (!myHolder.hasErrorResults()) { + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(enumConstantInitializer); + add(HighlightClassUtil.checkClassMustBeAbstract(enumConstantInitializer, textRange)); + } } - return result; - } + @Override + @RequiredReadAction + public void visitExpression(PsiExpression expression) { + ProgressManager.checkCanceled(); // visitLiteralExpression is invoked very often in array initializers + + super.visitExpression(expression); + PsiType type = expression.getType(); + if (add(HighlightUtil.checkMustBeBoolean(expression, type))) { + return; + } + + if (expression instanceof PsiArrayAccessExpression arrayAccess) { + add(HighlightUtil.checkValidArrayAccessExpression(arrayAccess)); + } - @Nullable - private JavaResolveResult resolveOptimised(@Nonnull PsiJavaCodeReferenceElement ref) { - try { - if (ref instanceof PsiReferenceExpressionImpl) { - PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE; - JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(ref, resolver, true, true, myFile); - return results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - } else { - return ref.advancedResolve(true); - } - } catch (IndexNotReadyException e) { - return null; - } - } - - @Nullable - private JavaResolveResult[] resolveOptimised(@Nonnull PsiReferenceExpression expression) { - try { - if (expression instanceof PsiReferenceExpressionImpl) { - PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE; - return JavaResolveUtil.resolveWithContainingFile(expression, resolver, true, true, myFile); - } else { - return expression.multiResolve(true); - } - } catch (IndexNotReadyException e) { - return null; - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression); - - if (!myHolder.hasErrorResults()) { - visitExpression(expression); - if (myHolder.hasErrorResults()) { - return; - } - } - - JavaResolveResult[] results = resolveOptimised(expression); - if (results == null) { - return; - } - JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - - PsiElement resolved = result.getElement(); - if (resolved instanceof PsiVariable && resolved.getContainingFile() == expression.getContainingFile()) { - if (!myHolder.hasErrorResults()) { - try { - myHolder.add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage(expression, (PsiVariable) resolved, myUninitializedVarProblems, myFile)); - } catch (IndexNotReadyException ignored) { + PsiElement parent = expression.getParent(); + if (parent instanceof PsiNewExpression newExpr + && newExpr.getQualifier() != expression + && newExpr.getArrayInitializer() != expression) { + // like in 'new String["s"]' + add(HighlightUtil.checkAssignability(PsiType.INT, expression.getType(), expression, expression)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightControlFlowUtil.checkCannotWriteToFinal(expression, myFile)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkVariableExpected(expression)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(HighlightUtil.checkArrayInitializer(expression, type)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkTernaryOperatorConditionIsBoolean(expression, type)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkAssertOperatorTypes(expression, type)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkSynchronizedExpressionType(expression, type, myFile)); } - } - PsiVariable variable = (PsiVariable) resolved; - boolean isFinal = variable.hasModifierProperty(PsiModifier.FINAL); - if (isFinal && !variable.hasInitializer()) { if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo(variable, expression, myFinalVarProblems)); + add(HighlightUtil.checkConditionalExpressionBranchTypesMatch(expression, type)); + } + if (!myHolder.hasErrorResults() && parent instanceof PsiThrowStatement throwStmt && throwStmt.getException() == expression) { + add(HighlightUtil.checkMustBeThrowable(type, expression, true)); } if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightControlFlowUtil.checkFinalVariableInitializedInLoop(expression, resolved)); + add(AnnotationsHighlightUtil.checkConstantExpression(expression)); + } + if (!myHolder.hasErrorResults() && parent instanceof PsiForeachStatement forEach && forEach.getIteratedValue() == expression) { + add(GenericsHighlightUtil.checkForeachExpressionTypeIsIterable(expression)); } - } } - PsiElement parent = expression.getParent(); - if (parent instanceof PsiMethodCallExpression && ((PsiMethodCallExpression) parent).getMethodExpression() == expression && (!result.isAccessible() || !result.isStaticsScopeCorrect())) { - PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) parent; - PsiExpressionList list = methodCallExpression.getArgumentList(); - if (!HighlightMethodUtil.isDummyConstructorCall(methodCallExpression, myResolveHelper, list, expression)) { - try { - myHolder.add(HighlightMethodUtil.checkAmbiguousMethodCallIdentifier(expression, results, list, resolved, result, methodCallExpression, myResolveHelper, myLanguageLevel, myFile)); - - if (!PsiTreeUtil.findChildrenOfType(methodCallExpression.getArgumentList(), PsiLambdaExpression.class).isEmpty()) { - PsiElement nameElement = expression.getReferenceNameElement(); - if (nameElement != null) { - myHolder.add(HighlightMethodUtil.checkAmbiguousMethodCallArguments(expression, results, list, resolved, result, methodCallExpression, myResolveHelper, nameElement)); - } - } - } catch (IndexNotReadyException ignored) { + @Override + @RequiredReadAction + public void visitExpressionList(PsiExpressionList list) { + super.visitExpressionList(list); + if (list.getParent() instanceof PsiMethodCallExpression expression) { + if (expression.getArgumentList() == list) { + PsiReferenceExpression referenceExpression = expression.getMethodExpression(); + JavaResolveResult[] results = resolveOptimised(referenceExpression); + if (results == null) { + return; + } + JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + PsiElement resolved = result.getElement(); + + if ((!result.isAccessible() || !result.isStaticsScopeCorrect()) + && !HighlightMethodUtil.isDummyConstructorCall(expression, myResolveHelper, list, referenceExpression) + // this check is for fake expression from JspMethodCallImpl + && referenceExpression.getParent() == expression) { + try { + if (PsiTreeUtil.findChildrenOfType(expression.getArgumentList(), PsiLambdaExpression.class).isEmpty()) { + add(HighlightMethodUtil.checkAmbiguousMethodCallArguments( + referenceExpression, + results, + list, + resolved, + result, + expression, + myResolveHelper, + list + )); + } + } + catch (IndexNotReadyException ignored) { + } + } + } } - } - } - - if (!myHolder.hasErrorResults() && resultForIncompleteCode != null) { - myHolder.add(HighlightUtil.checkExpressionRequired(expression, resultForIncompleteCode)); } - if (!myHolder.hasErrorResults() && resolved instanceof PsiField) { - try { - myHolder.add(HighlightUtil.checkIllegalForwardReferenceToField(expression, (PsiField) resolved)); - } catch (IndexNotReadyException ignored) { - } - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkAccessStaticFieldFromEnumConstructor(expression, result)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved)); + @Override + @RequiredReadAction + public void visitField(PsiField field) { + super.visitField(field); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightControlFlowUtil.checkFinalFieldInitialized(field)); + } } - final PsiExpression qualifierExpression = expression.getQualifierExpression(); - myHolder.add(HighlightUtil.checkUnqualifiedSuperInDefaultMethod(myLanguageLevel, expression, qualifierExpression)); - if (!myHolder.hasErrorResults() && qualifierExpression != null) { - PsiType type = qualifierExpression.getType(); - if (type instanceof PsiCapturedWildcardType) { - type = ((PsiCapturedWildcardType) type).getUpperBound(); - } - final PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (psiClass != null) { - myHolder.add(GenericsHighlightUtil.areSupersAccessible(psiClass, expression)); - } - } - - if (!myHolder.hasErrorResults() && resolved != null && myJavaModule != null) { - myHolder.add(ModuleHighlightUtil.checkPackageAccessibility(expression, resolved, myJavaModule)); - } - } - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - myHolder.add(checkFeature(expression, Feature.METHOD_REFERENCES)); - final PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (parent instanceof PsiExpressionStatement) { - return; + @Override + @RequiredReadAction + public void visitForStatement(PsiForStatement statement) { + myHolder.add(HighlightUtil.checkForStatement(statement)); } - final JavaResolveResult result; - final JavaResolveResult[] results; - try { - results = expression.multiResolve(true); - result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; - } catch (IndexNotReadyException e) { - return; - } - if (myRefCountHolder != null) { - myRefCountHolder.registerReference(expression, result); + @Override + @RequiredReadAction + public void visitForeachStatement(PsiForeachStatement statement) { + add(checkFeature(statement, JavaFeature.FOR_EACH)); } - final PsiElement method = result.getElement(); - if (method != null && !result.isAccessible()) { - final String accessProblem = HighlightUtil.buildProblemWithAccessDescription(expression, result); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(accessProblem).create(); - HighlightUtil.registerAccessQuickFixAction((PsiMember) method, expression, info, result.getCurrentFileResolveScope()); - myHolder.add(info); - } else { - final TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); - if (method instanceof PsiMethod && !expression.isConstructor()) { - PsiElement methodNameElement = expression.getReferenceNameElement(); - if (methodNameElement != null) { - myHolder.add(HighlightNamesUtil.highlightMethodName((PsiMethod) method, methodNameElement, false, colorsScheme)); + + @Override + @RequiredReadAction + public void visitImportStaticStatement(PsiImportStaticStatement statement) { + add(checkFeature(statement, JavaFeature.STATIC_IMPORTS)); + if (!myHolder.hasErrorResults()) { + myHolder.add(ImportsHighlightUtil.checkStaticOnDemandImportResolvesToClass(statement)); } - } - myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(expression, colorsScheme)); } - if (!LambdaUtil.isValidLambdaContext(parent)) { - String description = "Method reference expression is not expected here"; - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create()); - } + @Override + @RequiredReadAction + public void visitIdentifier(PsiIdentifier identifier) { + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + + PsiElement parent = identifier.getParent(); + if (parent instanceof PsiVariable variable) { + myHolder.add(HighlightUtil.checkVariableAlreadyDefined(variable)); + + if (variable.getInitializer() == null) { + PsiElement child = variable.getLastChild(); + if (child instanceof PsiErrorElement && child.getPrevSibling() == identifier) { + return; + } + } + + if (variable instanceof PsiParameter parameter + && parameter.getDeclarationScope() instanceof PsiMethod) { + myReassignedParameters.putInt(parameter, 1); // mark param as present in current file + } + // method params are highlighted in visitMethod since we should make sure the method body was visited before + else if (HighlightControlFlowUtil.isReassigned(variable, myFinalVarProblems)) { + add(HighlightNamesUtil.highlightReassignedVariable(variable, identifier)); + } + else { + myHolder.add(HighlightNamesUtil.highlightVariableName(variable, identifier, colorsScheme)); + } + } + else if (parent instanceof PsiClass aClass) { + if (aClass.isAnnotationType()) { + add(checkFeature(identifier, JavaFeature.ANNOTATIONS)); + } + + myHolder.add(HighlightClassUtil.checkClassAlreadyImported(aClass, identifier)); + if (!(parent instanceof PsiAnonymousClass) && aClass.getNameIdentifier() == identifier) { + myHolder.add(HighlightNamesUtil.highlightClassName(aClass, identifier, colorsScheme)); + } + if (!myHolder.hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + add(GenericsHighlightUtil.checkUnrelatedDefaultMethods(aClass, identifier)); + } - final PsiType functionalInterfaceType = expression.getFunctionalInterfaceType(); - if (!myHolder.hasErrorResults()) { - if (functionalInterfaceType != null) { - final boolean notFunctional = !LambdaUtil.isFunctionalType(functionalInterfaceType); - if (notFunctional) { - String description = functionalInterfaceType.getPresentableText() + " is not a functional interface"; - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create()); + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkUnrelatedConcrete(aClass, identifier)); + } + } + else if (parent instanceof PsiMethod method) { + if (method.isConstructor()) { + myHolder.add(HighlightMethodUtil.checkConstructorName(method)); + } + myHolder.add(HighlightNamesUtil.highlightMethodName(method, identifier, true, colorsScheme)); + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + myHolder.add(GenericsHighlightUtil.checkDefaultMethodOverrideEquivalentToObjectNonPrivate( + myLanguageLevel, + aClass, + method, + identifier + )); + } } - } + + myHolder.add(HighlightUtil.checkUnderscore(identifier, myLanguageLevel)); + + super.visitIdentifier(identifier); } - if (!myHolder.hasErrorResults()) { - final PsiElement referenceNameElement = expression.getReferenceNameElement(); - if (referenceNameElement instanceof PsiKeyword) { - if (!PsiMethodReferenceUtil.isValidQualifier(expression)) { - PsiElement qualifier = expression.getQualifier(); - if (qualifier != null) { - String description = "Cannot find class " + qualifier.getText(); - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create()); - } + + @Override + @RequiredReadAction + public void visitImportStatement(PsiImportStatement statement) { + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkSingleImportClassConflict(statement, mySingleImportedClasses, myFile)); } - } } - if (!myHolder.hasErrorResults()) { - checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType); - } - - if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { - final String errorMessage = PsiMethodReferenceUtil.checkMethodReferenceContext(expression); - if (errorMessage != null) { - final HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(errorMessage).create(); - if (method instanceof PsiMethod && !((PsiMethod) method).isConstructor() && !((PsiMethod) method).hasModifierProperty(PsiModifier.ABSTRACT)) { - final boolean shouldHave = !((PsiMethod) method).hasModifierProperty(PsiModifier.STATIC); - final LocalQuickFixAndIntentionActionOnPsiElement fixStaticModifier = QuickFixFactory.getInstance().createModifierListFix((PsiModifierListOwner) method, PsiModifier.STATIC, - shouldHave, false); - QuickFixAction.registerQuickFixAction(info, fixStaticModifier); - } - myHolder.add(info); - } - } - - if (!myHolder.hasErrorResults()) { - PsiElement qualifier = expression.getQualifier(); - if (qualifier instanceof PsiTypeElement) { - final PsiType psiType = ((PsiTypeElement) qualifier).getType(); - final HighlightInfo genericArrayCreationInfo = GenericsHighlightUtil.checkGenericArrayCreation(qualifier, psiType); - if (genericArrayCreationInfo != null) { - myHolder.add(genericArrayCreationInfo); - } else { - final String wildcardMessage = PsiMethodReferenceUtil.checkTypeArguments((PsiTypeElement) qualifier, psiType); - if (wildcardMessage != null) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(wildcardMessage).create()); - } - } - } - } - - if (!myHolder.hasErrorResults()) { - myHolder.add(PsiMethodReferenceHighlightingUtil.checkRawConstructorReference(expression)); - } - - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnhandledExceptions(expression, expression.getTextRange())); - } - - if (!myHolder.hasErrorResults()) { - final String badReturnTypeMessage = PsiMethodReferenceUtil.checkReturnType(expression, result, functionalInterfaceType); - if (badReturnTypeMessage != null) { - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(badReturnTypeMessage).create()); - } - } - - if (!myHolder.hasErrorResults()) { - if (results.length == 0 || results[0] instanceof MethodCandidateInfo && !((MethodCandidateInfo) results[0]).isApplicable() && functionalInterfaceType != null) { - String description = null; - if (results.length == 1) { - description = ((MethodCandidateInfo) results[0]).getInferenceErrorMessage(); - } - if (expression.isConstructor()) { - final PsiClass containingClass = PsiMethodReferenceUtil.getQualifierResolveResult(expression).getContainingClass(); - - if (containingClass != null) { - if (!myHolder.add(HighlightClassUtil.checkInstantiationOfAbstractClass(containingClass, expression)) && !myHolder.add(GenericsHighlightUtil.checkEnumInstantiation(expression, - containingClass)) && containingClass.isPhysical() && description == null) { - description = JavaErrorBundle.message("cannot.resolve.constructor", containingClass.getName()); - } - } - } else if (description == null) { - description = JavaErrorBundle.message("cannot.resolve.method", expression.getReferenceName()); + + @Override + @RequiredReadAction + public void visitImportStaticReferenceElement(PsiImportStaticReferenceElement ref) { + String refName = ref.getReferenceName(); + JavaResolveResult[] results = ref.multiResolve(false); + + PsiElement referenceNameElement = ref.getReferenceNameElement(); + if (results.length == 0) { + LocalizeValue description = JavaCompilationErrorLocalize.referenceUnresolved(refName); + assert referenceNameElement != null : ref; + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(referenceNameElement) + .descriptionAndTooltip(description) + .registerFix(QuickFixFactory.getInstance().createSetupJDKFix()) + .create(); + myHolder.add(info); } + else { + PsiManager manager = ref.getManager(); + for (JavaResolveResult result : results) { + PsiElement element = result.getElement(); + if (element instanceof PsiClass psiClass) { + Pair imported = mySingleImportedClasses.get(refName); + PsiClass importedClass = imported == null ? null : imported.getSecond(); + if (importedClass != null && !manager.areElementsEquivalent(importedClass, psiClass)) { + LocalizeValue description = imported.first == null + ? JavaErrorLocalize.singleImportClassConflict(refName) + : imported.first.equals(ref) + ? JavaCompilationErrorLocalize.importSingleStaticClassAmbiguous(refName) + : JavaCompilationErrorLocalize.importSingleStaticClassAlreadyDefined(refName); + myHolder.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(description) + .create() + ); + } + mySingleImportedClasses.put(refName, Pair.create(ref, psiClass)); + } + else if (element instanceof PsiField field) { + Pair imported = mySingleImportedFields.get(refName); + PsiField importedField = imported == null ? null : imported.getSecond(); + if (importedField != null && !manager.areElementsEquivalent(importedField, field)) { + LocalizeValue description = imported.first.equals(ref) + ? JavaCompilationErrorLocalize.importSingleStaticFieldAmbiguous(refName) + : JavaCompilationErrorLocalize.importSingleStaticFieldAlreadyDefined(refName); + myHolder.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(description) + .create() + ); + } + mySingleImportedFields.put(refName, Pair.create(ref, field)); + } + } + } + if (!myHolder.hasErrorResults()) { + PsiElement resolved = results.length >= 1 ? results[0].getElement() : null; + if (results.length > 1) { + for (int i = 1; i < results.length; i++) { + PsiElement element = results[i].getElement(); + if (resolved instanceof PsiMethod && !(element instanceof PsiMethod) + || resolved instanceof PsiVariable && !(element instanceof PsiVariable) + || resolved instanceof PsiClass && !(element instanceof PsiClass)) { + resolved = null; + break; + } + } + } + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + if (resolved instanceof PsiClass psiClass) { + myHolder.add(HighlightNamesUtil.highlightClassName(psiClass, ref, colorsScheme)); + } + else { + myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(ref, colorsScheme)); + if (referenceNameElement != null) { + if (resolved instanceof PsiVariable variable) { + myHolder.add(HighlightNamesUtil.highlightVariableName(variable, referenceNameElement, colorsScheme)); + } + else if (resolved instanceof PsiMethod method) { + myHolder.add(HighlightNamesUtil.highlightMethodName(method, referenceNameElement, false, colorsScheme)); + } + } + } + } + } - if (description != null) { - PsiElement referenceNameElement = notNull(expression.getReferenceNameElement(), expression); - HighlightInfoType type = results.length == 0 ? HighlightInfoType.WRONG_REF : HighlightInfoType.ERROR; - HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(type).descriptionAndTooltip(description).range(referenceNameElement).create(); - myHolder.add(highlightInfo); - TextRange fixRange = HighlightMethodUtil.getFixRange(referenceNameElement); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QuickFixFactory.getInstance().createCreateMethodFromUsageFix(expression)); - } - } - } - } - - // 15.13 | 15.27 - // It is a compile-time error if any class or interface mentioned by either U or the function type of U - // is not accessible from the class or interface in which the method reference expression appears. - private void checkFunctionalInterfaceTypeAccessible(@Nonnull PsiFunctionalExpression expression, PsiType functionalInterfaceType) { - PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final PsiClass psiClass = resolveResult.getElement(); - if (psiClass != null) { - if (!PsiUtil.isAccessible(myFile.getProject(), psiClass, expression, null)) { - String text = HighlightUtil.buildProblemWithAccessDescription(expression, resolveResult); - myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create()); - } else { - for (PsiType type : resolveResult.getSubstitutor().getSubstitutionMap().values()) { - checkFunctionalInterfaceTypeAccessible(expression, type); - } - } - } - } - - @Override - public void visitReferenceList(PsiReferenceList list) { - if (list.getFirstChild() == null) { - return; - } - PsiElement parent = list.getParent(); - if (!(parent instanceof PsiTypeParameter)) { - myHolder.add(AnnotationsHighlightUtil.checkAnnotationDeclaration(parent, list)); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkExtendsAllowed(list)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkImplementsAllowed(list)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightClassUtil.checkClassExtendsOnlyOneClass(list)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkGenericCannotExtendException(list)); - } - } - } - - @Override - public void visitReferenceParameterList(PsiReferenceParameterList list) { - if (list.getTextLength() == 0) { - return; - } - - myHolder.add(checkFeature(list, Feature.GENERICS)); - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkParametersAllowed(list)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkParametersOnRaw(list)); - } - if (!myHolder.hasErrorResults()) { - for (PsiTypeElement typeElement : list.getTypeParameterElements()) { - if (typeElement.getType() instanceof PsiDiamondType) { - myHolder.add(checkFeature(list, Feature.DIAMOND_TYPES)); - } - } - } - } - - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - try { - myHolder.add(HighlightUtil.checkReturnStatementType(statement)); - } catch (IndexNotReadyException ignore) { - } - } - - @Override - public void visitStatement(PsiStatement statement) { - super.visitStatement(statement); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkNotAStatement(statement)); - } - } - - @Override - public void visitSuperExpression(PsiSuperExpression expr) { - myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel)); - if (!myHolder.hasErrorResults()) { - visitExpression(expr); - } - } - - @Override - public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { - super.visitSwitchLabelStatement(statement); - if (!myHolder.hasErrorResults()) - myHolder.add(HighlightUtil.checkCaseStatement(statement)); - } - - @Override - public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) { - super.visitSwitchLabeledRuleStatement(statement); - if (!myHolder.hasErrorResults()) - myHolder.add(HighlightUtil.checkCaseStatement(statement)); - } - - - @Override - public void visitSwitchStatement(PsiSwitchStatement statement) { - super.visitSwitchStatement(statement); - checkSwitchBlock(statement); - } - - private void checkSwitchBlock(PsiSwitchBlock switchBlock) { - if (!myHolder.hasErrorResults()) - myHolder.add(HighlightUtil.checkSwitchBlockStatements(switchBlock, myLanguageLevel, myFile)); - if (!myHolder.hasErrorResults()) - myHolder.add(HighlightUtil.checkSwitchSelectorType(switchBlock, myLanguageLevel)); - if (!myHolder.hasErrorResults()) - myHolder.addAll(HighlightUtil.checkSwitchLabelValues(switchBlock)); - } - - @Override - public void visitThisExpression(PsiThisExpression expr) { - if (!(expr.getParent() instanceof PsiReceiverParameter)) { - myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel)); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, myFile)); - } - if (!myHolder.hasErrorResults()) { - visitExpression(expr); - } - } - } - - @Override - public void visitThrowStatement(PsiThrowStatement statement) { - myHolder.add(HighlightUtil.checkUnhandledExceptions(statement, null)); - if (!myHolder.hasErrorResults()) { - visitStatement(statement); - } - } - - @Override - public void visitTryStatement(PsiTryStatement statement) { - super.visitTryStatement(statement); - if (!myHolder.hasErrorResults()) { - final Set thrownTypes = HighlightUtil.collectUnhandledExceptions(statement); - for (PsiParameter parameter : statement.getCatchBlockParameters()) { - boolean added = myHolder.addAll(HighlightUtil.checkExceptionAlreadyCaught(parameter)); - if (!added) { - added = myHolder.addAll(HighlightUtil.checkExceptionThrownInTry(parameter, thrownTypes)); - } - if (!added) { - myHolder.addAll(HighlightUtil.checkWithImprovedCatchAnalysis(parameter, thrownTypes, myFile)); - } - } - } - } - - @Override - public void visitResourceList(PsiResourceList resourceList) { - super.visitResourceList(resourceList); - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(resourceList, Feature.TRY_WITH_RESOURCES)); - } - } - - @Override - public void visitResourceVariable(PsiResourceVariable resource) { - super.visitResourceVariable(resource); - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkTryResourceIsAutoCloseable(resource)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnhandledCloserExceptions(resource)); - } - } - - @Override - public void visitResourceExpression(PsiResourceExpression resource) { - super.visitResourceExpression(resource); - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(resource, Feature.REFS_AS_RESOURCE)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkResourceVariableIsFinal(resource)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkTryResourceIsAutoCloseable(resource)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkUnhandledCloserExceptions(resource)); - } - } - - @Override - public void visitTypeElement(PsiTypeElement type) { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkIllegalType(type)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkReferenceTypeUsedAsTypeArgument(type, myLanguageLevel)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkWildcardUsage(type)); - } - } - - @Override - public void visitTypeCastExpression(PsiTypeCastExpression typeCast) { - super.visitTypeCastExpression(typeCast); - try { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkIntersectionInTypeCast(typeCast, myLanguageLevel, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkInconvertibleTypeCast(typeCast)); - } - } catch (IndexNotReadyException ignored) { - } - } - - @Override - public void visitTypeParameterList(PsiTypeParameterList list) { - PsiTypeParameter[] typeParameters = list.getTypeParameters(); - if (typeParameters.length > 0) { - myHolder.add(checkFeature(list, Feature.GENERICS)); - if (!myHolder.hasErrorResults()) { - myHolder.add(GenericsHighlightUtil.checkTypeParametersList(list, typeParameters, myLanguageLevel)); - } - } - } - - @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - try { - if (!myHolder.hasErrorResults()) { - myHolder.add(HighlightUtil.checkVariableInitializerType(variable)); - } - } catch (IndexNotReadyException ignored) { - } - } - - private boolean isReassigned(@Nonnull PsiVariable variable) { - try { - boolean reassigned; - if (variable instanceof PsiParameter) { - reassigned = myReassignedParameters.getInt((PsiParameter) variable) == 2; - } else { - reassigned = HighlightControlFlowUtil.isReassigned(variable, myFinalVarProblems); - } - - return reassigned; - } catch (IndexNotReadyException e) { - return false; - } - } - - @Override - public void visitConditionalExpression(PsiConditionalExpression expression) { - super.visitConditionalExpression(expression); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) && PsiPolyExpressionUtil.isPolyExpression(expression)) { - final PsiExpression thenExpression = expression.getThenExpression(); - final PsiExpression elseExpression = expression.getElseExpression(); - if (thenExpression != null && elseExpression != null) { - final PsiType conditionalType = expression.getType(); - if (conditionalType != null) { - final PsiExpression[] sides = { - thenExpression, - elseExpression - }; - for (PsiExpression side : sides) { - final PsiType sideType = side.getType(); - if (sideType != null && !TypeConversionUtil.isAssignable(conditionalType, sideType)) { - myHolder.add(HighlightUtil.checkAssignability(conditionalType, sideType, side, side)); - } - } - } - } - } - } - - @Override - public void visitReceiverParameter(PsiReceiverParameter parameter) { - super.visitReceiverParameter(parameter); - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(parameter, Feature.RECEIVERS)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkReceiverPlacement(parameter)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(AnnotationsHighlightUtil.checkReceiverType(parameter)); - } - } - - @Override - public void visitModule(PsiJavaModule module) { - super.visitModule(module); - if (!myHolder.hasErrorResults()) { - myHolder.add(checkFeature(module, Feature.MODULES)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkFileName(module, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkFileDuplicates(module, myFile)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(ModuleHighlightUtil.checkDuplicateStatements(module)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkClashingReads(module)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(ModuleHighlightUtil.checkUnusedServices(module)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkFileLocation(module, myFile)); - } - } - - @Override - public void visitRequiresStatement(PsiRequiresStatement statement) { - super.visitRequiresStatement(statement); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - PsiJavaModule container = (PsiJavaModule) statement.getParent(); - PsiJavaModuleReferenceElement ref = statement.getReferenceElement(); - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkModuleReference(ref, container)); - } - } - } - - @Override - public void visitPackageAccessibilityStatement(PsiPackageAccessibilityStatement statement) { - super.visitPackageAccessibilityStatement(statement); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkHostModuleStrength(statement)); - } - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkPackageReference(statement)); - } - if (!myHolder.hasErrorResults()) { - myHolder.addAll(ModuleHighlightUtil.checkPackageAccessTargets(statement)); - } - } - } - - @Override - public void visitUsesStatement(PsiUsesStatement statement) { - super.visitUsesStatement(statement); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - if (!myHolder.hasErrorResults()) { - myHolder.add(ModuleHighlightUtil.checkServiceReference(statement.getClassReference())); - } - } - } - - @Override - public void visitProvidesStatement(PsiProvidesStatement statement) { - super.visitProvidesStatement(statement); - if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - if (!myHolder.hasErrorResults()) { - myHolder.addAll(ModuleHighlightUtil.checkServiceImplementations(statement)); - } - } - } - - @Nullable - private HighlightInfo checkFeature(@Nonnull PsiElement element, @Nonnull Feature feature) { - return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile); - } + @Override + @RequiredReadAction + public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { + super.visitInstanceOfExpression(expression); + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkInstanceOfApplicable(expression)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkInstanceOfGenericType(expression)); + } + } + + @Override + @RequiredReadAction + public void visitKeyword(PsiKeyword keyword) { + super.visitKeyword(keyword); + PsiElement parent = keyword.getParent(); + String text = keyword.getText(); + if (parent instanceof PsiModifierList modifierList) { + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkNotAllowedModifier(keyword, modifierList)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkIllegalModifierCombination(keyword, modifierList)); + } + if (PsiModifier.ABSTRACT.equals(text) && modifierList.getParent() instanceof PsiMethod method) { + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightMethodUtil.checkAbstractMethodInConcreteClass(method, keyword)); + } + } + } + else if (PsiKeyword.INTERFACE.equals(text) && parent instanceof PsiClass psiClass) { + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkInterfaceCannotBeLocal(psiClass)); + } + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkStaticDeclarationInInnerClass(keyword, myLanguageLevel)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkIllegalVoidType(keyword)); + } + } + + @Override + @RequiredReadAction + public void visitLabeledStatement(PsiLabeledStatement statement) { + super.visitLabeledStatement(statement); + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkLabelWithoutStatement(statement)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkLabelAlreadyInUse(statement)); + } + } + + @Override + @RequiredReadAction + public void visitLiteralExpression(PsiLiteralExpression expression) { + super.visitLiteralExpression(expression); + if (myHolder.hasErrorResults()) { + return; + } + add(HighlightUtil.checkLiteralExpressionParsingError(expression, myLanguageLevel, myFile)); + if (myRefCountHolder != null && !myHolder.hasErrorResults()) { + registerReferencesFromInjectedFragments(expression); + } + + if (myRefCountHolder != null && !myHolder.hasErrorResults()) { + for (PsiReference reference : expression.getReferences()) { + if (reference.resolve() instanceof PsiMember member) { + myRefCountHolder.registerReference(reference, new CandidateInfo(member, PsiSubstitutor.EMPTY)); + } + } + } + } + + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + if (!myHolder.hasErrorResults()) { + add(HighlightControlFlowUtil.checkUnreachableStatement(method.getBody())); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkConstructorHandleSuperClassExceptions(method)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkRecursiveConstructorInvocation(method)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkSafeVarargsAnnotation(method, myLanguageLevel)); + } + + PsiClass aClass = method.getContainingClass(); + if (!myHolder.hasErrorResults() && method.isConstructor()) { + add(HighlightClassUtil.checkThingNotAllowedInInterface(method)); + } + if (!myHolder.hasErrorResults() && method.hasModifierProperty(PsiModifier.DEFAULT)) { + add(checkFeature(method, JavaFeature.EXTENSION_METHODS)); + } + if (!myHolder.hasErrorResults() && aClass != null && aClass.isInterface() && method.isStatic()) { + add(checkFeature(method, JavaFeature.EXTENSION_METHODS)); + } + if (!myHolder.hasErrorResults() && aClass != null) { + add(HighlightMethodUtil.checkDuplicateMethod(aClass, method, getDuplicateMethods(aClass))); + } + + // method params are highlighted in visitMethod since we should make sure the method body was visited before + PsiParameter[] parameters = method.getParameterList().getParameters(); + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + + for (PsiParameter parameter : parameters) { + int info = myReassignedParameters.getInt(parameter); + if (info == 0) { + continue; // out of this file + } + + PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); + if (nameIdentifier != null) { + if (info == 2) { // reassigned + add(HighlightNamesUtil.highlightReassignedVariable(parameter, nameIdentifier)); + } + else { + myHolder.add(HighlightNamesUtil.highlightVariableName(parameter, nameIdentifier, colorsScheme)); + } + } + } + } + + @RequiredReadAction + private void highlightReferencedMethodOrClassName(PsiJavaCodeReferenceElement element, PsiElement resolved) { + PsiElement parent = element.getParent(); + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + if (parent instanceof PsiMethodCallExpression methodCall) { + PsiMethod method = methodCall.resolveMethod(); + PsiElement methodNameElement = element.getReferenceNameElement(); + if (method != null && methodNameElement != null && !(methodNameElement instanceof PsiKeyword)) { + myHolder.add(HighlightNamesUtil.highlightMethodName(method, methodNameElement, false, colorsScheme)); + myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(element, colorsScheme)); + } + } + else if (parent instanceof PsiConstructorCall constructorCall) { + try { + PsiMethod method = constructorCall.resolveConstructor(); + PsiMember methodOrClass = method != null ? method : resolved instanceof PsiClass psiClass ? psiClass : null; + if (methodOrClass != null) { + PsiElement referenceNameElement = element.getReferenceNameElement(); + if (referenceNameElement != null) { + // exclude type parameters from the highlighted text range + TextRange range = referenceNameElement.getTextRange(); + myHolder.add(HighlightNamesUtil.highlightMethodName( + methodOrClass, + referenceNameElement, + range, + colorsScheme, + false + )); + } + } + } + catch (IndexNotReadyException ignored) { + } + } + else if (resolved instanceof PsiPackage aPackage) { + // highlight package (and following dot) as a class + myHolder.add(HighlightNamesUtil.highlightPackage(aPackage, element, colorsScheme)); + } + else if (resolved instanceof PsiClass psiClass) { + myHolder.add(HighlightNamesUtil.highlightClassName(psiClass, element, colorsScheme)); + } + } + + @Override + @RequiredReadAction + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkEnumSuperConstructorCall(expression)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkSuperQualifierType(myFile.getProject(), expression)); + } + // in case of JSP synthetic method call, do not check + if (myFile.isPhysical() && !myHolder.hasErrorResults()) { + try { + add(HighlightMethodUtil.checkMethodCall(expression, myResolveHelper, myLanguageLevel, myJavaSdkVersion, myFile)); + } + catch (IndexNotReadyException ignored) { + } + } + + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkConstructorCallMustBeFirstStatement(expression)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkSuperAbstractMethodDirectCall(expression)); + } + + if (!myHolder.hasErrorResults()) { + visitExpression(expression); + } + } + + @Override + @RequiredReadAction + public void visitModifierList(PsiModifierList list) { + super.visitModifierList(list); + PsiElement parent = list.getParent(); + if (parent instanceof PsiMethod method) { + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkMethodCanHaveBody(method, myLanguageLevel)); + } + MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); + if (!method.isConstructor()) { + try { + List superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); + if (!superMethodSignatures.isEmpty()) { + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkMethodIncompatibleReturnType( + methodSignature, + superMethodSignatures, + true + )); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightMethodUtil.checkMethodIncompatibleThrows( + methodSignature, + superMethodSignatures, + true, + method.getContainingClass() + )); + } + if (!method.isStatic()) { + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkMethodWeakerPrivileges( + methodSignature, + superMethodSignatures, + true, + myFile + )); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkMethodOverridesFinal(methodSignature, superMethodSignatures)); + } + } + } + } + catch (IndexNotReadyException ignored) { + } + } + PsiClass aClass = method.getContainingClass(); + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkMethodMustHaveBody(method, aClass)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkConstructorCallsBaseClassConstructor(method, myRefCountHolder, myResolveHelper)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkStaticMethodOverride(method, myFile)); + } + if (!myHolder.hasErrorResults() && aClass != null && myOverrideEquivalentMethodsVisitedClasses.add(aClass)) { + myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(aClass)); + } + } + else if (parent instanceof PsiClass aClass) { + try { + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkDuplicateNestedClass(aClass)); + } + if (!myHolder.hasErrorResults()) { + TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + add(HighlightClassUtil.checkClassMustBeAbstract(aClass, textRange)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkClassDoesNotCallSuperConstructorOrHandleExceptions( + aClass, + myRefCountHolder, + myResolveHelper + )); + } + if (!myHolder.hasErrorResults()) { + add(HighlightMethodUtil.checkOverrideEquivalentInheritedMethods(aClass, myFile, myLanguageLevel)); + } + if (!myHolder.hasErrorResults() && myOverrideEquivalentMethodsVisitedClasses.add(aClass)) { + myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(aClass)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkCyclicInheritance(aClass)); + } + } + catch (IndexNotReadyException ignored) { + } + } + else if (parent instanceof PsiEnumConstant) { + if (!myHolder.hasErrorResults()) { + myHolder.addAll(GenericsHighlightUtil.checkEnumConstantModifierList(list)); + } + } + } + + @Override + @RequiredReadAction + public void visitNameValuePair(PsiNameValuePair pair) { + myHolder.add(AnnotationsHighlightUtil.checkNameValuePair(pair)); + if (!myHolder.hasErrorResults()) { + PsiIdentifier nameId = pair.getNameIdentifier(); + if (nameId != null) { + HighlightInfo result = + HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.ANNOTATION_ATTRIBUTE_NAME).range(nameId).create(); + myHolder.add(result); + } + } + } + + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + PsiType type = expression.getType(); + PsiClass aClass = PsiUtil.resolveClassInType(type); + add(HighlightUtil.checkUnhandledExceptions(expression, null)); + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkAnonymousInheritFinal(expression)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkQualifiedNew(expression, type, aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(expression, type, aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkTypeParameterInstantiation(expression)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkInstantiationOfAbstractClass(aClass, expression)); + } + try { + if (!myHolder.hasErrorResults()) { + HighlightMethodUtil.checkNewExpression(expression, type, myHolder, myJavaSdkVersion); + } + } + catch (IndexNotReadyException ignored) { + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkEnumInstantiation(expression, aClass)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkGenericArrayCreation(expression, type)); + } + if (!myHolder.hasErrorResults()) { + registerConstructorCall(expression); + } + + if (!myHolder.hasErrorResults()) { + visitExpression(expression); + } + } + + @Override + @RequiredReadAction + public void visitPackageStatement(PsiPackageStatement statement) { + super.visitPackageStatement(statement); + myHolder.add(AnnotationsHighlightUtil.checkPackageAnnotationContainingFile(statement, myFile)); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkPackageStatement(statement, myFile, myJavaModule)); + } + } + } + + @Override + @RequiredReadAction + public void visitParameter(PsiParameter parameter) { + super.visitParameter(parameter); + + PsiElement parent = parameter.getParent(); + if (parent instanceof PsiParameterList && parameter.isVarArgs()) { + if (!myHolder.hasErrorResults()) { + add(checkFeature(parameter, JavaFeature.VARARGS)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkVarArgParameterIsLast(parameter)); + } + } + else if (parent instanceof PsiCatchSection) { + if (!myHolder.hasErrorResults() && parameter.getType() instanceof PsiDisjunctionType) { + add(checkFeature(parameter, JavaFeature.MULTI_CATCH)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkCatchParameterIsThrowable(parameter)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(GenericsHighlightUtil.checkCatchParameterIsClass(parameter)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(HighlightUtil.checkCatchTypeIsDisjoint(parameter)); + } + } + else if (parent instanceof PsiForeachStatement forEach) { + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkForEachParameterType(forEach, parameter)); + } + } + } + + @Override + @RequiredReadAction + public void visitParameterList(PsiParameterList list) { + super.visitParameterList(list); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkAnnotationMethodParameters(list)); + } + } + + @Override + @RequiredReadAction + public void visitPostfixExpression(PsiPostfixExpression expression) { + super.visitPostfixExpression(expression); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnaryOperatorApplicable(expression.getOperationSign(), expression.getOperand())); + } + } + + @Override + @RequiredReadAction + public void visitPrefixExpression(PsiPrefixExpression expression) { + super.visitPrefixExpression(expression); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnaryOperatorApplicable(expression.getOperationSign(), expression.getOperand())); + } + } + + @RequiredReadAction + private void registerConstructorCall(PsiConstructorCall constructorCall) { + if (myRefCountHolder != null) { + if (constructorCall.resolveMethodGenerics().getElement() instanceof PsiNamedElement namedElem) { + myRefCountHolder.registerLocallyReferenced(namedElem); + } + } + } + + @Override + @RequiredReadAction + public void visitReferenceElement(PsiJavaCodeReferenceElement ref) { + JavaResolveResult result = doVisitReferenceElement(ref); + if (result != null) { + PsiElement resolved = result.getElement(); + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkRawOnParameterizedType(ref, resolved)); + } + if (!myHolder.hasErrorResults() && resolved != null && myJavaModule != null) { + add(ModuleHighlightUtil.checkPackageAccessibility(ref, resolved, myJavaModule)); + } + } + } + + @RequiredReadAction + private JavaResolveResult doVisitReferenceElement(PsiJavaCodeReferenceElement ref) { + JavaResolveResult result = resolveOptimised(ref); + if (result == null) { + return null; + } + + PsiElement resolved = result.getElement(); + PsiElement parent = ref.getParent(); + + if (myRefCountHolder != null) { + myRefCountHolder.registerReference(ref, result); + } + + add(HighlightUtil.checkReference(ref, result, myFile, myLanguageLevel)); + + if (parent instanceof PsiJavaCodeReferenceElement || ref.isQualified()) { + if (!myHolder.hasErrorResults() && resolved instanceof PsiTypeParameter) { + boolean canSelectFromTypeParameter = myJavaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7); + if (canSelectFromTypeParameter) { + PsiClass containingClass = PsiTreeUtil.getParentOfType(ref, PsiClass.class); + if (containingClass != null) { + if (PsiTreeUtil.isAncestor(containingClass.getExtendsList(), ref, false) + || PsiTreeUtil.isAncestor(containingClass.getImplementsList(), ref, false)) { + canSelectFromTypeParameter = false; + } + } + } + if (!canSelectFromTypeParameter) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .descriptionAndTooltip(JavaCompilationErrorLocalize.referenceSelectFromTypeParameter()) + .range(ref) + .create()); + } + } + } + + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkAbstractInstantiation(ref)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkExtendsDuplicate(ref, resolved, myFile)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkClassExtendsForeignInnerClass(ref, resolved)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkSelectStaticClassFromParameterizedType(resolved, ref)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkParameterizedReferenceTypeArguments( + resolved, + ref, + result.getSubstitutor(), + myJavaSdkVersion + )); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkCannotPassInner(ref)); + } + + if (resolved != null && parent instanceof PsiReferenceList referenceList && !myHolder.hasErrorResults()) { + add(HighlightUtil.checkElementInReferenceList(ref, referenceList, result)); + } + + if (parent instanceof PsiAnonymousClass anonymousClass + && ref.equals(anonymousClass.getBaseClassReference()) + && myOverrideEquivalentMethodsVisitedClasses.add(anonymousClass)) { + myHolder.addAll(GenericsHighlightUtil.checkOverrideEquivalentMethods(anonymousClass)); + } + + if (resolved instanceof PsiVariable variable) { + PsiElement containingClass = PsiTreeUtil.getNonStrictParentOfType(ref, PsiClass.class, PsiLambdaExpression.class); + if ((containingClass instanceof PsiAnonymousClass || containingClass instanceof PsiLambdaExpression) + && !PsiTreeUtil.isAncestor(containingClass, variable, false) + && !(variable instanceof PsiField) + && (containingClass instanceof PsiLambdaExpression + || !PsiTreeUtil.isAncestor(((PsiAnonymousClass)containingClass).getArgumentList(), ref, false))) { + myHolder.add(HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.IMPLICIT_ANONYMOUS_CLASS_PARAMETER).range(ref).create()); + } + + if (variable instanceof PsiParameter parameter + && ref instanceof PsiExpression expr + && PsiUtil.isAccessedForWriting(expr)) { + myReassignedParameters.putInt(parameter, 2); + } + + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + if (!variable.hasModifierProperty(PsiModifier.FINAL) && isReassigned(variable)) { + add(HighlightNamesUtil.highlightReassignedVariable(variable, ref)); + } + else { + PsiElement nameElement = ref.getReferenceNameElement(); + if (nameElement != null) { + myHolder.add(HighlightNamesUtil.highlightVariableName(variable, nameElement, colorsScheme)); + } + } + myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(ref, colorsScheme)); + } + else { + highlightReferencedMethodOrClassName(ref, resolved); + } + + if (parent instanceof PsiNewExpression newExpr && !(resolved instanceof PsiClass) && resolved instanceof PsiNamedElement namedElem + && newExpr.getClassOrAnonymousClassReference() == ref) { + add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(ref) + .descriptionAndTooltip(JavaCompilationErrorLocalize.referenceUnresolved(namedElem.getName())) + ); + } + + if (!myHolder.hasErrorResults() && resolved instanceof PsiClass psiClass) { + PsiClass containingClass = psiClass.getContainingClass(); + if (containingClass != null) { + PsiElement place; + if (ref.getQualifier() instanceof PsiJavaCodeReferenceElement javaCodeRef) { + place = javaCodeRef.resolve(); + } + else if (parent instanceof PsiNewExpression newExpr) { + PsiExpression newQualifier = newExpr.getQualifier(); + place = newQualifier == null ? ref : PsiUtil.resolveClassInType(newQualifier.getType()); + } + else { + place = ref; + } + if (place != null && PsiTreeUtil.isAncestor(containingClass, place, false) && containingClass.hasTypeParameters()) { + myHolder.add(HighlightClassUtil.checkCreateInnerClassFromStaticContext(ref, place, (PsiClass)resolved)); + } + } + else if (resolved instanceof PsiTypeParameter typeParam) { + PsiTypeParameterListOwner owner = typeParam.getOwner(); + if (owner instanceof PsiClass outerClass && !InheritanceUtil.hasEnclosingInstanceInScope(outerClass, ref, false, false)) { + myHolder.add(HighlightClassUtil.reportIllegalEnclosingUsage(ref, null, (PsiClass) owner, ref)); + } + } + } + + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkPackageAndClassConflict(ref, myFile)); + } + + return result; + } + + @Nullable + private JavaResolveResult resolveOptimised(PsiJavaCodeReferenceElement ref) { + try { + if (ref instanceof PsiReferenceExpressionImpl) { + PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE; + JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(ref, resolver, true, true, myFile); + return results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + } + else { + return ref.advancedResolve(true); + } + } + catch (IndexNotReadyException e) { + return null; + } + } + + @Nullable + private JavaResolveResult[] resolveOptimised(PsiReferenceExpression expression) { + try { + if (expression instanceof PsiReferenceExpressionImpl) { + PsiReferenceExpressionImpl.OurGenericsResolver resolver = PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE; + return JavaResolveUtil.resolveWithContainingFile(expression, resolver, true, true, myFile); + } + else { + return expression.multiResolve(true); + } + } + catch (IndexNotReadyException e) { + return null; + } + } + + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + JavaResolveResult resultForIncompleteCode = doVisitReferenceElement(expression); + + if (!myHolder.hasErrorResults()) { + visitExpression(expression); + if (myHolder.hasErrorResults()) { + return; + } + } + + JavaResolveResult[] results = resolveOptimised(expression); + if (results == null) { + return; + } + JavaResolveResult result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + + PsiElement resolved = result.getElement(); + if (resolved instanceof PsiVariable variable && variable.getContainingFile() == expression.getContainingFile()) { + if (!myHolder.hasErrorResults()) { + try { + myHolder.add(HighlightControlFlowUtil.checkVariableInitializedBeforeUsage( + expression, + variable, + myUninitializedVarProblems, + myFile + )); + } + catch (IndexNotReadyException ignored) { + } + } + boolean isFinal = variable.hasModifierProperty(PsiModifier.FINAL); + if (isFinal && !variable.hasInitializer()) { + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightControlFlowUtil.checkFinalVariableMightAlreadyHaveBeenAssignedTo( + variable, + expression, + myFinalVarProblems + )); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightControlFlowUtil.checkFinalVariableInitializedInLoop(expression, variable)); + } + } + } + + if (expression.getParent() instanceof PsiMethodCallExpression methodCall + && methodCall.getMethodExpression() == expression + && (!result.isAccessible() || !result.isStaticsScopeCorrect())) { + PsiExpressionList list = methodCall.getArgumentList(); + if (!HighlightMethodUtil.isDummyConstructorCall(methodCall, myResolveHelper, list, expression)) { + try { + add(HighlightMethodUtil.checkAmbiguousMethodCallIdentifier( + expression, + results, + list, + resolved, + result, + methodCall, + myResolveHelper, + myLanguageLevel, + myFile + )); + + if (!PsiTreeUtil.findChildrenOfType(methodCall.getArgumentList(), PsiLambdaExpression.class).isEmpty()) { + PsiElement nameElement = expression.getReferenceNameElement(); + if (nameElement != null) { + add(HighlightMethodUtil.checkAmbiguousMethodCallArguments( + expression, + results, + list, + resolved, + result, + methodCall, + myResolveHelper, + nameElement + )); + } + } + } + catch (IndexNotReadyException ignored) { + } + } + } + + if (!myHolder.hasErrorResults() && resultForIncompleteCode != null) { + myHolder.add(HighlightUtil.checkExpressionRequired(expression, resultForIncompleteCode)); + } + + if (!myHolder.hasErrorResults() && resolved instanceof PsiField field) { + try { + myHolder.add(HighlightUtil.checkIllegalForwardReferenceToField(expression, field)); + } + catch (IndexNotReadyException ignored) { + } + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkAccessStaticFieldFromEnumConstructor(expression, result)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved)); + } + PsiExpression qualifierExpression = expression.getQualifierExpression(); + myHolder.add(HighlightUtil.checkUnqualifiedSuperInDefaultMethod(myLanguageLevel, expression, qualifierExpression)); + if (!myHolder.hasErrorResults() && qualifierExpression != null) { + PsiType type = qualifierExpression.getType(); + if (type instanceof PsiCapturedWildcardType capturedWildcardType) { + type = capturedWildcardType.getUpperBound(); + } + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null) { + add(GenericsHighlightUtil.areSupersAccessible(psiClass, expression)); + } + } + + if (!myHolder.hasErrorResults() && resolved != null && myJavaModule != null) { + add(ModuleHighlightUtil.checkPackageAccessibility(expression, resolved, myJavaModule)); + } + } + + @Override + @RequiredReadAction + public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + add(checkFeature(expression, JavaFeature.METHOD_REFERENCES)); + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (parent instanceof PsiExpressionStatement) { + return; + } + + JavaResolveResult result; + JavaResolveResult[] results; + try { + results = expression.multiResolve(true); + result = results.length == 1 ? results[0] : JavaResolveResult.EMPTY; + } + catch (IndexNotReadyException e) { + return; + } + if (myRefCountHolder != null) { + myRefCountHolder.registerReference(expression, result); + } + PsiElement method = result.getElement(); + if (method != null && !result.isAccessible()) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(HighlightUtil.buildProblemWithAccessDescription(expression, result)); + HighlightUtil.registerAccessQuickFixAction( + (PsiMember)method, + expression, + hlBuilder, + expression.getTextRange(), + result.getCurrentFileResolveScope() + ); + myHolder.add(hlBuilder.create()); + } + else { + TextAttributesScheme colorsScheme = myHolder.getColorsScheme(); + if (method instanceof PsiMethod method1 && !expression.isConstructor()) { + PsiElement methodNameElement = expression.getReferenceNameElement(); + if (methodNameElement != null) { + myHolder.add(HighlightNamesUtil.highlightMethodName(method1, methodNameElement, false, colorsScheme)); + } + } + myHolder.add(HighlightNamesUtil.highlightClassNameInQualifier(expression, colorsScheme)); + } + + if (!LambdaUtil.isValidLambdaContext(parent)) { + add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodReferenceNotExpected()) + ); + } + + PsiType functionalInterfaceType = expression.getFunctionalInterfaceType(); + if (!myHolder.hasErrorResults()) { + if (functionalInterfaceType != null) { + boolean notFunctional = !LambdaUtil.isFunctionalType(functionalInterfaceType); + if (notFunctional) { + add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lambdaNotAFunctionalInterface( + functionalInterfaceType.getPresentableText() + )) + ); + } + } + } + + if (!myHolder.hasErrorResults()) { + PsiElement referenceNameElement = expression.getReferenceNameElement(); + if (referenceNameElement instanceof PsiKeyword && !PsiMethodReferenceUtil.isValidQualifier(expression)) { + PsiElement qualifier = expression.getQualifier(); + if (qualifier != null) { + add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaCompilationErrorLocalize.methodReferenceQualifierClassUnresolved(qualifier.getText())) + ); + } + } + } + + if (!myHolder.hasErrorResults()) { + checkFunctionalInterfaceTypeAccessible(expression, functionalInterfaceType); + } + + if (!myHolder.hasErrorResults() && functionalInterfaceType != null) { + LocalizeValue errorMessage = PsiMethodReferenceUtil.checkMethodReferenceContext(expression); + if (errorMessage.isNotEmpty()) { + HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(errorMessage); + if (method instanceof PsiMethod method1 && !method1.isConstructor() && !method1.isAbstract()) { + boolean shouldHave = !method1.isStatic(); + info.registerFix( + QuickFixFactory.getInstance() + .createModifierFixBuilder(method1) + .toggle(PsiModifier.STATIC, shouldHave) + .create() + ); + } + myHolder.add(info.create()); + } + } + + if (!myHolder.hasErrorResults() && expression.getQualifier() instanceof PsiTypeElement typeElem) { + PsiType psiType = typeElem.getType(); + HighlightInfo genericArrayCreationInfo = GenericsHighlightUtil.checkGenericArrayCreation(typeElem, psiType); + if (genericArrayCreationInfo != null) { + myHolder.add(genericArrayCreationInfo); + } + else { + LocalizeValue wildcardMessage = PsiMethodReferenceUtil.checkTypeArguments(typeElem, psiType); + if (wildcardMessage.isNotEmpty()) { + add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElem) + .descriptionAndTooltip(wildcardMessage) + ); + } + } + } + + if (!myHolder.hasErrorResults()) { + add(PsiMethodReferenceHighlightingUtil.checkRawConstructorReference(expression)); + } + + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkUnhandledExceptions(expression, expression.getTextRange())); + } + + if (!myHolder.hasErrorResults()) { + LocalizeValue badReturnTypeMessage = PsiMethodReferenceUtil.checkReturnType(expression, result, functionalInterfaceType); + if (badReturnTypeMessage.isNotEmpty()) { + myHolder.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(badReturnTypeMessage) + .create()); + } + } + + if (!myHolder.hasErrorResults()) { + if (results.length == 0 + || results[0] instanceof MethodCandidateInfo candidate && !candidate.isApplicable() && functionalInterfaceType != null) { + LocalizeValue description = null; + if (results.length == 1) { + description = LocalizeValue.ofNullable(((MethodCandidateInfo) results[0]).getInferenceErrorMessage()); + } + if (expression.isConstructor()) { + PsiClass containingClass = PsiMethodReferenceUtil.getQualifierResolveResult(expression).getContainingClass(); + + if (containingClass != null + && !add(HighlightClassUtil.checkInstantiationOfAbstractClass(containingClass, expression)) + && !myHolder.add(GenericsHighlightUtil.checkEnumInstantiation(expression, containingClass)) + && containingClass.isPhysical() && description == null) { + description = JavaCompilationErrorLocalize.methodReferenceUnresolvedConstructor(containingClass.getName()); + } + } + else if (description == null) { + description = JavaCompilationErrorLocalize.methodReferenceUnresolvedMethod(expression.getReferenceName()); + } + + if (description != null) { + PsiElement referenceNameElement = notNull(expression.getReferenceNameElement(), expression); + HighlightInfoType type = results.length == 0 ? HighlightInfoType.WRONG_REF : HighlightInfoType.ERROR; + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(type) + .descriptionAndTooltip(description) + .range(referenceNameElement) + .newFix(QuickFixFactory.getInstance().createCreateMethodFromUsageFix(expression)) + .fixRange(HighlightMethodUtil.getFixRange(referenceNameElement)) + .register(); + myHolder.add(hlBuilder.create()); + } + } + } + } + + // 15.13 | 15.27 + // It is a compile-time error if any class or interface mentioned by either U or the function type of U + // is not accessible from the class or interface in which the method reference expression appears. + @RequiredReadAction + private void checkFunctionalInterfaceTypeAccessible(PsiFunctionalExpression expression, PsiType functionalInterfaceType) { + PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass != null) { + if (!PsiUtil.isAccessible(myFile.getProject(), psiClass, expression, null)) { + myHolder.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(HighlightUtil.buildProblemWithAccessDescription(expression, resolveResult)) + .create() + ); + } + else { + for (PsiType type : resolveResult.getSubstitutor().getSubstitutionMap().values()) { + checkFunctionalInterfaceTypeAccessible(expression, type); + } + } + } + } + + @Override + @RequiredReadAction + public void visitReferenceList(PsiReferenceList list) { + if (list.getFirstChild() == null) { + return; + } + PsiElement parent = list.getParent(); + if (!(parent instanceof PsiTypeParameter)) { + myHolder.add(AnnotationsHighlightUtil.checkAnnotationDeclaration(parent, list)); + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkExtendsAllowed(list)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkImplementsAllowed(list)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightClassUtil.checkClassExtendsOnlyOneClass(list)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkGenericCannotExtendException(list)); + } + } + } + + @Override + @RequiredReadAction + public void visitReferenceParameterList(PsiReferenceParameterList list) { + if (list.getTextLength() == 0) { + return; + } + + add(checkFeature(list, JavaFeature.GENERICS)); + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkParametersAllowed(list)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkParametersOnRaw(list)); + } + if (!myHolder.hasErrorResults()) { + for (PsiTypeElement typeElement : list.getTypeParameterElements()) { + if (typeElement.getType() instanceof PsiDiamondType) { + add(checkFeature(list, JavaFeature.DIAMOND_TYPES)); + } + } + } + } + + @Override + @RequiredReadAction + public void visitReturnStatement(PsiReturnStatement statement) { + try { + add(HighlightUtil.checkReturnStatementType(statement)); + } + catch (IndexNotReadyException ignore) { + } + } + + @Override + @RequiredReadAction + public void visitStatement(PsiStatement statement) { + super.visitStatement(statement); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkNotAStatement(statement)); + } + } + + @Override + @RequiredReadAction + public void visitSuperExpression(PsiSuperExpression expr) { + myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel)); + if (!myHolder.hasErrorResults()) { + visitExpression(expr); + } + } + + @Override + @RequiredReadAction + public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) { + super.visitSwitchLabelStatement(statement); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkCaseStatement(statement)); + } + } + + @Override + @RequiredReadAction + public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) { + super.visitSwitchLabeledRuleStatement(statement); + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkCaseStatement(statement)); + } + } + + + @Override + @RequiredReadAction + public void visitSwitchStatement(PsiSwitchStatement statement) { + super.visitSwitchStatement(statement); + checkSwitchBlock(statement); + } + + @RequiredReadAction + private void checkSwitchBlock(PsiSwitchBlock switchBlock) { + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkSwitchBlockStatements(switchBlock, myLanguageLevel, myFile)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkSwitchSelectorType(switchBlock, myLanguageLevel)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(HighlightUtil.checkSwitchLabelValues(switchBlock)); + } + } + + @Override + @RequiredReadAction + public void visitThisExpression(PsiThisExpression expr) { + if (!(expr.getParent() instanceof PsiReceiverParameter)) { + myHolder.add(HighlightUtil.checkThisOrSuperExpressionInIllegalContext(expr, expr.getQualifier(), myLanguageLevel)); + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkMemberReferencedBeforeConstructorCalled(expr, null, myFile)); + } + if (!myHolder.hasErrorResults()) { + visitExpression(expr); + } + } + } + + @Override + @RequiredReadAction + public void visitThrowStatement(PsiThrowStatement statement) { + add(HighlightUtil.checkUnhandledExceptions(statement, null)); + if (!myHolder.hasErrorResults()) { + visitStatement(statement); + } + } + + @Override + @RequiredReadAction + public void visitTryStatement(PsiTryStatement statement) { + super.visitTryStatement(statement); + if (!myHolder.hasErrorResults()) { + Set thrownTypes = HighlightUtil.collectUnhandledExceptions(statement); + for (PsiParameter parameter : statement.getCatchBlockParameters()) { + boolean added = myHolder.addAll(HighlightUtil.checkExceptionAlreadyCaught(parameter)); + if (!added) { + added = myHolder.addAll(HighlightUtil.checkExceptionThrownInTry(parameter, thrownTypes)); + } + if (!added) { + myHolder.addAll(HighlightUtil.checkWithImprovedCatchAnalysis(parameter, thrownTypes, myFile)); + } + } + } + } + + @Override + @RequiredReadAction + public void visitResourceList(PsiResourceList resourceList) { + super.visitResourceList(resourceList); + if (!myHolder.hasErrorResults()) { + add(checkFeature(resourceList, JavaFeature.TRY_WITH_RESOURCES)); + } + } + + @Override + @RequiredReadAction + public void visitResourceVariable(PsiResourceVariable resource) { + super.visitResourceVariable(resource); + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkTryResourceIsAutoCloseable(resource)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnhandledCloserExceptions(resource)); + } + } + + @Override + @RequiredReadAction + public void visitResourceExpression(PsiResourceExpression resource) { + super.visitResourceExpression(resource); + if (!myHolder.hasErrorResults()) { + add(checkFeature(resource, JavaFeature.REFS_AS_RESOURCE)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkResourceVariableIsFinal(resource)); + } + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkTryResourceIsAutoCloseable(resource)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkUnhandledCloserExceptions(resource)); + } + } + + @Override + @RequiredReadAction + public void visitTypeElement(PsiTypeElement type) { + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkIllegalType(type)); + } + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkReferenceTypeUsedAsTypeArgument(type, myLanguageLevel)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(GenericsHighlightUtil.checkWildcardUsage(type)); + } + } + + @Override + @RequiredReadAction + public void visitTypeCastExpression(PsiTypeCastExpression typeCast) { + super.visitTypeCastExpression(typeCast); + try { + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkIntersectionInTypeCast(typeCast, myLanguageLevel, myFile)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(HighlightUtil.checkInconvertibleTypeCast(typeCast)); + } + } + catch (IndexNotReadyException ignored) { + } + } + + @Override + @RequiredReadAction + public void visitTypeParameterList(PsiTypeParameterList list) { + PsiTypeParameter[] typeParameters = list.getTypeParameters(); + if (typeParameters.length > 0) { + add(checkFeature(list, JavaFeature.GENERICS)); + if (!myHolder.hasErrorResults()) { + add(GenericsHighlightUtil.checkTypeParametersList(list, typeParameters, myLanguageLevel)); + } + } + } + + @Override + @RequiredReadAction + public void visitVariable(PsiVariable variable) { + super.visitVariable(variable); + try { + if (!myHolder.hasErrorResults()) { + add(HighlightUtil.checkVariableInitializerType(variable)); + } + } + catch (IndexNotReadyException ignored) { + } + } + + private boolean isReassigned(PsiVariable variable) { + try { + boolean reassigned; + if (variable instanceof PsiParameter) { + reassigned = myReassignedParameters.getInt((PsiParameter)variable) == 2; + } + else { + reassigned = HighlightControlFlowUtil.isReassigned(variable, myFinalVarProblems); + } + + return reassigned; + } + catch (IndexNotReadyException e) { + return false; + } + } + + @Override + @RequiredReadAction + public void visitConditionalExpression(PsiConditionalExpression expression) { + super.visitConditionalExpression(expression); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8) && PsiPolyExpressionUtil.isPolyExpression(expression)) { + PsiExpression thenExpression = expression.getThenExpression(); + PsiExpression elseExpression = expression.getElseExpression(); + if (thenExpression != null && elseExpression != null) { + PsiType conditionalType = expression.getType(); + if (conditionalType != null) { + PsiExpression[] sides = { + thenExpression, + elseExpression + }; + for (PsiExpression side : sides) { + PsiType sideType = side.getType(); + if (sideType != null && !TypeConversionUtil.isAssignable(conditionalType, sideType)) { + add(HighlightUtil.checkAssignability(conditionalType, sideType, side, side)); + } + } + } + } + } + } + + @Override + @RequiredReadAction + public void visitReceiverParameter(PsiReceiverParameter parameter) { + super.visitReceiverParameter(parameter); + if (!myHolder.hasErrorResults()) { + add(checkFeature(parameter, JavaFeature.RECEIVERS)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(AnnotationsHighlightUtil.checkReceiverPlacement(parameter)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(AnnotationsHighlightUtil.checkReceiverType(parameter)); + } + } + + @Override + @RequiredReadAction + public void visitModule(PsiJavaModule module) { + super.visitModule(module); + if (!myHolder.hasErrorResults()) { + add(checkFeature(module, JavaFeature.MODULES)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkFileName(module, myFile)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkFileDuplicates(module, myFile)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(ModuleHighlightUtil.checkDuplicateStatements(module)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkClashingReads(module)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(ModuleHighlightUtil.checkUnusedServices(module)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkFileLocation(module, myFile)); + } + } + + @Override + @RequiredReadAction + public void visitRequiresStatement(PsiRequiresStatement statement) { + super.visitRequiresStatement(statement); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + PsiJavaModule container = (PsiJavaModule)statement.getParent(); + PsiJavaModuleReferenceElement ref = statement.getReferenceElement(); + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkModuleReference(ref, container)); + } + } + } + + @Override + @RequiredReadAction + public void visitPackageAccessibilityStatement(PsiPackageAccessibilityStatement statement) { + super.visitPackageAccessibilityStatement(statement); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkHostModuleStrength(statement)); + } + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkPackageReference(statement)); + } + if (!myHolder.hasErrorResults()) { + myHolder.addAll(ModuleHighlightUtil.checkPackageAccessTargets(statement)); + } + } + } + + @Override + @RequiredReadAction + public void visitUsesStatement(PsiUsesStatement statement) { + super.visitUsesStatement(statement); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + if (!myHolder.hasErrorResults()) { + myHolder.add(ModuleHighlightUtil.checkServiceReference(statement.getClassReference())); + } + } + } + + @Override + @RequiredReadAction + public void visitProvidesStatement(PsiProvidesStatement statement) { + super.visitProvidesStatement(statement); + if (myLanguageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + if (!myHolder.hasErrorResults()) { + myHolder.addAll(ModuleHighlightUtil.checkServiceImplementations(statement)); + } + } + } + + @RequiredReadAction + private HighlightInfo.@Nullable Builder checkFeature(PsiElement element, JavaFeature feature) { + return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile); + } + + private boolean add(HighlightInfo.Builder hlInfoBuilder) { + return hlInfoBuilder != null && myHolder.add(hlInfoBuilder.create()); + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java index ac8cb8d4ed..2754dcd3ea 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/IncreaseLanguageLevelFix.java @@ -16,106 +16,110 @@ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; import com.intellij.java.language.LanguageLevel; -import com.intellij.java.language.projectRoots.JavaSdk; +import com.intellij.java.language.impl.projectRoots.JavaSdkVersionUtil; import com.intellij.java.language.projectRoots.JavaSdkVersion; -import consulo.application.ApplicationManager; import consulo.codeEditor.Editor; import consulo.content.bundle.Sdk; import consulo.java.language.module.extension.JavaModuleExtension; import consulo.java.language.module.extension.JavaMutableModuleExtension; -import consulo.language.editor.CodeInsightBundle; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.intention.SyntheticIntentionAction; +import consulo.language.editor.localize.CodeInsightLocalize; +import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.module.Module; import consulo.module.content.ModuleRootManager; import consulo.module.content.layer.ModifiableRootModel; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.virtualFileSystem.VirtualFile; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author cdr */ -public class IncreaseLanguageLevelFix implements SyntheticIntentionAction { - private static final Logger LOG = Logger.getInstance(IncreaseLanguageLevelFix.class); +public class IncreaseLanguageLevelFix implements SyntheticIntentionAction, LocalQuickFix { + private static final Logger LOG = Logger.getInstance(IncreaseLanguageLevelFix.class); - private final LanguageLevel myLevel; + private final LanguageLevel myLevel; - public IncreaseLanguageLevelFix(LanguageLevel targetLevel) { - myLevel = targetLevel; - } + public IncreaseLanguageLevelFix(LanguageLevel targetLevel) { + myLevel = targetLevel; + } - @Override - @Nonnull - public String getText() { - return CodeInsightBundle.message("set.language.level.to.0", myLevel.getDescription()); - } + @Override + public LocalizeValue getText() { + return CodeInsightLocalize.setLanguageLevelTo0(myLevel.getDescription().get()); + } - private static boolean isJdkSupportsLevel(@Nullable final Sdk jdk, final LanguageLevel level) { - if (jdk == null) { - return true; + @Override + public LocalizeValue getName() { + return getText(); } - final JavaSdk sdk = JavaSdk.getInstance(); - final JavaSdkVersion version = sdk.getVersion(jdk); - return version != null && version.getMaxLanguageLevel().isAtLeast(level); - } - - @Override - public boolean isAvailable(@Nonnull final Project project, final Editor editor, final PsiFile file) { - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return false; + + @Override + @RequiredUIAccess + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + invoke(project, null, element.getContainingFile()); } - final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); - if (module == null) { - return false; + private static boolean isJdkSupportsLevel(@Nullable Sdk jdk, LanguageLevel level) { + if (jdk == null) { + return true; + } + JavaSdkVersion version = JavaSdkVersionUtil.getJavaSdkVersion(jdk); + JavaSdkVersion required = JavaSdkVersion.fromLanguageLevel(level); + return version != null && (level.isPreview() ? version.equals(required) : version.isAtLeast(required)); } - return true; - // return isLanguageLevelAcceptable(module, myLevel); - } - - public static boolean isLanguageLevelAcceptable(Module module, final LanguageLevel level) { - return isJdkSupportsLevel(ModuleUtilCore.getSdk(module, JavaModuleExtension.class), level); - } - - @Override - public void invoke(@Nonnull 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); - if (module == null) { - return; + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + return false; + } + + Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + return module != null && isLanguageLevelAcceptable(module, myLevel); } - JavaModuleExtension extension = ModuleUtilCore.getExtension(module, JavaModuleExtension.class); - if (extension == null) { - return; + public static boolean isLanguageLevelAcceptable(Module module, LanguageLevel level) { + return isJdkSupportsLevel(ModuleUtilCore.getSdk(module, JavaModuleExtension.class), level); } - final ModifiableRootModel rootModel = ModuleRootManager.getInstance(module).getModifiableModel(); - JavaMutableModuleExtension mutableModuleExtension = rootModel.getExtension(JavaMutableModuleExtension.class); + @Override + @RequiredUIAccess + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + VirtualFile virtualFile = file.getVirtualFile(); + LOG.assertTrue(virtualFile != null); + Module module = ModuleUtilCore.findModuleForFile(virtualFile, project); + if (module == null) { + return; + } - assert mutableModuleExtension != null; + JavaModuleExtension extension = ModuleUtilCore.getExtension(module, JavaModuleExtension.class); + if (extension == null) { + return; + } - mutableModuleExtension.getInheritableLanguageLevel().set(null, myLevel.getName()); + ModifiableRootModel rootModel = ModuleRootManager.getInstance(module).getModifiableModel(); + JavaMutableModuleExtension mutableModuleExtension = rootModel.getExtension(JavaMutableModuleExtension.class); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - rootModel.commit(); - } - }); - } + assert mutableModuleExtension != null; - @Override - public boolean startInWriteAction() { - return false; - } + mutableModuleExtension.getInheritableLanguageLevel().set(null, myLevel.getName()); + + project.getApplication().runWriteAction(rootModel::commit); + } + + @Override + public boolean startInWriteAction() { + return false; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaHighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaHighlightUtil.java index 1a6947aafb..74119123dc 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaHighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaHighlightUtil.java @@ -20,21 +20,19 @@ import com.intellij.java.language.psi.util.PsiFormatUtilBase; import com.intellij.java.language.psi.util.TypeConversionUtil; import com.intellij.java.language.util.JavaPsiConstructorUtil; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiManager; import consulo.util.lang.ObjectUtil; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class JavaHighlightUtil { - public static boolean isSerializable(@Nonnull PsiClass aClass) { + public static boolean isSerializable(PsiClass aClass) { PsiManager manager = aClass.getManager(); - PsiClass serializableClass = JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Serializable", aClass.getResolveScope()); + PsiClass serializableClass = JavaPsiFacade.getInstance(manager.getProject()) + .findClass(CommonClassNames.JAVA_IO_SERIALIZABLE, aClass.getResolveScope()); return serializableClass != null && aClass.isInheritor(serializableClass, true); } @@ -45,7 +43,7 @@ public static boolean isSerializationRelatedMethod(PsiMethod method, PsiClass co if (method.hasModifierProperty(PsiModifier.STATIC)) { return false; } - @NonNls String name = method.getName(); + String name = method.getName(); PsiParameter[] parameters = method.getParameterList().getParameters(); PsiType returnType = method.getReturnType(); if ("readObjectNoData".equals(name)) { @@ -60,26 +58,25 @@ public static boolean isSerializationRelatedMethod(PsiMethod method, PsiClass co if ("readResolve".equals(name)) { return parameters.length == 0 && returnType != null - && returnType.equalsToText(JavaClassNames.JAVA_LANG_OBJECT) - && (containingClass.hasModifierProperty(PsiModifier.ABSTRACT) || isSerializable(containingClass)); + && returnType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + && (containingClass.isAbstract() || isSerializable(containingClass)); } if ("writeReplace".equals(name)) { return parameters.length == 0 && returnType != null - && returnType.equalsToText(JavaClassNames.JAVA_LANG_OBJECT) - && (containingClass.hasModifierProperty(PsiModifier.ABSTRACT) || isSerializable(containingClass)); + && returnType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + && (containingClass.isAbstract() || isSerializable(containingClass)); } if ("writeObject".equals(name)) { return parameters.length == 1 && TypeConversionUtil.isVoidType(returnType) && parameters[0].getType().equalsToText("java.io.ObjectOutputStream") - && method.hasModifierProperty(PsiModifier.PRIVATE) + && method.isPrivate() && isSerializable(containingClass); } return false; } - @Nonnull public static String formatType(@Nullable PsiType type) { if (type == null) { return PsiKeyword.NULL; @@ -89,7 +86,7 @@ public static String formatType(@Nullable PsiType type) { } @Nullable - private static PsiType getArrayInitializerType(@Nonnull final PsiArrayInitializerExpression element) { + private static PsiType getArrayInitializerType(final PsiArrayInitializerExpression element) { final PsiType typeCheckResult = sameType(element.getInitializers()); if (typeCheckResult != null) { return typeCheckResult.createArrayType(); @@ -98,7 +95,7 @@ private static PsiType getArrayInitializerType(@Nonnull final PsiArrayInitialize } @Nullable - public static PsiType sameType(@Nonnull PsiExpression[] expressions) { + public static PsiType sameType(PsiExpression[] expressions) { PsiType type = null; for (PsiExpression expression : expressions) { final PsiType currentType; @@ -116,8 +113,7 @@ public static PsiType sameType(@Nonnull PsiExpression[] expressions) { return type; } - @Nonnull - public static String formatMethod(@Nonnull PsiMethod method) { + public static String formatMethod(PsiMethod method) { return PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); } @@ -151,8 +147,7 @@ public static boolean isSuperOrThisCall(PsiStatement statement, boolean testForS * * @return referring constructor */ - @Nonnull - public static List getChainedConstructors(@Nonnull PsiMethod constructor) { + public static List getChainedConstructors(PsiMethod constructor) { final ConstructorVisitorInfo info = new ConstructorVisitorInfo(); visitConstructorChain(constructor, info); if (info.visitedConstructors != null) { @@ -161,7 +156,7 @@ public static List getChainedConstructors(@Nonnull PsiMethod construc return ObjectUtil.notNull(info.visitedConstructors, Collections.emptyList()); } - static void visitConstructorChain(@Nonnull PsiMethod entry, @Nonnull ConstructorVisitorInfo info) { + static void visitConstructorChain(PsiMethod entry, ConstructorVisitorInfo info) { PsiMethod constructor = entry; while (true) { PsiMethodCallExpression methodCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphHelperImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphHelperImpl.java new file mode 100644 index 0000000000..6670bafbd9 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphHelperImpl.java @@ -0,0 +1,37 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; + +import com.intellij.java.language.impl.psi.impl.light.LightJavaModule; +import com.intellij.java.language.psi.PsiJavaModule; +import com.intellij.java.language.psi.PsiPackageAccessibilityStatement; +import com.intellij.java.language.psi.util.JavaModuleGraphHelper; +import consulo.annotation.component.ServiceImpl; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiFile; +import jakarta.inject.Singleton; + +import org.jspecify.annotations.Nullable; +import java.util.List; + +@Singleton +@ServiceImpl +public final class JavaModuleGraphHelperImpl implements JavaModuleGraphHelper { + @Override + public List getExportedPackages(PsiElement place, PsiJavaModule module) { + return JavaModuleGraphUtil.getExportedPackages(place, module); + } + + @Override + public boolean isAccessible(String targetPackageName, @Nullable PsiFile targetFile, PsiElement place) { + PsiJavaModule refModule = JavaModuleGraphUtil.findDescriptorByElement(place); + if (refModule == null) return true; + PsiJavaModule targetModule = JavaModuleGraphUtil.findDescriptorByElement(targetFile); + if (targetModule == null || refModule.equals(targetModule)) return true; + + String requiredName = targetModule.getName(); + if (!(targetModule instanceof LightJavaModule || JavaModuleGraphUtil.exports(targetModule, targetPackageName, refModule))) { + return false; + } + return PsiJavaModule.JAVA_BASE.equals(requiredName) || JavaModuleGraphUtil.reads(refModule, targetModule); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java index 570ddd75ff..daafa23187 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java @@ -2,9 +2,13 @@ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; import com.intellij.java.indexing.impl.stubs.index.JavaModuleNameIndex; +import com.intellij.java.indexing.search.searches.JavaModuleSearch; import com.intellij.java.language.JavaLanguage; +import com.intellij.java.language.impl.psi.impl.PsiJavaModuleModificationTracker; import com.intellij.java.language.impl.psi.impl.light.LightJavaModule; +import com.intellij.java.language.impl.psi.util.JavaManifestUtil; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.CachedValueProvider.Result; import consulo.application.util.CachedValuesManager; import consulo.component.util.graph.DFSTBuilder; @@ -15,24 +19,29 @@ import consulo.language.file.LanguageFileType; import consulo.language.psi.*; import consulo.language.psi.scope.GlobalSearchScope; +import consulo.language.psi.search.FilenameIndex; +import consulo.language.psi.stub.DumbModeAccessType; +import consulo.language.psi.stub.FileBasedIndex; import consulo.language.psi.util.LanguageCachedValueUtil; +import consulo.project.DumbService; import consulo.module.Module; import consulo.module.ModuleManager; import consulo.module.content.ModuleRootManager; import consulo.module.content.ProjectFileIndex; import consulo.project.Project; -import consulo.project.content.scope.ProjectAwareSearchScope; +import consulo.project.content.ProjectRootModificationTracker; import consulo.project.content.scope.ProjectScopes; import consulo.util.collection.ContainerUtil; import consulo.util.collection.MultiMap; +import consulo.util.collection.SmartList; import consulo.util.lang.ObjectUtil; import consulo.util.lang.Trinity; import consulo.virtualFileSystem.VirtualFile; import consulo.virtualFileSystem.archive.ArchiveFileSystem; import consulo.virtualFileSystem.fileType.FileTypeRegistry; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.*; import java.util.function.BiFunction; import java.util.function.Predicate; @@ -46,29 +55,60 @@ private JavaModuleGraphUtil() { @Nullable public static PsiJavaModule findDescriptorByElement(@Nullable PsiElement element) { - if (element != null) { - PsiFileSystemItem fsItem = element instanceof PsiFileSystemItem ? (PsiFileSystemItem) element : element.getContainingFile(); - if (fsItem != null) { - return findDescriptorByFile(fsItem.getVirtualFile(), fsItem.getProject()); + if (element == null) { + return null; + } + if (element instanceof PsiJavaModule module) { + return module; + } + if (element.getContainingFile() instanceof PsiJavaFile file) { + PsiJavaModule module = file.getModuleDeclaration(); + if (module != null) { + return module; } } + if (element instanceof PsiFileSystemItem fsItem) { + return findDescriptorByFile(fsItem.getVirtualFile(), fsItem.getProject()); + } + + PsiFile file = element.getContainingFile(); + if (file != null) { + return findDescriptorByFile(file.getVirtualFile(), file.getProject()); + } + + if (element instanceof PsiJavaPackage psiPackage) { + PsiDirectory[] directories = psiPackage.getDirectories((GlobalSearchScope) ProjectScopes.getLibrariesScope(psiPackage.getProject())); + for (PsiDirectory directory : directories) { + PsiJavaModule descriptor = findDescriptorByFile(directory.getVirtualFile(), directory.getProject()); + if (descriptor != null) { + return descriptor; + } + } + } return null; } @Nullable - public static PsiJavaModule findDescriptorByFile(@Nullable VirtualFile file, @Nonnull Project project) { + public static PsiJavaModule findDescriptorByFile(@Nullable VirtualFile file, Project project) { if (file == null) { return null; } - ProjectFileIndex index = ProjectFileIndex.getInstance(project); return index.isInLibrary(file) - ? findDescriptorInLibrary(project, index, file) - : findDescriptorByModule(index.getModuleForFile(file), index.isInTestSourceContent(file)); + ? findDescriptorInLibrary(file, project) + : findDescriptorByModule(index.getModuleForFile(file), index.isInTestSourceContent(file)); } - private static PsiJavaModule findDescriptorInLibrary(Project project, ProjectFileIndex index, VirtualFile file) { + /** + * @param file library content root + * @param project current project + * @return JPMS module declared by the supplied library; null if not found + */ + @Nullable + @RequiredReadAction + public static PsiJavaModule findDescriptorInLibrary(VirtualFile file, Project project) { + ProjectFileIndex index = ProjectFileIndex.getInstance(project); VirtualFile root = index.getClassRootForFile(file); if (root != null) { VirtualFile descriptorFile = JavaModuleNameIndex.descriptorFile(root); @@ -77,86 +117,197 @@ private static PsiJavaModule findDescriptorInLibrary(Project project, ProjectFil if (psiFile instanceof PsiJavaFile) { return ((PsiJavaFile) psiFile).getModuleDeclaration(); } - } else if (root.getFileSystem() instanceof ArchiveFileSystem && "jar".equalsIgnoreCase(root.getExtension())) { + } + else if (root.getFileSystem() instanceof ArchiveFileSystem && "jar".equalsIgnoreCase(root.getExtension())) { return LightJavaModule.findModule(PsiManager.getInstance(project), root); } } + else { + root = index.getSourceRootForFile(file); + if (root != null) { + VirtualFile moduleDescriptor = root.findChild(PsiJavaModule.MODULE_INFO_FILE); + PsiFile psiFile = moduleDescriptor != null ? PsiManager.getInstance(project).findFile(moduleDescriptor) : null; + if (psiFile instanceof PsiJavaFile) { + return ((PsiJavaFile) psiFile).getModuleDeclaration(); + } + } + } return null; } @Nullable public static PsiJavaModule findDescriptorByModule(@Nullable Module module, boolean inTests) { - if (module != null) { - Predicate rootType = inTests ? LanguageContentFolderScopes.test() : LanguageContentFolderScopes.production(); - List files = ContainerUtil.mapNotNull(ModuleRootManager.getInstance(module).getContentFolderFiles(rootType), - root -> root.findChild(PsiJavaModule.MODULE_INFO_FILE)); - if (files.size() == 1) { - PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(files.get(0)); + if (module == null) { + return null; + } + CachedValuesManager valuesManager = CachedValuesManager.getManager(module.getProject()); + PsiJavaModule javaModule = inTests //to have different providers for production and tests + ? valuesManager.getCachedValue(module, () -> createModuleCacheResult(module, true)) + : valuesManager.getCachedValue(module, () -> createModuleCacheResult(module, false)); + return javaModule != null && javaModule.isValid() ? javaModule : null; + } + + private static Result createModuleCacheResult(Module module, boolean inTests) { + Project project = module.getProject(); + return Result.create(findDescriptionByModuleInner(module, inTests), + ProjectRootModificationTracker.getInstance(project), + PsiJavaModuleModificationTracker.getInstance(project)); + } + + @Nullable + private static PsiJavaModule findDescriptionByModuleInner(Module module, boolean inTests) { + Project project = module.getProject(); + GlobalSearchScope moduleScope = GlobalSearchScope.moduleScope(module); + String virtualAutoModuleName = JavaManifestUtil.getManifestAttributeValue(module, PsiJavaModule.AUTO_MODULE_NAME); + if (!DumbService.isDumb(project) && + FilenameIndex.getVirtualFilesByName(project, PsiJavaModule.MODULE_INFO_FILE, moduleScope).isEmpty() && + FilenameIndex.getVirtualFilesByName(project, "MANIFEST.MF", moduleScope).isEmpty() && + virtualAutoModuleName == null) { + return null; + } + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + Set excludeRoots = new HashSet<>(Arrays.asList(rootManager.getExcludeRoots())); + Predicate sourceScope = inTests ? LanguageContentFolderScopes.onlyTest() : LanguageContentFolderScopes.onlyProduction(); + List sourceRoots = + ContainerUtil.filter(Arrays.asList(rootManager.getContentFolderFiles(sourceScope)), root -> !excludeRoots.contains(root)); + + List files = ContainerUtil.mapNotNull(sourceRoots, root -> root.findChild(PsiJavaModule.MODULE_INFO_FILE)); + if (files.isEmpty()) { + // META-INF/MANIFEST.MF can live in source or resource roots + Predicate sourceAndResourceScope = + inTests ? LanguageContentFolderScopes.test() : LanguageContentFolderScopes.production(); + List roots = + ContainerUtil.filter(Arrays.asList(rootManager.getContentFolderFiles(sourceAndResourceScope)), root -> !excludeRoots.contains(root)); + files = ContainerUtil.mapNotNull(roots, root -> root.findFileByRelativePath(JarFile.MANIFEST_NAME)); + if (files.size() == 1 || new HashSet<>(files).size() == 1) { + VirtualFile manifest = files.get(0); + PsiFile manifestPsi = PsiManager.getInstance(project).findFile(manifest); + if (manifestPsi != null) { + return LanguageCachedValueUtil.getCachedValue(manifestPsi, () -> { + String name = LightJavaModule.claimedModuleName(manifest); + LightJavaModule result = + name != null ? LightJavaModule.create(PsiManager.getInstance(project), manifest.getParent().getParent(), name) : null; + return Result.create(result, manifestPsi, ProjectRootModificationTracker.getInstance(project)); + }); + } + } + // automatic module name provided by the build system (e.g. maven-jar-plugin Automatic-Module-Name in the POM) + VirtualFile[] sourceSourceRoots = rootManager.getContentFolderFiles(LanguageContentFolderScopes.onlyProduction()); + if (virtualAutoModuleName != null && sourceSourceRoots.length != 0) { + return LightJavaModule.create(PsiManager.getInstance(project), sourceSourceRoots[0], virtualAutoModuleName); + } + } + else { + VirtualFile file = files.get(0); + if (ContainerUtil.and(files, f -> f.equals(file))) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(file); if (psiFile instanceof PsiJavaFile) { return ((PsiJavaFile) psiFile).getModuleDeclaration(); } - } else if (files.isEmpty()) { - files = ContainerUtil.mapNotNull(ModuleRootManager.getInstance(module).getContentFolderFiles(rootType), - root -> root.findFileByRelativePath(JarFile.MANIFEST_NAME)); - if (files.size() == 1) { - VirtualFile manifest = files.get(0); - String name = LightJavaModule.claimedModuleName(manifest); - if (name != null) { - return LightJavaModule.findModule(PsiManager.getInstance(module.getProject()), manifest.getParent().getParent()); - } - } } } return null; } - @Nonnull - public static Collection findCycle(@Nonnull PsiJavaModule module) { + public static Collection findCycle(PsiJavaModule module) { Project project = module.getProject(); List> cycles = CachedValuesManager.getManager(project).getCachedValue(project, () -> - Result.create(findCycles(project), cacheDependency())); + Result.create(findCycles(project), + PsiJavaModuleModificationTracker.getInstance(project), + ProjectRootModificationTracker.getInstance(project))); return ObjectUtil.notNull(ContainerUtil.find(cycles, set -> set.contains(module)), Collections.emptyList()); } - public static boolean exports(@Nonnull PsiJavaModule source, @Nonnull String packageName, @Nullable PsiJavaModule target) { + public static boolean exports(PsiJavaModule source, String packageName, @Nullable PsiJavaModule target) { Map> exports = LanguageCachedValueUtil.getCachedValue(source, () -> Result.create(exportsMap(source), source.getContainingFile())); Set targets = exports.get(packageName); return targets != null && (targets.isEmpty() || target != null && targets.contains(target.getName())); } - public static boolean reads(@Nonnull PsiJavaModule source, @Nonnull PsiJavaModule destination) { + public static boolean reads(PsiJavaModule source, PsiJavaModule destination) { return getRequiresGraph(source).reads(source, destination); } - @Nonnull public static Set getAllDependencies(PsiJavaModule source) { return getRequiresGraph(source).getAllDependencies(source); } + /** + * Retrieves a list of package accessibility statements for a given Java module that + * are accessible to the specified place. + * + * @param place the place from which accessibility is being checked. + * @param module the module whose exported packages are to be retrieved. + * @return a list of `PsiPackageAccessibilityStatement` elements that represent the exported packages accessible + * to the specified place. + */ + public static List getExportedPackages(PsiElement place, PsiJavaModule module) { + List results = new ArrayList<>(); + PsiJavaModule currentModule = findDescriptorByElement(place); + List exports = getAllDeclaredExports(module); + for (PsiPackageAccessibilityStatement export : exports) { + PsiJavaCodeReferenceElement aPackage = export.getPackageReference(); + if (aPackage == null) continue; + List accessibleModules = export.getModuleNames(); + if (!accessibleModules.isEmpty()) { + if (currentModule == null || !accessibleModules.contains(currentModule.getName())) continue; + } + results.add(export); + } + return results; + } + + /** + * Retrieves all transitive modules required by the given module, including the module itself. + * + * @param module the module for which transitive dependencies are being collected; must not be null + * @return a set of transitive modules required by the given module, including the module itself + */ + public static Set getAllTransitiveModulesIncludeCurrent(PsiJavaModule module) { + return LanguageCachedValueUtil.getCachedValue(module, () -> { + Project project = module.getProject(); + Set collected = new HashSet<>(); + collected.addAll(getAllDependencies(module)); + collected.add(module); + return Result.create(collected, + PsiJavaModuleModificationTracker.getInstance(project), + ProjectRootModificationTracker.getInstance(project)); + }); + } + + private static List getAllDeclaredExports(PsiJavaModule module) { + Project project = module.getProject(); + return LanguageCachedValueUtil.getCachedValue(module, () -> { + List exports = new ArrayList<>(); + for (PsiJavaModule javaModule : getAllTransitiveModulesIncludeCurrent(module)) { + for (PsiPackageAccessibilityStatement export : javaModule.getExports()) { + exports.add(export); + } + } + return Result.create(exports, + PsiJavaModuleModificationTracker.getInstance(project), + ProjectRootModificationTracker.getInstance(project)); + }); + } + @Nullable - public static Trinity findConflict(@Nonnull PsiJavaModule module) { + public static Trinity findConflict(PsiJavaModule module) { return getRequiresGraph(module).findConflict(module); } public static @Nullable - PsiJavaModule findOrigin(@Nonnull PsiJavaModule module, @Nonnull String packageName) { + PsiJavaModule findOrigin(PsiJavaModule module, String packageName) { return getRequiresGraph(module).findOrigin(module, packageName); } - @SuppressWarnings("deprecation") - private static Object cacheDependency() { - return PsiModificationTracker.MODIFICATION_COUNT; - } - /* * Looks for cycles between Java modules in the project sources. * Library/JDK modules are excluded in an assumption there can't be any lib -> src dependencies. * Module references are resolved "globally" (i.e., without taking project dependencies into account). */ - @Nonnull private static List> findCycles(Project project) { Set projectModules = new HashSet<>(); for (Module module : ModuleManager.getInstance(project).getModules()) { @@ -200,7 +351,7 @@ private static List> findCycles(Project project) { return Collections.emptyList(); } - private static Map> exportsMap(@Nonnull PsiJavaModule source) { + private static Map> exportsMap(PsiJavaModule source) { Map> map = new HashMap<>(); for (PsiPackageAccessibilityStatement statement : source.getExports()) { String pkg = statement.getPackageName(); @@ -212,8 +363,13 @@ private static Map> exportsMap(@Nonnull PsiJavaModule source private static RequiresGraph getRequiresGraph(PsiJavaModule module) { Project project = module.getProject(); + if (DumbService.getInstance(project).isAlternativeResolveEnabled()) { + return FileBasedIndex.getInstance().ignoreDumbMode(DumbModeAccessType.RELIABLE_DATA_ONLY, () -> buildRequiresGraph(project)); + } return CachedValuesManager.getManager(project).getCachedValue(project, () -> - Result.create(buildRequiresGraph(project), cacheDependency())); + Result.create(FileBasedIndex.getInstance().ignoreDumbMode(DumbModeAccessType.RELIABLE_DATA_ONLY, () -> buildRequiresGraph(project)), + PsiJavaModuleModificationTracker.getInstance(project), + ProjectRootModificationTracker.getInstance(project))); } /* @@ -221,14 +377,19 @@ private static RequiresGraph getRequiresGraph(PsiJavaModule module) { * The resulting graph is used for tracing readability and checking package conflicts. */ private static RequiresGraph buildRequiresGraph(Project project) { + MultiMap allModules = MultiMap.create(); MultiMap relations = MultiMap.create(); Set transitiveEdges = new HashSet<>(); - JavaModuleNameIndex index = JavaModuleNameIndex.getInstance(); - ProjectAwareSearchScope scope = ProjectScopes.getAllScope(project); - for (String key : index.getAllKeys(project)) { - for (PsiJavaModule module : index.get(key, project, scope)) { - visit(module, relations, transitiveEdges); + GlobalSearchScope scope = (GlobalSearchScope) ProjectScopes.getAllScope(project); + JavaModuleSearch.allModules(project, scope).forEach(module -> { + allModules.putValue(module.getName(), module); + return true; + }); + + for (PsiJavaModule module : allModules.values()) { + if (!(module instanceof LightJavaModule)) { + visit(module, relations, transitiveEdges, allModules); } } @@ -236,36 +397,70 @@ private static RequiresGraph buildRequiresGraph(Project project) { return new RequiresGraph(graph, transitiveEdges); } - private static void visit(PsiJavaModule module, MultiMap relations, Set transitiveEdges) { - if (!(module instanceof LightJavaModule) && !relations.containsKey(module)) { - relations.putValues(module, Collections.emptyList()); - boolean explicitJavaBase = false; - for (PsiRequiresStatement statement : module.getRequires()) { - PsiJavaModuleReference ref = statement.getModuleReference(); - if (ref != null) { - if (PsiJavaModule.JAVA_BASE.equals(ref.getCanonicalText())) { - explicitJavaBase = true; - } - for (ResolveResult result : ref.multiResolve(false)) { - PsiJavaModule dependency = (PsiJavaModule) result.getElement(); - assert dependency != null : result; - relations.putValue(module, dependency); - if (statement.hasModifierProperty(PsiModifier.TRANSITIVE)) { - transitiveEdges.add(RequiresGraph.key(dependency, module)); - } - visit(dependency, relations, transitiveEdges); + /** + * Visits a given module and processes its dependencies and relations. Updates the set of visited modules, + * the relations between modules, and the set of transitive edges based on the requires statements within + * the module. + * + * @param module the module to be visited + * @param relations a mapping that represents module dependencies + * @param transitiveEdges a set of transitive edges representing transitive dependencies + * @param allModules a map of module names to PsiJavaModule instances + */ + private static void visit(PsiJavaModule module, + MultiMap relations, + Set transitiveEdges, + MultiMap allModules) { + relations.putValues(module, Collections.emptyList()); + boolean explicitJavaBase = false; + + GlobalSearchScope scope = GlobalSearchScope.allScope(module.getProject()); + for (PsiRequiresStatement statement : module.getRequires()) { + PsiJavaModuleReference ref = statement.getModuleReference(); + if (ref != null) { + String moduleName = ref.getCanonicalText(); + if (PsiJavaModule.JAVA_BASE.equals(moduleName)) { + explicitJavaBase = true; + } + for (PsiJavaModule dependency : filterModules(allModules.get(moduleName), scope)) { + relations.putValue(module, dependency); + if (statement.hasModifierProperty(PsiModifier.TRANSITIVE)) { + transitiveEdges.add(RequiresGraph.key(dependency, module)); } } } - if (!explicitJavaBase) { - PsiJavaModule javaBase = JavaPsiFacade.getInstance(module.getProject()).findModule(PsiJavaModule.JAVA_BASE, module.getResolveScope()); - if (javaBase != null) { - relations.putValue(module, javaBase); - } + } + + if (!explicitJavaBase) { + Collection modules = filterModules(allModules.get(PsiJavaModule.JAVA_BASE), module.getResolveScope()); + if (modules.size() == 1) { + relations.putValue(module, modules.iterator().next()); } } } + private static List filterModules(Collection modules, GlobalSearchScope scope) { + SmartList filtered = new SmartList<>(); + for (PsiJavaModule candidate : modules) { + VirtualFile candidateFile = getVirtualFile(candidate); + if (candidateFile != null && scope.contains(candidateFile)) { + filtered.add(candidate); + } + } + return filtered; + } + + @Nullable + private static VirtualFile getVirtualFile(@Nullable PsiJavaModule module) { + if (module == null) { + return null; + } + if (module instanceof LightJavaModule light) { + return light.getRootVirtualFile(); + } + return PsiUtilCore.getVirtualFile(module); + } + private static final class RequiresGraph { private final Graph myGraph; private final Set myTransitiveEdges; @@ -276,38 +471,56 @@ private RequiresGraph(Graph graph, Set transitiveEdges) { } public boolean reads(PsiJavaModule source, PsiJavaModule destination) { + source = getPhysicalModule(source); + destination = getPhysicalModule(destination); Collection nodes = myGraph.getNodes(); - if (nodes.contains(destination) && nodes.contains(source)) { + if (!nodes.contains(destination) || !nodes.contains(source)) { + return false; + } + + UniqueBuffer buffer = new UniqueBuffer<>(); + buffer.add(destination); + while (!buffer.isEmpty()) { + destination = buffer.poll(); Iterator directReaders = myGraph.getOut(destination); while (directReaders.hasNext()) { PsiJavaModule next = directReaders.next(); - if (source.equals(next) || myTransitiveEdges.contains(key(destination, next)) && reads(source, next)) { + if (source.equals(next)) { return true; } + if (myTransitiveEdges.contains(key(destination, next)) && !next.equals(destination)) { + buffer.add(next); + } } } return false; } public Trinity findConflict(PsiJavaModule source) { + source = getPhysicalModule(source); Map exports = new HashMap<>(); return processExports(source, (pkg, m) -> { - PsiJavaModule existing = exports.put(pkg, m); - return existing != null ? new Trinity<>(pkg, existing, m) : null; + PsiJavaModule found = exports.put(pkg, m); + return found == null || + found instanceof LightJavaModule && m instanceof LightJavaModule || + found.getName().equals(m.getName()) + ? null : new Trinity<>(pkg, found, m); }); } public PsiJavaModule findOrigin(PsiJavaModule module, String packageName) { - return processExports(module, (pkg, m) -> packageName.equals(pkg) ? m : null); + return processExports(getPhysicalModule(module), (pkg, m) -> packageName.equals(pkg) ? m : null); } private T processExports(PsiJavaModule start, BiFunction processor) { - return myGraph.getNodes().contains(start) ? processExports(start.getName(), start, 0, new HashSet<>(), processor) : null; + start = getPhysicalModule(start); + return myGraph.getNodes().contains(start) ? processExports(start.getName(), start, true, new HashSet<>(), processor) : null; } - private T processExports(String name, PsiJavaModule module, int layer, Set visited, BiFunction processor) { + private T processExports(String name, PsiJavaModule module, boolean direct, Set visited, BiFunction processor) { + module = getPhysicalModule(module); if (visited.add(module)) { - if (layer == 1) { + if (!direct) { for (PsiPackageAccessibilityStatement statement : module.getExports()) { List exportTargets = statement.getModuleNames(); if (exportTargets.isEmpty() || exportTargets.contains(name)) { @@ -318,14 +531,12 @@ private T processExports(String name, PsiJavaModule module, int layer, Set

iterator = myGraph.getIn(module); iterator.hasNext(); ) { - PsiJavaModule dependency = iterator.next(); - if (layer == 0 || myTransitiveEdges.contains(key(dependency, module))) { - T result = processExports(name, dependency, 1, visited, processor); - if (result != null) { - return result; - } + for (Iterator iterator = myGraph.getIn(module); iterator.hasNext(); ) { + PsiJavaModule dependency = iterator.next(); + if (direct || myTransitiveEdges.contains(key(dependency, module))) { + T result = processExports(name, dependency, false, visited, processor); + if (result != null) { + return result; } } } @@ -338,14 +549,14 @@ public static String key(PsiJavaModule module, PsiJavaModule exporter) { return module.getName() + '/' + exporter.getName(); } - @Nonnull public Set getAllDependencies(PsiJavaModule module) { Set requires = new HashSet<>(); - collectDependencies(module, requires); + collectDependencies(getPhysicalModule(module), requires); return requires; } private void collectDependencies(PsiJavaModule module, Set dependencies) { + module = getPhysicalModule(module); for (Iterator iterator = myGraph.getIn(module); iterator.hasNext(); ) { PsiJavaModule dependency = iterator.next(); if (!dependencies.contains(dependency)) { @@ -354,6 +565,45 @@ private void collectDependencies(PsiJavaModule module, Set depend } } } + + private static PsiJavaModule getPhysicalModule(PsiJavaModule from) { + if (from.isPhysical()) { + return from; + } + if (!(from.getContainingFile() instanceof PsiJavaFile file)) { + return from; + } + if (!(file.getOriginalFile() instanceof PsiJavaFile origin)) { + return from; + } + if (origin.getModuleDeclaration() instanceof PsiJavaModule result) { + return result; + } + return from; + } + + /** + * FIFO queue that prevents duplicate additions. + * Once added, an element cannot be added again even after being polled. + */ + private static class UniqueBuffer { + private final Set myUnique = new HashSet<>(); + private final Queue myBuffer = new ArrayDeque<>(); + + public void add(T value) { + if (myUnique.add(value)) { + myBuffer.add(value); + } + } + + public T poll() { + return myBuffer.poll(); + } + + public boolean isEmpty() { + return myBuffer.isEmpty(); + } + } } private static final class ChameleonGraph implements Graph { @@ -372,40 +622,52 @@ private ChameleonGraph(MultiMap edges, boolean inbound) { } @Override - @Nonnull public Collection getNodes() { return myNodes; } @Override - @Nonnull public Iterator getIn(N n) { return myInbound ? myEdges.get(n).iterator() : Collections.emptyIterator(); } @Override - @Nonnull public Iterator getOut(N n) { return myInbound ? Collections.emptyIterator() : myEdges.get(n).iterator(); } } public static class JavaModuleScope extends GlobalSearchScope { - private final PsiJavaModule myModule; + private final MultiMap myModules; private final boolean myIncludeLibraries; private final boolean myIsInTests; - private JavaModuleScope(@Nonnull Project project, @Nonnull PsiJavaModule module, @Nonnull VirtualFile moduleFile) { + private JavaModuleScope(Project project, Set modules) { super(project); - myModule = module; + myModules = new MultiMap<>(); + for (PsiJavaModule module : modules) { + myModules.putValue(module.getName(), module); + } ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(project); - myIncludeLibraries = fileIndex.isInLibrary(moduleFile); - myIsInTests = !myIncludeLibraries && fileIndex.isInTestSourceContent(moduleFile); + myIncludeLibraries = ContainerUtil.or(modules, m -> { + PsiFile containingFile = m.getContainingFile(); + if (containingFile == null) return true; + VirtualFile moduleFile = containingFile.getVirtualFile(); + if (moduleFile == null) return true; + return fileIndex.isInLibrary(moduleFile); + }); + myIsInTests = !myIncludeLibraries && ContainerUtil.or(modules, m -> { + PsiFile containingFile = m.getContainingFile(); + if (containingFile == null) return true; + VirtualFile moduleFile = containingFile.getVirtualFile(); + if (moduleFile == null) return true; + return fileIndex.isInTestSourceContent(moduleFile); + }); } @Override - public boolean isSearchInModuleContent(@Nonnull Module aModule) { - return findDescriptorByModule(aModule, myIsInTests) == myModule; + public boolean isSearchInModuleContent(Module aModule) { + return contains(findDescriptorByModule(aModule, myIsInTests)); } @Override @@ -414,7 +676,7 @@ public boolean isSearchInLibraries() { } @Override - public boolean contains(@Nonnull VirtualFile file) { + public boolean contains(VirtualFile file) { Project project = getProject(); if (project == null) { return false; @@ -422,15 +684,23 @@ public boolean contains(@Nonnull VirtualFile file) { if (!isJvmLanguageFile(file)) { return false; } - ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project); + ProjectFileIndex index = ProjectFileIndex.getInstance(project); if (index.isInLibrary(file)) { - return myIncludeLibraries && myModule.equals(findDescriptorInLibrary(project, index, file)); + return myIncludeLibraries && contains(findDescriptorInLibrary(file, project)); } Module module = index.getModuleForFile(file); - return myModule.equals(findDescriptorByModule(module, myIsInTests)); + return contains(findDescriptorByModule(module, myIsInTests)); + } + + private boolean contains(@Nullable PsiJavaModule module) { + if (module == null || !module.isValid()) { + return false; + } + Collection myCollectedModules = myModules.get(module.getName()); + return myCollectedModules.contains(module); } - private static boolean isJvmLanguageFile(@Nonnull VirtualFile file) { + private static boolean isJvmLanguageFile(VirtualFile file) { FileTypeRegistry fileTypeRegistry = FileTypeRegistry.getInstance(); LanguageFileType languageFileType = tryCast(fileTypeRegistry.getFileTypeByFileName(file.getName()), LanguageFileType.class); return languageFileType != null && languageFileType.getLanguage() instanceof JavaLanguage; @@ -438,7 +708,7 @@ private static boolean isJvmLanguageFile(@Nonnull VirtualFile file) { public static @Nullable - JavaModuleScope moduleScope(@Nonnull PsiJavaModule module) { + JavaModuleScope moduleScope(PsiJavaModule module) { PsiFile moduleFile = module.getContainingFile(); if (moduleFile == null) { return null; @@ -447,7 +717,23 @@ JavaModuleScope moduleScope(@Nonnull PsiJavaModule module) { if (virtualFile == null) { return null; } - return new JavaModuleScope(module.getProject(), module, virtualFile); + return new JavaModuleScope(module.getProject(), Set.of(module)); + } + + /** + * Creates a JavaModuleScope that includes the given module and all transitive modules. + * + * @param module the base PsiJavaModule for which to create the scope, must not be null + * @return a new JavaModuleScope including all transitive modules of the given module, or null if the moduleFile is null or no transitive modules are found + */ + public static + @Nullable + JavaModuleScope moduleWithTransitiveScope(PsiJavaModule module) { + Set allModules = getAllTransitiveModulesIncludeCurrent(module); + if (allModules.isEmpty()) { + return null; + } + return new JavaModuleScope(module.getProject(), allModules); } } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java new file mode 100644 index 0000000000..b1fe49ad27 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitor.java @@ -0,0 +1,59 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; + +import com.intellij.java.language.LanguageLevel; +import com.intellij.java.language.impl.lexer.JavaLexer; +import com.intellij.java.language.psi.JavaElementVisitor; +import com.intellij.java.language.psi.JavaTokenType; +import com.intellij.java.language.psi.PsiJavaModule; +import com.intellij.java.language.psi.PsiKeyword; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.dumb.DumbAware; +import consulo.language.editor.rawHighlight.HighlightInfoHolder; +import consulo.language.editor.rawHighlight.HighlightVisitor; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiFile; + +public class JavaNamesHighlightVisitor extends JavaElementVisitor implements HighlightVisitor, DumbAware { + private HighlightInfoHolder myHolder; + private PsiFile myFile; + private LanguageLevel myLanguageLevel; + private boolean shouldHighlightSoftKeywords; + + @Override + public void visit(PsiElement element) { + element.accept(this); + } + + @Override + public boolean analyze(PsiFile file, boolean updateWholeFile, HighlightInfoHolder holder, Runnable highlight) { + try { + prepare(holder, file); + highlight.run(); + } + finally { + myFile = null; + myHolder = null; + } + + return true; + } + + @RequiredReadAction + private void prepare(HighlightInfoHolder holder, PsiFile file) { + myHolder = holder; + myFile = file; + myLanguageLevel = PsiUtil.getLanguageLevel(file); + shouldHighlightSoftKeywords = PsiJavaModule.MODULE_INFO_FILE.equals(file.getName()) || myLanguageLevel.isAtLeast(LanguageLevel.JDK_10); + } + + @Override + public void visitKeyword(PsiKeyword keyword) { + if (shouldHighlightSoftKeywords && + (JavaLexer.isSoftKeyword(keyword.getNode().getChars(), + myLanguageLevel) || JavaTokenType.NON_SEALED_KEYWORD == keyword.getTokenType())) { + myHolder.add(HighlightNamesUtil.highlightKeyword(keyword)); + } + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitorFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitorFactory.java new file mode 100644 index 0000000000..b31d6f8223 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaNamesHighlightVisitorFactory.java @@ -0,0 +1,24 @@ +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; + +import com.intellij.java.language.psi.PsiImportHolder; +import consulo.annotation.component.ExtensionImpl; +import consulo.language.editor.rawHighlight.HighlightVisitor; +import consulo.language.editor.rawHighlight.HighlightVisitorFactory; +import consulo.language.psi.PsiFile; + +/** + * @author VISTALL + * @since 2024-02-03 + */ +@ExtensionImpl +public class JavaNamesHighlightVisitorFactory implements HighlightVisitorFactory { + @Override + public boolean suitableForFile(PsiFile file) { + return file instanceof PsiImportHolder; + } + + @Override + public HighlightVisitor createVisitor() { + return new JavaNamesHighlightVisitor(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaPsiVariableUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaPsiVariableUtil.java new file mode 100644 index 0000000000..430e059f26 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/JavaPsiVariableUtil.java @@ -0,0 +1,142 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; + +import com.intellij.java.language.impl.psi.scope.PatternResolveState; +import com.intellij.java.language.impl.psi.scope.processor.VariablesNotProcessor; +import com.intellij.java.language.impl.psi.scope.util.PsiScopesUtil; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.language.ast.IElementType; +import consulo.language.psi.ExternallyDefinedPsiElement; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiFile; +import consulo.language.psi.resolve.ResolveState; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; + +import java.util.Objects; + +/** + * Utility methods related to variables + */ +public final class JavaPsiVariableUtil { + + private static PsiVariable findSameNameSibling(PsiVariable variable) { + PsiElement scope = variable.getParent(); + PsiElement[] children = scope.getChildren(); + for (PsiElement child : children) { + if (child instanceof PsiVariable psiVariable) { + if (child.equals(variable)) continue; + if (Objects.equals(variable.getName(), psiVariable.getName())) { + return psiVariable; + } + } + } + return null; + } + + private static PsiPatternVariable findSamePatternVariableInBranches(PsiPatternVariable variable) { + PsiPattern pattern = variable.getPattern(); + PatternResolveState hint = PatternResolveState.WHEN_TRUE; + VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) { + @Override + protected boolean check(PsiVariable var, ResolveState state) { + return var instanceof PsiPatternVariable && super.check(var, state); + } + }; + PsiElement lastParent = pattern; + for (PsiElement parent = lastParent.getParent(); parent != null; lastParent = parent, parent = parent.getParent()) { + if (parent instanceof PsiInstanceOfExpression || parent instanceof PsiParenthesizedExpression) continue; + if (parent instanceof PsiPrefixExpression && ((PsiPrefixExpression) parent).getOperationTokenType().equals(JavaTokenType.EXCL)) { + hint = hint.invert(); + continue; + } + if (parent instanceof PsiPolyadicExpression expression) { + IElementType tokenType = expression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) { + PatternResolveState targetHint = PatternResolveState.fromBoolean(tokenType.equals(JavaTokenType.OROR)); + if (hint == targetHint) { + for (PsiExpression operand : expression.getOperands()) { + if (operand == lastParent) break; + operand.processDeclarations(proc, hint.putInto(ResolveState.initial()), null, pattern); + } + } + continue; + } + } + if (parent instanceof PsiConditionalExpression conditional) { + PsiExpression thenExpression = conditional.getThenExpression(); + if (lastParent == thenExpression) { + conditional.getCondition() + .processDeclarations(proc, PatternResolveState.WHEN_FALSE.putInto(ResolveState.initial()), null, pattern); + } + else if (lastParent == conditional.getElseExpression()) { + conditional.getCondition() + .processDeclarations(proc, PatternResolveState.WHEN_TRUE.putInto(ResolveState.initial()), null, pattern); + if (thenExpression != null) { + thenExpression.processDeclarations(proc, hint.putInto(ResolveState.initial()), null, pattern); + } + } + } + break; + } + return proc.size() > 0 ? (PsiPatternVariable) proc.getResult(0) : null; + } + + /** + * @param variable variable to check + * @return a previous declaration of a colliding variable with the same name; null if not found + */ + @Nullable + public static PsiVariable findPreviousVariableDeclaration(PsiVariable variable) { + if (variable instanceof ExternallyDefinedPsiElement || variable.isUnnamed()) return null; + PsiVariable oldVariable = null; + PsiElement declarationScope = null; + if (variable instanceof PsiLocalVariable || variable instanceof PsiPatternVariable || + variable instanceof PsiParameter && + ((declarationScope = ((PsiParameter) variable).getDeclarationScope()) instanceof PsiCatchSection || + declarationScope instanceof PsiForeachStatement || + declarationScope instanceof PsiLambdaExpression)) { + PsiElement scope = + PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class, PsiResourceList.class); + VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) { + @Override + protected boolean check(PsiVariable var, ResolveState state) { + return PsiUtil.isJvmLocalVariable(var) && super.check(var, state); + } + }; + PsiIdentifier identifier = variable.getNameIdentifier(); + assert identifier != null : variable; + PsiScopesUtil.treeWalkUp(proc, identifier, scope); + if (scope instanceof PsiResourceList && proc.size() == 0) { + scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class); + PsiScopesUtil.treeWalkUp(proc, identifier, scope); + } + if (proc.size() > 0) { + oldVariable = proc.getResult(0); + } + else if (declarationScope instanceof PsiLambdaExpression) { + oldVariable = findSameNameSibling(variable); + } + else if (variable instanceof PsiPatternVariable) { + oldVariable = findSamePatternVariableInBranches((PsiPatternVariable) variable); + } + } + else if (variable instanceof PsiField field) { + PsiClass aClass = field.getContainingClass(); + if (aClass == null) return null; + PsiField fieldByName = aClass.findFieldByName(variable.getName(), false); + if (fieldByName != null && fieldByName != field) { + oldVariable = fieldByName; + } + else { + oldVariable = ContainerUtil.find(aClass.getRecordComponents(), c -> field.getName().equals(c.getName())); + } + } + else { + oldVariable = findSameNameSibling(variable); + } + return oldVariable; + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java index 454301f82a..3577a9d1f3 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/LambdaHighlightingUtil.java @@ -20,144 +20,159 @@ import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.List; import java.util.Set; /** - * User: anna + * @author anna */ public class LambdaHighlightingUtil { - private static final Logger LOG = Logger.getInstance(LambdaHighlightingUtil.class); + private static final Logger LOG = Logger.getInstance(LambdaHighlightingUtil.class); - @Nullable - public static String checkInterfaceFunctional(@Nonnull PsiClass psiClass) { - return checkInterfaceFunctional(psiClass, "Target type of a lambda conversion must be an interface"); - } - - @Nullable - public static String checkInterfaceFunctional(@Nonnull PsiClass psiClass, String interfaceNonFunctionalMessage) { - if (psiClass instanceof PsiTypeParameter) { - return null; //should be logged as cyclic inference - } - final List signatures = LambdaUtil.findFunctionCandidates(psiClass); - if (signatures == null) { - return interfaceNonFunctionalMessage; - } - if (signatures.isEmpty()) { - return "No target method found"; + public static LocalizeValue checkInterfaceFunctional(PsiClass psiClass) { + return checkInterfaceFunctional(psiClass, JavaCompilationErrorLocalize.lambdaTargetNotInterface()); } - if (signatures.size() == 1) { - return null; + + public static LocalizeValue checkInterfaceFunctional(PsiClass psiClass, LocalizeValue interfaceNonFunctionalMessage) { + if (psiClass instanceof PsiTypeParameter) { + return LocalizeValue.empty(); //should be logged as cyclic inference + } + List signatures = LambdaUtil.findFunctionCandidates(psiClass); + if (signatures == null) { + return interfaceNonFunctionalMessage; + } + if (signatures.isEmpty()) { + return JavaCompilationErrorLocalize.lambdaNoTargetMethodFound(); + } + if (signatures.size() == 1) { + return LocalizeValue.empty(); + } + return JavaCompilationErrorLocalize.lambdaMultipleSamCandidates(HighlightUtil.formatClass(psiClass)); } - return "Multiple non-overriding abstract methods found in interface " + HighlightUtil.formatClass(psiClass); - } - @Nullable - public static HighlightInfo checkParametersCompatible(PsiLambdaExpression expression, PsiParameter[] methodParameters, PsiSubstitutor substitutor) { - final PsiParameter[] lambdaParameters = expression.getParameterList().getParameters(); - String incompatibleTypesMessage = "Incompatible parameter types in lambda expression: "; - if (lambdaParameters.length != methodParameters.length) { - incompatibleTypesMessage += "wrong number of parameters: expected " + methodParameters.length + " but found " + lambdaParameters.length; - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression.getParameterList()).descriptionAndTooltip(incompatibleTypesMessage).create(); + @Nullable + @RequiredReadAction + public static HighlightInfo checkParametersCompatible( + PsiLambdaExpression expression, + PsiParameter[] methodParams, + PsiSubstitutor substitutor + ) { + PsiParameter[] lambdaParams = expression.getParameterList().getParameters(); + if (lambdaParams.length != methodParams.length) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression.getParameterList()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lambdaWrongNumberOfParameters(methodParams.length, lambdaParams.length)) + .create(); + } + boolean hasFormalParameterTypes = expression.hasFormalParameterTypes(); + for (int i = 0; i < lambdaParams.length; i++) { + PsiParameter lambdaParameter = lambdaParams[i]; + PsiType lambdaParameterType = lambdaParameter.getType(); + PsiType substitutedParamType = substitutor.substitute(methodParams[i].getType()); + if (hasFormalParameterTypes && !PsiTypesUtil.compareTypes(lambdaParameterType, substitutedParamType, true) + || !TypeConversionUtil.isAssignable(substitutedParamType, lambdaParameterType)) { + String expectedType = substitutedParamType != null ? substitutedParamType.getPresentableText() : null; + String actualType = lambdaParameterType.getPresentableText(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression.getParameterList()) + .descriptionAndTooltip(JavaCompilationErrorLocalize.lambdaIncompatibleParameterTypes(expectedType, actualType)) + .create(); + } + } + return null; } - boolean hasFormalParameterTypes = expression.hasFormalParameterTypes(); - for (int i = 0; i < lambdaParameters.length; i++) { - PsiParameter lambdaParameter = lambdaParameters[i]; - PsiType lambdaParameterType = lambdaParameter.getType(); - PsiType substitutedParamType = substitutor.substitute(methodParameters[i].getType()); - if (hasFormalParameterTypes && !PsiTypesUtil.compareTypes(lambdaParameterType, substitutedParamType, true) || !TypeConversionUtil.isAssignable(substitutedParamType, lambdaParameterType)) { - final String expectedType = substitutedParamType != null ? substitutedParamType.getPresentableText() : null; - final String actualType = lambdaParameterType.getPresentableText(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression.getParameterList()).descriptionAndTooltip(incompatibleTypesMessage + "expected " + expectedType + " " + - "but found " + actualType).create(); - } + + public static boolean insertSemicolonAfter(PsiLambdaExpression lambdaExpression) { + return lambdaExpression.getBody() instanceof PsiCodeBlock || !insertSemicolon(lambdaExpression.getParent()); } - return null; - } - public static boolean insertSemicolonAfter(PsiLambdaExpression lambdaExpression) { - return lambdaExpression.getBody() instanceof PsiCodeBlock || !insertSemicolon(lambdaExpression.getParent()); - } + public static boolean insertSemicolon(PsiElement parent) { + return parent instanceof PsiExpressionList || parent instanceof PsiExpression; + } - public static boolean insertSemicolon(PsiElement parent) { - return parent instanceof PsiExpressionList || parent instanceof PsiExpression; - } + public static LocalizeValue checkInterfaceFunctional(PsiType functionalInterfaceType) { + if (functionalInterfaceType instanceof PsiIntersectionType) { + Set signatures = new HashSet<>(); + for (PsiType type : ((PsiIntersectionType) functionalInterfaceType).getConjuncts()) { + if (checkInterfaceFunctional(type).isEmpty()) { + MethodSignature signature = LambdaUtil.getFunction(PsiUtil.resolveClassInType(type)); + LOG.assertTrue(signature != null, type.getCanonicalText()); + signatures.add(signature); + } + } - @Nullable - public static String checkInterfaceFunctional(PsiType functionalInterfaceType) { - if (functionalInterfaceType instanceof PsiIntersectionType) { - final Set signatures = new HashSet(); - for (PsiType type : ((PsiIntersectionType) functionalInterfaceType).getConjuncts()) { - if (checkInterfaceFunctional(type) == null) { - final MethodSignature signature = LambdaUtil.getFunction(PsiUtil.resolveClassInType(type)); - LOG.assertTrue(signature != null, type.getCanonicalText()); - signatures.add(signature); + if (signatures.size() > 1) { + return JavaCompilationErrorLocalize.lambdaMultipleSamCandidates(functionalInterfaceType.getPresentableText()); + } + return LocalizeValue.empty(); } - } - - if (signatures.size() > 1) { - return "Multiple non-overriding abstract methods found in " + functionalInterfaceType.getPresentableText(); - } - return null; - } - final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final PsiClass aClass = resolveResult.getElement(); - if (aClass != null) { - if (aClass instanceof PsiTypeParameter) { - return null; //should be logged as cyclic inference - } - final List signatures = LambdaUtil.findFunctionCandidates(aClass); - if (signatures != null && signatures.size() == 1) { - final MethodSignature functionalMethod = signatures.get(0); - if (functionalMethod.getTypeParameters().length > 0) { - return "Target method is generic"; + PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiClass aClass = resolveResult.getElement(); + if (aClass != null) { + if (aClass instanceof PsiTypeParameter) { + return LocalizeValue.empty(); //should be logged as cyclic inference + } + List signatures = LambdaUtil.findFunctionCandidates(aClass); + if (signatures != null && signatures.size() == 1) { + MethodSignature functionalMethod = signatures.get(0); + if (functionalMethod.getTypeParameters().length > 0) { + return JavaCompilationErrorLocalize.lambdaSamGeneric(); + } + } + if (checkReturnTypeApplicable(resolveResult, aClass)) { + return LocalizeValue.localizeTODO( + "No instance of type " + functionalInterfaceType.getPresentableText() + + " exists so that lambda expression can be type-checked" + ); + } + return checkInterfaceFunctional(aClass); } - } - if (checkReturnTypeApplicable(resolveResult, aClass)) { - return "No instance of type " + functionalInterfaceType.getPresentableText() + " exists so that lambda expression can be type-checked"; - } - return checkInterfaceFunctional(aClass); + return JavaCompilationErrorLocalize.lambdaNotAFunctionalInterface(functionalInterfaceType.getPresentableText()); } - return functionalInterfaceType.getPresentableText() + " is not a functional interface"; - } - private static boolean checkReturnTypeApplicable(PsiClassType.ClassResolveResult resolveResult, final PsiClass aClass) { - final MethodSignature methodSignature = LambdaUtil.getFunction(aClass); - if (methodSignature == null) { - return false; - } + private static boolean checkReturnTypeApplicable(PsiClassType.ClassResolveResult resolveResult, final PsiClass aClass) { + MethodSignature methodSignature = LambdaUtil.getFunction(aClass); + if (methodSignature == null) { + return false; + } - for (PsiTypeParameter parameter : aClass.getTypeParameters()) { - if (parameter.getExtendsListTypes().length == 0) { - continue; - } - final PsiType substitution = resolveResult.getSubstitutor().substitute(parameter); - if (substitution instanceof PsiWildcardType && !((PsiWildcardType) substitution).isBounded()) { - boolean depends = false; - for (PsiType paramType : methodSignature.getParameterTypes()) { - if (LambdaUtil.depends(paramType, new LambdaUtil.TypeParamsChecker((PsiMethod) null, aClass) { - @Override - public boolean startedInference() { - return true; + for (PsiTypeParameter parameter : aClass.getTypeParameters()) { + if (parameter.getExtendsListTypes().length == 0) { + continue; + } + PsiType substitution = resolveResult.getSubstitutor().substitute(parameter); + if (substitution instanceof PsiWildcardType wildcardType && !wildcardType.isBounded()) { + boolean depends = false; + for (PsiType paramType : methodSignature.getParameterTypes()) { + if (LambdaUtil.depends( + paramType, + new LambdaUtil.TypeParamsChecker((PsiMethod) null, aClass) { + @Override + public boolean startedInference() { + return true; + } + }, + parameter + )) { + depends = true; + break; + } + } + if (!depends) { + return true; + } } - }, parameter)) { - depends = true; - break; - } - } - if (!depends) { - return true; } - } + return false; } - return false; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java index a0ba470a7c..4d01d69564 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java @@ -20,7 +20,6 @@ import com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix.GoToSymbolFix; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix.MergeModuleStatementsFix; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix.MoveFileFix; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.impl.light.LightJavaModule; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.PsiPackageAccessibilityStatement.Role; @@ -28,11 +27,11 @@ import com.intellij.java.language.psi.util.ClassUtil; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; import consulo.document.util.TextRange; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.language.editor.intention.QuickFixAction; -import consulo.language.editor.intention.QuickFixActionRegistrar; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import consulo.java.language.impl.localize.JavaErrorLocalize; +import consulo.java.language.localize.JavaCompilationErrorLocalize; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; import consulo.language.psi.*; @@ -40,6 +39,7 @@ import consulo.language.psi.search.FilenameIndex; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.module.Module; import consulo.module.content.ProjectFileIndex; import consulo.project.Project; @@ -49,11 +49,8 @@ import consulo.util.lang.StringUtil; import consulo.util.lang.Trinity; import consulo.virtualFileSystem.VirtualFile; -import consulo.virtualFileSystem.archive.ArchiveFileSystem; -import org.jetbrains.annotations.PropertyKey; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -62,442 +59,554 @@ import static com.intellij.java.language.psi.PsiJavaModule.MODULE_INFO_FILE; public class ModuleHighlightUtil { - @Nullable - public static PsiJavaModule getModuleDescriptor(@Nonnull PsiFileSystemItem fsItem) { - VirtualFile file = fsItem.getVirtualFile(); - if (file == null) { - return null; + @Nullable + public static PsiJavaModule getModuleDescriptor(PsiFileSystemItem fsItem) { + VirtualFile file = fsItem.getVirtualFile(); + if (file == null) { + return null; + } + + return JavaModuleGraphUtil.findDescriptorByFile(file, fsItem.getProject()); } - Project project = fsItem.getProject(); - ProjectFileIndex index = ProjectFileIndex.getInstance(project); - if (index.isInLibraryClasses(file)) { - VirtualFile classRoot = index.getClassRootForFile(file); - if (classRoot != null) { - VirtualFile descriptorFile = classRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE); - if (descriptorFile == null) { - descriptorFile = classRoot.findFileByRelativePath("META-INF/versions/9/" + PsiJavaModule.MODULE_INFO_CLS_FILE); + @RequiredReadAction + public static HighlightInfo checkPackageStatement( + PsiPackageStatement statement, + PsiFile file, + @Nullable PsiJavaModule module + ) { + if (PsiUtil.isModuleFile(file)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleNoPackage()) + .registerFix(factory().createDeleteFix(statement)) + .create(); } - if (descriptorFile != null) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile); - if (psiFile instanceof PsiJavaFile) { - return ((PsiJavaFile) psiFile).getModuleDeclaration(); - } - } else if (classRoot.getFileSystem() instanceof ArchiveFileSystem && "jar".equalsIgnoreCase(classRoot.getExtension())) { - return LightJavaModule.getModule(PsiManager.getInstance(project), classRoot); - } - } - } else { - Module module = index.getModuleForFile(file); - if (module != null) { - boolean isTest = index.isInTestSourceContent(file); - List files = FilenameIndex.getVirtualFilesByName(project, MODULE_INFO_FILE, GlobalSearchScope.moduleScope(module)).stream().filter(f -> index.isInTestSourceContent(f) == isTest) - .collect(Collectors.toList()); - if (files.size() == 1) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(files.get(0)); - if (psiFile instanceof PsiJavaFile) { - return ((PsiJavaFile) psiFile).getModuleDeclaration(); - } + + if (module != null) { + String packageName = statement.getPackageName(); + if (packageName != null) { + PsiJavaModule origin = JavaModuleGraphUtil.findOrigin(module, packageName); + if (origin != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleConflictingPackages(packageName, origin.getName())) + .create(); + } + } } - } + + return null; } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkFileName(PsiJavaModule element, PsiFile file) { + if (!MODULE_INFO_FILE.equals(file.getName())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(element)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleFileWrongName()) + .registerFix(factory().createRenameFileFix(MODULE_INFO_FILE)) + .create(); + } - public static HighlightInfo checkPackageStatement(@Nonnull PsiPackageStatement statement, @Nonnull PsiFile file, @Nullable PsiJavaModule module) { - if (PsiUtil.isModuleFile(file)) { - String message = JavaErrorBundle.message("module.no.package"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createDeleteFix(statement)); - return info; + return null; } - if (module != null) { - String packageName = statement.getPackageName(); - if (packageName != null) { - PsiJavaModule origin = JavaModuleGraphUtil.findOrigin(module, packageName); - if (origin != null) { - String message = JavaErrorBundle.message("module.conflicting.packages", packageName, origin.getName()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create(); + @Nullable + @RequiredReadAction + public static HighlightInfo checkFileDuplicates(PsiJavaModule element, PsiFile file) { + Module module = findModule(file); + if (module != null) { + Project project = file.getProject(); + Collection others = + FilenameIndex.getVirtualFilesByName(project, MODULE_INFO_FILE, GlobalSearchScope.moduleScope(module)); + if (others.size() > 1) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(element)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleFileDuplicate()); + others.stream() + .map(f -> PsiManager.getInstance(project).findFile(f)) + .filter(f -> f != file) + .findFirst() + .ifPresent(duplicate -> hlBuilder.registerFix(new GoToSymbolFix(duplicate, JavaErrorLocalize.moduleOpenDuplicateText()))); + return hlBuilder.create(); + } } - } - } - return null; - } + return null; + } - @Nullable - public static HighlightInfo checkFileName(@Nonnull PsiJavaModule element, @Nonnull PsiFile file) { - if (!MODULE_INFO_FILE.equals(file.getName())) { - String message = JavaErrorBundle.message("module.file.wrong.name"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(element)).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createRenameFileFix(MODULE_INFO_FILE)); - return info; + @RequiredReadAction + public static List checkDuplicateStatements(PsiJavaModule module) { + List results = new ArrayList<>(); + + checkDuplicateRefs( + module.getRequires(), + st -> Optional.ofNullable(st.getReferenceElement()).map(PsiJavaModuleReferenceElement::getReferenceText), + JavaCompilationErrorLocalize::moduleDuplicateRequires, + results + ); + + checkDuplicateRefs( + module.getExports(), + st -> Optional.ofNullable(st.getPackageReference()).map(ModuleHighlightUtil::refText), + JavaCompilationErrorLocalize::moduleDuplicateExports, + results + ); + + checkDuplicateRefs( + module.getOpens(), + st -> Optional.ofNullable(st.getPackageReference()).map(ModuleHighlightUtil::refText), + JavaCompilationErrorLocalize::moduleDuplicateOpens, + results + ); + + checkDuplicateRefs( + module.getUses(), + st -> Optional.ofNullable(st.getClassReference()).map(ModuleHighlightUtil::refText), + JavaCompilationErrorLocalize::moduleDuplicateUses, + results + ); + + checkDuplicateRefs( + module.getProvides(), + st -> Optional.ofNullable(st.getInterfaceReference()).map(ModuleHighlightUtil::refText), + JavaCompilationErrorLocalize::moduleDuplicateProvides, + results + ); + + return results; } - return null; - } - - @Nullable - public static HighlightInfo checkFileDuplicates(@Nonnull PsiJavaModule element, @Nonnull PsiFile file) { - Module module = findModule(file); - if (module != null) { - Project project = file.getProject(); - Collection others = FilenameIndex.getVirtualFilesByName(project, MODULE_INFO_FILE, GlobalSearchScope.moduleScope(module)); - if (others.size() > 1) { - String message = JavaErrorBundle.message("module.file.duplicate"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(element)).descriptionAndTooltip(message).create(); - others.stream().map(f -> PsiManager.getInstance(project).findFile(f)).filter(f -> f != file).findFirst().ifPresent(duplicate -> QuickFixAction.registerQuickFixAction(info, new - GoToSymbolFix(duplicate, JavaErrorBundle.message("module.open.duplicate.text")))); - return info; - } + @RequiredReadAction + private static void checkDuplicateRefs( + Iterable statements, + @RequiredReadAction Function> ref, + Function descriptionTemplate, + List results + ) { + Set filter = new HashSet<>(); + for (T statement : statements) { + String refText = ref.apply(statement).orElse(null); + if (refText != null && !filter.add(refText)) { + HighlightInfo.Builder hlBuilder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(descriptionTemplate.apply(refText)) + .registerFix(factory().createDeleteFix(statement)); + + MergeModuleStatementsFix mergeModuleStatementsFix = MergeModuleStatementsFix.createFix(statement); + if (mergeModuleStatementsFix != null) { + hlBuilder.registerFix(mergeModuleStatementsFix); + } + results.add(hlBuilder.create()); + } + } } - return null; - } + @RequiredReadAction + public static List checkUnusedServices(PsiJavaModule module) { + List results = new ArrayList<>(); + + Set exports = JBIterable.from(module.getExports()) + .map(st -> refText(st.getPackageReference())) + .filter(Objects::nonNull) + .toSet(); + Set uses = JBIterable.from(module.getUses()) + .map(st -> refText(st.getClassReference())) + .filter(Objects::nonNull) + .toSet(); + + Module host = findModule(module); + for (PsiProvidesStatement statement : module.getProvides()) { + PsiJavaCodeReferenceElement ref = statement.getInterfaceReference(); + if (ref != null && ref.resolve() instanceof PsiClass psiClass && findModule(psiClass) == host) { + String className = refText(ref), packageName = StringUtil.getPackageName(className); + if (!exports.contains(packageName) && !uses.contains(className)) { + results.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(range(ref)) + .descriptionAndTooltip(JavaErrorLocalize.moduleServiceUnused()) + .create() + ); + } + } + } - @Nonnull - public static List checkDuplicateStatements(@Nonnull PsiJavaModule module) { - List results = new ArrayList<>(); + return results; + } - checkDuplicateRefs(module.getRequires(), st -> Optional.ofNullable(st.getReferenceElement()).map(PsiJavaModuleReferenceElement::getReferenceText), "module.duplicate.requires", results); + @RequiredReadAction + private static String refText(PsiJavaCodeReferenceElement ref) { + return ref != null ? PsiNameHelper.getQualifiedClassName(ref.getText(), true) : null; + } - checkDuplicateRefs(module.getExports(), st -> Optional.ofNullable(st.getPackageReference()).map(ModuleHighlightUtil::refText), "module.duplicate.exports", results); + @Nullable + @RequiredReadAction + public static HighlightInfo checkFileLocation(PsiJavaModule element, PsiFile file) { + VirtualFile vFile = file.getVirtualFile(); + if (vFile != null) { + VirtualFile root = ProjectFileIndex.getInstance(file.getProject()).getSourceRootForFile(vFile); + if (root != null && !root.equals(vFile.getParent())) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(range(element)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleFileWrongLocation()) + .registerFix(new MoveFileFix(vFile, root, JavaQuickFixLocalize.moveFileToSourceRootText())) + .create(); + } + } - checkDuplicateRefs(module.getOpens(), st -> Optional.ofNullable(st.getPackageReference()).map(ModuleHighlightUtil::refText), "module.duplicate.opens", results); + return null; + } - checkDuplicateRefs(module.getUses(), st -> Optional.ofNullable(st.getClassReference()).map(ModuleHighlightUtil::refText), "module.duplicate.uses", results); + @Nullable + @RequiredReadAction + public static HighlightInfo checkModuleReference(@Nullable PsiJavaModuleReferenceElement refElement, PsiJavaModule container) { + if (refElement != null) { + PsiPolyVariantReference ref = refElement.getReference(); + assert ref != null : refElement.getParent(); + PsiElement target = ref.resolve(); + if (!(target instanceof PsiJavaModule)) { + return moduleResolveError(refElement, ref); + } + else if (target == container) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleCyclicDependence(container.getName())) + .create(); + } + else { + Collection cycle = JavaModuleGraphUtil.findCycle((PsiJavaModule)target); + if (cycle.contains(container)) { + Stream stream = cycle.stream().map(PsiJavaModule::getName); + if (container.getApplication().isUnitTestMode()) { + stream = stream.sorted(); + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleCyclicDependence(stream.collect(Collectors.joining(", ")))) + .create(); + } + } + } - checkDuplicateRefs(module.getProvides(), st -> Optional.ofNullable(st.getInterfaceReference()).map(ModuleHighlightUtil::refText), "module.duplicate.provides", results); + return null; + } - return results; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkHostModuleStrength(PsiPackageAccessibilityStatement statement) { + if (statement.getRole() == Role.OPENS && statement.getParent() instanceof PsiJavaModule javaModule + && javaModule.hasModifierProperty(PsiModifier.OPEN)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(JavaErrorLocalize.moduleOpensInWeakModule()) + .registerFix(factory().createModifierFixBuilder(javaModule).remove(PsiModifier.OPEN).create()) + .registerFix(factory().createDeleteFix(statement)) + .create(); + } - private static void checkDuplicateRefs(Iterable statements, - Function> ref, - @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key, - List results) { - Set filter = new HashSet<>(); - for (T statement : statements) { - String refText = ref.apply(statement).orElse(null); - if (refText != null && !filter.add(refText)) { - String message = JavaErrorBundle.message(key, refText); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createDeleteFix(statement)); - QuickFixAction.registerQuickFixAction(info, MergeModuleStatementsFix.createFix(statement)); - results.add(info); - } + return null; } - } - - @Nonnull - public static List checkUnusedServices(@Nonnull PsiJavaModule module) { - List results = new ArrayList<>(); - - Set exports = JBIterable.from(module.getExports()).map(st -> refText(st.getPackageReference())).filter(Objects::nonNull).toSet(); - Set uses = JBIterable.from(module.getUses()).map(st -> refText(st.getClassReference())).filter(Objects::nonNull).toSet(); - - Module host = findModule(module); - for (PsiProvidesStatement statement : module.getProvides()) { - PsiJavaCodeReferenceElement ref = statement.getInterfaceReference(); - if (ref != null) { - PsiElement target = ref.resolve(); - if (target instanceof PsiClass && findModule(target) == host) { - String className = refText(ref), packageName = StringUtil.getPackageName(className); - if (!exports.contains(packageName) && !uses.contains(className)) { - String message = JavaErrorBundle.message("module.service.unused"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(range(ref)).descriptionAndTooltip(message).create()); - } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkPackageReference(PsiPackageAccessibilityStatement statement) { + PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); + if (refElement != null) { + PsiElement target = refElement.resolve(); + Module module = findModule(refElement); + PsiDirectory[] directories = target instanceof PsiPackage psiPackage && module != null + ? psiPackage.getDirectories(GlobalSearchScope.moduleScope(module, false)) + : null; + String packageName = refText(refElement); + HighlightInfoType type = statement.getRole() == Role.OPENS ? HighlightInfoType.WARNING : HighlightInfoType.ERROR; + if (directories == null || directories.length == 0) { + return HighlightInfo.newHighlightInfo(type) + .range(refElement) + .descriptionAndTooltip(JavaErrorLocalize.packageNotFound(packageName)) + .create(); + } + if (PsiUtil.isPackageEmpty(directories, packageName)) { + return HighlightInfo.newHighlightInfo(type) + .range(refElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleReferencePackageEmpty(packageName)) + .create(); + } } - } - } - return results; - } - - private static String refText(PsiJavaCodeReferenceElement ref) { - return ref != null ? PsiNameHelper.getQualifiedClassName(ref.getText(), true) : null; - } - - @Nullable - public static HighlightInfo checkFileLocation(@Nonnull PsiJavaModule element, @Nonnull PsiFile file) { - VirtualFile vFile = file.getVirtualFile(); - if (vFile != null) { - VirtualFile root = ProjectFileIndex.SERVICE.getInstance(file.getProject()).getSourceRootForFile(vFile); - if (root != null && !root.equals(vFile.getParent())) { - String message = JavaErrorBundle.message("module.file.wrong.location"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(range(element)).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, new MoveFileFix(vFile, root, JavaQuickFixBundle.message("move.file.to.source.root.text"))); - return info; - } + return null; } - return null; - } - - @Nullable - public static HighlightInfo checkModuleReference(@Nullable PsiJavaModuleReferenceElement refElement, @Nonnull PsiJavaModule container) { - if (refElement != null) { - PsiPolyVariantReference ref = refElement.getReference(); - assert ref != null : refElement.getParent(); - PsiElement target = ref.resolve(); - if (!(target instanceof PsiJavaModule)) { - return moduleResolveError(refElement, ref); - } else if (target == container) { - String message = JavaErrorBundle.message("module.cyclic.dependence", container.getName()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).descriptionAndTooltip(message).create(); - } else { - Collection cycle = JavaModuleGraphUtil.findCycle((PsiJavaModule) target); - if (cycle != null && cycle.contains(container)) { - Stream stream = cycle.stream().map(PsiJavaModule::getName); - if (ApplicationManager.getApplication().isUnitTestMode()) { - stream = stream.sorted(); - } - String message = JavaErrorBundle.message("module.cyclic.dependence", stream.collect(Collectors.joining(", "))); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).descriptionAndTooltip(message).create(); + @RequiredReadAction + public static List checkPackageAccessTargets(PsiPackageAccessibilityStatement statement) { + List results = new ArrayList<>(); + + Set targets = new HashSet<>(); + for (PsiJavaModuleReferenceElement refElement : statement.getModuleReferences()) { + String refText = refElement.getReferenceText(); + PsiPolyVariantReference ref = refElement.getReference(); + assert ref != null : statement; + if (!targets.add(refText)) { + boolean exports = statement.getRole() == Role.EXPORTS; + LocalizeValue message = exports + ? JavaCompilationErrorLocalize.moduleDuplicateExportsTarget(refText) + : JavaCompilationErrorLocalize.moduleDuplicateOpensTarget(refText); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(refElement) + .descriptionAndTooltip(message) + .registerFix(factory().createDeleteFix(refElement, JavaQuickFixLocalize.deleteReferenceFixText())) + .create(); + results.add(info); + } + else if (ref.multiResolve(true).length == 0) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(refElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleNotFound(refElement.getReferenceText())) + .create()); + } } - } - } - return null; - } - - @Nullable - public static HighlightInfo checkHostModuleStrength(@Nonnull PsiPackageAccessibilityStatement statement) { - PsiElement parent; - if (statement.getRole() == Role.OPENS && (parent = statement.getParent()) instanceof PsiJavaModule && ((PsiJavaModule) parent).hasModifierProperty(PsiModifier.OPEN)) { - String message = JavaErrorBundle.message("module.opens.in.weak.module"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createModifierListFix((PsiModifierListOwner) parent, PsiModifier.OPEN, false, false)); - QuickFixAction.registerQuickFixAction(info, factory().createDeleteFix(statement)); - return info; + return results; } - return null; - } - - @Nullable - public static HighlightInfo checkPackageReference(@Nonnull PsiPackageAccessibilityStatement statement) { - PsiJavaCodeReferenceElement refElement = statement.getPackageReference(); - if (refElement != null) { - PsiElement target = refElement.resolve(); - Module module = findModule(refElement); - PsiDirectory[] directories = target instanceof PsiPackage && module != null ? ((PsiPackage) target).getDirectories(GlobalSearchScope.moduleScope(module, false)) : null; - String packageName = refText(refElement); - HighlightInfoType type = statement.getRole() == Role.OPENS ? HighlightInfoType.WARNING : HighlightInfoType.ERROR; - if (directories == null || directories.length == 0) { - String message = JavaErrorBundle.message("package.not.found", packageName); - return HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create(); - } - if (PsiUtil.isPackageEmpty(directories, packageName)) { - String message = JavaErrorBundle.message("package.is.empty", packageName); - return HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create(); - } - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkServiceReference(@Nullable PsiJavaCodeReferenceElement refElement) { + if (refElement != null) { + PsiElement target = refElement.resolve(); + if (target == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(refElement)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.referenceUnresolved(refElement.getReferenceName())) + .create(); + } + else if (target instanceof PsiClass psiClass && psiClass.isEnum()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(refElement)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceEnum(psiClass.getName())) + .create(); + } + } - return null; - } - - @Nonnull - public static List checkPackageAccessTargets(@Nonnull PsiPackageAccessibilityStatement statement) { - List results = new ArrayList<>(); - - Set targets = new HashSet<>(); - for (PsiJavaModuleReferenceElement refElement : statement.getModuleReferences()) { - String refText = refElement.getReferenceText(); - PsiPolyVariantReference ref = refElement.getReference(); - assert ref != null : statement; - if (!targets.add(refText)) { - boolean exports = statement.getRole() == Role.EXPORTS; - String message = JavaErrorBundle.message(exports ? "module.duplicate.exports.target" : "module.duplicate.opens.target", refText); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createDeleteFix(refElement, JavaQuickFixBundle.message("delete.reference.fix.text"))); - results.add(info); - } else if (ref.multiResolve(true).length == 0) { - String message = JavaErrorBundle.message("module.not.found", refElement.getReferenceText()); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).descriptionAndTooltip(message).create()); - } + return null; } - return results; - } - - @Nullable - public static HighlightInfo checkServiceReference(@Nullable PsiJavaCodeReferenceElement refElement) { - if (refElement != null) { - PsiElement target = refElement.resolve(); - if (target == null) { - String message = JavaErrorBundle.message("cannot.resolve.symbol", refElement.getReferenceName()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(refElement)).descriptionAndTooltip(message).create(); - } else if (target instanceof PsiClass && ((PsiClass) target).isEnum()) { - String message = JavaErrorBundle.message("module.service.enum", ((PsiClass) target).getName()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(refElement)).descriptionAndTooltip(message).create(); - } - } + @RequiredReadAction + public static List checkServiceImplementations(PsiProvidesStatement statement) { + PsiReferenceList implRefList = statement.getImplementationList(); + if (implRefList == null) { + return Collections.emptyList(); + } - return null; - } + List results = new ArrayList<>(); + PsiJavaCodeReferenceElement intRef = statement.getInterfaceReference(); + PsiElement intTarget = intRef != null ? intRef.resolve() : null; + + Set filter = new HashSet<>(); + for (PsiJavaCodeReferenceElement implRef : implRefList.getReferenceElements()) { + String refText = refText(implRef); + if (!filter.add(refText)) { + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(implRef) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleDuplicateImplementation(refText)) + .registerFix(factory().createDeleteFix(implRef, JavaQuickFixLocalize.deleteReferenceFixText())) + .create(); + results.add(info); + continue; + } - @Nonnull - public static List checkServiceImplementations(@Nonnull PsiProvidesStatement statement) { - PsiReferenceList implRefList = statement.getImplementationList(); - if (implRefList == null) { - return Collections.emptyList(); - } + if (!(intTarget instanceof PsiClass)) { + continue; + } - List results = new ArrayList<>(); - PsiJavaCodeReferenceElement intRef = statement.getInterfaceReference(); - PsiElement intTarget = intRef != null ? intRef.resolve() : null; - - Set filter = new HashSet<>(); - for (PsiJavaCodeReferenceElement implRef : implRefList.getReferenceElements()) { - String refText = refText(implRef); - if (!filter.add(refText)) { - String message = JavaErrorBundle.message("module.duplicate.impl", refText); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(implRef).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, factory().createDeleteFix(implRef, JavaQuickFixBundle.message("delete.reference.fix.text"))); - results.add(info); - continue; - } - - if (!(intTarget instanceof PsiClass)) { - continue; - } - - PsiElement implTarget = implRef.resolve(); - if (implTarget instanceof PsiClass) { - PsiClass implClass = (PsiClass) implTarget; - - if (findModule(statement) != findModule(implClass)) { - String message = JavaErrorBundle.message("module.service.alien"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); + PsiElement implTarget = implRef.resolve(); + if (implTarget instanceof PsiClass implClass) { + if (findModule(statement) != findModule(implClass)) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceAlien()) + .create()); + } + + PsiMethod provider = ContainerUtil.find( + implClass.findMethodsByName("provider", false), + m -> m.isPublic() && m.isStatic() + && m.getParameterList().getParametersCount() == 0 + ); + if (provider != null) { + PsiType type = provider.getReturnType(); + PsiClass typeClass = type instanceof PsiClassType classType ? classType.resolve() : null; + if (!InheritanceUtil.isInheritorOrSelf(typeClass, (PsiClass)intTarget, true)) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceProviderType(implClass.getName())) + .create()); + } + } + else if (InheritanceUtil.isInheritorOrSelf(implClass, (PsiClass)intTarget, true)) { + if (implClass.isAbstract()) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceAbstract(implClass.getName())) + .create()); + } + else if (!(ClassUtil.isTopLevelClass(implClass) || implClass.isStatic())) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceInner(implClass.getName())) + .create()); + } + else if (!PsiUtil.hasDefaultConstructor(implClass)) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceNoConstructor(implClass.getName())) + .create()); + } + } + else { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(implRef)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleServiceImplementationType()) + .create()); + } + } } - PsiMethod provider = ContainerUtil.find(implClass.findMethodsByName("provider", false), m -> m.hasModifierProperty(PsiModifier.PUBLIC) && m.hasModifierProperty(PsiModifier.STATIC) && - m.getParameterList().getParametersCount() == 0); - if (provider != null) { - PsiType type = provider.getReturnType(); - PsiClass typeClass = type instanceof PsiClassType ? ((PsiClassType) type).resolve() : null; - if (!InheritanceUtil.isInheritorOrSelf(typeClass, (PsiClass) intTarget, true)) { - String message = JavaErrorBundle.message("module.service.provider.type", implClass.getName()); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); - } - } else if (InheritanceUtil.isInheritorOrSelf(implClass, (PsiClass) intTarget, true)) { - if (implClass.hasModifierProperty(PsiModifier.ABSTRACT)) { - String message = JavaErrorBundle.message("module.service.abstract", implClass.getName()); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); - } else if (!(ClassUtil.isTopLevelClass(implClass) || implClass.hasModifierProperty(PsiModifier.STATIC))) { - String message = JavaErrorBundle.message("module.service.inner", implClass.getName()); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); - } else if (!PsiUtil.hasDefaultConstructor(implClass)) { - String message = JavaErrorBundle.message("module.service.no.ctor", implClass.getName()); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); - } - } else { - String message = JavaErrorBundle.message("module.service.impl"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(implRef)).descriptionAndTooltip(message).create()); - } - } + return results; } - return results; - } - - @Nullable - public static HighlightInfo checkPackageAccessibility(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull PsiElement target, @Nonnull PsiJavaModule refModule) { - if (PsiTreeUtil.getParentOfType(ref, PsiDocComment.class) == null) { - Module module = findModule(refModule); - if (module != null) { - if (target instanceof PsiClass) { - PsiElement targetFile = target.getParent(); - if (targetFile instanceof PsiClassOwner) { - PsiJavaModule targetModule = getModuleDescriptor((PsiFileSystemItem) targetFile); - String packageName = ((PsiClassOwner) targetFile).getPackageName(); - return checkPackageAccessibility(ref, refModule, targetModule, packageName); - } - } else if (target instanceof PsiPackage) { - PsiElement refImport = ref.getParent(); - if (refImport instanceof PsiImportStatementBase && ((PsiImportStatementBase) refImport).isOnDemand()) { - PsiDirectory[] dirs = ((PsiPackage) target).getDirectories(GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false)); - if (dirs.length == 1) { - PsiJavaModule targetModule = getModuleDescriptor(dirs[0]); - String packageName = ((PsiPackage) target).getQualifiedName(); - return checkPackageAccessibility(ref, refModule, targetModule, packageName); + @RequiredReadAction + public static HighlightInfo.@Nullable Builder checkPackageAccessibility( + PsiJavaCodeReferenceElement ref, + PsiElement target, + PsiJavaModule refModule + ) { + if (PsiTreeUtil.getParentOfType(ref, PsiDocComment.class) == null) { + Module module = findModule(refModule); + if (module != null) { + if (target instanceof PsiClass psiClass) { + if (psiClass.getParent() instanceof PsiClassOwner targetFile) { + PsiJavaModule targetModule = getModuleDescriptor(targetFile); + String packageName = targetFile.getPackageName(); + return checkPackageAccessibility(ref, refModule, targetModule, packageName); + } + } + else if (target instanceof PsiPackage psiPackage) { + PsiElement refImport = ref.getParent(); + if (refImport instanceof PsiImportStatementBase importStatementBase && importStatementBase.isOnDemand()) { + PsiDirectory[] dirs = + psiPackage.getDirectories(GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false)); + if (dirs.length == 1) { + PsiJavaModule targetModule = getModuleDescriptor(dirs[0]); + String packageName = psiPackage.getQualifiedName(); + return checkPackageAccessibility(ref, refModule, targetModule, packageName); + } + } + } } - } } - } + + return null; } - return null; - } - - private static HighlightInfo checkPackageAccessibility(PsiJavaCodeReferenceElement ref, PsiJavaModule refModule, PsiJavaModule targetModule, String packageName) { - if (!refModule.equals(targetModule)) { - if (targetModule == null) { - String message = JavaErrorBundle.message("module.package.on.classpath"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).descriptionAndTooltip(message).create(); - } - - String refModuleName = refModule.getName(); - String requiredName = targetModule.getName(); - if (!(targetModule instanceof LightJavaModule || JavaModuleGraphUtil.exports(targetModule, packageName, refModule))) { - String message = JavaErrorBundle.message("module.package.not.exported", requiredName, packageName, refModuleName); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).descriptionAndTooltip(message).create(); - } - - if (!(PsiJavaModule.JAVA_BASE.equals(requiredName) || JavaModuleGraphUtil.reads(refModule, targetModule))) { - String message = JavaErrorBundle.message("module.not.in.requirements", refModuleName, requiredName); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, new AddRequiredModuleFix(refModule, requiredName)); - return info; - } + @RequiredReadAction + private static HighlightInfo.Builder checkPackageAccessibility( + PsiJavaCodeReferenceElement ref, + PsiJavaModule refModule, + PsiJavaModule targetModule, + String packageName + ) { + if (!refModule.equals(targetModule)) { + if (targetModule == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(ref) + .descriptionAndTooltip(JavaErrorLocalize.modulePackageOnClasspath()); + } + + String refModuleName = refModule.getName(); + String requiredName = targetModule.getName(); + if (!(targetModule instanceof LightJavaModule || JavaModuleGraphUtil.exports(targetModule, packageName, refModule))) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(ref) + .descriptionAndTooltip(JavaErrorLocalize.modulePackageNotExported(requiredName, packageName, refModuleName)); + } + + if (!(PsiJavaModule.JAVA_BASE.equals(requiredName) || JavaModuleGraphUtil.reads(refModule, targetModule))) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(ref) + .descriptionAndTooltip(JavaErrorLocalize.moduleNotInRequirements(refModuleName, requiredName)) + .registerFix(new AddRequiredModuleFix(refModule, requiredName)); + } + } + + return null; } - return null; - } + @Nullable + @RequiredReadAction + public static HighlightInfo checkClashingReads(PsiJavaModule module) { + Trinity conflict = JavaModuleGraphUtil.findConflict(module); + if (conflict != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(range(module)) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleConflictingReads( + module.getName(), + conflict.first, + conflict.second.getName(), + conflict.third.getName() + )) + .create(); + } - @Nullable - public static HighlightInfo checkClashingReads(@Nonnull PsiJavaModule module) { - Trinity conflict = JavaModuleGraphUtil.findConflict(module); - if (conflict != null) { - String message = JavaErrorBundle.message("module.conflicting.reads", module.getName(), conflict.first, conflict.second.getName(), conflict.third.getName()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range(module)).descriptionAndTooltip(message).create(); + return null; } - return null; - } - - private static Module findModule(PsiElement element) { - return Optional.ofNullable(element.getContainingFile()).map(PsiFile::getVirtualFile).map(f -> ModuleUtilCore.findModuleForFile(f, element.getProject())).orElse(null); - } - - private static HighlightInfo moduleResolveError(PsiJavaModuleReferenceElement refElement, PsiPolyVariantReference ref) { - if (ref.multiResolve(true).length == 0) { - String message = JavaErrorBundle.message("module.not.found", refElement.getReferenceText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).descriptionAndTooltip(message).create(); - } else if (ref.multiResolve(false).length > 1) { - String message = JavaErrorBundle.message("module.ambiguous", refElement.getReferenceText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).descriptionAndTooltip(message).create(); - } else { - String message = JavaErrorBundle.message("module.not.on.path", refElement.getReferenceText()); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).descriptionAndTooltip(message).create(); - factory().registerOrderEntryFixes(QuickFixActionRegistrar.create(info), ref); - return info; + private static Module findModule(PsiElement element) { + return Optional.ofNullable(element.getContainingFile()) + .map(PsiFile::getVirtualFile) + .map(f -> ModuleUtilCore.findModuleForFile(f, element.getProject())) + .orElse(null); } - } - private static QuickFixFactory factory() { - return QuickFixFactory.getInstance(); - } + @RequiredReadAction + private static HighlightInfo moduleResolveError(PsiJavaModuleReferenceElement refElement, PsiPolyVariantReference ref) { + if (ref.multiResolve(true).length == 0) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refElement) + .descriptionAndTooltip(JavaCompilationErrorLocalize.moduleNotFound(refElement.getReferenceText())) + .create(); + } + else if (ref.multiResolve(false).length > 1) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING) + .range(refElement) + .descriptionAndTooltip(JavaErrorLocalize.moduleAmbiguous(refElement.getReferenceText())) + .create(); + } + else { + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refElement) + .descriptionAndTooltip(JavaErrorLocalize.moduleNotOnPath(refElement.getReferenceText())) + .create(); + factory().registerOrderEntryFixes(ref); + return info; + } + } - private static TextRange range(PsiJavaModule module) { - PsiKeyword kw = PsiTreeUtil.getChildOfType(module, PsiKeyword.class); - return new TextRange(kw != null ? kw.getTextOffset() : module.getTextOffset(), module.getNameIdentifier().getTextRange().getEndOffset()); - } + private static QuickFixFactory factory() { + return QuickFixFactory.getInstance(); + } - private static PsiElement range(PsiJavaCodeReferenceElement refElement) { - return ObjectUtil.notNull(refElement.getReferenceNameElement(), refElement); - } + @RequiredReadAction + private static TextRange range(PsiJavaModule module) { + PsiKeyword kw = PsiTreeUtil.getChildOfType(module, PsiKeyword.class); + return new TextRange( + kw != null ? kw.getTextOffset() : module.getTextOffset(), + module.getNameIdentifier().getTextRange().getEndOffset() + ); + } + + private static PsiElement range(PsiJavaCodeReferenceElement refElement) { + return ObjectUtil.notNull(refElement.getReferenceNameElement(), refElement); + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java index 232b98213c..87d4f2574b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PostHighlightingVisitor.java @@ -15,17 +15,16 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis; -import com.intellij.java.analysis.codeInsight.daemon.UnusedImportProvider; +import com.intellij.java.analysis.codeInsight.daemon.JavaImplicitUsageProvider; import com.intellij.java.analysis.codeInsight.intention.QuickFixFactory; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.GlobalUsageHelper; -import com.intellij.java.analysis.impl.codeInsight.daemon.impl.JavaHighlightInfoTypes; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.UnusedSymbolUtil; import com.intellij.java.analysis.impl.codeInspection.deadCode.UnusedDeclarationInspectionBase; +import com.intellij.java.analysis.impl.codeInspection.deadCode.UnusedDeclarationInspectionState; import com.intellij.java.analysis.impl.codeInspection.unusedImport.UnusedImportLocalInspection; import com.intellij.java.analysis.impl.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase; import com.intellij.java.analysis.impl.codeInspection.util.SpecialAnnotationsUtilBase; import com.intellij.java.indexing.search.searches.OverridingMethodsSearch; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.impl.psi.impl.PsiClassImplUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; @@ -33,519 +32,620 @@ import com.intellij.java.language.psi.util.PropertyUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.util.VisibilityUtil; -import consulo.application.ApplicationManager; -import consulo.application.TransactionGuard; +import consulo.annotation.access.RequiredReadAction; import consulo.application.progress.ProgressIndicator; import consulo.application.util.ConcurrentFactoryMap; +import consulo.codeEditor.CodeInsightColors; import consulo.component.ProcessCanceledException; -import consulo.component.extension.Extensions; import consulo.disposer.Disposable; import consulo.disposer.Disposer; import consulo.document.Document; +import consulo.java.language.impl.localize.JavaErrorLocalize; import consulo.language.Language; import consulo.language.editor.DaemonCodeAnalyzer; import consulo.language.editor.FileStatusMap; import consulo.language.editor.ImplicitUsageProvider; import consulo.language.editor.annotation.HighlightSeverity; import consulo.language.editor.highlight.HighlightingLevelManager; -import consulo.language.editor.inspection.InspectionsBundle; +import consulo.language.editor.inspection.HighlightInfoTypeSeverityByKey; import consulo.language.editor.inspection.SuppressionUtil; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionProfile; import consulo.language.editor.inspection.scheme.InspectionProjectProfileManager; +import consulo.language.editor.inspection.scheme.InspectionToolWrapper; import consulo.language.editor.intention.EmptyIntentionAction; import consulo.language.editor.intention.IntentionAction; -import consulo.language.editor.intention.QuickFixAction; -import consulo.language.editor.rawHighlight.HighlightDisplayKey; -import consulo.language.editor.rawHighlight.HighlightInfo; -import consulo.language.editor.rawHighlight.HighlightInfoHolder; -import consulo.language.editor.rawHighlight.HighlightInfoType; +import consulo.language.editor.rawHighlight.*; import consulo.language.editor.util.CollectHighlightsUtil; import consulo.language.file.FileViewProvider; import consulo.language.pom.PomNamedTarget; import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import org.jetbrains.annotations.PropertyKey; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; public class PostHighlightingVisitor { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass"); - private final RefCountHolder myRefCountHolder; - @Nonnull - private final Project myProject; - private final PsiFile myFile; - @Nonnull - private final Document myDocument; - - private boolean myHasRedundantImports; - private int myCurrentEntryIndex; - private boolean myHasMissortedImports; - private final UnusedSymbolLocalInspectionBase myUnusedSymbolInspection; - private final HighlightDisplayKey myDeadCodeKey; - private final HighlightInfoType myDeadCodeInfoType; - private final UnusedDeclarationInspectionBase myDeadCodeInspection; - - private void optimizeImportsOnTheFlyLater(@Nonnull final ProgressIndicator progress) { - if ((myHasRedundantImports || myHasMissortedImports) && !progress.isCanceled()) { - // schedule optimise action at the time of session disposal, which is after all applyInformation() calls - Disposable invokeFixLater = () -> - { - // later because should invoke when highlighting is finished - TransactionGuard.getInstance().submitTransactionLater(myProject, () -> - { - if (!myFile.isValid() || !myFile.isWritable()) { - return; - } - IntentionAction optimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true); - if (optimizeImportsFix.isAvailable(myProject, null, myFile)) { - optimizeImportsFix.invoke(myProject, null, myFile); - } - }); - }; - try { - Disposer.register((Disposable) progress, invokeFixLater); - } catch (Exception ignored) { - // suppress "parent already has been disposed" exception here - } - if (progress.isCanceled()) { - Disposer.dispose(invokeFixLater); - Disposer.dispose((Disposable) progress); - progress.checkCanceled(); - } + private static final Logger LOG = Logger.getInstance(PostHighlightingVisitor.class); + private final RefCountHolder myRefCountHolder; + private final Project myProject; + private final PsiFile myFile; + private final Document myDocument; + + private boolean myHasRedundantImports; + private int myCurrentEntryIndex; + private boolean myHasMissortedImports; + private final UnusedSymbolLocalInspectionBase myUnusedSymbolInspection; + private final HighlightDisplayKey myDeadCodeKey; + private final HighlightInfoType myDeadCodeInfoType; + private final UnusedDeclarationInspectionBase myDeadCodeInspection; + private final UnusedDeclarationInspectionState myDeadCodeState; + + private final HighlightInfoType myUnusedImportHighlightType; + + private void optimizeImportsOnTheFlyLater(ProgressIndicator progress) { + if ((myHasRedundantImports || myHasMissortedImports) && !progress.isCanceled()) { + // schedule optimise action at the time of session disposal, which is after all applyInformation() calls + Disposable invokeFixLater = () -> { + // later because should invoke when highlighting is finished + myProject.getUIAccess().give(() -> { + if (!myFile.isValid() || !myFile.isWritable()) { + return; + } + IntentionAction optimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true); + if (optimizeImportsFix.isAvailable(myProject, null, myFile)) { + optimizeImportsFix.invoke(myProject, null, myFile); + } + }); + }; + try { + Disposer.register((Disposable) progress, invokeFixLater); + } + catch (Exception ignored) { + // suppress "parent already has been disposed" exception here + } + if (progress.isCanceled()) { + Disposer.dispose(invokeFixLater); + Disposer.dispose((Disposable) progress); + progress.checkCanceled(); + } + } } - } - public PostHighlightingVisitor(@Nonnull PsiFile file, @Nonnull Document document, @Nonnull RefCountHolder refCountHolder) throws ProcessCanceledException { - myProject = file.getProject(); - myFile = file; - myDocument = document; + @RequiredReadAction + public PostHighlightingVisitor( + PsiFile file, + Document document, + RefCountHolder refCountHolder + ) throws ProcessCanceledException { + myProject = file.getProject(); + myFile = file; + myDocument = document; - myCurrentEntryIndex = -1; + myCurrentEntryIndex = -1; - myRefCountHolder = refCountHolder; + myRefCountHolder = refCountHolder; + myProject.getApplication().assertReadAccessAllowed(); - ApplicationManager.getApplication().assertReadAccessAllowed(); + InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); - InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); + InspectionToolWrapper tool = Objects.requireNonNull(profile.getToolById(UnusedDeclarationInspectionBase.SHORT_NAME, myFile)); - myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspectionBase.SHORT_NAME); + myDeadCodeKey = tool.getHighlightDisplayKey(); - myDeadCodeInspection = (UnusedDeclarationInspectionBase) profile.getUnwrappedTool(UnusedDeclarationInspectionBase.SHORT_NAME, myFile); - LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myDeadCodeInspection != null); + myDeadCodeInspection = (UnusedDeclarationInspectionBase) tool.getTool(); - myUnusedSymbolInspection = myDeadCodeInspection != null ? myDeadCodeInspection.getSharedLocalInspectionTool() : null; + myDeadCodeState = profile.getToolState(UnusedDeclarationInspectionBase.SHORT_NAME, myFile); - myDeadCodeInfoType = myDeadCodeKey == null ? HighlightInfoType.UNUSED_SYMBOL : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), - HighlightInfoType.UNUSED_SYMBOL.getAttributesKey()); - } + myUnusedSymbolInspection = myDeadCodeInspection.getSharedLocalInspectionTool(); - public void collectHighlights(@Nonnull HighlightInfoHolder result, @Nonnull ProgressIndicator progress) { - DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); - FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap(); - InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); + myDeadCodeInfoType = new HighlightInfoTypeImpl( + profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), + HighlightInfoType.RAW_UNUSED_SYMBOL.getAttributesKey() + ); + + InspectionToolWrapper unusedImportTool = + Objects.requireNonNull(profile.getToolById(UnusedImportLocalInspection.SHORT_NAME, myFile)); + + myUnusedImportHighlightType = + new HighlightInfoTypeSeverityByKey(unusedImportTool.getHighlightDisplayKey(), CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES); + } - boolean unusedSymbolEnabled = profile.isToolEnabled(myDeadCodeKey, myFile); - GlobalUsageHelper globalUsageHelper = myRefCountHolder.getGlobalUsageHelper(myFile, myDeadCodeInspection); + @RequiredReadAction + public void collectHighlights(HighlightInfoHolder result, ProgressIndicator progress) { + DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject); + FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap(); + InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); - boolean errorFound = false; + boolean unusedSymbolEnabled = profile.isToolEnabled(myDeadCodeKey, myFile); + GlobalUsageHelper globalUsageHelper = myRefCountHolder.getGlobalUsageHelper(myFile, myDeadCodeInspection, myDeadCodeState); - if (unusedSymbolEnabled) { - final FileViewProvider viewProvider = myFile.getViewProvider(); - final Set relevantLanguages = viewProvider.getLanguages(); - for (Language language : relevantLanguages) { - progress.checkCanceled(); - PsiElement psiRoot = viewProvider.getPsi(language); - if (!HighlightingLevelManager.getInstance(myProject).shouldInspect(psiRoot)) { - continue; + boolean errorFound = false; + + if (unusedSymbolEnabled) { + FileViewProvider viewProvider = myFile.getViewProvider(); + Set relevantLanguages = viewProvider.getLanguages(); + for (Language language : relevantLanguages) { + progress.checkCanceled(); + PsiElement psiRoot = viewProvider.getPsi(language); + if (!HighlightingLevelManager.getInstance(myProject).shouldInspect(psiRoot)) { + continue; + } + List elements = CollectHighlightsUtil.getElementsInRange(psiRoot, 0, myFile.getTextLength()); + for (PsiElement element : elements) { + progress.checkCanceled(); + if (element instanceof PsiIdentifier identifier) { + HighlightInfo info = processIdentifier(identifier, progress, globalUsageHelper); + if (info != null) { + errorFound |= info.getSeverity() == HighlightSeverity.ERROR; + result.add(info); + } + } + } + } } - List elements = CollectHighlightsUtil.getElementsInRange(psiRoot, 0, myFile.getTextLength()); - for (PsiElement element : elements) { - progress.checkCanceled(); - if (element instanceof PsiIdentifier) { - PsiIdentifier identifier = (PsiIdentifier) element; - HighlightInfo info = processIdentifier(identifier, progress, globalUsageHelper); - if (info != null) { - errorFound |= info.getSeverity() == HighlightSeverity.ERROR; - result.add(info); + + HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME); + if (isUnusedImportEnabled(unusedImportKey)) { + PsiImportList importList = ((PsiJavaFile) myFile).getImportList(); + if (importList != null) { + PsiImportStatementBase[] imports = importList.getAllImportStatements(); + for (PsiImportStatementBase statement : imports) { + progress.checkCanceled(); + HighlightInfo info = processImport(statement, unusedImportKey); + if (info != null) { + errorFound |= info.getSeverity() == HighlightSeverity.ERROR; + result.add(info); + } + } } - } } - } - } - HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME); - if (isUnusedImportEnabled(unusedImportKey)) { - PsiImportList importList = ((PsiJavaFile) myFile).getImportList(); - if (importList != null) { - final PsiImportStatementBase[] imports = importList.getAllImportStatements(); - for (PsiImportStatementBase statement : imports) { - progress.checkCanceled(); - final HighlightInfo info = processImport(statement, unusedImportKey); - if (info != null) { - errorFound |= info.getSeverity() == HighlightSeverity.ERROR; - result.add(info); - } - } - } - } + if (errorFound) { + fileStatusMap.setErrorFoundFlag(myProject, myDocument, true); + } - if (errorFound) { - fileStatusMap.setErrorFoundFlag(myProject, myDocument, true); + optimizeImportsOnTheFlyLater(progress); } - optimizeImportsOnTheFlyLater(progress); - } + private boolean isUnusedImportEnabled(HighlightDisplayKey unusedImportKey) { + InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); + //noinspection SimplifiableIfStatement + if (profile.isToolEnabled(unusedImportKey, myFile) && myFile instanceof PsiJavaFile + && HighlightingLevelManager.getInstance(myProject).shouldInspect(myFile)) { + return true; + } - private boolean isUnusedImportEnabled(HighlightDisplayKey unusedImportKey) { - InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); - if (profile.isToolEnabled(unusedImportKey, myFile) && myFile instanceof PsiJavaFile && HighlightingLevelManager.getInstance(myProject).shouldInspect(myFile)) { - return true; - } - final ImplicitUsageProvider[] implicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME); - for (ImplicitUsageProvider provider : implicitUsageProviders) { - if (provider instanceof UnusedImportProvider && ((UnusedImportProvider) provider).isUnusedImportEnabled(myFile)) { - return true; - } - } - return false; - } - - @Nullable - private HighlightInfo processIdentifier(@Nonnull PsiIdentifier identifier, @Nonnull ProgressIndicator progress, @Nonnull GlobalUsageHelper helper) { - PsiElement parent = identifier.getParent(); - if (!(parent instanceof PsiVariable || parent instanceof PsiMember)) { - return null; - } + return myProject.getExtensionPoint(ImplicitUsageProvider.class).anyMatchSafe( + implicitUsageProvider -> implicitUsageProvider instanceof JavaImplicitUsageProvider javaImplicitUsageProvider + && javaImplicitUsageProvider.isUnusedImportEnabled(myFile) + ); + } + + @Nullable + @RequiredReadAction + private HighlightInfo processIdentifier( + PsiIdentifier identifier, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + PsiElement parent = identifier.getParent(); + if (!(parent instanceof PsiVariable || parent instanceof PsiMember)) { + return null; + } - if (SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) { - return null; - } + if (SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) { + return null; + } - if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) { - return processLocalVariable((PsiLocalVariable) parent, identifier, progress); - } - if (parent instanceof PsiField && compareVisibilities((PsiModifierListOwner) parent, myUnusedSymbolInspection.getFieldVisibility())) { - return processField(myProject, (PsiField) parent, identifier, progress, helper); - } - if (parent instanceof PsiParameter) { - final PsiElement declarationScope = ((PsiParameter) parent).getDeclarationScope(); - if (declarationScope instanceof PsiMethod ? compareVisibilities((PsiModifierListOwner) declarationScope, myUnusedSymbolInspection.getParameterVisibility()) : myUnusedSymbolInspection - .LOCAL_VARIABLE) { - if (SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspectionBase.UNUSED_PARAMETERS_SHORT_NAME)) { - return null; - } - return processParameter(myProject, (PsiParameter) parent, identifier, progress); - } - } - if (parent instanceof PsiMethod) { - if (myUnusedSymbolInspection.isIgnoreAccessors() && PropertyUtil.isSimplePropertyAccessor((PsiMethod) parent)) { + if (parent instanceof PsiLocalVariable localVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) { + return processLocalVariable(localVariable, identifier, progress); + } + if (parent instanceof PsiField field && compareVisibilities(field, myUnusedSymbolInspection.getFieldVisibility())) { + return processField(myProject, field, identifier, progress, helper); + } + if (parent instanceof PsiParameter parameter) { + PsiElement declarationScope = parameter.getDeclarationScope(); + if (declarationScope instanceof PsiMethod method + ? compareVisibilities(method, myUnusedSymbolInspection.getParameterVisibility()) + : myUnusedSymbolInspection.LOCAL_VARIABLE) { + if (SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspectionBase.UNUSED_PARAMETERS_SHORT_NAME)) { + return null; + } + return processParameter(myProject, parameter, identifier, progress); + } + } + if (parent instanceof PsiMethod method) { + if (myUnusedSymbolInspection.isIgnoreAccessors() && PropertyUtil.isSimplePropertyAccessor(method)) { + return null; + } + if (compareVisibilities(method, myUnusedSymbolInspection.getMethodVisibility())) { + return processMethod(myProject, method, identifier, progress, helper); + } + } + if (parent instanceof PsiClass psiClass) { + String acceptedVisibility = psiClass.getContainingClass() == null + ? myUnusedSymbolInspection.getClassVisibility() + : myUnusedSymbolInspection.getInnerClassVisibility(); + if (compareVisibilities(psiClass, acceptedVisibility)) { + return processClass(myProject, psiClass, identifier, progress, helper); + } + } return null; - } - if (compareVisibilities((PsiModifierListOwner) parent, myUnusedSymbolInspection.getMethodVisibility())) { - return processMethod(myProject, (PsiMethod) parent, identifier, progress, helper); - } - } - if (parent instanceof PsiClass) { - final String acceptedVisibility = ((PsiClass) parent).getContainingClass() == null ? myUnusedSymbolInspection.getClassVisibility() : myUnusedSymbolInspection.getInnerClassVisibility(); - if (compareVisibilities((PsiModifierListOwner) parent, acceptedVisibility)) { - return processClass(myProject, (PsiClass) parent, identifier, progress, helper); - } } - return null; - } - private static boolean compareVisibilities(PsiModifierListOwner listOwner, final String visibility) { - if (visibility != null) { - while (listOwner != null) { - if (VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(listOwner.getModifierList()), visibility) >= 0) { - return true; + private static boolean compareVisibilities(PsiModifierListOwner listOwner, String visibility) { + if (visibility != null) { + while (listOwner != null) { + if (VisibilityUtil.compare(VisibilityUtil.getVisibilityModifier(listOwner.getModifierList()), visibility) >= 0) { + return true; + } + listOwner = PsiTreeUtil.getParentOfType(listOwner, PsiModifierListOwner.class, true); + } + } + return false; + } + + @Nullable + @RequiredReadAction + private HighlightInfo processLocalVariable( + PsiLocalVariable variable, + PsiIdentifier identifier, + ProgressIndicator progress + ) { + if (PsiUtil.isIgnoredName(variable.getName()) || variable.isUnnamed()) { + return null; } - listOwner = PsiTreeUtil.getParentOfType(listOwner, PsiModifierListOwner.class, true); - } - } - return false; - } - @Nullable - private HighlightInfo processLocalVariable(@Nonnull PsiLocalVariable variable, @Nonnull PsiIdentifier identifier, @Nonnull ProgressIndicator progress) { - if (variable instanceof PsiResourceVariable && PsiUtil.isIgnoredName(variable.getName())) { - return null; - } - if (UnusedSymbolUtil.isImplicitUsage(myProject, variable, progress)) { - return null; - } + if (UnusedSymbolUtil.isImplicitUsage(myProject, variable, progress)) { + return null; + } - if (!myRefCountHolder.isReferenced(variable)) { - String message = JavaErrorBundle.message("local.variable.is.never.used", identifier.getText()); - HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - IntentionAction fix = variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable) : QuickFixFactory.getInstance() - .createRemoveUnusedVariableFix(variable); - QuickFixAction.registerQuickFixAction(highlightInfo, fix, myDeadCodeKey); - return highlightInfo; - } + if (!myRefCountHolder.isReferenced(variable)) { + LocalizeValue message = JavaErrorLocalize.localVariableIsNeverUsed(identifier.getText()); + IntentionAction fix = variable instanceof PsiResourceVariable + ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable) + : QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(fix).key(myDeadCodeKey).register() + .create(); + } - boolean referenced = myRefCountHolder.isReferencedForRead(variable); - if (!referenced && !UnusedSymbolUtil.isImplicitRead(myProject, variable, progress)) { - String message = JavaErrorBundle.message("local.variable.is.not.used.for.reading", identifier.getText()); - HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable), myDeadCodeKey); - return highlightInfo; - } + boolean referenced = myRefCountHolder.isReferencedForRead(variable); + if (!referenced && !UnusedSymbolUtil.isImplicitRead(myProject, variable, progress)) { + LocalizeValue message = JavaErrorLocalize.localVariableIsNotUsedForReading(identifier.getText()); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable)).key(myDeadCodeKey).register() + .create(); + } - if (!variable.hasInitializer()) { - referenced = myRefCountHolder.isReferencedForWrite(variable); - if (!referenced && !UnusedSymbolUtil.isImplicitWrite(myProject, variable, progress)) { - String message = JavaErrorBundle.message("local.variable.is.not.assigned", identifier.getText()); - final HighlightInfo unusedSymbolInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspectionBase.DISPLAY_NAME), myDeadCodeKey); - return unusedSymbolInfo; - } - } + if (!variable.hasInitializer()) { + referenced = myRefCountHolder.isReferencedForWrite(variable); + if (!referenced && !UnusedSymbolUtil.isImplicitWrite(myProject, variable, progress)) { + LocalizeValue message = JavaErrorLocalize.localVariableIsNotAssigned(identifier.getText()); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(new EmptyIntentionAction(UnusedSymbolLocalInspectionBase.DISPLAY_NAME)).key(myDeadCodeKey).register() + .create(); + } + } - return null; - } - - @Nullable - private HighlightInfo processField(@Nonnull final Project project, - @Nonnull final PsiField field, - @Nonnull PsiIdentifier identifier, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - if (HighlightUtil.isSerializationImplicitlyUsedField(field)) { - return null; + return null; } - if (field.hasModifierProperty(PsiModifier.PRIVATE)) { - final QuickFixFactory quickFixFactory = QuickFixFactory.getInstance(); - if (!myRefCountHolder.isReferenced(field) && !UnusedSymbolUtil.isImplicitUsage(myProject, field, progress)) { - String message = JavaErrorBundle.message("private.field.is.not.used", identifier.getText()); - HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message); - if (!field.hasInitializer() && !field.hasModifierProperty(PsiModifier.FINAL)) { - QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), quickFixFactory.createCreateConstructorParameterFromFieldFix(field)); + @Nullable + @RequiredReadAction + private HighlightInfo processField( + Project project, + PsiField field, + PsiIdentifier identifier, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (HighlightUtil.isSerializationImplicitlyUsedField(field)) { + return null; } - return highlightInfo; - } + if (field.isPrivate()) { + QuickFixFactory quickFixFactory = QuickFixFactory.getInstance(); + if (!myRefCountHolder.isReferenced(field) && !UnusedSymbolUtil.isImplicitUsage(myProject, field, progress)) { + LocalizeValue message = JavaErrorLocalize.privateFieldIsNotUsed(identifier.getText()); + + HighlightInfo.Builder hlBuilder = suggestionsToMakeFieldUsed(field, identifier, message); + if (!field.hasInitializer() && !field.isFinal()) { + hlBuilder.newFix(quickFixFactory.createCreateConstructorParameterFromFieldFix(field)) + .fixRange(HighlightMethodUtil.getFixRange(field)) + .register(); + } + return hlBuilder.create(); + } - final boolean readReferenced = myRefCountHolder.isReferencedForRead(field); - if (!readReferenced && !UnusedSymbolUtil.isImplicitRead(project, field, progress)) { - String message = JavaErrorBundle.message("private.field.is.not.used.for.reading", identifier.getText()); - return suggestionsToMakeFieldUsed(field, identifier, message); - } + boolean readReferenced = myRefCountHolder.isReferencedForRead(field); + if (!readReferenced && !UnusedSymbolUtil.isImplicitRead(project, field, progress)) { + LocalizeValue message = JavaErrorLocalize.privateFieldIsNotUsedForReading(identifier.getText()); + return suggestionsToMakeFieldUsed(field, identifier, message).create(); + } - if (field.hasInitializer()) { - return null; - } - final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field); - if (!writeReferenced && !UnusedSymbolUtil.isImplicitWrite(project, field, progress)) { - String message = JavaErrorBundle.message("private.field.is.not.assigned", identifier.getText()); - final HighlightInfo info = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - - QuickFixAction.registerQuickFixAction(info, quickFixFactory.createCreateGetterOrSetterFix(false, true, field), myDeadCodeKey); - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), quickFixFactory.createCreateConstructorParameterFromFieldFix(field)); - } - SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(field, annoName -> - { - QuickFixAction.registerQuickFixAction(info, quickFixFactory.createAddToImplicitlyWrittenFieldsFix(project, annoName)); - return true; - }); - return info; - } - } else if (UnusedSymbolUtil.isImplicitUsage(myProject, field, progress) && !UnusedSymbolUtil.isImplicitWrite(myProject, field, progress)) { - return null; - } else if (UnusedSymbolUtil.isFieldUnused(myProject, myFile, field, progress, helper)) { - if (UnusedSymbolUtil.isImplicitWrite(myProject, field, progress)) { - String message = JavaErrorBundle.message("private.field.is.not.used.for.reading", identifier.getText()); - HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(field), myDeadCodeKey); - return highlightInfo; - } - return formatUnusedSymbolHighlightInfo(project, "field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType, identifier); - } - return null; - } - - private HighlightInfo suggestionsToMakeFieldUsed(@Nonnull PsiField field, @Nonnull PsiIdentifier identifier, @Nonnull String message) { - HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(field), myDeadCodeKey); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, false, field), myDeadCodeKey); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field), myDeadCodeKey); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, true, field), myDeadCodeKey); - return highlightInfo; - } - - private final Map isOverriddenOrOverrides = ConcurrentFactoryMap.createMap(method -> - { - boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null; - return overrides || OverridingMethodsSearch.search(method).findFirst() != null; - }); - - private boolean isOverriddenOrOverrides(@Nonnull PsiMethod method) { - return isOverriddenOrOverrides.get(method); - } - - @Nullable - private HighlightInfo processParameter(@Nonnull Project project, @Nonnull PsiParameter parameter, @Nonnull PsiIdentifier identifier, @Nonnull ProgressIndicator progress) { - PsiElement declarationScope = parameter.getDeclarationScope(); - if (declarationScope instanceof PsiMethod) { - PsiMethod method = (PsiMethod) declarationScope; - if (PsiUtilCore.hasErrorElementChild(method)) { + if (field.hasInitializer()) { + return null; + } + boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field); + if (!writeReferenced && !UnusedSymbolUtil.isImplicitWrite(project, field, progress)) { + LocalizeValue message = JavaErrorLocalize.privateFieldIsNotAssigned(identifier.getText()); + HighlightInfo.Builder hlBuilder = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); + + hlBuilder.newFix(quickFixFactory.createCreateGetterOrSetterFix(false, true, field)).key(myDeadCodeKey).register(); + if (!field.isFinal()) { + hlBuilder.newFix(quickFixFactory.createCreateConstructorParameterFromFieldFix(field)) + .fixRange(HighlightMethodUtil.getFixRange(field)) + .register(); + } + SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes( + field, + annoName -> { + hlBuilder.registerFix(quickFixFactory.createAddToImplicitlyWrittenFieldsFix(project, annoName)); + return true; + } + ); + return hlBuilder.create(); + } + } + else if (UnusedSymbolUtil.isImplicitUsage(myProject, field, progress) + && !UnusedSymbolUtil.isImplicitWrite(myProject, field, progress)) { + return null; + } + else if (UnusedSymbolUtil.isFieldUnused(myProject, myFile, field, progress, helper)) { + if (UnusedSymbolUtil.isImplicitWrite(myProject, field, progress)) { + LocalizeValue message = JavaErrorLocalize.privateFieldIsNotUsedForReading(identifier.getText()); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(QuickFixFactory.getInstance().createSafeDeleteFix(field)).key(myDeadCodeKey).register() + .create(); + } + return formatUnusedSymbolHighlightInfo( + project, + JavaErrorLocalize::fieldIsNotUsed, + field, + "fields", + myDeadCodeKey, + myDeadCodeInfoType, + identifier + ).create(); + } return null; - } - if ((method.isConstructor() || method.hasModifierProperty(PsiModifier.PRIVATE) || method.hasModifierProperty(PsiModifier.STATIC) || !method.hasModifierProperty(PsiModifier.ABSTRACT) && - !isOverriddenOrOverrides(method)) && !method.hasModifierProperty(PsiModifier.NATIVE) && !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) && - !PsiClassImplUtil.isMainOrPremainMethod(method)) { - if (UnusedSymbolUtil.isInjected(project, method)) { - return null; - } - HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); - if (highlightInfo != null) { - QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo); - return highlightInfo; - } - } - } else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) { - HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); - if (highlightInfo != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter), myDeadCodeKey); - return highlightInfo; - } } - return null; - } - - @Nullable - private HighlightInfo checkUnusedParameter(@Nonnull PsiParameter parameter, @Nonnull PsiIdentifier identifier, @Nonnull ProgressIndicator progress) { - if (!myRefCountHolder.isReferenced(parameter) && !UnusedSymbolUtil.isImplicitUsage(myProject, parameter, progress)) { - String message = JavaErrorBundle.message("parameter.is.not.used", identifier.getText()); - return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - } - return null; - } - - @Nullable - private HighlightInfo processMethod(@Nonnull final Project project, - @Nonnull final PsiMethod method, - @Nonnull PsiIdentifier identifier, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper) { - if (UnusedSymbolUtil.isMethodReferenced(myProject, myFile, method, progress, helper)) { - return null; + @RequiredReadAction + private HighlightInfo.Builder suggestionsToMakeFieldUsed( + PsiField field, + PsiIdentifier identifier, + LocalizeValue message + ) { + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(fixFactory.createRemoveUnusedVariableFix(field)).key(myDeadCodeKey).register() + .newFix(fixFactory.createCreateGetterOrSetterFix(true, false, field)).key(myDeadCodeKey).register() + .newFix(fixFactory.createCreateGetterOrSetterFix(false, true, field)).key(myDeadCodeKey).register() + .newFix(fixFactory.createCreateGetterOrSetterFix(true, true, field)).key(myDeadCodeKey).register(); } - String key; - if (method.hasModifierProperty(PsiModifier.PRIVATE)) { - key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used"; - } else { - key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used"; - } - String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY); - String message = JavaErrorBundle.message(key, symbolName); - final HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(method), myDeadCodeKey); - SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(method, annoName -> - { - IntentionAction fix = QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, "methods"); - QuickFixAction.registerQuickFixAction(highlightInfo, fix); - return true; + + private final Map isOverriddenOrOverrides = ConcurrentFactoryMap.createMap(method -> { + boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null; + return overrides || OverridingMethodsSearch.search(method).findFirst() != null; }); - return highlightInfo; - } - @Nullable - private HighlightInfo processClass(@Nonnull Project project, @Nonnull PsiClass aClass, @Nonnull PsiIdentifier identifier, @Nonnull ProgressIndicator progress, @Nonnull GlobalUsageHelper helper) { - if (UnusedSymbolUtil.isClassUsed(project, myFile, aClass, progress, helper)) { - return null; - } + private boolean isOverriddenOrOverrides(PsiMethod method) { + return isOverriddenOrOverrides.get(method); + } + + @Nullable + @RequiredReadAction + private HighlightInfo processParameter( + Project project, + PsiParameter parameter, + PsiIdentifier identifier, + ProgressIndicator progress + ) { + PsiElement declarationScope = parameter.getDeclarationScope(); + if (declarationScope instanceof PsiMethod method) { + if (PsiUtilCore.hasErrorElementChild(method)) { + return null; + } + if ((method.isConstructor() || method.isPrivate() || method.isStatic() + || !method.isAbstract() && !isOverriddenOrOverrides(method)) + && !method.hasModifierProperty(PsiModifier.NATIVE) + && !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) + && !PsiClassImplUtil.isMainOrPremainMethod(method)) { + if (UnusedSymbolUtil.isInjected(project, method)) { + return null; + } + HighlightInfo.Builder hlBuilder = checkUnusedParameter(parameter, identifier, progress); + if (hlBuilder != null) { + HighlightInfo highlightInfo = hlBuilder.create(); + if (highlightInfo != null) { + QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo); + return highlightInfo; + } + } + } + } + else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) { + HighlightInfo.Builder hlBuilder = checkUnusedParameter(parameter, identifier, progress); + if (hlBuilder != null) { + return hlBuilder + .newFix(QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter)).key(myDeadCodeKey).register() + .create(); + } + } - String pattern; - if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) { - pattern = aClass.isInterface() ? "private.inner.interface.is.not.used" : "private.inner.class.is.not.used"; - } else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class - pattern = "local.class.is.not.used"; - } else if (aClass instanceof PsiTypeParameter) { - pattern = "type.parameter.is.not.used"; - } else { - pattern = "class.is.not.used"; - } - return formatUnusedSymbolHighlightInfo(myProject, pattern, aClass, "classes", myDeadCodeKey, myDeadCodeInfoType, identifier); - } - - - private static HighlightInfo formatUnusedSymbolHighlightInfo(@Nonnull final Project project, - @Nonnull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String pattern, - @Nonnull final PsiNameIdentifierOwner aClass, - @Nonnull final String element, - HighlightDisplayKey highlightDisplayKey, - @Nonnull HighlightInfoType highlightInfoType, - @Nonnull PsiElement identifier) { - String symbolName = aClass.getName(); - String message = JavaErrorBundle.message(pattern, symbolName); - final HighlightInfo highlightInfo = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, highlightInfoType); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(aClass), highlightDisplayKey); - SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes((PsiModifierListOwner) aClass, annoName -> - { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, element)); - return true; - }); - return highlightInfo; - } - - @Nullable - private HighlightInfo processImport(@Nonnull PsiImportStatementBase importStatement, @Nonnull HighlightDisplayKey unusedImportKey) { - // jsp include directive hack - if (importStatement.isForeignFileImport()) { - return null; + return null; } - if (PsiUtilCore.hasErrorElementChild(importStatement)) { - return null; + @RequiredReadAction + private HighlightInfo.@Nullable Builder checkUnusedParameter( + PsiParameter parameter, + PsiIdentifier identifier, + ProgressIndicator progress + ) { + if (!myRefCountHolder.isReferenced(parameter) && !UnusedSymbolUtil.isImplicitUsage(myProject, parameter, progress)) { + LocalizeValue message = JavaErrorLocalize.parameterIsNotUsed(identifier.getText()); + return UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType); + } + return null; } - boolean isRedundant = myRefCountHolder.isRedundant(importStatement); - if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) { - //check import from same package - String packageName = ((PsiClassOwner) importStatement.getContainingFile()).getPackageName(); - PsiJavaCodeReferenceElement reference = importStatement.getImportReference(); - PsiElement resolved = reference == null ? null : reference.resolve(); - if (resolved instanceof PsiPackage) { - isRedundant = packageName.equals(((PsiQualifiedNamedElement) resolved).getQualifiedName()); - } else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) { - String qName = ((PsiClass) resolved).getQualifiedName(); - if (qName != null) { - String name = ((PomNamedTarget) resolved).getName(); - isRedundant = qName.equals(packageName + '.' + name); - } - } - } + @Nullable + @RequiredReadAction + private HighlightInfo processMethod( + Project project, + PsiMethod method, + PsiIdentifier identifier, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (UnusedSymbolUtil.isMethodReferenced(myProject, myFile, method, progress, helper)) { + return null; + } + Function key; + if (method.isPrivate()) { + key = method.isConstructor() ? JavaErrorLocalize::privateConstructorIsNotUsed : JavaErrorLocalize::privateMethodIsNotUsed; + } + else { + key = method.isConstructor() ? JavaErrorLocalize::constructorIsNotUsed : JavaErrorLocalize::methodIsNotUsed; + } + LocalizeValue symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY); + LocalizeValue message = key.apply(symbolName); + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, myDeadCodeInfoType) + .newFix(fixFactory.createSafeDeleteFix(method)).key(myDeadCodeKey).register(); + SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes( + method, + annoName -> { + hlBuilder.registerFix(fixFactory.createAddToDependencyInjectionAnnotationsFix(project, annoName, "methods")); + return true; + } + ); + return hlBuilder.create(); + } + + @Nullable + @RequiredReadAction + private HighlightInfo processClass( + Project project, + PsiClass aClass, + PsiIdentifier identifier, + ProgressIndicator progress, + GlobalUsageHelper helper + ) { + if (UnusedSymbolUtil.isClassUsed(project, myFile, aClass, progress, helper)) { + return null; + } - if (isRedundant) { - return registerRedundantImport(importStatement, unusedImportKey); + Function pattern; + if (aClass.getContainingClass() != null && aClass.isPrivate()) { + pattern = aClass.isInterface() + ? JavaErrorLocalize::privateInnerInterfaceIsNotUsed + : JavaErrorLocalize::privateInnerClassIsNotUsed; + } + else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class + pattern = JavaErrorLocalize::localClassIsNotUsed; + } + else if (aClass instanceof PsiTypeParameter) { + pattern = JavaErrorLocalize::typeParameterIsNotUsed; + } + else { + pattern = JavaErrorLocalize::classIsNotUsed; + } + return formatUnusedSymbolHighlightInfo(myProject, pattern, aClass, "classes", myDeadCodeKey, myDeadCodeInfoType, identifier) + .create(); + } + + @RequiredReadAction + private static HighlightInfo.Builder formatUnusedSymbolHighlightInfo( + Project project, + Function pattern, + PsiNameIdentifierOwner aClass, + String element, + HighlightDisplayKey highlightDisplayKey, + HighlightInfoType highlightInfoType, + PsiElement identifier + ) { + String symbolName = aClass.getName(); + LocalizeValue message = pattern.apply(symbolName); + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + HighlightInfo.Builder hlBuilder = UnusedSymbolUtil.createUnusedSymbolInfo(identifier, message, highlightInfoType) + .newFix(fixFactory.createSafeDeleteFix(aClass)).key(highlightDisplayKey).register(); + SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes( + (PsiModifierListOwner) aClass, + annoName -> { + hlBuilder.registerFix(fixFactory.createAddToDependencyInjectionAnnotationsFix(project, annoName, element)); + return true; + } + ); + return hlBuilder; } - int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement); - if (entryIndex < myCurrentEntryIndex) { - myHasMissortedImports = true; - } - myCurrentEntryIndex = entryIndex; + @Nullable + @RequiredReadAction + private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) { + // jsp include directive hack + if (importStatement.isForeignFileImport()) { + return null; + } + + if (PsiUtilCore.hasErrorElementChild(importStatement)) { + return null; + } + + boolean isRedundant = myRefCountHolder.isRedundant(importStatement); + if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) { + //check import from same package + String packageName = ((PsiClassOwner) importStatement.getContainingFile()).getPackageName(); + PsiJavaCodeReferenceElement reference = importStatement.getImportReference(); + PsiElement resolved = reference == null ? null : reference.resolve(); + if (resolved instanceof PsiPackage psiPackage) { + isRedundant = packageName.equals(psiPackage.getQualifiedName()); + } + else if (resolved instanceof PsiClass psiClass && !importStatement.isOnDemand()) { + String qName = psiClass.getQualifiedName(); + if (qName != null) { + String name = ((PomNamedTarget) resolved).getName(); + isRedundant = qName.equals(packageName + '.' + name); + } + } + } + + if (isRedundant) { + return registerRedundantImport(importStatement, unusedImportKey); + } + + int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement); + if (entryIndex < myCurrentEntryIndex) { + myHasMissortedImports = true; + } + myCurrentEntryIndex = entryIndex; - return null; - } + return null; + } - private HighlightInfo registerRedundantImport(@Nonnull PsiImportStatementBase importStatement, @Nonnull HighlightDisplayKey unusedImportKey) { - String description = InspectionsBundle.message("unused.import.statement"); - HighlightInfo info = HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT).range(importStatement).descriptionAndTooltip(description).create(); + @RequiredReadAction + private HighlightInfo registerRedundantImport( + PsiImportStatementBase importStatement, + HighlightDisplayKey unusedImportKey + ) { + myHasRedundantImports = true; - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createOptimizeImportsFix(false), unusedImportKey); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createEnableOptimizeImportsOnTheFlyFix(), unusedImportKey); - myHasRedundantImports = true; - return info; - } + QuickFixFactory fixFactory = QuickFixFactory.getInstance(); + return HighlightInfo.newHighlightInfo(myUnusedImportHighlightType) + .range(importStatement) + .descriptionAndTooltip(InspectionLocalize.unusedImportStatement()) + .newFix(fixFactory.createOptimizeImportsFix(false)).key(unusedImportKey).register() + .newFix(fixFactory.createEnableOptimizeImportsOnTheFlyFix()).key(unusedImportKey).register() + .create(); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PsiMethodReferenceHighlightingUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PsiMethodReferenceHighlightingUtil.java index 5a14aab8c9..2013aa7ec5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PsiMethodReferenceHighlightingUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/PsiMethodReferenceHighlightingUtil.java @@ -19,28 +19,24 @@ import com.intellij.java.language.psi.PsiMethodReferenceExpression; import com.intellij.java.language.psi.PsiReferenceExpression; import com.intellij.java.language.psi.PsiType; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.editor.rawHighlight.HighlightInfoType; -import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; public class PsiMethodReferenceHighlightingUtil { - public static HighlightInfo checkRawConstructorReference(@Nonnull PsiMethodReferenceExpression expression) { - if (expression.isConstructor()) { - PsiType[] typeParameters = expression.getTypeParameters(); - if (typeParameters.length > 0) { - PsiElement qualifier = expression.getQualifier(); - if (qualifier instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) qualifier).resolve(); - if (resolve instanceof PsiClass && ((PsiClass) resolve).hasTypeParameters()) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) - .descriptionAndTooltip("Raw constructor reference with explicit type parameters for " + - "constructor").create(); - } + @RequiredReadAction + public static HighlightInfo.Builder checkRawConstructorReference(PsiMethodReferenceExpression expression) { + if (expression.isConstructor()) { + PsiType[] typeParameters = expression.getTypeParameters(); + if (typeParameters.length > 0 + && expression.getQualifier() instanceof PsiReferenceExpression qualifierRefExpr + && qualifierRefExpr.resolve() instanceof PsiClass psiClass && psiClass.hasTypeParameters()) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip("Raw constructor reference with explicit type parameters for constructor"); + } } - } + return null; } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/QualifyWithThisFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/QualifyWithThisFix.java index 10acdc8bd9..7894a5ae03 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/QualifyWithThisFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/QualifyWithThisFix.java @@ -25,10 +25,9 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.PsiManager; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import javax.annotation.Nonnull; - /** * User: anna */ @@ -41,19 +40,18 @@ public QualifyWithThisFix(PsiClass containingClass, PsiElement expression) { myExpression = expression; } - @Nonnull @Override - public String getText() { - return "Qualify with " + myContainingClass.getName() + ".this"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Qualify with " + myContainingClass.getName() + ".this"); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return true; } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { final PsiThisExpression thisExpression = RefactoringChangeUtil.createThisExpression(PsiManager.getInstance(project), myContainingClass); ((PsiReferenceExpression)myExpression).setQualifierExpression(thisExpression); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/RefCountHolder.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/RefCountHolder.java index 2741a68924..cab1761765 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/RefCountHolder.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/RefCountHolder.java @@ -3,17 +3,14 @@ import com.intellij.java.analysis.impl.codeInsight.daemon.impl.GlobalUsageHelper; import com.intellij.java.analysis.impl.codeInspection.deadCode.UnusedDeclarationInspectionBase; +import com.intellij.java.analysis.impl.codeInspection.deadCode.UnusedDeclarationInspectionState; import com.intellij.java.analysis.impl.psi.util.PsiMatchers; import com.intellij.java.language.psi.*; import consulo.annotation.access.RequiredReadAction; import consulo.document.util.TextRange; import consulo.language.editor.highlight.ReadWriteAccessDetector; import consulo.language.file.FileViewProvider; -import consulo.language.impl.psi.PsiAnchor; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiFile; -import consulo.language.psi.PsiNamedElement; -import consulo.language.psi.PsiReference; +import consulo.language.psi.*; import consulo.language.psi.util.PsiMatcherImpl; import consulo.language.psi.util.PsiTreeUtil; import consulo.module.content.ProjectFileIndex; @@ -22,10 +19,8 @@ import consulo.util.collection.*; import consulo.util.dataholder.Key; import consulo.virtualFileSystem.VirtualFile; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.Collection; @@ -35,310 +30,349 @@ import java.util.concurrent.ConcurrentHashMap; final class RefCountHolder { - private final PsiFile myFile; - // resolved elements -> list of their references in this file - private final - @Nonnull - MultiMap myLocalRefsMap; - - private final Set myDclsUsedMap; - // reference -> import statement the reference has come from - private final Map myImportStatements; - - private static final Key> REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY"); - private volatile boolean ready; // true when analysis completed and inner maps can be queried - - static RefCountHolder get(@Nonnull PsiFile file, @Nonnull TextRange dirtyScope) { - Reference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY); - RefCountHolder storedHolder = consulo.util.lang.ref.SoftReference.dereference(ref); - boolean wholeFile = dirtyScope.equals(file.getTextRange()); - if (storedHolder == null && !wholeFile) { - // RefCountHolder was GCed and queried for subrange of the file, can't return anything meaningful - return null; + private final PsiFile myFile; + // resolved elements -> list of their references in this file + private final MultiMap myLocalRefsMap; + + private final Set myDclsUsedMap; + // reference -> import statement the reference has come from + private final Map myImportStatements; + + private static final Key> REF_COUNT_HOLDER_IN_FILE_KEY = Key.create("REF_COUNT_HOLDER_IN_FILE_KEY"); + private volatile boolean ready; // true when analysis completed and inner maps can be queried + + @RequiredReadAction + static RefCountHolder get(PsiFile file, TextRange dirtyScope) { + Reference ref = file.getUserData(REF_COUNT_HOLDER_IN_FILE_KEY); + RefCountHolder storedHolder = consulo.util.lang.ref.SoftReference.dereference(ref); + boolean wholeFile = dirtyScope.equals(file.getTextRange()); + if (storedHolder == null && !wholeFile) { + // RefCountHolder was GCed and queried for subrange of the file, can't return anything meaningful + return null; + } + return storedHolder == null || wholeFile + ? new RefCountHolder( + file, + MultiMap.createConcurrentSet(), + Sets.newConcurrentHashSet(HashingStrategy.canonical()), + new ConcurrentHashMap<>() + ) + : storedHolder.removeInvalidRefs(); } - return storedHolder == null || wholeFile ? - new RefCountHolder(file, MultiMap.createConcurrentSet(), Sets.newConcurrentHashSet(HashingStrategy.canonical()), new ConcurrentHashMap<>()) - : storedHolder.removeInvalidRefs(); - } - - void storeReadyHolder(@Nonnull PsiFile file) { - ready = true; - file.putUserData(REF_COUNT_HOLDER_IN_FILE_KEY, new SoftReference<>(this)); - } - - private RefCountHolder(@Nonnull PsiFile file, - @Nonnull MultiMap myLocalRefsMap, - @Nonnull Set myDclsUsedMap, - @Nonnull Map myImportStatements) { - myFile = file; - this.myLocalRefsMap = myLocalRefsMap; - this.myDclsUsedMap = myDclsUsedMap; - this.myImportStatements = myImportStatements; - log("c: created for ", file); - } - - @Nonnull - GlobalUsageHelper getGlobalUsageHelper(@Nonnull PsiFile file, @Nullable UnusedDeclarationInspectionBase deadCodeInspection) { - FileViewProvider viewProvider = file.getViewProvider(); - Project project = file.getProject(); - - ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - VirtualFile virtualFile = viewProvider.getVirtualFile(); - boolean inLibrary = fileIndex.isInLibrary(virtualFile); - - boolean isDeadCodeEnabled = deadCodeInspection != null && deadCodeInspection.isGlobalEnabledInEditor(); - if (isDeadCodeEnabled && !inLibrary) { - return new GlobalUsageHelperBase() { - final Map myEntryPointCache = FactoryMap.create((PsiMember member) -> { - if (deadCodeInspection.isEntryPoint(member)) return true; - if (member instanceof PsiClass) { - return !JBTreeTraverser - .from(m -> m instanceof PsiClass - ? JBIterable.from(PsiTreeUtil.getStubChildrenOfTypeAsList(m, PsiMember.class)) - : JBIterable.empty()) - .withRoot(member) - .traverse() - .skip(1) - .processEach(this::shouldCheckUsages); - } - return false; - }); - @Override - public boolean shouldCheckUsages(@Nonnull PsiMember member) { - return !myEntryPointCache.get(member); + void storeReadyHolder(PsiFile file) { + ready = true; + file.putUserData(REF_COUNT_HOLDER_IN_FILE_KEY, new SoftReference<>(this)); + } + + private RefCountHolder( + PsiFile file, + MultiMap localRefsMap, + Set dclsUsedMap, + Map importStatements + ) { + myFile = file; + myLocalRefsMap = localRefsMap; + myDclsUsedMap = dclsUsedMap; + myImportStatements = importStatements; + log("c: created for ", file); + } + + GlobalUsageHelper getGlobalUsageHelper( + PsiFile file, + @Nullable UnusedDeclarationInspectionBase deadCodeInspection, + UnusedDeclarationInspectionState deadCodeState + ) { + FileViewProvider viewProvider = file.getViewProvider(); + Project project = file.getProject(); + + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + VirtualFile virtualFile = viewProvider.getVirtualFile(); + boolean inLibrary = fileIndex.isInLibrary(virtualFile); + + boolean isDeadCodeEnabled = deadCodeInspection != null && deadCodeInspection.isGlobalEnabledInEditor(); + if (isDeadCodeEnabled && !inLibrary) { + return new GlobalUsageHelperBase() { + final Map myEntryPointCache = FactoryMap.create((PsiMember member) -> { + if (deadCodeInspection.isEntryPoint(member, deadCodeState)) { + return true; + } + if (member instanceof PsiClass) { + return !JBTreeTraverser.from( + m -> m instanceof PsiClass + ? JBIterable.from(PsiTreeUtil.getStubChildrenOfTypeAsList(m, PsiMember.class)) + : JBIterable.empty() + ) + .withRoot(member) + .traverse() + .skip(1) + .processEach(this::shouldCheckUsages); + } + return false; + }); + + @Override + public boolean shouldCheckUsages(PsiMember member) { + return !myEntryPointCache.get(member); + } + }; } - }; + return new GlobalUsageHelperBase(); } - return new GlobalUsageHelperBase(); - } - - void registerLocallyReferenced(@Nonnull PsiNamedElement result) { - myDclsUsedMap.add(PsiAnchor.create(result)); - } - - void registerReference(@Nonnull PsiReference ref, @Nonnull JavaResolveResult resolveResult) { - PsiElement refElement = resolveResult.getElement(); - PsiFile psiFile = refElement == null ? null : refElement.getContainingFile(); - if (psiFile != null) - psiFile = (PsiFile) psiFile.getNavigationElement(); // look at navigation elements because all references resolve into Cls elements when highlighting library source - if (refElement != null && psiFile != null && myFile.getViewProvider().equals(psiFile.getViewProvider())) { - registerLocalRef(ref, refElement.getNavigationElement()); + + @RequiredReadAction + void registerLocallyReferenced(PsiNamedElement result) { + myDclsUsedMap.add(PsiAnchor.create(result)); } - PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); - if (resolveScope instanceof PsiImportStatementBase) { - registerImportStatement(ref, (PsiImportStatementBase) resolveScope); - } else if (refElement == null && ref instanceof PsiJavaReference) { - for (JavaResolveResult result : ((PsiJavaReference) ref).multiResolve(true)) { - resolveScope = result.getCurrentFileResolveScope(); + void registerReference(PsiReference ref, JavaResolveResult resolveResult) { + PsiElement refElement = resolveResult.getElement(); + PsiFile psiFile = refElement == null ? null : refElement.getContainingFile(); + if (psiFile != null) { + // look at navigation elements because all references resolve into Cls elements when highlighting library source + psiFile = (PsiFile)psiFile.getNavigationElement(); + } + if (refElement != null && psiFile != null && myFile.getViewProvider().equals(psiFile.getViewProvider())) { + registerLocalRef(ref, refElement.getNavigationElement()); + } + + PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); if (resolveScope instanceof PsiImportStatementBase) { - registerImportStatement(ref, (PsiImportStatementBase) resolveScope); - break; + registerImportStatement(ref, (PsiImportStatementBase)resolveScope); + } + else if (refElement == null && ref instanceof PsiJavaReference) { + for (JavaResolveResult result : ((PsiJavaReference)ref).multiResolve(true)) { + resolveScope = result.getCurrentFileResolveScope(); + if (resolveScope instanceof PsiImportStatementBase) { + registerImportStatement(ref, (PsiImportStatementBase)resolveScope); + break; + } + } } - } } - } - - private void registerImportStatement(@Nonnull PsiReference ref, @Nonnull PsiImportStatementBase importStatement) { - myImportStatements.put(ref, importStatement); - } - - boolean isRedundant(@Nonnull PsiImportStatementBase importStatement) { - assert ready; - return !myImportStatements.containsValue(importStatement); - } - - private void registerLocalRef(@Nonnull PsiReference ref, PsiElement refElement) { - PsiElement element = ref.getElement(); - if (refElement instanceof PsiMethod && PsiTreeUtil.isAncestor(refElement, element, true)) - return; // filter self-recursive calls - if (refElement instanceof PsiClass) { - if (PsiTreeUtil.isAncestor(refElement, element, true)) { - return; // filter inner use of itself - } + + private void registerImportStatement(PsiReference ref, PsiImportStatementBase importStatement) { + myImportStatements.put(ref, importStatement); } - myLocalRefsMap.putValue(refElement, ref); - } - - @Nonnull - private RefCountHolder removeInvalidRefs() { - assert ready; - boolean changed = false; - MultiMap newLocalRefsMap = MultiMap.createConcurrentSet(); - for (Map.Entry> entry : myLocalRefsMap.entrySet()) { - PsiElement element = entry.getKey(); - for (PsiReference ref : entry.getValue()) { - if (ref.getElement().isValid()) { - newLocalRefsMap.putValue(element, ref); - } else { - changed = true; - } - } + + boolean isRedundant(PsiImportStatementBase importStatement) { + assert ready; + return !myImportStatements.containsValue(importStatement); } - Set newDclsUsedMap = Sets.newConcurrentHashSet(HashingStrategy.canonical()); - for (PsiAnchor element : myDclsUsedMap) { - if (element.retrieve() != null) { - newDclsUsedMap.add(element); - } else { - changed = true; - } + + private void registerLocalRef(PsiReference ref, PsiElement refElement) { + PsiElement element = ref.getElement(); + if (refElement instanceof PsiMethod && PsiTreeUtil.isAncestor(refElement, element, true)) { + return; // filter self-recursive calls + } + if (refElement instanceof PsiClass) { + if (PsiTreeUtil.isAncestor(refElement, element, true)) { + return; // filter inner use of itself + } + } + myLocalRefsMap.putValue(refElement, ref); } - Map newImportStatements = new ConcurrentHashMap<>(); - for (Map.Entry entry : myImportStatements.entrySet()) { - PsiReference key = entry.getKey(); - PsiImportStatementBase value = entry.getValue(); - if (value.isValid() && key.getElement().isValid()) { - newImportStatements.put(key, value); - } else { - changed = true; - } + + @RequiredReadAction + private RefCountHolder removeInvalidRefs() { + assert ready; + boolean changed = false; + MultiMap newLocalRefsMap = MultiMap.createConcurrentSet(); + for (Map.Entry> entry : myLocalRefsMap.entrySet()) { + PsiElement element = entry.getKey(); + for (PsiReference ref : entry.getValue()) { + if (ref.getElement().isValid()) { + newLocalRefsMap.putValue(element, ref); + } + else { + changed = true; + } + } + } + Set newDclsUsedMap = Sets.newConcurrentHashSet(HashingStrategy.canonical()); + for (PsiAnchor element : myDclsUsedMap) { + if (element.retrieve() != null) { + newDclsUsedMap.add(element); + } + else { + changed = true; + } + } + Map newImportStatements = new ConcurrentHashMap<>(); + for (Map.Entry entry : myImportStatements.entrySet()) { + PsiReference key = entry.getKey(); + PsiImportStatementBase value = entry.getValue(); + if (value.isValid() && key.getElement().isValid()) { + newImportStatements.put(key, value); + } + else { + changed = true; + } + } + return changed ? new RefCountHolder(myFile, newLocalRefsMap, newDclsUsedMap, newImportStatements) : this; } - return changed ? new RefCountHolder(myFile, newLocalRefsMap, newDclsUsedMap, newImportStatements) : this; - } - - @RequiredReadAction - boolean isReferenced(@Nonnull PsiElement element) { - assert ready; - Collection array = myLocalRefsMap.get(element); - if (!array.isEmpty() && - !isParameterUsedRecursively(element, array) && - !isClassUsedForInnerImports(element, array)) { - for (PsiReference reference : array) { - if (reference.isReferenceTo(element)) return true; - } + + @RequiredReadAction + boolean isReferenced(PsiElement element) { + assert ready; + Collection array = myLocalRefsMap.get(element); + if (!array.isEmpty() && !isParameterUsedRecursively(element, array) && !isClassUsedForInnerImports(element, array)) { + for (PsiReference reference : array) { + if (reference.isReferenceTo(element)) { + return true; + } + } + } + + return myDclsUsedMap.contains(PsiAnchor.create(element)); } - return myDclsUsedMap.contains(PsiAnchor.create(element)); - } + @RequiredReadAction + private boolean isClassUsedForInnerImports(PsiElement element, Collection array) { + assert ready; + if (!(element instanceof PsiClass)) { + return false; + } - private boolean isClassUsedForInnerImports(@Nonnull PsiElement element, @Nonnull Collection array) { - assert ready; - if (!(element instanceof PsiClass)) return false; + Set imports = new HashSet<>(); + for (PsiReference classReference : array) { + PsiImportStatementBase importStmt = PsiTreeUtil.getParentOfType(classReference.getElement(), PsiImportStatementBase.class); + if (importStmt == null) { + return false; + } + imports.add(importStmt); + } - Set imports = new HashSet<>(); - for (PsiReference classReference : array) { - PsiImportStatementBase importStmt = PsiTreeUtil.getParentOfType(classReference.getElement(), PsiImportStatementBase.class); - if (importStmt == null) return false; - imports.add(importStmt); + return ContainerUtil.all( + imports, + importStmt -> { + PsiElement importedMember = importStmt.resolve(); + if (importedMember != null && PsiTreeUtil.isAncestor(element, importedMember, false)) { + for (PsiReference memberReference : myLocalRefsMap.get(importedMember)) { + if (!PsiTreeUtil.isAncestor(element, memberReference.getElement(), false)) { + return false; + } + } + return true; + } + return false; + } + ); } - return ContainerUtil.all(imports, importStmt -> { - PsiElement importedMember = importStmt.resolve(); - if (importedMember != null && PsiTreeUtil.isAncestor(element, importedMember, false)) { - for (PsiReference memberReference : myLocalRefsMap.get(importedMember)) { - if (!PsiTreeUtil.isAncestor(element, memberReference.getElement(), false)) { + @RequiredReadAction + private static boolean isParameterUsedRecursively(PsiElement element, Collection array) { + if (!(element instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiMethod method)) { return false; - } } + int paramIndex = ArrayUtil.find(method.getParameterList().getParameters(), parameter); + + for (PsiReference reference : array) { + if (!(reference instanceof PsiElement argument)) { + return false; + } + PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)new PsiMatcherImpl(argument) + .dot(PsiMatchers.hasClass(PsiReferenceExpression.class)) + .parent(PsiMatchers.hasClass(PsiExpressionList.class)) + .parent(PsiMatchers.hasClass(PsiMethodCallExpression.class)) + .getElement(); + if (methodCallExpression == null) { + return false; + } + PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + if (method != methodExpression.resolve()) { + return false; + } + PsiExpressionList argumentList = methodCallExpression.getArgumentList(); + PsiExpression[] arguments = argumentList.getExpressions(); + int argumentIndex = ArrayUtil.find(arguments, argument); + if (paramIndex != argumentIndex) { + return false; + } + } + return true; - } - return false; - }); - } - - @RequiredReadAction - private static boolean isParameterUsedRecursively(@Nonnull PsiElement element, @Nonnull Collection array) { - if (!(element instanceof PsiParameter)) return false; - PsiParameter parameter = (PsiParameter) element; - PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiMethod)) return false; - PsiMethod method = (PsiMethod) scope; - int paramIndex = ArrayUtil.find(method.getParameterList().getParameters(), parameter); - - for (PsiReference reference : array) { - if (!(reference instanceof PsiElement)) return false; - PsiElement argument = (PsiElement) reference; - - PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) new PsiMatcherImpl(argument) - .dot(PsiMatchers.hasClass(PsiReferenceExpression.class)) - .parent(PsiMatchers.hasClass(PsiExpressionList.class)) - .parent(PsiMatchers.hasClass(PsiMethodCallExpression.class)) - .getElement(); - if (methodCallExpression == null) return false; - PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); - if (method != methodExpression.resolve()) return false; - PsiExpressionList argumentList = methodCallExpression.getArgumentList(); - PsiExpression[] arguments = argumentList.getExpressions(); - int argumentIndex = ArrayUtil.find(arguments, argument); - if (paramIndex != argumentIndex) return false; } - return true; - } - - @RequiredReadAction - boolean isReferencedForRead(@Nonnull PsiVariable variable) { - assert ready; - Collection array = myLocalRefsMap.get(variable); - if (array.isEmpty()) return false; - for (PsiReference ref : array) { - PsiElement refElement = ref.getElement(); - PsiElement resolved = ref.resolve(); - if (resolved != null) { - ReadWriteAccessDetector.Access access = getAccess(ref, resolved); - if (access != null && access.isReferencedForRead()) { - if (isJustIncremented(access, refElement)) continue; - return true; + @RequiredReadAction + boolean isReferencedForRead(PsiVariable variable) { + assert ready; + Collection array = myLocalRefsMap.get(variable); + if (array.isEmpty()) { + return false; } - } - } - return false; - } - - @RequiredReadAction - private static ReadWriteAccessDetector.Access getAccess(@Nonnull PsiReference ref, @Nonnull PsiElement resolved) { - PsiElement start = resolved.getLanguage() == ref.getElement().getLanguage() ? resolved : ref.getElement(); - ReadWriteAccessDetector detector = ReadWriteAccessDetector.findDetector(start); - if (detector != null) { - return detector.getReferenceAccess(resolved, ref); + for (PsiReference ref : array) { + PsiElement refElement = ref.getElement(); + PsiElement resolved = ref.resolve(); + if (resolved != null) { + ReadWriteAccessDetector.Access access = getAccess(ref, resolved); + if (access != null && access.isReferencedForRead()) { + if (isJustIncremented(access, refElement)) { + continue; + } + return true; + } + } + } + return false; } - return null; - } - - // "var++;" - private static boolean isJustIncremented(@Nonnull ReadWriteAccessDetector.Access access, @Nonnull PsiElement refElement) { - return access == ReadWriteAccessDetector.Access.ReadWrite && - refElement instanceof PsiExpression && - refElement.getParent() instanceof PsiExpression && - refElement.getParent().getParent() instanceof PsiExpressionStatement; - } - - @RequiredReadAction - boolean isReferencedForWrite(@Nonnull PsiVariable variable) { - assert ready; - Collection array = myLocalRefsMap.get(variable); - if (array.isEmpty()) return false; - for (PsiReference ref : array) { - PsiElement resolved = ref.resolve(); - if (resolved != null) { - ReadWriteAccessDetector.Access access = getAccess(ref, resolved); - if (access != null && access.isReferencedForWrite()) { - return true; + + @RequiredReadAction + private static ReadWriteAccessDetector.Access getAccess(PsiReference ref, PsiElement resolved) { + PsiElement start = resolved.getLanguage() == ref.getElement().getLanguage() ? resolved : ref.getElement(); + ReadWriteAccessDetector detector = ReadWriteAccessDetector.findDetector(start); + if (detector != null) { + return detector.getReferenceAccess(resolved, ref); } - } + return null; } - return false; - } - private static void log(@NonNls @Nonnull Object... info) { - //FileStatusMap.log(info); - } + // "var++;" + private static boolean isJustIncremented(ReadWriteAccessDetector.Access access, PsiElement refElement) { + return access == ReadWriteAccessDetector.Access.ReadWrite + && refElement instanceof PsiExpression + && refElement.getParent() instanceof PsiExpression + && refElement.getParent().getParent() instanceof PsiExpressionStatement; + } - private class GlobalUsageHelperBase extends GlobalUsageHelper { - @Override - public boolean shouldCheckUsages(@Nonnull PsiMember member) { - return false; + @RequiredReadAction + boolean isReferencedForWrite(PsiVariable variable) { + assert ready; + Collection array = myLocalRefsMap.get(variable); + if (array.isEmpty()) { + return false; + } + for (PsiReference ref : array) { + PsiElement resolved = ref.resolve(); + if (resolved != null) { + ReadWriteAccessDetector.Access access = getAccess(ref, resolved); + if (access != null && access.isReferencedForWrite()) { + return true; + } + } + } + return false; } - @Override - public boolean isCurrentFileAlreadyChecked() { - return true; + private static void log(Object... info) { + //FileStatusMap.log(info); } - @Override - public boolean isLocallyUsed(@Nonnull PsiNamedElement member) { - return isReferenced(member); + private class GlobalUsageHelperBase extends GlobalUsageHelper { + @Override + public boolean shouldCheckUsages(PsiMember member) { + return false; + } + + @Override + public boolean isCurrentFileAlreadyChecked() { + return true; + } + + @Override + @RequiredReadAction + public boolean isLocallyUsed(PsiNamedElement member) { + return isReferenced(member); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java index 256eb30fe1..24b70d178f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java @@ -18,16 +18,14 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.Nls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Pavel.Dolgov @@ -40,28 +38,19 @@ public AddRequiredModuleFix(PsiJavaModule module, String requiredName) { myRequiredName = requiredName; } - @Nls - @Nonnull - @Override - public String getText() { - return JavaQuickFixBundle.message("module.info.add.requires.name", myRequiredName); - } - - @Nls - @Nonnull @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("module.info.add.requires.family.name"); + public LocalizeValue getText() { + return JavaQuickFixLocalize.moduleInfoAddRequiresName(myRequiredName); } @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public boolean isAvailable(Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { return PsiUtil.isLanguageLevel9OrHigher(file) && startElement instanceof PsiJavaModule && startElement.getManager().isInProject(startElement) && getLBrace((PsiJavaModule) startElement) != null; } @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nullable Editor editor, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public void invoke(Project project, PsiFile file, @Nullable Editor editor, PsiElement startElement, PsiElement endElement) { PsiJavaModule module = (PsiJavaModule) startElement; PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(project).getParserFacade(); @@ -81,13 +70,13 @@ public boolean startInWriteAction() { } @Nullable - private static PsiElement findAddingPlace(@Nonnull PsiJavaModule module) { + private static PsiElement findAddingPlace(PsiJavaModule module) { PsiElement addingPlace = ContainerUtil.iterateAndGetLastItem(module.getRequires()); return addingPlace != null ? addingPlace : getLBrace(module); } @Nullable - private static PsiElement getLBrace(@Nonnull PsiJavaModule module) { + private static PsiElement getLBrace(PsiJavaModule module) { PsiJavaModuleReferenceElement nameElement = module.getNameIdentifier(); for (PsiElement element = nameElement.getNextSibling(); element != null; element = element.getNextSibling()) { if (PsiUtil.isJavaToken(element, JavaTokenType.LBRACE)) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsConditionalFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsConditionalFix.java index 9e640562e8..90a73f03c9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsConditionalFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsConditionalFix.java @@ -19,135 +19,144 @@ import com.intellij.java.language.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.lang.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** - * User: anna - * Date: 2/17/12 + * @author anna + * @since 2012-02-17 */ public class AddTypeArgumentsConditionalFix implements SyntheticIntentionAction { - private static final Logger LOG = Logger.getInstance(AddTypeArgumentsConditionalFix.class); + private static final Logger LOG = Logger.getInstance(AddTypeArgumentsConditionalFix.class); - private final PsiSubstitutor mySubstitutor; - private final PsiMethodCallExpression myExpression; - private final PsiMethod myMethod; + private final PsiSubstitutor mySubstitutor; + private final PsiMethodCallExpression myExpression; + private final PsiMethod myMethod; - public AddTypeArgumentsConditionalFix(PsiSubstitutor substitutor, - PsiMethodCallExpression expression, - PsiMethod method) { - mySubstitutor = substitutor; - myExpression = expression; - myMethod = method; - } + public AddTypeArgumentsConditionalFix( + PsiSubstitutor substitutor, + PsiMethodCallExpression expression, + PsiMethod method + ) { + mySubstitutor = substitutor; + myExpression = expression; + myMethod = method; + } - @Nonnull - @Override - public String getText() { - return "Add explicit type arguments"; - } + @Override + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Add explicit type arguments"); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (mySubstitutor.isValid() && myExpression.isValid() && myMethod.isValid()) { - return true; + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return mySubstitutor.isValid() && myExpression.isValid() && myMethod.isValid(); } - return false; - } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - final PsiTypeParameter[] typeParameters = myMethod.getTypeParameters(); - final String typeArguments = "<" + StringUtil.join(typeParameters, parameter -> { - final PsiType substituteTypeParam = mySubstitutor.substitute(parameter); - LOG.assertTrue(substituteTypeParam != null); - return GenericsUtil.eliminateWildcards(substituteTypeParam).getCanonicalText(); - }, ", ") + ">"; - final PsiExpression expression = myExpression.getMethodExpression().getQualifierExpression(); - String withTypeArgsText; - if (expression != null) { - withTypeArgsText = expression.getText(); - } else { - if (isInStaticContext(myExpression, null) || myMethod.hasModifierProperty(PsiModifier.STATIC)) { - final PsiClass aClass = myMethod.getContainingClass(); - LOG.assertTrue(aClass != null); - withTypeArgsText = aClass.getQualifiedName(); - } else { - withTypeArgsText = "this"; - } + @Override + @RequiredUIAccess + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PsiTypeParameter[] typeParameters = myMethod.getTypeParameters(); + String typeArguments = "<" + StringUtil.join( + typeParameters, + parameter -> { + PsiType substituteTypeParam = mySubstitutor.substitute(parameter); + LOG.assertTrue(substituteTypeParam != null); + return GenericsUtil.eliminateWildcards(substituteTypeParam).getCanonicalText(); + }, + ", " + ) + ">"; + final PsiExpression expression = myExpression.getMethodExpression().getQualifierExpression(); + String withTypeArgsText; + if (expression != null) { + withTypeArgsText = expression.getText(); + } + else if (isInStaticContext(myExpression, null) || myMethod.isStatic()) { + final PsiClass aClass = myMethod.getContainingClass(); + LOG.assertTrue(aClass != null); + withTypeArgsText = aClass.getQualifiedName(); + } + else { + withTypeArgsText = "this"; + } + withTypeArgsText += "." + typeArguments + myExpression.getMethodExpression().getReferenceName(); + final PsiExpression withTypeArgs = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText(withTypeArgsText + myExpression.getArgumentList().getText(), myExpression); + myExpression.replace(withTypeArgs); } - withTypeArgsText += "." + typeArguments + myExpression.getMethodExpression().getReferenceName(); - final PsiExpression withTypeArgs = JavaPsiFacade.getElementFactory(project).createExpressionFromText - (withTypeArgsText + myExpression.getArgumentList().getText(), myExpression); - myExpression.replace(withTypeArgs); - } - public static boolean isInStaticContext(PsiElement element, @Nullable final PsiClass aClass) { - return PsiUtil.getEnclosingStaticElement(element, aClass) != null; - } + public static boolean isInStaticContext(PsiElement element, @Nullable final PsiClass aClass) { + return PsiUtil.getEnclosingStaticElement(element, aClass) != null; + } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean startInWriteAction() { + return true; + } - public static void register(HighlightInfo highlightInfo, PsiExpression expression, @Nonnull PsiType lType) { - if (lType != PsiType.NULL && expression instanceof PsiConditionalExpression) { - final PsiExpression thenExpression = ((PsiConditionalExpression) expression).getThenExpression(); - final PsiExpression elseExpression = ((PsiConditionalExpression) expression).getElseExpression(); - if (thenExpression != null && elseExpression != null) { - final PsiType thenType = thenExpression.getType(); - final PsiType elseType = elseExpression.getType(); - if (thenType != null && elseType != null) { - final boolean thenAssignable = TypeConversionUtil.isAssignable(lType, thenType); - final boolean elseAssignable = TypeConversionUtil.isAssignable(lType, elseType); - if (!thenAssignable && thenExpression instanceof PsiMethodCallExpression) { - inferTypeArgs(highlightInfo, lType, thenExpression); - } - if (!elseAssignable && elseExpression instanceof PsiMethodCallExpression) { - inferTypeArgs(highlightInfo, lType, elseExpression); - } + @RequiredReadAction + public static void register(HighlightInfo.Builder hlBuilder, PsiExpression expression, PsiType lType) { + if (lType != PsiType.NULL && expression instanceof PsiConditionalExpression condExpr) { + PsiExpression thenExpression = condExpr.getThenExpression(); + PsiExpression elseExpression = condExpr.getElseExpression(); + if (thenExpression != null && elseExpression != null) { + PsiType thenType = thenExpression.getType(); + PsiType elseType = elseExpression.getType(); + if (thenType != null && elseType != null) { + boolean thenAssignable = TypeConversionUtil.isAssignable(lType, thenType); + boolean elseAssignable = TypeConversionUtil.isAssignable(lType, elseType); + if (!thenAssignable && thenExpression instanceof PsiMethodCallExpression) { + inferTypeArgs(hlBuilder, lType, thenExpression); + } + if (!elseAssignable && elseExpression instanceof PsiMethodCallExpression) { + inferTypeArgs(hlBuilder, lType, elseExpression); + } + } + } } - } } - } - private static void inferTypeArgs(HighlightInfo highlightInfo, PsiType lType, PsiExpression thenExpression) { - final JavaResolveResult result = ((PsiMethodCallExpression) thenExpression).resolveMethodGenerics(); - final PsiMethod method = (PsiMethod) result.getElement(); - if (method != null) { - final PsiType returnType = method.getReturnType(); - final PsiClass aClass = method.getContainingClass(); - if (returnType != null && aClass != null && aClass.getQualifiedName() != null) { - final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(method.getProject()); - final PsiDeclarationStatement variableDeclarationStatement = javaPsiFacade.getElementFactory() - .createVariableDeclarationStatement("xxx", lType, thenExpression); - final PsiExpression initializer = ((PsiLocalVariable) variableDeclarationStatement.getDeclaredElements - ()[0]).getInitializer(); - LOG.assertTrue(initializer != null); + @RequiredReadAction + private static void inferTypeArgs(HighlightInfo.Builder highlightInfo, PsiType lType, PsiExpression thenExpression) { + PsiMethodCallExpression thenMethodCall = (PsiMethodCallExpression) thenExpression; + JavaResolveResult result = thenMethodCall.resolveMethodGenerics(); + PsiMethod method = (PsiMethod) result.getElement(); + if (method != null) { + PsiType returnType = method.getReturnType(); + PsiClass aClass = method.getContainingClass(); + if (returnType != null && aClass != null && aClass.getQualifiedName() != null) { + JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(method.getProject()); + PsiDeclarationStatement variableDeclarationStatement = javaPsiFacade.getElementFactory() + .createVariableDeclarationStatement("xxx", lType, thenExpression); + PsiExpression initializer = ((PsiLocalVariable) variableDeclarationStatement.getDeclaredElements()[0]).getInitializer(); + LOG.assertTrue(initializer != null); - final PsiSubstitutor substitutor = javaPsiFacade.getResolveHelper().inferTypeArguments(method - .getTypeParameters(), method.getParameterList().getParameters(), - ((PsiMethodCallExpression) thenExpression).getArgumentList().getExpressions(), - PsiSubstitutor.EMPTY, initializer, DefaultParameterTypeInferencePolicy.INSTANCE); - PsiType substitutedType = substitutor.substitute(returnType); - if (substitutedType != null && TypeConversionUtil.isAssignable(lType, substitutedType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, thenExpression.getTextRange(), - new AddTypeArgumentsConditionalFix(substitutor, (PsiMethodCallExpression) thenExpression, - method)); + PsiSubstitutor substitutor = javaPsiFacade.getResolveHelper().inferTypeArguments( + method.getTypeParameters(), + method.getParameterList().getParameters(), + thenMethodCall.getArgumentList().getExpressions(), + PsiSubstitutor.EMPTY, + initializer, + DefaultParameterTypeInferencePolicy.INSTANCE + ); + PsiType substitutedType = substitutor.substitute(returnType); + if (substitutedType != null && TypeConversionUtil.isAssignable(lType, substitutedType)) { + highlightInfo.newFix(new AddTypeArgumentsConditionalFix(substitutor, thenMethodCall, method)) + .fixRange(thenExpression.getTextRange()) + .register(); + } + } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java index 2178dace02..a10da14bbd 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java @@ -19,12 +19,12 @@ import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; -import javax.annotation.Nonnull; public class AddTypeArgumentsFix extends MethodArgumentFix { private static final Logger LOG = Logger.getInstance(AddTypeArgumentsFix.class); @@ -34,13 +34,12 @@ private AddTypeArgumentsFix(PsiExpressionList list, int i, PsiType toType, final } @Override - @Nonnull - public String getText() { + public LocalizeValue getText() { if (myArgList.getExpressions().length == 1) { - return JavaQuickFixBundle.message("add.type.arguments.single.argument.text"); + return JavaQuickFixLocalize.addTypeArgumentsSingleArgumentText(); } - return JavaQuickFixBundle.message("add.type.arguments.text", myIndex + 1); + return JavaQuickFixLocalize.addTypeArgumentsText(myIndex + 1); } private static class MyFixerActionFactory extends ArgumentFixerActionFactory { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeCastFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeCastFix.java index 249c6f64e9..1026cf8bbb 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeCastFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/AddTypeCastFix.java @@ -24,26 +24,20 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.codeEditor.Editor; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import consulo.language.codeStyle.CodeStyleManager; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; -import consulo.codeEditor.Editor; -import consulo.project.Project; -import com.intellij.java.language.psi.JavaPsiFacade; -import com.intellij.java.language.psi.PsiConditionalExpression; import consulo.language.psi.PsiElement; -import com.intellij.java.language.psi.PsiElementFactory; -import com.intellij.java.language.psi.PsiExpression; import consulo.language.psi.PsiFile; -import com.intellij.java.language.psi.PsiType; -import com.intellij.java.language.psi.PsiTypeCastExpression; -import consulo.language.codeStyle.CodeStyleManager; -import com.intellij.java.language.psi.util.PsiUtil; -import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.language.util.IncorrectOperationException; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import org.jspecify.annotations.Nullable; public class AddTypeCastFix extends LocalQuickFixAndIntentionActionOnPsiElement { private final PsiType myType; @@ -54,31 +48,24 @@ public AddTypeCastFix(PsiType type, PsiExpression expression) { } @Override - @Nonnull - public String getText() { - return JavaQuickFixBundle.message("add.typecast.text", myType.getCanonicalText()); - } - - @Override - @Nonnull - public String getFamilyName() { - return JavaQuickFixBundle.message("add.typecast.family"); + public LocalizeValue getText() { + return JavaQuickFixLocalize.addTypecastText(myType.getCanonicalText()); } @Override - public boolean isAvailable(@Nonnull Project project, - @Nonnull PsiFile file, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { + public boolean isAvailable(Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement) { return myType.isValid() && startElement.isValid() && startElement.getManager().isInProject(startElement); } @Override - public void invoke(@Nonnull Project project, - @Nonnull PsiFile file, + public void invoke(Project project, + PsiFile file, @Nullable Editor editor, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { + PsiElement startElement, + PsiElement endElement) { if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; addTypeCast(project, (PsiExpression)startElement, myType); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ArgumentFixerActionFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ArgumentFixerActionFactory.java index 5d9765e4e9..51132946f6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ArgumentFixerActionFactory.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ArgumentFixerActionFactory.java @@ -17,97 +17,112 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.infos.CandidateInfo; +import consulo.annotation.access.RequiredWriteAction; import consulo.document.util.TextRange; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; import consulo.logging.Logger; import consulo.util.lang.Comparing; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; /** * @author ven */ public abstract class ArgumentFixerActionFactory { - private static final Logger LOG = Logger.getInstance(ArgumentFixerActionFactory.class); + private static final Logger LOG = Logger.getInstance(ArgumentFixerActionFactory.class); - @Nullable - protected abstract PsiExpression getModifiedArgument(PsiExpression expression, final PsiType toType) throws IncorrectOperationException; + @Nullable + protected abstract PsiExpression getModifiedArgument(PsiExpression expression, PsiType toType) throws IncorrectOperationException; - public void registerCastActions(CandidateInfo[] candidates, PsiCall call, HighlightInfo highlightInfo, final TextRange fixRange) { - if (candidates.length == 0) return; - List methodCandidates = new ArrayList(Arrays.asList(candidates)); - PsiExpressionList list = call.getArgumentList(); - PsiExpression[] expressions = list.getExpressions(); - if (expressions.length == 0) return; - // filter out not castable candidates - nextMethod: - for (int i = methodCandidates.size() - 1; i >= 0; i--) { - CandidateInfo candidate = methodCandidates.get(i); - PsiMethod method = (PsiMethod) candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - assert method != null; - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (expressions.length != parameters.length) { - methodCandidates.remove(i); - continue; - } - for (int j = 0; j < parameters.length; j++) { - PsiParameter parameter = parameters[j]; - PsiExpression expression = expressions[j]; - // check if we can cast to this method - PsiType exprType = expression.getType(); - PsiType parameterType = substitutor.substitute(parameter.getType()); - if (exprType == null - || parameterType == null - || !areTypesConvertible(exprType, parameterType, call)) { - methodCandidates.remove(i); - continue nextMethod; + @RequiredWriteAction + public void registerCastActions(CandidateInfo[] candidates, PsiCall call, HighlightInfo.Builder hlBuilder, TextRange fixRange) { + if (candidates.length == 0) { + return; + } + List methodCandidates = new ArrayList<>(Arrays.asList(candidates)); + PsiExpressionList list = call.getArgumentList(); + PsiExpression[] expressions = list.getExpressions(); + if (expressions.length == 0) { + return; + } + // filter out not castable candidates + nextMethod: + for (int i = methodCandidates.size() - 1; i >= 0; i--) { + CandidateInfo candidate = methodCandidates.get(i); + PsiMethod method = (PsiMethod)candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (expressions.length != parameters.length) { + methodCandidates.remove(i); + continue; + } + for (int j = 0; j < parameters.length; j++) { + PsiParameter parameter = parameters[j]; + PsiExpression expression = expressions[j]; + // check if we can cast to this method + PsiType exprType = expression.getType(); + PsiType parameterType = substitutor.substitute(parameter.getType()); + if (exprType == null + || parameterType == null + || !areTypesConvertible(exprType, parameterType, call)) { + methodCandidates.remove(i); + continue nextMethod; + } + } } - } - } - if (methodCandidates.isEmpty()) return; + if (methodCandidates.isEmpty()) { + return; + } - try { - for (int i = 0; i < expressions.length; i++) { - PsiExpression expression = expressions[i]; - PsiType exprType = expression.getType(); - Set suggestedCasts = new HashSet(); - // find to which type we can cast this param to get valid method call - for (CandidateInfo candidate : methodCandidates) { - PsiMethod method = (PsiMethod) candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - assert method != null; - PsiParameter[] parameters = method.getParameterList().getParameters(); - PsiType originalParameterType = parameters[i].getType(); - PsiType parameterType = substitutor.substitute(originalParameterType); - if (parameterType instanceof PsiWildcardType) continue; - if (!GenericsUtil.isFromExternalTypeLanguage(parameterType)) continue; - if (suggestedCasts.contains(parameterType.getCanonicalText())) continue; - // strict compare since even widening cast may help - if (Comparing.equal(exprType, parameterType)) continue; - PsiCall newCall = (PsiCall) call.copy(); - PsiExpression modifiedExpression = getModifiedArgument(expression, parameterType); - if (modifiedExpression == null) continue; - newCall.getArgumentList().getExpressions()[i].replace(modifiedExpression); - JavaResolveResult resolveResult = newCall.resolveMethodGenerics(); - if (resolveResult.getElement() != null && resolveResult.isValidResult()) { - suggestedCasts.add(parameterType.getCanonicalText()); - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, createFix(list, i, parameterType)); - } + try { + for (int i = 0; i < expressions.length; i++) { + PsiExpression expression = expressions[i]; + PsiType exprType = expression.getType(); + Set suggestedCasts = new HashSet<>(); + // find to which type we can cast this param to get valid method call + for (CandidateInfo candidate : methodCandidates) { + PsiMethod method = (PsiMethod)candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiType originalParameterType = parameters[i].getType(); + PsiType parameterType = substitutor.substitute(originalParameterType); + if (parameterType instanceof PsiWildcardType) { + continue; + } + if (!GenericsUtil.isFromExternalTypeLanguage(parameterType)) { + continue; + } + if (suggestedCasts.contains(parameterType.getCanonicalText())) { + continue; + } + // strict compare since even widening cast may help + if (Comparing.equal(exprType, parameterType)) { + continue; + } + PsiCall newCall = (PsiCall)call.copy(); + PsiExpression modifiedExpression = getModifiedArgument(expression, parameterType); + if (modifiedExpression == null) { + continue; + } + newCall.getArgumentList().getExpressions()[i].replace(modifiedExpression); + JavaResolveResult resolveResult = newCall.resolveMethodGenerics(); + if (resolveResult.getElement() != null && resolveResult.isValidResult()) { + suggestedCasts.add(parameterType.getCanonicalText()); + hlBuilder.newFix(createFix(list, i, parameterType)).fixRange(fixRange).register(); + } + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); } - } - } catch (IncorrectOperationException e) { - LOG.error(e); } - } - - public abstract boolean areTypesConvertible(final PsiType exprType, final PsiType parameterType, final PsiElement context); - public abstract MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType parameterType); + public abstract boolean areTypesConvertible(PsiType exprType, PsiType parameterType, PsiElement context); + public abstract MethodArgumentFix createFix(PsiExpressionList list, int i, PsiType parameterType); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/CastMethodArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/CastMethodArgumentFix.java index 49377b7aff..d84764f120 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/CastMethodArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/CastMethodArgumentFix.java @@ -23,17 +23,12 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; -import javax.annotation.Nonnull; - -import consulo.java.analysis.impl.JavaQuickFixBundle; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.JavaHighlightUtil; -import com.intellij.java.language.psi.PsiClassType; +import com.intellij.java.language.psi.*; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.psi.PsiElement; -import com.intellij.java.language.psi.PsiExpression; -import com.intellij.java.language.psi.PsiExpressionList; -import com.intellij.java.language.psi.PsiPrimitiveType; -import com.intellij.java.language.psi.PsiType; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; public class CastMethodArgumentFix extends MethodArgumentFix { private CastMethodArgumentFix(PsiExpressionList list, int i, PsiType toType, final ArgumentFixerActionFactory factory) { @@ -41,13 +36,12 @@ private CastMethodArgumentFix(PsiExpressionList list, int i, PsiType toType, fin } @Override - @Nonnull - public String getText() { + public LocalizeValue getText() { if (myArgList.getExpressions().length == 1) { - return JavaQuickFixBundle.message("cast.single.parameter.text", JavaHighlightUtil.formatType(myToType)); + return JavaQuickFixLocalize.castSingleParameterText(JavaHighlightUtil.formatType(myToType)); } - return JavaQuickFixBundle.message("cast.parameter.text", myIndex + 1, JavaHighlightUtil.formatType(myToType)); + return JavaQuickFixLocalize.castParameterText(myIndex + 1, JavaHighlightUtil.formatType(myToType)); } private static class MyFixerActionFactory extends ArgumentFixerActionFactory { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeNewOperatorTypeFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeNewOperatorTypeFix.java index 3683a62379..59b6b4228e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeNewOperatorTypeFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeNewOperatorTypeFix.java @@ -20,181 +20,212 @@ import com.intellij.java.language.psi.util.PsiExpressionTrimRenderer; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; import consulo.document.util.TextRange; import consulo.document.util.UnfairTextRange; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import consulo.ui.annotation.RequiredUIAccess; +import org.jspecify.annotations.Nullable; public class ChangeNewOperatorTypeFix implements SyntheticIntentionAction { - private final PsiType myType; - private final PsiNewExpression myExpression; - - private ChangeNewOperatorTypeFix(PsiType type, PsiNewExpression expression) { - myType = type; - myExpression = expression; - } + private final PsiType myType; + private final PsiNewExpression myExpression; - @Override - @Nonnull - public String getText() { - return JavaQuickFixBundle.message("change.new.operator.type.text", new PsiExpressionTrimRenderer.RenderFunction().apply(myExpression), myType.getPresentableText(), myType instanceof PsiArrayType ? "" : "()"); - } + private ChangeNewOperatorTypeFix(PsiType type, PsiNewExpression expression) { + myType = type; + myExpression = expression; + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return myType.isValid() - && myExpression.isValid() - && myExpression.getManager().isInProject(myExpression) - && !TypeConversionUtil.isPrimitiveAndNotNull(myType) - && (myType instanceof PsiArrayType || myExpression.getArgumentList() != null) - ; - } + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.changeNewOperatorTypeText( + new PsiExpressionTrimRenderer.RenderFunction().apply(myExpression), + myType.getPresentableText(), + myType instanceof PsiArrayType ? "" : "()" + ); + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - changeNewOperatorType(myExpression, myType, editor); - } + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return myType.isValid() + && myExpression.isValid() + && myExpression.getManager().isInProject(myExpression) + && !TypeConversionUtil.isPrimitiveAndNotNull(myType) + && (myType instanceof PsiArrayType || myExpression.getArgumentList() != null); + } - private static void changeNewOperatorType(PsiNewExpression originalExpression, PsiType toType, final Editor editor) throws IncorrectOperationException { - PsiNewExpression newExpression; - PsiElementFactory factory = JavaPsiFacade.getInstance(originalExpression.getProject()).getElementFactory(); - int caretOffset; - TextRange selection; - if (toType instanceof PsiArrayType) { - final PsiExpression[] originalExpressionArrayDimensions = originalExpression.getArrayDimensions(); - caretOffset = 0; - @NonNls String text = "new " + toType.getDeepComponentType().getCanonicalText() + "["; - if (originalExpressionArrayDimensions.length > 0) { - text += originalExpressionArrayDimensions[0].getText(); - } else { - text += "0"; - caretOffset = -2; - } - text += "]"; - for (int i = 1; i < toType.getArrayDimensions(); i++) { - text += "["; - String arrayDimension = ""; - if (originalExpressionArrayDimensions.length > i) { - arrayDimension = originalExpressionArrayDimensions[i].getText(); - text += arrayDimension; - } - text += "]"; - if (caretOffset < 0) { - caretOffset -= arrayDimension.length() + 2; + @Override + @RequiredUIAccess + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; } - } + changeNewOperatorType(myExpression, myType, editor); + } + + @RequiredUIAccess + private static void changeNewOperatorType(PsiNewExpression originalExpression, PsiType toType, Editor editor) + throws IncorrectOperationException { + PsiNewExpression newExpression; + PsiElementFactory factory = JavaPsiFacade.getInstance(originalExpression.getProject()).getElementFactory(); + int caretOffset; + TextRange selection; + if (toType instanceof PsiArrayType) { + final PsiExpression[] originalExpressionArrayDimensions = originalExpression.getArrayDimensions(); + caretOffset = 0; + String text = "new " + toType.getDeepComponentType().getCanonicalText() + "["; + if (originalExpressionArrayDimensions.length > 0) { + text += originalExpressionArrayDimensions[0].getText(); + } + else { + text += "0"; + caretOffset = -2; + } + text += "]"; + for (int i = 1; i < toType.getArrayDimensions(); i++) { + text += "["; + String arrayDimension = ""; + if (originalExpressionArrayDimensions.length > i) { + arrayDimension = originalExpressionArrayDimensions[i].getText(); + text += arrayDimension; + } + text += "]"; + if (caretOffset < 0) { + caretOffset -= arrayDimension.length() + 2; + } + } - newExpression = (PsiNewExpression) factory.createExpressionFromText(text, originalExpression); - if (caretOffset < 0) { - selection = new UnfairTextRange(caretOffset, caretOffset + 1); - } else { - selection = null; - } - } else { - final PsiAnonymousClass anonymousClass = originalExpression.getAnonymousClass(); - newExpression = (PsiNewExpression) factory.createExpressionFromText("new " + toType.getCanonicalText() + "()" + (anonymousClass != null ? "{}" : ""), originalExpression); - PsiExpressionList argumentList = originalExpression.getArgumentList(); - if (argumentList == null) return; - newExpression.getArgumentList().replace(argumentList); - if (anonymousClass == null) { //just to prevent useless inference - if (PsiDiamondTypeUtil.canCollapseToDiamond(newExpression, originalExpression, toType)) { - final PsiElement paramList = PsiDiamondTypeUtil.replaceExplicitWithDiamond(newExpression.getClassOrAnonymousClassReference().getParameterList()); - newExpression = PsiTreeUtil.getParentOfType(paramList, PsiNewExpression.class); + newExpression = (PsiNewExpression)factory.createExpressionFromText(text, originalExpression); + if (caretOffset < 0) { + selection = new UnfairTextRange(caretOffset, caretOffset + 1); + } + else { + selection = null; + } } - } + else { + PsiAnonymousClass anonymousClass = originalExpression.getAnonymousClass(); + newExpression = (PsiNewExpression)factory.createExpressionFromText( + "new " + toType.getCanonicalText() + "()" + (anonymousClass != null ? "{}" : ""), + originalExpression + ); + PsiExpressionList argumentList = originalExpression.getArgumentList(); + if (argumentList == null) { + return; + } + newExpression.getArgumentList().replace(argumentList); + if (anonymousClass == null) { //just to prevent useless inference + if (PsiDiamondTypeUtil.canCollapseToDiamond(newExpression, originalExpression, toType)) { + PsiElement paramList = + PsiDiamondTypeUtil.replaceExplicitWithDiamond(newExpression.getClassOrAnonymousClassReference().getParameterList()); + newExpression = PsiTreeUtil.getParentOfType(paramList, PsiNewExpression.class); + } + } - if (anonymousClass != null) { - PsiAnonymousClass newAnonymousClass = newExpression.getAnonymousClass(); - final PsiElement childInside = anonymousClass.getLBrace().getNextSibling(); - if (childInside != null) { - newAnonymousClass.addRange(childInside, anonymousClass.getRBrace().getPrevSibling()); + if (anonymousClass != null) { + PsiAnonymousClass newAnonymousClass = newExpression.getAnonymousClass(); + PsiElement childInside = anonymousClass.getLBrace().getNextSibling(); + if (childInside != null) { + newAnonymousClass.addRange(childInside, anonymousClass.getRBrace().getPrevSibling()); + } + } + selection = null; + caretOffset = -1; + } + PsiElement element = originalExpression.replace(newExpression); + editor.getCaretModel().moveToOffset(element.getTextRange().getEndOffset() + caretOffset); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + if (selection != null) { + selection = selection.shiftRight(element.getTextRange().getEndOffset()); + editor.getSelectionModel().setSelection(selection.getStartOffset(), selection.getEndOffset()); } - } - selection = null; - caretOffset = -1; - } - PsiElement element = originalExpression.replace(newExpression); - editor.getCaretModel().moveToOffset(element.getTextRange().getEndOffset() + caretOffset); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - if (selection != null) { - selection = selection.shiftRight(element.getTextRange().getEndOffset()); - editor.getSelectionModel().setSelection(selection.getStartOffset(), selection.getEndOffset()); } - } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean startInWriteAction() { + return true; + } - public static void register(final HighlightInfo highlightInfo, PsiExpression expression, final PsiType lType) { - expression = PsiUtil.deparenthesizeExpression(expression); - if (!(expression instanceof PsiNewExpression)) return; - final PsiType rType = expression.getType(); - PsiType newType = lType; - if (rType instanceof PsiClassType && newType instanceof PsiClassType) { - final PsiClassType.ClassResolveResult rResolveResult = ((PsiClassType) rType).resolveGenerics(); - PsiClass rClass = rResolveResult.getElement(); - if (rClass instanceof PsiAnonymousClass) { - rClass = ((PsiAnonymousClass) rClass).getBaseClassType().resolve(); - } - if (rClass != null) { - final PsiClassType.ClassResolveResult lResolveResult = ((PsiClassType) newType).resolveGenerics(); - final PsiClass lClass = lResolveResult.getElement(); - if (lClass != null) { - PsiSubstitutor substitutor = getInheritorSubstitutorForNewExpression(lClass, rClass, lResolveResult.getSubstitutor(), expression); - if (substitutor != null) { - newType = JavaPsiFacade.getInstance(lClass.getProject()).getElementFactory().createType(rClass, substitutor); - } + @RequiredReadAction + public static void register(HighlightInfo.Builder highlightInfo, PsiExpression expression, PsiType lType) { + if (!(PsiUtil.deparenthesizeExpression(expression) instanceof PsiNewExpression newExpr)) { + return; + } + PsiType newType = lType; + if (newExpr.getType() instanceof PsiClassType rClassType && newType instanceof PsiClassType lClassType) { + PsiClassType.ClassResolveResult rResolveResult = rClassType.resolveGenerics(); + PsiClass rClass = rResolveResult.getElement(); + if (rClass instanceof PsiAnonymousClass anonymousClass) { + rClass = anonymousClass.getBaseClassType().resolve(); + } + if (rClass != null) { + PsiClassType.ClassResolveResult lResolveResult = lClassType.resolveGenerics(); + PsiClass lClass = lResolveResult.getElement(); + if (lClass != null) { + PsiSubstitutor substitutor = + getInheritorSubstitutorForNewExpression(lClass, rClass, lResolveResult.getSubstitutor(), newExpr); + if (substitutor != null) { + newType = JavaPsiFacade.getInstance(lClass.getProject()).getElementFactory().createType(rClass, substitutor); + } + } + } } - } + highlightInfo.registerFix(new ChangeNewOperatorTypeFix(newType, newExpr)); } - PsiNewExpression newExpression = (PsiNewExpression) expression; - QuickFixAction.registerQuickFixAction(highlightInfo, new ChangeNewOperatorTypeFix(newType, newExpression)); - } - /* Guesswork - */ - @Nullable - private static PsiSubstitutor getInheritorSubstitutorForNewExpression(final PsiClass baseClass, final PsiClass inheritor, - final PsiSubstitutor baseSubstitutor, final PsiElement context) { - final Project project = baseClass.getProject(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - final PsiResolveHelper resolveHelper = facade.getResolveHelper(); - PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, inheritor, PsiSubstitutor.EMPTY); - if (superSubstitutor == null) return null; - PsiSubstitutor inheritorSubstitutor = PsiSubstitutor.EMPTY; - for (PsiTypeParameter inheritorParameter : PsiUtil.typeParametersIterable(inheritor)) { - for (PsiTypeParameter baseParameter : PsiUtil.typeParametersIterable(baseClass)) { - final PsiType substituted = superSubstitutor.substitute(baseParameter); - PsiType arg = baseSubstitutor.substitute(baseParameter); - if (arg instanceof PsiWildcardType) arg = ((PsiWildcardType) arg).getBound(); - PsiType substitution = - resolveHelper.getSubstitutionForTypeParameter(inheritorParameter, substituted, arg, true, PsiUtil.getLanguageLevel(context)); - if (PsiType.NULL.equals(substitution)) continue; - if (substitution == null) { - return facade.getElementFactory().createRawSubstitutor(inheritor); + /* Guesswork */ + @Nullable + @RequiredReadAction + private static PsiSubstitutor getInheritorSubstitutorForNewExpression( + PsiClass baseClass, + PsiClass inheritor, + PsiSubstitutor baseSubstitutor, + PsiElement context + ) { + Project project = baseClass.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiResolveHelper resolveHelper = facade.getResolveHelper(); + PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, inheritor, PsiSubstitutor.EMPTY); + if (superSubstitutor == null) { + return null; + } + PsiSubstitutor inheritorSubstitutor = PsiSubstitutor.EMPTY; + for (PsiTypeParameter inheritorParameter : PsiUtil.typeParametersIterable(inheritor)) { + for (PsiTypeParameter baseParameter : PsiUtil.typeParametersIterable(baseClass)) { + PsiType substituted = superSubstitutor.substitute(baseParameter); + PsiType arg = baseSubstitutor.substitute(baseParameter); + if (arg instanceof PsiWildcardType wildcardType) { + arg = wildcardType.getBound(); + } + PsiType substitution = resolveHelper.getSubstitutionForTypeParameter( + inheritorParameter, + substituted, + arg, + true, + PsiUtil.getLanguageLevel(context) + ); + if (PsiType.NULL.equals(substitution)) { + continue; + } + if (substitution == null) { + return facade.getElementFactory().createRawSubstitutor(inheritor); + } + inheritorSubstitutor = inheritorSubstitutor.put(inheritorParameter, substitution); + break; + } } - inheritorSubstitutor = inheritorSubstitutor.put(inheritorParameter, substitution); - break; - } - } - return inheritorSubstitutor; - } + return inheritorSubstitutor; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeStringLiteralToCharInMethodCallFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeStringLiteralToCharInMethodCallFix.java index f3bbf85369..5ad6758ae8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeStringLiteralToCharInMethodCallFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeStringLiteralToCharInMethodCallFix.java @@ -18,155 +18,173 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.infos.CandidateInfo; import com.intellij.java.language.psi.infos.MethodCandidateInfo; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; public class ChangeStringLiteralToCharInMethodCallFix implements SyntheticIntentionAction { - private final PsiLiteralExpression myLiteral; - private final PsiCall myCall; - - public ChangeStringLiteralToCharInMethodCallFix(final PsiLiteralExpression literal, final PsiCall methodCall) { - myLiteral = literal; - myCall = methodCall; - } - - @Override - @Nonnull - public String getText() { - final String convertedValue = convertedValue(); - final boolean isString = isString(myLiteral.getType()); - return JavaQuickFixBundle.message("fix.single.character.string.to.char.literal.text", myLiteral.getText(), - quote(convertedValue, !isString), isString ? PsiType.CHAR.getCanonicalText() : "String"); - } - - @Override - public boolean isAvailable(@Nonnull final Project project, final Editor editor, final PsiFile file) { - return myCall.isValid() && myLiteral.isValid() && myCall.getManager().isInProject(myCall); - } - - @Override - public void invoke(@Nonnull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - - final Object value = myLiteral.getValue(); - if (value != null && value.toString().length() == 1) { - final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - - final PsiExpression newExpression = factory.createExpressionFromText(quote(convertedValue(), !isString(myLiteral.getType())), - myLiteral.getParent()); - myLiteral.replace(newExpression); + private final PsiLiteralExpression myLiteral; + private final PsiCall myCall; + + public ChangeStringLiteralToCharInMethodCallFix(PsiLiteralExpression literal, final PsiCall methodCall) { + myLiteral = literal; + myCall = methodCall; } - } - - @Override - public boolean startInWriteAction() { - return true; - } - - private static String quote(final String value, final boolean doubleQuotes) { - final char quote = doubleQuotes ? '"' : '\''; - return quote + value + quote; - } - - private String convertedValue() { - String value = String.valueOf(myLiteral.getValue()); - final StringBuilder builder = new StringBuilder(); - StringUtil.escapeStringCharacters(value.length(), value, "\"'", builder); - return builder.toString(); - } - - public static void registerFixes(@Nonnull final PsiMethod[] candidates, @Nonnull final PsiConstructorCall call, - @Nonnull final HighlightInfo out) { - final Set literals = new HashSet(); - if (call.getArgumentList() == null) { - return; + + @Override + @RequiredReadAction + public LocalizeValue getText() { + String convertedValue = convertedValue(); + boolean isString = isString(myLiteral.getType()); + return JavaQuickFixLocalize.fixSingleCharacterStringToCharLiteralText( + myLiteral.getText(), + quote(convertedValue, !isString), + isString ? PsiType.CHAR.getCanonicalText() : "String" + ); } - boolean exactMatch = false; - for (PsiMethod method : candidates) { - exactMatch |= findMatchingExpressions(call.getArgumentList().getExpressions(), method, literals); + + @Override + public boolean isAvailable(final Project project, final Editor editor, final PsiFile file) { + return myCall.isValid() && myLiteral.isValid() && myCall.getManager().isInProject(myCall); } - if (!exactMatch) { - processLiterals(literals, call, out); + + @Override + public void invoke(final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } + + final Object value = myLiteral.getValue(); + if (value != null && value.toString().length() == 1) { + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + + final PsiExpression newExpression = factory.createExpressionFromText( + quote(convertedValue(), !isString(myLiteral.getType())), + myLiteral.getParent() + ); + myLiteral.replace(newExpression); + } } - } - - public static void registerFixes(@Nonnull final CandidateInfo[] candidates, - @Nonnull final PsiMethodCallExpression methodCall, - @Nullable final HighlightInfo info) { - if (info == null) return; - final Set literals = new HashSet(); - boolean exactMatch = false; - for (CandidateInfo candidate : candidates) { - if (candidate instanceof MethodCandidateInfo) { - final PsiMethod method = ((MethodCandidateInfo) candidate).getElement(); - exactMatch |= findMatchingExpressions(methodCall.getArgumentList().getExpressions(), method, literals); - } + + @Override + public boolean startInWriteAction() { + return true; } - if (!exactMatch) { - processLiterals(literals, methodCall, info); + + private static String quote(final String value, final boolean doubleQuotes) { + final char quote = doubleQuotes ? '"' : '\''; + return quote + value + quote; } - } - - private static void processLiterals(@Nonnull final Set literals, - @Nonnull final PsiCall call, - @Nonnull final HighlightInfo info) { - for (PsiLiteralExpression literal : literals) { - final ChangeStringLiteralToCharInMethodCallFix fix = new ChangeStringLiteralToCharInMethodCallFix(literal, call); - QuickFixAction.registerQuickFixAction(info, fix); + + private String convertedValue() { + String value = String.valueOf(myLiteral.getValue()); + final StringBuilder builder = new StringBuilder(); + StringUtil.escapeStringCharacters(value.length(), value, "\"'", builder); + return builder.toString(); } - } - - /** - * @return true if exact TYPEs match - */ - private static boolean findMatchingExpressions(final PsiExpression[] arguments, final PsiMethod existingMethod, - final Set result) { - final PsiParameterList parameterList = existingMethod.getParameterList(); - final PsiParameter[] parameters = parameterList.getParameters(); - - if (arguments.length != parameters.length) { - return false; + + public static void registerFixes( + PsiMethod[] candidates, + PsiConstructorCall call, + HighlightInfo.Builder hlBuilder + ) { + Set literals = new HashSet<>(); + if (call.getArgumentList() == null) { + return; + } + boolean exactMatch = false; + for (PsiMethod method : candidates) { + exactMatch |= findMatchingExpressions(call.getArgumentList().getExpressions(), method, literals); + } + if (!exactMatch) { + processLiterals(literals, call, hlBuilder); + } } - boolean typeMatch = true; - for (int i = 0; i < parameters.length && i < arguments.length; i++) { - final PsiParameter parameter = parameters[i]; - final PsiType parameterType = parameter.getType(); - final PsiType argumentType = arguments[i].getType(); + public static void registerFixes( + CandidateInfo[] candidates, + PsiMethodCallExpression methodCall, + HighlightInfo.@Nullable Builder hlBuilder + ) { + if (hlBuilder == null) { + return; + } + Set literals = new HashSet<>(); + boolean exactMatch = false; + for (CandidateInfo candidate : candidates) { + if (candidate instanceof MethodCandidateInfo methodCandidateInfo) { + PsiMethod method = methodCandidateInfo.getElement(); + exactMatch |= findMatchingExpressions(methodCall.getArgumentList().getExpressions(), method, literals); + } + } + if (!exactMatch) { + processLiterals(literals, methodCall, hlBuilder); + } + } - typeMatch &= Comparing.equal(parameterType, argumentType); + private static void processLiterals( + Set literals, + PsiCall call, + HighlightInfo.Builder hlBuilder + ) { + for (PsiLiteralExpression literal : literals) { + hlBuilder.registerFix(new ChangeStringLiteralToCharInMethodCallFix(literal, call)); + } + } - if (arguments[i] instanceof PsiLiteralExpression && !result.contains(arguments[i]) && - (charToString(parameterType, argumentType) || charToString(argumentType, parameterType))) { + /** + * @return true if exact TYPEs match + */ + private static boolean findMatchingExpressions( + PsiExpression[] arguments, + PsiMethod existingMethod, + Set result + ) { + PsiParameterList parameterList = existingMethod.getParameterList(); + PsiParameter[] parameters = parameterList.getParameters(); + + if (arguments.length != parameters.length) { + return false; + } + + boolean typeMatch = true; + for (int i = 0; i < parameters.length && i < arguments.length; i++) { + PsiParameter parameter = parameters[i]; + PsiType parameterType = parameter.getType(); + PsiType argumentType = arguments[i].getType(); + + typeMatch &= Comparing.equal(parameterType, argumentType); - final String value = String.valueOf(((PsiLiteralExpression) arguments[i]).getValue()); - if (value != null && value.length() == 1) { - result.add((PsiLiteralExpression) arguments[i]); + if (arguments[i] instanceof PsiLiteralExpression && !result.contains(arguments[i]) + && (charToString(parameterType, argumentType) || charToString(argumentType, parameterType))) { + + String value = String.valueOf(((PsiLiteralExpression)arguments[i]).getValue()); + if (value != null && value.length() == 1) { + result.add((PsiLiteralExpression)arguments[i]); + } + } } - } + return typeMatch; } - return typeMatch; - } - private static boolean charToString(final PsiType firstType, final PsiType secondType) { - return Comparing.equal(PsiType.CHAR, firstType) && isString(secondType); - } + private static boolean charToString(PsiType firstType, PsiType secondType) { + return Comparing.equal(PsiType.CHAR, firstType) && isString(secondType); + } - private static boolean isString(final PsiType type) { - return type != null && "java.lang.String".equals(type.getCanonicalText()); - } + private static boolean isString(PsiType type) { + return type != null && CommonClassNames.JAVA_LANG_STRING.equals(type.getCanonicalText()); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeToAppendFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeToAppendFix.java index dd3b588ac4..87863921f2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeToAppendFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeToAppendFix.java @@ -19,138 +19,143 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.ast.IElementType; import consulo.language.editor.FileModificationService; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiManager; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Bas Leijdekkers */ public class ChangeToAppendFix implements SyntheticIntentionAction { - private final IElementType myTokenType; - private final PsiType myLhsType; - private final PsiAssignmentExpression myAssignmentExpression; - - public ChangeToAppendFix(IElementType eqOpSign, PsiType lType, PsiAssignmentExpression assignmentExpression) { - myTokenType = eqOpSign; - myLhsType = lType; - myAssignmentExpression = assignmentExpression; - } + private final IElementType myTokenType; + private final PsiType myLhsType; + private final PsiAssignmentExpression myAssignmentExpression; - @Nonnull - @Override - public String getText() { - return JavaQuickFixBundle.message("change.to.append.text", - buildAppendExpression(myAssignmentExpression.getRExpression(), - myLhsType.equalsToText("java.lang.Appendable"), - new StringBuilder(myAssignmentExpression.getLExpression().getText()))); - } + public ChangeToAppendFix(IElementType eqOpSign, PsiType lType, PsiAssignmentExpression assignmentExpression) { + myTokenType = eqOpSign; + myLhsType = lType; + myAssignmentExpression = assignmentExpression; + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return JavaTokenType.PLUSEQ == myTokenType && - myAssignmentExpression.isValid() && - PsiManager.getInstance(project).isInProject(myAssignmentExpression) && - (myLhsType.equalsToText(JavaClassNames.JAVA_LANG_STRING_BUILDER) || - myLhsType.equalsToText(JavaClassNames.JAVA_LANG_STRING_BUFFER) || - myLhsType.equalsToText("java.lang.Appendable")); - } + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.changeToAppendText(buildAppendExpression(myAssignmentExpression.getRExpression(), + myLhsType.equalsToText("java.lang.Appendable"), + new StringBuilder(myAssignmentExpression.getLExpression().getText()))); + } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return JavaTokenType.PLUSEQ == myTokenType && + myAssignmentExpression.isValid() && + PsiManager.getInstance(project).isInProject(myAssignmentExpression) && + (myLhsType.equalsToText(CommonClassNames.JAVA_LANG_STRING_BUILDER) || + myLhsType.equalsToText(CommonClassNames.JAVA_LANG_STRING_BUFFER) || + myLhsType.equalsToText("java.lang.Appendable")); + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - final PsiExpression appendExpression = - buildAppendExpression(myAssignmentExpression.getLExpression(), myAssignmentExpression.getRExpression()); - if (appendExpression == null) return; - myAssignmentExpression.replace(appendExpression); - } + @Override + public boolean startInWriteAction() { + return true; + } - @Nullable - public static PsiExpression buildAppendExpression(PsiExpression appendable, PsiExpression concatenation) { - if (concatenation == null) return null; - final PsiType type = appendable.getType(); - if (type == null) return null; - final StringBuilder result = - buildAppendExpression(concatenation, type.equalsToText("java.lang.Appendable"), new StringBuilder(appendable.getText())); - if (result == null) return null; - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(appendable.getProject()); - return factory.createExpressionFromText(result.toString(), appendable); - } + @Override + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } + final PsiExpression appendExpression = + buildAppendExpression(myAssignmentExpression.getLExpression(), myAssignmentExpression.getRExpression()); + if (appendExpression == null) { + return; + } + myAssignmentExpression.replace(appendExpression); + } - @Nullable - private static StringBuilder buildAppendExpression(@Nullable PsiExpression concatenation, boolean useStringValueOf, @NonNls StringBuilder out) - throws IncorrectOperationException { - final PsiType type = concatenation == null ? null : concatenation.getType(); - if (type == null) { - return null; + @Nullable + public static PsiExpression buildAppendExpression(PsiExpression appendable, PsiExpression concatenation) { + if (concatenation == null) { + return null; + } + final PsiType type = appendable.getType(); + if (type == null) { + return null; + } + final StringBuilder result = + buildAppendExpression(concatenation, type.equalsToText("java.lang.Appendable"), new StringBuilder(appendable.getText())); + if (result == null) { + return null; + } + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(appendable.getProject()); + return factory.createExpressionFromText(result.toString(), appendable); } - if (concatenation instanceof PsiPolyadicExpression && type.equalsToText(JavaClassNames.JAVA_LANG_STRING)) { - PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)concatenation; - final PsiExpression[] operands = polyadicExpression.getOperands(); - boolean isConstant = true; - boolean isString = false; - final StringBuilder builder = new StringBuilder(); - for (PsiExpression operand : operands) { - if (isConstant && PsiUtil.isConstantExpression(operand)) { - if (builder.length() != 0) { - builder.append('+'); - } - final PsiType operandType = operand.getType(); - if (operandType != null && operandType.equalsToText(JavaClassNames.JAVA_LANG_STRING)) { - isString = true; - } - builder.append(operand.getText()); + + @Nullable + private static StringBuilder buildAppendExpression(@Nullable PsiExpression concatenation, boolean useStringValueOf, StringBuilder out) + throws IncorrectOperationException { + final PsiType type = concatenation == null ? null : concatenation.getType(); + if (type == null) { + return null; + } + if (concatenation instanceof PsiPolyadicExpression && type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) concatenation; + final PsiExpression[] operands = polyadicExpression.getOperands(); + boolean isConstant = true; + boolean isString = false; + final StringBuilder builder = new StringBuilder(); + for (PsiExpression operand : operands) { + if (isConstant && PsiUtil.isConstantExpression(operand)) { + if (builder.length() != 0) { + builder.append('+'); + } + final PsiType operandType = operand.getType(); + if (operandType != null && operandType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + isString = true; + } + builder.append(operand.getText()); + } + else { + isConstant = false; + if (builder.length() != 0) { + append(builder, useStringValueOf && !isString, out); + builder.setLength(0); + } + buildAppendExpression(operand, useStringValueOf, out); + } + } + if (builder.length() != 0) { + append(builder, false, out); + } + } + else if (concatenation instanceof PsiParenthesizedExpression) { + final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) concatenation; + final PsiExpression expression = parenthesizedExpression.getExpression(); + if (expression != null) { + return buildAppendExpression(expression, useStringValueOf, out); + } } else { - isConstant = false; - if (builder.length() != 0) { - append(builder, useStringValueOf && !isString, out); - builder.setLength(0); - } - buildAppendExpression(operand, useStringValueOf, out); + append(concatenation.getText(), useStringValueOf && !type.equalsToText(CommonClassNames.JAVA_LANG_STRING), out); } - } - if (builder.length() != 0) { - append(builder, false, out); - } - } - else if (concatenation instanceof PsiParenthesizedExpression) { - final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression)concatenation; - final PsiExpression expression = parenthesizedExpression.getExpression(); - if (expression != null) { - return buildAppendExpression(expression, useStringValueOf, out); - } + return out; } - else { - append(concatenation.getText(), useStringValueOf && !type.equalsToText(JavaClassNames.JAVA_LANG_STRING), out); - } - return out; - } - private static void append(CharSequence text, boolean useStringValueOf, StringBuilder out) { - out.append(".append("); - if (useStringValueOf) { - out.append("String.valueOf(").append(text).append(')'); - } - else { - out.append(text); + private static void append(CharSequence text, boolean useStringValueOf, StringBuilder out) { + out.append(".append("); + if (useStringValueOf) { + out.append("String.valueOf(").append(text).append(')'); + } + else { + out.append(text); + } + out.append(')'); } - out.append(')'); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeTypeArgumentsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeTypeArgumentsFix.java index 6eba036b72..9a690c4648 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeTypeArgumentsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ChangeTypeArgumentsFix.java @@ -27,138 +27,157 @@ import com.intellij.java.language.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.codeEditor.Editor; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.editor.FileModificationService; import consulo.language.editor.intention.HighPriorityAction; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.lang.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class ChangeTypeArgumentsFix implements SyntheticIntentionAction, HighPriorityAction { - private final PsiMethod myTargetMethod; - private final PsiClass myPsiClass; - private final PsiExpression[] myExpressions; - private static final Logger LOG = Logger.getInstance(ChangeTypeArgumentsFix.class); - private final PsiNewExpression myNewExpression; + private final PsiMethod myTargetMethod; + private final PsiClass myPsiClass; + private final PsiExpression[] myExpressions; + private static final Logger LOG = Logger.getInstance(ChangeTypeArgumentsFix.class); + private final PsiNewExpression myNewExpression; - ChangeTypeArgumentsFix(@Nonnull PsiMethod targetMethod, - PsiClass psiClass, - @Nonnull PsiExpression[] expressions, - @Nonnull PsiElement context) { - myTargetMethod = targetMethod; - myPsiClass = psiClass; - myExpressions = expressions; - myNewExpression = PsiTreeUtil.getParentOfType(context, PsiNewExpression.class); - } + ChangeTypeArgumentsFix( + PsiMethod targetMethod, + PsiClass psiClass, + PsiExpression[] expressions, + PsiElement context + ) { + myTargetMethod = targetMethod; + myPsiClass = psiClass; + myExpressions = expressions; + myNewExpression = PsiTreeUtil.getParentOfType(context, PsiNewExpression.class); + } - @Override - @Nonnull - public String getText() { - final PsiSubstitutor substitutor = inferTypeArguments(); - return "Change type arguments to <" + StringUtil.join(myPsiClass.getTypeParameters(), typeParameter -> { - final PsiType substituted = substitutor.substitute(typeParameter); - return substituted != null ? substituted.getPresentableText() : JavaClassNames.JAVA_LANG_OBJECT; - }, ", ") + ">"; - } + @Override + public LocalizeValue getText() { + PsiSubstitutor substitutor = inferTypeArguments(); + return LocalizeValue.localizeTODO("Change type arguments to <" + StringUtil.join( + myPsiClass.getTypeParameters(), + typeParameter -> { + PsiType substituted = substitutor.substitute(typeParameter); + return substituted != null ? substituted.getPresentableText() : CommonClassNames.JAVA_LANG_OBJECT; + }, + ", " + ) + ">"); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - final PsiTypeParameter[] typeParameters = myPsiClass.getTypeParameters(); - if (typeParameters.length > 0) { - if (myNewExpression != null && myNewExpression.isValid() && myNewExpression.getArgumentList() != null) { - final PsiJavaCodeReferenceElement reference = myNewExpression.getClassOrAnonymousClassReference(); - if (reference != null) { - final PsiReferenceParameterList parameterList = reference.getParameterList(); - if (parameterList != null) { - final PsiSubstitutor substitutor = inferTypeArguments(); - final PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters(); - if (parameters.length != myExpressions.length) return false; - for (int i = 0, length = parameters.length; i < length; i++) { - PsiParameter parameter = parameters[i]; - final PsiType expectedType = substitutor.substitute(parameter.getType()); - if (!myExpressions[i].isValid()) return false; - final PsiType actualType = myExpressions[i].getType(); - if (expectedType == null || actualType == null || !TypeConversionUtil.isAssignable(expectedType, actualType)) - return false; - } - for (PsiTypeParameter parameter : typeParameters) { - if (substitutor.substitute(parameter) == null) return false; + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + PsiTypeParameter[] typeParameters = myPsiClass.getTypeParameters(); + if (typeParameters.length > 0 + && myNewExpression != null && myNewExpression.isValid() && myNewExpression.getArgumentList() != null) { + PsiJavaCodeReferenceElement reference = myNewExpression.getClassOrAnonymousClassReference(); + if (reference != null) { + PsiReferenceParameterList parameterList = reference.getParameterList(); + if (parameterList != null) { + PsiSubstitutor substitutor = inferTypeArguments(); + PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters(); + if (parameters.length != myExpressions.length) { + return false; + } + for (int i = 0, length = parameters.length; i < length; i++) { + PsiParameter parameter = parameters[i]; + PsiType expectedType = substitutor.substitute(parameter.getType()); + if (!myExpressions[i].isValid()) { + return false; + } + PsiType actualType = myExpressions[i].getType(); + if (expectedType == null || actualType == null || !TypeConversionUtil.isAssignable(expectedType, actualType)) { + return false; + } + } + for (PsiTypeParameter parameter : typeParameters) { + if (substitutor.substitute(parameter) == null) { + return false; + } + } + return true; + } } - return true; - } } - } + return false; } - return false; - } - @Override - public void invoke(@Nonnull final Project project, Editor editor, final PsiFile file) { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; + @Override + @RequiredUIAccess + public void invoke(Project project, Editor editor, PsiFile file) { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } - final PsiTypeParameter[] typeParameters = myPsiClass.getTypeParameters(); - final PsiSubstitutor psiSubstitutor = inferTypeArguments(); - final PsiJavaCodeReferenceElement reference = myNewExpression.getClassOrAnonymousClassReference(); - LOG.assertTrue(reference != null, myNewExpression); - final PsiReferenceParameterList parameterList = reference.getParameterList(); - LOG.assertTrue(parameterList != null, myNewExpression); - PsiTypeElement[] elements = parameterList.getTypeParameterElements(); - for (int i = elements.length - 1; i >= 0; i--) { - PsiTypeElement typeElement = elements[i]; - final PsiType typeArg = psiSubstitutor.substitute(typeParameters[i]); - typeElement.replace(JavaPsiFacade.getElementFactory(project).createTypeElement(typeArg)); + PsiTypeParameter[] typeParameters = myPsiClass.getTypeParameters(); + PsiSubstitutor psiSubstitutor = inferTypeArguments(); + PsiJavaCodeReferenceElement reference = myNewExpression.getClassOrAnonymousClassReference(); + LOG.assertTrue(reference != null, myNewExpression); + PsiReferenceParameterList parameterList = reference.getParameterList(); + LOG.assertTrue(parameterList != null, myNewExpression); + PsiTypeElement[] elements = parameterList.getTypeParameterElements(); + for (int i = elements.length - 1; i >= 0; i--) { + PsiTypeElement typeElement = elements[i]; + PsiType typeArg = psiSubstitutor.substitute(typeParameters[i]); + typeElement.replace(JavaPsiFacade.getElementFactory(project).createTypeElement(typeArg)); + } } - } - - private PsiSubstitutor inferTypeArguments() { - final JavaPsiFacade facade = JavaPsiFacade.getInstance(myNewExpression.getProject()); - final PsiResolveHelper resolveHelper = facade.getResolveHelper(); - final PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters(); - final PsiExpressionList argumentList = myNewExpression.getArgumentList(); - LOG.assertTrue(argumentList != null); - final PsiExpression[] expressions = argumentList.getExpressions(); - return resolveHelper.inferTypeArguments(myPsiClass.getTypeParameters(), parameters, expressions, - PsiSubstitutor.EMPTY, - myNewExpression.getParent(), - DefaultParameterTypeInferencePolicy.INSTANCE); - } + private PsiSubstitutor inferTypeArguments() { + JavaPsiFacade facade = JavaPsiFacade.getInstance(myNewExpression.getProject()); + PsiResolveHelper resolveHelper = facade.getResolveHelper(); + PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters(); + PsiExpressionList argumentList = myNewExpression.getArgumentList(); + LOG.assertTrue(argumentList != null); + PsiExpression[] expressions = argumentList.getExpressions(); + return resolveHelper.inferTypeArguments(myPsiClass.getTypeParameters(), parameters, expressions, + PsiSubstitutor.EMPTY, + myNewExpression.getParent(), + DefaultParameterTypeInferencePolicy.INSTANCE + ); + } - public static void registerIntentions(@Nonnull JavaResolveResult[] candidates, - @Nonnull PsiExpressionList list, - @Nullable HighlightInfo highlightInfo, - PsiClass psiClass) { - if (candidates.length == 0) return; - PsiExpression[] expressions = list.getExpressions(); - for (JavaResolveResult candidate : candidates) { - registerIntention(expressions, highlightInfo, psiClass, candidate, list); + public static void registerIntentions( + JavaResolveResult[] candidates, + PsiExpressionList list, + HighlightInfo.@Nullable Builder highlightInfo, + PsiClass psiClass + ) { + if (highlightInfo == null || candidates.length == 0) { + return; + } + PsiExpression[] expressions = list.getExpressions(); + for (JavaResolveResult candidate : candidates) { + registerIntention(expressions, highlightInfo, psiClass, candidate, list); + } } - } - private static void registerIntention(@Nonnull PsiExpression[] expressions, - @Nullable HighlightInfo highlightInfo, - PsiClass psiClass, - @Nonnull JavaResolveResult candidate, - @Nonnull PsiElement context) { - if (!candidate.isStaticsScopeCorrect()) return; - PsiMethod method = (PsiMethod) candidate.getElement(); - if (method != null && context.getManager().isInProject(method)) { - final ChangeTypeArgumentsFix fix = new ChangeTypeArgumentsFix(method, psiClass, expressions, context); - QuickFixAction.registerQuickFixAction(highlightInfo, null, fix); + private static void registerIntention( + PsiExpression[] expressions, + HighlightInfo.Builder highlightInfo, + PsiClass psiClass, + JavaResolveResult candidate, + PsiElement context + ) { + if (!candidate.isStaticsScopeCorrect()) { + return; + } + PsiMethod method = (PsiMethod)candidate.getElement(); + if (method != null && context.getManager().isInProject(method)) { + highlightInfo.registerFix(new ChangeTypeArgumentsFix(method, psiClass, expressions, context)); + } } - } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean startInWriteAction() { + return true; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ClassKind.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ClassKind.java index 938f850692..128c427368 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ClassKind.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ClassKind.java @@ -15,10 +15,16 @@ */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; +import consulo.localize.LocalizeValue; + /** - * Created by Max Medvedev on 28/05/14 + * @author Max Medvedev + * @since 2014-05-28 */ -public interface ClassKind -{ - String getDescription(); +public interface ClassKind { + LocalizeValue getDescription(); + + default LocalizeValue getDescriptionAccusative() { + return getDescription(); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConstructorParametersFixer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConstructorParametersFixer.java index 11991e1f6a..0c7a233ddf 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConstructorParametersFixer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConstructorParametersFixer.java @@ -13,35 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * Propose to cast one argument to corresponding type - * in the constructor invocation - * E.g. - * - * User: cdr - * Date: Nov 13, 2002 - */ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; import consulo.language.editor.rawHighlight.HighlightInfo; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.infos.CandidateInfo; import consulo.document.util.TextRange; -import javax.annotation.Nonnull; +/** + * Propose to cast one argument to corresponding type + * in the constructor invocation + * E.g. + * + * @author cdr + * @since 2002-11-13 + */ public class ConstructorParametersFixer { - public static void registerFixActions(@Nonnull PsiJavaCodeReferenceElement ctrRef, PsiConstructorCall constructorCall, HighlightInfo highlightInfo, - final TextRange fixRange) { - JavaResolveResult resolved = ctrRef.advancedResolve(false); - PsiClass aClass = (PsiClass) resolved.getElement(); - if (aClass == null) return; - PsiMethod[] methods = aClass.getConstructors(); - CandidateInfo[] candidates = new CandidateInfo[methods.length]; - for (int i = 0; i < candidates.length; i++) { - candidates[i] = new CandidateInfo(methods[i], resolved.getSubstitutor()); + public static void registerFixActions( + PsiJavaCodeReferenceElement ctrRef, + PsiConstructorCall constructorCall, + HighlightInfo.Builder highlightInfo, + TextRange fixRange + ) { + JavaResolveResult resolved = ctrRef.advancedResolve(false); + PsiClass aClass = (PsiClass)resolved.getElement(); + if (aClass == null) { + return; + } + PsiMethod[] methods = aClass.getConstructors(); + CandidateInfo[] candidates = new CandidateInfo[methods.length]; + for (int i = 0; i < candidates.length; i++) { + candidates[i] = new CandidateInfo(methods[i], resolved.getSubstitutor()); + } + CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange); + AddTypeArgumentsFix.REGISTRAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange); } - CastMethodArgumentFix.REGISTRAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange); - AddTypeArgumentsFix.REGISTRAR.registerCastActions(candidates, constructorCall, highlightInfo, fixRange); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConvertDoubleToFloatFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConvertDoubleToFloatFix.java index d1c8fe69f3..1c2d4dde4d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConvertDoubleToFloatFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ConvertDoubleToFloatFix.java @@ -17,97 +17,112 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; import consulo.document.util.TextRange; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.lang.StringUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** - * User: anna - * Date: 2/10/12 + * @author anna + * @since 2012-02-10 */ public class ConvertDoubleToFloatFix implements SyntheticIntentionAction { - private final PsiExpression myExpression; + private final PsiExpression myExpression; - public ConvertDoubleToFloatFix(PsiExpression expression) { - myExpression = expression; - } + public ConvertDoubleToFloatFix(PsiExpression expression) { + myExpression = expression; + } - @Nonnull - @Override - public String getText() { - return "Convert '" + myExpression.getText() + "' to float"; - } + @Override + @RequiredReadAction + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Convert '" + myExpression.getText() + "' to float"); + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (myExpression.isValid()) { - if (!StringUtil.endsWithIgnoreCase(myExpression.getText(), "f")) { - final PsiLiteralExpression expression = (PsiLiteralExpression) createFloatingPointExpression(project); - final Object value = expression.getValue(); - return value instanceof Float && !((Float) value).isInfinite() && !(((Float) value).floatValue() == 0 && !TypeConversionUtil.isFPZero(expression.getText())); - } + @Override + @RequiredReadAction + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + if (myExpression.isValid()) { + if (!StringUtil.endsWithIgnoreCase(myExpression.getText(), "f")) { + PsiLiteralExpression expression = (PsiLiteralExpression)createFloatingPointExpression(project); + return expression.getValue() instanceof Float floatValue + && !floatValue.isInfinite() + && !(floatValue == 0 && !TypeConversionUtil.isFPZero(expression.getText())); + } + } + return false; } - return false; - } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - myExpression.replace(createFloatingPointExpression(project)); - } + @Override + @RequiredWriteAction + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + myExpression.replace(createFloatingPointExpression(project)); + } - private PsiExpression createFloatingPointExpression(Project project) { - final String text = myExpression.getText(); - if (StringUtil.endsWithIgnoreCase(text, "d")) { - return JavaPsiFacade.getElementFactory(project).createExpressionFromText(text.substring(0, text.length() - 1) + "f", myExpression); - } else { - return JavaPsiFacade.getElementFactory(project).createExpressionFromText(text + "f", myExpression); + @RequiredReadAction + private PsiExpression createFloatingPointExpression(Project project) { + String text = myExpression.getText(); + if (StringUtil.endsWithIgnoreCase(text, "d")) { + return JavaPsiFacade.getElementFactory(project) + .createExpressionFromText(text.substring(0, text.length() - 1) + "f", myExpression); + } + else { + return JavaPsiFacade.getElementFactory(project).createExpressionFromText(text + "f", myExpression); + } } - } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean startInWriteAction() { + return true; + } - public static void registerIntentions(@Nonnull JavaResolveResult[] candidates, - @Nonnull PsiExpressionList list, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange) { - if (candidates.length == 0) return; - PsiExpression[] expressions = list.getExpressions(); - for (JavaResolveResult candidate : candidates) { - registerIntention(expressions, highlightInfo, fixRange, candidate, list); + public static void registerIntentions( + JavaResolveResult[] candidates, + PsiExpressionList list, + HighlightInfo.@Nullable Builder hlBuilder, + @Nullable TextRange fixRange + ) { + if (hlBuilder == null || candidates.length == 0) { + return; + } + PsiExpression[] expressions = list.getExpressions(); + for (JavaResolveResult candidate : candidates) { + registerIntention(expressions, hlBuilder, fixRange, candidate, list); + } } - } - private static void registerIntention(@Nonnull PsiExpression[] expressions, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange, - @Nonnull JavaResolveResult candidate, - @Nonnull PsiElement context) { - if (!candidate.isStaticsScopeCorrect()) return; - PsiMethod method = (PsiMethod) candidate.getElement(); - if (method != null && context.getManager().isInProject(method)) { - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length == expressions.length) { - for (int i = 0, length = parameters.length; i < length; i++) { - PsiParameter parameter = parameters[i]; - final PsiExpression expression = expressions[i]; - if (expression instanceof PsiLiteralExpression && PsiType.FLOAT.equals(parameter.getType()) && PsiType.DOUBLE.equals(expression.getType())) { - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, new ConvertDoubleToFloatFix(expression)); - } + private static void registerIntention( + PsiExpression[] expressions, + HighlightInfo.Builder hlBuilder, + @Nullable TextRange fixRange, + JavaResolveResult candidate, + PsiElement context + ) { + if (!candidate.isStaticsScopeCorrect()) { + return; + } + PsiMethod method = (PsiMethod)candidate.getElement(); + if (method != null && context.getManager().isInProject(method)) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length == expressions.length) { + for (int i = 0, length = parameters.length; i < length; i++) { + PsiParameter parameter = parameters[i]; + if (expressions[i] instanceof PsiLiteralExpression literal + && PsiType.FLOAT.equals(parameter.getType()) + && PsiType.DOUBLE.equals(literal.getType())) { + hlBuilder.newFix(new ConvertDoubleToFloatFix(literal)).optionalFixRange(fixRange).register(); + } + } + } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteElementFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteElementFix.java index 0581820df0..9ea4a63040 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteElementFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteElementFix.java @@ -18,47 +18,35 @@ import com.siyeh.ig.psiutils.CommentTracker; import consulo.application.WriteAction; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import consulo.util.lang.ObjectUtil; -import org.jetbrains.annotations.Nls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class DeleteElementFix extends LocalQuickFixAndIntentionActionOnPsiElement { - private final String myText; + private final LocalizeValue myText; - public DeleteElementFix(@Nonnull PsiElement element) { + public DeleteElementFix(PsiElement element) { super(element); - myText = null; + myText = LocalizeValue.empty(); } - public DeleteElementFix(@Nonnull PsiElement element, @Nonnull @Nls String text) { + public DeleteElementFix(PsiElement element, LocalizeValue text) { super(element); myText = text; } - @Nls - @Nonnull - @Override - public String getText() { - return ObjectUtil.notNull(myText, getFamilyName()); - } - - @Nls - @Nonnull @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("delete.element.fix.text"); + public LocalizeValue getText() { + return myText.orIfEmpty(JavaQuickFixLocalize.deleteElementFixText()); } @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nullable Editor editor, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public void invoke(Project project, PsiFile file, @Nullable Editor editor, PsiElement startElement, PsiElement endElement) { if (FileModificationService.getInstance().preparePsiElementForWrite(file)) { WriteAction.run(() -> new CommentTracker().deleteAndRestoreComments(startElement)); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteRepeatedInterfaceFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteRepeatedInterfaceFix.java index 22f8a0cba2..f30e459e39 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteRepeatedInterfaceFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/DeleteRepeatedInterfaceFix.java @@ -23,11 +23,11 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.StringUtil; -import javax.annotation.Nonnull; import java.util.List; public class DeleteRepeatedInterfaceFix implements SyntheticIntentionAction { @@ -39,14 +39,13 @@ public DeleteRepeatedInterfaceFix(PsiTypeElement conjunct, List myConjList = conjList; } - @Nonnull @Override - public String getText() { - return "Delete repeated '" + myConjunct.getText() + "'"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Delete repeated '" + myConjunct.getText() + "'"); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { for (PsiTypeElement element : myConjList) { if (!element.isValid()) { return false; @@ -56,7 +55,7 @@ public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { if (!FileModificationService.getInstance().prepareFileForWrite(file)) { return; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/FlipIntersectionSidesFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/FlipIntersectionSidesFix.java index 59317f2ecd..a5bc7264cd 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/FlipIntersectionSidesFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/FlipIntersectionSidesFix.java @@ -26,12 +26,12 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; -import javax.annotation.Nonnull; import java.util.List; /** @@ -46,7 +46,7 @@ public class FlipIntersectionSidesFix implements SyntheticIntentionAction { private final PsiTypeElement myCastTypeElement; public FlipIntersectionSidesFix(String className, - @Nonnull List conjList, + List conjList, PsiTypeElement conjunct, PsiTypeElement castTypeElement) { myClassName = className; @@ -56,14 +56,13 @@ public FlipIntersectionSidesFix(String className, myCastTypeElement = castTypeElement; } - @Nonnull @Override - public String getText() { - return "Move '" + myClassName + "' to the beginning"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Move '" + myClassName + "' to the beginning"); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { for (PsiTypeElement typeElement : myConjuncts) { if (!typeElement.isValid()) { return false; @@ -73,7 +72,7 @@ public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { if (!FileModificationService.getInstance().prepareFileForWrite(file)) { return; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/GoToSymbolFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/GoToSymbolFix.java index 055a8ad8e1..193ae8ca38 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/GoToSymbolFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/GoToSymbolFix.java @@ -22,34 +22,31 @@ import consulo.language.psi.SmartPointerManager; import consulo.language.psi.SmartPsiElementPointer; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; public class GoToSymbolFix implements SyntheticIntentionAction { private final SmartPsiElementPointer myPointer; - private final String myMessage; + private final LocalizeValue myMessage; - public GoToSymbolFix(@Nonnull NavigatablePsiElement symbol, @Nonnull @Nls String message) { + public GoToSymbolFix(NavigatablePsiElement symbol, LocalizeValue message) { myPointer = SmartPointerManager.getInstance(symbol.getProject()).createSmartPsiElementPointer(symbol); myMessage = message; } - @Nls - @Nonnull @Override - public String getText() { + public LocalizeValue getText() { return myMessage; } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return myPointer.getElement() != null; } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { NavigatablePsiElement e = myPointer.getElement(); if (e != null && e.isValid()) { e.navigate(true); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeModuleStatementsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeModuleStatementsFix.java index ab0373a4fa..0a2890626e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeModuleStatementsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeModuleStatementsFix.java @@ -18,6 +18,8 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.CommentTracker; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; import consulo.language.codeStyle.CodeStyleManager; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; @@ -25,8 +27,8 @@ import consulo.language.psi.PsiFile; import consulo.project.Project; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,67 +38,76 @@ * @author Pavel.Dolgov */ public abstract class MergeModuleStatementsFix extends LocalQuickFixAndIntentionActionOnPsiElement { + protected MergeModuleStatementsFix(PsiJavaModule javaModule) { + super(javaModule); + } - protected MergeModuleStatementsFix(@Nonnull PsiJavaModule javaModule) { - super(javaModule); - } - - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - return PsiUtil.isLanguageLevel9OrHigher(file); - } + @Override + @RequiredReadAction + public boolean isAvailable( + Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement + ) { + return PsiUtil.isLanguageLevel9OrHigher(file); + } - @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nullable Editor editor, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - if (startElement instanceof PsiJavaModule) { - final PsiJavaModule javaModule = (PsiJavaModule) startElement; - final List statementsToMerge = getStatementsToMerge(javaModule); - LOG.assertTrue(!statementsToMerge.isEmpty()); + @Override + @RequiredWriteAction + public void invoke( + Project project, + PsiFile file, + @Nullable Editor editor, + PsiElement startElement, + PsiElement endElement + ) { + if (startElement instanceof PsiJavaModule javaModule) { + List statementsToMerge = getStatementsToMerge(javaModule); + LOG.assertTrue(!statementsToMerge.isEmpty()); - final String tempModuleText = PsiKeyword.MODULE + " " + javaModule.getName() + " {" + getReplacementText(statementsToMerge) + "}"; - final PsiJavaModule tempModule = JavaPsiFacade.getInstance(project).getElementFactory().createModuleFromText(tempModuleText); + String tempModuleText = + PsiKeyword.MODULE + " " + javaModule.getName() + " {" + getReplacementText(statementsToMerge) + "}"; + PsiJavaModule tempModule = JavaPsiFacade.getInstance(project).getElementFactory().createModuleFromText(tempModuleText); - final List tempStatements = getStatementsToMerge(tempModule); - LOG.assertTrue(!tempStatements.isEmpty()); - final T replacement = tempStatements.get(0); + List tempStatements = getStatementsToMerge(tempModule); + LOG.assertTrue(!tempStatements.isEmpty()); + T replacement = tempStatements.get(0); - final T firstStatement = statementsToMerge.get(0); - final CommentTracker commentTracker = new CommentTracker(); - final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); - final PsiElement resultingStatement = codeStyleManager.reformat(commentTracker.replace(firstStatement, replacement)); + T firstStatement = statementsToMerge.get(0); + CommentTracker commentTracker = new CommentTracker(); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + PsiElement resultingStatement = codeStyleManager.reformat(commentTracker.replace(firstStatement, replacement)); - for (int i = 1; i < statementsToMerge.size(); i++) { - T statement = statementsToMerge.get(i); - commentTracker.delete(statement); - } - commentTracker.insertCommentsBefore(resultingStatement); + for (int i = 1; i < statementsToMerge.size(); i++) { + T statement = statementsToMerge.get(i); + commentTracker.delete(statement); + } + commentTracker.insertCommentsBefore(resultingStatement); - if (editor != null) { - final int offset = resultingStatement.getTextRange().getEndOffset(); - editor.getCaretModel().moveToOffset(offset); - } + if (editor != null) { + int offset = resultingStatement.getTextRange().getEndOffset(); + editor.getCaretModel().moveToOffset(offset); + } + } } - } - @Nonnull - protected abstract String getReplacementText(List statementsToMerge); + protected abstract String getReplacementText(List statementsToMerge); - @Nonnull - protected abstract List getStatementsToMerge(@Nonnull PsiJavaModule javaModule); + protected abstract List getStatementsToMerge(PsiJavaModule javaModule); - @Nonnull - protected static String joinUniqueNames(@Nonnull List names) { - final Set unique = new HashSet<>(); - return names.stream().filter(name -> unique.add(name)).collect(Collectors.joining(",")); - } + protected static String joinUniqueNames(List names) { + return names.stream().distinct().collect(Collectors.joining(",")); + } - @Nullable - public static MergeModuleStatementsFix createFix(@Nullable PsiElement statement) { - if (statement instanceof PsiPackageAccessibilityStatement) { - return MergePackageAccessibilityStatementsFix.createFix((PsiPackageAccessibilityStatement) statement); - } else if (statement instanceof PsiProvidesStatement) { - return MergeProvidesStatementsFix.createFix((PsiProvidesStatement) statement); + @Nullable + public static MergeModuleStatementsFix createFix(@Nullable PsiElement statement) { + if (statement instanceof PsiPackageAccessibilityStatement packageAccessibilityStmt) { + return MergePackageAccessibilityStatementsFix.createFix(packageAccessibilityStmt); + } + else if (statement instanceof PsiProvidesStatement providesStmt) { + return MergeProvidesStatementsFix.createFix(providesStmt); + } + return null; } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergePackageAccessibilityStatementsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergePackageAccessibilityStatementsFix.java index 1617290041..5b5e94a8b9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergePackageAccessibilityStatementsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergePackageAccessibilityStatementsFix.java @@ -19,13 +19,11 @@ import com.intellij.java.language.psi.PsiKeyword; import com.intellij.java.language.psi.PsiPackageAccessibilityStatement; import com.intellij.java.language.psi.PsiPackageAccessibilityStatement.Role; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.language.psi.PsiElement; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; -import org.jetbrains.annotations.Nls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -36,95 +34,71 @@ * @author Pavel.Dolgov */ public class MergePackageAccessibilityStatementsFix extends MergeModuleStatementsFix { + private static final Logger LOG = Logger.getInstance(MergePackageAccessibilityStatementsFix.class); + private final String myPackageName; + private final Role myRole; - private static final Logger LOG = Logger.getInstance(MergePackageAccessibilityStatementsFix.class); - private final String myPackageName; - private final Role myRole; - - protected MergePackageAccessibilityStatementsFix(@Nonnull PsiJavaModule javaModule, @Nonnull String packageName, @Nonnull Role role) { - super(javaModule); - myPackageName = packageName; - myRole = role; - } - - @Nls - @Nonnull - @Override - public String getText() { - return JavaQuickFixBundle.message("java.9.merge.module.statements.fix.name", getKeyword(), myPackageName); - } + protected MergePackageAccessibilityStatementsFix(PsiJavaModule javaModule, String packageName, Role role) { + super(javaModule); + myPackageName = packageName; + myRole = role; + } - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("java.9.merge.module.statements.fix.family.name", getKeyword()); - } + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.java9MergeModuleStatementsFixName(getKeyword(), myPackageName); + } - @Nonnull - @Override - protected String getReplacementText(@Nonnull List statementsToMerge) { - final List moduleNames = getModuleNames(statementsToMerge); - if (!moduleNames.isEmpty()) { - return getKeyword() + " " + myPackageName + " " + PsiKeyword.TO + " " + joinUniqueNames(moduleNames) + ";"; + @Override + protected String getReplacementText(List statementsToMerge) { + List moduleNames = getModuleNames(statementsToMerge); + if (!moduleNames.isEmpty()) { + return getKeyword() + " " + myPackageName + " " + PsiKeyword.TO + " " + joinUniqueNames(moduleNames) + ";"; + } + return getKeyword() + " " + myPackageName + ";"; } - return getKeyword() + " " + myPackageName + ";"; - } - @Nonnull - private static List getModuleNames(@Nonnull List statements) { - final List result = new ArrayList<>(); - for (PsiPackageAccessibilityStatement statement : statements) { - final List moduleNames = statement.getModuleNames(); - if (moduleNames.isEmpty()) { - return Collections.emptyList(); - } - result.addAll(moduleNames); + private static List getModuleNames(List statements) { + List result = new ArrayList<>(); + for (PsiPackageAccessibilityStatement statement : statements) { + List moduleNames = statement.getModuleNames(); + if (moduleNames.isEmpty()) { + return Collections.emptyList(); + } + result.addAll(moduleNames); + } + return result; } - return result; - } - @Nonnull - @Override - protected List getStatementsToMerge(@Nonnull PsiJavaModule javaModule) { - return StreamSupport.stream(getStatements(javaModule, myRole).spliterator(), false).filter(statement -> myPackageName.equals(statement.getPackageName())).collect(Collectors.toList()); - } + @Override + protected List getStatementsToMerge(PsiJavaModule javaModule) { + return StreamSupport.stream(getStatements(javaModule, myRole).spliterator(), false) + .filter(statement -> myPackageName.equals(statement.getPackageName())) + .collect(Collectors.toList()); + } - @Nullable - public static MergeModuleStatementsFix createFix(@Nullable PsiPackageAccessibilityStatement statement) { - if (statement != null) { - final PsiElement parent = statement.getParent(); - if (parent instanceof PsiJavaModule) { - final String packageName = statement.getPackageName(); - if (packageName != null) { - return new MergePackageAccessibilityStatementsFix((PsiJavaModule) parent, packageName, statement.getRole()); + @Nullable + public static MergeModuleStatementsFix createFix(@Nullable PsiPackageAccessibilityStatement statement) { + if (statement != null && statement.getParent() instanceof PsiJavaModule javaModule) { + String packageName = statement.getPackageName(); + if (packageName != null) { + return new MergePackageAccessibilityStatementsFix(javaModule, packageName, statement.getRole()); + } } - } + return null; } - return null; - } - @Nonnull - private static Iterable getStatements(@Nonnull PsiJavaModule javaModule, @Nonnull Role role) { - switch (role) { - case OPENS: - return javaModule.getOpens(); - case EXPORTS: - return javaModule.getExports(); + private static Iterable getStatements(PsiJavaModule javaModule, Role role) { + return switch (role) { + case OPENS -> javaModule.getOpens(); + case EXPORTS -> javaModule.getExports(); + }; } - LOG.error("Unexpected role " + role); - return Collections.emptyList(); - } - @Nonnull - private String getKeyword() { - switch (myRole) { - case OPENS: - return PsiKeyword.OPENS; - case EXPORTS: - return PsiKeyword.EXPORTS; + private String getKeyword() { + return switch (myRole) { + case OPENS -> PsiKeyword.OPENS; + case EXPORTS -> PsiKeyword.EXPORTS; + }; } - LOG.error("Unexpected role " + myRole); - return ""; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeProvidesStatementsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeProvidesStatementsFix.java index 33edeb2c73..b7cb34f224 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeProvidesStatementsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MergeProvidesStatementsFix.java @@ -16,13 +16,11 @@ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; import com.intellij.java.language.psi.*; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.language.psi.PsiElement; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import consulo.localize.LocalizeValue; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.Nls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -32,73 +30,60 @@ * @author Pavel.Dolgov */ public class MergeProvidesStatementsFix extends MergeModuleStatementsFix { - private final String myInterfaceName; + private final String myInterfaceName; - MergeProvidesStatementsFix(@Nonnull PsiJavaModule javaModule, @Nonnull String interfaceName) { - super(javaModule); - myInterfaceName = interfaceName; - } - - @Nonnull - @Override - public String getText() { - return JavaQuickFixBundle.message("java.9.merge.module.statements.fix.name", PsiKeyword.PROVIDES, myInterfaceName); - } + MergeProvidesStatementsFix(PsiJavaModule javaModule, String interfaceName) { + super(javaModule); + myInterfaceName = interfaceName; + } - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("java.9.merge.module.statements.fix.family.name", PsiKeyword.PROVIDES); - } + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.java9MergeModuleStatementsFixName(PsiKeyword.PROVIDES, myInterfaceName); + } - @Nonnull - @Override - protected String getReplacementText(@Nonnull List statementsToMerge) { - final List implementationNames = getImplementationNames(statementsToMerge); - LOG.assertTrue(!implementationNames.isEmpty()); - return PsiKeyword.PROVIDES + " " + myInterfaceName + " " + PsiKeyword.WITH + " " + joinUniqueNames(implementationNames) + ";"; - } + @Override + protected String getReplacementText(List statementsToMerge) { + List implementationNames = getImplementationNames(statementsToMerge); + LOG.assertTrue(!implementationNames.isEmpty()); + return PsiKeyword.PROVIDES + " " + myInterfaceName + " " + PsiKeyword.WITH + " " + joinUniqueNames(implementationNames) + ";"; + } - @Nonnull - private static List getImplementationNames(@Nonnull List statements) { - List list = new ArrayList<>(); - for (PsiProvidesStatement statement : statements) { - PsiReferenceList implementationList = statement.getImplementationList(); - if (implementationList == null) { - continue; - } - for (PsiJavaCodeReferenceElement element : implementationList.getReferenceElements()) { - ContainerUtil.addIfNotNull(list, element.getQualifiedName()); - } + private static List getImplementationNames(List statements) { + List list = new ArrayList<>(); + for (PsiProvidesStatement statement : statements) { + PsiReferenceList implementationList = statement.getImplementationList(); + if (implementationList == null) { + continue; + } + for (PsiJavaCodeReferenceElement element : implementationList.getReferenceElements()) { + ContainerUtil.addIfNotNull(list, element.getQualifiedName()); + } + } + return list; } - return list; - } - @Nonnull - @Override - protected List getStatementsToMerge(@Nonnull PsiJavaModule javaModule) { - return StreamSupport.stream(javaModule.getProvides().spliterator(), false).filter(statement -> - { - final PsiJavaCodeReferenceElement reference = statement.getInterfaceReference(); - return reference != null && myInterfaceName.equals(reference.getQualifiedName()); - }).collect(Collectors.toList()); - } + @Override + protected List getStatementsToMerge(PsiJavaModule javaModule) { + return StreamSupport.stream(javaModule.getProvides().spliterator(), false) + .filter(statement -> { + PsiJavaCodeReferenceElement reference = statement.getInterfaceReference(); + return reference != null && myInterfaceName.equals(reference.getQualifiedName()); + }) + .collect(Collectors.toList()); + } - @Nullable - public static MergeModuleStatementsFix createFix(@Nullable PsiProvidesStatement statement) { - if (statement != null) { - final PsiElement parent = statement.getParent(); - if (parent instanceof PsiJavaModule) { - final PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference(); - if (interfaceReference != null) { - final String interfaceName = interfaceReference.getQualifiedName(); - if (interfaceName != null) { - return new MergeProvidesStatementsFix((PsiJavaModule) parent, interfaceName); - } + @Nullable + public static MergeModuleStatementsFix createFix(@Nullable PsiProvidesStatement statement) { + if (statement != null && statement.getParent() instanceof PsiJavaModule javaModule) { + PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference(); + if (interfaceReference != null) { + String interfaceName = interfaceReference.getQualifiedName(); + if (interfaceName != null) { + return new MergeProvidesStatementsFix(javaModule, interfaceName); + } + } } - } + return null; } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MethodArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MethodArgumentFix.java index 1da5c5d0b1..31af5d4c1c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MethodArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MethodArgumentFix.java @@ -20,7 +20,6 @@ import com.intellij.java.language.psi.PsiExpressionList; import com.intellij.java.language.psi.PsiType; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; import consulo.language.editor.FileModificationService; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiFile; @@ -28,7 +27,6 @@ import consulo.logging.Logger; import consulo.project.Project; -import javax.annotation.Nonnull; /** * @author ven @@ -49,7 +47,7 @@ protected MethodArgumentFix(PsiExpressionList list, int i, PsiType toType, Argum } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return myToType != null && myToType.isValid() @@ -65,7 +63,7 @@ public boolean startInWriteAction() { } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) { + public void invoke(Project project, Editor editor, PsiFile file) { if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; PsiExpression expression = myArgList.getExpressions()[myIndex]; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveAnnotationOnStaticMemberQualifyingTypeFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveAnnotationOnStaticMemberQualifyingTypeFix.java new file mode 100644 index 0000000000..ab858c37fb --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveAnnotationOnStaticMemberQualifyingTypeFix.java @@ -0,0 +1,84 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/* + * Copyright 2013-2026 consulo.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; + +import com.intellij.java.language.psi.*; +import com.siyeh.ig.psiutils.CommentTracker; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import org.jspecify.annotations.Nullable; + +public class MoveAnnotationOnStaticMemberQualifyingTypeFix implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.annotationOnStaticMemberQualifyingTypeFamilyName(); + } + + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiAnnotation annotation = descriptor.getPsiElement() instanceof PsiAnnotation a ? a : null; + if (annotation == null) return; + + PsiTypeElement psiTypeElement = getTypeElement(annotation); + if (psiTypeElement == null) return; + + PsiJavaCodeReferenceElement innermostParent = psiTypeElement.getInnermostComponentReferenceElement(); + if (innermostParent == null) return; + + PsiElement rightmostDot = getRightmostDot(innermostParent.getLastChild()); + if (rightmostDot == null) return; + + innermostParent.addAfter(annotation, rightmostDot); + + CommentTracker ct = new CommentTracker(); + ct.markUnchanged(annotation); + ct.deleteAndRestoreComments(annotation); + } + + private static @Nullable PsiElement getRightmostDot(@Nullable PsiElement element) { + if (element == null) { + return null; + } + PsiElement sibling = element.getPrevSibling(); + while (sibling != null) { + if (sibling.getNode() != null && sibling.getNode().getElementType() == JavaTokenType.DOT) { + return sibling; + } + sibling = sibling.getPrevSibling(); + } + return null; + } + + private static @Nullable PsiTypeElement getTypeElement(PsiElement startElement) { + PsiElement parent = PsiTreeUtil.getParentOfType(startElement, PsiTypeElement.class, PsiVariable.class, PsiMethod.class); + if (parent instanceof PsiTypeElement typeElement) { + return typeElement; + } + if (parent instanceof PsiVariable variable) { + return variable.getTypeElement(); + } + if (parent instanceof PsiMethod method) { + return method.getReturnTypeElement(); + } + return null; + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveFileFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveFileFix.java index 0b131f3095..31d0c292d9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveFileFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/MoveFileFix.java @@ -19,38 +19,35 @@ import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.virtualFileSystem.VirtualFile; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; import java.io.IOException; public class MoveFileFix implements SyntheticIntentionAction { private final VirtualFile myFile; private final VirtualFile myTarget; - private final String myMessage; + private final LocalizeValue myMessage; - public MoveFileFix(@Nonnull VirtualFile file, @Nonnull VirtualFile target, @Nonnull @Nls String message) { + public MoveFileFix(VirtualFile file, VirtualFile target, LocalizeValue message) { myFile = file; myTarget = target; myMessage = message; } - @Nls - @Nonnull @Override - public String getText() { + public LocalizeValue getText() { return myMessage; } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return true; } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { if (myFile.isValid() && myTarget.isValid()) { try { myFile.move(this, myTarget); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/PermuteArgumentsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/PermuteArgumentsFix.java index 05b53f9321..c8bc2bc00f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/PermuteArgumentsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/PermuteArgumentsFix.java @@ -20,20 +20,21 @@ import com.intellij.java.language.psi.infos.MethodCandidateInfo; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; import consulo.document.util.TextRange; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ArrayUtil; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -41,147 +42,199 @@ * @author cdr */ public class PermuteArgumentsFix implements SyntheticIntentionAction { - private static final Logger LOG = Logger.getInstance(PermuteArgumentsFix.class); - private final PsiCall myCall; - private final PsiCall myPermutation; - - private PermuteArgumentsFix(@Nonnull PsiCall call, @Nonnull PsiCall permutation) { - myCall = call; - myPermutation = permutation; - } - - @Override - public boolean startInWriteAction() { - return true; - } - - - @Override - @Nonnull - public String getText() { - return JavaQuickFixBundle.message("permute.arguments"); - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return !project.isDisposed() && myCall.isValid() && myCall.getManager().isInProject(myCall); - } - - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - myCall.getArgumentList().replace(myPermutation.getArgumentList()); - } - - public static void registerFix(HighlightInfo info, PsiCall callExpression, final CandidateInfo[] candidates, final TextRange fixRange) { - PsiExpression[] expressions = callExpression.getArgumentList().getExpressions(); - if (expressions.length < 2) return; - List permutations = new ArrayList(); - - for (CandidateInfo candidate : candidates) { - if (candidate instanceof MethodCandidateInfo) { - MethodCandidateInfo methodCandidate = (MethodCandidateInfo) candidate; - PsiMethod method = methodCandidate.getElement(); - PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); + private static final Logger LOG = Logger.getInstance(PermuteArgumentsFix.class); + private final PsiCall myCall; + private final PsiCall myPermutation; - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (expressions.length != parameters.length || parameters.length == 0) continue; - int minIncompatibleIndex = parameters.length; - int maxIncompatibleIndex = 0; - int incompatibilitiesCount = 0; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - PsiType type = substitutor.substitute(parameter.getType()); - if (TypeConversionUtil.areTypesAssignmentCompatible(type, expressions[i])) continue; - if (minIncompatibleIndex == parameters.length) minIncompatibleIndex = i; - maxIncompatibleIndex = i; - incompatibilitiesCount++; - } + private PermuteArgumentsFix(PsiCall call, PsiCall permutation) { + myCall = call; + myPermutation = permutation; + } - try { - registerSwapFixes(expressions, callExpression, permutations, methodCandidate, incompatibilitiesCount, minIncompatibleIndex, maxIncompatibleIndex); - registerShiftFixes(expressions, callExpression, permutations, methodCandidate, minIncompatibleIndex, maxIncompatibleIndex); - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } + @Override + public boolean startInWriteAction() { + return true; } - if (permutations.size() == 1) { - PermuteArgumentsFix fix = new PermuteArgumentsFix(callExpression, permutations.get(0)); - QuickFixAction.registerQuickFixAction(info, fixRange, fix); + + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.permuteArguments(); } - } - - private static void registerShiftFixes(final PsiExpression[] expressions, final PsiCall callExpression, final List permutations, - final MethodCandidateInfo methodCandidate, final int minIncompatibleIndex, final int maxIncompatibleIndex) - throws IncorrectOperationException { - PsiMethod method = methodCandidate.getElement(); - PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); - // shift range should include both incompatible indexes - for (int i = 0; i <= minIncompatibleIndex; i++) { - for (int j = Math.max(i + 2, maxIncompatibleIndex); j < expressions.length; j++) { // if j=i+1 the shift is equal to swap - { - ArrayUtil.rotateLeft(expressions, i, j); - if (PsiUtil.isApplicable(method, substitutor, expressions)) { - PsiCall copy = (PsiCall) callExpression.copy(); - PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); - for (int k = i; k < copyExpressions.length; k++) { - copyExpressions[k].replace(expressions[k]); - } - JavaResolveResult result = copy.resolveMethodGenerics(); - if (result.getElement() != null && result.isValidResult()) { - permutations.add(copy); - if (permutations.size() > 1) return; - } - } - ArrayUtil.rotateRight(expressions, i, j); + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return !project.isDisposed() && myCall.isValid() && myCall.getManager().isInProject(myCall); + } + + @Override + @RequiredUIAccess + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; } + myCall.getArgumentList().replace(myPermutation.getArgumentList()); + } - { - ArrayUtil.rotateRight(expressions, i, j); - if (PsiUtil.isApplicable(method, substitutor, expressions)) { - PsiCall copy = (PsiCall) callExpression.copy(); - PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); - for (int k = i; k < copyExpressions.length; k++) { - copyExpressions[k].replace(expressions[k]); + @RequiredReadAction + public static void registerFix( + HighlightInfo.Builder hlBuilder, + PsiCall callExpression, + CandidateInfo[] candidates, + TextRange fixRange + ) { + PsiExpression[] expressions = callExpression.getArgumentList().getExpressions(); + if (hlBuilder == null || expressions.length < 2) { + return; + } + List permutations = new ArrayList<>(); + + for (CandidateInfo candidate : candidates) { + if (candidate instanceof MethodCandidateInfo) { + MethodCandidateInfo methodCandidate = (MethodCandidateInfo) candidate; + PsiMethod method = methodCandidate.getElement(); + PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); + + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (expressions.length != parameters.length || parameters.length == 0) { + continue; + } + int minIncompatibleIndex = parameters.length; + int maxIncompatibleIndex = 0; + int incompatibilitiesCount = 0; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + PsiType type = substitutor.substitute(parameter.getType()); + if (TypeConversionUtil.areTypesAssignmentCompatible(type, expressions[i])) { + continue; + } + if (minIncompatibleIndex == parameters.length) { + minIncompatibleIndex = i; + } + maxIncompatibleIndex = i; + incompatibilitiesCount++; + } + + try { + registerSwapFixes( + expressions, + callExpression, + permutations, + methodCandidate, + incompatibilitiesCount, + minIncompatibleIndex, + maxIncompatibleIndex + ); + registerShiftFixes( + expressions, + callExpression, + permutations, + methodCandidate, + minIncompatibleIndex, + maxIncompatibleIndex + ); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } } + } + if (permutations.size() == 1) { + hlBuilder.newFix(new PermuteArgumentsFix(callExpression, permutations.get(0))).fixRange(fixRange).register(); + } + } - JavaResolveResult result = copy.resolveMethodGenerics(); - if (result.getElement() != null && result.isValidResult()) { - permutations.add(copy); - if (permutations.size() > 1) return; + @RequiredReadAction + private static void registerShiftFixes( + PsiExpression[] expressions, + PsiCall callExpression, + List permutations, + MethodCandidateInfo methodCandidate, + int minIncompatibleIndex, + int maxIncompatibleIndex + ) + throws IncorrectOperationException { + PsiMethod method = methodCandidate.getElement(); + PsiSubstitutor substitutor = methodCandidate.getSubstitutor(); + // shift range should include both incompatible indexes + for (int i = 0; i <= minIncompatibleIndex; i++) { + for (int j = Math.max(i + 2, maxIncompatibleIndex); j < expressions.length; j++) { // if j=i+1 the shift is equal to swap + { + ArrayUtil.rotateLeft(expressions, i, j); + if (PsiUtil.isApplicable(method, substitutor, expressions)) { + PsiCall copy = (PsiCall) callExpression.copy(); + PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); + for (int k = i; k < copyExpressions.length; k++) { + copyExpressions[k].replace(expressions[k]); + } + + JavaResolveResult result = copy.resolveMethodGenerics(); + if (result.getElement() != null && result.isValidResult()) { + permutations.add(copy); + if (permutations.size() > 1) { + return; + } + } + } + ArrayUtil.rotateRight(expressions, i, j); + } + + { + ArrayUtil.rotateRight(expressions, i, j); + if (PsiUtil.isApplicable(method, substitutor, expressions)) { + PsiCall copy = (PsiCall) callExpression.copy(); + PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); + for (int k = i; k < copyExpressions.length; k++) { + copyExpressions[k].replace(expressions[k]); + } + + JavaResolveResult result = copy.resolveMethodGenerics(); + if (result.getElement() != null && result.isValidResult()) { + permutations.add(copy); + if (permutations.size() > 1) { + return; + } + } + } + ArrayUtil.rotateLeft(expressions, i, j); + } } - } - ArrayUtil.rotateLeft(expressions, i, j); } - } } - } - - private static void registerSwapFixes(final PsiExpression[] expressions, final PsiCall callExpression, final List permutations, - MethodCandidateInfo candidate, final int incompatibilitiesCount, final int minIncompatibleIndex, - final int maxIncompatibleIndex) throws IncorrectOperationException { - PsiMethod method = candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - if (incompatibilitiesCount >= 3) return; // no way we can fix it by swapping - - for (int i = minIncompatibleIndex; i < maxIncompatibleIndex; i++) { - for (int j = i + 1; j <= maxIncompatibleIndex; j++) { - ArrayUtil.swap(expressions, i, j); - if (PsiUtil.isApplicable(method, substitutor, expressions)) { - PsiCall copy = (PsiCall) callExpression.copy(); - PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); - copyExpressions[i].replace(expressions[i]); - copyExpressions[j].replace(expressions[j]); - JavaResolveResult result = copy.resolveMethodGenerics(); - if (result.getElement() != null && result.isValidResult()) { - permutations.add(copy); - if (permutations.size() > 1) return; - } + + @RequiredReadAction + private static void registerSwapFixes( + PsiExpression[] expressions, + PsiCall callExpression, + List permutations, + MethodCandidateInfo candidate, + int incompatibilitiesCount, + int minIncompatibleIndex, + int maxIncompatibleIndex + ) throws IncorrectOperationException { + PsiMethod method = candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + if (incompatibilitiesCount >= 3) { + return; // no way we can fix it by swapping + } + + for (int i = minIncompatibleIndex; i < maxIncompatibleIndex; i++) { + for (int j = i + 1; j <= maxIncompatibleIndex; j++) { + ArrayUtil.swap(expressions, i, j); + if (PsiUtil.isApplicable(method, substitutor, expressions)) { + PsiCall copy = (PsiCall) callExpression.copy(); + PsiExpression[] copyExpressions = copy.getArgumentList().getExpressions(); + copyExpressions[i].replace(expressions[i]); + copyExpressions[j].replace(expressions[j]); + JavaResolveResult result = copy.resolveMethodGenerics(); + if (result.getElement() != null && result.isValidResult()) { + permutations.add(copy); + if (permutations.size() > 1) { + return; + } + } + } + ArrayUtil.swap(expressions, i, j); + } } - ArrayUtil.swap(expressions, i, j); - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifySuperArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifySuperArgumentFix.java index bd069e8198..e760ed7df2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifySuperArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifySuperArgumentFix.java @@ -25,50 +25,49 @@ import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; import com.intellij.java.language.psi.*; -import consulo.language.editor.intention.QuickFixAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiManager; import consulo.language.psi.util.PsiTreeUtil; -import javax.annotation.Nonnull; - public class QualifySuperArgumentFix extends QualifyThisOrSuperArgumentFix { - public QualifySuperArgumentFix(@Nonnull PsiExpression expression, @Nonnull PsiClass psiClass) { - super(expression, psiClass); - } + public QualifySuperArgumentFix(PsiExpression expression, PsiClass psiClass) { + super(expression, psiClass); + } - @Override - protected String getQualifierText() { - return "super"; - } + @Override + protected String getQualifierText() { + return "super"; + } - @Override - protected PsiExpression getQualifier(PsiManager manager) { - return RefactoringChangeUtil.createSuperExpression(manager, myPsiClass); - } + @Override + @RequiredWriteAction + protected PsiExpression getQualifier(PsiManager manager) { + return RefactoringChangeUtil.createSuperExpression(manager, myPsiClass); + } - public static void registerQuickFixAction(@Nonnull PsiSuperExpression expr, HighlightInfo highlightInfo) { - LOG.assertTrue(expr.getQualifier() == null); - final PsiClass containingClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); - if (containingClass != null && containingClass.isInterface()) { - final PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(expr, - PsiMethodCallExpression.class); - if (callExpression != null) { - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(callExpression.getProject()); - for (PsiClass superClass : containingClass.getSupers()) { - if (superClass.isInterface()) { - final PsiMethodCallExpression copy = (PsiMethodCallExpression) callExpression.copy(); - final PsiExpression superQualifierCopy = copy.getMethodExpression().getQualifierExpression(); - LOG.assertTrue(superQualifierCopy != null); - superQualifierCopy.delete(); - if (((PsiMethodCallExpression) elementFactory.createExpressionFromText(copy.getText(), - superClass)).resolveMethod() != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, new QualifySuperArgumentFix(expr, - superClass)); + @RequiredWriteAction + public static void registerQuickFixAction(PsiSuperExpression expr, HighlightInfo.Builder hlBuilder) { + LOG.assertTrue(expr.getQualifier() == null); + PsiClass containingClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); + if (containingClass != null && containingClass.isInterface()) { + PsiMethodCallExpression callExpression = PsiTreeUtil.getParentOfType(expr, PsiMethodCallExpression.class); + if (callExpression != null) { + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(callExpression.getProject()); + for (PsiClass superClass : containingClass.getSupers()) { + if (superClass.isInterface()) { + PsiMethodCallExpression copy = (PsiMethodCallExpression) callExpression.copy(); + PsiExpression superQualifierCopy = copy.getMethodExpression().getQualifierExpression(); + LOG.assertTrue(superQualifierCopy != null); + superQualifierCopy.delete(); + PsiMethodCallExpression expressionFromText = + (PsiMethodCallExpression) elementFactory.createExpressionFromText(copy.getText(), superClass); + if (expressionFromText.resolveMethod() != null) { + hlBuilder.registerFix(new QualifySuperArgumentFix(expr, superClass)); + } + } + } } - } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisArgumentFix.java index dca8b0f6d5..308a506f19 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisArgumentFix.java @@ -28,91 +28,107 @@ import com.intellij.java.language.psi.infos.CandidateInfo; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; import consulo.document.util.TextRange; import consulo.language.editor.intention.PsiElementBaseIntentionAction; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiManager; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.Set; public class QualifyThisArgumentFix extends PsiElementBaseIntentionAction { - private final PsiThisExpression myExpression; - private final PsiClass myPsiClass; + private final PsiThisExpression myExpression; + private final PsiClass myPsiClass; + public QualifyThisArgumentFix(PsiThisExpression expression, PsiClass psiClass) { + myExpression = expression; + myPsiClass = psiClass; + } - public QualifyThisArgumentFix(@Nonnull PsiThisExpression expression, @Nonnull PsiClass psiClass) { - myExpression = expression; - myPsiClass = psiClass; - } - - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, @Nonnull PsiElement element) { - if (!myExpression.isValid()) return false; - if (!myPsiClass.isValid()) return false; - setText("Qualify this expression with \'" + myPsiClass.getQualifiedName() + "\'"); - return true; - } - - @Override - public void invoke(@Nonnull Project project, Editor editor, @Nonnull PsiElement element) throws IncorrectOperationException { - myExpression.replace(RefactoringChangeUtil.createThisExpression(PsiManager.getInstance(project), myPsiClass)); - } - - public static void registerQuickFixAction(CandidateInfo[] candidates, PsiCall call, HighlightInfo highlightInfo, final TextRange fixRange) { - if (candidates.length == 0) return; - - final Set containingClasses = new HashSet(); - PsiClass parentClass = PsiTreeUtil.getParentOfType(call, PsiClass.class); - while (parentClass != null) { - if (parentClass.hasModifierProperty(PsiModifier.STATIC)) break; - if (!(parentClass instanceof PsiAnonymousClass)) { - containingClasses.add(parentClass); - } - parentClass = PsiTreeUtil.getParentOfType(parentClass, PsiClass.class, true); + @Override + @RequiredReadAction + public boolean isAvailable(Project project, Editor editor, PsiElement element) { + if (!myExpression.isValid()) { + return false; + } + if (!myPsiClass.isValid()) { + return false; + } + setText(LocalizeValue.localizeTODO("Qualify this expression with \'" + myPsiClass.getQualifiedName() + "\'")); + return true; } - if (containingClasses.isEmpty()) return; - final PsiExpressionList list = call.getArgumentList(); - final PsiExpression[] expressions = list.getExpressions(); - if (expressions.length == 0) return; + @Override + @RequiredWriteAction + public void invoke(Project project, Editor editor, PsiElement element) throws IncorrectOperationException { + myExpression.replace(RefactoringChangeUtil.createThisExpression(PsiManager.getInstance(project), myPsiClass)); + } - for (int i1 = 0, expressionsLength = expressions.length; i1 < expressionsLength; i1++) { - final PsiExpression expression = expressions[i1]; - if (expression instanceof PsiThisExpression) { - final PsiType exprType = expression.getType(); - for (CandidateInfo candidate : candidates) { - PsiMethod method = (PsiMethod) candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - assert method != null; - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (expressions.length != parameters.length) { - continue; - } + public static void registerQuickFixAction( + CandidateInfo[] candidates, + PsiCall call, + HighlightInfo.Builder highlightInfo, + TextRange fixRange + ) { + if (candidates.length == 0) { + return; + } - PsiParameter parameter = parameters[i1]; + Set containingClasses = new HashSet<>(); + PsiClass parentClass = PsiTreeUtil.getParentOfType(call, PsiClass.class); + while (parentClass != null) { + if (parentClass.isStatic()) { + break; + } + if (!(parentClass instanceof PsiAnonymousClass)) { + containingClasses.add(parentClass); + } + parentClass = PsiTreeUtil.getParentOfType(parentClass, PsiClass.class, true); + } + if (containingClasses.isEmpty()) { + return; + } - PsiType parameterType = substitutor.substitute(parameter.getType()); - if (exprType == null || parameterType == null) { - continue; - } + PsiExpressionList list = call.getArgumentList(); + PsiExpression[] expressions = list.getExpressions(); + if (expressions.length == 0) { + return; + } - if (!TypeConversionUtil.isAssignable(parameterType, exprType)) { - final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(parameterType); - if (psiClass != null && containingClasses.contains(psiClass)) { - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, new QualifyThisArgumentFix((PsiThisExpression) expression, psiClass)); + for (int i1 = 0, expressionsLength = expressions.length; i1 < expressionsLength; i1++) { + if (expressions[i1] instanceof PsiThisExpression thisExpr) { + PsiType exprType = thisExpr.getType(); + for (CandidateInfo candidate : candidates) { + PsiMethod method = (PsiMethod)candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (expressions.length != parameters.length) { + continue; + } + + PsiParameter parameter = parameters[i1]; + + PsiType parameterType = substitutor.substitute(parameter.getType()); + if (exprType == null || parameterType == null) { + continue; + } + + if (!TypeConversionUtil.isAssignable(parameterType, exprType)) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(parameterType); + if (psiClass != null && containingClasses.contains(psiClass)) { + highlightInfo.newFix(new QualifyThisArgumentFix(thisExpr, psiClass)).fixRange(fixRange).register(); + } + } + } } - } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisOrSuperArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisOrSuperArgumentFix.java index e8bb916486..60148c0159 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisOrSuperArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/QualifyThisOrSuperArgumentFix.java @@ -30,19 +30,17 @@ import consulo.language.psi.PsiFile; import consulo.language.psi.PsiManager; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import javax.annotation.Nonnull; - public abstract class QualifyThisOrSuperArgumentFix implements SyntheticIntentionAction { protected static final Logger LOG = Logger.getInstance(QualifyThisOrSuperArgumentFix.class); protected final PsiExpression myExpression; protected final PsiClass myPsiClass; - private String myText; - + private LocalizeValue myText = LocalizeValue.empty(); - public QualifyThisOrSuperArgumentFix(@Nonnull PsiExpression expression, @Nonnull PsiClass psiClass) { + public QualifyThisOrSuperArgumentFix(PsiExpression expression, PsiClass psiClass) { myExpression = expression; myPsiClass = psiClass; } @@ -52,9 +50,8 @@ public boolean startInWriteAction() { return true; } - @Nonnull @Override - public String getText() { + public LocalizeValue getText() { return myText; } @@ -63,19 +60,19 @@ public String getText() { protected abstract PsiExpression getQualifier(PsiManager manager); @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { if (!myExpression.isValid()) { return false; } if (!myPsiClass.isValid()) { return false; } - myText = "Qualify " + getQualifierText() + " expression with \'" + myPsiClass.getQualifiedName() + "\'"; + myText = LocalizeValue.localizeTODO("Qualify " + getQualifierText() + " expression with \'" + myPsiClass.getQualifiedName() + "\'"); return true; } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { myExpression.replace(getQualifier(PsiManager.getInstance(project))); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/RemoveRedundantArgumentsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/RemoveRedundantArgumentsFix.java index aa412f931b..853f6e7cd2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/RemoveRedundantArgumentsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/RemoveRedundantArgumentsFix.java @@ -20,110 +20,137 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.codeEditor.Editor; import consulo.document.util.TextRange; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Arrays; /** * @author Danila Ponomarenko */ public class RemoveRedundantArgumentsFix implements SyntheticIntentionAction { - private final PsiMethod myTargetMethod; - private final PsiExpression[] myArguments; - private final PsiSubstitutor mySubstitutor; - - private RemoveRedundantArgumentsFix(@Nonnull PsiMethod targetMethod, - @Nonnull PsiExpression[] arguments, - @Nonnull PsiSubstitutor substitutor) { - myTargetMethod = targetMethod; - myArguments = arguments; - mySubstitutor = substitutor; - } - - @Nonnull - @Override - public String getText() { - return JavaQuickFixBundle.message("remove.redundant.arguments.text", JavaHighlightUtil.formatMethod(myTargetMethod)); - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - if (!myTargetMethod.isValid() || myTargetMethod.getContainingClass() == null) return false; - for (PsiExpression expression : myArguments) { - if (!expression.isValid()) return false; + private final PsiMethod myTargetMethod; + private final PsiExpression[] myArguments; + private final PsiSubstitutor mySubstitutor; + + private RemoveRedundantArgumentsFix( + PsiMethod targetMethod, + PsiExpression[] arguments, + PsiSubstitutor substitutor + ) { + myTargetMethod = targetMethod; + myArguments = arguments; + mySubstitutor = substitutor; } - if (!mySubstitutor.isValid()) return false; - return findRedundantArgument(myArguments, myTargetMethod.getParameterList().getParameters(), mySubstitutor) != null; - } - - @Nullable - private static PsiExpression[] findRedundantArgument(@Nonnull PsiExpression[] arguments, - @Nonnull PsiParameter[] parameters, - @Nonnull PsiSubstitutor substitutor) { - if (arguments.length <= parameters.length) return null; + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.removeRedundantArgumentsText(JavaHighlightUtil.formatMethod(myTargetMethod)); + } - for (int i = 0; i < parameters.length; i++) { - final PsiExpression argument = arguments[i]; - final PsiParameter parameter = parameters[i]; + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + if (!myTargetMethod.isValid() || myTargetMethod.getContainingClass() == null) { + return false; + } + for (PsiExpression expression : myArguments) { + if (!expression.isValid()) { + return false; + } + } + //noinspection SimplifiableIfStatement + if (!mySubstitutor.isValid()) { + return false; + } + + return findRedundantArgument(myArguments, myTargetMethod.getParameterList().getParameters(), mySubstitutor) != null; + } - final PsiType argumentType = argument.getType(); - if (argumentType == null) return null; - final PsiType parameterType = substitutor.substitute(parameter.getType()); + @Nullable + private static PsiExpression[] findRedundantArgument( + PsiExpression[] arguments, + PsiParameter[] parameters, + PsiSubstitutor substitutor + ) { + if (arguments.length <= parameters.length) { + return null; + } + + for (int i = 0; i < parameters.length; i++) { + final PsiExpression argument = arguments[i]; + final PsiParameter parameter = parameters[i]; + + final PsiType argumentType = argument.getType(); + if (argumentType == null) { + return null; + } + final PsiType parameterType = substitutor.substitute(parameter.getType()); + + if (!TypeConversionUtil.isAssignable(parameterType, argumentType)) { + return null; + } + } + + return Arrays.copyOfRange(arguments, parameters.length, arguments.length); + } - if (!TypeConversionUtil.isAssignable(parameterType, argumentType)) { - return null; - } + @Override + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } + final PsiExpression[] redundantArguments = + findRedundantArgument(myArguments, myTargetMethod.getParameterList().getParameters(), mySubstitutor); + if (redundantArguments != null) { + for (PsiExpression argument : redundantArguments) { + argument.delete(); + } + } } - return Arrays.copyOfRange(arguments, parameters.length, arguments.length); - } - - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - final PsiExpression[] redundantArguments = findRedundantArgument(myArguments, myTargetMethod.getParameterList().getParameters(), mySubstitutor); - if (redundantArguments != null) { - for (PsiExpression argument : redundantArguments) { - argument.delete(); - } + @Override + public boolean startInWriteAction() { + return true; } - } - - @Override - public boolean startInWriteAction() { - return true; - } - - public static void registerIntentions(@Nonnull JavaResolveResult[] candidates, - @Nonnull PsiExpressionList arguments, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange) { - for (JavaResolveResult candidate : candidates) { - registerIntention(arguments, highlightInfo, fixRange, candidate, arguments); + + public static void registerIntentions( + JavaResolveResult[] candidates, + PsiExpressionList arguments, + HighlightInfo.@Nullable Builder hlBuilder, + TextRange fixRange + ) { + if (hlBuilder == null) { + return; + } + for (JavaResolveResult candidate : candidates) { + registerIntention(arguments, hlBuilder, fixRange, candidate, arguments); + } } - } - - private static void registerIntention(@Nonnull PsiExpressionList arguments, - @Nullable HighlightInfo highlightInfo, - TextRange fixRange, - @Nonnull JavaResolveResult candidate, - @Nonnull PsiElement context) { - if (!candidate.isStaticsScopeCorrect()) return; - PsiMethod method = (PsiMethod) candidate.getElement(); - PsiSubstitutor substitutor = candidate.getSubstitutor(); - if (method != null && context.getManager().isInProject(method)) { - QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, new RemoveRedundantArgumentsFix(method, arguments.getExpressions(), substitutor)); + + private static void registerIntention( + PsiExpressionList arguments, + HighlightInfo.Builder hlBuilder, + TextRange fixRange, + JavaResolveResult candidate, + PsiElement context + ) { + if (!candidate.isStaticsScopeCorrect()) { + return; + } + PsiMethod method = (PsiMethod)candidate.getElement(); + PsiSubstitutor substitutor = candidate.getSubstitutor(); + if (method != null && context.getManager().isInProject(method)) { + hlBuilder.newFix(new RemoveRedundantArgumentsFix(method, arguments.getExpressions(), substitutor)) + .fixRange(fixRange) + .register(); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ReplaceAssignmentFromVoidWithStatementIntentionAction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ReplaceAssignmentFromVoidWithStatementIntentionAction.java index d8d9dc5016..a07c8127cb 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ReplaceAssignmentFromVoidWithStatementIntentionAction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/ReplaceAssignmentFromVoidWithStatementIntentionAction.java @@ -22,10 +22,9 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; public class ReplaceAssignmentFromVoidWithStatementIntentionAction implements SyntheticIntentionAction { private final PsiElement myParent; @@ -36,20 +35,18 @@ public ReplaceAssignmentFromVoidWithStatementIntentionAction(PsiElement parent, myLExpr = lExpr; } - @Nls - @Nonnull @Override - public String getText() { - return "Remove left side of assignment"; + public LocalizeValue getText() { + return LocalizeValue.localizeTODO("Remove left side of assignment"); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return true; } @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { if (!FileModificationService.getInstance().prepareFileForWrite(file)) { return; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/SimplifyBooleanExpressionFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/SimplifyBooleanExpressionFix.java index 5c4fb878eb..d391e85b93 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/SimplifyBooleanExpressionFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/SimplifyBooleanExpressionFix.java @@ -22,7 +22,7 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.application.ApplicationManager; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.ast.IElementType; import consulo.language.codeStyle.CodeStyleManager; import consulo.language.editor.FileModificationService; @@ -32,13 +32,12 @@ import consulo.language.psi.PsiManager; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.lang.StringUtil; import consulo.util.lang.ref.Ref; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -49,22 +48,15 @@ public class SimplifyBooleanExpressionFix extends LocalQuickFixOnPsiElement { // subExpressionValue == Boolean.TRUE or Boolean.FALSE if subExpression evaluates to boolean constant and needs to be replaced // otherwise subExpressionValue= null and we starting to simplify expression without any further knowledge - public SimplifyBooleanExpressionFix(@Nonnull PsiExpression subExpression, Boolean subExpressionValue) { + public SimplifyBooleanExpressionFix(PsiExpression subExpression, Boolean subExpressionValue) { super(subExpression); mySubExpressionValue = subExpressionValue; } @Override - @Nonnull - public String getText() { + public LocalizeValue getText() { PsiExpression expression = getSubExpression(); - return JavaQuickFixBundle.message("simplify.boolean.expression.text", expression.getText(), mySubExpressionValue); - } - - @Override - @Nonnull - public String getFamilyName() { - return JavaQuickFixBundle.message("simplify.boolean.expression.family"); + return JavaQuickFixLocalize.simplifyBooleanExpressionText(expression.getText(), mySubExpressionValue); } @Override @@ -78,7 +70,7 @@ public boolean isAvailable() { } @Override - public void invoke(@Nonnull final Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public void invoke(final Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { if (!isAvailable()) return; final PsiExpression expression = getSubExpression(); LOG.assertTrue(expression.isValid()); @@ -204,7 +196,7 @@ public void visitExpression(PsiExpression expression) { simplifyIfStatement(newExpression); } - public static boolean canBeSimplified(@Nonnull PsiExpression expression) { + public static boolean canBeSimplified(PsiExpression expression) { if (!(expression instanceof PsiConditionalExpression) && !PsiType.BOOLEAN.equals(expression.getType())) return false; @@ -249,7 +241,7 @@ private ExpressionVisitor(PsiManager psiManager, final boolean createResult) { falseExpression = createResult ? createExpression(psiManager, Boolean.toString(false)) : null; } - private static PsiExpression createExpression(final PsiManager psiManager, @NonNls String text) { + private static PsiExpression createExpression(final PsiManager psiManager, String text) { try { return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createExpressionFromText(text, null); } catch (IncorrectOperationException e) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/VariableArrayTypeFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/VariableArrayTypeFix.java index 17da916440..383eab9b18 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/VariableArrayTypeFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/VariableArrayTypeFix.java @@ -19,37 +19,37 @@ import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import consulo.application.WriteAction; import consulo.application.presentation.TypePresentationService; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.LocalQuickFixOnPsiElement; import consulo.language.editor.util.LanguageUndoUtil; import consulo.language.findUsage.FindUsagesProvider; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.lang.StringUtil; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class VariableArrayTypeFix extends LocalQuickFixOnPsiElement { - @Nonnull private final PsiArrayType myTargetType; - private final String myName; - private final String myFamilyName; + private final LocalizeValue myName; - private VariableArrayTypeFix(@Nonnull PsiArrayInitializerExpression initializer, @Nonnull PsiArrayType arrayType, @Nonnull PsiVariable variable) { + private VariableArrayTypeFix(PsiArrayInitializerExpression initializer, PsiArrayType arrayType, PsiVariable variable) { super(initializer); myTargetType = arrayType; PsiExpression myNewExpression = getNewExpressionLocal(initializer); - myName = myTargetType.equals(variable.getType()) && myNewExpression != null ? JavaQuickFixBundle.message("change.new.operator.type.text", getNewText(myNewExpression, initializer), - myTargetType.getCanonicalText(), "") : JavaQuickFixBundle.message("fix.variable.type.text", formatType(variable), variable.getName(), myTargetType.getCanonicalText()); - myFamilyName = JavaQuickFixBundle.message(myTargetType.equals(variable.getType()) && myNewExpression != null ? "change.new.operator.type.family" : "fix.variable.type.family"); + if (myTargetType.equals(variable.getType()) && myNewExpression != null) { + myName = JavaQuickFixLocalize.changeNewOperatorTypeText(getNewText(myNewExpression, initializer), myTargetType.getCanonicalText(), ""); + } + else { + myName = JavaQuickFixLocalize.fixVariableTypeText(formatType(variable), variable.getName(), myTargetType.getCanonicalText()); + } } @Nullable - public static VariableArrayTypeFix createFix(PsiArrayInitializerExpression initializer, @Nonnull PsiType componentType) { + public static VariableArrayTypeFix createFix(PsiArrayInitializerExpression initializer, PsiType componentType) { PsiArrayType arrayType = new PsiArrayType(componentType); PsiArrayInitializerExpression arrayInitializer = initializer; while (arrayInitializer.getParent() instanceof PsiArrayInitializerExpression) { @@ -63,7 +63,7 @@ public static VariableArrayTypeFix createFix(PsiArrayInitializerExpression initi return new VariableArrayTypeFix(arrayInitializer, arrayType, variable); } - private static String formatType(@Nonnull PsiVariable variable) { + private static String formatType(PsiVariable variable) { FindUsagesProvider provider = FindUsagesProvider.forLanguage(variable.getLanguage()); final String type = provider.getType(variable); if (StringUtil.isNotEmpty(type)) { @@ -82,7 +82,7 @@ private static PsiArrayInitializerExpression getInitializer(PsiArrayInitializerE return arrayInitializer; } - private static PsiVariable getVariableLocal(@Nonnull PsiArrayInitializerExpression initializer) { + private static PsiVariable getVariableLocal(PsiArrayInitializerExpression initializer) { PsiVariable variableLocal = null; final PsiElement parent = initializer.getParent(); @@ -102,7 +102,7 @@ private static PsiVariable getVariableLocal(@Nonnull PsiArrayInitializerExpressi return variableLocal; } - private static PsiNewExpression getNewExpressionLocal(@Nonnull PsiArrayInitializerExpression initializer) { + private static PsiNewExpression getNewExpressionLocal(PsiArrayInitializerExpression initializer) { PsiNewExpression newExpressionLocal = null; final PsiElement parent = initializer.getParent(); @@ -131,20 +131,13 @@ private static String getNewText(PsiElement myNewExpression, PsiArrayInitializer return newText; } - @Nonnull @Override - public String getText() { + public LocalizeValue getText() { return myName; } @Override - @Nonnull - public String getFamilyName() { - return myFamilyName; - } - - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public boolean isAvailable(Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { final PsiArrayInitializerExpression myInitializer = (PsiArrayInitializerExpression) startElement; final PsiVariable myVariable = getVariableLocal(myInitializer); @@ -152,7 +145,7 @@ public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Non } @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { + public void invoke(Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { final PsiArrayInitializerExpression myInitializer = (PsiArrayInitializerExpression) startElement; final PsiVariable myVariable = getVariableLocal(myInitializer); if (myVariable == null) { @@ -180,7 +173,7 @@ public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull Psi } } - private void fixVariableType(@Nonnull Project project, @Nonnull PsiFile file, PsiVariable myVariable) { + private void fixVariableType(Project project, PsiFile file, PsiVariable myVariable) { myVariable.normalizeDeclaration(); myVariable.getTypeElement().replace(JavaPsiFacade.getElementFactory(project).createTypeElement(myTargetType)); JavaCodeStyleManager.getInstance(project).shortenClassReferences(myVariable); @@ -191,7 +184,7 @@ private void fixVariableType(@Nonnull Project project, @Nonnull PsiFile file, Ps } private void fixArrayInitializer(PsiArrayInitializerExpression myInitializer, PsiNewExpression myNewExpression) { - @NonNls String text = "new " + myTargetType.getCanonicalText() + "{}"; + String text = "new " + myTargetType.getCanonicalText() + "{}"; final PsiNewExpression newExpression = (PsiNewExpression) JavaPsiFacade.getElementFactory(myNewExpression.getProject()).createExpressionFromText(text, myNewExpression.getParent()); final PsiElement[] children = newExpression.getChildren(); children[children.length - 1].replace(myInitializer); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapArrayToArraysAsListFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapArrayToArraysAsListFix.java index 24a3f4b8db..b4d91bb64f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapArrayToArraysAsListFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapArrayToArraysAsListFix.java @@ -18,14 +18,12 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import com.intellij.java.language.psi.util.InheritanceUtil; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Dmitry Batkovich @@ -33,20 +31,19 @@ public class WrapArrayToArraysAsListFix extends MethodArgumentFix { public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory(); - protected WrapArrayToArraysAsListFix(final @Nonnull PsiExpressionList list, + protected WrapArrayToArraysAsListFix(final PsiExpressionList list, final int i, - final @Nonnull PsiType toType, - final @Nonnull ArgumentFixerActionFactory fixerActionFactory) { + final PsiType toType, + final ArgumentFixerActionFactory fixerActionFactory) { super(list, i, toType, fixerActionFactory); } - @Nonnull @Override - public String getText() { + public LocalizeValue getText() { if (myArgList.getExpressions().length == 1) { - return JavaQuickFixBundle.message("wrap.array.to.arrays.as.list.single.parameter.text"); + return JavaQuickFixLocalize.wrapArrayToArraysAsListSingleParameterText(); } else { - return JavaQuickFixBundle.message("wrap.array.to.arrays.as.list.parameter.text", myIndex + 1); + return JavaQuickFixLocalize.wrapArrayToArraysAsListParameterText(myIndex + 1); } } @@ -83,8 +80,7 @@ protected PsiExpression getModifiedArgument(final PsiExpression expression, @Nullable private static PsiClass getJavaUtilList(final PsiElement context) { - return JavaPsiFacade.getInstance(context.getProject()).findClass(JavaClassNames.JAVA_UTIL_LIST, - context.getResolveScope()); + return JavaPsiFacade.getInstance(context.getProject()).findClass(CommonClassNames.JAVA_UTIL_LIST, context.getResolveScope()); } @Override diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapExpressionFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapExpressionFix.java index 23d1c48045..28863bfc79 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapExpressionFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapExpressionFix.java @@ -16,21 +16,21 @@ package com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.FileModificationService; -import consulo.language.editor.intention.QuickFixAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.editor.rawHighlight.HighlightInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.LinkedHashSet; import java.util.Set; @@ -38,133 +38,155 @@ * @author ven */ public class WrapExpressionFix implements SyntheticIntentionAction { + private final PsiExpression myExpression; + private final PsiClassType myExpectedType; + private final boolean myPrimitiveExpected; - private final PsiExpression myExpression; - private final PsiClassType myExpectedType; - private final boolean myPrimitiveExpected; - - public WrapExpressionFix(PsiType expectedType, PsiExpression expression) { - myExpression = expression; - myExpectedType = getClassType(expectedType, expression); - myPrimitiveExpected = expectedType instanceof PsiPrimitiveType; - } + public WrapExpressionFix(PsiType expectedType, PsiExpression expression) { + myExpression = expression; + myExpectedType = getClassType(expectedType, expression); + myPrimitiveExpected = expectedType instanceof PsiPrimitiveType; + } - @Nullable - private static PsiClassType getClassType(PsiType type, PsiElement place) { - if (type instanceof PsiClassType) { - return (PsiClassType) type; - } else if (type instanceof PsiPrimitiveType) { - return ((PsiPrimitiveType) type).getBoxedType(place.getManager(), GlobalSearchScope.allScope(place.getProject())); + @Nullable + private static PsiClassType getClassType(PsiType type, PsiElement place) { + if (type instanceof PsiClassType classType) { + return classType; + } + else if (type instanceof PsiPrimitiveType primitiveType) { + return primitiveType.getBoxedType(place.getManager(), GlobalSearchScope.allScope(place.getProject())); + } + return null; } - return null; - } - @Override - @Nonnull - public String getText() { - final PsiMethod wrapper = myExpression.isValid() && myExpectedType != null ? findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected) : null; - final String methodPresentation = wrapper != null ? wrapper.getContainingClass().getName() + "." + wrapper.getName() : ""; - return JavaQuickFixBundle.message("wrap.expression.using.static.accessor.text", methodPresentation); - } + @Override + @RequiredReadAction + public LocalizeValue getText() { + PsiMethod wrapper = myExpression.isValid() && myExpectedType != null + ? findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected) + : null; + String methodPresentation = wrapper != null ? wrapper.getContainingClass().getName() + "." + wrapper.getName() : ""; + return JavaQuickFixLocalize.wrapExpressionUsingStaticAccessorText(methodPresentation); + } - @Nullable - private static PsiMethod findWrapper(PsiType type, @Nonnull PsiClassType expectedType, boolean primitiveExpected) { - PsiClass aClass = expectedType.resolve(); - if (aClass != null) { - PsiType expectedReturnType = expectedType; - if (primitiveExpected) { - expectedReturnType = PsiPrimitiveType.getUnboxedType(expectedType); - } - if (expectedReturnType == null) return null; - PsiMethod[] methods = aClass.getMethods(); - final Set wrapperMethods = new LinkedHashSet(); - for (PsiMethod method : methods) { - if (method.hasModifierProperty(PsiModifier.STATIC) - && method.getParameterList().getParametersCount() == 1 - && method.getParameterList().getParameters()[0].getType().isAssignableFrom(type) - && method.getReturnType() != null - && expectedReturnType.equals(method.getReturnType())) { - final String methodName = method.getName(); - if (methodName.startsWith("parse") || methodName.equals("valueOf")) { - return method; - } - wrapperMethods.add(method); + @Nullable + private static PsiMethod findWrapper(PsiType type, PsiClassType expectedType, boolean primitiveExpected) { + PsiClass aClass = expectedType.resolve(); + if (aClass != null) { + PsiType expectedReturnType = expectedType; + if (primitiveExpected) { + expectedReturnType = PsiPrimitiveType.getUnboxedType(expectedType); + } + if (expectedReturnType == null) { + return null; + } + PsiMethod[] methods = aClass.getMethods(); + Set wrapperMethods = new LinkedHashSet<>(); + for (PsiMethod method : methods) { + if (method.isStatic() + && method.getParameterList().getParametersCount() == 1 + && method.getParameterList().getParameters()[0].getType().isAssignableFrom(type) + && method.getReturnType() != null + && expectedReturnType.equals(method.getReturnType())) { + String methodName = method.getName(); + if (methodName.startsWith("parse") || methodName.equals("valueOf")) { + return method; + } + wrapperMethods.add(method); + } + } + if (!wrapperMethods.isEmpty()) { + return wrapperMethods.iterator().next(); + } } - } - if (!wrapperMethods.isEmpty()) return wrapperMethods.iterator().next(); - } - return null; - } + return null; + } - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return myExpression.isValid() - && myExpression.getManager().isInProject(myExpression) - && myExpectedType != null - && myExpectedType.isValid() - && myExpression.getType() != null - && findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected) != null; - } + @Override + @RequiredReadAction + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return myExpression.isValid() + && myExpression.getManager().isInProject(myExpression) + && myExpectedType != null + && myExpectedType.isValid() + && myExpression.getType() != null + && findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected) != null; + } - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - PsiMethod wrapper = findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected); - assert wrapper != null; - PsiElementFactory factory = JavaPsiFacade.getInstance(file.getProject()).getElementFactory(); - @NonNls String methodCallText = "Foo." + wrapper.getName() + "()"; - PsiMethodCallExpression call = (PsiMethodCallExpression) factory.createExpressionFromText(methodCallText, - null); - call.getArgumentList().add(myExpression); - ((PsiReferenceExpression) call.getMethodExpression().getQualifierExpression()).bindToElement( - wrapper.getContainingClass()); - myExpression.replace(call); - } + @Override + @RequiredWriteAction + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) { + return; + } + PsiMethod wrapper = findWrapper(myExpression.getType(), myExpectedType, myPrimitiveExpected); + assert wrapper != null; + PsiElementFactory factory = JavaPsiFacade.getInstance(file.getProject()).getElementFactory(); + String methodCallText = "Foo." + wrapper.getName() + "()"; + PsiMethodCallExpression call = (PsiMethodCallExpression) factory.createExpressionFromText(methodCallText, null); + call.getArgumentList().add(myExpression); + ((PsiReferenceExpression) call.getMethodExpression().getQualifierExpression()).bindToElement(wrapper.getContainingClass()); + myExpression.replace(call); + } - @Override - public boolean startInWriteAction() { - return true; - } + @Override + public boolean startInWriteAction() { + return true; + } - public static void registerWrapAction(JavaResolveResult[] candidates, PsiExpression[] expressions, HighlightInfo highlightInfo) { - PsiType expectedType = null; - PsiExpression expr = null; + @RequiredReadAction + public static void registerWrapAction( + JavaResolveResult[] candidates, + PsiExpression[] expressions, + HighlightInfo.@Nullable Builder hlBuilder + ) { + if (hlBuilder == null) { + return; + } + PsiType expectedType = null; + PsiExpression expr = null; - nextMethod: - for (int i = 0; i < candidates.length && expectedType == null; i++) { - final JavaResolveResult candidate = candidates[i]; - final PsiSubstitutor substitutor = candidate.getSubstitutor(); - final PsiElement element = candidate.getElement(); - assert element != null; - final PsiMethod method = (PsiMethod) element; - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (!method.isVarArgs() && parameters.length != expressions.length) continue; - for (int j = 0; j < expressions.length; j++) { - PsiExpression expression = expressions[j]; - final PsiType exprType = expression.getType(); - if (exprType != null) { - PsiType paramType = parameters[Math.min(j, parameters.length - 1)].getType(); - if (paramType instanceof PsiEllipsisType) { - paramType = ((PsiEllipsisType) paramType).getComponentType(); - } - paramType = substitutor != null ? substitutor.substitute(paramType) : paramType; - if (paramType.isAssignableFrom(exprType)) continue; - final PsiClassType classType = getClassType(paramType, expression); - if (expectedType == null && classType != null && findWrapper(exprType, classType, paramType instanceof PsiPrimitiveType) != null) { - expectedType = paramType; - expr = expression; - } else { - expectedType = null; - expr = null; - continue nextMethod; - } + nextMethod: + for (int i = 0; i < candidates.length && expectedType == null; i++) { + JavaResolveResult candidate = candidates[i]; + PsiSubstitutor substitutor = candidate.getSubstitutor(); + PsiElement element = candidate.getElement(); + assert element != null; + PsiMethod method = (PsiMethod) element; + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (!method.isVarArgs() && parameters.length != expressions.length) { + continue; + } + for (int j = 0; j < expressions.length; j++) { + PsiExpression expression = expressions[j]; + PsiType exprType = expression.getType(); + if (exprType != null) { + PsiType paramType = parameters[Math.min(j, parameters.length - 1)].getType(); + if (paramType instanceof PsiEllipsisType ellipsisType) { + paramType = ellipsisType.getComponentType(); + } + paramType = substitutor != null ? substitutor.substitute(paramType) : paramType; + if (paramType.isAssignableFrom(exprType)) { + continue; + } + PsiClassType classType = getClassType(paramType, expression); + if (expectedType == null && classType != null + && findWrapper(exprType, classType, paramType instanceof PsiPrimitiveType) != null) { + expectedType = paramType; + expr = expression; + } + else { + expectedType = null; + expr = null; + continue nextMethod; + } + } + } } - } - } - if (expectedType != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, expr.getTextRange(), new WrapExpressionFix(expectedType, expr)); + if (expectedType != null) { + hlBuilder.newFix(new WrapExpressionFix(expectedType, expr)).fixRange(expr.getTextRange()).register(); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java index b623921958..0fd225d35c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java @@ -18,108 +18,99 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Dmitry Batkovich */ public class WrapLongWithMathToIntExactFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { - public final static MyMethodArgumentFixerFactory REGISTAR = new MyMethodArgumentFixerFactory(); - - private final PsiType myType; - - public WrapLongWithMathToIntExactFix(final PsiType type, final @Nonnull PsiExpression expression) { - super(expression); - myType = type; - } - - @Nonnull - @Override - public String getText() { - return getFamilyName(); - } - - @Override - public void invoke(@Nonnull Project project, - @Nonnull PsiFile file, - @Nullable Editor editor, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - startElement.replace(getModifiedExpression(startElement)); - } - - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - return startElement.isValid() && - startElement.getManager().isInProject(startElement) && - PsiUtil.isLanguageLevel8OrHigher(startElement) && - areSameTypes(myType, PsiType.INT) && - areSameTypes(((PsiExpression) startElement).getType(), PsiType.LONG); - } - - private static boolean areSameTypes(@Nullable PsiType type, @Nonnull PsiPrimitiveType expected) { - return !(type == null || - !type.isValid() || - (!type.equals(expected) && !expected.getBoxedTypeName().equals(type.getCanonicalText(false)))); - } - - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("wrap.long.with.math.to.int.text"); - } - - private static PsiElement getModifiedExpression(PsiElement expression) { - return JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText("java.lang.Math.toIntExact(" + expression.getText() + ")", expression); - } - - private static class MyMethodArgumentFix extends MethodArgumentFix implements HighPriorityAction { - - protected MyMethodArgumentFix(@Nonnull PsiExpressionList list, int i, @Nonnull PsiType toType, @Nonnull ArgumentFixerActionFactory fixerActionFactory) { - super(list, i, toType, fixerActionFactory); + public final static MyMethodArgumentFixerFactory REGISTAR = new MyMethodArgumentFixerFactory(); + + private final PsiType myType; + + public WrapLongWithMathToIntExactFix(final PsiType type, final PsiExpression expression) { + super(expression); + myType = type; } - @Nls - @Nonnull @Override - public String getText() { - return myArgList.getExpressions().length == 1 ? JavaQuickFixBundle.message("wrap.long.with.math.to.int.parameter.single.text") : JavaQuickFixBundle.message("wrap.long.with.math.to.int" + - ".parameter" + - ".multiple.text", myIndex + 1); + public LocalizeValue getText() { + return JavaQuickFixLocalize.wrapLongWithMathToIntText(); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); + public void invoke(Project project, + PsiFile file, + @Nullable Editor editor, + PsiElement startElement, + PsiElement endElement) { + startElement.replace(getModifiedExpression(startElement)); } - } - public static class MyMethodArgumentFixerFactory extends ArgumentFixerActionFactory { - @Nullable @Override - protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - return areSameTypes(toType, PsiType.INT) ? (PsiExpression) getModifiedExpression(expression) : null; + public boolean isAvailable(Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { + return startElement.isValid() && + startElement.getManager().isInProject(startElement) && + PsiUtil.isLanguageLevel8OrHigher(startElement) && + areSameTypes(myType, PsiType.INT) && + areSameTypes(((PsiExpression) startElement).getType(), PsiType.LONG); } - @Override - public boolean areTypesConvertible(final PsiType exprType, final PsiType parameterType, @Nonnull final PsiElement context) { - return parameterType.isConvertibleFrom(exprType) || (areSameTypes(parameterType, PsiType.INT) && areSameTypes(exprType, PsiType.LONG)); + private static boolean areSameTypes(@Nullable PsiType type, PsiPrimitiveType expected) { + return !(type == null || + !type.isValid() || + (!type.equals(expected) && !expected.getBoxedTypeName().equals(type.getCanonicalText(false)))); } - @Override - public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { - return new MyMethodArgumentFix(list, i, toType, this); + private static PsiElement getModifiedExpression(PsiElement expression) { + return JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText("java.lang.Math.toIntExact(" + expression.getText() + ")", expression); + } + + private static class MyMethodArgumentFix extends MethodArgumentFix implements HighPriorityAction { + + protected MyMethodArgumentFix(PsiExpressionList list, int i, PsiType toType, ArgumentFixerActionFactory fixerActionFactory) { + super(list, i, toType, fixerActionFactory); + } + + @Override + public LocalizeValue getText() { + if (myArgList.getExpressions().length == 1) { + return JavaQuickFixLocalize.wrapLongWithMathToIntParameterSingleText(); + } + else { + return JavaQuickFixLocalize.wrapLongWithMathToIntParameterMultipleText(myIndex + 1); + } + } + + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); + } + } + + public static class MyMethodArgumentFixerFactory extends ArgumentFixerActionFactory { + @Nullable + @Override + protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { + return areSameTypes(toType, PsiType.INT) ? (PsiExpression) getModifiedExpression(expression) : null; + } + + @Override + public boolean areTypesConvertible(final PsiType exprType, final PsiType parameterType, final PsiElement context) { + return parameterType.isConvertibleFrom(exprType) || (areSameTypes(parameterType, PsiType.INT) && areSameTypes(exprType, PsiType.LONG)); + } + + @Override + public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { + return new MyMethodArgumentFix(list, i, toType, this); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java index 86a5d49931..ff4409929b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapObjectWithOptionalOfNullableFix.java @@ -20,8 +20,9 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.editor.intention.BaseIntentionAction; import consulo.language.editor.intention.HighPriorityAction; @@ -29,134 +30,137 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.Nls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; /** * @author Dmitry Batkovich */ public class WrapObjectWithOptionalOfNullableFix extends MethodArgumentFix implements HighPriorityAction { - public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory(); - - protected WrapObjectWithOptionalOfNullableFix(final @Nonnull PsiExpressionList list, - final int i, - final @Nonnull PsiType toType, - final @Nonnull ArgumentFixerActionFactory fixerActionFactory) { - super(list, i, toType, fixerActionFactory); - } - - @Nonnull - @Override - public String getText() { - if (myArgList.getExpressionCount() == 1) { - return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); - } else { - return JavaQuickFixBundle.message("wrap.with.optional.parameter.text", myIndex + 1); + public static final ArgumentFixerActionFactory REGISTAR = new MyFixerActionFactory(); + + protected WrapObjectWithOptionalOfNullableFix( + PsiExpressionList list, + int i, + PsiType toType, + ArgumentFixerActionFactory fixerActionFactory + ) { + super(list, i, toType, fixerActionFactory); } - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); - } - - public static IntentionAction createFix(@Nullable PsiType type, @Nonnull PsiExpression expression) { - class MyFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { - protected MyFix(@Nullable PsiElement element) { - super(element); - } - - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("wrap.with.optional.single.parameter.text"); - } - - @Override - public void invoke(@Nonnull Project project, - @Nonnull PsiFile file, - @Nullable Editor editor, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - startElement.replace(getModifiedExpression((PsiExpression) getStartElement())); - } - - @Override - public boolean isAvailable(@Nonnull Project project, - @Nonnull PsiFile file, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - return BaseIntentionAction.canModify(startElement) && - PsiUtil.isLanguageLevel8OrHigher(startElement) && areConvertible(((PsiExpression) startElement).getType(), type); - } - - @Nonnull - @Override - public String getText() { - return getFamilyName(); - } - } - return new MyFix(expression); - } - - public static class MyFixerActionFactory extends ArgumentFixerActionFactory { - @Nullable @Override - protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - return getModifiedExpression(expression); + public LocalizeValue getText() { + if (myArgList.getExpressionCount() == 1) { + return JavaQuickFixLocalize.wrapWithOptionalSingleParameterText(); + } + else { + return JavaQuickFixLocalize.wrapWithOptionalParameterText(myIndex + 1); + } } @Override - public boolean areTypesConvertible(@Nonnull final PsiType exprType, @Nonnull final PsiType parameterType, @Nonnull final PsiElement context) { - return parameterType.isConvertibleFrom(exprType) || areConvertible(exprType, parameterType); + @RequiredReadAction + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); } - @Override - public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { - return new WrapObjectWithOptionalOfNullableFix(list, i, toType, this); - } - } - - private static boolean areConvertible(@Nullable PsiType exprType, @Nullable PsiType parameterType) { - if (exprType == null || - !exprType.isValid() || - !(parameterType instanceof PsiClassType) || - !parameterType.isValid()) { - return false; - } - final PsiClassType.ClassResolveResult resolve = ((PsiClassType) parameterType).resolveGenerics(); - final PsiClass resolvedClass = resolve.getElement(); - if (resolvedClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolvedClass.getQualifiedName())) { - return false; + public static IntentionAction createFix(@Nullable PsiType type, PsiExpression expression) { + class MyFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { + protected MyFix(@Nullable PsiElement element) { + super(element); + } + + @RequiredReadAction + @Override + public void invoke( + Project project, + PsiFile file, + @Nullable Editor editor, + PsiElement startElement, + PsiElement endElement + ) { + startElement.replace(getModifiedExpression((PsiExpression)getStartElement())); + } + + @Override + @RequiredReadAction + public boolean isAvailable( + Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement + ) { + return BaseIntentionAction.canModify(startElement) + && PsiUtil.isLanguageLevel8OrHigher(startElement) + && areConvertible(((PsiExpression)startElement).getType(), type); + } + + @Override + public LocalizeValue getText() { + return JavaQuickFixLocalize.wrapWithOptionalSingleParameterText(); + } + } + return new MyFix(expression); } - final Collection values = resolve.getSubstitutor().getSubstitutionMap().values(); - if (values.isEmpty()) { - return true; + public static class MyFixerActionFactory extends ArgumentFixerActionFactory { + + @Nullable + @Override + @RequiredReadAction + protected PsiExpression getModifiedArgument(PsiExpression expression, PsiType toType) throws IncorrectOperationException { + return getModifiedExpression(expression); + } + + @Override + public boolean areTypesConvertible( + PsiType exprType, + PsiType parameterType, + PsiElement context + ) { + return parameterType.isConvertibleFrom(exprType) || areConvertible(exprType, parameterType); + } + + @Override + public MethodArgumentFix createFix(PsiExpressionList list, int i, PsiType toType) { + return new WrapObjectWithOptionalOfNullableFix(list, i, toType, this); + } } - if (values.size() > 1) { - return false; + + private static boolean areConvertible(@Nullable PsiType exprType, @Nullable PsiType parameterType) { + if (exprType == null + || !exprType.isValid() + || !(parameterType instanceof PsiClassType) + || !parameterType.isValid()) { + return false; + } + PsiClassType.ClassResolveResult resolve = ((PsiClassType)parameterType).resolveGenerics(); + PsiClass resolvedClass = resolve.getElement(); + if (resolvedClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(resolvedClass.getQualifiedName())) { + return false; + } + + Collection values = resolve.getSubstitutor().getSubstitutionMap().values(); + if (values.isEmpty()) { + return true; + } + if (values.size() > 1) { + return false; + } + PsiType optionalTypeParameter = ContainerUtil.getFirstItem(values); + return optionalTypeParameter != null && TypeConversionUtil.isAssignable(optionalTypeParameter, exprType); } - final PsiType optionalTypeParameter = ContainerUtil.getFirstItem(values); - if (optionalTypeParameter == null) { - return false; + + @RequiredReadAction + private static PsiExpression getModifiedExpression(PsiExpression expression) { + Project project = expression.getProject(); + Nullability nullability = NullabilityUtil.getExpressionNullability(expression, true); + String methodName = nullability == Nullability.NOT_NULL ? "of" : "ofNullable"; + String newExpressionText = CommonClassNames.JAVA_UTIL_OPTIONAL + "." + methodName + "(" + expression.getText() + ")"; + return JavaPsiFacade.getElementFactory(project).createExpressionFromText(newExpressionText, expression); } - return TypeConversionUtil.isAssignable(optionalTypeParameter, exprType); - } - - @Nonnull - private static PsiExpression getModifiedExpression(PsiExpression expression) { - final Project project = expression.getProject(); - final Nullability nullability = NullabilityUtil.getExpressionNullability(expression, true); - String methodName = nullability == Nullability.NOT_NULL ? "of" : "ofNullable"; - final String newExpressionText = CommonClassNames.JAVA_UTIL_OPTIONAL + "." + methodName + "(" + expression.getText() + ")"; - return JavaPsiFacade.getElementFactory(project).createExpressionFromText(newExpressionText, expression); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapStringWithFileFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapStringWithFileFix.java index 04c04fe4d2..4ee78b6d79 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapStringWithFileFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/quickfix/WrapStringWithFileFix.java @@ -18,107 +18,95 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.codeEditor.Editor; -import consulo.java.analysis.impl.JavaQuickFixBundle; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.inspection.LocalQuickFixAndIntentionActionOnPsiElement; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class WrapStringWithFileFix extends LocalQuickFixAndIntentionActionOnPsiElement implements HighPriorityAction { - public final static MyMethodArgumentFixerFactory REGISTAR = new MyMethodArgumentFixerFactory(); - - @Nullable - private final PsiType myType; - - public WrapStringWithFileFix(@Nullable PsiType type, @Nonnull PsiExpression expression) { - super(expression); - myType = type; - } - - @Nls - @Nonnull - @Override - public String getText() { - return getFamilyName(); - } - - @Nls - @Nonnull - @Override - public String getFamilyName() { - return JavaQuickFixBundle.message("wrap.with.java.io.file.text"); - } - - - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - return myType != null && myType.isValid() && myType.equalsToText(JavaClassNames.JAVA_IO_FILE) && startElement.isValid() && startElement.getManager().isInProject(startElement) && - isStringType(startElement); - } - - @Override - public void invoke(@Nonnull Project project, @Nonnull PsiFile file, @Nullable Editor editor, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) { - startElement.replace(getModifiedExpression(startElement)); - } - - private static boolean isStringType(@Nonnull PsiElement expression) { - if (!(expression instanceof PsiExpression)) { - return false; - } - final PsiType type = ((PsiExpression) expression).getType(); - if (type == null) { - return false; - } - return type.equalsToText(JavaClassNames.JAVA_LANG_STRING); - } + public final static MyMethodArgumentFixerFactory REGISTAR = new MyMethodArgumentFixerFactory(); - private static PsiElement getModifiedExpression(@Nonnull PsiElement expression) { - return JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(PsiKeyword.NEW + " " + JavaClassNames.JAVA_IO_FILE + "(" + expression.getText() + ")", expression); - } - - private static class MyMethodArgumentFix extends MethodArgumentFix implements HighPriorityAction { + @Nullable + private final PsiType myType; - protected MyMethodArgumentFix(@Nonnull PsiExpressionList list, int i, @Nonnull PsiType toType, @Nonnull ArgumentFixerActionFactory fixerActionFactory) { - super(list, i, toType, fixerActionFactory); + public WrapStringWithFileFix(@Nullable PsiType type, PsiExpression expression) { + super(expression); + myType = type; } - @Nls - @Nonnull @Override - public String getText() { - return myArgList.getExpressions().length == 1 ? JavaQuickFixBundle.message("wrap.with.java.io.file.parameter.single.text") : JavaQuickFixBundle.message("wrap.with.java.io.file.parameter" + - ".multiple" + - ".text", myIndex + 1); + public LocalizeValue getText() { + return JavaQuickFixLocalize.wrapWithJavaIoFileText(); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); + public boolean isAvailable(Project project, PsiFile file, PsiElement startElement, PsiElement endElement) { + return myType != null && myType.isValid() && myType.equalsToText(CommonClassNames.JAVA_IO_FILE) && startElement.isValid() && startElement.getManager().isInProject(startElement) && + isStringType(startElement); } - } - public static class MyMethodArgumentFixerFactory extends ArgumentFixerActionFactory { - @Nullable @Override - protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - return isStringType(expression) && toType.equalsToText(JavaClassNames.JAVA_IO_FILE) ? (PsiExpression) getModifiedExpression(expression) : null; + public void invoke(Project project, PsiFile file, @Nullable Editor editor, PsiElement startElement, PsiElement endElement) { + startElement.replace(getModifiedExpression(startElement)); } - @Override - public boolean areTypesConvertible(@Nonnull final PsiType exprType, @Nonnull final PsiType parameterType, @Nonnull final PsiElement context) { - return parameterType.isConvertibleFrom(exprType) || (parameterType.equalsToText(JavaClassNames.JAVA_IO_FILE) && exprType.equalsToText(JavaClassNames.JAVA_LANG_STRING)); + private static boolean isStringType(PsiElement expression) { + if (!(expression instanceof PsiExpression)) { + return false; + } + final PsiType type = ((PsiExpression) expression).getType(); + if (type == null) { + return false; + } + return type.equalsToText(CommonClassNames.JAVA_LANG_STRING); } - @Override - public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { - return new MyMethodArgumentFix(list, i, toType, this); + private static PsiElement getModifiedExpression(PsiElement expression) { + return JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(PsiKeyword.NEW + " " + CommonClassNames.JAVA_IO_FILE + "(" + expression.getText() + ")", expression); + } + + private static class MyMethodArgumentFix extends MethodArgumentFix implements HighPriorityAction { + + protected MyMethodArgumentFix(PsiExpressionList list, int i, PsiType toType, ArgumentFixerActionFactory fixerActionFactory) { + super(list, i, toType, fixerActionFactory); + } + + @Override + public LocalizeValue getText() { + if (myArgList.getExpressions().length == 1) { + return JavaQuickFixLocalize.wrapWithJavaIoFileParameterSingleText(); + } + else { + return JavaQuickFixLocalize.wrapWithJavaIoFileParameterMultipleText(myIndex + 1); + } + } + + @Override + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return PsiUtil.isLanguageLevel8OrHigher(file) && super.isAvailable(project, editor, file); + } + } + + public static class MyMethodArgumentFixerFactory extends ArgumentFixerActionFactory { + @Nullable + @Override + protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { + return isStringType(expression) && toType.equalsToText(CommonClassNames.JAVA_IO_FILE) ? (PsiExpression) getModifiedExpression(expression) : null; + } + + @Override + public boolean areTypesConvertible(final PsiType exprType, final PsiType parameterType, final PsiElement context) { + return parameterType.isConvertibleFrom(exprType) || (parameterType.equalsToText(CommonClassNames.JAVA_IO_FILE) && exprType.equalsToText(CommonClassNames.JAVA_LANG_STRING)); + } + + @Override + public MethodArgumentFix createFix(final PsiExpressionList list, final int i, final PsiType toType) { + return new MyMethodArgumentFix(list, i, toType, this); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/AssignmentFilteringMemoryState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/AssignmentFilteringMemoryState.java index 8b259a6a7f..2f803a2a65 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/AssignmentFilteringMemoryState.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/AssignmentFilteringMemoryState.java @@ -9,7 +9,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.java.language.psi.PsiLambdaExpression; import com.intellij.java.language.psi.PsiParameter; -import javax.annotation.Nonnull; /** * A memory state that may ignore type constraint known from assignment. @@ -32,7 +31,7 @@ public AssignmentFilteringMemoryState(AssignmentFilteringMemoryState toCopy) } @Override - protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @Nonnull DfType dfType) + protected DfType filterDfTypeOnAssignment(DfaVariableValue var, DfType dfType) { if(ControlFlowAnalyzer.isTempVariable(var) || (!(dfType instanceof DfReferenceType)) || var.getPsiVariable() instanceof PsiParameter && var.getPsiVariable().getParent().getParent() instanceof PsiLambdaExpression) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/ExpressionVariableDescriptor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/ExpressionVariableDescriptor.java index 2bc5117469..cb0c612bef 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/ExpressionVariableDescriptor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/ExpressionVariableDescriptor.java @@ -10,9 +10,8 @@ import com.intellij.java.language.psi.PsiReferenceExpression; import com.intellij.java.language.psi.PsiType; import consulo.util.collection.HashingStrategy; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Objects; public final class ExpressionVariableDescriptor implements VariableDescriptor @@ -20,10 +19,9 @@ public final class ExpressionVariableDescriptor implements VariableDescriptor public static final HashingStrategy EXPRESSION_HASHING_STRATEGY = new PsiExpressionStrategy(); private final - @Nonnull PsiExpression myExpression; - public ExpressionVariableDescriptor(@Nonnull PsiExpression expression) + public ExpressionVariableDescriptor(PsiExpression expression) { myExpression = expression; } @@ -35,7 +33,6 @@ public boolean isStable() } public - @Nonnull PsiExpression getExpression() { return myExpression; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java index 6b1987c516..fb4ef57650 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/guess/impl/GuessManagerImpl.java @@ -13,6 +13,7 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpressionUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ServiceImpl; import consulo.application.util.CachedValueProvider; import consulo.content.scope.SearchScope; @@ -27,580 +28,592 @@ import consulo.project.Project; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; +import consulo.util.collection.Maps; import consulo.util.collection.MultiMap; import consulo.util.lang.BitUtil; +import org.jspecify.annotations.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; @Singleton @ServiceImpl public final class GuessManagerImpl extends GuessManager { - private final MethodPatternMap myMethodPatternMap = new MethodPatternMap(); - - private final Project myProject; - - @Inject - public GuessManagerImpl(Project project) { - myProject = project; - initMethodPatterns(); - } - - private void initMethodPatterns() { - // Collection - myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("contains", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("remove", 1, 0)); - - // Vector - myMethodPatternMap.addPattern(new MethodPattern("add", 2, 1)); - myMethodPatternMap.addPattern(new MethodPattern("addElement", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("elementAt", 1, -1)); - myMethodPatternMap.addPattern(new MethodPattern("firstElement", 0, -1)); - myMethodPatternMap.addPattern(new MethodPattern("lastElement", 0, -1)); - myMethodPatternMap.addPattern(new MethodPattern("get", 1, -1)); - myMethodPatternMap.addPattern(new MethodPattern("indexOf", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("indexOf", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("insertElementAt", 2, 0)); - myMethodPatternMap.addPattern(new MethodPattern("removeElement", 1, 0)); - myMethodPatternMap.addPattern(new MethodPattern("set", 2, 1)); - myMethodPatternMap.addPattern(new MethodPattern("setElementAt", 2, 0)); - } - - @Override - @Nonnull - public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) { - HashSet typesSet = new HashSet<>(); - - PsiType type = containerExpr.getType(); - PsiType elemType; - if ((elemType = getGenericElementType(type)) != null) { - return new PsiType[]{elemType}; + private final MethodPatternMap myMethodPatternMap = new MethodPatternMap(); + + private final Project myProject; + + @Inject + public GuessManagerImpl(Project project) { + myProject = project; + initMethodPatterns(); + } + + private void initMethodPatterns() { + // Collection + myMethodPatternMap.addPattern(new MethodPattern("add", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("contains", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("remove", 1, 0)); + + // Vector + myMethodPatternMap.addPattern(new MethodPattern("add", 2, 1)); + myMethodPatternMap.addPattern(new MethodPattern("addElement", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("elementAt", 1, -1)); + myMethodPatternMap.addPattern(new MethodPattern("firstElement", 0, -1)); + myMethodPatternMap.addPattern(new MethodPattern("lastElement", 0, -1)); + myMethodPatternMap.addPattern(new MethodPattern("get", 1, -1)); + myMethodPatternMap.addPattern(new MethodPattern("indexOf", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("indexOf", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("lastIndexOf", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("insertElementAt", 2, 0)); + myMethodPatternMap.addPattern(new MethodPattern("removeElement", 1, 0)); + myMethodPatternMap.addPattern(new MethodPattern("set", 2, 1)); + myMethodPatternMap.addPattern(new MethodPattern("setElementAt", 2, 0)); } - if (containerExpr instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)containerExpr).resolve(); - if (refElement instanceof PsiVariable) { + @Override + @RequiredReadAction + public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) { + HashSet typesSet = new HashSet<>(); + + PsiType type = containerExpr.getType(); + PsiType elemType; + if ((elemType = getGenericElementType(type)) != null) { + return new PsiType[]{elemType}; + } - PsiFile file = refElement.getContainingFile(); - if (file == null) { - file = containerExpr.getContainingFile(); // implicit variable in jsp + if (containerExpr instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiVariable variable) { + PsiFile file = variable.getContainingFile(); + if (file == null) { + file = containerExpr.getContainingFile(); // implicit variable in jsp + } + HashSet checkedVariables = new HashSet<>(); + addTypesByVariable(typesSet, variable, file, checkedVariables, CHECK_USAGE | CHECK_DOWN, rangeToIgnore); + checkedVariables.clear(); + addTypesByVariable(typesSet, variable, file, checkedVariables, CHECK_UP, rangeToIgnore); } - HashSet checkedVariables = new HashSet<>(); - addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_USAGE | CHECK_DOWN, rangeToIgnore); - checkedVariables.clear(); - addTypesByVariable(typesSet, (PsiVariable)refElement, file, checkedVariables, CHECK_UP, rangeToIgnore); - } + + return typesSet.toArray(PsiType.createArray(typesSet.size())); } - return typesSet.toArray(PsiType.createArray(typesSet.size())); - } - - @Nullable - private static PsiType getGenericElementType(PsiType collectionType) { - if (collectionType instanceof PsiClassType) { - PsiClassType classType = (PsiClassType)collectionType; - PsiType[] parameters = classType.getParameters(); - if (parameters.length == 1) { - return parameters[0]; - } + @Nullable + private static PsiType getGenericElementType(PsiType collectionType) { + if (collectionType instanceof PsiClassType) { + PsiClassType classType = (PsiClassType)collectionType; + PsiType[] parameters = classType.getParameters(); + if (parameters.length == 1) { + return parameters[0]; + } + } + return null; } - return null; - } - - @Override - @Nonnull - public PsiType[] guessTypeToCast(PsiExpression expr) { - LinkedHashSet types = new LinkedHashSet<>(getControlFlowExpressionTypeConjuncts(expr)); - addExprTypesWhenContainerElement(types, expr); - addExprTypesByDerivedClasses(types, expr); - return types.toArray(PsiType.createArray(types.size())); - } - - @Nonnull - @Override - public MultiMap getControlFlowExpressionTypes(@Nonnull PsiExpression forPlace, boolean honorAssignments) { - PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); - if (scope == null) { - PsiFile file = forPlace.getContainingFile(); - if (!(file instanceof PsiCodeFragment)) { - return MultiMap.empty(); - } - scope = file; + + @Override + @RequiredReadAction + public PsiType[] guessTypeToCast(PsiExpression expr) { + LinkedHashSet types = new LinkedHashSet<>(getControlFlowExpressionTypeConjuncts(expr)); + addExprTypesWhenContainerElement(types, expr); + addExprTypesByDerivedClasses(types, expr); + return types.toArray(PsiType.createArray(types.size())); } - DataFlowRunner runner = createRunner(honorAssignments, scope); + @Override + @RequiredReadAction + public MultiMap getControlFlowExpressionTypes(PsiExpression forPlace, boolean honorAssignments) { + PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); + if (scope == null) { + PsiFile file = forPlace.getContainingFile(); + if (!(file instanceof PsiCodeFragment)) { + return MultiMap.empty(); + } + scope = file; + } + + DataFlowRunner runner = createRunner(honorAssignments, scope); - final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace); - if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { - return visitor.getResult(); + ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace); + if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { + return visitor.getResult(); + } + return MultiMap.empty(); } - return MultiMap.empty(); - } - - @Nullable - private static PsiType getTypeFromDataflow(PsiExpression forPlace, boolean honorAssignments) { - PsiType type = forPlace.getType(); - TypeConstraint initial = type == null ? TypeConstraints.TOP : TypeConstraints.instanceOf(type); - PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); - if (scope == null) { - PsiFile file = forPlace.getContainingFile(); - if (!(file instanceof PsiCodeFragment)) { + + @Nullable + @RequiredReadAction + private static PsiType getTypeFromDataflow(PsiExpression forPlace, boolean honorAssignments) { + PsiType type = forPlace.getType(); + TypeConstraint initial = type == null ? TypeConstraints.TOP : TypeConstraints.instanceOf(type); + PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace); + if (scope == null) { + PsiFile file = forPlace.getContainingFile(); + if (!(file instanceof PsiCodeFragment)) { + return null; + } + scope = file; + } + + DataFlowRunner runner = createRunner(honorAssignments, scope); + + class Visitor extends CastTrackingVisitor { + TypeConstraint constraint = TypeConstraints.BOTTOM; + + @Override + protected void beforeExpressionPush( + DfaValue value, + PsiExpression expression, + @Nullable TextRange range, + DfaMemoryState state + ) { + if (expression == forPlace && range == null) { + if (!(value instanceof DfaVariableValue dfaVarValue) || dfaVarValue.isFlushableByCalls()) { + value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); + } + constraint = constraint.join(TypeConstraint.fromDfType(state.getDfType(value))); + } + super.beforeExpressionPush(value, expression, range, state); + } + + @Override + boolean isInteresting(DfaValue value, PsiExpression expression) { + return (!(value instanceof DfaVariableValue dfaVarValue) || dfaVarValue.isFlushableByCalls()) + && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression, forPlace); + } + } + Visitor visitor = new Visitor(); + if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { + return visitor.constraint.meet(initial).getPsiType(scope.getProject()); + } return null; - } - scope = file; } - DataFlowRunner runner = createRunner(honorAssignments, scope); - - class Visitor extends CastTrackingVisitor { - TypeConstraint constraint = TypeConstraints.BOTTOM; - - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState state) { - if (expression == forPlace && range == null) { - if (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) { - value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); - } - constraint = constraint.join(TypeConstraint.fromDfType(state.getDfType(value))); - } - super.beforeExpressionPush(value, expression, range, state); - } - - @Override - boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { - return (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) && - ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression, forPlace); - } - } - final Visitor visitor = new Visitor(); - if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) { - return visitor.constraint.meet(initial).getPsiType(scope.getProject()); - } - return null; - } - - @Nonnull - private static DataFlowRunner createRunner(boolean honorAssignments, PsiElement scope) { - return honorAssignments ? new DataFlowRunner(scope.getProject()) : new DataFlowRunner(scope.getProject()) { - @Nonnull - @Override - protected DfaMemoryState createMemoryState() { - return new AssignmentFilteringMemoryState(getFactory()); - } - }; - } - - private static PsiElement getTopmostBlock(PsiElement scope) { - assert scope.isValid(); - PsiElement lastScope = scope; - while (true) { - final PsiCodeBlock lastCodeBlock = PsiTreeUtil.getParentOfType(lastScope, PsiCodeBlock.class, true); - if (lastCodeBlock == null) { - break; - } - lastScope = lastCodeBlock; - } - if (lastScope == scope) { - PsiFile file = scope.getContainingFile(); - if (file instanceof PsiCodeFragment) { - return file; - } + private static DataFlowRunner createRunner(boolean honorAssignments, PsiElement scope) { + return honorAssignments ? new DataFlowRunner(scope.getProject()) : new DataFlowRunner(scope.getProject()) { + @Override + protected DfaMemoryState createMemoryState() { + return new AssignmentFilteringMemoryState(getFactory()); + } + }; } - return lastScope; - } - private void addExprTypesByDerivedClasses(LinkedHashSet set, PsiExpression expr) { - PsiType type = expr.getType(); - if (!(type instanceof PsiClassType)) { - return; - } - PsiClass refClass = PsiUtil.resolveClassInType(type); - if (refClass == null) { - return; + private static PsiElement getTopmostBlock(PsiElement scope) { + assert scope.isValid(); + PsiElement lastScope = scope; + while (true) { + PsiCodeBlock lastCodeBlock = PsiTreeUtil.getParentOfType(lastScope, PsiCodeBlock.class, true); + if (lastCodeBlock == null) { + break; + } + lastScope = lastCodeBlock; + } + if (lastScope == scope && scope.getContainingFile() instanceof PsiCodeFragment codeFragment) { + return codeFragment; + } + return lastScope; } - PsiManager manager = PsiManager.getInstance(myProject); - PsiElementProcessor.CollectElementsWithLimit processor = new PsiElementProcessor.CollectElementsWithLimit<>(5); - ClassInheritorsSearch.search(refClass).forEach(new PsiElementProcessorAdapter<>(processor)); - if (processor.isOverflow()) { - return; - } + private void addExprTypesByDerivedClasses(LinkedHashSet set, PsiExpression expr) { + PsiType type = expr.getType(); + if (!(type instanceof PsiClassType)) { + return; + } + PsiClass refClass = PsiUtil.resolveClassInType(type); + if (refClass == null) { + return; + } - for (PsiClass derivedClass : processor.getCollection()) { - if (derivedClass instanceof PsiAnonymousClass) { - continue; - } - PsiType derivedType = JavaPsiFacade.getElementFactory(manager.getProject()).createType(derivedClass); - set.add(derivedType); - } - } - - private void addExprTypesWhenContainerElement(LinkedHashSet set, PsiExpression expr) { - if (expr instanceof PsiMethodCallExpression) { - PsiMethodCallExpression callExpr = (PsiMethodCallExpression)expr; - PsiReferenceExpression methodExpr = callExpr.getMethodExpression(); - String methodName = methodExpr.getReferenceName(); - MethodPattern pattern = myMethodPatternMap.findPattern(methodName, callExpr.getArgumentList().getExpressionCount()); - if (pattern != null && pattern.parameterIndex < 0/* return value */) { - PsiExpression qualifier = methodExpr.getQualifierExpression(); - if (qualifier != null) { - PsiType[] types = guessContainerElementType(qualifier, null); - for (PsiType type : types) { - if (type instanceof PsiClassType) { - if (((PsiClassType)type).resolve() instanceof PsiAnonymousClass) { + PsiManager manager = PsiManager.getInstance(myProject); + PsiElementProcessor.CollectElementsWithLimit processor = new PsiElementProcessor.CollectElementsWithLimit<>(5); + ClassInheritorsSearch.search(refClass).forEach(new PsiElementProcessorAdapter<>(processor)); + if (processor.isOverflow()) { + return; + } + + for (PsiClass derivedClass : processor.getCollection()) { + if (derivedClass instanceof PsiAnonymousClass) { continue; - } } - set.add(type); - } + PsiType derivedType = JavaPsiFacade.getElementFactory(manager.getProject()).createType(derivedClass); + set.add(derivedType); } - } - } - } - - private static final int CHECK_USAGE = 0x01; - private static final int CHECK_UP = 0x02; - private static final int CHECK_DOWN = 0x04; - - private void addTypesByVariable(HashSet typesSet, - PsiVariable var, - PsiFile scopeFile, - HashSet checkedVariables, - int flags, - TextRange rangeToIgnore) { - if (!checkedVariables.add(var)) { - return; } - //System.out.println("analyzing usages of " + var + " in file " + scopeFile); - SearchScope searchScope = new LocalSearchScope(scopeFile); - if (BitUtil.isSet(flags, CHECK_USAGE) || BitUtil.isSet(flags, CHECK_DOWN)) { - for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) { - PsiElement ref = varRef.getElement(); + @RequiredReadAction + private void addExprTypesWhenContainerElement(LinkedHashSet set, PsiExpression expr) { + if (expr instanceof PsiMethodCallExpression callExpr) { + PsiReferenceExpression methodExpr = callExpr.getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + MethodPattern pattern = myMethodPatternMap.findPattern(methodName, callExpr.getArgumentList().getExpressionCount()); + if (pattern != null && pattern.parameterIndex < 0/* return value */) { + PsiExpression qualifier = methodExpr.getQualifierExpression(); + if (qualifier != null) { + PsiType[] types = guessContainerElementType(qualifier, null); + for (PsiType type : types) { + if (type instanceof PsiClassType classType && classType.resolve() instanceof PsiAnonymousClass) { + continue; + } + set.add(type); + } + } + } + } + } - if (BitUtil.isSet(flags, CHECK_USAGE)) { - PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore); - if (type != null && !(type instanceof PsiPrimitiveType)) { - typesSet.add(type); - } + private static final int CHECK_USAGE = 0x01; + private static final int CHECK_UP = 0x02; + private static final int CHECK_DOWN = 0x04; + + @RequiredReadAction + private void addTypesByVariable( + HashSet typesSet, + PsiVariable var, + PsiFile scopeFile, + HashSet checkedVariables, + int flags, + TextRange rangeToIgnore + ) { + if (!checkedVariables.add(var)) { + return; + } + //System.out.println("analyzing usages of " + var + " in file " + scopeFile); + SearchScope searchScope = new LocalSearchScope(scopeFile); + + if (BitUtil.isSet(flags, CHECK_USAGE) || BitUtil.isSet(flags, CHECK_DOWN)) { + for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) { + PsiElement ref = varRef.getElement(); + + if (BitUtil.isSet(flags, CHECK_USAGE)) { + PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore); + if (type != null && !(type instanceof PsiPrimitiveType)) { + typesSet.add(type); + } + } + + if (BitUtil.isSet(flags, CHECK_DOWN)) { + if (ref.getParent() instanceof PsiExpressionList list + && list.getParent() instanceof PsiMethodCallExpression methodCall) { //TODO : new + int argIndex = ArrayUtil.indexOf(list.getExpressions(), ref); + + PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); + if (method != null) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (argIndex < parameters.length) { + addTypesByVariable( + typesSet, + parameters[argIndex], + method.getContainingFile(), + checkedVariables, + flags | CHECK_USAGE, + rangeToIgnore + ); + } + } + } + } + } } - if (BitUtil.isSet(flags, CHECK_DOWN)) { - if (ref.getParent() instanceof PsiExpressionList && ref.getParent().getParent() instanceof PsiMethodCallExpression) { //TODO : new - PsiExpressionList list = (PsiExpressionList)ref.getParent(); - int argIndex = ArrayUtil.indexOf(list.getExpressions(), ref); + if (BitUtil.isSet(flags, CHECK_UP) + && var instanceof PsiParameter + && var.getParent() instanceof PsiParameterList list + && list.getParent() instanceof PsiMethod) { + PsiParameter[] parameters = list.getParameters(); + int argIndex = -1; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + if (parameter.equals(var)) { + argIndex = i; + break; + } + } - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)list.getParent(); - PsiMethod method = (PsiMethod)methodCall.getMethodExpression().resolve(); - if (method != null) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (argIndex < parameters.length) { - addTypesByVariable(typesSet, parameters[argIndex], method.getContainingFile(), checkedVariables, flags | CHECK_USAGE, - rangeToIgnore); - } + PsiMethod method = (PsiMethod)var.getParent().getParent(); + //System.out.println("analyzing usages of " + method + " in file " + scopeFile); + for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) { + PsiElement ref = methodRef.getElement(); + if (ref.getParent() instanceof PsiMethodCallExpression methodCall) { + PsiExpression[] args = methodCall.getArgumentList().getExpressions(); + if (args.length <= argIndex) { + continue; + } + PsiExpression arg = args[argIndex]; + if (arg instanceof PsiReferenceExpression argRefExpr && argRefExpr.resolve() instanceof PsiVariable variable) { + addTypesByVariable( + typesSet, + variable, + scopeFile, + checkedVariables, + flags | CHECK_USAGE, + rangeToIgnore + ); + } + //TODO : constructor + } } - } } - } } - if (BitUtil.isSet(flags, CHECK_UP)) { - if (var instanceof PsiParameter && var.getParent() instanceof PsiParameterList && var.getParent().getParent() instanceof PsiMethod) { - PsiParameterList list = (PsiParameterList)var.getParent(); - PsiParameter[] parameters = list.getParameters(); - int argIndex = -1; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - if (parameter.equals(var)) { - argIndex = i; - break; - } - } - - PsiMethod method = (PsiMethod)var.getParent().getParent(); - //System.out.println("analyzing usages of " + method + " in file " + scopeFile); - for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) { - PsiElement ref = methodRef.getElement(); - if (ref.getParent() instanceof PsiMethodCallExpression) { - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)ref.getParent(); + @Nullable + @RequiredReadAction + private static PsiType guessElementTypeFromReference( + MethodPatternMap methodPatternMap, + PsiElement ref, + TextRange rangeToIgnore + ) { + if (ref.getParent() instanceof PsiReferenceExpression parentExpr + && ref.equals(parentExpr.getQualifierExpression()) + && parentExpr.getParent() instanceof PsiMethodCallExpression methodCall) { + String methodName = parentExpr.getReferenceName(); PsiExpression[] args = methodCall.getArgumentList().getExpressions(); - if (args.length <= argIndex) { - continue; - } - PsiExpression arg = args[argIndex]; - if (arg instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression)arg).resolve(); - if (refElement instanceof PsiVariable) { - addTypesByVariable(typesSet, (PsiVariable)refElement, scopeFile, checkedVariables, flags | CHECK_USAGE, rangeToIgnore); - } + MethodPattern pattern = methodPatternMap.findPattern(methodName, args.length); + if (pattern != null) { + if (pattern.parameterIndex < 0) { // return value + if (methodCall.getParent() instanceof PsiTypeCastExpression typeCast + && (rangeToIgnore == null || !rangeToIgnore.contains(methodCall.getTextRange()))) { + return typeCast.getType(); + } + } + else { + return args[pattern.parameterIndex].getType(); + } } - //TODO : constructor - } } - } - } - } - - @Nullable - private static PsiType guessElementTypeFromReference(MethodPatternMap methodPatternMap, - PsiElement ref, - TextRange rangeToIgnore) { - PsiElement refParent = ref.getParent(); - if (refParent instanceof PsiReferenceExpression) { - PsiReferenceExpression parentExpr = (PsiReferenceExpression)refParent; - if (ref.equals(parentExpr.getQualifierExpression()) && parentExpr.getParent() instanceof PsiMethodCallExpression) { - String methodName = parentExpr.getReferenceName(); - PsiMethodCallExpression methodCall = (PsiMethodCallExpression)parentExpr.getParent(); - PsiExpression[] args = methodCall.getArgumentList().getExpressions(); - MethodPattern pattern = methodPatternMap.findPattern(methodName, args.length); - if (pattern != null) { - if (pattern.parameterIndex < 0) { // return value - if (methodCall.getParent() instanceof PsiTypeCastExpression && - (rangeToIgnore == null || !rangeToIgnore.contains(methodCall.getTextRange()))) { - return ((PsiTypeCastExpression)methodCall.getParent()).getType(); - } - } - else { - return args[pattern.parameterIndex].getType(); - } - } - } - } - return null; - } - - @Nonnull - @Override - public List getControlFlowExpressionTypeConjuncts(@Nonnull PsiExpression expr, boolean honorAssignments) { - if (expr.getType() instanceof PsiPrimitiveType) { - return Collections.emptyList(); - } - PsiExpression place = PsiUtil.skipParenthesizedExprDown(expr); - if (place == null) { - return Collections.emptyList(); + return null; } - List result = null; - if (!ControlFlowAnalyzer.inlinerMayInferPreciseType(place)) { - GuessTypeVisitor visitor = tryGuessingTypeWithoutDfa(place, honorAssignments); - if (!visitor.isDfaNeeded()) { - result = visitor.mySpecificType == null ? - Collections.emptyList() : Collections.singletonList(DfaPsiUtil.tryGenerify(expr, visitor.mySpecificType)); - } - } - if (result == null) { - PsiType psiType = getTypeFromDataflow(expr, honorAssignments); - if (psiType instanceof PsiIntersectionType) { - result = ContainerUtil.mapNotNull(((PsiIntersectionType)psiType).getConjuncts(), type -> DfaPsiUtil.tryGenerify(expr, type)); - } - else if (psiType != null) { - result = Collections.singletonList(DfaPsiUtil.tryGenerify(expr, psiType)); - } - else { - result = Collections.emptyList(); - } - } - result = ContainerUtil.filter(result, t -> { - PsiClass typeClass = PsiUtil.resolveClassInType(t); - return typeClass == null || PsiUtil.isAccessible(typeClass, expr, null); - }); - if (result.equals(Collections.singletonList(TypeConversionUtil.erasure(expr.getType())))) { - return Collections.emptyList(); - } - return result; - } - - @Nonnull - private static GuessTypeVisitor tryGuessingTypeWithoutDfa(PsiExpression place, boolean honorAssignments) { - List exprsAndVars = getPotentiallyAffectingElements(place); - GuessTypeVisitor visitor = new GuessTypeVisitor(place, honorAssignments); - for (PsiElement e : exprsAndVars) { - e.accept(visitor); - if (e == place || visitor.isDfaNeeded()) { - break; - } - } - return visitor; - } - - private static List getPotentiallyAffectingElements(PsiExpression place) { - PsiElement topmostBlock = getTopmostBlock(place); - return LanguageCachedValueUtil.getCachedValue(topmostBlock, () -> { - List list = - SyntaxTraverser.psiTraverser(topmostBlock).filter(e -> e instanceof PsiExpression || e instanceof PsiLocalVariable).toList(); - return new CachedValueProvider.Result<>(list, topmostBlock); - }); - } - - private static class GuessTypeVisitor extends JavaElementVisitor { - private static final CallMatcher OBJECT_GET_CLASS = - CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0); - private final - @Nonnull - PsiExpression myPlace; - PsiType mySpecificType; - private boolean myNeedDfa; - private boolean myDeclared; - private final boolean myHonorAssignments; - - GuessTypeVisitor(@Nonnull PsiExpression place, boolean honorAssignments) { - myPlace = place; - myHonorAssignments = honorAssignments; - } + @Override + @RequiredReadAction + public List getControlFlowExpressionTypeConjuncts(PsiExpression expr, boolean honorAssignments) { + if (expr.getType() instanceof PsiPrimitiveType) { + return Collections.emptyList(); + } + PsiExpression place = PsiUtil.skipParenthesizedExprDown(expr); + if (place == null) { + return Collections.emptyList(); + } - protected void handleAssignment(@Nullable PsiExpression expression) { - if (!myHonorAssignments || expression == null) { - return; - } - PsiType type = expression.getType(); - if (type instanceof PsiPrimitiveType) { - type = ((PsiPrimitiveType)type).getBoxedType(expression); - } - PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; - if (rawType == null || rawType.equals(PsiType.NULL)) { - return; - } - if (mySpecificType == null) { - mySpecificType = rawType; - } - else if (!mySpecificType.equals(rawType)) { - myNeedDfa = true; - } + List result = null; + if (!ControlFlowAnalyzer.inlinerMayInferPreciseType(place)) { + GuessTypeVisitor visitor = tryGuessingTypeWithoutDfa(place, honorAssignments); + if (!visitor.isDfaNeeded()) { + result = visitor.mySpecificType == null ? + Collections.emptyList() : Collections.singletonList(DfaPsiUtil.tryGenerify(expr, visitor.mySpecificType)); + } + } + if (result == null) { + PsiType psiType = getTypeFromDataflow(expr, honorAssignments); + if (psiType instanceof PsiIntersectionType intersectionType) { + result = ContainerUtil.mapNotNull(intersectionType.getConjuncts(), type -> DfaPsiUtil.tryGenerify(expr, type)); + } + else if (psiType != null) { + result = Collections.singletonList(DfaPsiUtil.tryGenerify(expr, psiType)); + } + else { + result = Collections.emptyList(); + } + } + result = ContainerUtil.filter( + result, + t -> { + PsiClass typeClass = PsiUtil.resolveClassInType(t); + return typeClass == null || PsiUtil.isAccessible(typeClass, expr, null); + } + ); + if (result.equals(Collections.singletonList(TypeConversionUtil.erasure(expr.getType())))) { + return Collections.emptyList(); + } + return result; } - @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { - if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) { - handleAssignment(expression.getRExpression()); - } - super.visitAssignmentExpression(expression); + private static GuessTypeVisitor tryGuessingTypeWithoutDfa(PsiExpression place, boolean honorAssignments) { + List exprsAndVars = getPotentiallyAffectingElements(place); + GuessTypeVisitor visitor = new GuessTypeVisitor(place, honorAssignments); + for (PsiElement e : exprsAndVars) { + e.accept(visitor); + if (e == place || visitor.isDfaNeeded()) { + break; + } + } + return visitor; + } + + private static List getPotentiallyAffectingElements(PsiExpression place) { + PsiElement topmostBlock = getTopmostBlock(place); + return LanguageCachedValueUtil.getCachedValue( + topmostBlock, + () -> { + List list = SyntaxTraverser.psiTraverser(topmostBlock) + .filter(e -> e instanceof PsiExpression || e instanceof PsiLocalVariable) + .toList(); + return new CachedValueProvider.Result<>(list, topmostBlock); + } + ); } - @Override - public void visitLocalVariable(PsiLocalVariable variable) { - if (ExpressionUtils.isReferenceTo(myPlace, variable)) { - myDeclared = true; - handleAssignment(variable.getInitializer()); - } - super.visitLocalVariable(variable); - } + private static class GuessTypeVisitor extends JavaElementVisitor { + private static final CallMatcher OBJECT_GET_CLASS = + CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0); + private final PsiExpression myPlace; + PsiType mySpecificType; + private boolean myNeedDfa; + private boolean myDeclared; + private final boolean myHonorAssignments; - @Override - public void visitTypeCastExpression(PsiTypeCastExpression expression) { - PsiExpression operand = expression.getOperand(); - if (operand != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) { - myNeedDfa = true; - } - super.visitTypeCastExpression(expression); - } + GuessTypeVisitor(PsiExpression place, boolean honorAssignments) { + myPlace = place; + myHonorAssignments = honorAssignments; + } - @Override - public void visitMethodCallExpression(PsiMethodCallExpression call) { - if (OBJECT_GET_CLASS.test(call)) { - PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); - if (qualifier != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) { - myNeedDfa = true; - } - } - super.visitMethodCallExpression(call); - } + protected void handleAssignment(@Nullable PsiExpression expression) { + if (!myHonorAssignments || expression == null) { + return; + } + PsiType type = expression.getType(); + if (type instanceof PsiPrimitiveType primitiveType) { + type = primitiveType.getBoxedType(expression); + } + PsiType rawType = type instanceof PsiClassType classType ? classType.rawType() : type; + if (rawType == null || rawType.equals(PsiType.NULL)) { + return; + } + if (mySpecificType == null) { + mySpecificType = rawType; + } + else if (!mySpecificType.equals(rawType)) { + myNeedDfa = true; + } + } - @Override - public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { - if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) { - myNeedDfa = true; - } - super.visitInstanceOfExpression(expression); - } + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) { + handleAssignment(expression.getRExpression()); + } + super.visitAssignmentExpression(expression); + } - public boolean isDfaNeeded() { - if (myNeedDfa) { - return true; - } - if (myDeclared || mySpecificType == null) { - return false; - } - PsiType type = myPlace.getType(); - PsiType rawType = type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type; - return !mySpecificType.equals(rawType); - } - } + @Override + @RequiredReadAction + public void visitLocalVariable(PsiLocalVariable variable) { + if (ExpressionUtils.isReferenceTo(myPlace, variable)) { + myDeclared = true; + handleAssignment(variable.getInitializer()); + } + super.visitLocalVariable(variable); + } - abstract static class CastTrackingVisitor extends StandardInstructionVisitor { - @Override - public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - DfaValue value = memState.pop(); - memState.push(adjustValue(runner, value, instruction.getCasted())); - return super.visitTypeCast(instruction, runner, memState); - } + @Override + public void visitTypeCastExpression(PsiTypeCastExpression expression) { + PsiExpression operand = expression.getOperand(); + if (operand != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) { + myNeedDfa = true; + } + super.visitTypeCastExpression(expression); + } - @Override - public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - DfaValue dfaRight = memState.pop(); - DfaValue dfaLeft = memState.pop(); - memState.push(adjustValue(runner, dfaLeft, instruction.getLeft())); - memState.push(dfaRight); - return super.visitInstanceof(instruction, runner, memState); - } + @Override + @RequiredReadAction + public void visitMethodCallExpression(PsiMethodCallExpression call) { + if (OBJECT_GET_CLASS.test(call)) { + PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); + if (qualifier != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) { + myNeedDfa = true; + } + } + super.visitMethodCallExpression(call); + } - private DfaValue adjustValue(DataFlowRunner runner, DfaValue value, @Nullable PsiExpression expression) { - if (expression != null && isInteresting(value, expression)) { - value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); - } - return value; - } + @Override + public void visitInstanceOfExpression(PsiInstanceOfExpression expression) { + if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) { + myNeedDfa = true; + } + super.visitInstanceOfExpression(expression); + } - boolean isInteresting(@Nonnull DfaValue value, @Nonnull PsiExpression expression) { - return true; + public boolean isDfaNeeded() { + if (myNeedDfa) { + return true; + } + if (myDeclared || mySpecificType == null) { + return false; + } + PsiType type = myPlace.getType(); + PsiType rawType = type instanceof PsiClassType classType ? classType.rawType() : type; + return !mySpecificType.equals(rawType); + } } - } - private static final class ExpressionTypeInstructionVisitor extends CastTrackingVisitor { - private final Map myResult = new HashMap<>(); - private final PsiElement myForPlace; + abstract static class CastTrackingVisitor extends StandardInstructionVisitor { + @Override + public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + DfaValue value = memState.pop(); + memState.push(adjustValue(runner, value, instruction.getCasted())); + return super.visitTypeCast(instruction, runner, memState); + } - private ExpressionTypeInstructionVisitor(@Nonnull PsiElement forPlace) { - myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace); - } + @Override + public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + DfaValue dfaRight = memState.pop(); + DfaValue dfaLeft = memState.pop(); + memState.push(adjustValue(runner, dfaLeft, instruction.getLeft())); + memState.push(dfaRight); + return super.visitInstanceof(instruction, runner, memState); + } + + private DfaValue adjustValue(DataFlowRunner runner, DfaValue value, @Nullable PsiExpression expression) { + if (expression != null && isInteresting(value, expression)) { + value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression)); + } + return value; + } - MultiMap getResult() { - MultiMap result = MultiMap.createSet(ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY); - Project project = myForPlace.getProject(); - myResult.forEach((value, constraint) -> { - if (value.getDescriptor() instanceof ExpressionVariableDescriptor) { - PsiExpression expression = ((ExpressionVariableDescriptor)value.getDescriptor()).getExpression(); - PsiType type = constraint.getPsiType(project); - if (type instanceof PsiIntersectionType) { - result.putValues(expression, Arrays.asList(((PsiIntersectionType)type).getConjuncts())); - } - else if (type != null) { - result.putValue(expression, type); - } - } - }); - return result; + boolean isInteresting(DfaValue value, PsiExpression expression) { + return true; + } } - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState state) { - if (range == null && myForPlace == expression) { - ((DfaMemoryStateImpl)state).forRecordedVariableTypes((var, dfType) -> { - myResult.merge(var, TypeConstraint.fromDfType(dfType), TypeConstraint::join); - }); - } - super.beforeExpressionPush(value, expression, range, state); + private static final class ExpressionTypeInstructionVisitor extends CastTrackingVisitor { + private final Map myResult = new HashMap<>(); + private final PsiElement myForPlace; + + private ExpressionTypeInstructionVisitor(PsiElement forPlace) { + myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace); + } + + MultiMap getResult() { + MultiMap result = + MultiMap.createSet(Maps.newHashMap(ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY)); + Project project = myForPlace.getProject(); + myResult.forEach((value, constraint) -> { + if (value.getDescriptor() instanceof ExpressionVariableDescriptor exprVarDescr) { + PsiExpression expression = exprVarDescr.getExpression(); + PsiType type = constraint.getPsiType(project); + if (type instanceof PsiIntersectionType intersectionType) { + result.putValues(expression, Arrays.asList(intersectionType.getConjuncts())); + } + else if (type != null) { + result.putValue(expression, type); + } + } + }); + return result; + } + + @Override + protected void beforeExpressionPush( + DfaValue value, + PsiExpression expression, + @Nullable TextRange range, + DfaMemoryState state + ) { + if (range == null && myForPlace == expression) { + ((DfaMemoryStateImpl)state).forRecordedVariableTypes( + (var, dfType) -> myResult.merge(var, TypeConstraint.fromDfType(dfType), TypeConstraint::join) + ); + } + super.beforeExpressionPush(value, expression, range, state); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationFix.java index 0cfc188780..64fbcbd8fa 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationFix.java @@ -13,47 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.java.analysis.impl.codeInsight.intention; import com.intellij.java.language.psi.PsiModifierListOwner; import com.intellij.java.language.psi.PsiNameValuePair; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiFile; import consulo.language.util.IncorrectOperationException; import consulo.project.Project; -import javax.annotation.Nonnull; +import consulo.ui.annotation.RequiredUIAccess; /** * @author ven */ -public class AddAnnotationFix extends AddAnnotationPsiFix implements SyntheticIntentionAction -{ - public AddAnnotationFix(@Nonnull String fqn, @Nonnull PsiModifierListOwner modifierListOwner, @Nonnull String... annotationsToRemove) { - this(fqn, modifierListOwner, PsiNameValuePair.EMPTY_ARRAY, annotationsToRemove); - } - - public AddAnnotationFix(@Nonnull String fqn, - @Nonnull PsiModifierListOwner modifierListOwner, - @Nonnull PsiNameValuePair[] values, - @Nonnull String... annotationsToRemove) { - super(fqn, modifierListOwner, values, annotationsToRemove); - } - - @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { - return isAvailable(); - } - - @Override - public void invoke(@Nonnull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - applyFix(); - } - - @Override - public boolean startInWriteAction() { - return true; - } +public class AddAnnotationFix extends AddAnnotationPsiFix implements SyntheticIntentionAction { + @RequiredReadAction + public AddAnnotationFix(String fqn, PsiModifierListOwner modifierListOwner, String... annotationsToRemove) { + this(fqn, modifierListOwner, PsiNameValuePair.EMPTY_ARRAY, annotationsToRemove); + } + + @RequiredReadAction + public AddAnnotationFix( + String fqn, + PsiModifierListOwner modifierListOwner, + PsiNameValuePair[] values, + String... annotationsToRemove + ) { + super(fqn, modifierListOwner, values, annotationsToRemove); + } + + @Override + @RequiredUIAccess + public boolean isAvailable(Project project, Editor editor, PsiFile file) { + return isAvailable(); + } + + @Override + public void invoke(Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + applyFix(); + } + + @Override + public boolean startInWriteAction() { + return true; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java index ccda5fbbb3..981aadd5da 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddAnnotationPsiFix.java @@ -1,7 +1,6 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInsight.intention; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.AnnotationsHighlightUtil; import com.intellij.java.language.codeInsight.AnnotationTargetUtil; import com.intellij.java.language.codeInsight.AnnotationUtil; @@ -12,6 +11,9 @@ import com.intellij.java.language.psi.util.JavaElementKind; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.CommentTracker; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.editor.WriteCommandAction; import consulo.language.editor.inspection.LocalQuickFixOnPsiElement; import consulo.language.editor.intention.BaseIntentionAction; @@ -20,15 +22,16 @@ import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -36,364 +39,383 @@ import static com.intellij.java.language.codeInsight.AnnotationUtil.CHECK_TYPE; public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement { - protected final String myAnnotation; - final String[] myAnnotationsToRemove; - @SafeFieldForPreview - final PsiNameValuePair[] myPairs; // not used when registering local quick fix - protected final String myText; - private final ExternalAnnotationsManager.AnnotationPlace myAnnotationPlace; - - public AddAnnotationPsiFix(@Nonnull String fqn, - @Nonnull PsiModifierListOwner modifierListOwner, - @Nonnull String... annotationsToRemove) { - this(fqn, modifierListOwner, PsiNameValuePair.EMPTY_ARRAY, annotationsToRemove); - } - - public AddAnnotationPsiFix(@Nonnull String fqn, - @Nonnull PsiModifierListOwner modifierListOwner, - @Nonnull PsiNameValuePair[] values, - @Nonnull String... annotationsToRemove) { - super(modifierListOwner); - myAnnotation = fqn; - ObjectUtil.assertAllElementsNotNull(values); - myPairs = values; - ObjectUtil.assertAllElementsNotNull(annotationsToRemove); - myAnnotationsToRemove = annotationsToRemove; - myText = calcText(modifierListOwner, myAnnotation); - myAnnotationPlace = choosePlace(modifierListOwner); - } - - public static String calcText(PsiModifierListOwner modifierListOwner, @Nullable String annotation) { - final String shortName = annotation == null ? null : annotation.substring(annotation.lastIndexOf('.') + 1); - if (modifierListOwner instanceof PsiNamedElement) { - final String name = ((PsiNamedElement) modifierListOwner).getName(); - if (name != null) { - JavaElementKind type = JavaElementKind.fromElement(modifierListOwner).lessDescriptive(); - if (shortName == null) { - return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.element", type.object(), name); - } - return JavaAnalysisBundle - .message("inspection.i18n.quickfix.annotate.element.as", type.object(), name, shortName); - } - } - if (shortName == null) { - return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate"); + protected final String myAnnotation; + final String[] myAnnotationsToRemove; + @SafeFieldForPreview + final PsiNameValuePair[] myPairs; // not used when registering local quick fix + protected final LocalizeValue myText; + private final ExternalAnnotationsManager.AnnotationPlace myAnnotationPlace; + + @RequiredReadAction + public AddAnnotationPsiFix( + String fqn, + PsiModifierListOwner modifierListOwner, + String... annotationsToRemove + ) { + this(fqn, modifierListOwner, PsiNameValuePair.EMPTY_ARRAY, annotationsToRemove); } - return JavaAnalysisBundle.message("inspection.i18n.quickfix.annotate.as", shortName); - } - - public static - @Nullable - PsiModifierListOwner getContainer(PsiFile file, int offset) { - return getContainer(file, offset, false); - } - - public static - @Nullable - PsiModifierListOwner getContainer(PsiFile file, int offset, boolean availableOnReference) { - PsiReference reference = availableOnReference ? file.findReferenceAt(offset) : null; - if (reference != null) { - PsiElement target = reference.resolve(); - if (target instanceof PsiMember) { - return (PsiMember) target; - } + + @RequiredReadAction + public AddAnnotationPsiFix( + String fqn, + PsiModifierListOwner modifierListOwner, + PsiNameValuePair[] values, + String... annotationsToRemove + ) { + super(modifierListOwner); + myAnnotation = fqn; + ObjectUtil.assertAllElementsNotNull(values); + myPairs = values; + ObjectUtil.assertAllElementsNotNull(annotationsToRemove); + myAnnotationsToRemove = annotationsToRemove; + myText = calcText(modifierListOwner, myAnnotation); + myAnnotationPlace = choosePlace(modifierListOwner); } - PsiElement element = file.findElementAt(offset); + @RequiredReadAction + public static LocalizeValue calcText(PsiModifierListOwner modifierListOwner, @Nullable String annotation) { + String shortName = annotation == null ? null : annotation.substring(annotation.lastIndexOf('.') + 1); + if (modifierListOwner instanceof PsiNamedElement namedElement) { + String name = namedElement.getName(); + if (name != null) { + JavaElementKind type = JavaElementKind.fromElement(modifierListOwner).lessDescriptive(); + return shortName == null + ? JavaAnalysisLocalize.inspectionI18nQuickfixAnnotateElement(type.object(), name) + : JavaAnalysisLocalize.inspectionI18nQuickfixAnnotateElementAs(type.object(), name, shortName); + } + } + return shortName == null + ? JavaAnalysisLocalize.inspectionI18nQuickfixAnnotate() + : JavaAnalysisLocalize.inspectionI18nQuickfixAnnotateAs(shortName); + } - PsiModifierListOwner listOwner = PsiTreeUtil.getParentOfType(element, PsiModifierListOwner.class, false); - if (listOwner instanceof PsiParameter) { - return listOwner; + @Nullable + @RequiredReadAction + public static PsiModifierListOwner getContainer(PsiFile file, int offset) { + return getContainer(file, offset, false); } - if (listOwner instanceof PsiNameIdentifierOwner) { - PsiElement id = ((PsiNameIdentifierOwner) listOwner).getNameIdentifier(); - if (id != null && id.getTextRange().containsOffset(offset)) { // Groovy methods will pass this check as well - return listOwner; - } + @Nullable + @RequiredReadAction + public static PsiModifierListOwner getContainer(PsiFile file, int offset, boolean availableOnReference) { + PsiReference reference = availableOnReference ? file.findReferenceAt(offset) : null; + if (reference != null) { + PsiElement target = reference.resolve(); + if (target instanceof PsiMember member) { + return member; + } + } + + PsiElement element = file.findElementAt(offset); + + PsiModifierListOwner listOwner = PsiTreeUtil.getParentOfType(element, PsiModifierListOwner.class, false); + if (listOwner instanceof PsiParameter) { + return listOwner; + } + + if (listOwner instanceof PsiNameIdentifierOwner nameIdentifierOwner) { + PsiElement id = nameIdentifierOwner.getNameIdentifier(); + if (id != null && id.getTextRange().containsOffset(offset)) { // Groovy methods will pass this check as well + return listOwner; + } + } + + return null; } - return null; - } - - @Override - public - @Nonnull - String getText() { - return myText; - } - - @Override - public - @Nonnull - String getFamilyName() { - return JavaAnalysisBundle.message("intention.add.annotation.family"); - } - - @Override - public boolean isAvailable(@Nonnull Project project, - @Nonnull PsiFile file, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - return isAvailable((PsiModifierListOwner) startElement, myAnnotation); - } - - public static boolean isAvailable(@Nonnull PsiModifierListOwner modifierListOwner, @Nonnull String annotationFQN) { - if (!modifierListOwner.isValid()) { - return false; + @Override + public LocalizeValue getText() { + return myText; } - if (!PsiUtil.isLanguageLevel5OrHigher(modifierListOwner)) { - return false; + + @Override + @RequiredReadAction + public boolean isAvailable( + Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement + ) { + return isAvailable((PsiModifierListOwner)startElement, myAnnotation); } - if (modifierListOwner instanceof PsiParameter && ((PsiParameter) modifierListOwner).getTypeElement() == null) { - if (modifierListOwner.getParent() instanceof PsiParameterList && - modifierListOwner.getParent().getParent() instanceof PsiLambdaExpression) { - // Lambda parameter without type cannot be annotated. Check if we can specify types - if (PsiUtil.isLanguageLevel11OrHigher(modifierListOwner)) { - return true; + @RequiredReadAction + public static boolean isAvailable(PsiModifierListOwner modifierListOwner, String annotationFQN) { + if (!modifierListOwner.isValid()) { + return false; } - PsiLambdaExpression lambda = (PsiLambdaExpression) modifierListOwner.getParent().getParent(); - return LambdaUtil.createLambdaParameterListWithFormalTypes(lambda.getFunctionalInterfaceType(), lambda, false) != null; - } - return false; + if (!PsiUtil.isLanguageLevel5OrHigher(modifierListOwner)) { + return false; + } + + if (modifierListOwner instanceof PsiParameter parameter && parameter.getTypeElement() == null) { + if (modifierListOwner.getParent() instanceof PsiParameterList parameterList && + parameterList.getParent() instanceof PsiLambdaExpression lambda) { + // Lambda parameter without type cannot be annotated. Check if we can specify types + //noinspection SimplifiableIfStatement + if (PsiUtil.isLanguageLevel11OrHigher(modifierListOwner)) { + return true; + } + return LambdaUtil.createLambdaParameterListWithFormalTypes(lambda.getFunctionalInterfaceType(), lambda, false) != null; + } + return false; + } + // e.g. PsiTypeParameterImpl doesn't have modifier list + PsiModifierList modifierList = modifierListOwner.getModifierList(); + return modifierList != null + && !(modifierList instanceof LightElement) + && !(modifierListOwner instanceof LightElement) + && !AnnotationUtil.isAnnotated(modifierListOwner, annotationFQN, CHECK_EXTERNAL | CHECK_TYPE); } - // e.g. PsiTypeParameterImpl doesn't have modifier list - PsiModifierList modifierList = modifierListOwner.getModifierList(); - return modifierList != null - && !(modifierList instanceof LightElement) - && !(modifierListOwner instanceof LightElement) - && !AnnotationUtil.isAnnotated(modifierListOwner, annotationFQN, CHECK_EXTERNAL | CHECK_TYPE); - } - - @Override - public boolean startInWriteAction() { - return myAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.IN_CODE; - } - - @Override - public void invoke(@Nonnull Project project, - @Nonnull PsiFile file, - @Nonnull PsiElement startElement, - @Nonnull PsiElement endElement) { - final PsiModifierListOwner myModifierListOwner = (PsiModifierListOwner) startElement; - - PsiAnnotationOwner target = AnnotationTargetUtil.getTarget(myModifierListOwner, myAnnotation); - if (target == null || ContainerUtil.exists(target.getApplicableAnnotations(), anno -> anno.hasQualifiedName(myAnnotation))) { - return; + + @Override + public boolean startInWriteAction() { + return myAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.IN_CODE; } - final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); - ExternalAnnotationsManager.AnnotationPlace place = myAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.NEED_ASK_USER ? - annotationsManager.chooseAnnotationsPlace(myModifierListOwner) : myAnnotationPlace; - switch (place) { - case NOWHERE: - return; - case EXTERNAL: - for (String fqn : myAnnotationsToRemove) { - annotationsManager.deannotate(myModifierListOwner, fqn); - } - try { - annotationsManager.annotateExternally(myModifierListOwner, myAnnotation, file, myPairs); - } catch (ExternalAnnotationsManager.CanceledConfigurationException ignored) { + + @Override + @RequiredUIAccess + public void invoke( + Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement + ) { + PsiModifierListOwner myModifierListOwner = (PsiModifierListOwner)startElement; + + PsiAnnotationOwner target = AnnotationTargetUtil.getTarget(myModifierListOwner, myAnnotation); + if (target == null || ContainerUtil.exists(target.getApplicableAnnotations(), anno -> anno.hasQualifiedName(myAnnotation))) { + return; } - break; - case IN_CODE: - final PsiFile containingFile = myModifierListOwner.getContainingFile(); - Runnable command = () -> { - removePhysicalAnnotations(myModifierListOwner, myAnnotationsToRemove); - - PsiAnnotation inserted = addPhysicalAnnotationTo(myAnnotation, myPairs, target); - JavaCodeStyleManager.getInstance(project).shortenClassReferences(inserted); - }; - - if (!containingFile.isPhysical()) { - command.run(); - } else { - WriteCommandAction.runWriteCommandAction(project, null, null, command, containingFile); + ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); + ExternalAnnotationsManager.AnnotationPlace place = myAnnotationPlace == ExternalAnnotationsManager.AnnotationPlace.NEED_ASK_USER + ? annotationsManager.chooseAnnotationsPlace(myModifierListOwner) + : myAnnotationPlace; + switch (place) { + case NOWHERE: + return; + case EXTERNAL: + for (String fqn : myAnnotationsToRemove) { + annotationsManager.deannotate(myModifierListOwner, fqn); + } + try { + annotationsManager.annotateExternally(myModifierListOwner, myAnnotation, file, myPairs); + } + catch (ExternalAnnotationsManager.CanceledConfigurationException ignored) { + } + break; + case IN_CODE: + PsiFile containingFile = myModifierListOwner.getContainingFile(); + Runnable command = () -> { + removePhysicalAnnotations(myModifierListOwner, myAnnotationsToRemove); + + PsiAnnotation inserted = addPhysicalAnnotationTo(myAnnotation, myPairs, target); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(inserted); + }; + + if (!containingFile.isPhysical()) { + command.run(); + } + else { + WriteCommandAction.runWriteCommandAction(project, null, null, command, containingFile); + } + + if (containingFile != file) { + LanguageUndoUtil.markPsiFileForUndo(file); + } + break; } + } - if (containingFile != file) { - LanguageUndoUtil.markPsiFileForUndo(file); + @RequiredReadAction + private ExternalAnnotationsManager.AnnotationPlace choosePlace(PsiModifierListOwner modifierListOwner) { + Project project = modifierListOwner.getProject(); + ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); + PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(myAnnotation, modifierListOwner.getResolveScope()); + if (aClass != null && BaseIntentionAction.canModify(modifierListOwner)) { + if (AnnotationsHighlightUtil.getRetentionPolicy(aClass) == RetentionPolicy.RUNTIME) { + return ExternalAnnotationsManager.AnnotationPlace.IN_CODE; + } + if (!CommonClassNames.DEFAULT_PACKAGE.equals(StringUtil.getPackageName(myAnnotation))) { + PsiClass resolvedBySimpleName = JavaPsiFacade.getInstance(project).getResolveHelper() + .resolveReferencedClass(StringUtil.getShortName(myAnnotation), modifierListOwner); + if (resolvedBySimpleName != null && resolvedBySimpleName.getManager().areElementsEquivalent(resolvedBySimpleName, aClass)) { + // if class is already imported in current file + return ExternalAnnotationsManager.AnnotationPlace.IN_CODE; + } + } } - break; + return annotationsManager.chooseAnnotationsPlaceNoUi(modifierListOwner); } - } - - @Nonnull - private ExternalAnnotationsManager.AnnotationPlace choosePlace(@Nonnull PsiModifierListOwner modifierListOwner) { - Project project = modifierListOwner.getProject(); - final ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(project); - PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(myAnnotation, modifierListOwner.getResolveScope()); - if (aClass != null && BaseIntentionAction.canModify(modifierListOwner)) { - if (AnnotationsHighlightUtil.getRetentionPolicy(aClass) == RetentionPolicy.RUNTIME) { - return ExternalAnnotationsManager.AnnotationPlace.IN_CODE; - } - if (!CommonClassNames.DEFAULT_PACKAGE.equals(StringUtil.getPackageName(myAnnotation))) { - PsiClass resolvedBySimpleName = JavaPsiFacade.getInstance(project).getResolveHelper() - .resolveReferencedClass(StringUtil.getShortName(myAnnotation), modifierListOwner); - if (resolvedBySimpleName != null && resolvedBySimpleName.getManager().areElementsEquivalent(resolvedBySimpleName, aClass)) { - // if class is already imported in current file - return ExternalAnnotationsManager.AnnotationPlace.IN_CODE; - } - } + + /** + * @deprecated use {@link #addPhysicalAnnotationIfAbsent(String, PsiNameValuePair[], PsiAnnotationOwner)} + */ + @Deprecated + //@ApiStatus.ScheduledForRemoval(inVersion = "2020.3") + @RequiredWriteAction + public static PsiAnnotation addPhysicalAnnotation(String fqn, PsiNameValuePair[] pairs, PsiModifierList modifierList) { + return addPhysicalAnnotationTo(fqn, pairs, modifierList); } - return annotationsManager.chooseAnnotationsPlaceNoUi(modifierListOwner); - } - - /** - * @deprecated use {@link #addPhysicalAnnotationIfAbsent(String, PsiNameValuePair[], PsiAnnotationOwner)} - */ - @Deprecated - //@ApiStatus.ScheduledForRemoval(inVersion = "2020.3") - public static PsiAnnotation addPhysicalAnnotation(String fqn, PsiNameValuePair[] pairs, PsiModifierList modifierList) { - return addPhysicalAnnotationTo(fqn, pairs, modifierList); - } - - /** - * Add new physical (non-external) annotation to the annotation owner. Annotation will not be added if it already exists - * on the same annotation owner (externally or explicitly) or if there's a {@link PsiTypeElement} that follows the owner, - * and its innermost component type has the annotation with the same fully-qualified name. - * E.g. the method like {@code java.lang.@Foo String[] getStringArray()} will not be annotated with another {@code @Foo} - * annotation. - * - * @param fqn fully-qualified annotation name - * @param pairs name/value pairs for the new annotation (not changed by this method, - * could be result of {@link PsiAnnotationParameterList#getAttributes()} of existing annotation). - * @param owner an owner object to add the annotation to ({@link PsiModifierList} or {@link PsiType}). - * @return added physical annotation; null if annotation already exists (in this case, no changes are performed) - */ - @Nullable - public static PsiAnnotation addPhysicalAnnotationIfAbsent(@Nonnull String fqn, - @Nonnull PsiNameValuePair[] pairs, - @Nonnull PsiAnnotationOwner owner) { - if (owner.hasAnnotation(fqn)) { - return null; + + /** + * Add new physical (non-external) annotation to the annotation owner. Annotation will not be added if it already exists + * on the same annotation owner (externally or explicitly) or if there's a {@link PsiTypeElement} that follows the owner, + * and its innermost component type has the annotation with the same fully-qualified name. + * E.g. the method like {@code java.lang.@Foo String[] getStringArray()} will not be annotated with another {@code @Foo} + * annotation. + * + * @param fqn fully-qualified annotation name + * @param pairs name/value pairs for the new annotation (not changed by this method, + * could be result of {@link PsiAnnotationParameterList#getAttributes()} of existing annotation). + * @param owner an owner object to add the annotation to ({@link PsiModifierList} or {@link PsiType}). + * @return added physical annotation; null if annotation already exists (in this case, no changes are performed) + */ + @Nullable + @RequiredWriteAction + public static PsiAnnotation addPhysicalAnnotationIfAbsent( + String fqn, + PsiNameValuePair[] pairs, + PsiAnnotationOwner owner + ) { + if (owner.hasAnnotation(fqn)) { + return null; + } + if (owner instanceof PsiModifierList modifierList && modifierList.getParent() instanceof PsiModifierListOwner modListOwner) { + if (ExternalAnnotationsManager.getInstance(modListOwner.getProject()).findExternalAnnotation(modListOwner, fqn) != null) { + return null; + } + PsiTypeElement typeElement = modListOwner instanceof PsiMethod method + ? method.getReturnTypeElement() + : modListOwner instanceof PsiVariable variable + ? variable.getTypeElement() + : null; + while (typeElement != null && typeElement.getType() instanceof PsiArrayType) { + typeElement = PsiTreeUtil.getChildOfType(typeElement, PsiTypeElement.class); + } + if (typeElement != null && typeElement.getType().hasAnnotation(fqn)) { + return null; + } + } + return addPhysicalAnnotationTo(fqn, pairs, owner); } - if (owner instanceof PsiModifierList) { - PsiElement modListOwner = ((PsiModifierList) owner).getParent(); - if (modListOwner instanceof PsiModifierListOwner) { - if (ExternalAnnotationsManager.getInstance(modListOwner.getProject()) - .findExternalAnnotation((PsiModifierListOwner) modListOwner, fqn) != null) { - return null; + + @RequiredWriteAction + public static PsiAnnotation addPhysicalAnnotationTo(String fqn, PsiNameValuePair[] pairs, PsiAnnotationOwner owner) { + owner = expandParameterIfNecessary(owner); + PsiAnnotation inserted; + try { + inserted = owner.addAnnotation(fqn); } - PsiTypeElement typeElement = modListOwner instanceof PsiMethod ? ((PsiMethod) modListOwner).getReturnTypeElement() : - modListOwner instanceof PsiVariable ? ((PsiVariable) modListOwner).getTypeElement() : null; - while (typeElement != null && typeElement.getType() instanceof PsiArrayType) { - typeElement = PsiTreeUtil.getChildOfType(typeElement, PsiTypeElement.class); + catch (UnsupportedOperationException | IncorrectOperationException e) { + String message = "Cannot add annotation to " + owner.getClass(); + if (owner instanceof PsiElement psiElement) { + StreamEx.iterate( + psiElement.getParent(), + p -> p != null && !(p instanceof PsiFileSystemItem), + PsiElement::getParent + ) + .map(p -> p.getClass().getName()).toList(); + message += "; parents: " + message; + } + throw new RuntimeException(message, e); } - if (typeElement != null && typeElement.getType().hasAnnotation(fqn)) { - return null; + for (PsiNameValuePair pair : pairs) { + inserted.setDeclaredAttributeValue(pair.getName(), pair.getValue()); } - } - } - return addPhysicalAnnotationTo(fqn, pairs, owner); - } - - public static PsiAnnotation addPhysicalAnnotationTo(String fqn, PsiNameValuePair[] pairs, PsiAnnotationOwner owner) { - owner = expandParameterIfNecessary(owner); - PsiAnnotation inserted; - try { - inserted = owner.addAnnotation(fqn); - } catch (UnsupportedOperationException | IncorrectOperationException e) { - String message = "Cannot add annotation to " + owner.getClass(); - if (owner instanceof PsiElement) { - StreamEx.iterate(((PsiElement) owner).getParent(), p -> p != null && !(p instanceof PsiFileSystemItem), PsiElement::getParent) - .map(p -> p.getClass().getName()).toList(); - message += "; parents: " + message; - } - throw new RuntimeException(message, e); + return inserted; } - for (PsiNameValuePair pair : pairs) { - inserted.setDeclaredAttributeValue(pair.getName(), pair.getValue()); + + @RequiredWriteAction + private static PsiAnnotationOwner expandParameterIfNecessary(PsiAnnotationOwner owner) { + if (owner instanceof PsiModifierList modifierList) { + PsiParameter parameter = ObjectUtil.tryCast(modifierList.getParent(), PsiParameter.class); + if (parameter != null && parameter.getTypeElement() == null) { + PsiParameterList list = ObjectUtil.tryCast(parameter.getParent(), PsiParameterList.class); + if (list != null && list.getParent() instanceof PsiLambdaExpression) { + PsiParameter[] parameters = list.getParameters(); + int index = ArrayUtil.indexOf(parameters, parameter); + PsiParameterList newList; + if (PsiUtil.isLanguageLevel11OrHigher(list)) { + String newListText = StreamEx.of(parameters) + .map(p -> PsiKeyword.VAR + " " + p.getName()).joining(",", "(", ")"); + newList = ((PsiLambdaExpression)JavaPsiFacade.getElementFactory(list.getProject()) + .createExpressionFromText(newListText + " -> {}", null)) + .getParameterList(); + newList = (PsiParameterList)new CommentTracker().replaceAndRestoreComments(list, newList); + } + else { + newList = LambdaUtil.specifyLambdaParameterTypes((PsiLambdaExpression)list.getParent()); + } + if (newList != null) { + list = newList; + parameter = list.getParameter(index); + LOG.assertTrue(parameter != null); + owner = parameter.getModifierList(); + LOG.assertTrue(owner != null); + } + } + } + } + return owner; } - return inserted; - } - - private static PsiAnnotationOwner expandParameterIfNecessary(PsiAnnotationOwner owner) { - if (owner instanceof PsiModifierList) { - PsiParameter parameter = ObjectUtil.tryCast(((PsiModifierList) owner).getParent(), PsiParameter.class); - if (parameter != null && parameter.getTypeElement() == null) { - PsiParameterList list = ObjectUtil.tryCast(parameter.getParent(), PsiParameterList.class); - if (list != null && list.getParent() instanceof PsiLambdaExpression) { - PsiParameter[] parameters = list.getParameters(); - int index = ArrayUtil.indexOf(parameters, parameter); - PsiParameterList newList; - if (PsiUtil.isLanguageLevel11OrHigher(list)) { - String newListText = StreamEx.of(parameters).map(p -> PsiKeyword.VAR + " " + p.getName()).joining(",", "(", ")"); - newList = ((PsiLambdaExpression) JavaPsiFacade.getElementFactory(list.getProject()) - .createExpressionFromText(newListText + " -> {}", null)).getParameterList(); - newList = (PsiParameterList) new CommentTracker().replaceAndRestoreComments(list, newList); - } else { - newList = LambdaUtil.specifyLambdaParameterTypes((PsiLambdaExpression) list.getParent()); - } - if (newList != null) { - list = newList; - parameter = list.getParameter(index); - LOG.assertTrue(parameter != null); - owner = parameter.getModifierList(); - LOG.assertTrue(owner != null); - } + + @RequiredWriteAction + public static void removePhysicalAnnotations(PsiModifierListOwner owner, String... fqns) { + for (String fqn : fqns) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, true, fqn); + if (annotation != null && !AnnotationUtil.isInferredAnnotation(annotation)) { + new CommentTracker().deleteAndRestoreComments(annotation); + } } - } } - return owner; - } - - public static void removePhysicalAnnotations(@Nonnull PsiModifierListOwner owner, @Nonnull String... fqns) { - for (String fqn : fqns) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, true, fqn); - if (annotation != null && !AnnotationUtil.isInferredAnnotation(annotation)) { - new CommentTracker().deleteAndRestoreComments(annotation); - } + + protected String[] getAnnotationsToRemove() { + return myAnnotationsToRemove; } - } - @Nonnull - protected String[] getAnnotationsToRemove() { - return myAnnotationsToRemove; - } + public static boolean isNullabilityAnnotationApplicable(PsiModifierListOwner owner) { + if (owner instanceof PsiMethod method) { + PsiType returnType = method.getReturnType(); + return returnType != null && !(returnType instanceof PsiPrimitiveType); + } + return !(owner instanceof PsiClass); + } + + /** + * Creates a fix which will add default "Nullable" annotation to the given element. + * + * @param owner an element to add the annotation + * @return newly created fix or null if adding nullability annotation is impossible for the specified element. + */ + @Nullable + @RequiredReadAction + public static AddAnnotationPsiFix createAddNullableFix(PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + return createAddNullableNotNullFix(owner, manager.getDefaultNullable(), manager.getNotNulls()); + } - public static boolean isNullabilityAnnotationApplicable(@Nonnull PsiModifierListOwner owner) { - if (owner instanceof PsiMethod) { - PsiType returnType = ((PsiMethod) owner).getReturnType(); - return returnType != null && !(returnType instanceof PsiPrimitiveType); + /** + * Creates a fix which will add default "NotNull" annotation to the given element. + * + * @param owner an element to add the annotation + * @return newly created fix or null if adding nullability annotation is impossible for the specified element. + */ + @Nullable + @RequiredReadAction + public static AddAnnotationPsiFix createAddNotNullFix(PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + return createAddNullableNotNullFix(owner, manager.getDefaultNotNull(), manager.getNullables()); } - return !(owner instanceof PsiClass); - } - - /** - * Creates a fix which will add default "Nullable" annotation to the given element. - * - * @param owner an element to add the annotation - * @return newly created fix or null if adding nullability annotation is impossible for the specified element. - */ - public static - @Nullable - AddAnnotationPsiFix createAddNullableFix(PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - return createAddNullableNotNullFix(owner, manager.getDefaultNullable(), manager.getNotNulls()); - } - - /** - * Creates a fix which will add default "NotNull" annotation to the given element. - * - * @param owner an element to add the annotation - * @return newly created fix or null if adding nullability annotation is impossible for the specified element. - */ - public static - @Nullable - AddAnnotationPsiFix createAddNotNullFix(PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - return createAddNullableNotNullFix(owner, manager.getDefaultNotNull(), manager.getNullables()); - } - - private static - @Nullable - AddAnnotationPsiFix createAddNullableNotNullFix(PsiModifierListOwner owner, String annotationToAdd, - List annotationsToRemove) { - if (!isNullabilityAnnotationApplicable(owner)) { - return null; + + @Nullable + @RequiredReadAction + private static AddAnnotationPsiFix createAddNullableNotNullFix( + PsiModifierListOwner owner, String annotationToAdd, + List annotationsToRemove + ) { + return !isNullabilityAnnotationApplicable(owner) + ? null + : new AddAnnotationPsiFix(annotationToAdd, owner, ArrayUtil.toStringArray(annotationsToRemove)); } - return new AddAnnotationPsiFix(annotationToAdd, owner, ArrayUtil.toStringArray(annotationsToRemove)); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddTypeAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddTypeAnnotationFix.java new file mode 100644 index 0000000000..274fb4fe79 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/AddTypeAnnotationFix.java @@ -0,0 +1,62 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/* + * Copyright 2013-2026 consulo.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInsight.intention; + +import com.intellij.java.language.psi.PsiAnnotation; +import com.intellij.java.language.psi.PsiTypeElement; +import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.language.psi.SmartPointerManager; +import consulo.language.psi.SmartPsiElementPointer; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.util.lang.StringUtil; + +import java.util.Collection; + +public class AddTypeAnnotationFix implements LocalQuickFix { + private final SmartPsiElementPointer myElement; + private final String myAnnotationToAdd; + private final Collection myAnnotationsToRemove; + + public AddTypeAnnotationFix(PsiTypeElement element, String annotationToAdd, Collection annotationsToRemove) { + myElement = SmartPointerManager.createPointer(element); + myAnnotationToAdd = annotationToAdd; + myAnnotationsToRemove = annotationsToRemove; + } + + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.inspectionI18nQuickfixAnnotateAs(StringUtil.getShortName(myAnnotationToAdd)); + } + + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiTypeElement typeElement = myElement.getElement(); + if (typeElement == null || !typeElement.acceptsAnnotations()) return; + + for (PsiAnnotation annotation : typeElement.getAnnotations()) { + if (myAnnotationsToRemove.contains(annotation.getQualifiedName())) { + annotation.delete(); + } + } + JavaCodeStyleManager.getInstance(project).shortenClassReferences(typeElement.addAnnotation(myAnnotationToAdd)); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNotNullAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNotNullAnnotationFix.java index 684777f04c..7c9883e159 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNotNullAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNotNullAnnotationFix.java @@ -24,20 +24,18 @@ import java.util.List; -import javax.annotation.Nonnull; import com.intellij.java.language.codeInsight.NullableNotNullManager; import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.util.collection.ArrayUtil; public class AddNotNullAnnotationFix extends AddNullableNotNullAnnotationFix { - public AddNotNullAnnotationFix(@Nonnull PsiModifierListOwner owner) { + public AddNotNullAnnotationFix(PsiModifierListOwner owner) { super(NullableNotNullManager.getInstance(owner.getProject()).getDefaultNotNull(), owner, getNullables(owner)); } - @Nonnull - private static String[] getNullables(@Nonnull PsiModifierListOwner owner) { + private static String[] getNullables(PsiModifierListOwner owner) { final List nullables = NullableNotNullManager.getInstance(owner.getProject()).getNullables(); return ArrayUtil.toStringArray(nullables); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableAnnotationFix.java index 453c83a540..57d06bb907 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableAnnotationFix.java @@ -24,21 +24,19 @@ import java.util.List; -import javax.annotation.Nonnull; import com.intellij.java.language.codeInsight.NullableNotNullManager; import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.util.collection.ArrayUtil; public class AddNullableAnnotationFix extends AddNullableNotNullAnnotationFix { - public AddNullableAnnotationFix(@Nonnull PsiModifierListOwner owner) { + public AddNullableAnnotationFix(PsiModifierListOwner owner) { super(NullableNotNullManager.getInstance(owner.getProject()).getDefaultNullable(), owner, getNotNulls(owner)); } - @Nonnull - private static String[] getNotNulls(@Nonnull PsiModifierListOwner owner) { + private static String[] getNotNulls(PsiModifierListOwner owner) { final List notnulls = NullableNotNullManager.getInstance(owner.getProject()).getNotNulls(); return ArrayUtil.toStringArray(notnulls); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java index d266403c2e..c7a2b0c8f5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/intention/impl/AddNullableNotNullAnnotationFix.java @@ -13,16 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * Created by IntelliJ IDEA. - * User: cdr - * Date: Jul 20, 2007 - * Time: 2:57:59 PM - */ package com.intellij.java.analysis.impl.codeInsight.intention.impl; -import javax.annotation.Nonnull; +import consulo.annotation.access.RequiredReadAction; import com.intellij.java.language.codeInsight.AnnotationUtil; import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationPsiFix; @@ -35,31 +28,40 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -public class AddNullableNotNullAnnotationFix extends AddAnnotationPsiFix -{ - public AddNullableNotNullAnnotationFix(@Nonnull String fqn, @Nonnull PsiModifierListOwner owner, @Nonnull String... annotationToRemove) - { - super(fqn, owner, PsiNameValuePair.EMPTY_ARRAY, annotationToRemove); - } +/** + * @author cdr + * @since 2007-07-20 + */ +public class AddNullableNotNullAnnotationFix extends AddAnnotationPsiFix { + @RequiredReadAction + public AddNullableNotNullAnnotationFix( + String fqn, + PsiModifierListOwner owner, + String... annotationToRemove + ) { + super(fqn, owner, PsiNameValuePair.EMPTY_ARRAY, annotationToRemove); + } - @Override - public boolean isAvailable(@Nonnull Project project, @Nonnull PsiFile file, @Nonnull PsiElement startElement, @Nonnull PsiElement endElement) - { - if(!super.isAvailable(project, file, startElement, endElement)) - { - return false; - } - PsiModifierListOwner owner = getContainer(file, startElement.getTextRange().getStartOffset()); - if(owner == null || AnnotationUtil.isAnnotated(owner, getAnnotationsToRemove()[0], false, false)) - { - return false; - } - if(owner instanceof PsiMethod) - { - PsiType returnType = ((PsiMethod) owner).getReturnType(); + @Override + @RequiredReadAction + public boolean isAvailable( + Project project, + PsiFile file, + PsiElement startElement, + PsiElement endElement + ) { + if (!super.isAvailable(project, file, startElement, endElement)) { + return false; + } + PsiModifierListOwner owner = getContainer(file, startElement.getTextRange().getStartOffset()); + if (owner == null || AnnotationUtil.isAnnotated(owner, getAnnotationsToRemove()[0], false, false)) { + return false; + } + if (owner instanceof PsiMethod method) { + PsiType returnType = method.getReturnType(); - return returnType != null && !(returnType instanceof PsiPrimitiveType); - } - return true; - } + return returnType != null && !(returnType instanceof PsiPrimitiveType); + } + return true; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java index db31251516..321a1777e8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java @@ -13,11 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * User: anna - * Date: 27-Aug-2009 - */ package com.intellij.java.analysis.impl.codeInsight.quickfix; import consulo.annotation.component.ComponentScope; @@ -27,9 +22,13 @@ import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.PsiVariable; +/** + * User: anna + * Date: 27-Aug-2009 + */ @ExtensionAPI(ComponentScope.APPLICATION) public interface ChangeVariableTypeQuickFixProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(ChangeVariableTypeQuickFixProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ChangeVariableTypeQuickFixProvider.class); - IntentionAction[] getFixes(PsiVariable variable, PsiType toReturn); + IntentionAction[] getFixes(PsiVariable variable, PsiType toReturn); } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/SetupJDKFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/SetupJDKFix.java index 66b759e684..b9c382a245 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/SetupJDKFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/SetupJDKFix.java @@ -18,17 +18,16 @@ import consulo.application.ApplicationManager; import consulo.codeEditor.Editor; import consulo.content.bundle.Sdk; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.editor.intention.IntentionAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiFile; import consulo.language.util.ModuleUtilCore; +import consulo.localize.LocalizeValue; import consulo.module.Module; import consulo.project.Project; -import javax.annotation.Nonnull; - /** * @author mike * Date: Aug 20, 2002 @@ -44,19 +43,18 @@ private SetupJDKFix() { } @Override - @Nonnull - public String getText() { - return JavaQuickFixBundle.message("setup.jdk.location.text"); + public LocalizeValue getText() { + return JavaQuickFixLocalize.setupJdkLocationText(); } @Override - public boolean isAvailable(@Nonnull Project project, Editor editor, PsiFile file) { + public boolean isAvailable(Project project, Editor editor, PsiFile file) { return false; //return JavaPsiFacade.getInstance(project).findClass(CommonClassNames.JAVA_LANG_OBJECT, file.getResolveScope()) == null; } @Override - public void invoke(@Nonnull Project project, Editor editor, final PsiFile file) { + public void invoke(Project project, Editor editor, final PsiFile file) { Sdk projectJdk = null; if (projectJdk == null) return; ApplicationManager.getApplication().runWriteAction(new Runnable() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnnotateMethodFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnnotateMethodFix.java index 09c14c0047..c2c31cca54 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnnotateMethodFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnnotateMethodFix.java @@ -23,17 +23,17 @@ import com.intellij.java.language.psi.util.ClassUtil; import consulo.application.ReadAction; import consulo.application.progress.ProgressManager; -import consulo.java.analysis.impl.codeInsight.JavaInspectionsBundle; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.util.LanguageUndoUtil; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -46,34 +46,30 @@ public class AnnotateMethodFix implements LocalQuickFix { private final String myAnnotation; private final String[] myAnnotationsToRemove; - public AnnotateMethodFix(@Nonnull String fqn, @Nonnull String... annotationsToRemove) { + public AnnotateMethodFix(String fqn, String... annotationsToRemove) { myAnnotation = fqn; myAnnotationsToRemove = annotationsToRemove; LOG.assertTrue(annotateSelf() || annotateOverriddenMethods(), "annotate method quick fix should not do nothing"); } @Override - @Nonnull - public String getName() { - return getFamilyName() + " " + getPreposition() + " \'@" + ClassUtil.extractClassName(myAnnotation) + "\'"; + public LocalizeValue getName() { + return LocalizeValue.join(getFamilyName(), LocalizeValue.space(), LocalizeValue.of(getPreposition()), LocalizeValue.of(" \'@"), LocalizeValue.of(ClassUtil.extractClassName(myAnnotation)), LocalizeValue.of("\'")); } - @Nonnull protected String getPreposition() { return "with"; } - @Override - @Nonnull - public String getFamilyName() { + private LocalizeValue getFamilyName() { if (annotateSelf()) { if (annotateOverriddenMethods()) { - return JavaInspectionsBundle.message("inspection.annotate.overridden.method.and.self.quickfix.family.name"); + return JavaInspectionsLocalize.inspectionAnnotateOverriddenMethodAndSelfQuickfixFamilyName(); } else { - return JavaInspectionsBundle.message("inspection.annotate.method.quickfix.family.name"); + return JavaInspectionsLocalize.inspectionAnnotateMethodQuickfixFamilyName(); } } else { - return JavaInspectionsBundle.message("inspection.annotate.overridden.method.quickfix.family.name"); + return JavaInspectionsLocalize.inspectionAnnotateOverriddenMethodQuickfixFamilyName(); } } @@ -83,7 +79,7 @@ public boolean startInWriteAction() { } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + public void applyFix(Project project, ProblemDescriptor descriptor) { final PsiElement psiElement = descriptor.getPsiElement(); PsiMethod method = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class); @@ -126,7 +122,7 @@ protected boolean annotateSelf() { return true; } - private void annotateMethod(@Nonnull PsiMethod method) { + private void annotateMethod(PsiMethod method) { AddAnnotationPsiFix fix = new AddAnnotationPsiFix(myAnnotation, method, PsiNameValuePair.EMPTY_ARRAY, myAnnotationsToRemove); fix.invoke(method.getProject(), method.getContainingFile(), method, method); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java index 7a5d9a2369..b7b6161f01 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspection.java @@ -16,7 +16,6 @@ package com.intellij.java.analysis.impl.codeInspection; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.codeInsight.AnnotationUtil; @@ -30,31 +29,26 @@ import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.RedundantCastUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.component.util.text.UniqueNameGenerator; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.editor.inspection.ui.SingleCheckboxOptionsPanel; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiComment; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Comparing; import consulo.util.lang.StringUtil; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.*; @@ -64,558 +58,614 @@ * User: anna */ @ExtensionImpl -public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspectionTool { - public static final Logger LOG = Logger.getInstance(AnonymousCanBeLambdaInspection.class); - - public boolean reportNotAnnotatedInterfaces = true; - - @Nls - @Nonnull - @Override - public String getGroupDisplayName() { - return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; - } - - @Nls - @Nonnull - @Override - public String getDisplayName() { - return "Anonymous type can be replaced with lambda"; - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public String getShortName() { - return "Convert2Lambda"; - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Nullable - @Override - public JComponent createOptionsPanel() { - return new SingleCheckboxOptionsPanel("Report when interface is not annotated with @FunctionalInterface", this, "reportNotAnnotatedInterfaces"); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitAnonymousClass(final PsiAnonymousClass aClass) { - super.visitAnonymousClass(aClass); - final PsiElement parent = aClass.getParent(); - final PsiElement lambdaContext = parent != null ? parent.getParent() : null; - if (lambdaContext != null && - (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) && - canBeConvertedToLambda(aClass, false, isOnTheFly || reportNotAnnotatedInterfaces, Collections.emptySet())) { - final PsiElement lBrace = aClass.getLBrace(); - LOG.assertTrue(lBrace != null); - final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); - ProblemHighlightType problemHighlightType = ProblemHighlightType.LIKE_UNUSED_SYMBOL; - if (isOnTheFly && !reportNotAnnotatedInterfaces) { - final PsiClass baseClass = aClass.getBaseClassType().resolve(); - LOG.assertTrue(baseClass != null); - if (!AnnotationUtil.isAnnotated(baseClass, JavaClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { - problemHighlightType = ProblemHighlightType.INFORMATION; - } - } - holder.registerProblem(parent, "Anonymous #ref #loc can be replaced with lambda", problemHighlightType, rangeInElement, new ReplaceWithLambdaFix()); - } - } - }; - } - - static boolean hasRuntimeAnnotations(PsiMethod method, @Nonnull Set runtimeAnnotationsToIgnore) { - PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); - for (PsiAnnotation annotation : annotations) { - PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); - PsiElement target = ref != null ? ref.resolve() : null; - if (target instanceof PsiClass) { - if (runtimeAnnotationsToIgnore.contains(((PsiClass) target).getQualifiedName())) { - continue; - } - final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass) target, Retention.class.getName()); - if (retentionAnno != null) { - PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value"); - if (value instanceof PsiReferenceExpression) { - final PsiElement resolved = ((PsiReferenceExpression) value).resolve(); - if (resolved instanceof PsiField && RetentionPolicy.RUNTIME.name().equals(((PsiField) resolved).getName())) { - final PsiClass containingClass = ((PsiField) resolved).getContainingClass(); - if (containingClass != null && RetentionPolicy.class.getName().equals(containingClass.getQualifiedName())) { - return true; - } - } - } - } - } - } - return false; - } - - public static boolean hasForbiddenRefsInsideBody(PsiMethod method, PsiAnonymousClass aClass) { - final ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, aClass); - final PsiCodeBlock body = method.getBody(); - LOG.assertTrue(body != null); - body.accept(checker); - return checker.hasForbiddenRefs(); - } - - private static PsiType getInferredType(PsiAnonymousClass aClass, PsiMethod method) { - final PsiExpression expression = (PsiExpression) aClass.getParent(); - final PsiType psiType = PsiTypesUtil.getExpectedTypeByParent(expression); - if (psiType != null) { - return psiType; +public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspectionTool { + public static final Logger LOG = Logger.getInstance(AnonymousCanBeLambdaInspection.class); + + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); } - PsiExpression topExpr = expression; - while (topExpr.getParent() instanceof PsiParenthesizedExpression) { - topExpr = (PsiExpression) topExpr.getParent(); + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Anonymous type can be replaced with lambda"); } - final PsiCall call = LambdaUtil.treeWalkUp(topExpr); - if (call != null && call.resolveMethod() != null) { - final int offsetInTopCall = aClass.getTextRange().getStartOffset() - call.getTextRange().getStartOffset(); - PsiCall copyCall = LambdaUtil.copyTopLevelCall(call); - if (copyCall == null) { - return null; - } - final PsiAnonymousClass classArg = PsiTreeUtil.getParentOfType(copyCall.findElementAt(offsetInTopCall), PsiAnonymousClass.class); - if (classArg != null) { - PsiExpression lambda = JavaPsiFacade.getElementFactory(aClass.getProject()).createExpressionFromText(ReplaceWithLambdaFix.composeLambdaText(method), expression); - lambda = (PsiExpression) classArg.getParent().replace(lambda); - ((PsiLambdaExpression) lambda).getBody().replace(method.getBody()); - final PsiType interfaceType; - if (copyCall.resolveMethod() == null) { - return PsiType.NULL; - } else { - interfaceType = ((PsiLambdaExpression) lambda).getFunctionalInterfaceType(); - } + @Override + public boolean isEnabledByDefault() { + return true; + } - return interfaceType; - } + @Override + public String getShortName() { + return "Convert2Lambda"; } - return PsiType.NULL; - } + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } - public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, boolean acceptParameterizedFunctionTypes, @Nonnull Set ignoredRuntimeAnnotations) { - return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations); - } + @Override + public AnonymousCanBeLambdaInspectionState createStateProvider() { + return new AnonymousCanBeLambdaInspectionState(); + } - public static boolean isLambdaForm(PsiAnonymousClass aClass, Set ignoredRuntimeAnnotations) { - PsiMethod[] methods = aClass.getMethods(); - if (methods.length != 1) { - return false; + @Override + public PsiElementVisitor buildVisitorImpl( + final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + AnonymousCanBeLambdaInspectionState state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitAnonymousClass(PsiAnonymousClass aClass) { + super.visitAnonymousClass(aClass); + PsiElement parent = aClass.getParent(); + PsiElement lambdaContext = parent != null ? parent.getParent() : null; + if (lambdaContext != null + && (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) + && canBeConvertedToLambda(aClass, false, isOnTheFly || state.reportNotAnnotatedInterfaces, Collections.emptySet())) { + PsiElement lBrace = aClass.getLBrace(); + LOG.assertTrue(lBrace != null); + TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); + ProblemHighlightType problemHighlightType = ProblemHighlightType.LIKE_UNUSED_SYMBOL; + if (isOnTheFly && !state.reportNotAnnotatedInterfaces) { + PsiClass baseClass = aClass.getBaseClassType().resolve(); + LOG.assertTrue(baseClass != null); + if (!AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { + problemHighlightType = ProblemHighlightType.INFORMATION; + } + } + holder.newProblem(LocalizeValue.localizeTODO("Anonymous #ref #loc can be replaced with lambda")) + .range(parent, rangeInElement) + .highlightType(problemHighlightType) + .withFix(new ReplaceWithLambdaFix()) + .create(); + } + } + }; } - PsiMethod method = methods[0]; - return aClass.getFields().length == 0 && - aClass.getInnerClasses().length == 0 && - aClass.getInitializers().length == 0 && - method.getBody() != null && - method.getDocComment() == null && - !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) && - !method.hasModifierProperty(PsiModifier.SYNCHRONIZED) && - !hasForbiddenRefsInsideBody(method, aClass); - } - - public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, - boolean acceptParameterizedFunctionTypes, - boolean reportNotAnnotatedInterfaces, - @Nonnull Set ignoredRuntimeAnnotations) { - if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { - final PsiClassType baseClassType = aClass.getBaseClassType(); - final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); - final PsiClass baseClass = resolveResult.getElement(); - if (baseClass == null || !reportNotAnnotatedInterfaces && !AnnotationUtil.isAnnotated(baseClass, JavaClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { - return false; - } - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); - if (interfaceMethod != null && (acceptParameterizedFunctionTypes || !interfaceMethod.hasTypeParameters())) { - if (isLambdaForm(aClass, ignoredRuntimeAnnotations)) { - final PsiMethod method = aClass.getMethods()[0]; - return getInferredType(aClass, method) != null; + + @RequiredReadAction + static boolean hasRuntimeAnnotations(PsiMethod method, Set runtimeAnnotationsToIgnore) { + PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); + for (PsiAnnotation annotation : annotations) { + PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); + if (ref != null && ref.resolve() instanceof PsiClass targetClass) { + if (runtimeAnnotationsToIgnore.contains(targetClass.getQualifiedName())) { + continue; + } + PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation(targetClass, Retention.class.getName()); + if (retentionAnno != null + && retentionAnno.findAttributeValue("value") instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiField field + && RetentionPolicy.RUNTIME.name().equals(field.getName())) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass != null && RetentionPolicy.class.getName().equals(containingClass.getQualifiedName())) { + return true; + } + } + } } - } - } - return false; - } - - public static PsiExpression replaceAnonymousWithLambda(@Nonnull PsiElement anonymousClass, PsiType expectedType) { - PsiNewExpression newArrayExpression = (PsiNewExpression) JavaPsiFacade.getElementFactory(anonymousClass.getProject()).createExpressionFromText("new " + expectedType.getCanonicalText() + - "[]{" + anonymousClass.getText() + "}", anonymousClass); - PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer(); - LOG.assertTrue(initializer != null); - return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false); - } - - public static PsiExpression replacePsiElementWithLambda(@Nonnull PsiElement element, final boolean ignoreEqualsMethod, boolean forceIgnoreTypeCast) { - if (!(element instanceof PsiNewExpression)) { - return null; + return false; } - final PsiNewExpression newExpression = (PsiNewExpression) element; - final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - - if (anonymousClass == null) { - return null; + public static boolean hasForbiddenRefsInsideBody(PsiMethod method, PsiAnonymousClass aClass) { + ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, aClass); + PsiCodeBlock body = method.getBody(); + LOG.assertTrue(body != null); + body.accept(checker); + return checker.hasForbiddenRefs(); } - final PsiMethod method; - if (ignoreEqualsMethod) { - final List methods = ContainerUtil.filter(anonymousClass.getMethods(), method1 -> !"equals".equals(method1.getName())); - method = methods.get(0); - } else { - method = anonymousClass.getMethods()[0]; - } - if (method == null || method.getBody() == null) { - return null; - } + @RequiredReadAction + private static PsiType getInferredType(PsiAnonymousClass aClass, PsiMethod method) { + PsiExpression expression = (PsiExpression) aClass.getParent(); + PsiType psiType = PsiTypesUtil.getExpectedTypeByParent(expression); + if (psiType != null) { + return psiType; + } - return generateLambdaByMethod(anonymousClass, method, lambda -> (PsiLambdaExpression) newExpression.replace(lambda), forceIgnoreTypeCast); - } + PsiExpression topExpr = expression; + while (topExpr.getParent() instanceof PsiParenthesizedExpression parenthesized) { + topExpr = parenthesized; + } - /** - * Try convert given method of given anonymous class into lambda and replace given element. - * - * @param anonymousClass physical anonymous class containing method - * @param method physical method to convert with non-empty body - * @param replacer an operator which actually inserts a lambda into the file (possibly removing anonymous class) - * and returns an inserted physical lambda - * @param forceIgnoreTypeCast if false, type cast might be added if necessary - * @return newly-generated lambda expression (possibly with typecast) - */ - @Nonnull - static PsiExpression generateLambdaByMethod(PsiAnonymousClass anonymousClass, PsiMethod method, UnaryOperator replacer, boolean forceIgnoreTypeCast) { - ChangeContextUtil.encodeContextInfo(anonymousClass, true); - final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); + PsiCall call = LambdaUtil.treeWalkUp(topExpr); + if (call != null && call.resolveMethod() != null) { + int offsetInTopCall = aClass.getTextRange().getStartOffset() - call.getTextRange().getStartOffset(); + PsiCall copyCall = LambdaUtil.copyTopLevelCall(call); + if (copyCall == null) { + return null; + } + PsiAnonymousClass classArg = + PsiTreeUtil.getParentOfType(copyCall.findElementAt(offsetInTopCall), PsiAnonymousClass.class); + if (classArg != null) { + PsiExpression lambda = JavaPsiFacade.getElementFactory(aClass.getProject()) + .createExpressionFromText(ReplaceWithLambdaFix.composeLambdaText(method), expression); + lambda = (PsiExpression) classArg.getParent().replace(lambda); + ((PsiLambdaExpression) lambda).getBody().replace(method.getBody()); + PsiType interfaceType; + if (copyCall.resolveMethod() == null) { + return PsiType.NULL; + } + else { + interfaceType = ((PsiLambdaExpression) lambda).getFunctionalInterfaceType(); + } - final PsiCodeBlock body = method.getBody(); - LOG.assertTrue(body != null); + return interfaceType; + } + } - final Collection comments = collectCommentsOutsideMethodBody(anonymousClass, body); - final Project project = anonymousClass.getProject(); - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + return PsiType.NULL; + } - final String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method); + @RequiredReadAction + public static boolean canBeConvertedToLambda( + PsiAnonymousClass aClass, + boolean acceptParameterizedFunctionTypes, + Set ignoredRuntimeAnnotations + ) { + return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations); + } - PsiLambdaExpression lambdaExpression = (PsiLambdaExpression) elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); + @RequiredReadAction + public static boolean isLambdaForm(PsiAnonymousClass aClass, Set ignoredRuntimeAnnotations) { + PsiMethod[] methods = aClass.getMethods(); + if (methods.length != 1) { + return false; + } + PsiMethod method = methods[0]; + return aClass.getFields().length == 0 + && aClass.getInnerClasses().length == 0 + && aClass.getInitializers().length == 0 + && method.getBody() != null + && method.getDocComment() == null + && !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) + && !method.hasModifierProperty(PsiModifier.SYNCHRONIZED) + && !hasForbiddenRefsInsideBody(method, aClass); + } - PsiElement lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - lambdaBody.replace(body); - lambdaExpression = replacer.apply(lambdaExpression); + @RequiredReadAction + public static boolean canBeConvertedToLambda( + PsiAnonymousClass aClass, + boolean acceptParameterizedFunctionTypes, + boolean reportNotAnnotatedInterfaces, + Set ignoredRuntimeAnnotations + ) { + if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { + PsiClassType baseClassType = aClass.getBaseClassType(); + PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); + PsiClass baseClass = resolveResult.getElement(); + if (baseClass == null || !reportNotAnnotatedInterfaces + && !AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, false, false)) { + return false; + } + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod != null && (acceptParameterizedFunctionTypes || !interfaceMethod.hasTypeParameters())) { + if (isLambdaForm(aClass, ignoredRuntimeAnnotations)) { + PsiMethod method = aClass.getMethods()[0]; + return getInferredType(aClass, method) != null; + } + } + } + return false; + } - final Set variables = new HashSet<>(); - final Set usedLocalNames = new HashSet<>(); + @RequiredWriteAction + public static PsiExpression replaceAnonymousWithLambda(PsiElement anonymousClass, PsiType expectedType) { + PsiNewExpression newArrayExpression = (PsiNewExpression) JavaPsiFacade.getElementFactory(anonymousClass.getProject()) + .createExpressionFromText("new " + expectedType.getCanonicalText() + "[]{" + anonymousClass.getText() + "}", anonymousClass); + PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer(); + LOG.assertTrue(initializer != null); + return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false); + } - collectLocalVariablesDefinedInsideLambda(lambdaExpression, variables, usedLocalNames); + @RequiredWriteAction + public static PsiExpression replacePsiElementWithLambda( + PsiElement element, + boolean ignoreEqualsMethod, + boolean forceIgnoreTypeCast + ) { + if (!(element instanceof PsiNewExpression newExpression)) { + return null; + } - ReplaceWithLambdaFix.giveUniqueNames(project, elementFactory, lambdaExpression, usedLocalNames, variables.toArray(new PsiVariable[variables.size()])); + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - final PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression.getBody()); - if (singleExpr != null) { - lambdaExpression.getBody().replace(singleExpr); - } - ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); - restoreComments(comments, lambdaExpression); + if (anonymousClass == null) { + return null; + } - final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); - if (forceIgnoreTypeCast) { - return (PsiExpression) javaCodeStyleManager.shortenClassReferences(lambdaExpression); - } + PsiMethod method; + if (ignoreEqualsMethod) { + List methods = + ContainerUtil.filter(anonymousClass.getMethods(), method1 -> !"equals".equals(method1.getName())); + method = methods.get(0); + } + else { + method = anonymousClass.getMethods()[0]; + } + if (method == null || method.getBody() == null) { + return null; + } - PsiTypeCastExpression typeCast = (PsiTypeCastExpression) elementFactory.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression); - final PsiExpression typeCastOperand = typeCast.getOperand(); - LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); - final PsiElement fromText = ((PsiLambdaExpression) typeCastOperand).getBody(); - LOG.assertTrue(fromText != null); - lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - fromText.replace(lambdaBody); - ((PsiLambdaExpression) typeCastOperand).getParameterList().replace(lambdaExpression.getParameterList()); - typeCast = (PsiTypeCastExpression) lambdaExpression.replace(typeCast); - if (RedundantCastUtil.isCastRedundant(typeCast)) { - final PsiExpression operand = typeCast.getOperand(); - LOG.assertTrue(operand != null); - return (PsiExpression) typeCast.replace(operand); - } - return (PsiExpression) javaCodeStyleManager.shortenClassReferences(typeCast); - } - - @Nonnull - static Collection collectCommentsOutsideMethodBody(PsiAnonymousClass anonymousClass, PsiCodeBlock body) { - final Collection psiComments = PsiTreeUtil.findChildrenOfType(anonymousClass, PsiComment.class); - psiComments.removeIf(comment -> PsiTreeUtil.isAncestor(body, comment, false)); - return ContainerUtil.map(psiComments, (comment) -> (PsiComment) comment.copy()); - } - - private static void collectLocalVariablesDefinedInsideLambda(PsiLambdaExpression lambdaExpression, final Set variables, Set namesOfVariablesInTheBlock) { - PsiElement block = PsiUtil.getTopLevelEnclosingCodeBlock(lambdaExpression, null); - if (block == null) { - block = lambdaExpression; + return generateLambdaByMethod( + anonymousClass, + method, + lambda -> (PsiLambdaExpression) newExpression.replace(lambda), + forceIgnoreTypeCast + ); } - block.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - if (!(variable instanceof PsiField)) { - variables.add(variable); + /** + * Try convert given method of given anonymous class into lambda and replace given element. + * + * @param anonymousClass physical anonymous class containing method + * @param method physical method to convert with non-empty body + * @param replacer an operator which actually inserts a lambda into the file (possibly removing anonymous class) + * and returns an inserted physical lambda + * @param forceIgnoreTypeCast if false, type cast might be added if necessary + * @return newly-generated lambda expression (possibly with typecast) + */ + @RequiredWriteAction + static PsiExpression generateLambdaByMethod( + PsiAnonymousClass anonymousClass, + PsiMethod method, + UnaryOperator replacer, + boolean forceIgnoreTypeCast + ) { + ChangeContextUtil.encodeContextInfo(anonymousClass, true); + String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); + + PsiCodeBlock body = method.getBody(); + LOG.assertTrue(body != null); + + Collection comments = collectCommentsOutsideMethodBody(anonymousClass, body); + Project project = anonymousClass.getProject(); + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + + String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method); + + PsiLambdaExpression lambdaExpression = + (PsiLambdaExpression) elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); + + PsiElement lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + lambdaBody.replace(body); + lambdaExpression = replacer.apply(lambdaExpression); + + Set variables = new HashSet<>(); + Set usedLocalNames = new HashSet<>(); + + collectLocalVariablesDefinedInsideLambda(lambdaExpression, variables, usedLocalNames); + + ReplaceWithLambdaFix.giveUniqueNames( + project, + elementFactory, + lambdaExpression, + usedLocalNames, + variables.toArray(new PsiVariable[variables.size()]) + ); + + PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression.getBody()); + if (singleExpr != null) { + lambdaExpression.getBody().replace(singleExpr); } - } - }); - - final PsiResolveHelper helper = PsiResolveHelper.SERVICE.getInstance(lambdaExpression.getProject()); - for (Iterator iterator = variables.iterator(); iterator.hasNext(); ) { - PsiVariable local = iterator.next(); - final String localName = local.getName(); - if (localName == null || - shadowingResolve(localName, lambdaExpression, helper) || - !PsiTreeUtil.isAncestor(lambdaExpression, local, false)) { - iterator.remove(); - namesOfVariablesInTheBlock.add(localName); - } - } - } + ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); + restoreComments(comments, lambdaExpression); - private static boolean shadowingResolve(String localName, PsiLambdaExpression lambdaExpression, PsiResolveHelper helper) { - final PsiVariable variable = helper.resolveReferencedVariable(localName, lambdaExpression); - return variable == null || variable instanceof PsiField; - } + JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); + if (forceIgnoreTypeCast) { + return (PsiExpression) javaCodeStyleManager.shortenClassReferences(lambdaExpression); + } - private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction { - @Nonnull - @Override - public String getFamilyName() { - return "Replace with lambda"; + PsiTypeCastExpression typeCast = (PsiTypeCastExpression) elementFactory.createExpressionFromText( + "(" + canonicalText + ")" + withoutTypesDeclared, + lambdaExpression + ); + PsiExpression typeCastOperand = typeCast.getOperand(); + LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); + PsiElement fromText = ((PsiLambdaExpression) typeCastOperand).getBody(); + LOG.assertTrue(fromText != null); + lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + fromText.replace(lambdaBody); + ((PsiLambdaExpression) typeCastOperand).getParameterList().replace(lambdaExpression.getParameterList()); + typeCast = (PsiTypeCastExpression) lambdaExpression.replace(typeCast); + if (RedundantCastUtil.isCastRedundant(typeCast)) { + PsiExpression operand = typeCast.getOperand(); + LOG.assertTrue(operand != null); + return (PsiExpression) typeCast.replace(operand); + } + return (PsiExpression) javaCodeStyleManager.shortenClassReferences(typeCast); } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (element != null) { - replacePsiElementWithLambda(element, false, false); - } + @RequiredReadAction + static Collection collectCommentsOutsideMethodBody(PsiAnonymousClass anonymousClass, PsiCodeBlock body) { + Collection psiComments = PsiTreeUtil.findChildrenOfType(anonymousClass, PsiComment.class); + psiComments.removeIf(comment -> PsiTreeUtil.isAncestor(body, comment, false)); + return ContainerUtil.map(psiComments, (comment) -> (PsiComment) comment.copy()); } - private static void giveUniqueNames(Project project, final PsiElementFactory elementFactory, PsiElement body, Set usedLocalNames, PsiVariable[] parameters) { - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); - final Map names = new HashMap<>(); - for (PsiVariable parameter : parameters) { - String parameterName = parameter.getName(); - String uniqueVariableName = UniqueNameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(parameterName, parameter.getParent(), false), usedLocalNames); - if (!Comparing.equal(parameterName, uniqueVariableName)) { - names.put(parameter, uniqueVariableName); + @RequiredReadAction + private static void collectLocalVariablesDefinedInsideLambda( + PsiLambdaExpression lambdaExpression, + final Set variables, + Set namesOfVariablesInTheBlock + ) { + PsiElement block = PsiUtil.getTopLevelEnclosingCodeBlock(lambdaExpression, null); + if (block == null) { + block = lambdaExpression; } - } - if (names.isEmpty()) { - return; - } + block.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitVariable(PsiVariable variable) { + super.visitVariable(variable); + if (!(variable instanceof PsiField)) { + variables.add(variable); + } + } + }); + + PsiResolveHelper helper = PsiResolveHelper.getInstance(lambdaExpression.getProject()); + for (Iterator iterator = variables.iterator(); iterator.hasNext(); ) { + PsiVariable local = iterator.next(); + String localName = local.getName(); + if (localName == null || + shadowingResolve(localName, lambdaExpression, helper) || + !PsiTreeUtil.isAncestor(lambdaExpression, local, false)) { + iterator.remove(); + namesOfVariablesInTheBlock.add(localName); + } + } + } + + private static boolean shadowingResolve(String localName, PsiLambdaExpression lambdaExpression, PsiResolveHelper helper) { + PsiVariable variable = helper.resolveReferencedVariable(localName, lambdaExpression); + return variable == null || variable instanceof PsiField; + } - final Map replacements = new LinkedHashMap<>(); - body.accept(new JavaRecursiveElementWalkingVisitor() { + private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction { @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - final String newName = names.get(variable); - if (newName != null) { - replacements.put(variable.getNameIdentifier(), elementFactory.createIdentifier(newName)); - } + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with lambda"); } @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - super.visitReferenceExpression(expression); - final PsiElement resolve = expression.resolve(); - if (resolve instanceof PsiVariable) { - final String newName = names.get(resolve); - if (newName != null) { - replacements.put(expression, elementFactory.createExpressionFromText(newName, expression)); + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element != null) { + replacePsiElementWithLambda(element, false, false); } - } } - }); - for (PsiElement psiElement : replacements.keySet()) { - psiElement.replace(replacements.get(psiElement)); - } - } + @RequiredWriteAction + private static void giveUniqueNames( + Project project, + final PsiElementFactory elementFactory, + PsiElement body, + Set usedLocalNames, + PsiVariable[] parameters + ) { + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + final Map names = new HashMap<>(); + for (PsiVariable parameter : parameters) { + String parameterName = parameter.getName(); + String uniqueVariableName = UniqueNameGenerator.generateUniqueName( + codeStyleManager.suggestUniqueVariableName(parameterName, parameter.getParent(), false), + usedLocalNames + ); + if (!Comparing.equal(parameterName, uniqueVariableName)) { + names.put(parameter, uniqueVariableName); + } + } - private static String composeLambdaText(PsiMethod method) { - final StringBuilder buf = new StringBuilder(); - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length != 1) { - buf.append("("); - } - buf.append(StringUtil.join(parameters, ReplaceWithLambdaFix::composeParameter, ",")); - if (parameters.length != 1) { - buf.append(")"); - } - buf.append("-> {}"); - return buf.toString(); - } + if (names.isEmpty()) { + return; + } - private static String composeParameter(PsiParameter parameter) { - String parameterName = parameter.getName(); - if (parameterName == null) { - parameterName = ""; - } - return parameterName; - } - } + final Map replacements = new LinkedHashMap<>(); + body.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitVariable(PsiVariable variable) { + super.visitVariable(variable); + String newName = names.get(variable); + if (newName != null) { + replacements.put(variable.getNameIdentifier(), elementFactory.createIdentifier(newName)); + } + } - public static boolean functionalInterfaceMethodReferenced(PsiMethod psiMethod, PsiAnonymousClass anonymClass, PsiCallExpression callExpression) { - if (psiMethod != null && !psiMethod.hasModifierProperty(PsiModifier.STATIC)) { - final PsiClass containingClass = psiMethod.getContainingClass(); - if (containingClass != null && JavaClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { - return false; - } + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + super.visitReferenceExpression(expression); + if (expression.resolve() instanceof PsiVariable variable) { + String newName = names.get(variable); + if (newName != null) { + replacements.put(expression, elementFactory.createExpressionFromText(newName, expression)); + } + } + } + }); - if (callExpression instanceof PsiMethodCallExpression && ((PsiMethodCallExpression) callExpression).getMethodExpression().isQualified()) { - return false; - } + for (PsiElement psiElement : replacements.keySet()) { + psiElement.replace(replacements.get(psiElement)); + } + } - if (InheritanceUtil.isInheritorOrSelf(anonymClass, containingClass, true) && !InheritanceUtil.hasEnclosingInstanceInScope(containingClass, anonymClass.getParent(), true, true)) { - return true; - } - } - return false; - } + private static String composeLambdaText(PsiMethod method) { + StringBuilder buf = new StringBuilder(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != 1) { + buf.append("("); + } + buf.append(StringUtil.join(parameters, ReplaceWithLambdaFix::composeParameter, ",")); + if (parameters.length != 1) { + buf.append(")"); + } + buf.append("-> {}"); + return buf.toString(); + } - public static void restoreComments(Collection comments, PsiElement lambda) { - PsiElement anchor = PsiTreeUtil.getParentOfType(lambda, PsiStatement.class, PsiField.class); - if (anchor == null) { - anchor = lambda; - } - for (PsiComment comment : comments) { - anchor.getParent().addBefore(comment, anchor); + private static String composeParameter(PsiParameter parameter) { + return parameter.getName(); + } } - } - private static class ForbiddenRefsChecker extends JavaRecursiveElementWalkingVisitor { - private boolean myBodyContainsForbiddenRefs; + public static boolean functionalInterfaceMethodReferenced( + PsiMethod psiMethod, + PsiAnonymousClass anonymousClass, + PsiCallExpression callExpression + ) { + if (psiMethod != null && !psiMethod.isStatic()) { + PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { + return false; + } - private final PsiMethod myMethod; - private final PsiAnonymousClass myAnonymClass; + if (callExpression instanceof PsiMethodCallExpression methodCall && methodCall.getMethodExpression().isQualified()) { + return false; + } - public ForbiddenRefsChecker(PsiMethod method, PsiAnonymousClass aClass) { - myMethod = method; - myAnonymClass = aClass; + if (InheritanceUtil.isInheritorOrSelf(anonymousClass, containingClass, true) + && !InheritanceUtil.hasEnclosingInstanceInScope(containingClass, anonymousClass.getParent(), true, true)) { + return true; + } + } + return false; } - @Override - public void visitMethodCallExpression(PsiMethodCallExpression methodCallExpression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - super.visitMethodCallExpression(methodCallExpression); - final PsiMethod psiMethod = methodCallExpression.resolveMethod(); - if (psiMethod == myMethod || - functionalInterfaceMethodReferenced(psiMethod, myAnonymClass, methodCallExpression) || - psiMethod != null && - !methodCallExpression.getMethodExpression().isQualified() && - "getClass".equals(psiMethod.getName()) && - psiMethod.getParameterList().getParametersCount() == 0) { - myBodyContainsForbiddenRefs = true; - } + @RequiredWriteAction + public static void restoreComments(Collection comments, PsiElement lambda) { + PsiElement anchor = PsiTreeUtil.getParentOfType(lambda, PsiStatement.class, PsiField.class); + if (anchor == null) { + anchor = lambda; + } + for (PsiComment comment : comments) { + anchor.getParent().addBefore(comment, anchor); + } } - @Override - public void visitThisExpression(PsiThisExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - if (expression.getQualifier() == null) { - myBodyContainsForbiddenRefs = true; - } - } + private static class ForbiddenRefsChecker extends JavaRecursiveElementWalkingVisitor { + private boolean myBodyContainsForbiddenRefs; - @Override - public void visitSuperExpression(PsiSuperExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - if (expression.getQualifier() == null) { - myBodyContainsForbiddenRefs = true; - } - } + private final PsiMethod myMethod; + private final PsiAnonymousClass myAnonClass; - @Override - public void visitVariable(PsiVariable variable) { - if (myBodyContainsForbiddenRefs) { - return; - } + public ForbiddenRefsChecker(PsiMethod method, PsiAnonymousClass aClass) { + myMethod = method; + myAnonClass = aClass; + } - super.visitVariable(variable); - } + @Override + public void visitMethodCallExpression(PsiMethodCallExpression methodCallExpression) { + if (myBodyContainsForbiddenRefs) { + return; + } - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - if (myBodyContainsForbiddenRefs) { - return; - } - - super.visitReferenceExpression(expression); - if (!(expression.getParent() instanceof PsiMethodCallExpression)) { - final PsiMember member = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMember.class); - if (member instanceof PsiField || member instanceof PsiClassInitializer) { - final PsiElement resolved = expression.resolve(); - final PsiClass memberContainingClass = member.getContainingClass(); - if (resolved instanceof PsiField && - memberContainingClass != null && - PsiTreeUtil.isAncestor(((PsiField) resolved).getContainingClass(), memberContainingClass, false) && - expression.getQualifierExpression() == null) { - final PsiExpression initializer = ((PsiField) resolved).getInitializer(); - if (initializer == null || - resolved == member || - initializer.getTextOffset() > myAnonymClass.getTextOffset() && ((PsiField) resolved).hasModifierProperty(PsiModifier.STATIC) == member.hasModifierProperty(PsiModifier - .STATIC)) { - myBodyContainsForbiddenRefs = true; + super.visitMethodCallExpression(methodCallExpression); + PsiMethod psiMethod = methodCallExpression.resolveMethod(); + if (psiMethod == myMethod || + functionalInterfaceMethodReferenced(psiMethod, myAnonClass, methodCallExpression) || + psiMethod != null && + !methodCallExpression.getMethodExpression().isQualified() && + "getClass".equals(psiMethod.getName()) && + psiMethod.getParameterList().getParametersCount() == 0) { + myBodyContainsForbiddenRefs = true; } - } - } else { - final PsiMethod method = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMethod.class); - if (method != null && method.isConstructor()) { - final PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiField && - ((PsiField) resolved).hasModifierProperty(PsiModifier.FINAL) && - ((PsiField) resolved).getInitializer() == null && - ((PsiField) resolved).getContainingClass() == method.getContainingClass()) { - try { - final PsiCodeBlock constructorBody = method.getBody(); - if (constructorBody != null) { - final ControlFlow flow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(constructorBody); - final int startOffset = flow.getStartOffset(myAnonymClass); - final Collection writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, startOffset, false); - if (!writtenVariables.contains(resolved)) { - myBodyContainsForbiddenRefs = true; - } - } - } catch (AnalysisCanceledException e) { + } + + @Override + public void visitThisExpression(PsiThisExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + if (expression.getQualifier() == null) { myBodyContainsForbiddenRefs = true; - } } - } } - } - } - public boolean hasForbiddenRefs() { - return myBodyContainsForbiddenRefs; + @Override + public void visitSuperExpression(PsiSuperExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + if (expression.getQualifier() == null) { + myBodyContainsForbiddenRefs = true; + } + } + + @Override + public void visitVariable(PsiVariable variable) { + if (myBodyContainsForbiddenRefs) { + return; + } + + super.visitVariable(variable); + } + + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + if (myBodyContainsForbiddenRefs) { + return; + } + + super.visitReferenceExpression(expression); + if (!(expression.getParent() instanceof PsiMethodCallExpression)) { + PsiMember member = PsiTreeUtil.getParentOfType(myAnonClass, PsiMember.class); + if (member instanceof PsiField || member instanceof PsiClassInitializer) { + PsiElement resolved = expression.resolve(); + PsiClass memberContainingClass = member.getContainingClass(); + if (resolved instanceof PsiField field + && memberContainingClass != null + && PsiTreeUtil.isAncestor(field.getContainingClass(), memberContainingClass, false) + && expression.getQualifierExpression() == null) { + PsiExpression initializer = field.getInitializer(); + if (initializer == null + || resolved == member + || initializer.getTextOffset() > myAnonClass.getTextOffset() && field.isStatic() == member.isStatic()) { + myBodyContainsForbiddenRefs = true; + } + } + } + else { + PsiMethod method = PsiTreeUtil.getParentOfType(myAnonClass, PsiMethod.class); + if (method != null + && method.isConstructor() + && expression.resolve() instanceof PsiField field + && field.isFinal() + && field.getInitializer() == null + && field.getContainingClass() == method.getContainingClass()) { + try { + PsiCodeBlock constructorBody = method.getBody(); + if (constructorBody != null) { + ControlFlow flow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(constructorBody); + int startOffset = flow.getStartOffset(myAnonClass); + Collection writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, startOffset, false); + if (!writtenVariables.contains(field)) { + myBodyContainsForbiddenRefs = true; + } + } + } + catch (AnalysisCanceledException e) { + myBodyContainsForbiddenRefs = true; + } + } + } + } + } + + public boolean hasForbiddenRefs() { + return myBodyContainsForbiddenRefs; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspectionState.java new file mode 100644 index 0000000000..3c9b43e60e --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeLambdaInspectionState.java @@ -0,0 +1,39 @@ +package com.intellij.java.analysis.impl.codeInspection; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 19/03/2023 + */ +public class AnonymousCanBeLambdaInspectionState implements InspectionToolState { + public boolean reportNotAnnotatedInterfaces = true; + + @Nullable + @Override + public AnonymousCanBeLambdaInspectionState getState() { + return this; + } + + @Override + public void loadState(AnonymousCanBeLambdaInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder builder = ConfigurableBuilder.newBuilder(); + builder.checkBox(LocalizeValue.localizeTODO("Report when interface is not annotated with @FunctionalInterface"), + () -> reportNotAnnotatedInterfaces, + b -> reportNotAnnotatedInterfaces = b); + return builder.buildUnnamed(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java index 5182491670..c7cb7affb0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspection.java @@ -16,30 +16,26 @@ package com.intellij.java.analysis.impl.codeInspection; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import com.intellij.java.language.psi.util.RedundantCastUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.document.util.TextRange; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.editor.inspection.ui.SingleCheckboxOptionsPanel; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiComment; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.collection.ContainerUtil; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; + import java.util.Collection; import java.util.Collections; @@ -47,138 +43,161 @@ * User: anna */ @ExtensionImpl -public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool { - private static final Logger LOG = Logger.getInstance(AnonymousCanBeMethodReferenceInspection.class); - - public boolean reportNotAnnotatedInterfaces = true; - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Nls - @Nonnull - @Override - public String getGroupDisplayName() { - return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; - } - - @Nls - @Nonnull - @Override - public String getDisplayName() { - return "Anonymous type can be replaced with method reference"; - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public String getShortName() { - return "Anonymous2MethodRef"; - } - - @Nullable - @Override - public JComponent createOptionsPanel() { - return new SingleCheckboxOptionsPanel("Report when interface is not annotated with @FunctionalInterface", this, "reportNotAnnotatedInterfaces"); - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitAnonymousClass(PsiAnonymousClass aClass) { - super.visitAnonymousClass(aClass); - if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, reportNotAnnotatedInterfaces, Collections.emptySet())) { - final PsiMethod method = aClass.getMethods()[0]; - final PsiCodeBlock body = method.getBody(); - PsiExpression lambdaBodyCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(body, false); - final PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(method.getParameterList().getParameters(), aClass.getBaseClassType(), - aClass.getParent(), lambdaBodyCandidate); - if (methodRefCandidate instanceof PsiCallExpression) { - final PsiCallExpression callExpression = (PsiCallExpression) methodRefCandidate; - final PsiMethod resolveMethod = callExpression.resolveMethod(); - if (resolveMethod != method && !AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, callExpression)) { - final PsiElement parent = aClass.getParent(); - if (parent instanceof PsiNewExpression) { - final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression) parent).getClassOrAnonymousClassReference(); - if (classReference != null) { - final PsiElement lBrace = aClass.getLBrace(); - LOG.assertTrue(lBrace != null); - final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); - ProblemHighlightType highlightType = LambdaCanBeMethodReferenceInspection.checkQualifier(lambdaBodyCandidate) ? ProblemHighlightType.LIKE_UNUSED_SYMBOL : - ProblemHighlightType.INFORMATION; - holder.registerProblem(parent, "Anonymous #ref #loc can be replaced with method reference", highlightType, rangeInElement, new ReplaceWithMethodRefFix()); - } - } - } - } - } - } - }; - } +public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool { + private static final Logger LOG = Logger.getInstance(AnonymousCanBeMethodReferenceInspection.class); + - private static class ReplaceWithMethodRefFix implements LocalQuickFix { - @Nonnull @Override - public String getFamilyName() { - return "Replace with method reference"; + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (element instanceof PsiNewExpression) { - final PsiAnonymousClass anonymousClass = ((PsiNewExpression) element).getAnonymousClass(); - if (anonymousClass == null) { - return; - } - final PsiMethod[] methods = anonymousClass.getMethods(); - if (methods.length != 1) { - return; - } + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); + } + + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Anonymous type can be replaced with method reference"); + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Override + public String getShortName() { + return "Anonymous2MethodRef"; + } + + @Override + public InspectionToolState createStateProvider() { + return new AnonymousCanBeMethodReferenceInspectionState(); + } - final PsiParameter[] parameters = methods[0].getParameterList().getParameters(); - final PsiType functionalInterfaceType = anonymousClass.getBaseClassType(); - PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(methods[0].getBody(), false); - final PsiExpression candidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(parameters, functionalInterfaceType, anonymousClass.getParent(), methodRefCandidate); + @Override + public PsiElementVisitor buildVisitorImpl( + final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + AnonymousCanBeMethodReferenceInspectionState state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitAnonymousClass(PsiAnonymousClass aClass) { + super.visitAnonymousClass(aClass); + if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda( + aClass, + true, + state.reportNotAnnotatedInterfaces, + Collections.emptySet() + )) { + PsiMethod method = aClass.getMethods()[0]; + PsiCodeBlock body = method.getBody(); + PsiExpression lambdaBodyCandidate = + LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(body, false); + PsiExpression methodRefCandidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem( + method.getParameterList().getParameters(), + aClass.getBaseClassType(), + aClass.getParent(), + lambdaBodyCandidate + ); + if (methodRefCandidate instanceof PsiCallExpression call) { + PsiMethod resolveMethod = call.resolveMethod(); + if (resolveMethod != method + && !AnonymousCanBeLambdaInspection.functionalInterfaceMethodReferenced(resolveMethod, aClass, call) + && aClass.getParent() instanceof PsiNewExpression newExpr) { + PsiJavaCodeReferenceElement classReference = newExpr.getClassOrAnonymousClassReference(); + if (classReference != null) { + PsiElement lBrace = aClass.getLBrace(); + LOG.assertTrue(lBrace != null); + TextRange rangeInElement = + new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); + ProblemHighlightType highlightType = + LambdaCanBeMethodReferenceInspection.checkQualifier(lambdaBodyCandidate) + ? ProblemHighlightType.LIKE_UNUSED_SYMBOL + : ProblemHighlightType.INFORMATION; + holder.newProblem(LocalizeValue.localizeTODO("Anonymous #ref #loc can be replaced with method reference")) + .range(newExpr, rangeInElement) + .highlightType(highlightType) + .withFix(new ReplaceWithMethodRefFix()) + .create(); + } + } + } + } + } + }; + } + + private static class ReplaceWithMethodRefFix implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with method reference"); + } - final String methodRefText = LambdaCanBeMethodReferenceInspection.createMethodReferenceText(candidate, functionalInterfaceType, parameters); + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + if (descriptor.getPsiElement() instanceof PsiNewExpression newExpression) { + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + if (anonymousClass == null) { + return; + } + PsiMethod[] methods = anonymousClass.getMethods(); + if (methods.length != 1) { + return; + } - replaceWithMethodReference(project, methodRefText, anonymousClass.getBaseClassType(), anonymousClass.getParent()); - } + PsiParameter[] parameters = methods[0].getParameterList().getParameters(); + PsiType functionalInterfaceType = anonymousClass.getBaseClassType(); + PsiExpression methodRefCandidate = + LambdaCanBeMethodReferenceInspection.extractMethodReferenceCandidateExpression(methods[0].getBody(), false); + PsiExpression candidate = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem( + parameters, + functionalInterfaceType, + anonymousClass.getParent(), + methodRefCandidate + ); + + String methodRefText = + LambdaCanBeMethodReferenceInspection.createMethodReferenceText(candidate, functionalInterfaceType, parameters); + + replaceWithMethodReference(project, methodRefText, anonymousClass.getBaseClassType(), anonymousClass.getParent()); + } + } } - } - - static void replaceWithMethodReference(@Nonnull Project project, String methodRefText, PsiType castType, PsiElement replacementTarget) { - final Collection comments = ContainerUtil.map(PsiTreeUtil.findChildrenOfType(replacementTarget, PsiComment.class), comment -> (PsiComment) comment.copy()); - - if (methodRefText != null) { - final String canonicalText = castType.getCanonicalText(); - final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText("(" + canonicalText + ")" + methodRefText, replacementTarget); - - PsiElement castExpr = replacementTarget.replace(psiExpression); - if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression) castExpr)) { - final PsiExpression operand = ((PsiTypeCastExpression) castExpr).getOperand(); - LOG.assertTrue(operand != null); - castExpr = castExpr.replace(operand); - } - - PsiElement anchor = PsiTreeUtil.getParentOfType(castExpr, PsiStatement.class); - if (anchor == null) { - anchor = castExpr; - } - for (PsiComment comment : comments) { - anchor.getParent().addBefore(comment, anchor); - } - JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr); + + @RequiredWriteAction + static void replaceWithMethodReference(Project project, String methodRefText, PsiType castType, PsiElement replacementTarget) { + Collection comments = ContainerUtil.map( + PsiTreeUtil.findChildrenOfType(replacementTarget, PsiComment.class), + comment -> (PsiComment) comment.copy() + ); + + if (methodRefText != null) { + String canonicalText = castType.getCanonicalText(); + PsiExpression psiExpression = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText("(" + canonicalText + ")" + methodRefText, replacementTarget); + + PsiElement castExpr = replacementTarget.replace(psiExpression); + if (RedundantCastUtil.isCastRedundant((PsiTypeCastExpression) castExpr)) { + PsiExpression operand = ((PsiTypeCastExpression) castExpr).getOperand(); + LOG.assertTrue(operand != null); + castExpr = castExpr.replace(operand); + } + + PsiElement anchor = PsiTreeUtil.getParentOfType(castExpr, PsiStatement.class); + if (anchor == null) { + anchor = castExpr; + } + for (PsiComment comment : comments) { + anchor.getParent().addBefore(comment, anchor); + } + JavaCodeStyleManager.getInstance(project).shortenClassReferences(castExpr); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspectionState.java new file mode 100644 index 0000000000..2183c19e02 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/AnonymousCanBeMethodReferenceInspectionState.java @@ -0,0 +1,39 @@ +package com.intellij.java.analysis.impl.codeInspection; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 19/03/2023 + */ +public class AnonymousCanBeMethodReferenceInspectionState implements InspectionToolState { + public boolean reportNotAnnotatedInterfaces = true; + + @Nullable + @Override + public AnonymousCanBeMethodReferenceInspectionState getState() { + return this; + } + + @Override + public void loadState(AnonymousCanBeMethodReferenceInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder builder = ConfigurableBuilder.newBuilder(); + builder.checkBox(LocalizeValue.localizeTODO("Report when interface is not annotated with @FunctionalInterface"), + () -> reportNotAnnotatedInterfaces, + b -> reportNotAnnotatedInterfaces = b); + return builder.buildUnnamed(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BaseJavaLocalInspectionTool.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BaseJavaLocalInspectionTool.java index b908aa4232..7692ec1672 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BaseJavaLocalInspectionTool.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BaseJavaLocalInspectionTool.java @@ -19,34 +19,33 @@ import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.java.analysis.codeInspection.SuppressManager; import consulo.language.editor.inspection.CustomSuppressableInspectionTool; +import consulo.language.editor.inspection.GlobalInspectionTool; import consulo.language.editor.inspection.LocalInspectionTool; import consulo.language.editor.intention.SuppressIntentionAction; import consulo.language.editor.rawHighlight.HighlightDisplayKey; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; /** * Implement this abstract class in order to provide new inspection tool functionality. The major API limitation here is - * subclasses should be stateless. Thus check<XXX> methods will be called in no particular order and - * instances of this class provided by {@link InspectionToolProvider#getInspectionClasses()} will be created on demand. + * subclasses should be stateless. * The other important thing is problem anchors (PsiElements) reported by check<XXX> methods should * lie under corresponding first parameter of one method. * * @see GlobalInspectionTool */ -public abstract class BaseJavaLocalInspectionTool extends AbstractBaseJavaLocalInspectionTool implements CustomSuppressableInspectionTool { - @Override - public SuppressIntentionAction[] getSuppressActions(final PsiElement element) { - return SuppressManager.getInstance().createSuppressActions(HighlightDisplayKey.find(getShortName())); - } +public abstract class BaseJavaLocalInspectionTool extends AbstractBaseJavaLocalInspectionTool implements CustomSuppressableInspectionTool { + @Override + public SuppressIntentionAction[] getSuppressActions(final PsiElement element) { + return SuppressManager.getInstance().createSuppressActions(HighlightDisplayKey.find(getShortName())); + } - @Override - public boolean isSuppressedFor(@Nonnull PsiElement element) { - return isSuppressedFor(element, this); - } + @Override + public boolean isSuppressedFor(PsiElement element) { + return isSuppressedFor(element, this); + } - public static boolean isSuppressedFor(@Nonnull PsiElement element, @Nonnull LocalInspectionTool tool) { - return BaseJavaBatchLocalInspectionTool.isSuppressedFor(element, tool); - } + public static boolean isSuppressedFor(PsiElement element, LocalInspectionTool tool) { + return BaseJavaBatchLocalInspectionTool.isSuppressedFor(element, tool); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BatchSuppressManagerImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BatchSuppressManagerImpl.java index 629f65631f..73f8c655ba 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BatchSuppressManagerImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/BatchSuppressManagerImpl.java @@ -25,16 +25,14 @@ import com.intellij.java.language.psi.PsiModifierListOwner; import jakarta.inject.Singleton; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; @Singleton @ServiceImpl public class BatchSuppressManagerImpl implements BatchSuppressManager { - @Nonnull @Override - public SuppressQuickFix[] createBatchSuppressActions(@Nonnull HighlightDisplayKey displayKey) { + public SuppressQuickFix[] createBatchSuppressActions(HighlightDisplayKey displayKey) { return new SuppressQuickFix[]{ new SuppressByJavaCommentFix(displayKey), new SuppressLocalWithCommentFix(displayKey), @@ -47,53 +45,52 @@ public SuppressQuickFix[] createBatchSuppressActions(@Nonnull HighlightDisplayKe } @Override - public boolean isSuppressedFor(@Nonnull final PsiElement element, final String toolId) { + public boolean isSuppressedFor(final PsiElement element, final String toolId) { return JavaSuppressionUtil.getElementToolSuppressedIn(element, toolId) != null; } @Override @Nullable - public PsiElement getElementMemberSuppressedIn(@Nonnull final PsiDocCommentOwner owner, final String inspectionToolID) { + public PsiElement getElementMemberSuppressedIn(final PsiDocCommentOwner owner, final String inspectionToolID) { return JavaSuppressionUtil.getElementMemberSuppressedIn(owner, inspectionToolID); } @Override @Nullable - public PsiElement getAnnotationMemberSuppressedIn(@Nonnull final PsiModifierListOwner owner, final String inspectionToolID) { + public PsiElement getAnnotationMemberSuppressedIn(final PsiModifierListOwner owner, final String inspectionToolID) { return JavaSuppressionUtil.getAnnotationMemberSuppressedIn(owner, inspectionToolID); } @Override @Nullable - public PsiElement getDocCommentToolSuppressedIn(@Nonnull final PsiDocCommentOwner owner, final String inspectionToolID) { + public PsiElement getDocCommentToolSuppressedIn(final PsiDocCommentOwner owner, final String inspectionToolID) { return JavaSuppressionUtil.getDocCommentToolSuppressedIn(owner, inspectionToolID); } @Override - @Nonnull - public Collection getInspectionIdsSuppressedInAnnotation(@Nonnull final PsiModifierListOwner owner) { + public Collection getInspectionIdsSuppressedInAnnotation(final PsiModifierListOwner owner) { return JavaSuppressionUtil.getInspectionIdsSuppressedInAnnotation(owner); } @Override @Nullable - public String getSuppressedInspectionIdsIn(@Nonnull PsiElement element) { + public String getSuppressedInspectionIdsIn(PsiElement element) { return JavaSuppressionUtil.getSuppressedInspectionIdsIn(element); } @Override @Nullable - public PsiElement getElementToolSuppressedIn(@Nonnull final PsiElement place, final String toolId) { + public PsiElement getElementToolSuppressedIn(final PsiElement place, final String toolId) { return JavaSuppressionUtil.getElementToolSuppressedIn(place, toolId); } @Override - public boolean canHave15Suppressions(@Nonnull final PsiElement file) { + public boolean canHave15Suppressions(final PsiElement file) { return JavaSuppressionUtil.canHave15Suppressions(file); } @Override - public boolean alreadyHas14Suppressions(@Nonnull final PsiDocCommentOwner commentOwner) { + public boolean alreadyHas14Suppressions(final PsiDocCommentOwner commentOwner) { return JavaSuppressionUtil.alreadyHas14Suppressions(commentOwner); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ControlFlowUtils.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ControlFlowUtils.java new file mode 100644 index 0000000000..d3e83f915e --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ControlFlowUtils.java @@ -0,0 +1,1074 @@ +/* + * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInspection; + +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.JavaPsiPatternUtil; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.language.psi.PsiComment; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiWhiteSpace; +import consulo.language.psi.util.PsiTreeUtil; +import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Contract; + +public class ControlFlowUtils { + + private ControlFlowUtils() { + } + + public static boolean isElseIf(PsiIfStatement ifStatement) { + final PsiElement parent = ifStatement.getParent(); + if (!(parent instanceof PsiIfStatement)) { + return false; + } + final PsiIfStatement parentStatement = (PsiIfStatement) parent; + final PsiStatement elseBranch = parentStatement.getElseBranch(); + return ifStatement.equals(elseBranch); + } + + public static boolean statementMayCompleteNormally(@Nullable PsiStatement statement) { + return statementMayCompleteNormally(statement, null); + } + + private static boolean statementMayCompleteNormally(@Nullable PsiStatement statement, @Nullable PsiMethod psiMethod) { + if (statement == null) { + return true; + } + if (statement instanceof PsiBreakStatement || statement instanceof PsiContinueStatement || statement instanceof PsiYieldStatement || + statement instanceof PsiReturnStatement || statement instanceof PsiThrowStatement) { + return false; + } + else if (statement instanceof PsiExpressionListStatement || statement instanceof PsiEmptyStatement || + statement instanceof PsiAssertStatement || statement instanceof PsiDeclarationStatement || + statement instanceof PsiSwitchLabelStatement || statement instanceof PsiForeachStatementBase) { + return true; + } + else if (statement instanceof final PsiExpressionStatement expressionStatement) { + final PsiExpression expression = expressionStatement.getExpression(); + if (!(expression instanceof final PsiMethodCallExpression methodCallExpression)) { + return true; + } + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) { + return true; + } + if (method.equals(psiMethod)) { + return false; + } + final String methodName = method.getName(); + if (!methodName.equals("exit")) { + return true; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return true; + } + final String className = aClass.getQualifiedName(); + return !"java.lang.System".equals(className); + } + else if (statement instanceof PsiForStatement forStatement) { + return forStatementMayCompleteNormally(forStatement); + } + else if (statement instanceof PsiWhileStatement whileStatement) { + return whileStatementMayCompleteNormally(whileStatement); + } + else if (statement instanceof PsiDoWhileStatement doWhileStatement) { + return doWhileStatementMayCompleteNormally(doWhileStatement, psiMethod); + } + else if (statement instanceof PsiSynchronizedStatement synchronizedStatement) { + return codeBlockMayCompleteNormally(synchronizedStatement.getBody(), psiMethod); + } + else if (statement instanceof PsiBlockStatement block) { + return codeBlockMayCompleteNormally(block.getCodeBlock(), psiMethod); + } + else if (statement instanceof PsiLabeledStatement labeled) { + return labeledStatementMayCompleteNormally(labeled, psiMethod); + } + else if (statement instanceof PsiIfStatement ifStatement) { + return ifStatementMayCompleteNormally(ifStatement, psiMethod); + } + else if (statement instanceof PsiTryStatement tryStatement) { + return tryStatementMayCompleteNormally(tryStatement, psiMethod); + } + else if (statement instanceof PsiSwitchStatement switchStatement) { + return switchStatementMayCompleteNormally(switchStatement, psiMethod); + } + else if (statement instanceof PsiSwitchLabeledRuleStatement rule) { + PsiStatement body = rule.getBody(); + return body != null && statementMayCompleteNormally(body, psiMethod); + } + else if (statement instanceof PsiTemplateStatement || statement instanceof PsiClassLevelDeclarationStatement) { + return true; + } + else { + assert false : "unknown statement type: " + statement.getClass(); + return true; + } + } + + private static boolean doWhileStatementMayCompleteNormally(PsiDoWhileStatement loopStatement, @Nullable PsiMethod method) { + final PsiExpression condition = loopStatement.getCondition(); + final Object value = ExpressionUtils.computeConstantExpression(condition); + final PsiStatement body = loopStatement.getBody(); + return statementMayCompleteNormally(body, method) && value != Boolean.TRUE + || statementContainsBreakToStatementOrAncestor(loopStatement) || statementContainsContinueToAncestor(loopStatement); + } + + private static boolean statementContainsBreakToStatementOrAncestor(PsiStatement statement) { + final BreakFinder breakFinder = new BreakFinder(statement, true); + statement.accept(breakFinder); + return breakFinder.breakFound(); + } + + private static boolean whileStatementMayCompleteNormally(PsiWhileStatement loopStatement) { + final PsiExpression condition = loopStatement.getCondition(); + final Object value = ExpressionUtils.computeConstantExpression(condition); + return value != Boolean.TRUE || statementIsBreakTarget(loopStatement) || statementContainsContinueToAncestor(loopStatement); + } + + private static boolean forStatementMayCompleteNormally(PsiForStatement loopStatement) { + if (statementIsBreakTarget(loopStatement)) { + return true; + } + if (statementContainsContinueToAncestor(loopStatement)) { + return true; + } + final PsiExpression condition = loopStatement.getCondition(); + if (condition == null) { + return false; + } + final Object value = ExpressionUtils.computeConstantExpression(condition); + return Boolean.TRUE != value; + } + + private static boolean switchStatementMayCompleteNormally(PsiSwitchStatement switchStatement, @Nullable PsiMethod method) { + if (statementIsBreakTarget(switchStatement)) { + return true; + } + final PsiExpression selectorExpression = switchStatement.getExpression(); + if (selectorExpression == null) { + return true; + } + final PsiType selectorType = selectorExpression.getType(); + if (selectorType == null) { + return true; + } + final PsiCodeBlock body = switchStatement.getBody(); + if (body == null) { + return true; + } + final PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) { + return true; + } + int numCases = 0; + boolean hasDefaultCase = false, hasUnconditionalPattern = false; + for (PsiStatement statement : statements) { + if (statement instanceof PsiSwitchLabelStatementBase switchLabelStatement) { + if (statement instanceof PsiSwitchLabelStatement) { + numCases++; + } + if (hasDefaultCase || hasUnconditionalPattern) { + continue; + } + if (switchLabelStatement.isDefaultCase()) { + hasDefaultCase = true; + continue; + } + // this information doesn't exist in spec draft (14.22) for pattern in switch as expected + // but for now javac considers the switch statement containing at least either case default label element or an unconditional pattern "incomplete normally" + PsiCaseLabelElementList labelElementList = switchLabelStatement.getCaseLabelElementList(); + if (labelElementList == null) { + continue; + } + for (PsiCaseLabelElement labelElement : labelElementList.getElements()) { + if (labelElement instanceof PsiDefaultCaseLabelElement) { + hasDefaultCase = true; + } + else if (labelElement instanceof PsiPattern) { + hasUnconditionalPattern = JavaPsiPatternUtil.isUnconditionalForType(labelElement, selectorType); + } + } + } + else if (statement instanceof final PsiBreakStatement breakStatement && breakStatement.getLabelIdentifier() == null) { + return true; + } + } + // todo actually there is no information about an impact of enum constants on switch statements being complete normally in spec (Unreachable statements) + // todo comparing to javac that produces some false-negative highlighting in enum switch statements containing all possible constants + final boolean isEnum = isEnumSwitch(switchStatement); + if (!hasDefaultCase && !hasUnconditionalPattern && !isEnum) { + return true; + } + if (!hasDefaultCase && !hasUnconditionalPattern) { + final PsiClass aClass = ((PsiClassType) selectorType).resolve(); + if (aClass == null) { + return true; + } + if (!hasChildrenOfTypeCount(aClass, numCases, PsiEnumConstant.class)) { + return true; + } + } + // todo replace the code and comments below with the method that helps to understand whether + // todo we need to check every statement or only the last statement in the code block + // 14.22. Unreachable Statements + // We need to check every rule's body not just the last one if the switch block includes the switch rules + boolean isLabeledRuleSwitch = statements[0] instanceof PsiSwitchLabeledRuleStatement; + if (isLabeledRuleSwitch) { + for (PsiStatement statement : statements) { + if (statementMayCompleteNormally(statement, method)) { + return true; + } + } + return false; + } + return statementMayCompleteNormally(statements[statements.length - 1], method); + } + + private static boolean isEnumSwitch(PsiSwitchStatement statement) { + final PsiExpression expression = statement.getExpression(); + if (expression == null) { + return false; + } + final PsiType type = expression.getType(); + if (type == null) { + return false; + } + if (!(type instanceof PsiClassType)) { + return false; + } + final PsiClass aClass = ((PsiClassType) type).resolve(); + return aClass != null && aClass.isEnum(); + } + + private static boolean tryStatementMayCompleteNormally(PsiTryStatement tryStatement, @Nullable PsiMethod method) { + final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); + if (finallyBlock != null) { + if (!codeBlockMayCompleteNormally(finallyBlock, method)) { + return false; + } + } + final PsiCodeBlock tryBlock = tryStatement.getTryBlock(); + if (codeBlockMayCompleteNormally(tryBlock, method)) { + return true; + } + final PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks(); + for (final PsiCodeBlock catchBlock : catchBlocks) { + if (codeBlockMayCompleteNormally(catchBlock, method)) { + return true; + } + } + return false; + } + + private static boolean ifStatementMayCompleteNormally(PsiIfStatement ifStatement, @Nullable PsiMethod method) { + final PsiExpression condition = ifStatement.getCondition(); + final Object value = ExpressionUtils.computeConstantExpression(condition); + final PsiStatement thenBranch = ifStatement.getThenBranch(); + if (value == Boolean.TRUE) { + return statementMayCompleteNormally(thenBranch, method); + } + final PsiStatement elseBranch = ifStatement.getElseBranch(); + if (value == Boolean.FALSE) { + return statementMayCompleteNormally(elseBranch, method); + } + // process branch with fewer statements first + PsiStatement branch1; + PsiStatement branch2; + if ((thenBranch == null ? 0 : thenBranch.getTextLength()) < (elseBranch == null ? 0 : elseBranch.getTextLength())) { + branch1 = thenBranch; + branch2 = elseBranch; + } + else { + branch2 = thenBranch; + branch1 = elseBranch; + } + return statementMayCompleteNormally(branch1, method) || statementMayCompleteNormally(branch2, method); + } + + private static boolean labeledStatementMayCompleteNormally(PsiLabeledStatement labeledStatement, @Nullable PsiMethod method) { + final PsiStatement statement = labeledStatement.getStatement(); + if (statement == null) { + return false; + } + return statementMayCompleteNormally(statement, method) || statementContainsBreakToStatementOrAncestor(statement); + } + + public static boolean codeBlockMayCompleteNormally(@Nullable PsiCodeBlock block) { + return codeBlockMayCompleteNormally(block, null); + } + + private static boolean codeBlockMayCompleteNormally(@Nullable PsiCodeBlock block, @Nullable PsiMethod method) { + if (block == null) { + return true; + } + final PsiStatement[] statements = block.getStatements(); + for (final PsiStatement statement : statements) { + if (!statementMayCompleteNormally(statement, method)) { + return false; + } + } + return true; + } + + private static boolean statementIsBreakTarget(PsiStatement statement) { + final BreakFinder breakFinder = new BreakFinder(statement, false); + statement.accept(breakFinder); + return breakFinder.breakFound(); + } + + private static boolean statementContainsContinueToAncestor(PsiStatement statement) { + PsiElement parent = statement.getParent(); + while (parent instanceof PsiLabeledStatement) { + statement = (PsiStatement) parent; + parent = parent.getParent(); + } + final ContinueToAncestorFinder continueToAncestorFinder = new ContinueToAncestorFinder(statement); + statement.accept(continueToAncestorFinder); + return continueToAncestorFinder.continueToAncestorFound(); + } + + public static boolean containsReturn(PsiElement element) { + final ReturnFinder returnFinder = new ReturnFinder(); + element.accept(returnFinder); + return returnFinder.returnFound(); + } + + public static boolean statementIsContinueTarget(PsiStatement statement) { + final ContinueFinder continueFinder = new ContinueFinder(statement); + statement.accept(continueFinder); + return continueFinder.continueFound(); + } + + public static boolean containsSystemExit(PsiElement element) { + final SystemExitFinder systemExitFinder = new SystemExitFinder(); + element.accept(systemExitFinder); + return systemExitFinder.exitFound(); + } + + public static boolean elementContainsCallToMethod(PsiElement context, + String containingClassName, + PsiType returnType, + String methodName, + PsiType... parameterTypes) { + final MethodCallFinder methodCallFinder = new MethodCallFinder(containingClassName, returnType, methodName, parameterTypes); + context.accept(methodCallFinder); + return methodCallFinder.containsCallToMethod(); + } + + public static boolean isInLoop(PsiElement element) { + final PsiLoopStatement loopStatement = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class, true, PsiClass.class); + if (loopStatement == null) { + return false; + } + final PsiStatement body = loopStatement.getBody(); + return body != null && PsiTreeUtil.isAncestor(body, element, true); + } + + public static boolean isInFinallyBlock(PsiElement element) { + PsiElement currentElement = element; + while (true) { + final PsiTryStatement tryStatement = + PsiTreeUtil.getParentOfType(currentElement, PsiTryStatement.class, true, PsiClass.class, PsiLambdaExpression.class); + if (tryStatement == null) { + return false; + } + final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); + if (finallyBlock != null) { + if (PsiTreeUtil.isAncestor(finallyBlock, currentElement, true)) { + final PsiMethod elementMethod = PsiTreeUtil.getParentOfType(currentElement, PsiMethod.class); + final PsiMethod finallyMethod = PsiTreeUtil.getParentOfType(finallyBlock, PsiMethod.class); + return elementMethod != null && elementMethod.equals(finallyMethod); + } + } + currentElement = tryStatement; + } + } + + public static boolean isInCatchBlock(PsiElement element) { + return PsiTreeUtil.getParentOfType(element, PsiCatchSection.class, true, PsiClass.class) != null; + } + + public static boolean isInExitStatement(PsiExpression expression) { + return isInReturnStatementArgument(expression) || isInThrowStatementArgument(expression); + } + + private static boolean isInReturnStatementArgument(PsiExpression expression) { + return PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class) != null; + } + + public static boolean isInThrowStatementArgument(PsiExpression expression) { + return PsiTreeUtil.getParentOfType(expression, PsiThrowStatement.class) != null; + } + + @Nullable + public static PsiStatement stripBraces(@Nullable PsiStatement statement) { + if (statement instanceof PsiBlockStatement) { + final PsiBlockStatement block = (PsiBlockStatement) statement; + final PsiStatement onlyStatement = getOnlyStatementInBlock(block.getCodeBlock()); + return (onlyStatement != null) ? onlyStatement : block; + } + else { + return statement; + } + } + + public static boolean statementCompletesWithStatement(PsiElement containingStatement, PsiStatement statement) { + PsiElement statementToCheck = statement; + while (true) { + if (statementToCheck.equals(containingStatement)) { + return true; + } + final PsiElement container = getContainingStatementOrBlock(statementToCheck); + if (container == null) { + return false; + } + if (container instanceof PsiCodeBlock) { + if (!statementIsLastInBlock((PsiCodeBlock) container, (PsiStatement) statementToCheck)) { + return false; + } + } + if (container instanceof PsiLoopStatement) { + return false; + } + statementToCheck = container; + } + } + + public static boolean blockCompletesWithStatement(PsiCodeBlock body, PsiStatement statement) { + PsiElement statementToCheck = statement; + while (true) { + if (statementToCheck == null) { + return false; + } + final PsiElement container = getContainingStatementOrBlock(statementToCheck); + if (container == null) { + return false; + } + if (container instanceof PsiLoopStatement) { + return false; + } + if (container instanceof PsiCodeBlock) { + if (!statementIsLastInBlock((PsiCodeBlock) container, (PsiStatement) statementToCheck)) { + return false; + } + if (container.equals(body)) { + return true; + } + statementToCheck = PsiTreeUtil.getParentOfType(container, PsiStatement.class); + } + else { + statementToCheck = container; + } + } + } + + @Nullable + private static PsiElement getContainingStatementOrBlock(PsiElement statement) { + return PsiTreeUtil.getParentOfType(statement, PsiStatement.class, PsiCodeBlock.class); + } + + private static boolean statementIsLastInBlock(PsiCodeBlock block, PsiStatement statement) { + for (PsiElement child = block.getLastChild(); child != null; child = child.getPrevSibling()) { + if (!(child instanceof PsiStatement)) { + continue; + } + final PsiStatement childStatement = (PsiStatement) child; + if (statement.equals(childStatement)) { + return true; + } + if (!(statement instanceof PsiEmptyStatement)) { + return false; + } + } + return false; + } + + @Nullable + public static PsiStatement getFirstStatementInBlock(@Nullable PsiCodeBlock codeBlock) { + return PsiTreeUtil.getChildOfType(codeBlock, PsiStatement.class); + } + + @Nullable + public static PsiStatement getLastStatementInBlock(@Nullable PsiCodeBlock codeBlock) { + return getLastChildOfType(codeBlock, PsiStatement.class); + } + + private static T getLastChildOfType(@Nullable PsiElement element, Class aClass) { + if (element == null) { + return null; + } + for (PsiElement child = element.getLastChild(); child != null; child = child.getPrevSibling()) { + if (aClass.isInstance(child)) { + //noinspection unchecked + return (T) child; + } + } + return null; + } + + /** + * @return null, if zero or more than one statements in the specified code block. + */ + @Nullable + public static PsiStatement getOnlyStatementInBlock(@Nullable PsiCodeBlock codeBlock) { + return getOnlyChildOfType(codeBlock, PsiStatement.class); + } + + public static T getOnlyChildOfType(@Nullable PsiElement element, Class aClass) { + if (element == null) { + return null; + } + T result = null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + if (result == null) { + //noinspection unchecked + result = (T) child; + } + else { + return null; + } + } + } + return result; + } + + public static boolean hasStatementCount(@Nullable PsiCodeBlock codeBlock, int count) { + return hasChildrenOfTypeCount(codeBlock, count, PsiStatement.class); + } + + public static boolean hasChildrenOfTypeCount(@Nullable PsiElement element, int count, Class aClass) { + if (element == null) { + return false; + } + int i = 0; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (aClass.isInstance(child)) { + i++; + if (i > count) { + return false; + } + } + } + return i == count; + } + + public static boolean isEmptyCodeBlock(PsiCodeBlock codeBlock) { + return hasStatementCount(codeBlock, 0); + } + + public static boolean methodAlwaysThrowsException(PsiMethod method) { + final PsiCodeBlock body = method.getBody(); + if (body == null) { + return true; + } + return !containsReturn(body) && !codeBlockMayCompleteNormally(body); + } + + public static boolean lambdaExpressionAlwaysThrowsException(PsiLambdaExpression expression) { + final PsiElement body = expression.getBody(); + if (body instanceof PsiExpression) { + return false; + } + if (!(body instanceof PsiCodeBlock)) { + return true; + } + final PsiCodeBlock codeBlock = (PsiCodeBlock) body; + return !containsReturn(codeBlock) && !codeBlockMayCompleteNormally(codeBlock); + } + + public static boolean statementContainsNakedBreak(PsiStatement statement) { + if (statement == null) { + return false; + } + final NakedBreakFinder breakFinder = new NakedBreakFinder(); + statement.accept(breakFinder); + return breakFinder.breakFound(); + } + + /** + * Checks whether the given statement effectively breaks given loop. Returns true + * if the statement is {@link PsiBreakStatement} having given loop as a target. Also may return + * true in other cases if the statement is semantically equivalent to break like this: + *

+ *

{@code
+     * int myMethod(int[] data) {
+     *   for(int val : data) {
+     *     if(val == 5) {
+     *       System.out.println(val);
+     *       return 0; // this statement is semantically equivalent to break.
+     *     }
+     *   }
+     *   return 0;
+     * }}
+ * + * @param statement statement which may break the loop + * @param loop a loop to break + * @return true if the statement actually breaks the loop + */ + @Contract("null, _ -> false") + public static boolean statementBreaksLoop(PsiStatement statement, PsiLoopStatement loop) { + if (statement instanceof PsiBreakStatement) { + return ((PsiBreakStatement) statement).findExitedStatement() == loop; + } + if (statement instanceof PsiReturnStatement) { + PsiExpression returnValue = ((PsiReturnStatement) statement).getReturnValue(); + PsiElement cur = loop; + for (PsiElement parent = cur.getParent(); ; parent = cur.getParent()) { + if (parent instanceof PsiLabeledStatement) { + cur = parent; + } + else if (parent instanceof PsiCodeBlock) { + PsiCodeBlock block = (PsiCodeBlock) parent; + PsiStatement[] statements = block.getStatements(); + if (block.getParent() instanceof PsiBlockStatement && statements.length > 0 && statements[statements.length - 1] == cur) { + cur = block.getParent(); + } + else { + break; + } + } + else if (parent instanceof PsiIfStatement) { + if (cur == ((PsiIfStatement) parent).getThenBranch() || cur == ((PsiIfStatement) parent).getElseBranch()) { + cur = parent; + } + else { + break; + } + } + else { + break; + } + } + PsiElement nextElement = PsiTreeUtil.skipSiblingsForward(cur, PsiComment.class, PsiWhiteSpace.class); + if (nextElement instanceof PsiReturnStatement) { + return EquivalenceChecker.getCanonicalPsiEquivalence() + .expressionsAreEquivalent(returnValue, ((PsiReturnStatement) nextElement).getReturnValue()); + } + if (nextElement == null && returnValue == null && cur.getParent() instanceof PsiMethod) { + return true; + } + } + return false; + } + + /** + * Checks whether control flow after executing given statement will definitely not go into the next iteration of given loop. + * + * @param statement executed statement. It's not checked whether this statement itself breaks the loop. + * @param loop a surrounding loop. Must be parent of statement + * @return true if it can be statically defined that next loop iteration will not be executed. + */ + @Contract("null, _ -> false") + public static boolean flowBreaksLoop(PsiStatement statement, PsiLoopStatement loop) { + if (statement == null || statement == loop) { + return false; + } + for (PsiStatement sibling = nextExecutedStatement(statement); sibling != null; sibling = nextExecutedStatement(sibling)) { + if (sibling instanceof PsiContinueStatement) { + return false; + } + if (sibling instanceof PsiThrowStatement || sibling instanceof PsiReturnStatement) { + return true; + } + if (sibling instanceof PsiBreakStatement) { + PsiBreakStatement breakStatement = (PsiBreakStatement) sibling; + PsiStatement exitedStatement = breakStatement.findExitedStatement(); + if (exitedStatement == loop) { + return true; + } + return flowBreaksLoop(exitedStatement, loop); + } + } + return false; + } + + /** + * Returns true if given element is an empty statement + * + * @param element element to check + * @param commentIsContent if true, empty statement containing comments is not considered empty + * @param emptyBlocks if true, empty block (or nested empty block like {@code {{}}}) is considered an empty statement + * @return true if given element is an empty statement + */ + public static boolean isEmpty(PsiElement element, boolean commentIsContent, boolean emptyBlocks) { + if (!commentIsContent && element instanceof PsiComment) { + return true; + } + else if (element instanceof PsiEmptyStatement) { + return !commentIsContent || + PsiTreeUtil.getChildOfType(element, PsiComment.class) == null && + !(PsiTreeUtil.skipWhitespacesBackward(element) instanceof PsiComment); + } + else if (element instanceof PsiWhiteSpace) { + return true; + } + else if (element instanceof PsiBlockStatement) { + final PsiBlockStatement block = (PsiBlockStatement) element; + return isEmpty(block.getCodeBlock(), commentIsContent, emptyBlocks); + } + else if (emptyBlocks && element instanceof PsiCodeBlock) { + final PsiCodeBlock codeBlock = (PsiCodeBlock) element; + final PsiElement[] children = codeBlock.getChildren(); + if (children.length == 2) { + return true; + } + for (int i = 1; i < children.length - 1; i++) { + final PsiElement child = children[i]; + if (!isEmpty(child, commentIsContent, true)) { + return false; + } + } + return true; + } + return false; + } + + @Nullable + private static PsiStatement nextExecutedStatement(PsiStatement statement) { + PsiStatement next = PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class); + while (next instanceof PsiBlockStatement) { + PsiStatement[] statements = ((PsiBlockStatement) next).getCodeBlock().getStatements(); + if (statements.length == 0) { + break; + } + next = statements[0]; + } + if (next == null) { + PsiElement parent = statement.getParent(); + if (parent instanceof PsiCodeBlock) { + PsiElement gParent = parent.getParent(); + if (gParent instanceof PsiBlockStatement || gParent instanceof PsiSwitchStatement) { + return nextExecutedStatement((PsiStatement) gParent); + } + } + else if (parent instanceof PsiLabeledStatement || parent instanceof PsiIfStatement || parent instanceof PsiSwitchLabelStatement || parent instanceof PsiSwitchStatement) { + return nextExecutedStatement((PsiStatement) parent); + } + } + return next; + } + + private static class NakedBreakFinder extends JavaRecursiveElementWalkingVisitor { + private boolean m_found; + + private boolean breakFound() { + return m_found; + } + + @Override + public void visitElement(PsiElement element) { + if (m_found) { + return; + } + super.visitElement(element); + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + + @Override + public void visitBreakStatement(PsiBreakStatement statement) { + if (statement.getLabelIdentifier() != null) { + return; + } + m_found = true; + } + + @Override + public void visitDoWhileStatement(PsiDoWhileStatement statement) { + // don't drill down + } + + @Override + public void visitForStatement(PsiForStatement statement) { + // don't drill down + } + + @Override + public void visitForeachStatement(PsiForeachStatement statement) { + // don't drill down + } + + @Override + public void visitWhileStatement(PsiWhileStatement statement) { + // don't drill down + } + + @Override + public void visitSwitchStatement(PsiSwitchStatement statement) { + // don't drill down + } + } + + private static class SystemExitFinder extends JavaRecursiveElementWalkingVisitor { + + private boolean m_found; + + private boolean exitFound() { + return m_found; + } + + @Override + public void visitClass(PsiClass aClass) { + // do nothing to keep from drilling into inner classes + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (m_found) { + return; + } + super.visitMethodCallExpression(expression); + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return; + } + final String methodName = method.getName(); + if (!methodName.equals("exit")) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return; + } + final String className = aClass.getQualifiedName(); + if (!"java.lang.System".equals(className) && !"java.lang.Runtime".equals(className)) { + return; + } + m_found = true; + } + } + + private static class ReturnFinder extends JavaRecursiveElementWalkingVisitor { + + private boolean m_found; + + private boolean returnFound() { + return m_found; + } + + @Override + public void visitClass(PsiClass psiClass) { + // do nothing, to keep drilling into inner classes + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + + @Override + public void visitReturnStatement(PsiReturnStatement returnStatement) { + if (m_found) { + return; + } + super.visitReturnStatement(returnStatement); + m_found = true; + } + } + + private static class BreakFinder extends JavaRecursiveElementWalkingVisitor { + + private boolean m_found; + private final PsiStatement m_target; + private final boolean myAncestor; + + BreakFinder(PsiStatement target, boolean ancestor) { + m_target = target; + myAncestor = ancestor; + } + + boolean breakFound() { + return m_found; + } + + @Override + public void visitBreakStatement(PsiBreakStatement statement) { + if (m_found) { + return; + } + super.visitBreakStatement(statement); + final PsiStatement exitedStatement = statement.findExitedStatement(); + if (exitedStatement == null) { + return; + } + if (myAncestor) { + if (PsiTreeUtil.isAncestor(exitedStatement, m_target, false)) { + m_found = true; + } + } + else if (exitedStatement == m_target) { + m_found = true; + } + } + + @Override + public void visitIfStatement(PsiIfStatement statement) { + if (m_found) { + return; + } + final PsiExpression condition = statement.getCondition(); + final Object value = ExpressionUtils.computeConstantExpression(condition); + if (Boolean.FALSE != value) { + final PsiStatement thenBranch = statement.getThenBranch(); + if (thenBranch != null) { + thenBranch.accept(this); + } + } + if (Boolean.TRUE != value) { + final PsiStatement elseBranch = statement.getElseBranch(); + if (elseBranch != null) { + elseBranch.accept(this); + } + } + } + } + + private static class ContinueFinder extends JavaRecursiveElementWalkingVisitor { + + private boolean m_found; + private final PsiStatement m_target; + + private ContinueFinder(PsiStatement target) { + m_target = target; + } + + private boolean continueFound() { + return m_found; + } + + @Override + public void visitContinueStatement(PsiContinueStatement statement) { + if (m_found) { + return; + } + super.visitContinueStatement(statement); + final PsiStatement continuedStatement = statement.findContinuedStatement(); + if (continuedStatement == null) { + return; + } + if (PsiTreeUtil.isAncestor(continuedStatement, m_target, false)) { + m_found = true; + } + } + + @Override + public void visitIfStatement(PsiIfStatement statement) { + if (m_found) { + return; + } + final PsiExpression condition = statement.getCondition(); + final Object value = ExpressionUtils.computeConstantExpression(condition); + if (Boolean.FALSE != value) { + final PsiStatement thenBranch = statement.getThenBranch(); + if (thenBranch != null) { + thenBranch.accept(this); + } + } + if (Boolean.TRUE != value) { + final PsiStatement elseBranch = statement.getElseBranch(); + if (elseBranch != null) { + elseBranch.accept(this); + } + } + } + } + + private static class MethodCallFinder extends JavaRecursiveElementWalkingVisitor { + + private final String containingClassName; + private final PsiType returnType; + private final String methodName; + private final PsiType[] parameterTypeNames; + private boolean containsCallToMethod; + + private MethodCallFinder(String containingClassName, PsiType returnType, String methodName, PsiType... parameterTypeNames) { + this.containingClassName = containingClassName; + this.returnType = returnType; + this.methodName = methodName; + this.parameterTypeNames = parameterTypeNames; + } + + @Override + public void visitElement(PsiElement element) { + if (containsCallToMethod) { + return; + } + super.visitElement(element); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (containsCallToMethod) { + return; + } + super.visitMethodCallExpression(expression); + if (!MethodCallUtils.isCallToMethod(expression, containingClassName, returnType, methodName, parameterTypeNames)) { + return; + } + containsCallToMethod = true; + } + + private boolean containsCallToMethod() { + return containsCallToMethod; + } + } + + private static class ContinueToAncestorFinder extends JavaRecursiveElementWalkingVisitor { + + private final PsiStatement statement; + private boolean found; + + private ContinueToAncestorFinder(PsiStatement statement) { + this.statement = statement; + } + + @Override + public void visitElement(PsiElement element) { + if (found) { + return; + } + super.visitElement(element); + } + + @Override + public void visitContinueStatement(PsiContinueStatement continueStatement) { + if (found) { + return; + } + super.visitContinueStatement(continueStatement); + final PsiIdentifier labelIdentifier = continueStatement.getLabelIdentifier(); + if (labelIdentifier == null) { + return; + } + final PsiStatement continuedStatement = continueStatement.findContinuedStatement(); + if (continuedStatement == null) { + return; + } + if (PsiTreeUtil.isAncestor(continuedStatement, statement, true)) { + found = true; + } + } + + private boolean continueToAncestorFound() { + return found; + } + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java new file mode 100644 index 0000000000..18002807d0 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspection.java @@ -0,0 +1,1620 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInspection; + +import com.intellij.java.analysis.codeInspection.AbstractBaseJavaLocalInspectionTool; +import com.intellij.java.analysis.impl.psi.controlFlow.AllVariablesControlFlowPolicy; +import com.intellij.java.language.JavaFeature; +import com.intellij.java.language.impl.codeInsight.BlockUtils; +import com.intellij.java.language.impl.psi.controlFlow.*; +import com.intellij.java.language.psi.PsiElementFactory; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import com.intellij.java.language.psi.util.TypeConversionUtil; +import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.StatementExtractor; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; +import consulo.annotation.component.ExtensionImpl; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; +import consulo.language.editor.inspection.*; +import consulo.language.psi.*; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.util.collection.ArrayUtil; +import consulo.util.collection.ContainerUtil; +import consulo.util.lang.ObjectUtil; +import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; +import one.util.streamex.StreamEx; + +import java.util.*; + +@ExtensionImpl +public final class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLocalInspectionTool { + private static final SwitchConversion[] ourInspections = new SwitchConversion[]{ + EnhancedSwitchMigrationInspection::inspectReturningSwitch, + EnhancedSwitchMigrationInspection::inspectVariableAssigningSwitch, + (statement, branches, isExhaustive, maxNumberStatementsForExpression) -> + inspectReplacementWithStatement(statement, branches) + }; + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Override + public InspectionToolState createStateProvider() { + return new EnhancedSwitchMigrationInspectionState(); + } + + @Override + public Set requiredFeatures() { + return Set.of(JavaFeature.ENHANCED_SWITCH); + } + + @Override + public LocalizeValue getGroupDisplayName() { + return JavaInspectionsLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids14(); + } + + @Override + public PsiElementVisitor buildVisitorImpl( + ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession localInspectionToolSession, + EnhancedSwitchMigrationInspectionState state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitSwitchExpression(PsiSwitchExpression expression) { + PsiElement switchKeyword = expression.getFirstChild(); + if (switchKeyword == null) { + return; + } + PsiCodeBlock body = expression.getBody(); + if (body == null) { + return; + } + boolean onlyOneYieldAfterLabel = true; + boolean isOldSwitchWithoutRule = true; + int statementAfterLabelCount = 0; + PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) { + return; + } + for (PsiStatement statement : statements) { + if (statement instanceof PsiSwitchLabeledRuleStatement) { + isOldSwitchWithoutRule = false; + break; + } + if (statement instanceof PsiSwitchLabelStatement) { + statementAfterLabelCount = 0; + } + else { + statementAfterLabelCount++; + if (!(statement instanceof PsiYieldStatement || statement instanceof PsiThrowStatement)) { + onlyOneYieldAfterLabel = false; + } + else if (statementAfterLabelCount > 1) { + onlyOneYieldAfterLabel = false; + } + } + } + if (!isOldSwitchWithoutRule) { + return; + } + ProblemHighlightType warningType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; + if (!onlyOneYieldAfterLabel) { + warningType = ProblemHighlightType.INFORMATION; + } + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchExpressionDescription()) + .range(switchKeyword) + .withFix(new ReplaceExpressionWithEnhancedSwitchExpressionFix()) + .highlightType(warningType) + .create(); + } + + @Override + @RequiredReadAction + public void visitSwitchStatement(PsiSwitchStatement statement) { + PsiElement switchKeyword = statement.getFirstChild(); + if (switchKeyword == null) { + return; + } + List replacers = findSwitchReplacers(statement, state.myMaxNumberStatementsForBranch); + if (replacers.isEmpty()) { + return; + } + Optional replacerWithWarningLevel = replacers.stream().filter(this::isWarningLevel).findFirst(); + if (replacerWithWarningLevel.isPresent()) { + SwitchReplacer replacer = replacerWithWarningLevel.get(); + List fixes = new ArrayList<>(); + fixes.add(new ReplaceWithSwitchExpressionFix(replacer.getType())); + if (!state.myWarnOnlyOnExpressionConversion && replacer.getType() == ReplacementType.Statement) { + fixes.add(new UpdateInspectionOptionFix( + EnhancedSwitchMigrationInspection.this, + JavaInspectionsLocalize.inspectionSwitchExpressionMigrationWarnOnlyOnExpression(), + state -> state.myWarnOnlyOnExpressionConversion = true + )); + } + if (replacer.getType() == ReplacementType.Expression && + replacer.getMaxNumberStatementsInBranch() != null && + replacer.getMaxNumberStatementsInBranch() > 1) { + int newMaxValue = replacer.getMaxNumberStatementsInBranch() - 1; + + fixes.add(new UpdateInspectionOptionFix( + EnhancedSwitchMigrationInspection.this, + JavaInspectionsLocalize.inspectionSwitchExpressionMigrationOptionExpressionMaxStatements(newMaxValue), + state -> state.myMaxNumberStatementsForBranch = newMaxValue + ) + ); + } + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription()) + .range(switchKeyword) + .withFixes(fixes) + .create(); + replacers.remove(replacer); + } + if (!holder.isOnTheFly()) { + return; + } + if (replacers.isEmpty()) { + return; + } + List fixes = + ContainerUtil.map(replacers, replacer -> new ReplaceWithSwitchExpressionFix(replacer.getType())); + holder.newProblem(JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription()) + .range(switchKeyword) + .highlightType(ProblemHighlightType.INFORMATION) + .withFixes(fixes) + .create(); + } + + private boolean isWarningLevel(SwitchReplacer replacer) { + if (replacer.isInformLevel()) { + return false; + } + return !(state.myWarnOnlyOnExpressionConversion && replacer.getType() == ReplacementType.Statement); + } + }; + } + + private static List runInspections( + PsiStatement statement, + boolean isExhaustive, + List branches, + int maxNumberStatementsForExpression + ) { + List replacers = new ArrayList<>(); + for (SwitchConversion inspection : ourInspections) { + SwitchReplacer replacer = inspection.suggestReplacer(statement, branches, isExhaustive, maxNumberStatementsForExpression); + if (replacer != null) { + replacers.add(replacer); + } + } + return replacers; + } + + private static OldSwitchStatementBranch addBranch( + List branches, + PsiStatement[] statements, + int unmatchedCaseIndex, + int endIndexExcl, + boolean isFallthrough, PsiBreakStatement current + ) { + PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement) statements[unmatchedCaseIndex]; + PsiStatement[] branchStatements = Arrays.copyOfRange(statements, unmatchedCaseIndex + 1, endIndexExcl); + OldSwitchStatementBranch branch = new OldSwitchStatementBranch(isFallthrough, branchStatements, labelStatement, current); + branches.add(branch); + return branch; + } + + private static @Nullable List extractBranches( + PsiCodeBlock body, + PsiSwitchStatement switchStatement + ) { + List branches = new ArrayList<>(); + PsiStatement[] statements = body.getStatements(); + int unmatchedCaseIndex = -1; + OldSwitchStatementBranch previousBranch = null; + for (int i = 0, length = statements.length; i < length; i++) { + PsiStatement current = statements[i]; + if (current instanceof PsiSwitchLabelStatement) { + if (unmatchedCaseIndex != -1) { + boolean isFallthrough = i != 0 && ControlFlowUtils.statementMayCompleteNormally(statements[i - 1]); + OldSwitchStatementBranch newBranch = addBranch(branches, statements, unmatchedCaseIndex, i, isFallthrough, null); + newBranch.myPreviousSwitchBranch = previousBranch; + previousBranch = newBranch; + } + unmatchedCaseIndex = i; + } + else if (current instanceof PsiBreakStatement breakStmt) { + if (breakStmt.findExitedStatement() != switchStatement) { + return null; + } + if (unmatchedCaseIndex == -1) { + return null; + } + OldSwitchStatementBranch newBranch = addBranch(branches, statements, unmatchedCaseIndex, i, false, breakStmt); + newBranch.myPreviousSwitchBranch = previousBranch; + previousBranch = newBranch; + unmatchedCaseIndex = -1; + } + else if (current instanceof PsiSwitchLabeledRuleStatement) { + return null; + } + } + // tail + if (unmatchedCaseIndex != -1) { + OldSwitchStatementBranch branch = addBranch(branches, statements, unmatchedCaseIndex, statements.length, false, null); + branch.myPreviousSwitchBranch = previousBranch; + } + return branches; + } + + /** + * Before using this method, make sure you are using a correct version of Java. + */ + public static @Nullable SwitchReplacer findSwitchReplacer(PsiSwitchStatement switchStatement) { + List replacers = findSwitchReplacers(switchStatement, 1); + for (SwitchReplacer replacer : replacers) { + if (!replacer.isInformLevel()) { + return replacer; + } + } + return null; + } + + private static List findSwitchReplacers( + PsiSwitchStatement switchStatement, + int maxNumberStatementsForExpression + ) { + PsiExpression expression = switchStatement.getExpression(); + if (expression == null) { + return List.of(); + } + PsiCodeBlock body = switchStatement.getBody(); + if (body == null) { + return List.of(); + } + List branches = extractBranches(body, switchStatement); + if (branches == null || branches.isEmpty()) { + return List.of(); + } + boolean isExhaustive = isExhaustiveSwitch(branches, switchStatement); + return runInspections(switchStatement, isExhaustive, branches, maxNumberStatementsForExpression); + } + + @RequiredReadAction + private static @Nullable PsiSwitchBlock generateEnhancedSwitch( + PsiStatement statementToReplace, + List newBranches, + CommentTracker ct, + boolean isExpr + ) { + if (!(statementToReplace instanceof PsiSwitchStatement switchStmt)) { + return null; + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(switchStmt.getProject()); + PsiCodeBlock body = switchStmt.getBody(); + if (body == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + for (PsiElement e = switchStmt.getFirstChild(); e != null && e != body; e = e.getNextSibling()) { + sb.append(ct.text(e)); + } + PsiJavaToken lBrace = body.getLBrace(); + sb.append(lBrace != null ? ct.textWithComments(lBrace) : "{"); + for (SwitchBranch newBranch : newBranches) { + sb.append(newBranch.generate(ct)); + } + PsiJavaToken rBrace = body.getRBrace(); + sb.append(rBrace != null ? ct.textWithComments(rBrace) : "}"); + PsiSwitchBlock switchBlock; + if (isExpr) { + switchBlock = (PsiSwitchBlock) factory.createExpressionFromText(sb.toString(), switchStmt); + } + else { + switchBlock = (PsiSwitchBlock) factory.createStatementFromText(sb.toString(), switchStmt); + } + StreamEx.ofTree((PsiElement) switchBlock, block -> Arrays.stream(block.getChildren())) + .select(PsiBreakStatement.class) + .filter( + breakStmt -> ControlFlowUtils.statementCompletesWithStatement(switchBlock, breakStmt) + && breakStmt.findExitedStatement() == switchBlock + ) + .forEach(statement -> new CommentTracker().delete(statement)); + return switchBlock; + } + + private static boolean isExhaustiveSwitch(List branches, PsiSwitchStatement switchStatement) { + for (OldSwitchStatementBranch branch : branches) { + if (branch.isDefault()) { + return true; + } + if (existsDefaultLabelElement(branch.myLabelStatement)) { + return true; + } + } + SwitchUtils.SwitchExhaustivenessState completenessResult = SwitchUtils.evaluateSwitchCompleteness(switchStatement, true); + return completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT + || completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT; + } + + private static boolean isConvertibleBranch(OldSwitchStatementBranch branch, boolean hasNext) { + int length = branch.getStatements().length; + if (length == 0) { + return (branch.isFallthrough() && hasNext) || (!branch.isFallthrough() && branch.isDefault()); + } + return !branch.isFallthrough(); + } + + @Override + public LocalizeValue getDisplayName() { + return JavaInspectionsLocalize.inspectionSwitchExpressionMigrationInspectionSwitchDescription(); + } + + private enum ReplacementType { + Expression(JavaInspectionsLocalize.inspectionReplaceWithSwitchExpressionFixName()), + Statement(JavaInspectionsLocalize.inspectionReplaceWithEnhancedSwitchStatementFixName()); + private final LocalizeValue key; + + ReplacementType(LocalizeValue key) { + this.key = key; + } + + LocalizeValue getFixName() { + return key; + } + } + + public interface SwitchReplacer { + void replace(PsiStatement switchStatement); + + ReplacementType getType(); + + boolean isInformLevel(); + + //if null, it is not applicable + @Nullable + Integer getMaxNumberStatementsInBranch(); + } + + private interface SwitchConversion { + @Nullable + SwitchReplacer suggestReplacer( + PsiStatement statement, + List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ); + } + + //Right part of switch rule (case labels -> result) + private interface SwitchRuleResult { + String generate(CommentTracker ct, SwitchBranch branch); + } + + private static class ReplaceExpressionWithEnhancedSwitchExpressionFix implements LocalQuickFix { + + @Override + public LocalizeValue getName() { + return JavaInspectionsLocalize.inspectionReplaceWithSwitchRuleExpressionFixFamilyName(); + } + + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor problemDescriptor) { + PsiElement element = problemDescriptor.getPsiElement(); + + StringBuilder builder = new StringBuilder(); + if (!(element.getParent() instanceof PsiSwitchExpression switchExpression)) { + return; + } + for (PsiElement switchExpressionChild : switchExpression.getChildren()) { + if (switchExpressionChild instanceof PsiCodeBlock codeBlock) { + boolean previousHasBodyStatement = true; + for (PsiElement codeBlockChildren : codeBlock.getChildren()) { + if (codeBlockChildren instanceof PsiWhiteSpace && builder.charAt(builder.length() - 1) == ',') { + continue; + } + if (codeBlockChildren instanceof PsiSwitchLabelStatement switchLabelStatement) { + boolean nextIsBodyStatement = checkNextIsStatement(codeBlockChildren); + for (PsiElement labelStatementChild : switchLabelStatement.getChildren()) { + if (!previousHasBodyStatement + && labelStatementChild instanceof PsiJavaToken javaToken + && javaToken.textMatches("case")) { + continue; + } + if (labelStatementChild instanceof PsiJavaToken javaToken && javaToken.textMatches(":")) { + if (nextIsBodyStatement) { + builder.append("->"); //replace ':' with '->' + } + else { + //next label + builder.append(","); + } + } + else { + builder.append(labelStatementChild.getText()); + } + } + PsiElement nextOfSwitchLabelStatement = PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelStatement); + if (nextIsBodyStatement && !(nextOfSwitchLabelStatement instanceof PsiBlockStatement) + && findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsForward(codeBlockChildren)) == null) { + builder.append("{"); //wrap multiline rule into '{}' + } + previousHasBodyStatement = nextIsBodyStatement; + } + else { + PsiStatement yieldStatement = findOneYieldOrThrowStatement(codeBlockChildren); + if (yieldStatement != null) { + for (PsiElement yieldStatementChild : yieldStatement.getChildren()) { + if (!(yieldStatementChild instanceof PsiJavaToken javaToken && javaToken.textMatches("yield"))) { + builder.append(yieldStatementChild.getText()); // skip 'yield' for one-line rule + } + } + } + else { + builder.append(codeBlockChildren.getText()); + PsiElement nextOfCodeBlockChildren = PsiTreeUtil.skipWhitespacesAndCommentsForward(codeBlockChildren); + if (!(codeBlockChildren instanceof PsiComment || codeBlockChildren instanceof PsiWhiteSpace) + && !(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren) instanceof PsiJavaToken) + && findOneYieldOrThrowStatement(PsiTreeUtil.skipWhitespacesAndCommentsBackward(codeBlockChildren)) == null + && !(codeBlockChildren instanceof PsiJavaToken) + && (nextOfCodeBlockChildren instanceof PsiSwitchLabelStatement || + nextOfCodeBlockChildren instanceof PsiJavaToken javaToken && javaToken.textMatches("}"))) { + builder.append("\n}"); //wrap multiline rule into '{}' + } + } + } + } + } + else { + builder.append(switchExpressionChild.getText()); + } + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject()); + PsiExpression newSwitchExpression = factory.createExpressionFromText(builder.toString(), element); + switchExpression.replace(newSwitchExpression); + } + + @RequiredReadAction + private static boolean checkNextIsStatement(@Nullable PsiElement statement) { + PsiElement forward = PsiTreeUtil.skipWhitespacesAndCommentsForward(statement); + return statement instanceof PsiSwitchLabelStatement + && !(forward instanceof PsiSwitchLabelStatement) + && forward instanceof PsiStatement; + } + + @Nullable + @RequiredReadAction + private static PsiStatement findOneYieldOrThrowStatement(@Nullable PsiElement switchBlockChild) { + if (switchBlockChild == null) { + return null; + } + boolean isOldOrThrow = switchBlockChild instanceof PsiYieldStatement + || switchBlockChild instanceof PsiThrowStatement; + if (!isOldOrThrow) { + return null; + } + boolean hasSwitchLabelBefore = + PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchBlockChild) instanceof PsiSwitchLabelStatement; + if (!hasSwitchLabelBefore) { + return null; + } + PsiElement nextElement = PsiTreeUtil.skipWhitespacesAndCommentsForward(switchBlockChild); + boolean nextElementIsSwitchLabel = nextElement instanceof PsiSwitchLabelStatement; + boolean isClosingBrace = nextElement instanceof PsiJavaToken javaToken && javaToken.textMatches("}"); + if (!(nextElementIsSwitchLabel || isClosingBrace)) { + return null; + } + return (PsiStatement) switchBlockChild; + } + } + + private static class ReplaceWithSwitchExpressionFix implements LocalQuickFix { + private final ReplacementType myReplacementType; + + ReplaceWithSwitchExpressionFix(ReplacementType replacementType) { + myReplacementType = replacementType; + } + + @Override + public LocalizeValue getName() { + return myReplacementType.getFixName(); + } + + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor problemDescriptor) { + PsiElement element = problemDescriptor.getPsiElement(); + + PsiSwitchStatement statement = PsiTreeUtil.getParentOfType(element, PsiSwitchStatement.class); + if (statement == null) { + return; + } + SwitchReplacer replacer = + ContainerUtil.find(findSwitchReplacers(statement, Integer.MAX_VALUE), t -> t.getType() == myReplacementType); + if (replacer == null) { + return; + } + replacer.replace(statement); + } + } + + /** + * This method is used to rearrange branches. + * Now it can change the order of branches or divide some branches into several separate branches. + * For example, a null branch will be extracted from others. + * + * @return rearranged branches + */ + @RequiredReadAction + private static List rearrangeBranches(List branches, PsiElement context) { + if (branches.isEmpty()) { + return branches; + } + if (!PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, context)) { + return branches; + } + List result = new ArrayList<>(); + for (SwitchBranch branch : branches) { + if (branch.myIsDefault) { + result.add(branch); + continue; + } + List caseExpressions = branch.myCaseExpressions; + if (caseExpressions == null || caseExpressions.size() <= 1) { + result.add(branch); + continue; + } + rearrangeCases(branch, caseExpressions, result); + } + return result; + } + + /** + * This method is used to rearrange case elements. + * Method is used by {@link #rearrangeBranches(List, PsiElement)}. + * + * @param result - container, where sorted cases will be added + */ + private static void rearrangeCases( + SwitchBranch branch, + List caseExpressions, + List result + ) { + List previousExpressions = new ArrayList<>(); + for (PsiCaseLabelElement expression : caseExpressions) { + PsiCaseLabelElement external = findNullLabel(List.of(expression)); + if (external == null && expression instanceof PsiPattern) { + external = expression; + } + if (external == null) { + previousExpressions.add(expression); + } + else { + if (!previousExpressions.isEmpty()) { + SwitchBranch otherBranch = branch.withLabels(new ArrayList<>(previousExpressions)); + result.add(otherBranch); + previousExpressions.clear(); + } + SwitchBranch specialBranch = branch.withLabels(List.of(external)); + result.add(specialBranch); + } + } + if (!previousExpressions.isEmpty()) { + SwitchBranch otherBranch = branch.withLabels(previousExpressions); + result.add(otherBranch); + } + } + + private static @Nullable PsiCaseLabelElement findNullLabel(List expressions) { + return ContainerUtil.find( + expressions, + label -> label instanceof PsiExpression literal && TypeConversionUtil.isNullType(literal.getType()) + ); + } + + private static final class ReturningSwitchReplacer implements SwitchReplacer { + final PsiStatement myStatement; + final List myNewBranches; + final @Nullable PsiReturnStatement myReturnToDelete; + final @Nullable PsiThrowStatement myThrowStatementToDelete; + private final List myStatementsToDelete; + private final boolean myIsInfo; + private final int myMaxNumberStatementsInBranch; + + @RequiredReadAction + private ReturningSwitchReplacer( + PsiStatement statement, + List newBranches, + @Nullable PsiReturnStatement returnToDelete, + @Nullable PsiThrowStatement throwToDelete, + List statementsToDelete, + boolean isInfo, + int maxNumberStatementsInBranch + ) { + myStatement = statement; + myNewBranches = rearrangeBranches(newBranches, statement); + myReturnToDelete = returnToDelete; + myThrowStatementToDelete = throwToDelete; + myStatementsToDelete = statementsToDelete; + myIsInfo = isInfo; + myMaxNumberStatementsInBranch = maxNumberStatementsInBranch; + } + + @Override + public Integer getMaxNumberStatementsInBranch() { + return myMaxNumberStatementsInBranch; + } + + @Override + public boolean isInformLevel() { + return myIsInfo; + } + + @Override + @RequiredWriteAction + public void replace(PsiStatement statement) { + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock switchBlock = generateEnhancedSwitch(statement, myNewBranches, commentTracker, true); + if (switchBlock == null) { + return; + } + + if (myReturnToDelete != null) { + CommentTracker ct = new CommentTracker(); + commentTracker.markUnchanged(myReturnToDelete.getReturnValue()); + ct.delete(myReturnToDelete); + } + if (myThrowStatementToDelete != null) { + CommentTracker ct = new CommentTracker(); + commentTracker.markUnchanged(myThrowStatementToDelete.getException()); + ct.delete(myThrowStatementToDelete); + } + for (PsiStatement toDelete : myStatementsToDelete) { + commentTracker.delete(toDelete); + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(statement.getProject()); + PsiStatement returnStatement = factory.createStatementFromText("return " + switchBlock.getText() + ";", switchBlock); + commentTracker.replaceAndRestoreComments(statement, returnStatement); + } + + @Override + public ReplacementType getType() { + return ReplacementType.Expression; + } + } + + /** + *
+     * switch (n) {
+     *   case 1:
+     *     return "a";
+     *   case 2:
+     *     return "b";
+     *   default:
+     *     return "?";
+     * }
+     * 
+ */ + + @RequiredReadAction + private static @Nullable SwitchReplacer inspectReturningSwitch( + PsiStatement statement, + List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ) { + PsiReturnStatement returnAfterSwitch = + ObjectUtil.tryCast(PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class), PsiReturnStatement.class); + PsiThrowStatement throwAfterSwitch = + ObjectUtil.tryCast(PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class), PsiThrowStatement.class); + if (returnAfterSwitch == null && throwAfterSwitch == null && !isExhaustive) { + return null; + } + List newBranches = new ArrayList<>(); + boolean hasReturningBranch = false; + boolean isInfo = false; + int maxLines = 0; + for (int i = 0, size = branches.size(); i < size; i++) { + OldSwitchStatementBranch branch = branches.get(i); + if (!isConvertibleBranch(branch, i != size - 1)) { + return null; + } + if (branch.isFallthrough()) { + continue; + } + PsiStatement[] statements = branch.getStatements(); + if (statements.length == 1 && statements[0] instanceof PsiBlockStatement psiCodeBlock) { + statements = psiCodeBlock.getCodeBlock().getStatements(); + } + if (statements.length == 0) { + if ((i == branches.size() - 1) || branch.isDefault()) { + if (returnAfterSwitch != null) { + statements = new PsiStatement[]{returnAfterSwitch}; + } + else if (throwAfterSwitch != null) { + statements = new PsiStatement[]{throwAfterSwitch}; + } + else { + return null; + } + } + else { + return null; + } + } + if (maxLines < statements.length) { + maxLines = statements.length; + } + if (statements.length > maxNumberStatementsForExpression) { + isInfo = true; + } + int lastIndex = statements.length - 1; + if (ContainerUtil.exists( + statements, + st -> !PsiTreeUtil.findChildrenOfAnyType( + st, + PsiContinueStatement.class, + PsiBreakStatement.class, + PsiYieldStatement.class + ).isEmpty() + )) { + return null; + } + PsiReturnStatement returnStmt = ObjectUtil.tryCast(statements[lastIndex], PsiReturnStatement.class); + SwitchRuleResult result; + if (returnStmt == null) { + PsiThrowStatement throwStatement = ObjectUtil.tryCast(statements[lastIndex], PsiThrowStatement.class); + if (throwStatement == null) { + return null; + } + PsiStatement[] psiStatements = replaceAllReturnWithYield(statements); + if (psiStatements == null) { + return null; + } + result = new SwitchStatementBranch(psiStatements, statements); + } + else { + PsiExpression returnExpr = returnStmt.getReturnValue(); + if (returnExpr == null) { + return null; + } + if (statements.length == 1) { + result = new SwitchRuleExpressionResult(returnExpr); + } + else { + PsiStatement[] psiStatements = replaceAllReturnWithYield(statements); + if (psiStatements == null) { + return null; + } + result = new SwitchStatementBranch(psiStatements, statements); + } + hasReturningBranch = true; + } + newBranches.add(SwitchBranch.fromOldBranch(branch, result, branch.getUsedElements())); + } + if (!hasReturningBranch) { + return null; + } + if (!isExhaustive && returnAfterSwitch != null) { + PsiExpression returnExpr = returnAfterSwitch.getReturnValue(); + if (returnExpr == null) { + return null; + } + newBranches.add(SwitchBranch.createDefault(new SwitchRuleExpressionResult(returnExpr))); + } + if (!isExhaustive && throwAfterSwitch != null) { + newBranches.add(SwitchBranch.createDefault(new SwitchStatementBranch(new PsiStatement[]{throwAfterSwitch}))); + } + List statementsToDelete = new ArrayList<>(); + if (isExhaustive && returnAfterSwitch == null && throwAfterSwitch == null) { + PsiElement current = statement.getNextSibling(); + while (current != null) { + if (current instanceof PsiStatement stmt) { + if (current instanceof PsiSwitchLabelStatement) { + break; + } + statementsToDelete.add(stmt); + if (stmt instanceof PsiReturnStatement || stmt instanceof PsiThrowStatement) { + break; + } + } + current = current.getNextSibling(); + } + } + return new ReturningSwitchReplacer( + statement, + newBranches, + returnAfterSwitch, + throwAfterSwitch, + statementsToDelete, + isInfo, + maxLines + ); + } + + @RequiredReadAction + private static PsiStatement[] replaceAllReturnWithYield(PsiStatement[] statements) { + PsiStatement[] result = ArrayUtil.copyOf(statements); + for (int i = 0; i < statements.length; i++) { + PsiStatement statement = result[i]; + if (statement instanceof PsiReturnStatement returnStatement) { + PsiExpression returnValue = returnStatement.getReturnValue(); + if (returnValue == null || !returnValue.isValid() || PsiTreeUtil.hasErrorElements(returnValue) + //skip PsiCall not to resolve and get exceptions + || (!(returnValue instanceof PsiCall) && returnValue.getType() == null)) { + return null; + } + result[i] = createYieldStatement(returnValue); + continue; + } + PsiStatement copy = (PsiStatement) statement.copy(); + result[i] = copy; + Collection returnStatements = PsiTreeUtil.findChildrenOfType(copy, PsiReturnStatement.class); + for (PsiReturnStatement returnStatement : returnStatements) { + PsiExpression returnValue = returnStatement.getReturnValue(); + if (returnValue == null + || PsiTreeUtil.hasErrorElements(returnValue) + || !returnValue.isValid() + || returnValue.getType() == null) { + return null; + } + //noinspection RequiredXAction + returnStatement.replace(createYieldStatement(returnValue)); + } + } + return result; + } + + @RequiredReadAction + private static PsiStatement[] withLastStatementReplacedWithYield(PsiStatement[] statements, PsiExpression expr) { + PsiStatement[] result = ArrayUtil.copyOf(statements); + PsiStatement yieldStatement = createYieldStatement(expr); + result[result.length - 1] = yieldStatement; + return result; + } + + @RequiredReadAction + private static PsiStatement createYieldStatement(PsiExpression expr) { + Project project = expr.getProject(); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + return factory.createStatementFromText("yield " + StringUtil.trim(expr.getText()) + ";", expr); + } + + private static final class SwitchExistingVariableReplacer implements SwitchReplacer { + final PsiVariable myVariableToAssign; + final PsiStatement myStatement; + final List myNewBranches; + final boolean myIsRightAfterDeclaration; + private final boolean myIsInfo; + private final int myMaxNumberStatementsInBranch; + + @RequiredReadAction + private SwitchExistingVariableReplacer( + PsiVariable variableToAssign, + PsiStatement statement, + List newBranches, + boolean isRightAfterDeclaration, + boolean isInfo, + int maxNumberStatementsInBranch + ) { + myVariableToAssign = variableToAssign; + myStatement = statement; + myNewBranches = rearrangeBranches(newBranches, statement); + myIsRightAfterDeclaration = isRightAfterDeclaration; + myIsInfo = isInfo; + myMaxNumberStatementsInBranch = maxNumberStatementsInBranch; + } + + @Override + public Integer getMaxNumberStatementsInBranch() { + return myMaxNumberStatementsInBranch; + } + + @Override + public boolean isInformLevel() { + return myIsInfo; + } + + @Override + @RequiredWriteAction + public void replace(PsiStatement switchStatement) { + PsiLabeledStatement labeledStatement = ObjectUtil.tryCast(switchStatement.getParent(), PsiLabeledStatement.class); + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock replacement = generateEnhancedSwitch(switchStatement, myNewBranches, commentTracker, true); + if (replacement == null) { + return; + } + PsiExpression initializer = myVariableToAssign.getInitializer(); + if (myIsRightAfterDeclaration && + (isNotUsed(myVariableToAssign, switchStatement) && isNotUsed(myVariableToAssign, replacement))) { + if (initializer != null) { + List sideEffectExpressions = SideEffectChecker.extractSideEffectExpressions(initializer); + PsiStatement[] sideEffectStatements = StatementExtractor.generateStatements(sideEffectExpressions, initializer); + if (sideEffectStatements.length > 0) { + PsiStatement statement = ObjectUtil.tryCast(myVariableToAssign.getParent(), PsiStatement.class); + if (statement == null) { + return; + } + BlockUtils.addBefore(statement, sideEffectStatements); + } + } + myVariableToAssign.setInitializer((PsiSwitchExpression) replacement); + commentTracker.delete(switchStatement); + commentTracker.insertCommentsBefore(myVariableToAssign); + if (labeledStatement != null) { + new CommentTracker().deleteAndRestoreComments(labeledStatement); + } + } + else { + String text = myVariableToAssign.getName() + "=" + replacement.getText() + ";"; + PsiStatement statementToReplace = labeledStatement != null ? labeledStatement : switchStatement; + commentTracker.replaceAndRestoreComments(statementToReplace, text); + } + } + + @RequiredReadAction + private static boolean isNotUsed(PsiVariable variable, PsiElement switchElement) { + try { + ControlFlow controlFlow = ControlFlowFactory + .getControlFlow(switchElement, AllVariablesControlFlowPolicy.getInstance(), ControlFlowOptions.NO_CONST_EVALUATE); + List references = ControlFlowUtil.getReadBeforeWrite(controlFlow); + for (PsiReferenceExpression reference : references) { + if (reference != null && reference.resolve() == variable) { + return false; + } + } + return true; + } + catch (AnalysisCanceledException e) { + return false; + } + } + + @Override + public ReplacementType getType() { + return ReplacementType.Expression; + } + } + + /** + *
+     * int result;
+     * switch(s) {
+     *   case "a": result = 1; break;
+     *   case "b": result = 2; break;
+     *   default: result = 0;
+     * }
+     * 
+ */ + @RequiredReadAction + private static @Nullable SwitchReplacer inspectVariableAssigningSwitch( + PsiStatement statement, + List branches, + boolean isExhaustive, + int maxNumberStatementsForExpression + ) { + PsiElement parent = statement.getParent(); + PsiElement anchor = parent instanceof PsiLabeledStatement ? parent : statement; + PsiLocalVariable assignedVariable = null; + List newBranches = new ArrayList<>(); + boolean hasAssignedBranch = false; + boolean wasDefault = false; + boolean isInfo = false; + int maxNumberStatementsInBranch = 0; + for (int i = 0, size = branches.size(); i < size; i++) { + OldSwitchStatementBranch branch = branches.get(i); + if (!isConvertibleBranch(branch, i != size - 1)) { + return null; + } + PsiStatement[] statements = branch.getStatements(); + if (branch.isFallthrough() && statements.length == 0) { + continue; + } + if (statements.length == 0) { + return null; + } + if (ContainerUtil.exists( + statements, + st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiContinueStatement.class, + PsiYieldStatement.class, PsiReturnStatement.class + ).isEmpty() + )) { + return null; + } + if (ContainerUtil.exists( + Arrays.stream(statements).toList().subList(0, statements.length - 1), + st -> !PsiTreeUtil.findChildrenOfAnyType(st, PsiBreakStatement.class).isEmpty() + )) { + return null; + } + if (statements.length > maxNumberStatementsForExpression) { + isInfo = true; + } + if (maxNumberStatementsInBranch < statements.length) { + maxNumberStatementsInBranch = statements.length; + } + PsiStatement last = statements[statements.length - 1]; + PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(last); + PsiExpression rExpression = null; + if (assignment != null) { + rExpression = assignment.getRExpression(); + PsiLocalVariable var = ExpressionUtils.resolveLocalVariable(assignment.getLExpression()); + if (var == null) { + return null; + } + if (assignedVariable == null) { + assignedVariable = var; + } + else if (assignedVariable != var) { + return null; + } + } + SwitchRuleResult result; + if (rExpression == null) { + PsiThrowStatement throwStatement = ObjectUtil.tryCast(last, PsiThrowStatement.class); + if (throwStatement == null) { + return null; + } + result = new SwitchStatementBranch(statements); + } + else { + hasAssignedBranch = true; + if (statements.length == 1) { + result = new SwitchRuleExpressionResult(rExpression); + } + else { + if (PsiTreeUtil.hasErrorElements(rExpression) || rExpression.getType() == null) { + return null; + } + result = new SwitchStatementBranch(withLastStatementReplacedWithYield(statements, rExpression)); + } + } + wasDefault = branch.isDefault() || existsDefaultLabelElement(branch.myLabelStatement); + newBranches.add(SwitchBranch.fromOldBranch(branch, result, branch.getRelatedStatements())); + } + if (assignedVariable == null || !hasAssignedBranch) { + return null; + } + boolean isRightAfterDeclaration = isRightAfterDeclaration(anchor, assignedVariable); + if (!wasDefault && !isExhaustive) { + SwitchBranch defaultBranch = getVariableAssigningDefaultBranch(assignedVariable, isRightAfterDeclaration, statement); + if (defaultBranch != null) { + newBranches.add(defaultBranch); + } + else { + return null; + } + } + return new SwitchExistingVariableReplacer( + assignedVariable, + statement, + newBranches, + isRightAfterDeclaration, + isInfo, + maxNumberStatementsInBranch + ); + } + + private static boolean existsDefaultLabelElement(PsiSwitchLabelStatement statement) { + PsiCaseLabelElementList labelElementList = statement.getCaseLabelElementList(); + if (labelElementList == null) { + return false; + } + return ContainerUtil.exists(labelElementList.getElements(), el -> el instanceof PsiDefaultCaseLabelElement); + } + + private static EnhancedSwitchMigrationInspection.@Nullable SwitchBranch getVariableAssigningDefaultBranch( + @Nullable PsiLocalVariable assignedVariable, + boolean isRightAfterDeclaration, + PsiStatement statement + ) { + if (assignedVariable == null) { + return null; + } + PsiExpression initializer = assignedVariable.getInitializer(); + if (isRightAfterDeclaration && initializer instanceof PsiLiteralExpression) { + return SwitchBranch.createDefault(new SwitchRuleExpressionResult(initializer)); + } + PsiDeclarationStatement declaration = ObjectUtil.tryCast(assignedVariable.getParent(), PsiDeclarationStatement.class); + if (declaration == null || declaration.getParent() == null) { + return null; + } + try { + LocalsOrMyInstanceFieldsControlFlowPolicy policy = LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(); + ControlFlow controlFlow = + ControlFlowFactory.getInstance(declaration.getProject()).getControlFlow(declaration.getParent(), policy); + int switchStart = controlFlow.getStartOffset(statement); + if (switchStart <= 0) { + return null; + } + ControlFlow beforeFlow = new ControlFlowSubRange(controlFlow, 0, switchStart); + if (!ControlFlowUtil.isVariableDefinitelyAssigned(assignedVariable, beforeFlow)) { + return null; + } + } + catch (AnalysisCanceledException e) { + return null; + } + Project project = assignedVariable.getProject(); + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + PsiExpression reference = factory.createExpressionFromText(assignedVariable.getName(), assignedVariable); + return SwitchBranch.createDefault(new SwitchRuleExpressionResult(reference)); + } + + @RequiredReadAction + private static boolean isRightAfterDeclaration(PsiElement anchor, PsiVariable assignedVariable) { + PsiDeclarationStatement declaration = + ObjectUtil.tryCast(PsiTreeUtil.getPrevSiblingOfType(anchor, PsiStatement.class), PsiDeclarationStatement.class); + if (declaration != null) { + PsiElement[] elements = declaration.getDeclaredElements(); + if (elements.length == 1) { + PsiLocalVariable localVariable = ObjectUtil.tryCast(elements[0], PsiLocalVariable.class); + if (localVariable != null && localVariable == assignedVariable) { + return true; + } + } + } + return false; + } + + /** + * Replaces with an enhanced switch statement + */ + private static final class SwitchStatementReplacer implements SwitchReplacer { + final PsiStatement myStatement; + final List myExpressionBranches; + + @RequiredReadAction + private SwitchStatementReplacer(PsiStatement statement, List ruleResults) { + myStatement = statement; + myExpressionBranches = rearrangeBranches(ruleResults, statement); + } + + @Override + public boolean isInformLevel() { + return false; + } + + @Override + public Integer getMaxNumberStatementsInBranch() { + return null; + } + + @Override + @RequiredWriteAction + public void replace(PsiStatement switchStatement) { + CommentTracker commentTracker = new CommentTracker(); + PsiSwitchBlock switchBlock = generateEnhancedSwitch(switchStatement, myExpressionBranches, commentTracker, false); + if (switchBlock == null) { + return; + } + commentTracker.replaceAndRestoreComments(switchStatement, switchBlock); + } + + @Override + public ReplacementType getType() { + return ReplacementType.Statement; + } + } + + /** + * Suggest replacement with an enhanced switch statement + */ + @Nullable + @RequiredReadAction + private static SwitchReplacer inspectReplacementWithStatement( + PsiStatement statement, + List branches + ) { + for (int i = 0, size = branches.size(); i < size; i++) { + OldSwitchStatementBranch branch = branches.get(i); + if (!isConvertibleBranch(branch, i != size - 1) && + //example: + //case 0: break + !(!branch.isFallthrough() && branch.getStatements().length == 0)) { + return null; + } + } + List switchRules = new ArrayList<>(); + for (int i = 0, branchesSize = branches.size(); i < branchesSize; i++) { + OldSwitchStatementBranch branch = branches.get(i); + if (branch.isFallthrough() && branch.getStatements().length == 0) { + continue; + } + boolean allBranchRefsWillBeValid = StreamEx.of(branch.getStatements()) + .limit(i) // only previous branches + .flatMap((PsiElement stmt) -> StreamEx.ofTree(stmt, el -> StreamEx.of(el.getChildren()))) + .select(PsiReferenceExpression.class) + .map(PsiReference::resolve) + .select(PsiLocalVariable.class) + .allMatch(variable -> isInBranchOrOutside(statement, branch, variable)); + if (!allBranchRefsWillBeValid) { + return null; + } + if (branch.isFallthrough() && branch.getStatements().length == 0) { + continue; + } + PsiStatement[] statements = branch.getStatements(); + switchRules.add(SwitchBranch.fromOldBranch(branch, new SwitchStatementBranch(statements), branch.getRelatedStatements())); + } + return new SwitchStatementReplacer(statement, switchRules); + } + + private static boolean isInBranchOrOutside( + PsiStatement switchStmt, + OldSwitchStatementBranch branch, + PsiLocalVariable variable + ) { + return !PsiTreeUtil.isAncestor(switchStmt, variable, false) + || ContainerUtil.or(branch.getStatements(), stmt -> PsiTreeUtil.isAncestor(stmt, variable, false)); + } + + private static final class SwitchStatementBranch implements SwitchRuleResult { + + private final @Nullable PsiStatement[] myResultStatements; + + private final @Nullable PsiStatement[] myOriginalResultStatements; + + private SwitchStatementBranch(@Nullable PsiStatement[] resultStatements) { + myResultStatements = resultStatements; + myOriginalResultStatements = null; + } + + private SwitchStatementBranch( + @Nullable PsiStatement[] resultStatements, + @Nullable PsiStatement[] originalResultStatements + ) { + myResultStatements = resultStatements; + myOriginalResultStatements = originalResultStatements; + } + + @Override + @RequiredReadAction + public String generate(CommentTracker ct, SwitchBranch branch) { + @Nullable PsiStatement[] resultStatements = myResultStatements; + if (resultStatements == null) { + return ""; + } + if (resultStatements.length == 1) { + PsiStatement first = resultStatements[0]; + if (first instanceof PsiExpressionStatement || first instanceof PsiBlockStatement || first instanceof PsiThrowStatement) { + return ct.textWithComments(resultStatements[0]) + "\n"; + } + } + StringBuilder sb = new StringBuilder("{"); + for (int i = 0, length = resultStatements.length; i < length; i++) { + PsiStatement element = resultStatements[i]; + if (element == null) { + continue; + } + if (i == 0) { + PsiElement current = getElementForComments(element, i); + if (current != null) { + current = current.getPrevSibling(); + } + while (current instanceof PsiWhiteSpace || current instanceof PsiComment) { + current = current.getPrevSibling(); + } + addWhiteSpaceAndComments(current, sb, ct); + } + sb.append(ct.text(element)); + if (i + 1 < length) { + PsiElement current = getElementForComments(element, i); + addWhiteSpaceAndComments(current, sb, ct); + } + if (element.getNextSibling() == null + && element.getLastChild() instanceof PsiComment comment + && comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { + addNewLine(sb); + } + } + addCommentsUntilNextLabel(ct, branch, sb); + addNewLine(sb); + sb.append("}"); + for (PsiElement element : branch.myUsedElements) { + ct.markUnchanged(element); + } + return sb.toString(); + } + + private static void addNewLine(StringBuilder sb) { + String string = sb.toString(); + String trimmed = string.trim(); + if (!string.substring(trimmed.length()).contains("\n")) { + sb.append("\n"); + } + } + + private @Nullable PsiElement getElementForComments(@Nullable PsiStatement element, int i) { + PsiElement current = element; + if (myOriginalResultStatements != null && + myOriginalResultStatements.length > i && + myOriginalResultStatements[i] != null) { + current = myOriginalResultStatements[i]; + } + return current; + } + + @RequiredReadAction + private static void addWhiteSpaceAndComments(@Nullable PsiElement element, StringBuilder sb, CommentTracker ct) { + if (element == null) { + return; + } + PsiElement current = element.getNextSibling(); + while (current instanceof PsiComment || current instanceof PsiWhiteSpace) { + sb.append(ct.text(current)); + current = current.getNextSibling(); + } + } + } + + private static final class SwitchRuleExpressionResult implements SwitchRuleResult { + private final PsiExpression myExpression; + + private SwitchRuleExpressionResult(PsiExpression expression) { + myExpression = expression; + } + + @Override + @RequiredReadAction + public String generate(CommentTracker ct, SwitchBranch branch) { + return ct.textWithComments(myExpression) + ";"; + } + } + + /** + * Adds comments until the next label statement in a switch branch. + * If comments exist, builder will end with '\n' + * + * @param ct the CommentTracker object for tracking comments + * @param branch the SwitchBranch object representing a switch branch + * @param builder the StringBuilder object to append comments to + */ + @RequiredReadAction + private static void addCommentsUntilNextLabel(CommentTracker ct, SwitchBranch branch, StringBuilder builder) { + PsiElement label = ContainerUtil.find(branch.myUsedElements, e -> e instanceof PsiSwitchLabelStatement); + if (!(label instanceof PsiSwitchLabelStatement labelStatement)) { + return; + } + PsiSwitchLabelStatement nextLabelStatement = PsiTreeUtil.getNextSiblingOfType(labelStatement, PsiSwitchLabelStatement.class); + PsiElement untilComment = null; + if (nextLabelStatement != null) { + untilComment = PsiTreeUtil.getPrevSiblingOfType(nextLabelStatement, PsiStatement.class); + } + if (untilComment == null) { + PsiElement next = labelStatement.getNextSibling(); + if (next != null) { + while (next.getNextSibling() != null) { + next = next.getNextSibling(); + } + } + untilComment = next; + } + if (untilComment != null) { + String commentsBefore = grubCommentsBefore(untilComment, ct, branch).stripTrailing(); + String previousText = builder.toString().stripTrailing(); + if (previousText.length() > 1 && previousText.charAt(builder.length() - 1) == '\n') { + commentsBefore = StringUtil.trimStart(commentsBefore, "\n"); + } + if (!commentsBefore.isEmpty()) { + commentsBefore += '\n'; + } + builder.append(commentsBefore); + } + } + + @RequiredReadAction + private static String grubCommentsBefore(PsiElement untilComment, CommentTracker ct, SwitchBranch branch) { + List comments = new ArrayList<>(); + PsiElement current = (untilComment instanceof PsiComment || untilComment instanceof PsiWhiteSpace) + ? untilComment + : PsiTreeUtil.prevLeaf(untilComment); + while (current != null) { + if (current instanceof PsiComment || current instanceof PsiWhiteSpace) { + if (branch.myUsedElements.isEmpty() + || !PsiTreeUtil.isAncestor(branch.myUsedElements.get(branch.myUsedElements.size() - 1), current, false)) { + comments.add(ct.text(current)); + } + } + else { + break; + } + current = PsiTreeUtil.prevLeaf(current); + } + Collections.reverse(comments); + return StringUtil.join(comments, ""); + } + + private static final class SwitchBranch { + final boolean myIsDefault; + final List myCaseExpressions; + final @Nullable PsiExpression myGuard; + final List myUsedElements; // used elements only for this branch + private final SwitchRuleResult myRuleResult; + + private SwitchBranch( + boolean isDefault, + List caseExpressions, + @Nullable PsiExpression guard, SwitchRuleResult ruleResult, + List usedElements + ) { + if (ContainerUtil.exists(caseExpressions, exp -> exp instanceof PsiDefaultCaseLabelElement)) { + myIsDefault = true; + } + else { + myIsDefault = isDefault; + } + myGuard = guard; + if (myIsDefault) { + PsiCaseLabelElement nullLabel = findNullLabel(caseExpressions); + if (nullLabel != null) { + myCaseExpressions = List.of(nullLabel); + } + else { + myCaseExpressions = List.of(); + } + } + else { + myCaseExpressions = caseExpressions; + } + myRuleResult = ruleResult; + myUsedElements = usedElements; + } + + @RequiredReadAction + private String generate(CommentTracker ct) { + StringBuilder sb = new StringBuilder(); + PsiElement label = ContainerUtil.find(myUsedElements, e -> e instanceof PsiSwitchLabelStatement); + if (label != null) { + sb.append(ct.commentsBefore(label.getFirstChild())); + } + if (!myCaseExpressions.isEmpty()) { + String labels = StreamEx.of(myCaseExpressions).map(ct::textWithComments).joining(","); + sb.append("case"); + if (!labels.startsWith(" ")) { + sb.append(" "); + } + sb.append(labels); + } + else if (!myIsDefault) { + sb.append("case "); + } + if (myIsDefault) { + if (!myCaseExpressions.isEmpty()) { + sb.append(","); + } + sb.append("default"); + } + if (myGuard != null) { + sb.append(" when ").append(ct.text(myGuard)); + } + grabCommentsBeforeColon(label, ct, sb); + sb.append("->"); + sb.append(myRuleResult.generate(ct, this)); + if (!myUsedElements.isEmpty()) { + PsiElement element = PsiTreeUtil.nextCodeLeaf(myUsedElements.get(myUsedElements.size() - 1)); + if (element instanceof PsiJavaToken javaToken + && javaToken.textMatches("}") + && element.getParent() instanceof PsiCodeBlock codeBlock + && codeBlock.getParent() instanceof PsiSwitchBlock) { + sb.append(ct.commentsBefore(javaToken)); + } + } + return sb.toString(); + } + + @RequiredReadAction + private static void grabCommentsBeforeColon(PsiElement label, CommentTracker ct, StringBuilder sb) { + if (label != null) { + PsiElement child = label.getLastChild(); + while (child != null && !child.textMatches(":")) { + child = child.getPrevSibling(); + } + if (child != null) { + sb.append(ct.commentsBefore(child)); + } + } + } + + private SwitchBranch withLabels(List caseElements) { + return new SwitchBranch(myIsDefault, caseElements, myGuard, myRuleResult, myUsedElements); + } + + private static SwitchBranch createDefault(SwitchRuleResult ruleResult) { + return new SwitchBranch(true, Collections.emptyList(), null, ruleResult, Collections.emptyList()); + } + + private static SwitchBranch fromOldBranch( + OldSwitchStatementBranch branch, + SwitchRuleResult result, + List usedElements + ) { + return new SwitchBranch(branch.isDefault(), branch.getCaseLabelElements(), branch.getGuardExpression(), result, usedElements); + } + } + + private static final class OldSwitchStatementBranch { + final boolean myIsFallthrough; + final PsiStatement[] myStatements; + final PsiSwitchLabelStatement myLabelStatement; + final @Nullable PsiBreakStatement myBreakStatement; + @Nullable + OldSwitchStatementBranch myPreviousSwitchBranch; + + private OldSwitchStatementBranch( + boolean isFallthrough, + PsiStatement[] statements, + PsiSwitchLabelStatement switchLabelStatement, + @Nullable PsiBreakStatement breakStatement + ) { + myIsFallthrough = isFallthrough; + myStatements = statements; + myLabelStatement = switchLabelStatement; + myBreakStatement = breakStatement; + } + + private boolean isDefault() { + List branches = getWithFallthroughBranches(); + return ContainerUtil.or(branches, branch -> branch.myLabelStatement.isDefaultCase()); + } + + private boolean isFallthrough() { + return myIsFallthrough; + } + + public @Nullable PsiExpression getGuardExpression() { + return myLabelStatement.getGuardExpression(); + } + + private List getCaseLabelElements() { + List branches = getWithFallthroughBranches(); + Collections.reverse(branches); + return StreamEx.of(branches).flatMap(branch -> { + PsiCaseLabelElementList caseLabelElementList = branch.myLabelStatement.getCaseLabelElementList(); + if (caseLabelElementList == null) { + return StreamEx.empty(); + } + return StreamEx.of(caseLabelElementList.getElements()); + }).toList(); + } + + /** + * @return only meaningful statements, without break and case statements + */ + private PsiStatement[] getStatements() { + return myStatements; + } + + private List getRelatedStatements() { + StreamEx withoutBreak = StreamEx.of(myStatements).prepend(myLabelStatement); + return withoutBreak.prepend(myBreakStatement).toList(); + } + + private List getWithFallthroughBranches() { + List withPrevious = new ArrayList<>(); + OldSwitchStatementBranch current = this; + while (true) { + withPrevious.add(current); + current = current.myPreviousSwitchBranch; + if (current == null || current.myStatements.length != 0 || !current.myIsFallthrough) { + return withPrevious; + } + } + } + + private List getUsedElements() { + return StreamEx.of(getWithFallthroughBranches()).flatMap(branch -> StreamEx.of(branch.getRelatedStatements())).toList(); + } + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspectionState.java new file mode 100644 index 0000000000..f661ebac69 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EnhancedSwitchMigrationInspectionState.java @@ -0,0 +1,41 @@ +package com.intellij.java.analysis.impl.codeInspection; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.UnnamedConfigurable; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.util.xml.serializer.XmlSerializerUtil; +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 2025-10-16 + */ +public class EnhancedSwitchMigrationInspectionState implements InspectionToolState { + public boolean myWarnOnlyOnExpressionConversion = true; + public int myMaxNumberStatementsForBranch = 2; + + @Nullable + @Override + public EnhancedSwitchMigrationInspectionState getState() { + return this; + } + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + return ConfigurableBuilder.newBuilder() + .checkBox( + JavaInspectionsLocalize.inspectionSwitchExpressionMigrationWarnOnlyOnExpression(), + () -> myWarnOnlyOnExpressionConversion, + b -> myWarnOnlyOnExpressionConversion = b + ) + // TODO support JavaInspectionsLocalize.inspectionSwitchExpressionMigrationExpressionMaxStatements() + .buildUnnamed(); + } + + @Override + public void loadState(EnhancedSwitchMigrationInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/EquivalenceChecker.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EquivalenceChecker.java similarity index 90% rename from java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/EquivalenceChecker.java rename to java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EquivalenceChecker.java index 0fc813db1d..4f027f49f9 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/EquivalenceChecker.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/EquivalenceChecker.java @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.siyeh.ig.psiutils; +package com.intellij.java.analysis.impl.codeInspection; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.psi.*; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.lang.Comparing; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.ArrayList; import java.util.List; @@ -162,7 +163,7 @@ public Decision statementsAreEquivalentDecision(@Nullable PsiStatement statement return text1.equals(text2) ? EXACTLY_MATCHES : EXACTLY_UN_MATCHES; } - protected Decision declarationStatementsAreEquivalentDecision(@Nonnull PsiDeclarationStatement statement1, @Nonnull PsiDeclarationStatement statement2) { + protected Decision declarationStatementsAreEquivalentDecision(PsiDeclarationStatement statement1, PsiDeclarationStatement statement2) { final PsiElement[] elements1 = statement1.getDeclaredElements(); final List vars1 = new ArrayList<>(elements1.length); for (PsiElement anElement : elements1) { @@ -191,7 +192,7 @@ protected Decision declarationStatementsAreEquivalentDecision(@Nonnull PsiDeclar return EXACTLY_MATCHES; } - protected Decision localVariablesAreEquivalent(@Nonnull PsiLocalVariable localVariable1, @Nonnull PsiLocalVariable localVariable2) { + protected Decision localVariablesAreEquivalent(PsiLocalVariable localVariable1, PsiLocalVariable localVariable2) { final PsiType type1 = localVariable1.getType(); final PsiType type2 = localVariable2.getType(); if (!typesAreEquivalent(type1, type2)) { @@ -210,7 +211,7 @@ protected Decision localVariablesAreEquivalent(@Nonnull PsiLocalVariable localVa return expressionsAreEquivalentDecision(initializer1, initializer2).setConcreteIfExactUnMatches(initializer1, initializer2); } - protected Decision tryStatementsAreEquivalentDecision(@Nonnull PsiTryStatement statement1, @Nonnull PsiTryStatement statement2) { + protected Decision tryStatementsAreEquivalentDecision(PsiTryStatement statement1, PsiTryStatement statement2) { final PsiCodeBlock tryBlock1 = statement1.getTryBlock(); final PsiCodeBlock tryBlock2 = statement2.getTryBlock(); if (!codeBlocksAreEquivalentDecision(tryBlock1, tryBlock2).getExactlyMatches()) { @@ -273,7 +274,7 @@ protected Decision tryStatementsAreEquivalentDecision(@Nonnull PsiTryStatement s return EXACTLY_MATCHES; } - protected Decision parametersAreEquivalent(@Nonnull PsiParameter parameter1, @Nonnull PsiParameter parameter2) { + protected Decision parametersAreEquivalent(PsiParameter parameter1, PsiParameter parameter2) { final PsiType type1 = parameter1.getType(); final PsiType type2 = parameter2.getType(); if (!typesAreEquivalent(type1, type2)) { @@ -299,7 +300,7 @@ public boolean typesAreEquivalent(@Nullable PsiType type1, @Nullable PsiType typ return type1Text.equals(type2Text); } - protected Decision whileStatementsAreEquivalentDecision(@Nonnull PsiWhileStatement statement1, @Nonnull PsiWhileStatement statement2) { + protected Decision whileStatementsAreEquivalentDecision(PsiWhileStatement statement1, PsiWhileStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement body1 = statement1.getBody(); @@ -310,7 +311,7 @@ protected Decision whileStatementsAreEquivalentDecision(@Nonnull PsiWhileStateme return getComplexElementDecision(bodyEquivalence, conditionEquivalence, body1, body2, condition1, condition2); } - protected Decision forStatementsAreEquivalentDecision(@Nonnull PsiForStatement statement1, @Nonnull PsiForStatement statement2) { + protected Decision forStatementsAreEquivalentDecision(PsiForStatement statement1, PsiForStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); if (!expressionsAreEquivalentDecision(condition1, condition2).getExactlyMatches()) { @@ -331,7 +332,7 @@ protected Decision forStatementsAreEquivalentDecision(@Nonnull PsiForStatement s return statementsAreEquivalentDecision(body1, body2).setConcreteIfExactUnMatches(body1, body2); } - protected Decision forEachStatementsAreEquivalentDecision(@Nonnull PsiForeachStatement statement1, @Nonnull PsiForeachStatement statement2) { + protected Decision forEachStatementsAreEquivalentDecision(PsiForeachStatement statement1, PsiForeachStatement statement2) { final PsiExpression value1 = statement1.getIteratedValue(); final PsiExpression value2 = statement2.getIteratedValue(); if (!expressionsAreEquivalentDecision(value1, value2).getExactlyMatches()) { @@ -355,7 +356,7 @@ protected Decision forEachStatementsAreEquivalentDecision(@Nonnull PsiForeachSta return statementsAreEquivalentDecision(body1, body2).setConcreteIfExactUnMatches(body1, body2); } - protected Decision switchStatementsAreEquivalentDecision(@Nonnull PsiSwitchStatement statement1, @Nonnull PsiSwitchStatement statement2) { + protected Decision switchStatementsAreEquivalentDecision(PsiSwitchStatement statement1, PsiSwitchStatement statement2) { final PsiExpression switchExpression1 = statement1.getExpression(); final PsiExpression switchExpression2 = statement2.getExpression(); final PsiCodeBlock body1 = statement1.getBody(); @@ -367,7 +368,7 @@ protected Decision switchStatementsAreEquivalentDecision(@Nonnull PsiSwitchState return expressionsAreEquivalentDecision(switchExpression1, switchExpression2).setConcreteIfExactUnMatches(switchExpression1, switchExpression2); } - protected Decision doWhileStatementsAreEquivalentDecision(@Nonnull PsiDoWhileStatement statement1, @Nonnull PsiDoWhileStatement statement2) { + protected Decision doWhileStatementsAreEquivalentDecision(PsiDoWhileStatement statement1, PsiDoWhileStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement body1 = statement1.getBody(); @@ -377,7 +378,7 @@ protected Decision doWhileStatementsAreEquivalentDecision(@Nonnull PsiDoWhileSta return getComplexElementDecision(bodyEq, conditionEq, body1, body2, condition1, condition2); } - protected Decision assertStatementsAreEquivalentDecision(@Nonnull PsiAssertStatement statement1, @Nonnull PsiAssertStatement statement2) { + protected Decision assertStatementsAreEquivalentDecision(PsiAssertStatement statement1, PsiAssertStatement statement2) { final PsiExpression condition1 = statement1.getAssertCondition(); final PsiExpression condition2 = statement2.getAssertCondition(); final PsiExpression description1 = statement1.getAssertDescription(); @@ -387,7 +388,7 @@ protected Decision assertStatementsAreEquivalentDecision(@Nonnull PsiAssertState return getComplexElementDecision(condEq, exprEq, condition1, condition2, description1, description2); } - protected Decision synchronizedStatementsAreEquivalentDecision(@Nonnull PsiSynchronizedStatement statement1, @Nonnull PsiSynchronizedStatement statement2) { + protected Decision synchronizedStatementsAreEquivalentDecision(PsiSynchronizedStatement statement1, PsiSynchronizedStatement statement2) { final PsiExpression lock1 = statement1.getLockExpression(); final PsiExpression lock2 = statement2.getLockExpression(); final PsiCodeBlock body1 = statement1.getBody(); @@ -397,13 +398,13 @@ protected Decision synchronizedStatementsAreEquivalentDecision(@Nonnull PsiSynch return getComplexElementDecision(blockEq, lockEq, body1, body2, lock1, lock2); } - protected Decision blockStatementsAreEquivalentDecision(@Nonnull PsiBlockStatement statement1, @Nonnull PsiBlockStatement statement2) { + protected Decision blockStatementsAreEquivalentDecision(PsiBlockStatement statement1, PsiBlockStatement statement2) { final PsiCodeBlock block1 = statement1.getCodeBlock(); final PsiCodeBlock block2 = statement2.getCodeBlock(); return codeBlocksAreEquivalentDecision(block1, block2); } - protected Decision breakStatementsAreEquivalentDecision(@Nonnull PsiBreakStatement statement1, @Nonnull PsiBreakStatement statement2) { + protected Decision breakStatementsAreEquivalentDecision(PsiBreakStatement statement1, PsiBreakStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); if (identifier1 == null) { @@ -417,7 +418,7 @@ protected Decision breakStatementsAreEquivalentDecision(@Nonnull PsiBreakStateme return Decision.exact(text1.equals(text2)); } - protected Decision continueStatementsAreEquivalentDecision(@Nonnull PsiContinueStatement statement1, @Nonnull PsiContinueStatement statement2) { + protected Decision continueStatementsAreEquivalentDecision(PsiContinueStatement statement1, PsiContinueStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); if (identifier1 == null) { @@ -431,7 +432,7 @@ protected Decision continueStatementsAreEquivalentDecision(@Nonnull PsiContinueS return Decision.exact(text1.equals(text2)); } - protected Decision switchLabelStatementsAreEquivalentDecision(@Nonnull PsiSwitchLabelStatement statement1, @Nonnull PsiSwitchLabelStatement statement2) { + protected Decision switchLabelStatementsAreEquivalentDecision(PsiSwitchLabelStatement statement1, PsiSwitchLabelStatement statement2) { if (statement1.isDefaultCase()) { return Decision.exact(statement2.isDefaultCase()); } @@ -443,7 +444,7 @@ protected Decision switchLabelStatementsAreEquivalentDecision(@Nonnull PsiSwitch return expressionsAreEquivalentDecision(caseExpression1, caseExpression2).setConcreteIfExactUnMatches(caseExpression1, caseExpression2); } - protected Decision labeledStatementsAreEquivalentDecision(@Nonnull PsiLabeledStatement statement1, @Nonnull PsiLabeledStatement statement2) { + protected Decision labeledStatementsAreEquivalentDecision(PsiLabeledStatement statement1, PsiLabeledStatement statement2) { final PsiIdentifier identifier1 = statement1.getLabelIdentifier(); final PsiIdentifier identifier2 = statement2.getLabelIdentifier(); final String text1 = identifier1.getText(); @@ -475,7 +476,7 @@ protected Decision codeBlocksAreEquivalentDecision(@Nullable PsiCodeBlock block1 return EXACTLY_MATCHES; } - protected Decision ifStatementsAreEquivalentDecision(@Nonnull PsiIfStatement statement1, @Nonnull PsiIfStatement statement2) { + protected Decision ifStatementsAreEquivalentDecision(PsiIfStatement statement1, PsiIfStatement statement2) { final PsiExpression condition1 = statement1.getCondition(); final PsiExpression condition2 = statement2.getCondition(); final PsiStatement thenBranch1 = statement1.getThenBranch(); @@ -491,13 +492,13 @@ protected Decision ifStatementsAreEquivalentDecision(@Nonnull PsiIfStatement sta return EXACTLY_UN_MATCHES; } - protected Decision expressionStatementsAreEquivalentDecision(@Nonnull PsiExpressionStatement statement1, @Nonnull PsiExpressionStatement statement2) { + protected Decision expressionStatementsAreEquivalentDecision(PsiExpressionStatement statement1, PsiExpressionStatement statement2) { final PsiExpression expression1 = statement1.getExpression(); final PsiExpression expression2 = statement2.getExpression(); return expressionsAreEquivalentDecision(expression1, expression2); } - protected Decision returnStatementsAreEquivalentDecision(@Nonnull PsiReturnStatement statement1, @Nonnull PsiReturnStatement statement2) { + protected Decision returnStatementsAreEquivalentDecision(PsiReturnStatement statement1, PsiReturnStatement statement2) { final PsiExpression returnValue1 = statement1.getReturnValue(); final PsiExpression returnValue2 = statement2.getReturnValue(); final Decision decision = expressionsAreEquivalentDecision(returnValue1, returnValue2); @@ -507,13 +508,13 @@ protected Decision returnStatementsAreEquivalentDecision(@Nonnull PsiReturnState return decision; } - protected Decision throwStatementsAreEquivalentDecision(@Nonnull PsiThrowStatement statement1, @Nonnull PsiThrowStatement statement2) { + protected Decision throwStatementsAreEquivalentDecision(PsiThrowStatement statement1, PsiThrowStatement statement2) { final PsiExpression exception1 = statement1.getException(); final PsiExpression exception2 = statement2.getException(); return expressionsAreEquivalentDecision(exception1, exception2); } - protected Decision expressionListStatementsAreEquivalentDecision(@Nonnull PsiExpressionListStatement statement1, @Nonnull PsiExpressionListStatement statement2) { + protected Decision expressionListStatementsAreEquivalentDecision(PsiExpressionListStatement statement1, PsiExpressionListStatement statement2) { final PsiExpressionList expressionList1 = statement1.getExpressionList(); final PsiExpression[] expressions1 = expressionList1.getExpressions(); final PsiExpressionList expressionList2 = statement2.getExpressionList(); @@ -667,7 +668,7 @@ protected Decision typeElementsAreEquivalent(PsiTypeElement typeElement1, PsiTyp return Decision.exact(typesAreEquivalent(type1, type2)); } - protected Decision methodCallExpressionsAreEquivalentDecision(@Nonnull PsiMethodCallExpression methodCallExpression1, @Nonnull PsiMethodCallExpression methodCallExpression2) { + protected Decision methodCallExpressionsAreEquivalentDecision(PsiMethodCallExpression methodCallExpression1, PsiMethodCallExpression methodCallExpression2) { final PsiReferenceExpression methodExpression1 = methodCallExpression1.getMethodExpression(); final PsiReferenceExpression methodExpression2 = methodCallExpression2.getMethodExpression(); if (!expressionsAreEquivalentDecision(methodExpression1, methodExpression2).getExactlyMatches()) { @@ -697,7 +698,7 @@ protected Decision methodCallExpressionsAreEquivalentDecision(@Nonnull PsiMethod return decision; } - protected Decision newExpressionsAreEquivalentDecision(@Nonnull PsiNewExpression newExpression1, @Nonnull PsiNewExpression newExpression2) { + protected Decision newExpressionsAreEquivalentDecision(PsiNewExpression newExpression1, PsiNewExpression newExpression2) { final PsiJavaCodeReferenceElement classReference1 = newExpression1.getClassReference(); final PsiJavaCodeReferenceElement classReference2 = newExpression2.getClassReference(); if (classReference1 == null || classReference2 == null) { @@ -738,14 +739,14 @@ protected Decision newExpressionsAreEquivalentDecision(@Nonnull PsiNewExpression return expressionListsAreEquivalent(args1, args2); } - protected Decision arrayInitializerExpressionsAreEquivalentDecision(@Nonnull PsiArrayInitializerExpression arrayInitializerExpression1, - @Nonnull PsiArrayInitializerExpression arrayInitializerExpression2) { + protected Decision arrayInitializerExpressionsAreEquivalentDecision(PsiArrayInitializerExpression arrayInitializerExpression1, + PsiArrayInitializerExpression arrayInitializerExpression2) { final PsiExpression[] initializers1 = arrayInitializerExpression1.getInitializers(); final PsiExpression[] initializers2 = arrayInitializerExpression2.getInitializers(); return expressionListsAreEquivalent(initializers1, initializers2); } - protected Decision typeCastExpressionsAreEquivalentDecision(@Nonnull PsiTypeCastExpression typeCastExpression1, @Nonnull PsiTypeCastExpression typeCastExpression2) { + protected Decision typeCastExpressionsAreEquivalentDecision(PsiTypeCastExpression typeCastExpression1, PsiTypeCastExpression typeCastExpression2) { final PsiTypeElement typeElement1 = typeCastExpression1.getCastType(); final PsiTypeElement typeElement2 = typeCastExpression2.getCastType(); if (!typeElementsAreEquivalent(typeElement1, typeElement2).getExactlyMatches()) { @@ -759,7 +760,7 @@ protected Decision typeCastExpressionsAreEquivalentDecision(@Nonnull PsiTypeCast return expressionsAreEquivalentDecision(operand1, operand2).setConcreteIfExactUnMatches(operand1, operand2); } - protected Decision arrayAccessExpressionsAreEquivalentDecision(@Nonnull PsiArrayAccessExpression arrayAccessExpression1, @Nonnull PsiArrayAccessExpression arrayAccessExpression2) { + protected Decision arrayAccessExpressionsAreEquivalentDecision(PsiArrayAccessExpression arrayAccessExpression1, PsiArrayAccessExpression arrayAccessExpression2) { final PsiExpression arrayExpression2 = arrayAccessExpression1.getArrayExpression(); final PsiExpression arrayExpression1 = arrayAccessExpression2.getArrayExpression(); final PsiExpression indexExpression2 = arrayAccessExpression1.getIndexExpression(); @@ -771,7 +772,7 @@ protected Decision arrayAccessExpressionsAreEquivalentDecision(@Nonnull PsiArray return expressionsAreEquivalentDecision(indexExpression1, indexExpression2).setConcreteIfExactUnMatches(indexExpression1, indexExpression2); } - protected Decision prefixExpressionsAreEquivalentDecision(@Nonnull PsiPrefixExpression prefixExpression1, @Nonnull PsiPrefixExpression prefixExpression2) { + protected Decision prefixExpressionsAreEquivalentDecision(PsiPrefixExpression prefixExpression1, PsiPrefixExpression prefixExpression2) { final IElementType tokenType1 = prefixExpression1.getOperationTokenType(); if (!tokenType1.equals(prefixExpression2.getOperationTokenType())) { return EXACTLY_UN_MATCHES; @@ -781,7 +782,7 @@ protected Decision prefixExpressionsAreEquivalentDecision(@Nonnull PsiPrefixExpr return expressionsAreEquivalentDecision(operand1, operand2); } - protected Decision postfixExpressionsAreEquivalentDecision(@Nonnull PsiPostfixExpression postfixExpression1, @Nonnull PsiPostfixExpression postfixExpression2) { + protected Decision postfixExpressionsAreEquivalentDecision(PsiPostfixExpression postfixExpression1, PsiPostfixExpression postfixExpression2) { final IElementType tokenType1 = postfixExpression1.getOperationTokenType(); if (!tokenType1.equals(postfixExpression2.getOperationTokenType())) { return EXACTLY_UN_MATCHES; @@ -791,7 +792,7 @@ protected Decision postfixExpressionsAreEquivalentDecision(@Nonnull PsiPostfixEx return expressionsAreEquivalentDecision(operand1, operand2); } - protected Decision polyadicExpressionsAreEquivalentDecision(@Nonnull PsiPolyadicExpression polyadicExpression1, @Nonnull PsiPolyadicExpression polyadicExpression2) { + protected Decision polyadicExpressionsAreEquivalentDecision(PsiPolyadicExpression polyadicExpression1, PsiPolyadicExpression polyadicExpression2) { final IElementType tokenType1 = polyadicExpression1.getOperationTokenType(); final IElementType tokenType2 = polyadicExpression2.getOperationTokenType(); if (!tokenType1.equals(tokenType2)) { @@ -817,7 +818,7 @@ protected Decision polyadicExpressionsAreEquivalentDecision(@Nonnull PsiPolyadic return incompleteDecision != null ? incompleteDecision : EXACTLY_MATCHES; } - protected Decision assignmentExpressionsAreEquivalentDecision(@Nonnull PsiAssignmentExpression assignmentExpression1, @Nonnull PsiAssignmentExpression assignmentExpression2) { + protected Decision assignmentExpressionsAreEquivalentDecision(PsiAssignmentExpression assignmentExpression1, PsiAssignmentExpression assignmentExpression2) { final IElementType tokenType1 = assignmentExpression1.getOperationTokenType(); if (!tokenType1.equals(assignmentExpression2.getOperationTokenType())) { return EXACTLY_UN_MATCHES; @@ -831,7 +832,7 @@ protected Decision assignmentExpressionsAreEquivalentDecision(@Nonnull PsiAssign return getComplexElementDecision(leftEq, rightEq, lhs1, lhs2, rhs1, rhs2); } - protected Decision conditionalExpressionsAreEquivalentDecision(@Nonnull PsiConditionalExpression conditionalExpression1, @Nonnull PsiConditionalExpression conditionalExpression2) { + protected Decision conditionalExpressionsAreEquivalentDecision(PsiConditionalExpression conditionalExpression1, PsiConditionalExpression conditionalExpression2) { final PsiExpression condition1 = conditionalExpression1.getCondition(); final PsiExpression condition2 = conditionalExpression2.getCondition(); final PsiExpression thenExpression1 = conditionalExpression1.getThenExpression(); @@ -871,7 +872,6 @@ protected Decision expressionListsAreEquivalent(@Nullable PsiExpression[] expres return oneUnMatchedDecision == null ? EXACTLY_MATCHES : oneUnMatchedDecision; } - @Nonnull private Decision getComplexElementDecision(Decision equivalence1, Decision equivalence2, PsiElement left1, PsiElement right1, PsiElement left2, PsiElement right2) { if (equivalence2 == EXACTLY_MATCHES) { if (equivalence1 == EXACTLY_MATCHES) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/JavaSuppressionUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/JavaSuppressionUtil.java index 79fe7e017e..c85403d2b9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/JavaSuppressionUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/JavaSuppressionUtil.java @@ -27,7 +27,6 @@ import consulo.application.util.function.Computable; import consulo.content.bundle.Sdk; import consulo.java.language.module.extension.JavaModuleExtension; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.editor.DaemonCodeAnalyzerSettings; import consulo.language.editor.inspection.SuppressionUtil; import consulo.language.psi.PsiComment; @@ -40,9 +39,8 @@ import consulo.module.Module; import consulo.project.Project; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -52,7 +50,7 @@ public class JavaSuppressionUtil { public static final String SUPPRESS_INSPECTIONS_ANNOTATION_NAME = "java.lang.SuppressWarnings"; - public static boolean alreadyHas14Suppressions(@Nonnull PsiDocCommentOwner commentOwner) { + public static boolean alreadyHas14Suppressions(PsiDocCommentOwner commentOwner) { final PsiDocComment docComment = commentOwner.getDocComment(); return docComment != null && docComment.findTagByName(SuppressionUtil.SUPPRESS_INSPECTIONS_TAG_NAME) != null; } @@ -76,7 +74,6 @@ public static String getInspectionIdSuppressedInAnnotationAttribute(PsiElement e return null; } - @Nonnull public static Collection getInspectionIdsSuppressedInAnnotation(final PsiModifierList modifierList) { if (modifierList == null) { return Collections.emptyList(); @@ -109,7 +106,7 @@ public static Collection getInspectionIdsSuppressedInAnnotation(final Ps return result; } - public static PsiElement getElementMemberSuppressedIn(@Nonnull PsiDocCommentOwner owner, String inspectionToolID) { + public static PsiElement getElementMemberSuppressedIn(PsiDocCommentOwner owner, String inspectionToolID) { PsiElement element = getDocCommentToolSuppressedIn(owner, inspectionToolID); if (element != null) return element; element = getAnnotationMemberSuppressedIn(owner, inspectionToolID); @@ -127,8 +124,8 @@ public static PsiElement getElementMemberSuppressedIn(@Nonnull PsiDocCommentOwne return null; } - public static PsiElement getAnnotationMemberSuppressedIn(@Nonnull PsiModifierListOwner owner, String inspectionToolID) { - final PsiAnnotation generatedAnnotation = AnnotationUtil.findAnnotation(owner, JavaClassNames.JAVAX_ANNOTATION_GENERATED); + public static PsiElement getAnnotationMemberSuppressedIn(PsiModifierListOwner owner, String inspectionToolID) { + final PsiAnnotation generatedAnnotation = AnnotationUtil.findAnnotation(owner, CommonClassNames.JAVAX_ANNOTATION_GENERATED); if (generatedAnnotation != null) return generatedAnnotation; PsiModifierList modifierList = owner.getModifierList(); Collection suppressedIds = getInspectionIdsSuppressedInAnnotation(modifierList); @@ -140,7 +137,7 @@ public static PsiElement getAnnotationMemberSuppressedIn(@Nonnull PsiModifierLis return null; } - public static PsiElement getDocCommentToolSuppressedIn(@Nonnull PsiDocCommentOwner owner, String inspectionToolID) { + public static PsiElement getDocCommentToolSuppressedIn(PsiDocCommentOwner owner, String inspectionToolID) { PsiDocComment docComment = owner.getDocComment(); if (docComment == null && owner.getParent() instanceof PsiDeclarationStatement) { final PsiElement el = PsiTreeUtil.skipSiblingsBackward(owner.getParent(), PsiWhiteSpace.class); @@ -163,13 +160,13 @@ public static PsiElement getDocCommentToolSuppressedIn(@Nonnull PsiDocCommentOwn return null; } - public static Collection getInspectionIdsSuppressedInAnnotation(@Nonnull PsiModifierListOwner owner) { + public static Collection getInspectionIdsSuppressedInAnnotation(PsiModifierListOwner owner) { if (!PsiUtil.isLanguageLevel5OrHigher(owner)) return Collections.emptyList(); PsiModifierList modifierList = owner.getModifierList(); return getInspectionIdsSuppressedInAnnotation(modifierList); } - public static String getSuppressedInspectionIdsIn(@Nonnull PsiElement element) { + public static String getSuppressedInspectionIdsIn(PsiElement element) { if (element instanceof PsiComment) { String text = element.getText(); Matcher matcher = SuppressionUtil.SUPPRESS_IN_LINE_COMMENT_PATTERN.matcher(text); @@ -197,7 +194,7 @@ public static String getSuppressedInspectionIdsIn(@Nonnull PsiElement element) { return null; } - public static PsiElement getElementToolSuppressedIn(@Nonnull final PsiElement place, final String toolId) { + public static PsiElement getElementToolSuppressedIn(final PsiElement place, final String toolId) { if (place instanceof PsiFile) return null; return ApplicationManager.getApplication().runReadAction(new Computable() { @Override @@ -235,10 +232,10 @@ public PsiElement compute() { }); } - public static void addSuppressAnnotation(@Nonnull Project project, + public static void addSuppressAnnotation(Project project, final PsiElement container, final PsiModifierListOwner modifierOwner, - @Nonnull String id) throws IncorrectOperationException { + String id) throws IncorrectOperationException { PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierOwner, SUPPRESS_INSPECTIONS_ANNOTATION_NAME); final PsiAnnotation newAnnotation = createNewAnnotation(project, container, annotation, id); if (newAnnotation != null) { @@ -251,10 +248,10 @@ public static void addSuppressAnnotation(@Nonnull Project project, } } - private static PsiAnnotation createNewAnnotation(@Nonnull Project project, + private static PsiAnnotation createNewAnnotation(Project project, PsiElement container, PsiAnnotation annotation, - @Nonnull String id) throws IncorrectOperationException { + String id) throws IncorrectOperationException { if (annotation == null) { return JavaPsiFacade.getInstance(project).getElementFactory() .createAnnotationFromText("@" + SUPPRESS_INSPECTIONS_ANNOTATION_NAME + "(\"" + id + "\")", container); @@ -283,7 +280,7 @@ private static PsiAnnotation createNewAnnotation(@Nonnull Project project, return null; } - public static boolean canHave15Suppressions(@Nonnull PsiElement file) { + public static boolean canHave15Suppressions(PsiElement file) { final Module module = ModuleUtilCore.findModuleForPsiElement(file); if (module == null) return false; final Sdk jdk = ModuleUtilCore.getSdk(module, JavaModuleExtension.class); @@ -295,7 +292,7 @@ public static boolean canHave15Suppressions(@Nonnull PsiElement file) { } @Nullable - private static JavaSdkVersion getVersion(@Nonnull Sdk sdk) { + private static JavaSdkVersion getVersion(Sdk sdk) { String version = sdk.getVersionString(); if (version == null) return null; return JavaSdkVersion.fromVersionString(version); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/LambdaCanBeMethodReferenceInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/LambdaCanBeMethodReferenceInspection.java index fae1891cec..d3e0cc63f6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/LambdaCanBeMethodReferenceInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/LambdaCanBeMethodReferenceInspection.java @@ -16,7 +16,6 @@ package com.intellij.java.analysis.impl.codeInspection; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.impl.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil; import com.intellij.java.language.impl.refactoring.util.RefactoringChangeUtil; @@ -28,10 +27,8 @@ import consulo.annotation.component.ExtensionImpl; import consulo.language.ast.IElementType; import consulo.language.codeStyle.CodeStyleSettingsManager; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionProjectProfileManager; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiComment; @@ -40,6 +37,7 @@ import consulo.language.psi.SyntaxTraverser; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.collection.ArrayUtil; @@ -47,10 +45,8 @@ import consulo.util.lang.function.Condition; import consulo.util.lang.function.Conditions; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.Map; @@ -61,24 +57,19 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInspectionTool { private static final Logger LOG = Logger.getInstance(LambdaCanBeMethodReferenceInspection.class); - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; } - @Nls - @Nonnull @Override - public String getGroupDisplayName() { - return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); } - @Nls - @Nonnull @Override - public String getDisplayName() { - return "Lambda can be replaced with method reference"; + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Lambda can be replaced with method reference"); } @Override @@ -86,15 +77,16 @@ public boolean isEnabledByDefault() { return true; } - @Nonnull @Override public String getShortName() { return "Convert2MethodRef"; } - @Nonnull @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { + public PsiElementVisitor buildVisitorImpl(final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state) { return new JavaElementVisitor() { @Override public void visitLambdaExpression(PsiLambdaExpression expression) { @@ -305,8 +297,7 @@ public static void replaceAllLambdasWithMethodReferences(PsiElement root) { } } - @Nonnull - public static PsiExpression replaceLambdaWithMethodReference(@Nonnull PsiLambdaExpression lambda) { + public static PsiExpression replaceLambdaWithMethodReference(PsiLambdaExpression lambda) { PsiElement body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()); final PsiExpression candidate = canBeMethodReferenceProblem(body, lambda.getParameterList().getParameters(), lambda.getFunctionalInterfaceType(), lambda); return tryConvertToMethodReference(lambda, candidate); @@ -330,7 +321,7 @@ public static boolean checkQualifier(@Nullable PsiElement qualifier) { } @Nullable - private static PsiMethod getNonAmbiguousReceiver(PsiVariable[] parameters, @Nonnull PsiMethod psiMethod) { + private static PsiMethod getNonAmbiguousReceiver(PsiVariable[] parameters, PsiMethod psiMethod) { String methodName = psiMethod.getName(); PsiClass containingClass = psiMethod.getContainingClass(); if (containingClass == null) { @@ -367,7 +358,7 @@ private static boolean isPairedNoReceiver(PsiVariable[] parameters, PsiType rece TypeConversionUtil.areTypesConvertible(nonReceiverCandidateParams[0].getType(), receiverType); } - private static boolean isSoleParameter(@Nonnull PsiVariable[] parameters, @Nullable PsiExpression expression) { + private static boolean isSoleParameter(PsiVariable[] parameters, @Nullable PsiExpression expression) { return parameters.length == 1 && expression instanceof PsiReferenceExpression && parameters[0] == ((PsiReferenceExpression) expression).resolve(); @@ -505,7 +496,7 @@ private static String getQualifierTextByMethodCall(final PsiMethodCallExpression } @Nullable - private static String composeReceiverQualifierText(PsiVariable[] parameters, PsiMethod psiMethod, PsiClass containingClass, @Nonnull PsiExpression qualifierExpression) { + private static String composeReceiverQualifierText(PsiVariable[] parameters, PsiMethod psiMethod, PsiClass containingClass, PsiExpression qualifierExpression) { if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) { return null; } @@ -555,21 +546,13 @@ public ReplaceWithMethodRefFix(String suffix) { mySuffix = suffix; } - @Nls - @Nonnull @Override - public String getName() { - return getFamilyName() + mySuffix; + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace lambda with method reference" + mySuffix); } - @Nonnull @Override - public String getFamilyName() { - return "Replace lambda with method reference"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + public void applyFix(Project project, ProblemDescriptor descriptor) { PsiElement element = descriptor.getPsiElement(); if (element instanceof PsiLambdaExpression) { element = LambdaUtil.extractSingleExpressionFromBody(((PsiLambdaExpression) element).getBody()); @@ -582,8 +565,7 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descri } } - @Nonnull - static PsiExpression tryConvertToMethodReference(@Nonnull PsiLambdaExpression lambda, PsiElement body) { + static PsiExpression tryConvertToMethodReference(PsiLambdaExpression lambda, PsiElement body) { Project project = lambda.getProject(); PsiType functionalInterfaceType = lambda.getFunctionalInterfaceType(); if (functionalInterfaceType == null || !functionalInterfaceType.isValid()) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/NumericOverflowInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/NumericOverflowInspection.java index 036e47db46..4cccfaa94b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/NumericOverflowInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/NumericOverflowInspection.java @@ -16,7 +16,6 @@ package com.intellij.java.analysis.impl.codeInspection; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.psi.JavaElementVisitor; import com.intellij.java.language.psi.JavaPsiFacade; @@ -25,15 +24,16 @@ import com.intellij.java.language.psi.util.ConstantEvaluationOverflowException; import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.annotation.component.ExtensionImpl; +import consulo.language.editor.inspection.LocalInspectionToolSession; import consulo.language.editor.inspection.ProblemHighlightType; import consulo.language.editor.inspection.ProblemsHolder; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.dataholder.Key; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; /** * User: cdr @@ -42,11 +42,9 @@ public class NumericOverflowInspection extends BaseJavaBatchLocalInspectionTool { private static final Key HAS_OVERFLOW_IN_CHILD = Key.create("HAS_OVERFLOW_IN_CHILD"); - @Nls - @Nonnull @Override - public String getGroupDisplayName() { - return GroupNames.NUMERIC_GROUP_NAME; + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesNumericIssues(); } @Override @@ -54,22 +52,21 @@ public boolean isEnabledByDefault() { return true; } - @Nls - @Nonnull @Override - public String getDisplayName() { - return "Numeric overflow"; + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Numeric overflow"); } - @Nonnull @Override public String getShortName() { return "NumericOverflow"; } - @Nonnull @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { + public PsiElementVisitor buildVisitorImpl(final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state) { return new JavaElementVisitor() { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { @@ -86,7 +83,7 @@ public void visitExpression(PsiExpression expression) { }; } - private static boolean hasOverflow(PsiExpression expr, @Nonnull Project project) { + private static boolean hasOverflow(PsiExpression expr, Project project) { if (!TypeConversionUtil.isNumericType(expr.getType())) return false; boolean overflow = false; try { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RedundantLambdaCodeBlockInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RedundantLambdaCodeBlockInspection.java index 7e475b60e4..50767502b0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RedundantLambdaCodeBlockInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RedundantLambdaCodeBlockInspection.java @@ -16,25 +16,21 @@ package com.intellij.java.analysis.impl.codeInspection; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.annotation.component.ExtensionImpl; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.intention.HighPriorityAction; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiComment; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; import java.util.Collection; /** @@ -44,24 +40,19 @@ public class RedundantLambdaCodeBlockInspection extends BaseJavaBatchLocalInspectionTool { public static final Logger LOG = Logger.getInstance(RedundantLambdaCodeBlockInspection.class); - @Nls - @Nonnull @Override - public String getGroupDisplayName() { - return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); } - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; } - @Nls - @Nonnull @Override - public String getDisplayName() { - return "Statement lambda can be replaced with expression lambda"; + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Statement lambda can be replaced with expression lambda"); } @Override @@ -69,15 +60,16 @@ public boolean isEnabledByDefault() { return true; } - @Nonnull @Override public String getShortName() { return "CodeBlock2Expr"; } - @Nonnull @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { + public PsiElementVisitor buildVisitorImpl(final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state) { return new JavaElementVisitor() { @Override public void visitLambdaExpression(PsiLambdaExpression expression) { @@ -141,14 +133,13 @@ private static boolean findCommentsOutsideExpression(PsiElement body, PsiExpress } private static class ReplaceWithExprFix implements LocalQuickFix, HighPriorityAction { - @Nonnull @Override - public String getFamilyName() { - return "Replace with expression lambda"; + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with expression lambda"); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + public void applyFix(Project project, ProblemDescriptor descriptor) { final PsiElement element = descriptor.getPsiElement(); if (element != null) { final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(element, PsiLambdaExpression.class); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RemoveAnnotationQuickFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RemoveAnnotationQuickFix.java index df6279db19..38d3ecc162 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RemoveAnnotationQuickFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/RemoveAnnotationQuickFix.java @@ -15,55 +15,68 @@ */ package com.intellij.java.analysis.impl.codeInspection; -import consulo.language.editor.CodeInsightBundle; import com.intellij.java.language.codeInsight.ExternalAnnotationsManager; +import com.intellij.java.language.psi.PsiAnnotation; +import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.language.editor.FileModificationService; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import com.intellij.java.language.psi.PsiAnnotation; -import com.intellij.java.language.psi.PsiModifierListOwner; +import consulo.language.editor.localize.CodeInsightLocalize; +import consulo.language.psi.SmartPointerManager; +import consulo.language.psi.SmartPsiElementPointer; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; - -import javax.annotation.Nonnull; +import consulo.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; /** * @author yole */ public class RemoveAnnotationQuickFix implements LocalQuickFix { - private static final Logger LOG = Logger.getInstance("com.intellij.codeInsight.i18n.AnnotateNonNlsQuickfix"); - private final PsiAnnotation myAnnotation; - private final PsiModifierListOwner myListOwner; + private static final Logger LOG = Logger.getInstance(RemoveAnnotationQuickFix.class); + private final SmartPsiElementPointer myAnnotation; + private final SmartPsiElementPointer myListOwner; + private final boolean myRemoveInheritors; - public RemoveAnnotationQuickFix(PsiAnnotation annotation, final PsiModifierListOwner listOwner) { - myAnnotation = annotation; - myListOwner = listOwner; - } + public RemoveAnnotationQuickFix(@NotNull PsiAnnotation annotation, @Nullable PsiModifierListOwner listOwner) { + this(annotation, listOwner, false); + } - @Override - @Nonnull - public String getName() { - return CodeInsightBundle.message("remove.annotation"); - } + public RemoveAnnotationQuickFix(@NotNull PsiAnnotation annotation, @Nullable PsiModifierListOwner listOwner, boolean removeInheritors) { + Project project = annotation.getProject(); + SmartPointerManager pm = SmartPointerManager.getInstance(project); + myAnnotation = pm.createSmartPsiElementPointer(annotation); + myListOwner = listOwner == null ? null : pm.createSmartPsiElementPointer(listOwner); + myRemoveInheritors = removeInheritors; + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - if (myAnnotation.isPhysical()) { - try { - if (!FileModificationService.getInstance().preparePsiElementForWrite(myAnnotation)) return; - myAnnotation.delete(); - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } else { - ExternalAnnotationsManager.getInstance(project).deannotate(myListOwner, myAnnotation.getQualifiedName()); + @Override + public LocalizeValue getName() { + return CodeInsightLocalize.removeAnnotation(); } - } - @Override - @Nonnull - public String getFamilyName() { - return getName(); - } + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiAnnotation annotation = myAnnotation.getElement(); + if (annotation == null) { + return; + } + + if (annotation.isPhysical()) { + try { + if (!FileModificationService.getInstance().preparePsiElementForWrite(annotation)) { + return; + } + annotation.delete(); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + else { + ExternalAnnotationsManager.getInstance(project).deannotate(myListOwner.getElement(), annotation.getQualifiedName()); + } + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SetInspectionOptionFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SetInspectionOptionFix.java index fd872866ee..cd584c1b3a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SetInspectionOptionFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SetInspectionOptionFix.java @@ -1,103 +1,88 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection; +import com.intellij.java.analysis.codeInspection.AbstractBaseJavaLocalInspectionTool; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.AllIcons; import consulo.component.util.Iconable; -import consulo.language.editor.inspection.LocalInspectionTool; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.scheme.*; +import consulo.language.editor.inspection.scheme.InspectionProfile; +import consulo.language.editor.inspection.scheme.InspectionProjectProfileManager; import consulo.language.editor.intention.LowPriorityAction; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiManager; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.ui.image.Image; import consulo.undoRedo.BasicUndoableAction; import consulo.undoRedo.ProjectUndoManager; import consulo.virtualFileSystem.VirtualFile; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import java.io.IOException; -import java.util.function.BiConsumer; - -public class SetInspectionOptionFix implements LocalQuickFix, LowPriorityAction, Iconable { - private final String myID; - private final BiConsumer myPropertySetter; - private final String myMessage; - private final boolean myValue; - public SetInspectionOptionFix(I inspection, BiConsumer propertySetter, String message, boolean value) { - myID = inspection.getShortName(); - myPropertySetter = propertySetter; - myMessage = message; - myValue = value; - } +import java.util.function.BiConsumer; - @Nls - @Nonnull - @Override - public String getName() { - return myMessage; - } +public class SetInspectionOptionFix, State> + implements LocalQuickFix, LowPriorityAction, Iconable { + private final String myID; + private final BiConsumer myPropertySetter; + private final LocalizeValue myMessage; + private final boolean myValue; - @Nls - @Nonnull - @Override - public String getFamilyName() { - return "Set inspection option"; - } + public SetInspectionOptionFix(I inspection, BiConsumer propertySetter, LocalizeValue message, boolean value) { + myID = inspection.getShortName(); + myPropertySetter = propertySetter; + myMessage = message; + myValue = value; + } - @Override - public boolean startInWriteAction() { - return false; - } + @Override + public LocalizeValue getName() { + return myMessage; + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - VirtualFile vFile = descriptor.getPsiElement().getContainingFile().getVirtualFile(); - setOption(project, vFile, myValue); - ProjectUndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction(vFile) { - @Override - public void undo() { - setOption(project, vFile, !myValue); - } + @Override + public boolean startInWriteAction() { + return false; + } - @Override - public void redo() { + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + VirtualFile vFile = descriptor.getPsiElement().getContainingFile().getVirtualFile(); setOption(project, vFile, myValue); - } - }); - } + ProjectUndoManager.getInstance(project).undoableActionPerformed(new BasicUndoableAction(vFile) { + @Override + @RequiredReadAction + public void undo() { + setOption(project, vFile, !myValue); + } - @RequiredReadAction - @SuppressWarnings("unchecked") - private void setOption(@Nonnull Project project, @Nonnull VirtualFile vFile, boolean value) { - PsiFile file = PsiManager.getInstance(project).findFile(vFile); - if (file == null) { - return; + @Override + @RequiredReadAction + public void redo() { + setOption(project, vFile, myValue); + } + }); } - InspectionProjectProfileManager manager = InspectionProjectProfileManager.getInstance(project); - InspectionProfile inspectionProfile = manager.getInspectionProfile(); + @RequiredReadAction + private void setOption(Project project, VirtualFile vFile, boolean value) { + PsiFile file = PsiManager.getInstance(project).findFile(vFile); + if (file == null) { + return; + } - ModifiableModel modifiableModel = inspectionProfile.getModifiableModel(); + InspectionProjectProfileManager manager = InspectionProjectProfileManager.getInstance(project); - InspectionToolWrapper inspectionTool = modifiableModel.getInspectionTool(myID, file); - if (inspectionTool != null) { - InspectionProfileEntry tool = inspectionTool.getTool(); - myPropertySetter.accept((I)tool, value); - } - try { - modifiableModel.commit(); - } catch (IOException e) { - throw new RuntimeException(e); + InspectionProfile inspectionProfile = manager.getInspectionProfile(); + + inspectionProfile.modifyToolSettings(myID, file, (inspectionTool, state) -> myPropertySetter.accept(state, value)); } - } - @Override - public Image getIcon(int flags) { - return AllIcons.Actions.Cancel; - } + @Override + public Image getIcon(int flags) { + return AllIcons.Actions.Cancel; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SideEffectChecker.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SideEffectChecker.java new file mode 100644 index 0000000000..8c9053ff39 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SideEffectChecker.java @@ -0,0 +1,355 @@ +/* + * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInspection; + +import com.intellij.java.analysis.impl.codeInspection.dataFlow.ContractValue; +import com.intellij.java.analysis.impl.codeInspection.dataFlow.JavaMethodContractUtil; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.InheritanceUtil; +import com.intellij.java.language.psi.util.PropertyUtil; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.language.ast.IElementType; +import consulo.language.psi.PsiDirectory; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiFile; +import consulo.language.psi.PsiPackage; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.util.collection.SmartList; +import org.jspecify.annotations.Nullable; +import one.util.streamex.StreamEx; + +import java.util.*; +import java.util.function.Predicate; + +import static consulo.util.lang.ObjectUtil.tryCast; + +public class SideEffectChecker { + private static final Set ourSideEffectFreeClasses = new HashSet<>(Arrays.asList( + Object.class.getName(), + Short.class.getName(), + Character.class.getName(), + Byte.class.getName(), + Integer.class.getName(), + Long.class.getName(), + Float.class.getName(), + Double.class.getName(), + String.class.getName(), + StringBuffer.class.getName(), + Boolean.class.getName(), + + ArrayList.class.getName(), + Date.class.getName(), + HashMap.class.getName(), + HashSet.class.getName(), + Hashtable.class.getName(), + LinkedHashMap.class.getName(), + LinkedHashSet.class.getName(), + LinkedList.class.getName(), + Stack.class.getName(), + TreeMap.class.getName(), + TreeSet.class.getName(), + Vector.class.getName(), + WeakHashMap.class.getName() + )); + + private SideEffectChecker() { + } + + public static boolean mayHaveSideEffects(PsiExpression exp) { + SideEffectsVisitor visitor = new SideEffectsVisitor(null, exp); + exp.accept(visitor); + return visitor.mayHaveSideEffects(); + } + + @RequiredReadAction + public static boolean mayHaveSideEffects( + PsiElement element, + @RequiredReadAction Predicate shouldIgnoreElement + ) { + SideEffectsVisitor visitor = new SideEffectsVisitor(null, element, shouldIgnoreElement); + element.accept(visitor); + return visitor.mayHaveSideEffects(); + } + + /** + * Returns true if element execution may cause non-local side-effect. Side-effects like control flow within method; throw/return or + * local variable declaration or update are considered as local side-effects. + * + * @param element element to check + * @return true if element execution may cause non-local side-effect. + */ + @RequiredReadAction + public static boolean mayHaveNonLocalSideEffects(PsiElement element) { + return mayHaveSideEffects(element, SideEffectChecker::isLocalSideEffect); + } + + @RequiredReadAction + private static boolean isLocalSideEffect(PsiElement e) { + if (e instanceof PsiContinueStatement || e instanceof PsiReturnStatement || e instanceof PsiThrowStatement) { + return true; + } + if (e instanceof PsiLocalVariable) { + return true; + } + + PsiReferenceExpression ref = null; + if (e instanceof PsiAssignmentExpression assignment) { + ref = tryCast(PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()), PsiReferenceExpression.class); + } + if (e instanceof PsiUnaryExpression unaryExpr) { + PsiExpression operand = unaryExpr.getOperand(); + ref = tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class); + } + if (ref != null) { + PsiElement target = ref.resolve(); + if (target instanceof PsiLocalVariable || target instanceof PsiParameter) { + return true; + } + } + return false; + } + + public static boolean checkSideEffects(PsiExpression element, @Nullable List sideEffects) { + return checkSideEffects(element, sideEffects, e -> false); + } + + public static boolean checkSideEffects( + PsiExpression element, + @Nullable List sideEffects, + Predicate ignoreElement + ) { + SideEffectsVisitor visitor = new SideEffectsVisitor(sideEffects, element, ignoreElement); + element.accept(visitor); + return visitor.mayHaveSideEffects(); + } + + public static List extractSideEffectExpressions(PsiExpression element) { + List list = new SmartList<>(); + element.accept(new SideEffectsVisitor(list, element)); + return StreamEx.of(list).select(PsiExpression.class).toList(); + } + + private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { + private final + @Nullable + List mySideEffects; + private final + PsiElement myStartElement; + private final + Predicate myIgnorePredicate; + boolean found; + + SideEffectsVisitor(@Nullable List sideEffects, PsiElement startElement) { + this(sideEffects, startElement, call -> false); + } + + SideEffectsVisitor( + @Nullable List sideEffects, + PsiElement startElement, + Predicate predicate + ) { + myStartElement = startElement; + myIgnorePredicate = predicate; + mySideEffects = sideEffects; + } + + private boolean addSideEffect(PsiElement element) { + if (myIgnorePredicate.test(element)) { + return false; + } + found = true; + if (mySideEffects != null) { + mySideEffects.add(element); + } + else { + stopWalking(); + } + return true; + } + + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + if (addSideEffect(expression)) { + return; + } + super.visitAssignmentExpression(expression); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + PsiMethod method = expression.resolveMethod(); + if (!isPure(method)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitMethodCallExpression(expression); + } + + protected boolean isPure(PsiMethod method) { + if (method == null) { + return false; + } + PsiField field = PropertyUtil.getFieldOfGetter(method); + if (field != null) { + return !field.hasModifierProperty(PsiModifier.VOLATILE); + } + return JavaMethodContractUtil.isPure(method) && !mayHaveExceptionalSideEffect(method); + } + + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + if (!expression.isArrayCreation() && !isSideEffectFreeConstructor(expression)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitNewExpression(expression); + } + + @Override + public void visitUnaryExpression(PsiUnaryExpression expression) { + IElementType tokenType = expression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { + if (addSideEffect(expression)) { + return; + } + } + super.visitUnaryExpression(expression); + } + + @Override + public void visitVariable(PsiVariable variable) { + if (addSideEffect(variable)) { + return; + } + super.visitVariable(variable); + } + + @Override + public void visitBreakStatement(PsiBreakStatement statement) { + PsiStatement exitedStatement = statement.findExitedStatement(); + if (exitedStatement == null || !PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { + if (addSideEffect(statement)) { + return; + } + } + super.visitBreakStatement(statement); + } + + @Override + public void visitClass(PsiClass aClass) { + // local or anonymous class declaration is not side effect per se (unless it's instantiated) + } + + @Override + public void visitContinueStatement(PsiContinueStatement statement) { + PsiStatement exitedStatement = statement.findContinuedStatement(); + if (exitedStatement != null && PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { + return; + } + if (addSideEffect(statement)) { + return; + } + super.visitContinueStatement(statement); + } + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + if (addSideEffect(statement)) { + return; + } + super.visitReturnStatement(statement); + } + + @Override + public void visitThrowStatement(PsiThrowStatement statement) { + if (addSideEffect(statement)) { + return; + } + super.visitThrowStatement(statement); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + // lambda is not side effect per se (unless it's called) + } + + public boolean mayHaveSideEffects() { + return found; + } + } + + /** + * Returns true if given method function is likely to throw an exception (e.g. "assertEquals"). In some cases this means that + * the method call should be preserved in source code even if it's pure (i.e. does not change the program state). + * + * @param method a method to check + * @return true if the method has exceptional side effect + */ + public static boolean mayHaveExceptionalSideEffect(PsiMethod method) { + String name = method.getName(); + if (name.startsWith("assert") || name.startsWith("check") || name.startsWith("require")) { + return true; + } + PsiClass aClass = method.getContainingClass(); + //noinspection SimplifiableIfStatement + if (InheritanceUtil.isInheritor(aClass, "org.assertj.core.api.Descriptable")) { + // See com.intellij.codeInsight.DefaultInferredAnnotationProvider#getHardcodedContractAnnotation + return true; + } + return JavaMethodContractUtil.getMethodCallContracts(method, null).stream() + .filter(mc -> mc.getConditions().stream().noneMatch(ContractValue::isBoundCheckingCondition)) + .anyMatch(mc -> mc.getReturnValue().isFail()); + } + + @RequiredReadAction + private static boolean isSideEffectFreeConstructor(PsiNewExpression newExpression) { + PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); + if (anonymousClass != null && anonymousClass.getInitializers().length == 0) { + PsiClass baseClass = anonymousClass.getBaseClassType().resolve(); + if (baseClass != null && baseClass.isInterface()) { + return true; + } + } + PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); + PsiClass aClass = classReference == null ? null : (PsiClass)classReference.resolve(); + String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); + if (qualifiedName == null) { + return false; + } + if (ourSideEffectFreeClasses.contains(qualifiedName)) { + return true; + } + + PsiFile file = aClass.getContainingFile(); + PsiDirectory directory = file.getContainingDirectory(); + PsiPackage classPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); + String packageName = classPackage == null ? null : classPackage.getQualifiedName(); + + // all Throwable descendants from java.lang are side effects free + if (CommonClassNames.DEFAULT_PACKAGE.equals(packageName) || "java.io".equals(packageName)) { + PsiClass throwableClass = + JavaPsiFacade.getInstance(aClass.getProject()).findClass(CommonClassNames.JAVA_LANG_THROWABLE, aClass.getResolveScope()); + if (throwableClass != null && InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { + return true; + } + } + return false; + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SwitchUtils.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SwitchUtils.java new file mode 100644 index 0000000000..2df7cadb97 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/SwitchUtils.java @@ -0,0 +1,526 @@ +/* + * Copyright 2003-2013 Dave Griffith, Bas Leijdekkers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInspection; + +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; +import com.intellij.java.language.JavaFeature; +import com.intellij.java.language.LanguageLevel; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.language.ast.IElementType; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiErrorElement; +import consulo.language.psi.PsiReference; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.Contract; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SwitchUtils { + /** + * State of switch exhaustiveness. + */ + public enum SwitchExhaustivenessState { + /** + * Switch is malformed and produces a compilation error (no body, no selector, etc.), + * no exhaustiveness analysis is performed + */ + MALFORMED, + /** + * Switch contains no labels (except probably default label) + */ + EMPTY, + /** + * Switch should not be exhaustive (classic switch statement) + */ + UNNECESSARY, + /** + * Switch is not exhaustive + */ + INCOMPLETE, + /** + * Switch is exhaustive (complete), and adding a default branch would be a compilation error. + * This includes a switch over boolean having both true and false branches, + * or a switch that has an unconditional pattern branch. + */ + EXHAUSTIVE_NO_DEFAULT, + /** + * Switch is exhaustive (complete), but it's possible to add a default branch. + */ + EXHAUSTIVE_CAN_ADD_DEFAULT + } + + private SwitchUtils() { + } + + /** + * Evaluates the exhaustiveness state of a switch block. + * + * @param switchBlock the PsiSwitchBlock to evaluate + * @param considerNestedDeconstructionPatterns flag indicating whether to consider nested deconstruction patterns. It is necessary to take into account, + * because nested deconstruction patterns don't cover null values + * @return exhaustiveness state. + */ + public static SwitchExhaustivenessState evaluateSwitchCompleteness(PsiSwitchBlock switchBlock, + boolean considerNestedDeconstructionPatterns) { + PsiExpression selector = switchBlock.getExpression(); + if (selector == null) { + return SwitchExhaustivenessState.MALFORMED; + } + PsiType selectorType = selector.getType(); + if (selectorType == null) { + return SwitchExhaustivenessState.MALFORMED; + } + PsiCodeBlock switchBody = switchBlock.getBody(); + if (switchBody == null) { + return SwitchExhaustivenessState.MALFORMED; + } + List labelElements = StreamEx.of(JavaPsiSwitchUtil.getSwitchBranches(switchBlock)).select(PsiCaseLabelElement.class) + .filter(element -> !(element instanceof PsiDefaultCaseLabelElement)).toList(); + if (labelElements.isEmpty()) { + return SwitchExhaustivenessState.EMPTY; + } + boolean needToCheckCompleteness = ExpressionUtil.isEnhancedSwitch(switchBlock); + boolean isEnumSelector = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType) == JavaPsiSwitchUtil.SelectorKind.ENUM; + if (ContainerUtil.find(labelElements, element -> JavaPsiPatternUtil.isUnconditionalForType(element, selectorType)) != null) { + return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT; + } + if (JavaPsiSwitchUtil.isBooleanSwitchWithTrueAndFalse(switchBlock)) { + return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT; + } + if (!needToCheckCompleteness && !isEnumSelector) { + return SwitchExhaustivenessState.INCOMPLETE; + } + // It is necessary because deconstruction patterns don't cover cases + // when some of their components are null and deconstructionPattern too + if (!considerNestedDeconstructionPatterns) { + labelElements = ContainerUtil.filter( + labelElements, label -> !(label instanceof PsiDeconstructionPattern deconstructionPattern && + ContainerUtil.or( + deconstructionPattern.getDeconstructionList().getDeconstructionComponents(), + component -> component instanceof PsiDeconstructionPattern))); + } + boolean hasError = hasExhaustivenessError(switchBlock, labelElements); + // if a switch block is needed to check completeness and switch is incomplete we let highlighting to inform about it as it's a compilation error + if (!hasError) { + return SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT; + } + if (needToCheckCompleteness) { + return SwitchExhaustivenessState.UNNECESSARY; + } + return SwitchExhaustivenessState.INCOMPLETE; + } + + /** + * @param block switch block to analyze + * @return true if this block is not exhaustive while it should be + */ + public static boolean hasExhaustivenessError(PsiSwitchBlock block) { + return hasExhaustivenessError(block, JavaPsiSwitchUtil.getCaseLabelElements(block)); + } + + /** + * @param block switch block to analyze + * @param elements list of labels to analyze (can be a subset of all labels of the block) + * @return true if this block is not exhaustive while it should be + */ + public static boolean hasExhaustivenessError(PsiSwitchBlock block, List elements) { + PsiExpression selector = block.getExpression(); + if (selector == null) { + return false; + } + PsiType selectorType = selector.getType(); + if (selectorType == null) { + return false; + } + PsiPrimitiveType unboxedType = PsiPrimitiveType.getUnboxedType(TypeConversionUtil.erasure(selectorType)); + if (unboxedType != null) { + for (PsiCaseLabelElement t : elements) { + if (JavaPsiPatternUtil.findUnconditionalPattern(t) instanceof PsiTypeTestPattern testPattern && + JavaPsiPatternUtil.getPatternType(testPattern) instanceof PsiPrimitiveType primitiveType && + JavaPsiPatternUtil.isUnconditionallyExactForType(t, unboxedType, primitiveType)) { + return false; + } + } + } + if (JavaPsiSwitchUtil.isBooleanSwitchWithTrueAndFalse(block)) { + return false; + } + //enums are final; checking intersections is not needed + PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(selectorType)); + if (selectorClass != null && JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType) == JavaPsiSwitchUtil.SelectorKind.ENUM) { + List enumElements = getEnumConstants(elements); + if (enumElements.isEmpty()) { + return true; + } + return StreamEx.of(selectorClass.getFields()).select(PsiEnumConstant.class).anyMatch(e -> !enumElements.contains(e)); + } + boolean hasAbstractSealedType = StreamEx.of(JavaPsiPatternUtil.deconstructSelectorType(selectorType)) + .map(type -> PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type))) + .nonNull() + .anyMatch(JavaPsiSealedUtil::isAbstractSealed); + if (hasAbstractSealedType) { + return !JavaPatternExhaustivenessUtil.findMissedClasses(block, selectorType, elements).isEmpty(); + } + //records are final; checking intersections is not needed + boolean recordExhaustive = selectorClass != null && + selectorClass.isRecord() && + JavaPatternExhaustivenessUtil.checkRecordExhaustiveness(elements, selectorType, block).isExhaustive(); + return !recordExhaustive; + } + + private static List getEnumConstants(List elements) { + return StreamEx.of(elements).map(JavaPsiSwitchUtil::getEnumConstant).nonNull().toList(); + } + + /** + * Calculates the number of branches in the specified switch statement. + * When a default case is present the count will be returned as a negative number, + * e.g. if a switch statement contains 4 labeled cases and a default case, it will return -5 + * + * @param statement the statement to count the cases of. + * @return a negative number if a default case was encountered. + */ + public static int calculateBranchCount(PsiSwitchStatement statement) { + // preserved for plugin compatibility + return calculateBranchCount((PsiSwitchBlock) statement); + } + + /** + * Calculates the number of branches in the specified switch block. + * When a default case is present the count will be returned as a negative number, + * e.g. if a switch block contains 4 labeled cases and a default case, it will return -5 + * + * @param block the switch block to count the cases of. + * @return a negative number if a default case was encountered. + */ + public static int calculateBranchCount(PsiSwitchBlock block) { + final PsiCodeBlock body = block.getBody(); + if (body == null) { + return 0; + } + int branches = 0; + boolean defaultFound = false; + for (final PsiSwitchLabelStatementBase child : PsiTreeUtil.getChildrenOfTypeAsList(body, PsiSwitchLabelStatementBase.class)) { + if (child.isDefaultCase()) { + defaultFound = true; + } + else { + branches++; + } + } + return defaultFound ? -branches - 1 : branches; + } + + @Nullable + public static PsiExpression getSwitchExpression(PsiIfStatement statement, int minimumBranches) { + final PsiExpression condition = statement.getCondition(); + final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(statement); + final PsiExpression possibleSwitchExpression = determinePossibleSwitchExpressions(condition, languageLevel); + if (!canBeSwitchExpression(possibleSwitchExpression, languageLevel)) { + return null; + } + int branchCount = 0; + while (true) { + branchCount++; + if (!canBeMadeIntoCase(statement.getCondition(), possibleSwitchExpression, languageLevel)) { + break; + } + final PsiStatement elseBranch = statement.getElseBranch(); + if (!(elseBranch instanceof PsiIfStatement)) { + if (elseBranch != null) { + branchCount++; + } + if (branchCount < minimumBranches) { + return null; + } + return possibleSwitchExpression; + } + statement = (PsiIfStatement) elseBranch; + } + return null; + } + + private static boolean canBeMadeIntoCase(PsiExpression expression, PsiExpression switchExpression, LanguageLevel languageLevel) { + expression = ParenthesesUtils.stripParentheses(expression); + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { + final PsiExpression stringSwitchExpression = determinePossibleStringSwitchExpression(expression); + if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(switchExpression, stringSwitchExpression)) { + return true; + } + } + if (!(expression instanceof PsiPolyadicExpression)) { + return false; + } + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) expression; + final IElementType operation = polyadicExpression.getOperationTokenType(); + final PsiExpression[] operands = polyadicExpression.getOperands(); + if (operation.equals(JavaTokenType.OROR)) { + for (PsiExpression operand : operands) { + if (!canBeMadeIntoCase(operand, switchExpression, languageLevel)) { + return false; + } + } + return true; + } + else if (operation.equals(JavaTokenType.EQEQ) && operands.length == 2) { + return (canBeCaseLabel(operands[0], languageLevel) && EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(switchExpression, operands[1])) || + (canBeCaseLabel(operands[1], languageLevel) && EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(switchExpression, operands[0])); + } + else { + return false; + } + } + + private static boolean canBeSwitchExpression(PsiExpression expression, LanguageLevel languageLevel) { + if (expression == null || SideEffectChecker.mayHaveSideEffects(expression)) { + return false; + } + final PsiType type = expression.getType(); + if (PsiType.CHAR.equals(type) || PsiType.BYTE.equals(type) || PsiType.SHORT.equals(type) || PsiType.INT.equals(type)) { + return true; + } + else if (type instanceof PsiClassType) { + if (type.equalsToText(CommonClassNames.JAVA_LANG_CHARACTER) || type.equalsToText(CommonClassNames.JAVA_LANG_BYTE) || + type.equalsToText(CommonClassNames.JAVA_LANG_SHORT) || type.equalsToText(CommonClassNames.JAVA_LANG_INTEGER)) { + return true; + } + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) { + final PsiClassType classType = (PsiClassType) type; + final PsiClass aClass = classType.resolve(); + if (aClass != null && aClass.isEnum()) { + return true; + } + } + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_7) && type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return true; + } + } + return false; + } + + private static PsiExpression determinePossibleSwitchExpressions(PsiExpression expression, LanguageLevel languageLevel) { + expression = ParenthesesUtils.stripParentheses(expression); + if (expression == null) { + return null; + } + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { + final PsiExpression jdk17Expression = determinePossibleStringSwitchExpression(expression); + if (jdk17Expression != null) { + return jdk17Expression; + } + } + if (!(expression instanceof PsiPolyadicExpression)) { + return null; + } + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) expression; + final IElementType operation = polyadicExpression.getOperationTokenType(); + final PsiExpression[] operands = polyadicExpression.getOperands(); + if (operation.equals(JavaTokenType.OROR) && operands.length > 0) { + return determinePossibleSwitchExpressions(operands[0], languageLevel); + } + else if (operation.equals(JavaTokenType.EQEQ) && operands.length == 2) { + final PsiExpression lhs = operands[0]; + final PsiExpression rhs = operands[1]; + if (canBeCaseLabel(lhs, languageLevel)) { + return rhs; + } + else if (canBeCaseLabel(rhs, languageLevel)) { + return lhs; + } + } + return null; + } + + private static PsiExpression determinePossibleStringSwitchExpression(PsiExpression expression) { + if (!(expression instanceof PsiMethodCallExpression)) { + return null; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) expression; + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final String referenceName = methodExpression.getReferenceName(); + if (!"equals".equals(referenceName)) { + return null; + } + final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + if (qualifierExpression == null) { + return null; + } + final PsiType type = qualifierExpression.getType(); + if (type == null || !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return null; + } + final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); + final PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length != 1) { + return null; + } + final PsiExpression argument = arguments[0]; + final PsiType argumentType = argument.getType(); + if (argumentType == null || !argumentType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return null; + } + if (PsiUtil.isConstantExpression(qualifierExpression)) { + return argument; + } + else if (PsiUtil.isConstantExpression(argument)) { + return qualifierExpression; + } + return null; + } + + private static boolean canBeCaseLabel(PsiExpression expression, LanguageLevel languageLevel) { + if (expression == null) { + return false; + } + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5) && expression instanceof PsiReferenceExpression) { + final PsiElement referent = ((PsiReference) expression).resolve(); + if (referent instanceof PsiEnumConstant) { + return true; + } + } + final PsiType type = expression.getType(); + return (PsiType.INT.equals(type) || PsiType.SHORT.equals(type) || PsiType.BYTE.equals(type) || PsiType.CHAR.equals(type)) && + PsiUtil.isConstantExpression(expression); + } + + public static String findUniqueLabelName(PsiStatement statement, String baseName) { + final PsiElement ancestor = PsiTreeUtil.getParentOfType(statement, PsiMember.class); + if (!checkForLabel(baseName, ancestor)) { + return baseName; + } + int val = 1; + while (true) { + final String name = baseName + val; + if (!checkForLabel(name, ancestor)) { + return name; + } + val++; + } + } + + private static boolean checkForLabel(String name, PsiElement ancestor) { + final LabelSearchVisitor visitor = new LabelSearchVisitor(name); + ancestor.accept(visitor); + return visitor.isUsed(); + } + + /** + * Returns true if given switch block has a rule-based format (like 'case 0 ->') + * + * @param block block to test + * @return true if given switch block has a rule-based format; false if it has conventional label-based format (like 'case 0:') + * If switch body has no labels yet and language level permits, rule-based format is assumed. + */ + @RequiredReadAction + public static boolean isRuleFormatSwitch(PsiSwitchBlock block) { + if (!PsiUtil.isAvailable(JavaFeature.ENHANCED_SWITCH, block)) { + return false; + } + + final PsiCodeBlock switchBody = block.getBody(); + if (switchBody != null) { + for (var child = switchBody.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiSwitchLabelStatementBase && !isBeingCompleted((PsiSwitchLabelStatementBase) child)) { + return child instanceof PsiSwitchLabeledRuleStatement; + } + } + } + + return true; + } + + /** + * Checks if the label is being completed and there are no other case label elements in the list of the case label's elements + * + * @param label the label to analyze + * @return true if the label is currently being completed + */ + @Contract(pure = true) + @RequiredReadAction + private static boolean isBeingCompleted(PsiSwitchLabelStatementBase label) { + if (!(label.getLastChild() instanceof PsiErrorElement)) { + return false; + } + + final PsiCaseLabelElementList list = label.getCaseLabelElementList(); + return list != null && list.getElements().length == 1; + } + + /** + * @param label a switch label statement + * @return list of enum constants which are targets of the specified label; empty list if the supplied element is not a switch label, + * or it is not an enum switch. + */ + public static List findEnumConstants(PsiSwitchLabelStatementBase label) { + if (label == null) { + return Collections.emptyList(); + } + final PsiExpressionList list = label.getCaseValues(); + if (list == null) { + return Collections.emptyList(); + } + List constants = new ArrayList<>(); + for (PsiExpression value : list.getExpressions()) { + if (value instanceof PsiReferenceExpression) { + final PsiElement target = ((PsiReferenceExpression) value).resolve(); + if (target instanceof PsiEnumConstant) { + constants.add((PsiEnumConstant) target); + continue; + } + } + return Collections.emptyList(); + } + return constants; + } + + private static class LabelSearchVisitor extends JavaRecursiveElementWalkingVisitor { + + private final String m_labelName; + private boolean m_used = false; + + LabelSearchVisitor(String name) { + m_labelName = name; + } + + @Override + public void visitElement(PsiElement element) { + if (m_used) { + return; + } + super.visitElement(element); + } + + @Override + public void visitLabeledStatement(PsiLabeledStatement statement) { + final PsiIdentifier labelIdentifier = statement.getLabelIdentifier(); + final String labelText = labelIdentifier.getText(); + if (labelText.equals(m_labelName)) { + m_used = true; + } + } + + public boolean isUsed() { + return m_used; + } + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/booleanIsAlwaysInverted/BooleanMethodIsAlwaysInvertedInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/booleanIsAlwaysInverted/BooleanMethodIsAlwaysInvertedInspection.java index 0454483ca4..9eb367ae4b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/booleanIsAlwaysInverted/BooleanMethodIsAlwaysInvertedInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/booleanIsAlwaysInverted/BooleanMethodIsAlwaysInvertedInspection.java @@ -2,16 +2,16 @@ import com.intellij.java.analysis.codeInspection.GlobalJavaInspectionContext; import com.intellij.java.analysis.codeInspection.GlobalJavaInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.analysis.codeInspection.reference.RefJavaVisitor; import com.intellij.java.analysis.codeInspection.reference.RefMethod; import com.intellij.java.analysis.refactoring.JavaRefactoringActionHandlerFactory; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.application.ApplicationManager; import consulo.dataContext.DataManager; import consulo.language.ast.IElementType; import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefEntity; import consulo.language.editor.inspection.reference.RefGraphAnnotator; @@ -22,211 +22,206 @@ import consulo.language.editor.refactoring.action.RefactoringActionHandler; import consulo.language.editor.scope.AnalysisScope; import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiReference; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.dataholder.Key; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; /** - * User: anna - * Date: 06-Jan-2006 + * @author anna + * @since 2006-01-06 */ @ExtensionImpl @IntentionMetaData(ignoreId = "java.BooleanMethodIsAlwaysInvertedInspection", fileExtensions = "java", categories = {"Java", "Boolean"}) public class BooleanMethodIsAlwaysInvertedInspection extends GlobalJavaInspectionTool { - private static final Key ALWAYS_INVERTED = Key.create("ALWAYS_INVERTED_METHOD"); - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("boolean.method.is.always.inverted.display.name"); - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.DATA_FLOW_ISSUES; - } - - @Override - @Nonnull - @NonNls - public String getShortName() { - return "BooleanMethodIsAlwaysInverted"; - } - - @Override - @Nullable - public RefGraphAnnotator getAnnotator(final RefManager refManager) { - return new BooleanInvertedAnnotator(); - } - - @Override - public CommonProblemDescriptor[] checkElement(RefEntity refEntity, - AnalysisScope scope, - final InspectionManager manager, - final GlobalInspectionContext globalContext) { - if (refEntity instanceof RefMethod) { - RefMethod refMethod = (RefMethod) refEntity; - if (!refMethod.isReferenced()) return null; - if (hasNonInvertedCalls(refMethod)) return null; - if (!refMethod.getSuperMethods().isEmpty()) return null; - final PsiMethod psiMethod = (PsiMethod) refMethod.getElement(); - final PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier(); - if (psiIdentifier != null) { - return new ProblemDescriptor[]{manager.createProblemDescriptor(psiIdentifier, - InspectionsBundle - .message("boolean.method.is.always.inverted.problem.descriptor"), - new InvertMethodFix(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)}; - } + private static final Key ALWAYS_INVERTED = Key.create("ALWAYS_INVERTED_METHOD"); + + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; } - return null; - } - - private static boolean hasNonInvertedCalls(final RefMethod refMethod) { - final Boolean alwaysInverted = refMethod.getUserData(ALWAYS_INVERTED); - if (alwaysInverted == null) return true; - if (refMethod.isExternalOverride()) return true; - if (refMethod.isReferenced() && !alwaysInverted.booleanValue()) return true; - final Collection superMethods = refMethod.getSuperMethods(); - for (RefMethod superMethod : superMethods) { - if (hasNonInvertedCalls(superMethod)) return true; + + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.booleanMethodIsAlwaysInvertedDisplayName(); } - return false; - } - - @Override - protected boolean queryExternalUsagesRequests(final RefManager manager, final GlobalJavaInspectionContext context, - final ProblemDescriptionsProcessor descriptionsProcessor) { - manager.iterate(new RefJavaVisitor() { - @Override - public void visitMethod(@Nonnull final RefMethod refMethod) { - if (descriptionsProcessor.getDescriptions(refMethod) != null) { //suspicious method -> need to check external usages - final GlobalJavaInspectionContext.UsagesProcessor usagesProcessor = new GlobalJavaInspectionContext.UsagesProcessor() { - @Override - public boolean process(PsiReference psiReference) { - final PsiElement psiReferenceExpression = psiReference.getElement(); - if (psiReferenceExpression instanceof PsiReferenceExpression && - !isInvertedMethodCall((PsiReferenceExpression) psiReferenceExpression)) { - descriptionsProcessor.ignoreElement(refMethod); - } - return false; + + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesDataFlowIssues(); + } + + @Override + public String getShortName() { + return "BooleanMethodIsAlwaysInverted"; + } + + @Nullable + @Override + public RefGraphAnnotator getAnnotator(RefManager refManager, Object state) { + return new BooleanInvertedAnnotator(); + } + + @Override + @RequiredReadAction + public CommonProblemDescriptor[] checkElement( + RefEntity refEntity, + AnalysisScope scope, + InspectionManager manager, + GlobalInspectionContext globalContext, + Object state + ) { + if (refEntity instanceof RefMethod refMethod) { + if (!refMethod.isReferenced() || hasNonInvertedCalls(refMethod) || !refMethod.getSuperMethods().isEmpty()) { + return null; + } + PsiMethod psiMethod = (PsiMethod) refMethod.getElement(); + PsiIdentifier psiIdentifier = psiMethod.getNameIdentifier(); + if (psiIdentifier != null) { + return new ProblemDescriptor[]{ + manager.newProblemDescriptor(InspectionLocalize.booleanMethodIsAlwaysInvertedProblemDescriptor()) + .range(psiIdentifier) + .withFix(new InvertMethodFix()) + .create() + }; } - }; - traverseSuperMethods(refMethod, context, usagesProcessor); } - } - }); - return false; - } - - private static void traverseSuperMethods(RefMethod refMethod, - GlobalJavaInspectionContext globalContext, - GlobalJavaInspectionContext.UsagesProcessor processor) { - final Collection superMethods = refMethod.getSuperMethods(); - for (RefMethod superMethod : superMethods) { - traverseSuperMethods(superMethod, globalContext, processor); + return null; } - globalContext.enqueueMethodUsagesProcessor(refMethod, processor); - } - - private static void checkMethodCall(RefElement refWhat, final PsiElement element) { - if (!(refWhat instanceof RefMethod)) return; - final RefMethod refMethod = (RefMethod) refWhat; - final PsiElement psiElement = refMethod.getElement(); - if (!(psiElement instanceof PsiMethod)) return; - final PsiMethod psiMethod = (PsiMethod) psiElement; - if (!PsiType.BOOLEAN.equals(psiMethod.getReturnType())) return; - element.accept(new JavaRecursiveElementVisitor() { - @Override - public void visitMethodCallExpression(PsiMethodCallExpression call) { - super.visitMethodCallExpression(call); - final PsiReferenceExpression methodExpression = call.getMethodExpression(); - if (methodExpression.isReferenceTo(psiMethod)) { - if (isInvertedMethodCall(methodExpression)) return; - refMethod.putUserData(ALWAYS_INVERTED, Boolean.FALSE); + + private static boolean hasNonInvertedCalls(RefMethod refMethod) { + Boolean alwaysInverted = refMethod.getUserData(ALWAYS_INVERTED); + if (alwaysInverted == null || refMethod.isExternalOverride() || (refMethod.isReferenced() && !alwaysInverted)) { + return true; + } + Collection superMethods = refMethod.getSuperMethods(); + for (RefMethod superMethod : superMethods) { + if (hasNonInvertedCalls(superMethod)) { + return true; + } } - } - }); - } - - private static boolean isInvertedMethodCall(final PsiReferenceExpression methodExpression) { - final PsiPrefixExpression prefixExpression = PsiTreeUtil.getParentOfType(methodExpression, PsiPrefixExpression.class); - if (methodExpression.getQualifierExpression() instanceof PsiSuperExpression) return true; //don't flag super calls - if (prefixExpression != null) { - final IElementType tokenType = prefixExpression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.EXCL)) { - return true; - } + return false; } - return false; - } - private static class BooleanInvertedAnnotator extends RefGraphAnnotator { @Override - public void onInitialize(RefElement refElement) { - if (refElement instanceof RefMethod) { - final PsiElement element = refElement.getElement(); - if (!(element instanceof PsiMethod)) return; - if (!PsiType.BOOLEAN.equals(((PsiMethod) element).getReturnType())) return; - refElement.putUserData(ALWAYS_INVERTED, Boolean.TRUE); //initial mark boolean methods - } + protected boolean queryExternalUsagesRequests( + RefManager manager, + final GlobalJavaInspectionContext context, + final ProblemDescriptionsProcessor descriptionsProcessor, + Object state + ) { + manager.iterate(new RefJavaVisitor() { + @Override + @RequiredReadAction + public void visitMethod(RefMethod refMethod) { + if (descriptionsProcessor.getDescriptions(refMethod) != null) { //suspicious method -> need to check external usages + GlobalJavaInspectionContext.UsagesProcessor usagesProcessor = psiReference -> { + PsiElement psiReferenceExpression = psiReference.getElement(); + if (psiReferenceExpression instanceof PsiReferenceExpression referenceExpression + && !isInvertedMethodCall(referenceExpression)) { + descriptionsProcessor.ignoreElement(refMethod); + } + return false; + }; + traverseSuperMethods(refMethod, context, usagesProcessor); + } + } + }); + return false; } - @Override - public void onMarkReferenced(RefElement refWhat, RefElement refFrom, boolean referencedFromClassInitializer) { - checkMethodCall(refWhat, refFrom.getElement()); + private static void traverseSuperMethods( + RefMethod refMethod, + GlobalJavaInspectionContext globalContext, + GlobalJavaInspectionContext.UsagesProcessor processor + ) { + Collection superMethods = refMethod.getSuperMethods(); + for (RefMethod superMethod : superMethods) { + traverseSuperMethods(superMethod, globalContext, processor); + } + globalContext.enqueueMethodUsagesProcessor(refMethod, processor); } - } - @Override - public QuickFix getQuickFix(final String hint) { - return new InvertMethodFix(); - } + private static void checkMethodCall(RefElement refWhat, PsiElement element) { + if (refWhat instanceof RefMethod refMethod + && refMethod.getElement() instanceof PsiMethod method + && PsiType.BOOLEAN.equals(method.getReturnType())) { + element.accept(new JavaRecursiveElementVisitor() { + @Override + @RequiredReadAction + public void visitMethodCallExpression(PsiMethodCallExpression call) { + super.visitMethodCallExpression(call); + PsiReferenceExpression methodExpression = call.getMethodExpression(); + if (methodExpression.isReferenceTo(method)) { + if (isInvertedMethodCall(methodExpression)) { + return; + } + refMethod.putUserData(ALWAYS_INVERTED, Boolean.FALSE); + } + } + }); + } + } - private static class InvertMethodFix implements LocalQuickFix { + private static boolean isInvertedMethodCall(PsiReferenceExpression methodExpression) { + PsiPrefixExpression prefixExpression = PsiTreeUtil.getParentOfType(methodExpression, PsiPrefixExpression.class); + if (methodExpression.getQualifierExpression() instanceof PsiSuperExpression) { + return true; //don't flag super calls + } + if (prefixExpression != null) { + IElementType tokenType = prefixExpression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.EXCL)) { + return true; + } + } + return false; + } - @Override - @Nonnull - public String getName() { - return "Invert method"; + private static class BooleanInvertedAnnotator extends RefGraphAnnotator { + @Override + public void onInitialize(RefElement refElement) { + if (refElement instanceof RefMethod refMethod + && refMethod.getElement() instanceof PsiMethod method + && PsiType.BOOLEAN.equals(method.getReturnType())) { + refElement.putUserData(ALWAYS_INVERTED, Boolean.TRUE); //initial mark boolean methods + } + } + + @Override + public void onMarkReferenced(RefElement refWhat, RefElement refFrom, boolean referencedFromClassInitializer) { + checkMethodCall(refWhat, refFrom.getElement()); + } } @Override - @Nonnull - public String getFamilyName() { - return getName(); + public QuickFix getQuickFix(String hint) { + return new InvertMethodFix(); } - @Override - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); - assert psiMethod != null; - final RefactoringActionHandler invertBooleanHandler = JavaRefactoringActionHandlerFactory.getInstance().createInvertBooleanHandler(); - final Runnable runnable = new Runnable() { + private static class InvertMethodFix implements LocalQuickFix { @Override - public void run() { - invertBooleanHandler.invoke(project, new PsiElement[]{psiMethod}, DataManager.getInstance().getDataContext()); + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Invert method"); + } + + @Override + @RequiredReadAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + PsiMethod psiMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); + assert psiMethod != null; + RefactoringActionHandler invertBooleanHandler = JavaRefactoringActionHandlerFactory.getInstance().createInvertBooleanHandler(); + Runnable runnable = + () -> invertBooleanHandler.invoke(project, new PsiElement[]{psiMethod}, DataManager.getInstance().getDataContext()); + if (project.getApplication().isUnitTestMode()) { + runnable.run(); + } + else { + project.getApplication().invokeLater(runnable, project.getDisposed()); + } } - }; - if (ApplicationManager.getApplication().isUnitTestMode()) { - runnable.run(); - } else { - ApplicationManager.getApplication().invokeLater(runnable, project.getDisposed()); - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Analysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Analysis.java index 7202c36906..59d9886647 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Analysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Analysis.java @@ -25,7 +25,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -97,7 +96,6 @@ static boolean stateEquiv(State curr, State prev) return true; } - @Nonnull protected abstract Equation analyze() throws AnalyzerException; final Frame createStartFrame() @@ -137,7 +135,6 @@ final Frame createStartFrame() return frame; } - @Nonnull static Frame createCatchFrame(Frame frame) { Frame catchFrame = new Frame<>(frame); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java index 6316de8e62..b9b268a556 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisConverter.java @@ -7,8 +7,7 @@ import consulo.util.io.DigestUtil; import consulo.util.lang.ThreadLocalCachedValue; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Map; @@ -23,14 +22,13 @@ */ public class BytecodeAnalysisConverter { private static final ThreadLocalCachedValue DIGEST_CACHE = new ThreadLocalCachedValue() { - @Nonnull @Override public MessageDigest create() { return DigestUtil.md5(); } @Override - protected void init(@Nonnull MessageDigest value) { + protected void init(MessageDigest value) { value.reset(); } }; @@ -44,7 +42,7 @@ public static MessageDigest getMessageDigest() { * Returns null if conversion is impossible (something is not resolvable). */ @Nullable - public static EKey psiKey(@Nonnull PsiMember psiMethod, @Nonnull Direction direction) { + public static EKey psiKey(PsiMember psiMethod, Direction direction) { PsiClass psiClass = psiMethod.getContainingClass(); if (psiClass != null) { String className = descriptor(psiClass, 0, false); @@ -68,7 +66,7 @@ public static EKey psiKey(@Nonnull PsiMember psiMethod, @Nonnull Direction direc } @Nullable - private static String methodSignature(@Nonnull PsiMethod psiMethod, @Nonnull PsiClass psiClass) { + private static String methodSignature(PsiMethod psiMethod, PsiClass psiClass) { StringBuilder sb = new StringBuilder(); sb.append('('); @@ -106,7 +104,7 @@ private static String methodSignature(@Nonnull PsiMethod psiMethod, @Nonnull Psi } @Nullable - private static String descriptor(@Nonnull PsiClass psiClass, int dimensions, boolean full) { + private static String descriptor(PsiClass psiClass, int dimensions, boolean full) { PsiFile containingFile = psiClass.getContainingFile(); if (!(containingFile instanceof PsiClassOwner)) { LOG.debug("containingFile was not resolved for " + psiClass.getQualifiedName()); @@ -143,7 +141,7 @@ private static String descriptor(@Nonnull PsiClass psiClass, int dimensions, boo } @Nullable - private static String descriptor(@Nonnull PsiType psiType) { + private static String descriptor(PsiType psiType) { int dimensions = 0; psiType = TypeConversionUtil.erasure(psiType); if (psiType instanceof PsiArrayType) { @@ -197,8 +195,7 @@ private static String descriptor(@Nonnull PsiType psiType) { * @param primaryKey primary stable keys * @return corresponding (stable!) keys */ - @Nonnull - public static ArrayList mkInOutKeys(@Nonnull PsiMethod psiMethod, @Nonnull EKey primaryKey) { + public static ArrayList mkInOutKeys(PsiMethod psiMethod, EKey primaryKey) { PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); ArrayList keys = new ArrayList<>(parameters.length * 2 + 2); keys.add(primaryKey); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java index 42a94fa38f..edb4d5996c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/BytecodeAnalysisIndex.java @@ -18,7 +18,6 @@ import consulo.util.lang.Pair; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -34,13 +33,11 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension { static final ID NAME = ID.create("bytecodeAnalysis"); - @Nonnull @Override public ID getName() { return NAME; } - @Nonnull @Override public DataIndexer getIndexer() { return inputData -> { @@ -57,7 +54,6 @@ public DataIndexer getIndexer() { }; } - @Nonnull private static Map collectKeys(byte[] content) { HashMap map = new HashMap<>(); MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); @@ -81,7 +77,6 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si return map; } - @Nonnull @Override public KeyDescriptor getKeyDescriptor() { return HKeyDescriptor.INSTANCE; @@ -92,7 +87,6 @@ public boolean hasSnapshotMapping() { return true; } - @Nonnull @Override public FileBasedIndex.InputFilter getInputFilter() { return new DefaultFileTypeSpecificInputFilter(JavaClassFileType.INSTANCE); @@ -115,12 +109,12 @@ private static class HKeyDescriptor implements KeyDescriptor, Different static final HKeyDescriptor INSTANCE = new HKeyDescriptor(); @Override - public void save(@Nonnull DataOutput out, HMember value) throws IOException { + public void save(DataOutput out, HMember value) throws IOException { out.write(value.asBytes()); } @Override - public HMember read(@Nonnull DataInput in) throws IOException { + public HMember read(DataInput in) throws IOException { byte[] bytes = new byte[HMember.HASH_SIZE]; in.readFully(bytes); return new HMember(bytes); @@ -132,7 +126,7 @@ public HMember read(@Nonnull DataInput in) throws IOException { */ public static class EquationsExternalizer implements DataExternalizer> { @Override - public void save(@Nonnull DataOutput out, Map value) throws IOException { + public void save(DataOutput out, Map value) throws IOException { DataInputOutputUtil.writeSeq(out, value.entrySet(), entry -> { HKeyDescriptor.INSTANCE.save(out, entry.getKey()); saveEquations(out, entry.getValue()); @@ -140,12 +134,12 @@ public void save(@Nonnull DataOutput out, Map value) throws } @Override - public Map read(@Nonnull DataInput in) throws IOException { + public Map read(DataInput in) throws IOException { return StreamEx.of(DataInputOutputUtil.readSeq(in, () -> Pair.create(HKeyDescriptor.INSTANCE.read(in), readEquations(in)))). toMap(p -> p.getFirst(), p -> p.getSecond(), ClassDataIndexer.MERGER); } - private static void saveEquations(@Nonnull DataOutput out, Equations eqs) throws IOException { + private static void saveEquations(DataOutput out, Equations eqs) throws IOException { out.writeBoolean(eqs.stable); MessageDigest md = BytecodeAnalysisConverter.getMessageDigest(); DataInputOutputUtil.writeINT(out, eqs.results.size()); @@ -180,7 +174,7 @@ private static void saveEquations(@Nonnull DataOutput out, Equations eqs) throws } } - private static Equations readEquations(@Nonnull DataInput in) throws IOException { + private static Equations readEquations(DataInput in) throws IOException { boolean stable = in.readBoolean(); int size = DataInputOutputUtil.readINT(in); ArrayList results = new ArrayList<>(size); @@ -222,22 +216,21 @@ private static Equations readEquations(@Nonnull DataInput in) throws IOException return new Equations(results, stable); } - @Nonnull - private static EKey readKey(@Nonnull DataInput in) throws IOException { + private static EKey readKey(DataInput in) throws IOException { byte[] bytes = new byte[HMember.HASH_SIZE]; in.readFully(bytes); int rawDirKey = DataInputOutputUtil.readINT(in); return new EKey(new HMember(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0); } - private static void writeKey(@Nonnull DataOutput out, EKey key, MessageDigest md) throws IOException { + private static void writeKey(DataOutput out, EKey key, MessageDigest md) throws IOException { out.write(key.member.hashed(md).asBytes()); int rawDirKey = key.negated ? -key.dirKey : key.dirKey; DataInputOutputUtil.writeINT(out, rawDirKey); out.writeBoolean(key.stable); } - private static void writeEffect(@Nonnull DataOutput out, EffectQuantum effect, MessageDigest md) throws IOException { + private static void writeEffect(DataOutput out, EffectQuantum effect, MessageDigest md) throws IOException { if (effect == EffectQuantum.TopEffectQuantum) { DataInputOutputUtil.writeINT(out, -1); } else if (effect == EffectQuantum.ThisChangeQuantum) { @@ -262,7 +255,7 @@ private static void writeEffect(@Nonnull DataOutput out, EffectQuantum effect, M } } - private static EffectQuantum readEffect(@Nonnull DataInput in) throws IOException { + private static EffectQuantum readEffect(DataInput in) throws IOException { int effectMask = DataInputOutputUtil.readINT(in); switch (effectMask) { case -1: @@ -287,7 +280,7 @@ private static EffectQuantum readEffect(@Nonnull DataInput in) throws IOExceptio } } - private static void writeDataValue(@Nonnull DataOutput out, DataValue dataValue, MessageDigest md) throws IOException { + private static void writeDataValue(DataOutput out, DataValue dataValue, MessageDigest md) throws IOException { if (dataValue == DataValue.ThisDataValue) { DataInputOutputUtil.writeINT(out, -1); } else if (dataValue == DataValue.LocalDataValue) { @@ -306,7 +299,7 @@ private static void writeDataValue(@Nonnull DataOutput out, DataValue dataValue, } } - private static DataValue readDataValue(@Nonnull DataInput in) throws IOException { + private static DataValue readDataValue(DataInput in) throws IOException { int dataI = DataInputOutputUtil.readINT(in); switch (dataI) { case -1: diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ClassDataIndexer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ClassDataIndexer.java index 77738299ce..7e3f717f9f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ClassDataIndexer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ClassDataIndexer.java @@ -18,8 +18,7 @@ import one.util.streamex.EntryStream; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -60,7 +59,7 @@ public class ClassDataIndexer implements BiFunction apply(Project project, @Nonnull VirtualFile file) { + public Map apply(Project project, VirtualFile file) { HashMap map = new HashMap<>(); if (isFileExcluded(file)) { return map; @@ -150,7 +149,6 @@ private static EffectQuantum hash(EffectQuantum effect, MessageDigest md) { return effect; } - @Nonnull private static Equations convertEquations(EKey methodKey, List rawMethodEquations) { List compressedMethodEquations = ContainerUtil.map(rawMethodEquations, equation -> new DirectionResultPair(equation.key.dirKey, equation.result)); @@ -215,7 +213,6 @@ public FieldVisitor visitField(int access, String name, String desc, String sign return staticFields; } - @Nonnull static List getEquations(GlobalSearchScope scope, HMember key) { return ContainerUtil.mapNotNull(FileBasedIndex.getInstance().getContainingFiles(BytecodeAnalysisIndex.NAME, key, scope), file -> ourGist.getFileData(null, file).get(key)); @@ -619,7 +616,6 @@ private static List topEquations(Member method, return result; } - @Nonnull private static LeakingParameters leakingParametersAndFrames(Member method, MethodNode methodNode, Type[] argumentTypes, boolean jsr) throws AnalyzerException { return argumentTypes.length < 32 ? diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedAnalysis.java index 51e7c480ec..3c65c1ab2a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedAnalysis.java @@ -26,7 +26,7 @@ import consulo.internal.org.objectweb.asm.tree.analysis.Frame; import one.util.streamex.EntryStream; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.List; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedData.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedData.java index 23b4c82a67..6a13fb61d7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedData.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedData.java @@ -3,7 +3,6 @@ import consulo.internal.org.objectweb.asm.Type; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.List; @@ -99,7 +98,6 @@ public int getOriginInsnIndex() return originInsnIndex; } - @Nonnull Set getKeysForParameter(int idx, ParamValueBasedDirection direction) { Set keys = new HashSet<>(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedInterpreter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedInterpreter.java index dad3609a05..d91771a5a5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedInterpreter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CombinedInterpreter.java @@ -25,7 +25,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.BasicInterpreter; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -285,7 +284,6 @@ public BasicValue naryOperation(AbstractInsnNode insn, List values) { Type retType = Type.getReturnType(method.methodDesc); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Component.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Component.java index b2e5fc85f3..9daacd4340 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Component.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Component.java @@ -1,7 +1,6 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Set; @@ -12,17 +11,15 @@ final class Component { static final Component[] EMPTY_ARRAY = new Component[0]; - @Nonnull Value value; - @Nonnull final EKey[] ids; - Component(@Nonnull Value value, @Nonnull Set ids) + Component(Value value, Set ids) { this(value, ids.toArray(new EKey[0])); } - Component(@Nonnull Value value, @Nonnull EKey[] ids) + Component(Value value, EKey[] ids) { this.value = value; this.ids = ids; @@ -47,7 +44,7 @@ public int hashCode() return 31 * value.hashCode() + Arrays.hashCode(ids); } - public boolean remove(@Nonnull EKey id) + public boolean remove(EKey id) { boolean removed = false; for(int i = 0; i < ids.length; i++) @@ -71,7 +68,6 @@ public boolean isEmpty() return true; } - @Nonnull public Component copy() { return new Component(value, ids.clone()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ContractAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ContractAnalysis.java index a8bcfa71cb..a13b93ad1e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ContractAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ContractAnalysis.java @@ -10,7 +10,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; @@ -41,7 +40,6 @@ protected ContractAnalysis(RichControlFlow richControlFlow, Direction direction, internalResult = Value.Bot; } - @Nonnull Equation mkEquation(Result res) { return new Equation(aKey, res); @@ -61,7 +59,6 @@ static Result checkLimit(Result result) throws AnalyzerException } @Override - @Nonnull protected Equation analyze() throws AnalyzerException { pendingPush(createStartState()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CoreHKey.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CoreHKey.java index 44bda6fb1e..5697c60162 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CoreHKey.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/CoreHKey.java @@ -16,16 +16,14 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; final class CoreHKey { final - @Nonnull MemberDescriptor myMethod; final int dirKey; - CoreHKey(@Nonnull MemberDescriptor method, int dirKey) + CoreHKey(MemberDescriptor method, int dirKey) { this.myMethod = method; this.dirKey = dirKey; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DataInterpreter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DataInterpreter.java index fec4f2f2f4..3eaac3ecc8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DataInterpreter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DataInterpreter.java @@ -22,8 +22,8 @@ import consulo.internal.org.objectweb.asm.Type; import consulo.internal.org.objectweb.asm.tree.*; import consulo.internal.org.objectweb.asm.tree.analysis.Interpreter; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; class DataInterpreter extends Interpreter diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Direction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Direction.java index 1e1cc8d536..786738cebc 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Direction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Direction.java @@ -1,7 +1,6 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.List; @@ -27,7 +26,6 @@ public abstract class Direction * @return Direction object * @see #asInt() */ - @Nonnull static Direction fromInt(int directionKey) { if(directionKey < CONCRETE_DIRECTIONS_OFFSET) @@ -74,7 +72,6 @@ public boolean equals(Object obj) return asInt() == ((Direction) obj).asInt(); } - @Nonnull private static Direction explicitDirection(String name) { return new Direction() diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DirectionResultPair.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DirectionResultPair.java index 10bb158ed6..126917fb62 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DirectionResultPair.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/DirectionResultPair.java @@ -1,14 +1,12 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; class DirectionResultPair { final int directionKey; - @Nonnull final Result result; - DirectionResultPair(int directionKey, @Nonnull Result result) { + DirectionResultPair(int directionKey, Result result) { this.directionKey = directionKey; this.result = result; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/EKey.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/EKey.java index 4d54b7f021..668b0e41b1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/EKey.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/EKey.java @@ -1,7 +1,6 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.security.MessageDigest; @@ -11,23 +10,22 @@ public final class EKey { final - @Nonnull MemberDescriptor member; final int dirKey; final boolean stable; final boolean negated; - public EKey(@Nonnull MemberDescriptor member, Direction direction, boolean stable) + public EKey(MemberDescriptor member, Direction direction, boolean stable) { this(member, direction, stable, false); } - EKey(@Nonnull MemberDescriptor member, Direction direction, boolean stable, boolean negated) + EKey(MemberDescriptor member, Direction direction, boolean stable, boolean negated) { this(member, direction.asInt(), stable, negated); } - EKey(@Nonnull MemberDescriptor member, int dirKey, boolean stable, boolean negated) + EKey(MemberDescriptor member, int dirKey, boolean stable, boolean negated) { this.member = member; this.dirKey = dirKey; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Effects.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Effects.java index 197e8bd61a..747a1a2cb9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Effects.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Effects.java @@ -16,7 +16,6 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; @@ -28,12 +27,10 @@ final class Effects implements Result static final Set TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum); static final Effects VOLATILE_EFFECTS = new Effects(DataValue.UnknownDataValue2, Collections.singleton(EffectQuantum.TopEffectQuantum)); - @Nonnull final DataValue returnValue; - @Nonnull final Set effects; - Effects(@Nonnull DataValue returnValue, @Nonnull Set effects) + Effects(DataValue returnValue, Set effects) { this.returnValue = returnValue; this.effects = effects; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equation.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equation.java index ce3bb4d39d..ef6526cf49 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equation.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equation.java @@ -16,16 +16,13 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; final class Equation { - @Nonnull final EKey key; - @Nonnull final Result result; - Equation(@Nonnull EKey key, @Nonnull Result result) + Equation(EKey key, Result result) { this.key = key; this.result = result; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equations.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equations.java index 8c76ae0f4e..4a5f733e1d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equations.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Equations.java @@ -2,16 +2,14 @@ import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.util.List; import java.util.Optional; class Equations { - @Nonnull final List results; final boolean stable; - Equations(@Nonnull List results, boolean stable) { + Equations(List results, boolean stable) { this.results = results; this.stable = stable; } @@ -32,7 +30,6 @@ public int hashCode() { return 31 * results.hashCode() + (stable ? 1 : 0); } - @Nonnull Equations update(@SuppressWarnings("SameParameterValue") Direction direction, Effects newResult) { List newPairs = StreamEx.of(this.results) .map(drp -> drp.updateForDirection(direction, newResult)) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/HMember.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/HMember.java index 69c6181df2..10e2369768 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/HMember.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/HMember.java @@ -2,7 +2,6 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; import one.util.streamex.IntStreamEx; -import javax.annotation.Nonnull; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -42,7 +41,7 @@ public final class HMember implements MemberDescriptor myMethod = ByteBuffer.wrap(sigDigest).getInt(); } - public HMember(@Nonnull byte[] bytes) + public HMember(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); myClassHi = buffer.getLong(); @@ -50,7 +49,6 @@ public HMember(@Nonnull byte[] bytes) myMethod = buffer.getInt(); } - @Nonnull byte[] asBytes() { ByteBuffer bytes = ByteBuffer.allocate(HASH_SIZE); @@ -83,7 +81,6 @@ public int hashCode() return result; } - @Nonnull @Override public HMember hashed(MessageDigest md) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java index a483d4acf6..624c0a5421 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/KeyedMethodVisitor.java @@ -6,7 +6,7 @@ import consulo.internal.org.objectweb.asm.Opcodes; import consulo.internal.org.objectweb.asm.tree.MethodNode; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis.Direction.Out; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Member.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Member.java index 3547479942..29a3f41728 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Member.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Member.java @@ -1,9 +1,8 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import consulo.internal.org.objectweb.asm.tree.MethodInsnNode; +import org.jspecify.annotations.Nullable; import java.security.MessageDigest; @@ -20,7 +19,7 @@ public final class Member implements MemberDescriptor * @param methodName method name * @param methodDesc method descriptor in asm format */ - public Member(@Nonnull String internalClassName, @Nonnull String methodName, @Nonnull String methodDesc) + public Member(String internalClassName, String methodName, String methodDesc) { this.internalClassName = internalClassName; this.methodName = methodName; @@ -59,7 +58,6 @@ public int hashCode() return result; } - @Nonnull @Override public HMember hashed(@Nullable MessageDigest md) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/MemberDescriptor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/MemberDescriptor.java index 8209357665..b812818e64 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/MemberDescriptor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/MemberDescriptor.java @@ -1,9 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.security.MessageDigest; /** @@ -19,6 +17,5 @@ public interface MemberDescriptor * @param md message digest to use for hashing (could be null to use the default one) * @return a corresponding HMethod. */ - @Nonnull HMember hashed(@Nullable MessageDigest md); } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NonNullInAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NonNullInAnalysis.java index 223d652e44..02ca6caa41 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NonNullInAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NonNullInAnalysis.java @@ -23,7 +23,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; -import javax.annotation.Nonnull; import java.util.List; import java.util.Set; @@ -64,7 +63,6 @@ PResult combineResults(PResult delta, int[] subResults) throws AnalyzerException return meet(delta, result); } - @Nonnull Equation mkEquation(PResult result) { if(Identity == result || Return == result) @@ -88,7 +86,6 @@ else if(NPE == result) private PResult subResult; @Override - @Nonnull protected Equation analyze() throws AnalyzerException { pendingPush(new ProceedState(createStartState())); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableInAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableInAnalysis.java index 8e3f69bdea..c08968840e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableInAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableInAnalysis.java @@ -23,7 +23,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; -import javax.annotation.Nonnull; import java.util.List; import java.util.Set; @@ -46,7 +45,6 @@ protected NullableInAnalysis(RichControlFlow richControlFlow, Direction directio this.pending = pending; } - @Nonnull Equation mkEquation(PResult result) { if(NPE == result) @@ -72,7 +70,6 @@ Equation mkEquation(PResult result) private boolean top; @Override - @Nonnull protected Equation analyze() throws AnalyzerException { pendingPush(createStartState()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java index 4bc8ada3b6..5f04946efa 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/NullableMethodAnalysis.java @@ -24,7 +24,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.BasicValue; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -75,7 +74,6 @@ static Result analyze(MethodNode methodNode, boolean[] origins, boolean jsr) thr return Value.Bot; } - @Nonnull private static int[] mapOrigins(boolean[] origins) { int[] originsMapping = new int[origins.length]; int mapped = 0; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Pending.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Pending.java index 578004ea1c..2d96d257e8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Pending.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Pending.java @@ -16,20 +16,18 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.util.Arrays; import java.util.Collection; import java.util.stream.Stream; final class Pending implements Result { - @Nonnull final Component[] delta; // sum Pending(Collection delta) { this(delta.toArray(Component.EMPTY_ARRAY)); } - Pending(@Nonnull Component[] delta) { + Pending(Component[] delta) { this.delta = delta; } @@ -47,7 +45,6 @@ public int hashCode() { return Arrays.hashCode(delta); } - @Nonnull Pending copy() { Component[] copy = new Component[delta.length]; for (int i = 0; i < delta.length; i++) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java index 3a440e4194..f002272e8f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ProjectBytecodeAnalysis.java @@ -19,7 +19,6 @@ import consulo.application.util.ConcurrentFactoryMap; import consulo.application.util.registry.Registry; import consulo.component.util.ModificationTracker; -import consulo.ide.ServiceManager; import consulo.internal.org.objectweb.asm.ClassReader; import consulo.language.file.light.LightVirtualFile; import consulo.language.psi.*; @@ -39,8 +38,7 @@ import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; import java.security.MessageDigest; @@ -73,8 +71,8 @@ public class ProjectBytecodeAnalysis { private final EquationProvider myEquationProvider; private final NullableNotNullManager myNullabilityManager; - public static ProjectBytecodeAnalysis getInstance(@Nonnull Project project) { - return ServiceManager.getService(project, ProjectBytecodeAnalysis.class); + public static ProjectBytecodeAnalysis getInstance(Project project) { + return project.getInstance(ProjectBytecodeAnalysis.class); } @Inject @@ -87,7 +85,7 @@ public ProjectBytecodeAnalysis(Project project) { } @Nullable - public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { + public PsiAnnotation findInferredAnnotation(PsiModifierListOwner listOwner, String annotationFQN) { if (!(listOwner instanceof PsiCompiledElement)) { return null; } @@ -104,8 +102,7 @@ public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOw return null; } - @Nonnull - public PsiAnnotation[] findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { + public PsiAnnotation[] findInferredAnnotations(PsiModifierListOwner listOwner) { if (!(listOwner instanceof PsiCompiledElement)) { return PsiAnnotation.EMPTY_ARRAY; } @@ -114,7 +111,6 @@ public PsiAnnotation[] findInferredAnnotations(@Nonnull PsiModifierListOwner lis listOwner)); } - @Nonnull private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) { PsiFile psiFile = listOwner.getContainingFile(); VirtualFile file = psiFile == null ? null : psiFile.getVirtualFile(); @@ -164,7 +160,6 @@ else if (listOwner instanceof PsiField && listOwner.hasModifierProperty(PsiModif * @param methodAnnotations inferred annotations * @return Psi annotations */ - @Nonnull private PsiAnnotation[] toPsi(EKey primaryKey, MethodAnnotations methodAnnotations) { boolean notNull = methodAnnotations.notNulls.contains(primaryKey); boolean nullable = methodAnnotations.nullables.contains(primaryKey); @@ -211,7 +206,6 @@ else if (pure) { * @param parameterAnnotations inferred parameter annotations * @return Psi annotations */ - @Nonnull private PsiAnnotation[] toPsi(ParameterAnnotations parameterAnnotations) { if (parameterAnnotations.notNull) { return new PsiAnnotation[]{getNotNullAnnotation()}; @@ -242,7 +236,7 @@ public PsiAnnotation createContractAnnotation(String contractValue) { } @Nullable - public EKey getKey(@Nonnull PsiModifierListOwner owner, MessageDigest md) { + public EKey getKey(PsiModifierListOwner owner, MessageDigest md) { LOG.assertTrue(owner instanceof PsiCompiledElement, owner); EKey key = null; if (owner instanceof PsiMethod) { @@ -271,11 +265,11 @@ else if (owner instanceof PsiParameter) { * @param primaryKey primary compressed key for this method * @return compressed keys for this method */ - public static List collectMethodKeys(@Nonnull PsiMethod method, EKey primaryKey) { + public static List collectMethodKeys(PsiMethod method, EKey primaryKey) { return BytecodeAnalysisConverter.mkInOutKeys(method, primaryKey); } - private ParameterAnnotations loadParameterAnnotations(@Nonnull EKey notNullKey) throws EquationsLimitException { + private ParameterAnnotations loadParameterAnnotations(EKey notNullKey) throws EquationsLimitException { Solver notNullSolver = new Solver(new ELattice<>(Value.NotNull, Value.Top), Value.Top); collectEquations(Collections.singletonList(notNullKey), notNullSolver); Map notNullSolutions = notNullSolver.solve(); @@ -294,8 +288,8 @@ private ParameterAnnotations loadParameterAnnotations(@Nonnull EKey notNullKey) return new ParameterAnnotations(notNull, nullable); } - private MethodAnnotations loadMethodAnnotations(@Nonnull PsiMethod owner, - @Nonnull EKey key, + private MethodAnnotations loadMethodAnnotations(PsiMethod owner, + EKey key, List allKeys) throws EquationsLimitException { MethodAnnotations result = new MethodAnnotations(); @@ -406,14 +400,13 @@ private void collectSingleEquation(EKey curKey, Solver solver) { } } - @Nonnull - private PsiAnnotation createAnnotationFromText(@Nonnull String text) throws IncorrectOperationException { + private PsiAnnotation createAnnotationFromText(String text) throws IncorrectOperationException { PsiAnnotation annotation = JavaPsiFacade.getElementFactory(myProject).createAnnotationFromText(text, null); ((LightVirtualFile)annotation.getContainingFile().getViewProvider().getVirtualFile()).setWritable(false); return annotation; } - BitSet findAlwaysNotNullParameters(@Nonnull EKey methodKey, BitSet possiblyNotNullParameters) throws EquationsLimitException { + BitSet findAlwaysNotNullParameters(EKey methodKey, BitSet possiblyNotNullParameters) throws EquationsLimitException { BitSet alwaysNotNullParameters = new BitSet(); if (possiblyNotNullParameters.cardinality() != 0) { List keys = IntStreamEx.of(possiblyNotNullParameters).mapToObj(idx -> methodKey.withDirection(new In(idx, false))).toList(); @@ -437,9 +430,9 @@ BitSet findAlwaysNotNullParameters(@Nonnull EKey methodKey, BitSet possiblyNotNu * @param methodKey a primary key of a method being analyzed. not it is stable * @param arity arity of this method (hint for constructing @Contract annotations) */ - private void addMethodAnnotations(@Nonnull Map solution, - @Nonnull MethodAnnotations methodAnnotations, - @Nonnull EKey methodKey, + private void addMethodAnnotations(Map solution, + MethodAnnotations methodAnnotations, + EKey methodKey, int arity) throws EquationsLimitException { List contractClauses = new ArrayList<>(); @@ -509,7 +502,7 @@ else if (direction instanceof ParamValueBasedDirection) { } } - private void removeConstraintFromNonNullParameter(@Nonnull EKey methodKey, + private void removeConstraintFromNonNullParameter(EKey methodKey, List allContracts) throws EquationsLimitException { BitSet possiblyNotNullParameters = StreamEx.of(allContracts) .flatMapToInt( @@ -526,7 +519,6 @@ private void removeConstraintFromNonNullParameter(@Nonnull EKey methodKey, } } - @Nonnull private static List squashContracts(List contractClauses) { // If there's a pair of contracts yielding the same value like "null,_->true", "!null,_->true" // then trivial contract should be used like "_,_->true" @@ -571,7 +563,7 @@ static abstract class EquationProvider { project.getMessageBus().connect().subscribe(PsiModificationTrackerListener.class, myEquationCache::clear); } - abstract EKey adaptKey(@Nonnull EKey key, MessageDigest messageDigest); + abstract EKey adaptKey(EKey key, MessageDigest messageDigest); abstract List getEquations(MemberDescriptor method); } @@ -586,7 +578,7 @@ static class PlainEquationProvider extends EquationProvider { } @Override - public EKey adaptKey(@Nonnull EKey key, MessageDigest messageDigest) { + public EKey adaptKey(EKey key, MessageDigest messageDigest) { assert key.member instanceof Member; return key; } @@ -653,7 +645,7 @@ static class IndexedEquationProvider extends EquationProvider { } @Override - public EKey adaptKey(@Nonnull EKey key, MessageDigest messageDigest) { + public EKey adaptKey(EKey key, MessageDigest messageDigest) { return key.hashed(messageDigest); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/PurityAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/PurityAnalysis.java index 57774d1b13..d689263f55 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/PurityAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/PurityAnalysis.java @@ -19,8 +19,8 @@ import consulo.internal.org.objectweb.asm.tree.MethodNode; import consulo.internal.org.objectweb.asm.tree.analysis.Analyzer; import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ResultUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ResultUtil.java index 5860168dea..c3c362a15c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ResultUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/ResultUtil.java @@ -17,9 +17,8 @@ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; import consulo.util.collection.ArrayUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -85,7 +84,6 @@ private Result checkFinal(Result r1, Result r2) return null; } - @Nonnull private Result addSingle(Pending pending, Value value) { for(int i = 0; i < pending.delta.length; i++) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Solver.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Solver.java index 656db959f8..50939b354a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Solver.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/Solver.java @@ -15,7 +15,6 @@ */ package com.intellij.java.analysis.impl.codeInspection.bytecodeAnalysis; -import javax.annotation.Nonnull; import java.util.HashMap; import java.util.HashSet; @@ -188,7 +187,7 @@ Map solve() } // substitute id -> value into pending - Result substitute(@Nonnull Pending pending, @Nonnull EKey id, @Nonnull Value value) + Result substitute(Pending pending, EKey id, Value value) { Component[] sum = pending.delta; for(Component intIdComponent : sum) @@ -201,8 +200,7 @@ Result substitute(@Nonnull Pending pending, @Nonnull EKey id, @Nonnull Value val return normalize(sum); } - @Nonnull - Result normalize(@Nonnull Component[] sum) + Result normalize(Component[] sum) { Value acc = lattice.bot; boolean computableNow = true; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java index 182ac965fa..3797077bf6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/FramelessAnalyzer.java @@ -4,8 +4,8 @@ import consulo.internal.org.objectweb.asm.tree.*; import consulo.internal.org.objectweb.asm.tree.analysis.Analyzer; import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; /** diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java index 9158893a62..4ef0d61d73 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/LeakingParameters.java @@ -8,7 +8,6 @@ import consulo.internal.org.objectweb.asm.tree.analysis.AnalyzerException; import consulo.internal.org.objectweb.asm.tree.analysis.Frame; import consulo.internal.org.objectweb.asm.tree.analysis.Value; -import javax.annotation.Nonnull; /** * @author lambdamix @@ -26,7 +25,6 @@ public LeakingParameters(Frame[] frames, boolean[] parameters, this.nullableParameters = nullableParameters; } - @Nonnull public static LeakingParameters build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { Frame[] frames = jsr ? new Analyzer<>(new ParametersUsage(methodNode)).analyze(className, methodNode) @@ -59,7 +57,6 @@ public static LeakingParameters build(String className, MethodNode methodNode, b return new LeakingParameters(frames, notNullParameters, nullableParameters); } - @Nonnull public static LeakingParameters buildFast(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException { IParametersUsage parametersUsage = new IParametersUsage(methodNode); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/OriginsAnalysis.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/OriginsAnalysis.java index 33eb2a45b6..7220dd5bfb 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/OriginsAnalysis.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/OriginsAnalysis.java @@ -8,8 +8,7 @@ import consulo.util.collection.primitive.ints.IntList; import consulo.util.collection.primitive.ints.IntLists; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.HashSet; import java.util.LinkedList; @@ -105,7 +104,6 @@ public int hashCode() * @return array, array[i] == true means that the result of a method execution may originate at an i-th instruction * @throws AnalyzerException */ - @Nonnull public static boolean[] resultOrigins(Frame[] frames, InsnList instructions, ControlFlowGraph graph) throws AnalyzerException { @@ -211,8 +209,7 @@ private static Location previousLocation(Frame frame, Location return null; } - @Nonnull - private static Frame makePreFrame(@Nonnull Frame frame) + private static Frame makePreFrame(Frame frame) { Frame preFrame = new Frame<>(frame.getLocals(), frame.getMaxStackSize()); for(int i = 0; i < frame.getLocals(); i++) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/ParamsValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/ParamsValue.java index e8237ec449..f846790e75 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/ParamsValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/ParamsValue.java @@ -18,17 +18,15 @@ import java.util.Arrays; -import javax.annotation.Nonnull; import consulo.internal.org.objectweb.asm.tree.analysis.Value; final class ParamsValue implements Value { - @Nonnull final boolean[] params; final int size; - ParamsValue(@Nonnull boolean[] params, int size) { + ParamsValue(boolean[] params, int size) { this.params = params; this.size = size; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/Subroutine.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/Subroutine.java index 0b6e2df791..37291b7049 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/Subroutine.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/bytecodeAnalysis/asm/Subroutine.java @@ -3,8 +3,8 @@ import consulo.internal.org.objectweb.asm.tree.JumpInsnNode; import consulo.internal.org.objectweb.asm.tree.LabelNode; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java index b1b880cda6..3278fcb9f6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java @@ -26,14 +26,16 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class CanBeFinalHandler { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(CanBeFinalHandler.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(CanBeFinalHandler.class); - public abstract boolean canBeFinal(PsiMember member); + public abstract boolean canBeFinal(PsiMember member); - public static boolean allowToBeFinal(PsiMember member) { - for (CanBeFinalHandler handler : EP_NAME.getExtensionList()) { - if (!handler.canBeFinal(member)) return false; + public static boolean allowToBeFinal(PsiMember member) { + for (CanBeFinalHandler handler : EP_NAME.getExtensionList()) { + if (!handler.canBeFinal(member)) { + return false; + } + } + return true; } - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/concurrencyAnnotations/JCiPUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/concurrencyAnnotations/JCiPUtil.java index a6e4ac37b1..b76ed4dee8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/concurrencyAnnotations/JCiPUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/concurrencyAnnotations/JCiPUtil.java @@ -27,8 +27,8 @@ import consulo.language.psi.util.LanguageCachedValueUtil; import consulo.language.psi.util.PsiTreeUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.List; public class JCiPUtil { @@ -39,11 +39,11 @@ public static boolean isJCiPAnnotation(String ref) { private JCiPUtil() { } - public static boolean isImmutable(@Nonnull PsiClass aClass) { + public static boolean isImmutable(PsiClass aClass) { return isImmutable(aClass, true); } - public static boolean isImmutable(@Nonnull PsiClass aClass, boolean checkDocComment) { + public static boolean isImmutable(PsiClass aClass, boolean checkDocComment) { final PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, ConcurrencyAnnotationsManager.getInstance(aClass.getProject()).getImmutableAnnotations()); if (annotation != null) { return true; @@ -61,7 +61,7 @@ private static boolean containsImmutableWord(PsiFile file) { } @Nullable - public static String findGuardForMember(@Nonnull PsiMember member) { + public static String findGuardForMember(PsiMember member) { final PsiAnnotation annotation = AnnotationUtil.findAnnotation(member, ConcurrencyAnnotationsManager.getInstance(member.getProject()).getGuardedByAnnotations()); if (annotation != null) { return getGuardValue(annotation); @@ -77,7 +77,7 @@ public static String findGuardForMember(@Nonnull PsiMember member) { return visitor.getGuardString(); } - public static boolean isGuardedBy(@Nonnull PsiMember member, @Nonnull String guard) { + public static boolean isGuardedBy(PsiMember member, String guard) { final List annotations = ConcurrencyAnnotationsManager.getInstance(member.getProject()).getGuardedByAnnotations(); final PsiAnnotation annotation = AnnotationUtil.findAnnotation(member, annotations); return annotation != null && guard.equals(getGuardValue(annotation)); @@ -87,7 +87,7 @@ public static boolean isGuardedBy(PsiMember member, PsiField field) { return isGuardedBy(member, field.getName()); } - public static boolean isGuardedByAnnotation(@Nonnull PsiAnnotation annotation) { + public static boolean isGuardedByAnnotation(PsiAnnotation annotation) { return ConcurrencyAnnotationsManager.getInstance(annotation.getProject()).getGuardedByAnnotations().contains(annotation.getQualifiedName()); } @@ -115,7 +115,6 @@ public static String getGuardValue(PsiAnnotation annotation) { return null; } - @Nonnull public static String getGuardValue(PsiDocTag tag) { final String text = tag.getText(); return text.substring(text.indexOf((int) '(') + 1, text.indexOf((int) ')')).trim(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CFGBuilder.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CFGBuilder.java index 65908ff5c8..a0336a1ff7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CFGBuilder.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CFGBuilder.java @@ -36,8 +36,7 @@ import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Consumer; @@ -96,7 +95,7 @@ public CFGBuilder pushNull() { * @param expression expression to evaluate * @return this builder */ - public CFGBuilder pushExpression(@Nonnull PsiExpression expression) { + public CFGBuilder pushExpression(PsiExpression expression) { expression.accept(myAnalyzer); return this; } @@ -114,7 +113,7 @@ public CFGBuilder pushExpression(@Nonnull PsiExpression expression) { * Passing {@code null} means no custom problem to register (just like {@link #pushExpression(PsiExpression)}). * @return this builder */ - public CFGBuilder pushExpression(@Nonnull PsiExpression expression, @Nullable NullabilityProblemKind kind) { + public CFGBuilder pushExpression(PsiExpression expression, @Nullable NullabilityProblemKind kind) { if (kind == null) { return pushExpression(expression); } @@ -134,7 +133,7 @@ public CFGBuilder pushExpression(@Nonnull PsiExpression expression, @Nullable Nu * @param descriptor a {@link SpecialField} which describes a field to get * @return this builder */ - public CFGBuilder unwrap(@Nonnull SpecialField descriptor) { + public CFGBuilder unwrap(SpecialField descriptor) { return add(new UnwrapSpecialFieldInstruction(descriptor)); } @@ -314,7 +313,7 @@ public CFGBuilder isAssignableFrom(PsiMethodCallExpression anchor) { * @param castType cast type (pushed before) * @return this builder */ - public CFGBuilder isInstance(PsiExpression anchor, @Nullable PsiExpression operand, @Nonnull PsiType castType) { + public CFGBuilder isInstance(PsiExpression anchor, @Nullable PsiExpression operand, PsiType castType) { return add(new InstanceofInstruction(anchor, operand, castType)); } @@ -449,7 +448,7 @@ public CFGBuilder doWhileUnknown() { * @param expectedType an expected type * @return this builder */ - public CFGBuilder boxUnbox(@Nonnull PsiExpression expression, PsiType expectedType) { + public CFGBuilder boxUnbox(PsiExpression expression, PsiType expectedType) { myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expectedType); return this; } @@ -556,7 +555,7 @@ public CFGBuilder assign(DfaValue target, DfType source) { * @param anchor PSI anchor to handle nested traps * @return this builder */ - public CFGBuilder doTry(@Nonnull PsiElement anchor) { + public CFGBuilder doTry(PsiElement anchor) { ControlFlow.DeferredOffset offset = new ControlFlow.DeferredOffset(); myAnalyzer.pushTrap(new Trap.TryCatchAll(anchor, offset)); myBranches.add(() -> offset.setOffset(myAnalyzer.getInstructionCount())); @@ -582,7 +581,7 @@ public CFGBuilder catchAll() { * @param exceptionType exception type to throw * @return this builder */ - public CFGBuilder doThrow(@Nonnull PsiType exceptionType) { + public CFGBuilder doThrow(PsiType exceptionType) { myAnalyzer.throwException(exceptionType, null); return this; } @@ -898,7 +897,6 @@ public CFGBuilder loopOver(PsiExpression[] expressions, DfaVariableValue targetV * @param type a type of variable to create * @return newly created variable */ - @Nonnull public DfaVariableValue createTempVariable(@Nullable PsiType type) { return myAnalyzer.createTempVariable(type); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CommonDataflow.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CommonDataflow.java index bf5ba4c7f6..43dca01120 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CommonDataflow.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CommonDataflow.java @@ -22,8 +22,7 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ForkJoinPool; @@ -33,7 +32,6 @@ public final class CommonDataflow { private static class DataflowPoint { - @Nonnull DfType myDfType = DfTypes.BOTTOM; // empty = top; null = bottom @Nullable @@ -93,10 +91,8 @@ void addFacts(DfaMemoryState memState, DfaValue value) { */ public static final class DataflowResult { private final - @Nonnull Map myData = new HashMap<>(); private - @Nonnull Map myDataAssertionsDisabled = myData; private final RunnerResult myResult; @@ -104,7 +100,6 @@ public DataflowResult(RunnerResult result) { myResult = result; } - @Nonnull DataflowResult copy() { DataflowResult copy = new DataflowResult(myResult); myData.forEach((expression, point) -> copy.myData.put(expression, new DataflowPoint(point))); @@ -187,7 +182,6 @@ public boolean cannotFailByContract(PsiCallExpression call) { * @param expression an expression to get its value * @return a set of possible values or empty set if not known */ - @Nonnull public Set getExpressionValues(@Nullable PsiExpression expression) { DataflowPoint point = myData.get(expression); if (point == null) { @@ -203,7 +197,6 @@ public Set getExpressionValues(@Nullable PsiExpression expression) { * May return {@link DfTypes#TOP} if no information from dataflow is known about this expression * @see #getDfTypeNoAssertions(PsiExpression) */ - @Nonnull public DfType getDfType(PsiExpression expression) { DataflowPoint point = myData.get(expression); return point == null ? DfTypes.TOP : point.myDfType; @@ -215,14 +208,12 @@ public DfType getDfType(PsiExpression expression) { * May return {@link DfTypes#TOP} if no information from dataflow is known about this expression * @see #getDfType(PsiExpression) */ - @Nonnull public DfType getDfTypeNoAssertions(PsiExpression expression) { DataflowPoint point = myDataAssertionsDisabled.get(expression); return point == null ? DfTypes.TOP : point.myDfType; } } - @Nonnull private static DataflowResult runDFA(@Nullable PsiElement block) { if (block == null) { return new DataflowResult(RunnerResult.NOT_APPLICABLE); @@ -310,7 +301,6 @@ DataflowResult getResult() { * @param expression an expression to infer the DfType * @return DfType for that expression. May return {@link DfTypes#TOP} if no information from dataflow is known about this expression */ - @Nonnull public static DfType getDfType(PsiExpression expression) { DataflowResult result = getDataflowResult(expression); if (result == null) { @@ -376,10 +366,10 @@ public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction i } @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, + protected void beforeExpressionPush(DfaValue value, + PsiExpression expression, @Nullable TextRange range, - @Nonnull DfaMemoryState state) { + DfaMemoryState state) { if (range == null) { // Do not track instructions which cover part of expression myResult.add(expression, state, value); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractChecker.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractChecker.java index 939c5c1e2a..0488170c04 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractChecker.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractChecker.java @@ -1,7 +1,6 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.StandardMethodContract.ValueConstraint; import com.intellij.java.analysis.impl.codeInspection.dataFlow.instructions.MethodCallInstruction; import com.intellij.java.analysis.impl.codeInspection.dataFlow.instructions.ReturnInstruction; @@ -12,139 +11,162 @@ import com.intellij.java.language.codeInsight.NullableNotNullManager; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; -import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; import java.util.*; /** * @author peter */ public final class ContractChecker { - private static class ContractCheckerVisitor extends StandardInstructionVisitor { - private final PsiMethod myMethod; - private final StandardMethodContract myContract; - private final boolean myOwnContract; - private final Set myViolations = new HashSet<>(); - private final Set myNonViolations = new HashSet<>(); - private final Set myFailures = new HashSet<>(); - private boolean myMayReturnNormally = false; + private static class ContractCheckerVisitor extends StandardInstructionVisitor { + private final PsiMethod myMethod; + private final StandardMethodContract myContract; + private final boolean myOwnContract; + private final Set myViolations = new HashSet<>(); + private final Set myNonViolations = new HashSet<>(); + private final Set myFailures = new HashSet<>(); + private boolean myMayReturnNormally = false; - ContractCheckerVisitor(PsiMethod method, StandardMethodContract contract, boolean ownContract) { - super(true); - myMethod = method; - myContract = contract; - myOwnContract = ownContract; - } - - @Override - protected void checkReturnValue(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nonnull PsiParameterListOwner context, - @Nonnull DfaMemoryState state) { - if (context != myMethod || state.isEphemeral()) { - return; - } - if (!myContract.getReturnValue().isValueCompatible(state, value)) { - myViolations.add(expression); - } else { - myNonViolations.add(expression); - } - } - - @Override - public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, - DataFlowRunner runner, - DfaMemoryState memState) { - PsiCall call = instruction.getCallExpression(); - if (!memState.isEphemeral() && call != null) { - if (myContract.getReturnValue().isFail()) { - myFailures.add(call); - return DfaInstructionState.EMPTY_ARRAY; + ContractCheckerVisitor(PsiMethod method, StandardMethodContract contract, boolean ownContract) { + super(true); + myMethod = method; + myContract = contract; + myOwnContract = ownContract; } - if (weCannotInferAnythingAboutMethodReturnValue(instruction)) { - DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState); - for (DfaInstructionState state : states) { - state.getMemoryState().markEphemeral(); - } - return states; + + @Override + protected void checkReturnValue( + DfaValue value, + PsiExpression expression, + PsiParameterListOwner context, + DfaMemoryState state + ) { + if (context != myMethod || state.isEphemeral()) { + return; + } + if (!myContract.getReturnValue().isValueCompatible(state, value)) { + myViolations.add(expression); + } + else { + myNonViolations.add(expression); + } } - } - return super.visitMethodCall(instruction, runner, memState); - } - @Override - @Nonnull - public DfaInstructionState[] visitControlTransfer(@Nonnull ControlTransferInstruction instruction, - @Nonnull DataFlowRunner runner, - @Nonnull DfaMemoryState state) { - if (instruction instanceof ReturnInstruction && ((ReturnInstruction) instruction).isViaException()) { - ContainerUtil.addIfNotNull(myFailures, ((ReturnInstruction) instruction).getAnchor()); - } else { - myMayReturnNormally = true; - } - return super.visitControlTransfer(instruction, runner, state); - } + @Override + public DfaInstructionState[] visitMethodCall( + MethodCallInstruction instruction, + DataFlowRunner runner, + DfaMemoryState memState + ) { + PsiCall call = instruction.getCallExpression(); + if (!memState.isEphemeral() && call != null) { + if (myContract.getReturnValue().isFail()) { + myFailures.add(call); + return DfaInstructionState.EMPTY_ARRAY; + } + if (weCannotInferAnythingAboutMethodReturnValue(instruction)) { + DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState); + for (DfaInstructionState state : states) { + state.getMemoryState().markEphemeral(); + } + return states; + } + } + return super.visitMethodCall(instruction, runner, memState); + } - private Map getErrors() { - HashMap errors = new HashMap<>(); - for (PsiElement element : myViolations) { - if (!myNonViolations.contains(element)) { - errors.put(element, JavaAnalysisBundle.message("inspection.contract.checker.contract.violated", myContract)); + @Override + public DfaInstructionState[] visitControlTransfer( + ControlTransferInstruction instruction, + DataFlowRunner runner, + DfaMemoryState state + ) { + if (instruction instanceof ReturnInstruction returnInsn && returnInsn.isViaException()) { + ContainerUtil.addIfNotNull(myFailures, returnInsn.getAnchor()); + } + else { + myMayReturnNormally = true; + } + return super.visitControlTransfer(instruction, runner, state); } - } - if (!myContract.getReturnValue().isFail()) { - if (myOwnContract && !myMayReturnNormally && - !(PsiUtil.canBeOverridden(myMethod) && ControlFlowUtils.methodAlwaysThrowsException(myMethod))) { - for (PsiElement element : myFailures) { - if (myContract.isTrivial()) { - errors.put(element, JavaAnalysisBundle.message("inspection.contract.checker.method.always.fails.trivial", myContract)); - } else { - errors.put(element, JavaAnalysisBundle.message("inspection.contract.checker.method.always.fails.nontrivial", myContract)); + private Map getErrors() { + Map errors = new HashMap<>(); + for (PsiElement element : myViolations) { + if (!myNonViolations.contains(element)) { + errors.put(element, JavaAnalysisLocalize.inspectionContractCheckerContractViolated(myContract)); + } + } + + if (!myContract.getReturnValue().isFail()) { + if (myOwnContract && !myMayReturnNormally && + !(PsiUtil.canBeOverridden(myMethod) && ControlFlowUtils.methodAlwaysThrowsException(myMethod))) { + for (PsiElement element : myFailures) { + if (myContract.isTrivial()) { + errors.put( + element, + JavaAnalysisLocalize.inspectionContractCheckerMethodAlwaysFailsTrivial(myContract) + ); + } + else { + errors.put( + element, + JavaAnalysisLocalize.inspectionContractCheckerMethodAlwaysFailsNontrivial(myContract) + ); + } + } + } } - } + else if (myFailures.isEmpty() && errors.isEmpty() && myMayReturnNormally) { + PsiIdentifier nameIdentifier = myMethod.getNameIdentifier(); + errors.put( + nameIdentifier != null ? nameIdentifier : myMethod, + JavaAnalysisLocalize.inspectionContractCheckerNoExceptionThrown(myContract) + ); + } + + return errors; } - } else if (myFailures.isEmpty() && errors.isEmpty() && myMayReturnNormally) { - PsiIdentifier nameIdentifier = myMethod.getNameIdentifier(); - errors.put(nameIdentifier != null ? nameIdentifier : myMethod, - JavaAnalysisBundle.message("inspection.contract.checker.no.exception.thrown", myContract)); - } - return errors; + private static boolean weCannotInferAnythingAboutMethodReturnValue(MethodCallInstruction instruction) { + PsiMethod target = instruction.getTargetMethod(); + return instruction.getContracts().isEmpty() + && target != null + && !target.isConstructor() + && !NullableNotNullManager.isNotNull(target); + } } - private static boolean weCannotInferAnythingAboutMethodReturnValue(MethodCallInstruction instruction) { - PsiMethod target = instruction.getTargetMethod(); - return instruction.getContracts().isEmpty() && target != null && !target.isConstructor() && !NullableNotNullManager.isNotNull(target); - } - } + @RequiredReadAction + public static Map checkContractClause(PsiMethod method, StandardMethodContract contract, boolean ownContract) { + PsiCodeBlock body = method.getBody(); + if (body == null) { + return Collections.emptyMap(); + } - public static Map checkContractClause(PsiMethod method, StandardMethodContract contract, boolean ownContract) { - PsiCodeBlock body = method.getBody(); - if (body == null) { - return Collections.emptyMap(); - } + DataFlowRunner runner = new DataFlowRunner(method.getProject(), null); - DataFlowRunner runner = new DataFlowRunner(method.getProject(), null); + PsiParameter[] parameters = method.getParameterList().getParameters(); + DfaMemoryState initialState = runner.createMemoryState(); + DfaValueFactory factory = runner.getFactory(); + for (int i = 0; i < contract.getParameterCount(); i++) { + ValueConstraint constraint = contract.getParameterConstraint(i); + DfaValue comparisonValue = constraint.getComparisonValue(factory); + if (comparisonValue != null) { + boolean negated = constraint.shouldUseNonEqComparison(); + DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i]); + initialState.applyCondition(dfaParam.cond(RelationType.equivalence(!negated), comparisonValue)); + } + } - PsiParameter[] parameters = method.getParameterList().getParameters(); - final DfaMemoryState initialState = runner.createMemoryState(); - final DfaValueFactory factory = runner.getFactory(); - for (int i = 0; i < contract.getParameterCount(); i++) { - ValueConstraint constraint = contract.getParameterConstraint(i); - DfaValue comparisonValue = constraint.getComparisonValue(factory); - if (comparisonValue != null) { - boolean negated = constraint.shouldUseNonEqComparison(); - DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i]); - initialState.applyCondition(dfaParam.cond(RelationType.equivalence(!negated), comparisonValue)); - } + ContractCheckerVisitor visitor = new ContractCheckerVisitor(method, contract, ownContract); + runner.analyzeMethod(body, visitor, Collections.singletonList(initialState)); + return visitor.getErrors(); } - - ContractCheckerVisitor visitor = new ContractCheckerVisitor(method, contract, ownContract); - runner.analyzeMethod(body, visitor, Collections.singletonList(initialState)); - return visitor.getErrors(); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractReturnValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractReturnValue.java index 0b7781306c..b6d97a7f02 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractReturnValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractReturnValue.java @@ -7,11 +7,11 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.language.psi.*; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Objects; import java.util.function.Function; @@ -21,661 +21,575 @@ * Representation of {@link MethodContract} return value. It may represent the concrete value (e.g. "false") or pose some constraints * to the method return value (e.g. "!null"). */ -public abstract class ContractReturnValue -{ - private static final int PARAMETER_ORDINAL_BASE = 10; - private static final int MAX_SUPPORTED_PARAMETER = 100; - - private interface Validator extends Function - { - } - - private static final Validator NOT_CONSTRUCTOR = - method -> method.isConstructor() ? JavaAnalysisBundle.message("contract.return.validator.not.applicable.for.constructor") : null; - private static final Validator NOT_STATIC = - method -> method.hasModifierProperty(PsiModifier.STATIC) ? JavaAnalysisBundle.message("contract.return.validator.not.applicable.static") - : null; - private static final Validator NOT_PRIMITIVE_RETURN = - method -> { - PsiType returnType = method.getReturnType(); - return returnType instanceof PsiPrimitiveType - ? JavaAnalysisBundle.message("contract.return.validator.not.applicable.primitive", returnType.getPresentableText()) - : null; - }; - private static final Validator BOOLEAN_RETURN = - method -> PsiType.BOOLEAN.equals(method.getReturnType()) ? null : JavaAnalysisBundle - .message("contract.return.validator.return.type.must.be.boolean"); - - private final - @Nonnull - String myName; - private final int myOrdinal; - - private ContractReturnValue(@Nonnull String name, int ordinal) - { - myName = name; - myOrdinal = ordinal; - } - - /** - * @return a hashcode which is stable across VM restart - */ - @Override - public int hashCode() - { - return myOrdinal; - } - - /** - * @return a string which is used to represent this return value inside {@link Contract} annotation. - */ - @Override - public String toString() - { - return myName; - } - - /** - * Checks whether this return value makes sense for the specified method signature. The method body is not checked. - * E.g. "true" contract value makes sense for method returning {@code boolean}, but does not make sense for method returning {@code int}. - * This method can be used to check the contract correctness. - * - * @param method method to check - * @return null if this contract return value makes sense for the supplied return type. - * Otherwise the human-readable error message is returned. - */ - public final String getMethodCompatibilityProblem(PsiMethod method) - { - //noinspection HardCodedStringLiteral - return validators().map(fn -> fn.apply(method)).filter(Objects::nonNull).findFirst() - .map((JavaAnalysisBundle.message("contract.return.value.validation.prefix", this) + ' ')::concat) - .orElse(null); - } - - /** - * Checks whether this return value makes sense for the specified method signature. The method body is not checked. - * E.g. "true" contract value makes sense for method returning {@code boolean}, but does not make sense for method returning {@code int}. - * This method can be used to check the contract correctness. - * - * @param method method to check - * @return true if this contract return value makes sense for the supplied return type. - */ - public final boolean isMethodCompatible(PsiMethod method) - { - return validators().map(fn -> fn.apply(method)).allMatch(Objects::isNull); - } - - abstract Stream validators(); - - public ContractReturnValue intersect(ContractReturnValue other) - { - if(this.equals(other) || other == ANY_VALUE) - { - return this; - } - if(this == ANY_VALUE) - { - return other; - } - if(this.isNotNull() && other.isNotNull()) - { - return NOT_NULL_VALUE; - } - return FAIL_VALUE; - } - - public boolean isSuperValueOf(ContractReturnValue value) - { - return this == value || this == ANY_VALUE || (this == NOT_NULL_VALUE && value.isNotNull()); - } - - static DfaValue merge(DfaValue defaultValue, DfaValue newValue, DfaMemoryState memState) - { - if(defaultValue == null || DfaTypeValue.isUnknown(defaultValue)) - { - return newValue; - } - if(newValue == null || DfaTypeValue.isUnknown(newValue)) - { - return defaultValue; - } - DfType defaultType = memState.getDfType(defaultValue); - DfType newType = memState.getDfType(newValue); - DfType result = defaultType.meet(newType); - if(result == DfTypes.BOTTOM) - { - return newValue; - } - if(newValue instanceof DfaVariableValue) - { - memState.meetDfType(newValue, result); - return newValue; - } - if(defaultValue instanceof DfaVariableValue) - { - memState.meetDfType(defaultValue, result); - return defaultValue; - } - return defaultValue.getFactory().fromDfType(result); - } - - /** - * Converts this return value to the most suitable {@link DfaValue} which represents the same constraints. - * - * @param factory a {@link DfaValueFactory} which can be used to create new values if necessary - * @param defaultValue a default method return type value in the absence of the contracts (may contain method type information) - * @param callState call state - * @return a value which represents the constraints of this contract return value. - */ - public abstract DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState); - - /** - * Returns true if the supplied {@link DfaValue} could be compatible with this return value. If false is returned, then - * returning given {@link DfaValue} would violate the contract with this return value. This method can be used - * to check the contract correctness. - * - * @param state memory state to use - * @param value value to check - * @return whether the supplied value could be compatible with this return value. - */ - public abstract boolean isValueCompatible(DfaMemoryState state, DfaValue value); - - /** - * Returns a unique non-negative number which identifies this return value. Can be used for serialization. For two return values - * {@code a} and {@code b} the following holds: - * {@code a.ordinal() == b.ordinal() <=> a.equals(b)}. - * - * @return a unique non-negative number which identifies this return value. - */ - public int ordinal() - { - return myOrdinal; - } - - /** - * @return true if this return value represents a non-null object value (possibly with some other restrictions) - */ - public boolean isNotNull() - { - return false; - } - - /** - * @return true if this return value represents a null - */ - public boolean isNull() - { - return this == NULL_VALUE; - } - - /** - * @return true if this return value represents a failure (that is, method must throw to fulfill this contract) - */ - public boolean isFail() - { - return this == FAIL_VALUE; - } - - /** - * @return true if this return value represents a boolean value (either "true" or "false") - */ - public boolean isBoolean() - { - return this instanceof BooleanReturnValue; - } - - /** - * Returns a {@code ContractReturnValue} which corresponds to given ordinal index. For any return value {@code x} - * the following holds: {@code ContractReturnValue.valueOf(x.ordinal()).equals(x)}. - * - * @param ordinal ordinal to create a ContractReturnValue object from - * @return a ContractReturnValue object. Returns an object which represents any possible value if the supplied ordinal does not - * correspond to any valid ContractReturnValue. - */ - @Nonnull - public static ContractReturnValue valueOf(int ordinal) - { - switch(ordinal) - { - case 0: - case 1: - return returnNull(); - case 2: - return returnNotNull(); - case 3: - return returnTrue(); - case 4: - return returnFalse(); - case 5: - return fail(); - case 6: - return returnNew(); - case 7: - return returnThis(); - default: - if(ordinal >= PARAMETER_ORDINAL_BASE && ordinal <= PARAMETER_ORDINAL_BASE + MAX_SUPPORTED_PARAMETER) - { - return returnParameter(ordinal - PARAMETER_ORDINAL_BASE); - } - return returnAny(); - } - } - - /** - * Returns a {@code ContractReturnValue} which corresponds to given string representation. For any return value {@code x} - * the following holds: {@code ContractReturnValue.valueOf(x.toString()).equals(x)} and for string {@code str} the following holds: - * {@code ContractReturnValue.valueOf(str) == null || ContractReturnValue.valueOf(str).toString().equals(str)}. - * - * @param value string representation of return value - * @return ContractReturnValue object which corresponds to given string representation; null if given value is not supported. - */ - @Nullable - public static ContractReturnValue valueOf(@Nonnull String value) - { - switch(value) - { - case "_": - return returnAny(); - case "fail": - return fail(); - case "true": - return returnTrue(); - case "false": - return returnFalse(); - case "null": - return returnNull(); - case "!null": - return returnNotNull(); - case "new": - return returnNew(); - case "this": - return returnThis(); - } - if(value.startsWith("param")) - { - String suffix = value.substring("param".length()); - try - { - int paramNumber = Integer.parseInt(suffix) - 1; - if(paramNumber >= 0 && paramNumber <= MAX_SUPPORTED_PARAMETER) - { - return new ParameterReturnValue(paramNumber); - } - } - catch(NumberFormatException ignored) - { - // unexpected non-integer suffix: ignore - } - } - return null; - } - - /** - * @return any possible return value ("top" element) - */ - public static ContractReturnValue returnAny() - { - return ANY_VALUE; - } - - /** - * @return return value indicating that the method throws an exception ("bottom" element) - */ - public static ContractReturnValue fail() - { - return FAIL_VALUE; - } - - /** - * @param value a boolean value to return - * @return the corresponding boolean return value - */ - public static BooleanReturnValue returnBoolean(boolean value) - { - return value ? returnTrue() : returnFalse(); - } - - /** - * @return boolean "true" return value - */ - public static BooleanReturnValue returnTrue() - { - return BooleanReturnValue.TRUE_VALUE; - } - - /** - * @return boolean "false" return value - */ - public static BooleanReturnValue returnFalse() - { - return BooleanReturnValue.FALSE_VALUE; - } - - /** - * @return "null" return value - */ - public static ContractReturnValue returnNull() - { - return NULL_VALUE; - } - - /** - * @return non-null return value - */ - public static ContractReturnValue returnNotNull() - { - return NOT_NULL_VALUE; - } - - /** - * @return non-null new object return value - */ - public static ContractReturnValue returnNew() - { - return NEW_VALUE; - } - - /** - * @return non-null "this" return value (qualifier) - */ - public static ContractReturnValue returnThis() - { - return THIS_VALUE; - } - - /** - * @return non-null parameter return value (parameter number is zero-based) - */ - public static ContractReturnValue returnParameter(int n) - { - if(n < 0) - { - throw new IllegalArgumentException("Negative parameter: " + n); - } - if(n > MAX_SUPPORTED_PARAMETER) - { - return ANY_VALUE; - } - return new ParameterReturnValue(n); - } - - private static final ContractReturnValue ANY_VALUE = new ContractReturnValue("_", 0) - { - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - return defaultValue; - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return true; - } - }; - - private static final ContractReturnValue FAIL_VALUE = new ContractReturnValue("fail", 5) - { - @Override - Stream validators() - { - return Stream.empty(); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - return factory.fromDfType(DfTypes.FAIL); - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return false; - } - }; - - private static final ContractReturnValue NULL_VALUE = new ContractReturnValue("null", 1) - { - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - return factory.getNull(); - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return !state.isNotNull(value); - } - }; - - private static final ContractReturnValue NOT_NULL_VALUE = new ContractReturnValue("!null", 2) - { - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); - } - - @Override - public boolean isNotNull() - { - return true; - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState); - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return !state.isNull(value); - } - }; - - private static final ContractReturnValue NEW_VALUE = new ContractReturnValue("new", 6) - { - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - DfType dfType = callState.myMemoryState.getDfType(defaultValue); - dfType = dfType.meet(DfTypes.NOT_NULL_OBJECT); - if(callState.myCallArguments.myMutation.isPure()) - { - boolean unmodifiableView = Mutability.fromDfType(dfType) == Mutability.UNMODIFIABLE_VIEW; - // Unmodifiable view methods like Collections.unmodifiableList create new object, but their special field "size" is - // actually a delegate, so we cannot trust it if the original value is not local - if(!unmodifiableView) - { - dfType = dfType.meet(DfTypes.LOCAL_OBJECT); - } - } - return merge(defaultValue, factory.fromDfType(dfType), callState.myMemoryState); - } - - @Override - public boolean isNotNull() - { - return true; - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return !state.isNull(value); - } - }; - - private static final ContractReturnValue THIS_VALUE = new ContractReturnValue("this", 7) - { - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, NOT_STATIC, NOT_PRIMITIVE_RETURN, method -> { - PsiType returnType = method.getReturnType(); - if(returnType instanceof PsiClassType) - { - PsiClass aClass = method.getContainingClass(); - if(aClass != null && JavaPsiFacade.getElementFactory(method.getProject()).createType(aClass).isConvertibleFrom(returnType)) - { - return null; - } - } - return JavaAnalysisBundle.message("contract.return.validator.method.return.incompatible.with.method.containing.class"); - }); - } - - @Override - public boolean isNotNull() - { - return true; - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - DfaValue qualifier = callState.myCallArguments.myQualifier; - if(qualifier != null && !DfaTypeValue.isUnknown(qualifier)) - { - return merge(defaultValue, qualifier, callState.myMemoryState); - } - return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState); - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return !state.isNull(value); - } - }; - - /** - * Boolean return value (either "true" or "false"). - */ - public static final class BooleanReturnValue extends ContractReturnValue - { - static final BooleanReturnValue TRUE_VALUE = new BooleanReturnValue(true, 3); - static final BooleanReturnValue FALSE_VALUE = new BooleanReturnValue(false, 4); - private final boolean myValue; - - private BooleanReturnValue(boolean value, int ordinal) - { - super(String.valueOf(value), ordinal); - myValue = value; - } - - public boolean getValue() - { - return myValue; - } - - /** - * @return the return value opposite to this return value - */ - public BooleanReturnValue negate() - { - return myValue ? FALSE_VALUE : TRUE_VALUE; - } - - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, BOOLEAN_RETURN); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - return factory.getBoolean(myValue); - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - DfType type = state.getUnboxedDfType(value); - return type.isSuperType(DfTypes.booleanValue(myValue)); - } - } - - public static final class ParameterReturnValue extends ContractReturnValue - { - private final int myParamNumber; // zero-based - - public ParameterReturnValue(int n) - { - super("param" + (n + 1), n + PARAMETER_ORDINAL_BASE); - myParamNumber = n; - } - - public int getParameterNumber() - { - return myParamNumber; - } - - @Override - Stream validators() - { - return Stream.of(NOT_CONSTRUCTOR, method -> { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if(parameters.length <= myParamNumber) - { - return JavaAnalysisBundle - .message("contract.return.validator.too.few.parameters", parameters.length); - } - PsiType parameterType = parameters[myParamNumber].getType(); - PsiType returnType = method.getReturnType(); - if(returnType != null && !returnType.isConvertibleFrom(parameterType)) - { - return JavaAnalysisBundle.message("contract.return.validator.incompatible.return.parameter.type", - returnType.getPresentableText(), parameterType.getPresentableText()); - } - return null; - }); - } - - @Override - public boolean equals(Object obj) - { - return this == obj || (obj instanceof ParameterReturnValue && ((ParameterReturnValue) obj).myParamNumber == myParamNumber); - } - - @Override - public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) - { - if(callState.myCallArguments.myArguments != null && callState.myCallArguments.myArguments.length > myParamNumber) - { - DfaValue argument = callState.myCallArguments.myArguments[myParamNumber]; - return merge(defaultValue, argument, callState.myMemoryState); - } - return defaultValue; - } - - @Override - public boolean isValueCompatible(DfaMemoryState state, DfaValue value) - { - return true; - } - } +public abstract class ContractReturnValue { + private static final int PARAMETER_ORDINAL_BASE = 10; + private static final int MAX_SUPPORTED_PARAMETER = 100; + + private interface Validator extends Function { + } + + private static final Validator NOT_CONSTRUCTOR = + method -> method.isConstructor() + ? JavaAnalysisLocalize.contractReturnValidatorNotApplicableForConstructor().get() + : null; + private static final Validator NOT_STATIC = + method -> method.hasModifierProperty(PsiModifier.STATIC) + ? JavaAnalysisLocalize.contractReturnValidatorNotApplicableStatic().get() + : null; + private static final Validator NOT_PRIMITIVE_RETURN = + method -> { + PsiType returnType = method.getReturnType(); + return returnType instanceof PsiPrimitiveType + ? JavaAnalysisLocalize.contractReturnValidatorNotApplicablePrimitive(returnType.getPresentableText()).get() + : null; + }; + private static final Validator BOOLEAN_RETURN = + method -> PsiType.BOOLEAN.equals(method.getReturnType()) + ? null + : JavaAnalysisLocalize.contractReturnValidatorReturnTypeMustBeBoolean().get(); + + private final + String myName; + private final int myOrdinal; + + private ContractReturnValue(String name, int ordinal) { + myName = name; + myOrdinal = ordinal; + } + + /** + * @return a hashcode which is stable across VM restart + */ + @Override + public int hashCode() { + return myOrdinal; + } + + /** + * @return a string which is used to represent this return value inside {@link Contract} annotation. + */ + @Override + public String toString() { + return myName; + } + + /** + * Checks whether this return value makes sense for the specified method signature. The method body is not checked. + * E.g. "true" contract value makes sense for method returning {@code boolean}, but does not make sense for method returning {@code int}. + * This method can be used to check the contract correctness. + * + * @param method method to check + * @return null if this contract return value makes sense for the supplied return type. + * Otherwise the human-readable error message is returned. + */ + public final LocalizeValue getMethodCompatibilityProblem(PsiMethod method) { + return validators() + .map(fn -> fn.apply(method)) + .filter(Objects::nonNull) + .findFirst() + .map(value -> JavaAnalysisLocalize.contractReturnValueValidationProblem(myName, value)) + .orElse(LocalizeValue.empty()); + } + + /** + * Checks whether this return value makes sense for the specified method signature. The method body is not checked. + * E.g. "true" contract value makes sense for method returning {@code boolean}, but does not make sense for method returning {@code int}. + * This method can be used to check the contract correctness. + * + * @param method method to check + * @return true if this contract return value makes sense for the supplied return type. + */ + public final boolean isMethodCompatible(PsiMethod method) { + return validators().map(fn -> fn.apply(method)).allMatch(Objects::isNull); + } + + abstract Stream validators(); + + public ContractReturnValue intersect(ContractReturnValue other) { + if (this.equals(other) || other == ANY_VALUE) { + return this; + } + if (this == ANY_VALUE) { + return other; + } + if (this.isNotNull() && other.isNotNull()) { + return NOT_NULL_VALUE; + } + return FAIL_VALUE; + } + + public boolean isSuperValueOf(ContractReturnValue value) { + return this == value || this == ANY_VALUE || (this == NOT_NULL_VALUE && value.isNotNull()); + } + + static DfaValue merge(DfaValue defaultValue, DfaValue newValue, DfaMemoryState memState) { + if (defaultValue == null || DfaTypeValue.isUnknown(defaultValue)) { + return newValue; + } + if (newValue == null || DfaTypeValue.isUnknown(newValue)) { + return defaultValue; + } + DfType defaultType = memState.getDfType(defaultValue); + DfType newType = memState.getDfType(newValue); + DfType result = defaultType.meet(newType); + if (result == DfTypes.BOTTOM) { + return newValue; + } + if (newValue instanceof DfaVariableValue) { + memState.meetDfType(newValue, result); + return newValue; + } + if (defaultValue instanceof DfaVariableValue) { + memState.meetDfType(defaultValue, result); + return defaultValue; + } + return defaultValue.getFactory().fromDfType(result); + } + + /** + * Converts this return value to the most suitable {@link DfaValue} which represents the same constraints. + * + * @param factory a {@link DfaValueFactory} which can be used to create new values if necessary + * @param defaultValue a default method return type value in the absence of the contracts (may contain method type information) + * @param callState call state + * @return a value which represents the constraints of this contract return value. + */ + public abstract DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState); + + /** + * Returns true if the supplied {@link DfaValue} could be compatible with this return value. If false is returned, then + * returning given {@link DfaValue} would violate the contract with this return value. This method can be used + * to check the contract correctness. + * + * @param state memory state to use + * @param value value to check + * @return whether the supplied value could be compatible with this return value. + */ + public abstract boolean isValueCompatible(DfaMemoryState state, DfaValue value); + + /** + * Returns a unique non-negative number which identifies this return value. Can be used for serialization. For two return values + * {@code a} and {@code b} the following holds: + * {@code a.ordinal() == b.ordinal() <=> a.equals(b)}. + * + * @return a unique non-negative number which identifies this return value. + */ + public int ordinal() { + return myOrdinal; + } + + /** + * @return true if this return value represents a non-null object value (possibly with some other restrictions) + */ + public boolean isNotNull() { + return false; + } + + /** + * @return true if this return value represents a null + */ + public boolean isNull() { + return this == NULL_VALUE; + } + + /** + * @return true if this return value represents a failure (that is, method must throw to fulfill this contract) + */ + public boolean isFail() { + return this == FAIL_VALUE; + } + + /** + * @return true if this return value represents a boolean value (either "true" or "false") + */ + public boolean isBoolean() { + return this instanceof BooleanReturnValue; + } + + /** + * Returns a {@code ContractReturnValue} which corresponds to given ordinal index. For any return value {@code x} + * the following holds: {@code ContractReturnValue.valueOf(x.ordinal()).equals(x)}. + * + * @param ordinal ordinal to create a ContractReturnValue object from + * @return a ContractReturnValue object. Returns an object which represents any possible value if the supplied ordinal does not + * correspond to any valid ContractReturnValue. + */ + public static ContractReturnValue valueOf(int ordinal) { + switch (ordinal) { + case 0: + case 1: + return returnNull(); + case 2: + return returnNotNull(); + case 3: + return returnTrue(); + case 4: + return returnFalse(); + case 5: + return fail(); + case 6: + return returnNew(); + case 7: + return returnThis(); + default: + if (ordinal >= PARAMETER_ORDINAL_BASE && ordinal <= PARAMETER_ORDINAL_BASE + MAX_SUPPORTED_PARAMETER) { + return returnParameter(ordinal - PARAMETER_ORDINAL_BASE); + } + return returnAny(); + } + } + + /** + * Returns a {@code ContractReturnValue} which corresponds to given string representation. For any return value {@code x} + * the following holds: {@code ContractReturnValue.valueOf(x.toString()).equals(x)} and for string {@code str} the following holds: + * {@code ContractReturnValue.valueOf(str) == null || ContractReturnValue.valueOf(str).toString().equals(str)}. + * + * @param value string representation of return value + * @return ContractReturnValue object which corresponds to given string representation; null if given value is not supported. + */ + @Nullable + public static ContractReturnValue valueOf(String value) { + switch (value) { + case "_": + return returnAny(); + case "fail": + return fail(); + case "true": + return returnTrue(); + case "false": + return returnFalse(); + case "null": + return returnNull(); + case "!null": + return returnNotNull(); + case "new": + return returnNew(); + case "this": + return returnThis(); + } + if (value.startsWith("param")) { + String suffix = value.substring("param".length()); + try { + int paramNumber = Integer.parseInt(suffix) - 1; + if (paramNumber >= 0 && paramNumber <= MAX_SUPPORTED_PARAMETER) { + return new ParameterReturnValue(paramNumber); + } + } + catch (NumberFormatException ignored) { + // unexpected non-integer suffix: ignore + } + } + return null; + } + + /** + * @return any possible return value ("top" element) + */ + public static ContractReturnValue returnAny() { + return ANY_VALUE; + } + + /** + * @return return value indicating that the method throws an exception ("bottom" element) + */ + public static ContractReturnValue fail() { + return FAIL_VALUE; + } + + /** + * @param value a boolean value to return + * @return the corresponding boolean return value + */ + public static BooleanReturnValue returnBoolean(boolean value) { + return value ? returnTrue() : returnFalse(); + } + + /** + * @return boolean "true" return value + */ + public static BooleanReturnValue returnTrue() { + return BooleanReturnValue.TRUE_VALUE; + } + + /** + * @return boolean "false" return value + */ + public static BooleanReturnValue returnFalse() { + return BooleanReturnValue.FALSE_VALUE; + } + + /** + * @return "null" return value + */ + public static ContractReturnValue returnNull() { + return NULL_VALUE; + } + + /** + * @return non-null return value + */ + public static ContractReturnValue returnNotNull() { + return NOT_NULL_VALUE; + } + + /** + * @return non-null new object return value + */ + public static ContractReturnValue returnNew() { + return NEW_VALUE; + } + + /** + * @return non-null "this" return value (qualifier) + */ + public static ContractReturnValue returnThis() { + return THIS_VALUE; + } + + /** + * @return non-null parameter return value (parameter number is zero-based) + */ + public static ContractReturnValue returnParameter(int n) { + if (n < 0) { + throw new IllegalArgumentException("Negative parameter: " + n); + } + if (n > MAX_SUPPORTED_PARAMETER) { + return ANY_VALUE; + } + return new ParameterReturnValue(n); + } + + private static final ContractReturnValue ANY_VALUE = new ContractReturnValue("_", 0) { + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + return defaultValue; + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return true; + } + }; + + private static final ContractReturnValue FAIL_VALUE = new ContractReturnValue("fail", 5) { + @Override + Stream validators() { + return Stream.empty(); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + return factory.fromDfType(DfTypes.FAIL); + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return false; + } + }; + + private static final ContractReturnValue NULL_VALUE = new ContractReturnValue("null", 1) { + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + return factory.getNull(); + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return !state.isNotNull(value); + } + }; + + private static final ContractReturnValue NOT_NULL_VALUE = new ContractReturnValue("!null", 2) { + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); + } + + @Override + public boolean isNotNull() { + return true; + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState); + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return !state.isNull(value); + } + }; + + private static final ContractReturnValue NEW_VALUE = new ContractReturnValue("new", 6) { + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, NOT_PRIMITIVE_RETURN); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + DfType dfType = callState.myMemoryState.getDfType(defaultValue); + dfType = dfType.meet(DfTypes.NOT_NULL_OBJECT); + if (callState.myCallArguments.myMutation.isPure()) { + boolean unmodifiableView = Mutability.fromDfType(dfType) == Mutability.UNMODIFIABLE_VIEW; + // Unmodifiable view methods like Collections.unmodifiableList create new object, but their special field "size" is + // actually a delegate, so we cannot trust it if the original value is not local + if (!unmodifiableView) { + dfType = dfType.meet(DfTypes.LOCAL_OBJECT); + } + } + return merge(defaultValue, factory.fromDfType(dfType), callState.myMemoryState); + } + + @Override + public boolean isNotNull() { + return true; + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return !state.isNull(value); + } + }; + + private static final ContractReturnValue THIS_VALUE = new ContractReturnValue("this", 7) { + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, NOT_STATIC, NOT_PRIMITIVE_RETURN, method -> { + PsiType returnType = method.getReturnType(); + if (returnType instanceof PsiClassType) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null && JavaPsiFacade.getElementFactory(method.getProject()) + .createType(aClass) + .isConvertibleFrom(returnType)) { + return null; + } + } + return JavaAnalysisLocalize.contractReturnValidatorMethodReturnIncompatibleWithMethodContainingClass().get(); + }); + } + + @Override + public boolean isNotNull() { + return true; + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + DfaValue qualifier = callState.myCallArguments.myQualifier; + if (qualifier != null && !DfaTypeValue.isUnknown(qualifier)) { + return merge(defaultValue, qualifier, callState.myMemoryState); + } + return merge(defaultValue, factory.fromDfType(DfTypes.NOT_NULL_OBJECT), callState.myMemoryState); + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return !state.isNull(value); + } + }; + + /** + * Boolean return value (either "true" or "false"). + */ + public static final class BooleanReturnValue extends ContractReturnValue { + static final BooleanReturnValue TRUE_VALUE = new BooleanReturnValue(true, 3); + static final BooleanReturnValue FALSE_VALUE = new BooleanReturnValue(false, 4); + private final boolean myValue; + + private BooleanReturnValue(boolean value, int ordinal) { + super(String.valueOf(value), ordinal); + myValue = value; + } + + public boolean getValue() { + return myValue; + } + + /** + * @return the return value opposite to this return value + */ + public BooleanReturnValue negate() { + return myValue ? FALSE_VALUE : TRUE_VALUE; + } + + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, BOOLEAN_RETURN); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + return factory.getBoolean(myValue); + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + DfType type = state.getUnboxedDfType(value); + return type.isSuperType(DfTypes.booleanValue(myValue)); + } + } + + public static final class ParameterReturnValue extends ContractReturnValue { + private final int myParamNumber; // zero-based + + public ParameterReturnValue(int n) { + super("param" + (n + 1), n + PARAMETER_ORDINAL_BASE); + myParamNumber = n; + } + + public int getParameterNumber() { + return myParamNumber; + } + + @Override + Stream validators() { + return Stream.of(NOT_CONSTRUCTOR, method -> { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length <= myParamNumber) { + return JavaAnalysisLocalize.contractReturnValidatorTooFewParameters(parameters.length).get(); + } + PsiType parameterType = parameters[myParamNumber].getType(); + PsiType returnType = method.getReturnType(); + if (returnType != null && !returnType.isConvertibleFrom(parameterType)) { + return JavaAnalysisLocalize.contractReturnValidatorIncompatibleReturnParameterType( + returnType.getPresentableText(), + parameterType.getPresentableText() + ).get(); + } + return null; + }); + } + + @Override + public boolean equals(Object obj) { + return this == obj || (obj instanceof ParameterReturnValue paramReturnValue && paramReturnValue.myParamNumber == myParamNumber); + } + + @Override + public DfaValue getDfaValue(DfaValueFactory factory, DfaValue defaultValue, DfaCallState callState) { + if (callState.myCallArguments.myArguments != null && callState.myCallArguments.myArguments.length > myParamNumber) { + DfaValue argument = callState.myCallArguments.myArguments[myParamNumber]; + return merge(defaultValue, argument, callState.myMemoryState); + } + return defaultValue; + } + + @Override + public boolean isValueCompatible(DfaMemoryState state, DfaValue value) { + return true; + } + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractValue.java index cb8b7b7034..6c74c81c8b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ContractValue.java @@ -25,465 +25,449 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.psiutils.MethodCallUtils; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.OptionalInt; import java.util.function.Function; import java.util.function.UnaryOperator; public abstract class ContractValue { - // package private to avoid uncontrolled implementations - ContractValue() { - - } - - abstract DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments); + // package private to avoid uncontrolled implementations + ContractValue() { + } - @Nonnull - DfaCondition makeCondition(DfaValueFactory factory, DfaCallArguments arguments) { - return DfaCondition.getUnknown(); - } + abstract DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments); - public DfaCondition fromCall(DfaValueFactory factory, PsiCallExpression call) { - DfaCallArguments arguments = DfaCallArguments.fromCall(factory, call); - if (arguments == null) { - return DfaCondition.getUnknown(); + DfaCondition makeCondition(DfaValueFactory factory, DfaCallArguments arguments) { + return DfaCondition.getUnknown(); } - return makeCondition(factory, arguments); - } - - /** - * @param other other contract condition - * @return true if this contract condition and other condition cannot be fulfilled at the same time - */ - public boolean isExclusive(ContractValue other) { - return false; - } - - public ContractValue invert() { - return null; - } - - /** - * @return true if this contract value represents a bounds-checking condition - */ - public boolean isBoundCheckingCondition() { - return false; - } - - public DfaCallArguments updateArguments(DfaCallArguments arguments, boolean negated) { - return arguments; - } - - public OptionalInt getNullCheckedArgument(boolean equalToNull) { - return getArgumentComparedTo(nullValue(), equalToNull); - } - - public OptionalInt getArgumentComparedTo(ContractValue value, boolean equal) { - return OptionalInt.empty(); - } - - @Nonnull - DfaCallArguments fixArgument(@Nonnull DfaCallArguments arguments, @Nonnull UnaryOperator converter) { - return arguments; - } - - public String getPresentationText(PsiMethod method) { - return toString(); - } - - /** - * @param call call to find the place in - * @return the expression in the call that is the most relevant to the current value - */ - public PsiExpression findPlace(PsiCallExpression call) { - return null; - } - - public static ContractValue qualifier() { - return Qualifier.INSTANCE; - } - - public static ContractValue argument(int index) { - return new Argument(index); - } - - public ContractValue specialField(@Nonnull SpecialField field) { - return new Spec(this, field); - } - - public static ContractValue constant(Object value, @Nonnull PsiType type) { - return new IndependentValue(String.valueOf(value), factory -> factory.getConstant(TypeConversionUtil.computeCastTo(value, type), type) - ); - } - - public static ContractValue booleanValue(boolean value) { - return value ? IndependentValue.TRUE : IndependentValue.FALSE; - } - - public static ContractValue nullValue() { - return IndependentValue.NULL; - } - - public static ContractValue zero() { - return IndependentValue.ZERO; - } - - public static ContractValue condition(ContractValue left, RelationType relation, ContractValue right) { - return new Condition(left, relation, right); - } - - private static class Qualifier extends ContractValue { - static final Qualifier INSTANCE = new Qualifier(); - - @Override - DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { - return arguments.myQualifier; + + public DfaCondition fromCall(DfaValueFactory factory, PsiCallExpression call) { + DfaCallArguments arguments = DfaCallArguments.fromCall(factory, call); + return arguments == null ? DfaCondition.getUnknown() : makeCondition(factory, arguments); } - @Override - public PsiExpression findPlace(PsiCallExpression call) { - if (call instanceof PsiMethodCallExpression) { - return ((PsiMethodCallExpression) call).getMethodExpression().getQualifierExpression(); - } - return null; + /** + * @param other other contract condition + * @return true if this contract condition and other condition cannot be fulfilled at the same time + */ + public boolean isExclusive(ContractValue other) { + return false; } - @Override - @Nonnull - DfaCallArguments fixArgument(@Nonnull DfaCallArguments arguments, @Nonnull UnaryOperator converter) { - if (arguments.myQualifier instanceof DfaTypeValue) { - DfType type = arguments.myQualifier.getDfType(); - DfType newType = converter.apply(type); - if (!type.equals(newType)) { - return new DfaCallArguments(arguments.myQualifier.getFactory().fromDfType(newType), arguments.myArguments, arguments.myMutation); - } - } - return arguments; + public ContractValue invert() { + return null; } - @Override - public String toString() { - return "this"; + /** + * @return true if this contract value represents a bounds-checking condition + */ + public boolean isBoundCheckingCondition() { + return false; } - } - private static final class Argument extends ContractValue { - private final int myIndex; + public DfaCallArguments updateArguments(DfaCallArguments arguments, boolean negated) { + return arguments; + } - Argument(int index) { - myIndex = index; + public OptionalInt getNullCheckedArgument(boolean equalToNull) { + return getArgumentComparedTo(nullValue(), equalToNull); } - @Override - DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { - if (arguments.myArguments.length <= myIndex) { - return factory.getUnknown(); - } - return arguments.myArguments[myIndex]; + public OptionalInt getArgumentComparedTo(ContractValue value, boolean equal) { + return OptionalInt.empty(); } - @Override - public PsiExpression findPlace(PsiCallExpression call) { - PsiExpressionList list = call.getArgumentList(); - if (list != null) { - PsiExpression[] args = list.getExpressions(); - if (myIndex < args.length - 1 || (myIndex == args.length - 1 && !MethodCallUtils.isVarArgCall(call))) { - return args[myIndex]; - } - } - return null; + DfaCallArguments fixArgument(DfaCallArguments arguments, UnaryOperator converter) { + return arguments; } - @Override public String getPresentationText(PsiMethod method) { - PsiParameter[] params = method.getParameterList().getParameters(); - if (myIndex == 0 && params.length == 1) { - return JavaElementKind.PARAMETER.subject(); - } - if (myIndex < params.length) { - PsiParameter param = params[myIndex]; - if (param instanceof ClsParameterImpl && ((ClsParameterImpl) param).isAutoGeneratedName()) { - return "param" + (myIndex + 1); - } - return param.getName(); - } - return toString(); + return toString(); } - @Override - @Nonnull - DfaCallArguments fixArgument(@Nonnull DfaCallArguments arguments, @Nonnull UnaryOperator converter) { - if (arguments.myArguments != null && arguments.myArguments.length > myIndex) { - DfaValue value = arguments.myArguments[myIndex]; - if (value instanceof DfaTypeValue) { - DfType type = value.getDfType(); - DfType newType = converter.apply(type); - if (!type.equals(newType)) { - DfaValue[] clone = arguments.myArguments.clone(); - clone[myIndex] = value.getFactory().fromDfType(newType); - return new DfaCallArguments(arguments.myQualifier, clone, arguments.myMutation); - } - } - } - return arguments; + /** + * @param call call to find the place in + * @return the expression in the call that is the most relevant to the current value + */ + public PsiExpression findPlace(PsiCallExpression call) { + return null; } - @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof Argument && myIndex == ((Argument) obj).myIndex); + public static ContractValue qualifier() { + return Qualifier.INSTANCE; } - @Override - public String toString() { - return "param" + (myIndex + 1); - } - } - - private static class IndependentValue extends ContractValue { - static final IndependentValue NULL = new IndependentValue("null", factory -> factory.getNull()); - static final IndependentValue TRUE = new IndependentValue("true", factory -> factory.getBoolean(true)) { - @Override - public boolean isExclusive(ContractValue other) { - return other == FALSE; - } - }; - static final IndependentValue FALSE = new IndependentValue("false", factory -> factory.getBoolean(false)) { - @Override - public boolean isExclusive(ContractValue other) { - return other == TRUE; - } - }; - static final IndependentValue ZERO = new IndependentValue("0", factory -> factory.getInt(0)); - - private final Function mySupplier; - private final String myPresentation; - - IndependentValue(String presentation, Function supplier) { - mySupplier = supplier; - myPresentation = presentation; + public static ContractValue argument(int index) { + return new Argument(index); } - @Override - DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { - return mySupplier.apply(factory); + public ContractValue specialField(SpecialField field) { + return new Spec(this, field); } - @Override - public String toString() { - return myPresentation; - } - } - - private static final class Spec extends ContractValue { - private final - @Nonnull - ContractValue myQualifier; - private final - @Nonnull - SpecialField myField; - - Spec(@Nonnull ContractValue qualifier, @Nonnull SpecialField field) { - myQualifier = qualifier; - myField = field; + public static ContractValue constant(Object value, PsiType type) { + return new IndependentValue( + String.valueOf(value), + factory -> factory.getConstant(TypeConversionUtil.computeCastTo(value, type), type) + ); } - @Override - DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { - return myField.createValue(factory, myQualifier.makeDfaValue(factory, arguments)); + public static ContractValue booleanValue(boolean value) { + return value ? IndependentValue.TRUE : IndependentValue.FALSE; } - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof Spec)) { - return false; - } - Spec that = (Spec) obj; - return myQualifier.equals(that.myQualifier) && myField == that.myField; + public static ContractValue nullValue() { + return IndependentValue.NULL; } - @Override - @Nonnull - DfaCallArguments fixArgument(@Nonnull DfaCallArguments arguments, @Nonnull UnaryOperator converter) { - return myQualifier.fixArgument(arguments, t -> { - if (!(t instanceof DfReferenceType)) { - return t; - } - DfType sfType = myField.getFromQualifier(t); - DfType newType = converter.apply(sfType); - return newType.equals(sfType) ? t : ((DfReferenceType) t).dropSpecialField().meet(myField.asDfType(newType)); - }); + public static ContractValue zero() { + return IndependentValue.ZERO; } - @Override - public PsiExpression findPlace(PsiCallExpression call) { - return myQualifier.findPlace(call); + public static ContractValue condition(ContractValue left, RelationType relation, ContractValue right) { + return new Condition(left, relation, right); } - @Override - public String getPresentationText(PsiMethod method) { - return myQualifier.getPresentationText(method) + "." + myField + (myField == SpecialField.ARRAY_LENGTH ? "" : "()"); - } + private static class Qualifier extends ContractValue { + static final Qualifier INSTANCE = new Qualifier(); - @Override - public String toString() { - return myQualifier + "." + myField + "()"; - } - } - - /** - * A contract value that represents a relation between two other values - */ - public static class Condition extends ContractValue { - private final ContractValue myLeft, myRight; - private final RelationType myRelationType; - - Condition(ContractValue left, RelationType type, ContractValue right) { - myLeft = left; - myRight = right; - myRelationType = type; - } + @Override + DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { + return arguments.myQualifier; + } - @Override - public boolean isBoundCheckingCondition() { - switch (myRelationType) { - case LE: - case LT: - case GE: - case GT: - return true; - default: - return false; - } - } + @Override + public PsiExpression findPlace(PsiCallExpression call) { + return call instanceof PsiMethodCallExpression methodCall ? methodCall.getMethodExpression().getQualifierExpression() : null; + } - @Override - public boolean isExclusive(ContractValue other) { - if (!(other instanceof Condition)) { - return false; - } - Condition that = (Condition) other; - if (that.myLeft.equals(myLeft) && that.myRight.equals(myRight) && that.myRelationType.getNegated() == myRelationType) { - return true; - } - if (that.myLeft.equals(myRight) && that.myRight.equals(myLeft) && that.myRelationType.getNegated() == myRelationType.getFlipped()) { - return true; - } - if (that.myRelationType == myRelationType) { - if (that.myLeft.equals(myLeft) && that.myRight.isExclusive(myRight)) { - return true; - } - if (that.myLeft.equals(myRight) && that.myRight.isExclusive(myLeft)) { - return true; - } - } - return false; - } + @Override + DfaCallArguments fixArgument(DfaCallArguments arguments, UnaryOperator converter) { + if (arguments.myQualifier instanceof DfaTypeValue) { + DfType type = arguments.myQualifier.getDfType(); + DfType newType = converter.apply(type); + if (!type.equals(newType)) { + return new DfaCallArguments( + arguments.myQualifier.getFactory().fromDfType(newType), + arguments.myArguments, + arguments.myMutation + ); + } + } + return arguments; + } - @Override - public DfaCallArguments updateArguments(DfaCallArguments arguments, boolean negated) { - ContractValue target = getValueComparedTo(nullValue(), negated); - if (target != null) { - return target.fixArgument(arguments, dfType -> dfType.meet(DfaNullability.NOT_NULL.asDfType())); - } - target = getValueComparedTo(nullValue(), !negated); - if (target != null) { - return target.fixArgument(arguments, dfType -> dfType.meet(DfaNullability.NULL.asDfType())); - } - return arguments; + @Override + public String toString() { + return "this"; + } } - private - @Nullable - ContractValue getValueComparedTo(ContractValue value, boolean equal) { - if (myRelationType == RelationType.equivalence(equal)) { - ContractValue other; - if (myLeft == value) { - other = myRight; - } else if (myRight == value) { - other = myLeft; - } else { - return null; - } - return other; - } - if (value == IndependentValue.FALSE) { - return getValueComparedTo(IndependentValue.TRUE, !equal); - } - return null; - } + private static final class Argument extends ContractValue { + private final int myIndex; - @Override - public OptionalInt getArgumentComparedTo(ContractValue value, boolean equal) { - ContractValue other = getValueComparedTo(value, equal); - return other instanceof Argument ? OptionalInt.of(((Argument) other).myIndex) : OptionalInt.empty(); - } + Argument(int index) { + myIndex = index; + } - @Override - DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { - return factory.getUnknown(); - } + @Override + DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { + if (arguments.myArguments.length <= myIndex) { + return factory.getUnknown(); + } + return arguments.myArguments[myIndex]; + } - @Nonnull - @Override - DfaCondition makeCondition(DfaValueFactory factory, DfaCallArguments arguments) { - DfaValue left = myLeft.makeDfaValue(factory, arguments); - DfaValue right = myRight.makeDfaValue(factory, arguments); - if (left.getDfType() instanceof DfPrimitiveType) { - right = DfaUtil.boxUnbox(right, left.getType()); - } - if (right.getDfType() instanceof DfPrimitiveType) { - left = DfaUtil.boxUnbox(left, right.getType()); - } - return left.cond(myRelationType, right); - } + @Override + public PsiExpression findPlace(PsiCallExpression call) { + PsiExpressionList list = call.getArgumentList(); + if (list != null) { + PsiExpression[] args = list.getExpressions(); + if (myIndex < args.length - 1 || (myIndex == args.length - 1 && !MethodCallUtils.isVarArgCall(call))) { + return args[myIndex]; + } + } + return null; + } - @Override - public String getPresentationText(PsiMethod method) { - if (myLeft instanceof IndependentValue) { - return myRight.getPresentationText(method) + " " + myRelationType.getFlipped() + " " + myLeft.getPresentationText(method); - } - return myLeft.getPresentationText(method) + " " + myRelationType + " " + myRight.getPresentationText(method); + @Override + public String getPresentationText(PsiMethod method) { + PsiParameter[] params = method.getParameterList().getParameters(); + if (myIndex == 0 && params.length == 1) { + return JavaElementKind.PARAMETER.subject().get(); + } + if (myIndex < params.length) { + PsiParameter param = params[myIndex]; + if (param instanceof ClsParameterImpl && ((ClsParameterImpl)param).isAutoGeneratedName()) { + return "param" + (myIndex + 1); + } + return param.getName(); + } + return toString(); + } + + @Override + DfaCallArguments fixArgument(DfaCallArguments arguments, UnaryOperator converter) { + if (arguments.myArguments != null && arguments.myArguments.length > myIndex) { + DfaValue value = arguments.myArguments[myIndex]; + if (value instanceof DfaTypeValue) { + DfType type = value.getDfType(); + DfType newType = converter.apply(type); + if (!type.equals(newType)) { + DfaValue[] clone = arguments.myArguments.clone(); + clone[myIndex] = value.getFactory().fromDfType(newType); + return new DfaCallArguments(arguments.myQualifier, clone, arguments.myMutation); + } + } + } + return arguments; + } + + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof Argument && myIndex == ((Argument)obj).myIndex); + } + + @Override + public String toString() { + return "param" + (myIndex + 1); + } } - /** - * @return condition relation type - */ - public - @Nonnull - RelationType getRelationType() { - return myRelationType; + private static class IndependentValue extends ContractValue { + static final IndependentValue NULL = new IndependentValue("null", DfaValueFactory::getNull); + static final IndependentValue TRUE = new IndependentValue("true", factory -> factory.getBoolean(true)) { + @Override + public boolean isExclusive(ContractValue other) { + return other == FALSE; + } + }; + static final IndependentValue FALSE = new IndependentValue("false", factory -> factory.getBoolean(false)) { + @Override + public boolean isExclusive(ContractValue other) { + return other == TRUE; + } + }; + static final IndependentValue ZERO = new IndependentValue("0", factory -> factory.getInt(0)); + + private final Function mySupplier; + private final String myPresentation; + + IndependentValue(String presentation, Function supplier) { + mySupplier = supplier; + myPresentation = presentation; + } + + @Override + DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { + return mySupplier.apply(factory); + } + + @Override + public String toString() { + return myPresentation; + } } - /** - * @return condition left operand - */ - public - @Nonnull - ContractValue getLeft() { - return myLeft; + private static final class Spec extends ContractValue { + private final ContractValue myQualifier; + private final SpecialField myField; + + Spec(ContractValue qualifier, SpecialField field) { + myQualifier = qualifier; + myField = field; + } + + @Override + DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { + return myField.createValue(factory, myQualifier.makeDfaValue(factory, arguments)); + } + + @Override + public boolean equals(Object obj) { + return obj == this + || obj instanceof Spec that + && myQualifier.equals(that.myQualifier) + && myField == that.myField; + } + + @Override + DfaCallArguments fixArgument(DfaCallArguments arguments, UnaryOperator converter) { + return myQualifier.fixArgument( + arguments, + t -> { + if (t instanceof DfReferenceType drt) { + DfType sfType = myField.getFromQualifier(drt); + DfType newType = converter.apply(sfType); + return newType.equals(sfType) ? drt : drt.dropSpecialField().meet(myField.asDfType(newType)); + } + else { + return t; + } + } + ); + } + + @Override + public PsiExpression findPlace(PsiCallExpression call) { + return myQualifier.findPlace(call); + } + + @Override + public String getPresentationText(PsiMethod method) { + return myQualifier.getPresentationText(method) + "." + myField + (myField == SpecialField.ARRAY_LENGTH ? "" : "()"); + } + + @Override + public String toString() { + return myQualifier + "." + myField + "()"; + } } /** - * @return condition right operand + * A contract value that represents a relation between two other values */ - public - @Nonnull - ContractValue getRight() { - return myRight; - } + public static class Condition extends ContractValue { + private final ContractValue myLeft, myRight; + private final RelationType myRelationType; + + Condition(ContractValue left, RelationType type, ContractValue right) { + myLeft = left; + myRight = right; + myRelationType = type; + } - @Override - public ContractValue invert() { - return new Condition(myLeft, myRelationType.getNegated(), myRight); - } + @Override + public boolean isBoundCheckingCondition() { + return switch (myRelationType) { + case LE, LT, GE, GT -> true; + default -> false; + }; + } + + @Override + public boolean isExclusive(ContractValue other) { + if (!(other instanceof Condition)) { + return false; + } + Condition that = (Condition)other; + if (that.myLeft.equals(myLeft) && that.myRight.equals(myRight) && that.myRelationType.getNegated() == myRelationType) { + return true; + } + if (that.myLeft.equals(myRight) && that.myRight.equals(myLeft) && that.myRelationType.getNegated() == myRelationType.getFlipped()) { + return true; + } + if (that.myRelationType == myRelationType) { + if (that.myLeft.equals(myLeft) && that.myRight.isExclusive(myRight)) { + return true; + } + if (that.myLeft.equals(myRight) && that.myRight.isExclusive(myLeft)) { + return true; + } + } + return false; + } + + @Override + public DfaCallArguments updateArguments(DfaCallArguments arguments, boolean negated) { + ContractValue target = getValueComparedTo(nullValue(), negated); + if (target != null) { + return target.fixArgument(arguments, dfType -> dfType.meet(DfaNullability.NOT_NULL.asDfType())); + } + target = getValueComparedTo(nullValue(), !negated); + if (target != null) { + return target.fixArgument(arguments, dfType -> dfType.meet(DfaNullability.NULL.asDfType())); + } + return arguments; + } + + private + @Nullable + ContractValue getValueComparedTo(ContractValue value, boolean equal) { + if (myRelationType == RelationType.equivalence(equal)) { + ContractValue other; + if (myLeft == value) { + other = myRight; + } + else if (myRight == value) { + other = myLeft; + } + else { + return null; + } + return other; + } + if (value == IndependentValue.FALSE) { + return getValueComparedTo(IndependentValue.TRUE, !equal); + } + return null; + } + + @Override + public OptionalInt getArgumentComparedTo(ContractValue value, boolean equal) { + ContractValue other = getValueComparedTo(value, equal); + return other instanceof Argument argument ? OptionalInt.of(argument.myIndex) : OptionalInt.empty(); + } + + @Override + DfaValue makeDfaValue(DfaValueFactory factory, DfaCallArguments arguments) { + return factory.getUnknown(); + } - @Override - public String toString() { - return myLeft + " " + myRelationType + " " + myRight; + @Override + DfaCondition makeCondition(DfaValueFactory factory, DfaCallArguments arguments) { + DfaValue left = myLeft.makeDfaValue(factory, arguments); + DfaValue right = myRight.makeDfaValue(factory, arguments); + if (left.getDfType() instanceof DfPrimitiveType) { + right = DfaUtil.boxUnbox(right, left.getType()); + } + if (right.getDfType() instanceof DfPrimitiveType) { + left = DfaUtil.boxUnbox(left, right.getType()); + } + return left.cond(myRelationType, right); + } + + @Override + public String getPresentationText(PsiMethod method) { + if (myLeft instanceof IndependentValue) { + return myRight.getPresentationText(method) + " " + myRelationType.getFlipped() + " " + myLeft.getPresentationText(method); + } + return myLeft.getPresentationText(method) + " " + myRelationType + " " + myRight.getPresentationText(method); + } + + /** + * @return condition relation type + */ + public + RelationType getRelationType() { + return myRelationType; + } + + /** + * @return condition left operand + */ + public + ContractValue getLeft() { + return myLeft; + } + + /** + * @return condition right operand + */ + public + ContractValue getRight() { + return myRight; + } + + @Override + public ContractValue invert() { + return new Condition(myLeft, myRelationType.getNegated(), myRight); + } + + @Override + public String toString() { + return myLeft + " " + myRelationType + " " + myRight; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlow.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlow.java index 8045eaa7d5..a71cc546fe 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlow.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlow.java @@ -12,7 +12,7 @@ import consulo.util.collection.primitive.objects.ObjectMaps; import one.util.streamex.StreamEx; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlowAnalyzer.java index bbf5ece911..3dd9ee48ee 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -2,6 +2,7 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow; import com.intellij.java.analysis.codeInsight.daemon.JavaImplicitUsageProvider; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.UnusedSymbolUtil; import com.intellij.java.analysis.impl.codeInspection.dataFlow.ControlFlow.ControlFlowOffset; import com.intellij.java.analysis.impl.codeInspection.dataFlow.Trap.InsideFinally; @@ -17,6 +18,7 @@ import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.language.impl.codeInsight.ExceptionUtil; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.augment.PsiAugmentProvider; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; @@ -35,11 +37,10 @@ import consulo.util.collection.FList; import consulo.util.collection.FactoryMap; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.language.psi.CommonClassNames.*; @@ -74,7 +75,7 @@ private static class CannotAnalyzeException extends RuntimeException { * If PsiClass then class initializers + field initializers will be analyzed * @param inlining if true inlining is performed for known method calls */ - ControlFlowAnalyzer(final DfaValueFactory valueFactory, @Nonnull PsiElement codeFragment, boolean inlining) { + ControlFlowAnalyzer(final DfaValueFactory valueFactory, PsiElement codeFragment, boolean inlining) { myInlining = inlining; myFactory = valueFactory; myCodeFragment = codeFragment; @@ -92,13 +93,14 @@ private void buildClassInitializerFlow(PsiClass psiClass, boolean isStatic) { } } if (!isStatic && - ImplicitUsageProvider.EP_NAME.getExtensionList().stream().anyMatch(p -> JavaImplicitUsageProvider.isClassWithCustomizedInitialization(p, psiClass))) { + myProject.getExtensionList(ImplicitUsageProvider.class).stream().anyMatch(p -> JavaImplicitUsageProvider.isClassWithCustomizedInitialization(p, psiClass))) { addInstruction(new EscapeInstruction(Collections.singleton(getFactory().getVarFactory().createThisValue(psiClass)))); addInstruction(new FlushFieldsInstruction()); } for (PsiElement element = psiClass.getFirstChild(); element != null; element = element.getNextSibling()) { - if (((element instanceof PsiField && ((PsiField) element).hasInitializer()) || element instanceof PsiClassInitializer) && - ((PsiMember) element).hasModifierProperty(PsiModifier.STATIC) == isStatic) { + if ((element instanceof PsiField field && + field.hasInitializer() && PsiAugmentProvider.canTrustFieldInitializer(field) || element instanceof PsiClassInitializer) && + ((PsiMember)element).hasModifierProperty(PsiModifier.STATIC) == isStatic) { element.accept(this); } } @@ -138,7 +140,6 @@ DfaValueFactory getFactory() { } private - @Nonnull PsiClassType createClassType(GlobalSearchScope scope, String fqn) { PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(fqn, scope); if (aClass != null) { @@ -182,7 +183,6 @@ private void finishElement(PsiElement element) { } private - @Nonnull List getSynthetics(PsiElement element) { int startOffset = getStartOffset(element).getInstructionOffset(); List synthetics = new ArrayList<>(); @@ -201,7 +201,7 @@ List getSynthetics(PsiElement element) { } @Override - public void visitErrorElement(@Nonnull PsiErrorElement element) { + public void visitErrorElement(PsiErrorElement element) { throw new CannotAnalyzeException(); } @@ -404,7 +404,8 @@ public void visitYieldStatement(PsiYieldStatement statement) { PsiExpression expression = statement.getExpression(); if (enclosing != null && myExpressionBlockContext != null && myExpressionBlockContext.myCodeBlock == enclosing.getBody()) { myExpressionBlockContext.generateReturn(expression, this); - } else { + } + else { // yield in incorrect location or only part of switch is analyzed if (expression != null) { expression.accept(this); @@ -415,11 +416,11 @@ public void visitYieldStatement(PsiYieldStatement statement) { finishElement(statement); } - void addNullCheck(@Nonnull PsiExpression expression) { + void addNullCheck(PsiExpression expression) { addNullCheck(NullabilityProblemKind.fromContext(expression, myCustomNullabilityProblems)); } - void addNullCheck(@Nullable NullabilityProblemKind.NullabilityProblem problem) { + void addNullCheck(NullabilityProblemKind.@Nullable NullabilityProblem problem) { if (problem != null) { DfaControlTransferValue transfer = shouldHandleException() && problem.thrownException() != null ? myFactory.controlTransfer(myExceptionCache.get(problem.thrownException()), myTrapStack) : null; @@ -437,19 +438,17 @@ private void jumpOut(PsiElement exitedStatement) { } } - private void controlTransfer(@Nonnull TransferTarget target, FList traps) { + private void controlTransfer(TransferTarget target, FList traps) { addInstruction(new ControlTransferInstruction(myFactory.controlTransfer(target, traps))); } private - @Nonnull FList getTrapsInsideElement(PsiElement element) { return FList.createFromReversed(ContainerUtil.reverse( ContainerUtil.findAll(myTrapStack, cd -> PsiTreeUtil.isAncestor(element, cd.getAnchor(), true)))); } private - @Nonnull List getVariablesInside(PsiElement exitedStatement) { return ContainerUtil.map(PsiTreeUtil.findChildrenOfType(exitedStatement, PsiVariable.class), myFactory.getVarFactory()::createVariableValue); @@ -918,7 +917,7 @@ public void visitSwitchExpression(PsiSwitchExpression expression) { } } - private void processSwitch(@Nonnull PsiSwitchBlock switchBlock) { + private void processSwitch(PsiSwitchBlock switchBlock) { PsiExpression selector = PsiUtil.skipParenthesizedExprDown(switchBlock.getExpression()); DfaVariableValue expressionValue = null; boolean syntheticVar = true; @@ -1255,8 +1254,9 @@ public void visitArrayAccessExpression(PsiArrayAccessExpression expression) { if (toPush == null) { toPush = myFactory.getObjectType(expression.getType(), Nullability.UNKNOWN); } - DfaControlTransferValue transfer = - shouldHandleException() ? myFactory.controlTransfer(myExceptionCache.get("java.lang.ArrayIndexOutOfBoundsException"), myTrapStack) : null; + DfaControlTransferValue transfer = shouldHandleException() + ? myFactory.controlTransfer(myExceptionCache.get(CommonClassNames.JAVA_LANG_ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION), myTrapStack) + : null; addInstruction(new ArrayAccessInstruction(toPush, expression, transfer)); addNullCheck(expression); finishElement(expression); @@ -1459,11 +1459,11 @@ private void acceptBinaryRightOperand(@Nullable IElementType op, PsiType type, } } - void generateBoxingUnboxingInstructionFor(@Nonnull PsiExpression expression, PsiType expectedType) { + void generateBoxingUnboxingInstructionFor(PsiExpression expression, PsiType expectedType) { generateBoxingUnboxingInstructionFor(expression, expression.getType(), expectedType, false); } - void generateBoxingUnboxingInstructionFor(@Nonnull PsiExpression context, PsiType actualType, PsiType expectedType, boolean explicit) { + void generateBoxingUnboxingInstructionFor(PsiExpression context, PsiType actualType, PsiType expectedType, boolean explicit) { if (PsiType.VOID.equals(expectedType)) { return; } @@ -1697,7 +1697,7 @@ private void finishCall(PsiMethodCallExpression call) { finishElement(call); } - void addBareCall(@Nullable PsiMethodCallExpression expression, @Nonnull PsiReferenceExpression reference) { + void addBareCall(@Nullable PsiMethodCallExpression expression, PsiReferenceExpression reference) { addConditionalErrorThrow(); PsiMethod method = ObjectUtil.tryCast(reference.resolve(), PsiMethod.class); List contracts = @@ -2019,8 +2019,9 @@ public void visitTypeCastExpression(PsiTypeCastExpression castExpression) { final PsiTypeElement typeElement = castExpression.getCastType(); if (typeElement != null && operand != null && operand.getType() != null && !(typeElement.getType() instanceof PsiPrimitiveType)) { - DfaControlTransferValue transfer = - shouldHandleException() ? myFactory.controlTransfer(myExceptionCache.get("java.lang.ClassCastException"), myTrapStack) : null; + DfaControlTransferValue transfer = shouldHandleException() + ? myFactory.controlTransfer(myExceptionCache.get(CommonClassNames.JAVA_LANG_CLASS_CAST_EXCEPTION), myTrapStack) + : null; addInstruction(new TypeCastInstruction(castExpression, operand, typeElement.getType(), transfer)); } finishElement(castExpression); @@ -2037,7 +2038,7 @@ final boolean wasAdded(PsiElement element) { myCurrentFlow.getEndOffset(element).getInstructionOffset() > -1; } - public void removeLambda(@Nonnull PsiLambdaExpression lambda) { + public void removeLambda(PsiLambdaExpression lambda) { int start = myCurrentFlow.getStartOffset(lambda).getInstructionOffset(); int end = myCurrentFlow.getEndOffset(lambda).getInstructionOffset(); for (int i = start; i < end; i++) { @@ -2055,13 +2056,13 @@ public void removeLambda(@Nonnull PsiLambdaExpression lambda) { * @param resultNullability desired nullability returned by block return statement * @param target a variable to store the block result (returned via {@code return} statement) */ - void inlineBlock(@Nonnull PsiCodeBlock block, @Nonnull Nullability resultNullability, @Nonnull DfaVariableValue target) { + void inlineBlock(PsiCodeBlock block, Nullability resultNullability, DfaVariableValue target) { enterExpressionBlock(block, resultNullability, target); block.accept(this); exitExpressionBlock(); } - private void enterExpressionBlock(@Nonnull PsiCodeBlock block, @Nonnull Nullability resultNullability, @Nonnull DfaVariableValue target) { + private void enterExpressionBlock(PsiCodeBlock block, Nullability resultNullability, DfaVariableValue target) { // Transfer value is pushed to avoid emptying stack beyond this point pushTrap(new Trap.InsideInlinedBlock(block)); addInstruction(new PushInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, FList.emptyList()), null)); @@ -2086,7 +2087,7 @@ private void exitExpressionBlock() { * @param problem a problem to check. Use {@link NullabilityProblemKind#noProblem} to suppress the problem which * would be detected by default. */ - void addCustomNullabilityProblem(@Nonnull PsiExpression expression, @Nonnull NullabilityProblemKind problem) { + void addCustomNullabilityProblem(PsiExpression expression, NullabilityProblemKind problem) { myCustomNullabilityProblems.put(expression, problem); } @@ -2096,7 +2097,7 @@ void addCustomNullabilityProblem(@Nonnull PsiExpression expression, @Nonnull Nul * * @param expression an expression to deregister. */ - void removeCustomNullabilityProblem(@Nonnull PsiExpression expression) { + void removeCustomNullabilityProblem(PsiExpression expression) { myCustomNullabilityProblems.remove(expression); } @@ -2106,7 +2107,6 @@ void removeCustomNullabilityProblem(@Nonnull PsiExpression expression) { * @param type a type of variable to create * @return newly created variable */ - @Nonnull DfaVariableValue createTempVariable(@Nullable PsiType type) { if (type == null) { type = PsiType.VOID; @@ -2120,7 +2120,7 @@ DfaVariableValue createTempVariable(@Nullable PsiType type) { * @param variable to check * @return true if supplied variable is a temp variable. */ - public static boolean isTempVariable(@Nonnull DfaVariableValue variable) { + public static boolean isTempVariable(DfaVariableValue variable) { return variable.getDescriptor() instanceof Synthetic; } @@ -2143,7 +2143,6 @@ private Synthetic(int location, PsiType type) { @Override public - @Nonnull String toString() { return "tmp$" + myLocation; } @@ -2166,17 +2165,15 @@ static class ExpressionBlockContext { @Nullable ExpressionBlockContext myPreviousBlock; final - @Nonnull PsiCodeBlock myCodeBlock; final boolean myForceNonNullBlockResult; final - @Nonnull DfaVariableValue myTarget; ExpressionBlockContext(@Nullable ExpressionBlockContext previousBlock, - @Nonnull PsiCodeBlock codeBlock, + PsiCodeBlock codeBlock, boolean forceNonNullBlockResult, - @Nonnull DfaVariableValue target) { + DfaVariableValue target) { myPreviousBlock = previousBlock; myCodeBlock = codeBlock; myForceNonNullBlockResult = forceNonNullBlockResult; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferHandler.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferHandler.java index f54f0ad256..712afb061a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferHandler.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferHandler.java @@ -25,7 +25,6 @@ import consulo.util.collection.ContainerUtil; import consulo.util.collection.FList; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -51,7 +50,6 @@ class ControlTransferHandler this.traps = transferValue.getTraps(); } - @Nonnull public List dispatch() { Trap head = traps.getHead(); @@ -90,7 +88,7 @@ public DataFlowRunner getRunner() return runner; } - public List processCatches(@Nonnull TypeConstraint thrownValue, Map catches) + public List processCatches(TypeConstraint thrownValue, Map catches) { List result = new ArrayList<>(); @@ -134,7 +132,6 @@ public List processCatches(@Nonnull TypeConstraint thrownVa return ContainerUtil.concat(result, dispatch()); } - @Nonnull private List allCaughtTypes(PsiParameter param) { PsiType type = param.getType(); @@ -143,7 +140,6 @@ private List allCaughtTypes(PsiParameter param) return psiTypes.stream().map(TypeConstraints::instanceOf).collect(Collectors.toList()); } - @Nonnull private DfaMemoryState stateForCatchClause(PsiParameter param, TypeConstraint constraint) { DfaMemoryState catchingCopy = state.createCopy(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferInstruction.java index 0924c9e8a8..9a991a78a7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/ControlTransferInstruction.java @@ -19,7 +19,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.instructions.Instruction; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -31,10 +30,9 @@ */ public class ControlTransferInstruction extends Instruction { - @Nonnull private DfaControlTransferValue transfer; - public ControlTransferInstruction(@Nonnull DfaControlTransferValue transfer) + public ControlTransferInstruction(DfaControlTransferValue transfer) { this.transfer = transfer; this.transfer.getTraps().forEach(trap -> trap.link(this)); @@ -46,13 +44,11 @@ public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState state, return visitor.visitControlTransfer(this, runner, state); } - @Nonnull public DfaControlTransferValue getTransfer() { return transfer; } - @Nonnull public List getPossibleTargetIndices() { List trapPossibleTargets = transfer.getTraps().stream().flatMap(trap -> trap.getPossibleTargets().stream()).collect(Collectors.toList()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CustomMethodHandlers.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CustomMethodHandlers.java index 01d2b63e7f..f7211d6e96 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/CustomMethodHandlers.java @@ -21,10 +21,9 @@ import consulo.language.psi.util.LanguageCachedValueUtil; import consulo.util.collection.ArrayUtil; import consulo.util.lang.reflect.ReflectionUtil; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -49,7 +48,7 @@ final class CustomMethodHandlers { staticCall(JAVA_LANG_FLOAT, "toString", "toHexString").parameterTypes("float"), staticCall(JAVA_LANG_BYTE, "toString").parameterTypes("byte"), staticCall(JAVA_LANG_SHORT, "toString").parameterTypes("short"), - staticCall(JAVA_LANG_BOOLEAN, "parseBoolean").parameterTypes("java.lang.String"), + staticCall(JAVA_LANG_BOOLEAN, "parseBoolean").parameterTypes(CommonClassNames.JAVA_LANG_STRING), staticCall(JAVA_LANG_INTEGER, "compare", "compareUnsigned").parameterTypes("int", "int"), staticCall(JAVA_LANG_LONG, "compare", "compareUnsigned").parameterTypes("long", "long"), staticCall(JAVA_LANG_DOUBLE, "compare").parameterTypes("double", "double"), @@ -70,7 +69,6 @@ final class CustomMethodHandlers { interface CustomMethodHandler { - @Nonnull DfType getMethodResult(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory, @@ -155,7 +153,6 @@ private static boolean isConstantCall(PsiMethod method) { } private static - @Nonnull DfType handleConstantCall(DfaCallArguments arguments, DfaMemoryState state, PsiMethod method) { PsiType returnType = method.getReturnType(); if (returnType == null) { @@ -199,7 +196,6 @@ private static Method toJvmMethod(PsiMethod method) { return LanguageCachedValueUtil.getCachedValue(method, new CachedValueProvider<>() { @Override public - @Nonnull Result compute() { Method reflection = getMethod(); return Result.create(reflection, method); @@ -266,7 +262,6 @@ Method getMethod() { } private static - @Nonnull DfType indexOf(DfaValue qualifier, DfaMemoryState memState, DfaValueFactory factory, @@ -277,7 +272,6 @@ DfType indexOf(DfaValue qualifier, } private static - @Nonnull DfType collectionFactory(DfaCallArguments args, DfaMemoryState memState, DfaValueFactory factory, PsiMethod method) { @@ -314,7 +308,6 @@ private static DfType getEmptyCollectionConstant(PsiMethod method) { } private static - @Nonnull DfType substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, PsiType stringType) { if (stringType == null || !stringType.equalsToText(JAVA_LANG_STRING)) { return TOP; @@ -341,7 +334,6 @@ DfType substring(DfaCallArguments args, DfaMemoryState state, DfaValueFactory fa } private static - @Nonnull DfType mathAbs(DfaValue[] args, DfaMemoryState memState, boolean isLong) { DfaValue arg = ArrayUtil.getFirstElement(args); if (arg == null) { @@ -353,7 +345,6 @@ DfType mathAbs(DfaValue[] args, DfaMemoryState memState, boolean isLong) { } private static - @Nonnull DfType calendarGet(DfaValue[] arguments, DfaMemoryState state) { if (arguments.length != 1) { return TOP; @@ -394,7 +385,6 @@ DfType calendarGet(DfaValue[] arguments, DfaMemoryState state) { } private static - @Nonnull DfType skip(DfaValue[] arguments, DfaMemoryState state) { if (arguments.length != 1) { return TOP; @@ -404,7 +394,6 @@ DfType skip(DfaValue[] arguments, DfaMemoryState state) { } private static - @Nonnull DfType numberAsString(DfaCallArguments args, DfaMemoryState state, int bitsPerChar, int maxBits) { DfaValue arg = args.myArguments[0]; if (arg == null) { @@ -417,7 +406,6 @@ DfType numberAsString(DfaCallArguments args, DfaMemoryState state, int bitsPerCh } private static - @Nonnull DfType enumName(DfaValue qualifier, DfaMemoryState state, PsiType type) { DfType dfType = state.getDfType(qualifier); PsiEnumConstant value = DfConstantType.getConstantOfType(dfType, PsiEnumConstant.class); @@ -437,7 +425,6 @@ private static Object getConstantValue(DfaMemoryState memoryState, DfaValue valu } private static - @Nonnull DfType randomNextInt(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory, PsiMethod method) { DfaValue[] values = arguments.myArguments; if (values == null) { @@ -459,7 +446,6 @@ DfType randomNextInt(DfaCallArguments arguments, DfaMemoryState state, DfaValueF } private static - @Nonnull DfType className(DfaMemoryState memState, DfaValue qualifier, String name, diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionBase.java index ab33125976..9e3b62850c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -1,10 +1,10 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.codeInspection.AbstractBaseJavaLocalInspectionTool; import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationPsiFix; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; +import com.intellij.java.analysis.impl.codeInspection.EquivalenceChecker; import com.intellij.java.analysis.impl.codeInspection.SetInspectionOptionFix; import com.intellij.java.analysis.impl.codeInspection.dataFlow.NullabilityProblemKind.NullabilityProblem; import com.intellij.java.analysis.impl.codeInspection.dataFlow.fix.*; @@ -16,1462 +16,1604 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.nullable.NullableStuffInspectionBase; import com.intellij.java.language.codeInsight.*; -import com.intellij.java.language.impl.codeInsight.ExpressionUtil; import com.intellij.java.language.impl.psi.impl.PsiImplUtil; -import com.intellij.java.language.impl.psi.util.JavaPsiPatternUtil; import com.intellij.java.language.psi.*; -import com.intellij.java.language.psi.util.PsiExpressionTrimRenderer; -import com.intellij.java.language.psi.util.PsiTypesUtil; -import com.intellij.java.language.psi.util.PsiUtil; -import com.intellij.java.language.psi.util.TypeConversionUtil; +import com.intellij.java.language.psi.util.*; import com.intellij.java.language.util.JavaPsiConstructorUtil; import com.siyeh.ig.bugs.EqualsWithItselfInspection; import com.siyeh.ig.fixes.EqualsToEqualityFix; import com.siyeh.ig.psiutils.*; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.Application; import consulo.application.util.registry.Registry; -import consulo.document.util.TextRange; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.ast.IElementType; import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.intention.BaseIntentionAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; -import consulo.util.collection.SmartList; import consulo.util.lang.StringUtil; import consulo.util.lang.ThreeState; -import consulo.util.xml.serializer.WriteExternalException; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import org.jdom.Element; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.PropertyKey; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; import java.util.*; import java.util.function.Consumer; +import java.util.function.Function; import static consulo.util.lang.ObjectUtil.tryCast; -public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool { - static final Logger LOG = Logger.getInstance(DataFlowInspectionBase.class); - @NonNls - private static final String SHORT_NAME = "ConstantConditions"; - public boolean SUGGEST_NULLABLE_ANNOTATIONS; - public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS; - public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE; - public boolean IGNORE_ASSERT_STATEMENTS; - public boolean REPORT_CONSTANT_REFERENCE_VALUES = true; - public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; - public boolean REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true; - public boolean REPORT_UNSOUND_WARNINGS = true; - - @Override - public JComponent createOptionsPanel() { - throw new RuntimeException("no UI in headless mode"); - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - node.addContent(new Element("option").setAttribute("name", "SUGGEST_NULLABLE_ANNOTATIONS").setAttribute("value", String.valueOf(SUGGEST_NULLABLE_ANNOTATIONS))); - node.addContent(new Element("option").setAttribute("name", "DONT_REPORT_TRUE_ASSERT_STATEMENTS").setAttribute("value", String.valueOf(DONT_REPORT_TRUE_ASSERT_STATEMENTS))); - if (IGNORE_ASSERT_STATEMENTS) { - node.addContent(new Element("option").setAttribute("name", "IGNORE_ASSERT_STATEMENTS").setAttribute("value", "true")); - } - if (!REPORT_CONSTANT_REFERENCE_VALUES) { - node.addContent(new Element("option").setAttribute("name", "REPORT_CONSTANT_REFERENCE_VALUES").setAttribute("value", "false")); - } - if (TREAT_UNKNOWN_MEMBERS_AS_NULLABLE) { - node.addContent(new Element("option").setAttribute("name", "TREAT_UNKNOWN_MEMBERS_AS_NULLABLE").setAttribute("value", "true")); - } - if (!REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER) { - node.addContent(new Element("option").setAttribute("name", "REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER").setAttribute("value", "false")); - } - if (!REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL) { - node.addContent(new Element("option").setAttribute("name", "REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL").setAttribute("value", "false")); - } - if (!REPORT_UNSOUND_WARNINGS) { - node.addContent(new Element("option").setAttribute("name", "REPORT_UNSOUND_WARNINGS").setAttribute("value", "false")); - } - } - - @Override - public - @Nonnull - PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitClass(PsiClass aClass) { - if (aClass instanceof PsiTypeParameter) { - return; - } - if (PsiUtil.isLocalOrAnonymousClass(aClass) && !(aClass instanceof PsiEnumConstantInitializer)) { - return; - } - - final DataFlowRunner runner = new DataFlowRunner(holder.getProject(), aClass, TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, - ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS)); - DataFlowInstructionVisitor visitor = - analyzeDfaWithNestedClosures(aClass, holder, runner, Collections.singletonList(runner.createMemoryState())); - List states = visitor.getEndOfInitializerStates(); - boolean physical = aClass.isPhysical(); - for (PsiMethod method : aClass.getConstructors()) { - if (physical && !method.isPhysical()) { - // Constructor could be provided by, e.g. Lombok plugin: ignore it, we won't report any problems inside anyway - continue; - } - List initialStates; - PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method); - if (JavaPsiConstructorUtil.isChainedConstructorCall(call) || (call == null && DfaUtil.hasImplicitImpureSuperCall(aClass, method))) { - initialStates = Collections.singletonList(runner.createMemoryState()); - } else { - initialStates = StreamEx.of(states).map(DfaMemoryState::createCopy).toList(); - } - analyzeMethod(method, runner, initialStates); - } - } - - @Override - public void visitMethod(PsiMethod method) { - if (method.isConstructor()) { - return; - } - final DataFlowRunner runner = new DataFlowRunner( - holder.getProject(), method.getBody(), TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, ThreeState.fromBoolean(IGNORE_ASSERT_STATEMENTS)); - analyzeMethod(method, runner, Collections.singletonList(runner.createMemoryState())); - } - - private void analyzeMethod(PsiMethod method, DataFlowRunner runner, List initialStates) { - PsiCodeBlock scope = method.getBody(); - if (scope == null) { - return; - } - PsiClass containingClass = PsiTreeUtil.getParentOfType(method, PsiClass.class); - if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass) && !(containingClass instanceof PsiEnumConstantInitializer)) { - return; - } - - analyzeDfaWithNestedClosures(scope, holder, runner, initialStates); - analyzeNullLiteralMethodArguments(method, holder); - } - - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - super.visitMethodReferenceExpression(expression); - if (!REPORT_UNSOUND_WARNINGS) { - return; - } - final PsiElement resolve = expression.resolve(); - if (resolve instanceof PsiMethod) { - final PsiType methodReturnType = ((PsiMethod) resolve).getReturnType(); - if (TypeConversionUtil.isPrimitiveWrapper(methodReturnType) && NullableNotNullManager.isNullable((PsiMethod) resolve)) { - final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); - if (TypeConversionUtil.isPrimitiveAndNotNull(returnType)) { - holder.registerProblem(expression, JavaAnalysisBundle.message("dataflow.message.unboxing.method.reference")); - } - } - } - } - - @Override - public void visitIfStatement(PsiIfStatement statement) { - PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition()); - if (BoolUtils.isBooleanLiteral(condition)) { - LocalQuickFix fix = createSimplifyBooleanExpressionFix(condition, condition.textMatches(PsiKeyword.TRUE)); - holder.registerProblem(condition, JavaAnalysisBundle - .message("dataflow.message.constant.no.ref", condition.textMatches(PsiKeyword.TRUE) ? 1 : 0), fix); - } - } - - @Override - public void visitWhileStatement(PsiWhileStatement statement) { - checkLoopCondition(statement.getCondition()); - } - - @Override - public void visitDoWhileStatement(PsiDoWhileStatement statement) { - checkLoopCondition(statement.getCondition()); - } - - @Override - public void visitForStatement(PsiForStatement statement) { - checkLoopCondition(statement.getCondition()); - } - - private void checkLoopCondition(PsiExpression condition) { - condition = PsiUtil.skipParenthesizedExprDown(condition); - if (condition != null && condition.textMatches(PsiKeyword.FALSE)) { - holder.registerProblem(condition, JavaAnalysisBundle.message("dataflow.message.constant.no.ref", 0), createSimplifyBooleanExpressionFix(condition, false)); - } - } - }; - } - - protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { - return null; - } - - private void analyzeNullLiteralMethodArguments(PsiMethod method, ProblemsHolder holder) { - if (REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && holder.isOnTheFly()) { - for (PsiParameter parameter : NullParameterConstraintChecker.checkMethodParameters(method)) { - PsiIdentifier name = parameter.getNameIdentifier(); - if (name != null) { - holder.registerProblem(name, JavaAnalysisBundle.message("dataflow.method.fails.with.null.argument"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createNavigateToNullParameterUsagesFix(parameter)); - } - } - } - } - - private DataFlowInstructionVisitor analyzeDfaWithNestedClosures(PsiElement scope, - ProblemsHolder holder, - DataFlowRunner dfaRunner, - Collection initialStates) { - final DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor(); - final RunnerResult rc = dfaRunner.analyzeMethod(scope, visitor, initialStates); - if (rc == RunnerResult.OK) { - if (dfaRunner.wasForciblyMerged() && - (ApplicationManager.getApplication().isUnitTestMode() || Registry.is("ide.dfa.report.imprecise", false))) { - reportAnalysisQualityProblem(holder, scope, "dataflow.not.precise"); - } - createDescription(dfaRunner, holder, visitor, scope); - dfaRunner.forNestedClosures((closure, states) -> analyzeDfaWithNestedClosures(closure, holder, dfaRunner, states)); - } else if (rc == RunnerResult.TOO_COMPLEX) { - reportAnalysisQualityProblem(holder, scope, "dataflow.too.complex"); - } - return visitor; - } - - private static void reportAnalysisQualityProblem(ProblemsHolder holder, PsiElement scope, @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String problemKey) { - PsiIdentifier name = null; - String message = null; - if (scope.getParent() instanceof PsiMethod) { - name = ((PsiMethod) scope.getParent()).getNameIdentifier(); - message = JavaAnalysisBundle.message(problemKey, "Method #ref"); - } else if (scope instanceof PsiClass) { - name = ((PsiClass) scope).getNameIdentifier(); - message = JavaAnalysisBundle.message(problemKey, "Class initializer"); - } - if (name != null) { // Might be null for synthetic methods like JSP page. - holder.registerProblem(name, message, ProblemHighlightType.WEAK_WARNING); - } - } - - protected - @Nonnull - List createCastFixes(PsiTypeCastExpression castExpression, - PsiType realType, - boolean onTheFly, - boolean alwaysFails) { - return Collections.emptyList(); - } - - protected - @Nonnull - List createNPEFixes(PsiExpression qualifier, PsiExpression expression, boolean onTheFly) { - return Collections.emptyList(); - } - - protected List createMethodReferenceNPEFixes(PsiMethodReferenceExpression methodRef, boolean onTheFly) { - return Collections.emptyList(); - } - - protected - @Nullable - LocalQuickFix createUnwrapSwitchLabelFix() { - return null; - } - - protected - @Nullable - LocalQuickFix createIntroduceVariableFix() { - return null; - } - - protected LocalQuickFix createRemoveAssignmentFix(PsiAssignmentExpression assignment) { - return null; - } - - protected LocalQuickFix createReplaceWithTrivialLambdaFix(Object value) { - return null; - } - - private void createDescription(DataFlowRunner runner, - ProblemsHolder holder, - final DataFlowInstructionVisitor visitor, - PsiElement scope) { - ProblemReporter reporter = new ProblemReporter(holder, scope); - - Map constantExpressions = visitor.getConstantExpressions(); - reportFailingCasts(reporter, visitor, constantExpressions); - reportUnreachableSwitchBranches(visitor.getSwitchLabelsReachability(), holder); - - reportAlwaysFailingCalls(reporter, visitor); - - List> problems = NullabilityProblemKind.postprocessNullabilityProblems(visitor.problems().toList()); - reportNullabilityProblems(reporter, problems, constantExpressions); - reportNullableReturns(reporter, problems, constantExpressions, scope); - - reportOptionalOfNullableImprovements(reporter, visitor.getOfNullableCalls()); - - reportRedundantInstanceOf(runner, visitor, reporter); - - reportConstants(reporter, visitor); - - reportMethodReferenceProblems(holder, visitor); - - reportArrayAccessProblems(holder, visitor); - - reportArrayStoreProblems(holder, visitor); - - if (REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL && visitor.isAlwaysReturnsNotNull(runner.getInstructions())) { - reportAlwaysReturnsNotNull(holder, scope); - } - - reportMutabilityViolations(holder, visitor.getMutabilityViolations(true), - JavaAnalysisBundle.message("dataflow.message.immutable.modified")); - reportMutabilityViolations(holder, visitor.getMutabilityViolations(false), - JavaAnalysisBundle.message("dataflow.message.immutable.passed")); - - reportDuplicateAssignments(reporter, visitor); - reportPointlessSameArguments(reporter, visitor); - } - - private static void reportRedundantInstanceOf(DataFlowRunner runner, - DataFlowInstructionVisitor visitor, - ProblemReporter reporter) { - for (Instruction instruction : runner.getInstructions()) { - if (instruction instanceof InstanceofInstruction) { - InstanceofInstruction instanceOf = (InstanceofInstruction) instruction; - if (visitor.isInstanceofRedundant(instanceOf)) { - PsiExpression expression = instanceOf.getExpression(); - if (expression != null && - (!JavaPsiPatternUtil.getExposedPatternVariables(expression).isEmpty() || shouldBeSuppressed(expression))) { - continue; - } - reporter.registerProblem(expression, - JavaAnalysisBundle.message("dataflow.message.redundant.instanceof"), - new RedundantInstanceofFix()); - } - } - } - } - - private void reportUnreachableSwitchBranches(Map labelReachability, ProblemsHolder holder) { - Set coveredSwitches = new HashSet<>(); - - for (Map.Entry entry : labelReachability.entrySet()) { - if (entry.getValue() != ThreeState.YES) { - continue; - } - PsiExpression label = entry.getKey(); - PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(PsiImplUtil.getSwitchLabel(label)); - PsiSwitchBlock statement = labelStatement.getEnclosingSwitchBlock(); - if (statement == null || !canRemoveUnreachableBranches(labelStatement, statement)) { - continue; - } - if (!StreamEx.iterate(labelStatement, Objects::nonNull, l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiSwitchLabelStatementBase.class)) - .skip(1).map(PsiSwitchLabelStatementBase::getCaseValues) - .nonNull().flatArray(PsiExpressionList::getExpressions) - .append(StreamEx.iterate(label, Objects::nonNull, l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiExpression.class)).skip(1)) - .allMatch(l -> labelReachability.get(l) == ThreeState.NO)) { - continue; - } - coveredSwitches.add(statement); - holder.registerProblem(label, JavaAnalysisBundle.message("dataflow.message.only.switch.label"), - createUnwrapSwitchLabelFix()); - } - for (Map.Entry entry : labelReachability.entrySet()) { - if (entry.getValue() != ThreeState.NO) { - continue; - } - PsiExpression label = entry.getKey(); - PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(PsiImplUtil.getSwitchLabel(label)); - if (!coveredSwitches.contains(labelStatement.getEnclosingSwitchBlock())) { - holder.registerProblem(label, JavaAnalysisBundle.message("dataflow.message.unreachable.switch.label"), - new DeleteSwitchLabelFix(label)); - } - } - } - - private static boolean canRemoveUnreachableBranches(PsiSwitchLabelStatementBase labelStatement, PsiSwitchBlock statement) { - if (Objects.requireNonNull(labelStatement.getCaseValues()).getExpressionCount() != 1) { - return true; - } - List allBranches = - PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatementBase.class); - if (statement instanceof PsiSwitchStatement) { - // Cannot do anything if we have already single branch and we cannot restore flow due to non-terminal breaks - return allBranches.size() != 1 || BreakConverter.from(statement) != null; - } - // Expression switch: if we cannot unwrap existing branch and the other one is default case, we cannot kill it either - return (allBranches.size() <= 2 && - !allBranches.stream().allMatch(branch -> branch == labelStatement || branch.isDefaultCase())) || - (labelStatement instanceof PsiSwitchLabeledRuleStatement && - ((PsiSwitchLabeledRuleStatement) labelStatement).getBody() instanceof PsiExpressionStatement); - } - - private void reportConstants(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { - visitor.getConstantExpressionChunks().forEach((chunk, result) -> { - if (result == ConstantResult.UNKNOWN) { - return; - } - PsiExpression expression = chunk.myExpression; - if (chunk.myRange != null) { - if (result.value() instanceof Boolean) { - // report rare cases like a == b == c where "a == b" part is constant - String message = JavaAnalysisBundle.message("dataflow.message.constant.condition", - ((Boolean) result.value()).booleanValue() ? 1 : 0); - reporter.registerProblem(expression, chunk.myRange, message); - // do not add to reported anchors if only part of expression was reported - } - return; - } - if (isCondition(expression)) { - if (result.value() instanceof Boolean) { - reportConstantBoolean(reporter, expression, (Boolean) result.value()); - } - } else { - reportConstantReferenceValue(reporter, expression, result); - } - }); - } - - private static boolean isCondition(@Nonnull PsiExpression expression) { - PsiType type = expression.getType(); - if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) { - return false; - } - if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) { - return true; - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (parent instanceof PsiStatement) { - return !(parent instanceof PsiReturnStatement); - } - if (parent instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression) parent).getOperationTokenType(); - return tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR) || - tokenType.equals(JavaTokenType.AND) || tokenType.equals(JavaTokenType.OR); - } - if (parent instanceof PsiConditionalExpression) { - return PsiTreeUtil.isAncestor(((PsiConditionalExpression) parent).getCondition(), expression, false); - } - return PsiUtil.isAccessedForWriting(expression); - } - - private void reportConstantReferenceValue(ProblemReporter reporter, PsiExpression ref, ConstantResult constant) { - if (!REPORT_CONSTANT_REFERENCE_VALUES && ref instanceof PsiReferenceExpression) { - return; - } - if (shouldBeSuppressed(ref) || constant == ConstantResult.UNKNOWN) { - return; - } - List fixes = new SmartList<>(); - String presentableName = constant.toString(); - if (Integer.valueOf(0).equals(constant.value()) && !shouldReportZero(ref)) { - return; - } - if (constant.value() instanceof Boolean) { - fixes.add(createSimplifyBooleanExpressionFix(ref, (Boolean) constant.value())); - } else { - fixes.add(new ReplaceWithConstantValueFix(presentableName, presentableName)); - } - Object value = constant.value(); - boolean isAssertion = isAssertionEffectively(ref, constant); - if (isAssertion && DONT_REPORT_TRUE_ASSERT_STATEMENTS) { - return; - } - if (value instanceof Boolean) { - ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(ref, (Boolean) value)); - } - if (reporter.isOnTheFly()) { - if (ref instanceof PsiReferenceExpression) { - fixes.add(new SetInspectionOptionFix<>(this, (i, v) -> i.REPORT_CONSTANT_REFERENCE_VALUES = v, - JavaAnalysisBundle.message("inspection.data.flow.turn.off.constant.references.quickfix"), - false)); - } - if (isAssertion) { - fixes.add(new SetInspectionOptionFix<>(this, (i, v) -> i.DONT_REPORT_TRUE_ASSERT_STATEMENTS = v, - JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true)); - } - } - if (reporter.isOnTheFly()) { - ContainerUtil.addIfNotNull(fixes, createExplainFix(ref, new TrackingRunner.ValueDfaProblemType(value))); - } - - ProblemHighlightType type; - String message; - if (ref instanceof PsiMethodCallExpression || ref instanceof PsiPolyadicExpression) { - type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; - message = JavaAnalysisBundle.message("dataflow.message.constant.expression", presentableName); - } else { - type = ProblemHighlightType.WEAK_WARNING; - message = JavaAnalysisBundle.message("dataflow.message.constant.value", presentableName); - } - reporter.registerProblem(ref, message, type, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } - - private static boolean shouldReportZero(PsiExpression ref) { - if (ref instanceof PsiPolyadicExpression) { - if (PsiUtil.isConstantExpression(ref)) { - return false; - } - PsiPolyadicExpression polyadic = (PsiPolyadicExpression) ref; - IElementType tokenType = polyadic.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.ASTERISK)) { - PsiMethod method = PsiTreeUtil.getParentOfType(ref, PsiMethod.class, true, PsiLambdaExpression.class, PsiClass.class); - if (MethodUtils.isHashCode(method)) { - // Standard hashCode template generates int result = 0; result = result * 31 + ...; - // so annoying warnings might be produced there - return false; - } - } - } else if (ref instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) ref; - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if (PsiUtil.isConstantExpression(qualifier) && - ContainerUtil.and(call.getArgumentList().getExpressions(), PsiUtil::isConstantExpression)) { - return false; - } - } else { - return false; - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); - PsiBinaryExpression binOp = tryCast(parent, PsiBinaryExpression.class); - if (binOp != null && ComparisonUtils.isEqualityComparison(binOp) && - (ExpressionUtils.isZero(binOp.getLOperand()) || ExpressionUtils.isZero(binOp.getROperand()))) { - return false; - } - return true; - } - - private static void reportPointlessSameArguments(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { - visitor.pointlessSameArguments().forKeyValue((expr, eq) -> { - PsiElement name = expr.getReferenceNameElement(); - if (name != null) { - PsiExpression[] expressions = PsiExpression.EMPTY_ARRAY; - if (expr.getParent() instanceof PsiMethodCallExpression) { - expressions = ((PsiMethodCallExpression) expr.getParent()).getArgumentList().getExpressions(); - if (expressions.length == 2 && PsiUtil.isConstantExpression(expressions[0]) && PsiUtil.isConstantExpression(expressions[1]) && - !EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(expressions[0], expressions[1])) { +public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool { + static final Logger LOG = Logger.getInstance(DataFlowInspectionBase.class); + + private static final String SHORT_NAME = "ConstantConditions"; + private static final Set TRUE_OR_FALSE = Set.of("TRUE", "FALSE"); + + @Override + public PsiElementVisitor buildVisitorImpl( + ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + DataFlowInspectionStateBase state + ) { + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitClass(PsiClass aClass) { + if (aClass instanceof PsiTypeParameter) { + return; + } + if (PsiUtil.isLocalOrAnonymousClass(aClass) && !(aClass instanceof PsiEnumConstantInitializer)) { + return; + } + + DataFlowRunner runner = new DataFlowRunner( + holder.getProject(), + aClass, + state.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, + ThreeState.fromBoolean(state.IGNORE_ASSERT_STATEMENTS) + ); + DataFlowInstructionVisitor visitor = + analyzeDfaWithNestedClosures(aClass, holder, runner, Collections.singletonList(runner.createMemoryState()), state); + List states = visitor.getEndOfInitializerStates(); + boolean physical = aClass.isPhysical(); + for (PsiMethod method : aClass.getConstructors()) { + if (physical && !method.isPhysical()) { + // Constructor could be provided by, e.g. Lombok plugin: ignore it, we won't report any problems inside anyway + continue; + } + List initialStates; + PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(method); + if (JavaPsiConstructorUtil.isChainedConstructorCall(call) + || (call == null && DfaUtil.hasImplicitImpureSuperCall(aClass, method))) { + initialStates = Collections.singletonList(runner.createMemoryState()); + } + else { + initialStates = StreamEx.of(states).map(DfaMemoryState::createCopy).toList(); + } + analyzeMethod(method, runner, initialStates); + } + } + + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + if (method.isConstructor()) { + return; + } + DataFlowRunner runner = new DataFlowRunner( + holder.getProject(), + method.getBody(), + state.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE, + ThreeState.fromBoolean(state.IGNORE_ASSERT_STATEMENTS) + ); + analyzeMethod(method, runner, Collections.singletonList(runner.createMemoryState())); + } + + @RequiredReadAction + private void analyzeMethod(PsiMethod method, DataFlowRunner runner, List initialStates) { + PsiCodeBlock scope = method.getBody(); + if (scope == null) { + return; + } + PsiClass containingClass = PsiTreeUtil.getParentOfType(method, PsiClass.class); + if (containingClass != null && PsiUtil.isLocalOrAnonymousClass(containingClass) + && !(containingClass instanceof PsiEnumConstantInitializer)) { + return; + } + + analyzeDfaWithNestedClosures(scope, holder, runner, initialStates, state); + analyzeNullLiteralMethodArguments(method, holder, state); + } + + @Override + @RequiredReadAction + public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + super.visitMethodReferenceExpression(expression); + if (!state.REPORT_UNSOUND_WARNINGS) { + return; + } + if (expression.resolve() instanceof PsiMethod method + && TypeConversionUtil.isPrimitiveWrapper(method.getReturnType()) + && NullableNotNullManager.isNullable(method)) { + PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); + if (TypeConversionUtil.isPrimitiveAndNotNull(returnType)) { + holder.newProblem(JavaAnalysisLocalize.dataflowMessageUnboxingMethodReference()) + .range(expression) + .create(); + } + } + } + + @Override + public void visitIfStatement(PsiIfStatement statement) { + PsiExpression condition = PsiUtil.skipParenthesizedExprDown(statement.getCondition()); + if (BoolUtils.isBooleanLiteral(condition)) { + holder.newProblem(JavaAnalysisLocalize.dataflowMessageConstantNoRef(condition.textMatches(PsiKeyword.TRUE) ? 1 : 0)) + .range(condition) + .withFixes(createSimplifyBooleanExpressionFix(condition, condition.textMatches(PsiKeyword.TRUE))) + .create(); + } + } + + @Override + public void visitWhileStatement(PsiWhileStatement statement) { + checkLoopCondition(statement.getCondition()); + } + + @Override + public void visitDoWhileStatement(PsiDoWhileStatement statement) { + checkLoopCondition(statement.getCondition()); + } + + @Override + public void visitForStatement(PsiForStatement statement) { + checkLoopCondition(statement.getCondition()); + } + + private void checkLoopCondition(PsiExpression condition) { + condition = PsiUtil.skipParenthesizedExprDown(condition); + if (condition != null && condition.textMatches(PsiKeyword.FALSE)) { + holder.newProblem(JavaAnalysisLocalize.dataflowMessageConstantNoRef(0)) + .range(condition) + .withOptionalFix(createSimplifyBooleanExpressionFix(condition, false)) + .create(); + } + } + }; + } + + protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { + return null; + } + + private void analyzeNullLiteralMethodArguments(PsiMethod method, ProblemsHolder holder, DataFlowInspectionStateBase state) { + if (state.REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER && holder.isOnTheFly()) { + for (PsiParameter parameter : NullParameterConstraintChecker.checkMethodParameters(method)) { + PsiIdentifier name = parameter.getNameIdentifier(); + if (name != null) { + holder.newProblem(JavaAnalysisLocalize.dataflowMethodFailsWithNullArgument()) + .range(name) + .withFix(createNavigateToNullParameterUsagesFix(parameter)) + .create(); + } + } + } + } + + @RequiredReadAction + private DataFlowInstructionVisitor analyzeDfaWithNestedClosures( + PsiElement scope, + ProblemsHolder holder, + DataFlowRunner dfaRunner, + Collection initialStates, + DataFlowInspectionStateBase state + ) { + DataFlowInstructionVisitor visitor = new DataFlowInstructionVisitor(); + RunnerResult rc = dfaRunner.analyzeMethod(scope, visitor, initialStates); + if (rc == RunnerResult.OK) { + if (dfaRunner.wasForciblyMerged() + && (Application.get().isUnitTestMode() || Registry.is("ide.dfa.report.imprecise", false))) { + reportAnalysisQualityProblem(holder, scope, JavaAnalysisLocalize::dataflowNotPrecise); + } + createDescription(dfaRunner, holder, visitor, scope, state); + dfaRunner.forNestedClosures((closure, states) -> analyzeDfaWithNestedClosures(closure, holder, dfaRunner, states, state)); + } + else if (rc == RunnerResult.TOO_COMPLEX) { + reportAnalysisQualityProblem(holder, scope, JavaAnalysisLocalize::dataflowTooComplex); + } + return visitor; + } + + private static void reportAnalysisQualityProblem( + ProblemsHolder holder, + PsiElement scope, + Function problemMessageGenerator + ) { + PsiIdentifier name = null; + LocalizeValue message = LocalizeValue.empty(); + if (scope.getParent() instanceof PsiMethod method) { + name = method.getNameIdentifier(); + message = problemMessageGenerator.apply(JavaAnalysisLocalize.dataflowMethodWithNameTemplate()); + } + else if (scope instanceof PsiClass psiClass) { + name = psiClass.getNameIdentifier(); + message = problemMessageGenerator.apply(JavaAnalysisLocalize.dataflowConstructor()); + } + if (name != null) { // Might be null for synthetic methods like JSP page. + holder.newProblem(message) + .range(name) + .highlightType(ProblemHighlightType.WEAK_WARNING) + .create(); + } + } + + protected List createCastFixes( + PsiTypeCastExpression castExpression, + PsiType realType, + boolean onTheFly, + boolean alwaysFails + ) { + return Collections.emptyList(); + } + + protected List createNPEFixes( + PsiExpression qualifier, + PsiExpression expression, + boolean onTheFly, + DataFlowInspectionStateBase state + ) { + return Collections.emptyList(); + } + + protected List createMethodReferenceNPEFixes(PsiMethodReferenceExpression methodRef, boolean onTheFly) { + return Collections.emptyList(); + } + + @Nullable + protected LocalQuickFix createUnwrapSwitchLabelFix() { + return null; + } + + @Nullable + protected LocalQuickFix createIntroduceVariableFix() { + return null; + } + + @Nullable + protected LocalQuickFix createRemoveAssignmentFix(PsiAssignmentExpression assignment) { + return null; + } + + protected LocalQuickFix createReplaceWithTrivialLambdaFix(Object value) { + return null; + } + + @RequiredReadAction + private void createDescription( + DataFlowRunner runner, + ProblemsHolder holder, + DataFlowInstructionVisitor visitor, + PsiElement scope, + DataFlowInspectionStateBase state + ) { + ProblemReporter reporter = new ProblemReporter(holder, scope); + + Map constantExpressions = visitor.getConstantExpressions(); + reportFailingCasts(reporter, visitor, constantExpressions, state); + reportUnreachableSwitchBranches(visitor.getSwitchLabelsReachability(), holder); + + reportAlwaysFailingCalls(reporter, visitor, state); + + List> problems = NullabilityProblemKind.postprocessNullabilityProblems(visitor.problems().toList()); + reportNullabilityProblems(reporter, problems, constantExpressions, state); + + reportNullableReturns(reporter, problems, constantExpressions, scope, state); + + reportOptionalOfNullableImprovements(reporter, visitor.getOfNullableCalls()); + + reportRedundantInstanceOf(runner, visitor, reporter); + + reportConstants(reporter, visitor, state); + + reportMethodReferenceProblems(holder, visitor); + + reportArrayAccessProblems(holder, visitor); + + reportArrayStoreProblems(holder, visitor); + + if (state.REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL && visitor.isAlwaysReturnsNotNull(runner.getInstructions())) { + reportAlwaysReturnsNotNull(holder, scope); + } + + reportMutabilityViolations( + holder, + visitor.getMutabilityViolations(true), + JavaAnalysisLocalize.dataflowMessageImmutableModified() + ); + reportMutabilityViolations( + holder, + visitor.getMutabilityViolations(false), + JavaAnalysisLocalize.dataflowMessageImmutablePassed() + ); + + reportDuplicateAssignments(reporter, visitor); + reportPointlessSameArguments(reporter, visitor); + } + + @RequiredReadAction + private static void reportRedundantInstanceOf( + DataFlowRunner runner, + DataFlowInstructionVisitor visitor, + ProblemReporter reporter + ) { + for (Instruction instruction : runner.getInstructions()) { + if (instruction instanceof InstanceofInstruction instanceOf && visitor.isInstanceofRedundant(instanceOf)) { + PsiExpression expression = instanceOf.getExpression(); + if (expression != null + && (!JavaPsiPatternUtil.getExposedPatternVariables(expression).isEmpty() || shouldBeSuppressed(expression))) { + continue; + } + reporter.newProblem(JavaAnalysisLocalize.dataflowMessageRedundantInstanceof()) + .range(expression) + .withFix(new RedundantInstanceofFix()) + .create(); + } + } + } + + @RequiredReadAction + private void reportUnreachableSwitchBranches(Map labelReachability, ProblemsHolder holder) { + Set coveredSwitches = new HashSet<>(); + + for (Map.Entry entry : labelReachability.entrySet()) { + if (entry.getValue() != ThreeState.YES) { + continue; + } + PsiExpression label = entry.getKey(); + PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(PsiImplUtil.getSwitchLabel(label)); + PsiSwitchBlock statement = labelStatement.getEnclosingSwitchBlock(); + if (statement == null || !canRemoveUnreachableBranches(labelStatement, statement)) { + continue; + } + if (!StreamEx.iterate( + labelStatement, + Objects::nonNull, + l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiSwitchLabelStatementBase.class) + ) + .skip(1).map(PsiSwitchLabelStatementBase::getCaseValues) + .nonNull().flatArray(PsiExpressionList::getExpressions) + .append(StreamEx.iterate(label, Objects::nonNull, l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiExpression.class)).skip(1)) + .allMatch(l -> labelReachability.get(l) == ThreeState.NO)) { + continue; + } + coveredSwitches.add(statement); + holder.newProblem(JavaAnalysisLocalize.dataflowMessageOnlySwitchLabel()) + .range(label) + .withOptionalFix(createUnwrapSwitchLabelFix()) + .create(); + } + for (Map.Entry entry : labelReachability.entrySet()) { + if (entry.getValue() != ThreeState.NO) { + continue; + } + PsiExpression label = entry.getKey(); + PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(PsiImplUtil.getSwitchLabel(label)); + if (!coveredSwitches.contains(labelStatement.getEnclosingSwitchBlock())) { + holder.newProblem(JavaAnalysisLocalize.dataflowMessageUnreachableSwitchLabel()) + .range(label) + .withFix(new DeleteSwitchLabelFix(label)) + .create(); + } + } + } + + @RequiredReadAction + private static boolean canRemoveUnreachableBranches(PsiSwitchLabelStatementBase labelStatement, PsiSwitchBlock statement) { + if (Objects.requireNonNull(labelStatement.getCaseValues()).getExpressionCount() != 1) { + return true; + } + List allBranches = + PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatementBase.class); + if (statement instanceof PsiSwitchStatement) { + // Cannot do anything if we have already single branch and we cannot restore flow due to non-terminal breaks + return allBranches.size() != 1 || BreakConverter.from(statement) != null; + } + // Expression switch: if we cannot unwrap existing branch and the other one is default case, we cannot kill it either + return (allBranches.size() <= 2 + && !allBranches.stream().allMatch(branch -> branch == labelStatement || branch.isDefaultCase())) + || (labelStatement instanceof PsiSwitchLabeledRuleStatement switchLabeledRuleStatement + && switchLabeledRuleStatement.getBody() instanceof PsiExpressionStatement); + } + + private void reportConstants(ProblemReporter reporter, DataFlowInstructionVisitor visitor, DataFlowInspectionStateBase state) { + visitor.getConstantExpressionChunks().forEach((chunk, result) -> { + if (result == ConstantResult.UNKNOWN) { + return; + } + PsiExpression expression = chunk.myExpression; + if (chunk.myRange != null) { + if (result.value() instanceof Boolean booleanValue) { + // report rare cases like a == b == c where "a == b" part is constant + reporter.myHolder.newProblem(JavaAnalysisLocalize.dataflowMessageConstantCondition(booleanValue ? 1 : 0)) + .range(expression, chunk.myRange) + .create(); + // do not add to reported anchors if only part of expression was reported + } + return; + } + if (!isCondition(expression)) { + reportConstantReferenceValue(reporter, expression, result, state); + } + else if (result.value() instanceof Boolean booleanValue) { + reportConstantBoolean(reporter, expression, booleanValue, state); + } + }); + } + + private static boolean isCondition(PsiExpression expression) { + PsiType type = expression.getType(); + if (type == null || !PsiType.BOOLEAN.isAssignableFrom(type)) { + return false; + } + if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) { + return true; + } + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (parent instanceof PsiStatement) { + return !(parent instanceof PsiReturnStatement); + } + if (parent instanceof PsiPolyadicExpression polyadic) { + IElementType tokenType = polyadic.getOperationTokenType(); + return JavaTokenType.ANDAND.equals(tokenType) || JavaTokenType.OROR.equals(tokenType) + || JavaTokenType.AND.equals(tokenType) || JavaTokenType.OR.equals(tokenType); + } + if (parent instanceof PsiConditionalExpression conditional) { + return PsiTreeUtil.isAncestor(conditional.getCondition(), expression, false); + } + return PsiUtil.isAccessedForWriting(expression); + } + + @RequiredReadAction + private void reportConstantReferenceValue( + ProblemReporter reporter, + PsiExpression ref, + ConstantResult constant, + DataFlowInspectionStateBase state + ) { + if (!state.REPORT_CONSTANT_REFERENCE_VALUES && ref instanceof PsiReferenceExpression) { return; - } - } - if (eq.firstArgEqualToResult) { - String message = eq.argsEqual ? JavaAnalysisBundle.message("dataflow.message.pointless.same.arguments") : - JavaAnalysisBundle.message("dataflow.message.pointless.same.argument.and.result", 1); - LocalQuickFix fix = expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[0], 0) : null; - reporter.registerProblem(name, message, fix); - } else if (eq.argsEqual) { - reporter.registerProblem(name, JavaAnalysisBundle.message("dataflow.message.pointless.same.arguments")); - } else if (eq.secondArgEqualToResult) { - LocalQuickFix fix = expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[1], 1) : null; - reporter.registerProblem(name, JavaAnalysisBundle.message("dataflow.message.pointless.same.argument.and.result", 2), fix); - } - } - }); - } - - private void reportDuplicateAssignments(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { - visitor.sameValueAssignments().forEach(expr -> { - expr = PsiUtil.skipParenthesizedExprDown(expr); - if (expr == null) { - return; - } - PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(expr, PsiAssignmentExpression.class); - PsiElement context = PsiTreeUtil.getParentOfType(expr, PsiForStatement.class, PsiClassInitializer.class); - if (context instanceof PsiForStatement && PsiTreeUtil.isAncestor(((PsiForStatement) context).getInitialization(), expr, true)) { - return; - } - if (context instanceof PsiClassInitializer && expr instanceof PsiReferenceExpression) { - if (assignment != null) { - Object constValue = ExpressionUtils.computeConstantExpression(assignment.getRExpression()); - if (constValue == PsiTypesUtil.getDefaultValue(expr.getType())) { - PsiReferenceExpression ref = (PsiReferenceExpression) expr; - PsiElement target = ref.resolve(); - if (target instanceof PsiField && - (((PsiField) target).hasModifierProperty(PsiModifier.STATIC) || ExpressionUtil.isEffectivelyUnqualified(ref)) && - ((PsiField) target).getContainingClass() == ((PsiClassInitializer) context).getContainingClass()) { - return; - } - } - } - } - String message = assignment != null && !assignment.getOperationTokenType().equals(JavaTokenType.EQ) - ? JavaAnalysisBundle.message("dataflow.message.redundant.update") - : JavaAnalysisBundle.message("dataflow.message.redundant.assignment"); - reporter.registerProblem(expr, message, createRemoveAssignmentFix(assignment)); - }); - } - - private void reportMutabilityViolations(ProblemsHolder holder, Set violations, String message) { - for (PsiElement violation : violations) { - holder.registerProblem(violation, message, createMutabilityViolationFix(violation, holder.isOnTheFly())); - } - } - - protected LocalQuickFix createMutabilityViolationFix(PsiElement violation, boolean onTheFly) { - return null; - } - - protected void reportNullabilityProblems(ProblemReporter reporter, - List> problems, - Map expressions) { - for (NullabilityProblem problem : problems) { - PsiExpression expression = problem.getDereferencedExpression(); - if (!REPORT_UNSOUND_WARNINGS) { - if (expression == null) { - continue; - } - PsiExpression unwrapped = PsiUtil.skipParenthesizedExprDown(expression); - if (!ExpressionUtils.isNullLiteral(unwrapped) && expressions.get(expression) != ConstantResult.NULL) { - continue; - } - } - NullabilityProblemKind.innerClassNPE.ifMyProblem(problem, newExpression -> { - List fixes = createNPEFixes(newExpression.getQualifier(), newExpression, reporter.isOnTheFly()); - reporter - .registerProblem(getElementToHighlight(newExpression), problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - }); - NullabilityProblemKind.callMethodRefNPE.ifMyProblem(problem, methodRef -> - reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.npe.methodref.invocation"), - createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY))); - NullabilityProblemKind.callNPE.ifMyProblem(problem, call -> reportCallMayProduceNpe(reporter, problem.getMessage(expressions), call)); - NullabilityProblemKind.passingToNotNullParameter.ifMyProblem(problem, expr -> { - List fixes = createNPEFixes(expression, expression, reporter.isOnTheFly()); - reporter.registerProblem(expression, problem.getMessage(expressions), fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - }); - NullabilityProblemKind.passingToNotNullMethodRefParameter.ifMyProblem(problem, methodRef -> { - LocalQuickFix[] fixes = createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY); - reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.passing.nullable.argument.methodref"), fixes); - }); - NullabilityProblemKind.unboxingMethodRefParameter.ifMyProblem(problem, methodRef -> { - LocalQuickFix[] fixes = createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY); - reporter.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.unboxing.nullable.argument.methodref"), fixes); - }); - NullabilityProblemKind.arrayAccessNPE.ifMyProblem(problem, arrayAccess -> { - LocalQuickFix[] fixes = - createNPEFixes(arrayAccess.getArrayExpression(), arrayAccess, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY); - reporter.registerProblem(arrayAccess, problem.getMessage(expressions), fixes); - }); - NullabilityProblemKind.fieldAccessNPE.ifMyProblem(problem, element -> { - PsiElement parent = element.getParent(); - PsiExpression fieldAccess = parent instanceof PsiReferenceExpression ? (PsiExpression) parent : element; - LocalQuickFix[] fix = createNPEFixes(element, fieldAccess, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY); - reporter.registerProblem(element, problem.getMessage(expressions), fix); - }); - NullabilityProblemKind.unboxingNullable.ifMyProblem(problem, element -> { - PsiExpression anchor = expression; - if (anchor instanceof PsiTypeCastExpression && anchor.getType() instanceof PsiPrimitiveType) { - anchor = Objects.requireNonNull(((PsiTypeCastExpression) anchor).getOperand()); - } - reporter.registerProblem(anchor, problem.getMessage(expressions)); - }); - NullabilityProblemKind.nullableFunctionReturn.ifMyProblem( - problem, expr -> reporter.registerProblem(expression == null ? expr : expression, problem.getMessage(expressions))); - Consumer reportNullability = expr -> reportNullabilityProblem(reporter, problem, expression, expressions); - NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, reportNullability); - NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, reportNullability); - if (SUGGEST_NULLABLE_ANNOTATIONS) { - NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.ifMyProblem( - problem, methodRef -> reporter.registerProblem(methodRef, problem.getMessage(expressions))); - NullabilityProblemKind.passingToNonAnnotatedParameter.ifMyProblem( - problem, top -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(expressions), expression, top)); - NullabilityProblemKind.assigningToNonAnnotatedField.ifMyProblem( - problem, top -> reportNullableAssignedToNonAnnotatedField(reporter, top, expression, problem.getMessage(expressions))); - } - } - } - - private void reportNullabilityProblem(ProblemReporter reporter, - NullabilityProblem problem, - PsiExpression expr, - Map expressions) { - LocalQuickFix[] fixes = createNPEFixes(expr, expr, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY); - reporter.registerProblem(expr, problem.getMessage(expressions), fixes); - } - - private static void reportArrayAccessProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { - visitor.outOfBoundsArrayAccesses().forEach(access -> { - PsiExpression indexExpression = access.getIndexExpression(); - if (indexExpression != null) { - holder.registerProblem(indexExpression, JavaAnalysisBundle.message("dataflow.message.array.index.out.of.bounds")); - } - }); - } - - private static void reportArrayStoreProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { - visitor.getArrayStoreProblems().forEach( - (assignment, types) -> holder.registerProblem(assignment.getOperationSign(), JavaAnalysisBundle - .message("dataflow.message.arraystore", types.getFirst().getCanonicalText(), types.getSecond().getCanonicalText()))); - } - - private void reportMethodReferenceProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { - visitor.getMethodReferenceResults().forEach((methodRef, result) -> { - if (result != ConstantResult.UNKNOWN) { - Object value = result.value(); - holder.registerProblem(methodRef, JavaAnalysisBundle.message("dataflow.message.constant.method.reference", value), - createReplaceWithTrivialLambdaFix(value)); - } - }); - } - - private void reportAlwaysReturnsNotNull(ProblemsHolder holder, PsiElement scope) { - if (!(scope.getParent() instanceof PsiMethod)) { - return; - } - - PsiMethod method = (PsiMethod) scope.getParent(); - if (PsiUtil.canBeOverridden(method)) { - return; - } - - NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(scope.getProject()).findOwnNullabilityInfo(method); - if (info == null || info.getNullability() != Nullability.NULLABLE) { - return; - } - - PsiAnnotation annotation = info.getAnnotation(); - if (!annotation.isPhysical() || alsoAppliesToInternalSubType(annotation, method)) { - return; - } - - PsiJavaCodeReferenceElement annoName = annotation.getNameReferenceElement(); - assert annoName != null; - String msg = JavaAnalysisBundle - .message("dataflow.message.return.notnull.from.nullable", NullableStuffInspectionBase.getPresentableAnnoName(annotation), method.getName()); - LocalQuickFix[] fixes = {AddAnnotationPsiFix.createAddNotNullFix(method)}; - if (holder.isOnTheFly()) { - fixes = ArrayUtil.append(fixes, new SetInspectionOptionFix<>(this, (i, v) -> i.REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = v, - JavaAnalysisBundle - .message( - "inspection.data.flow.turn.off.nullable.returning.notnull.quickfix"), - false)); - } - holder.registerProblem(annoName, msg, fixes); - } - - private static boolean alsoAppliesToInternalSubType(PsiAnnotation annotation, PsiMethod method) { - return AnnotationTargetUtil.isTypeAnnotation(annotation) && method.getReturnType() instanceof PsiArrayType; - } - - private void reportAlwaysFailingCalls(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { - visitor.alwaysFailingCalls().remove(TestUtils::isExceptionExpected).forEach(call -> { - String message = getContractMessage(JavaMethodContractUtil.getMethodCallContracts(call)); - LocalQuickFix causeFix = reporter.isOnTheFly() ? createExplainFix(call, new TrackingRunner.FailingCallDfaProblemType()) : null; - reporter.registerProblem(getElementToHighlight(call), message, causeFix); - }); - } - - private static - @Nonnull - String getContractMessage(List contracts) { - if (contracts.stream().allMatch(mc -> mc.getConditions().stream().allMatch(ContractValue::isBoundCheckingCondition))) { - return JavaAnalysisBundle.message("dataflow.message.contract.fail.index"); - } - return JavaAnalysisBundle.message("dataflow.message.contract.fail"); - } - - private static - @Nonnull - PsiElement getElementToHighlight(@Nonnull PsiCall call) { - PsiJavaCodeReferenceElement ref; - if (call instanceof PsiNewExpression) { - ref = ((PsiNewExpression) call).getClassReference(); - } else if (call instanceof PsiMethodCallExpression) { - ref = ((PsiMethodCallExpression) call).getMethodExpression(); - } else { - return call; - } - if (ref != null) { - PsiElement name = ref.getReferenceNameElement(); - return name != null ? name : ref; - } - return call; - } - - private static void reportOptionalOfNullableImprovements(ProblemReporter reporter, Map nullArgs) { - nullArgs.forEach((anchor, alwaysPresent) -> { - if (alwaysPresent == ThreeState.UNSURE) { - return; - } - if (alwaysPresent.toBoolean()) { - reporter.registerProblem(anchor, JavaAnalysisBundle.message("dataflow.message.passing.non.null.argument.to.optional"), - DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(anchor)); - } else { - reporter.registerProblem(anchor, JavaAnalysisBundle.message("dataflow.message.passing.null.argument.to.optional"), - DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(anchor)); - } - }); - } - - private void reportNullableArgumentsPassedToNonAnnotated(ProblemReporter reporter, - String message, - PsiExpression expression, - PsiExpression top) { - PsiParameter parameter = MethodCallUtils.getParameterForArgument(top); - if (parameter != null && BaseIntentionAction.canModify(parameter) && AnnotationUtil.isAnnotatingApplicable(parameter)) { - List fixes = createNPEFixes(expression, top, reporter.isOnTheFly()); - fixes.add(AddAnnotationPsiFix.createAddNullableFix(parameter)); - reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } - } - - private void reportNullableAssignedToNonAnnotatedField(ProblemReporter reporter, - PsiExpression top, - PsiExpression expression, - String message) { - PsiField field = getAssignedField(top); - if (field != null) { - List fixes = createNPEFixes(expression, top, reporter.isOnTheFly()); - fixes.add(AddAnnotationPsiFix.createAddNullableFix(field)); - reporter.registerProblem(expression, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } - } - - private static - @Nullable - PsiField getAssignedField(PsiElement assignedValue) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(assignedValue.getParent()); - if (parent instanceof PsiAssignmentExpression) { - PsiExpression lExpression = ((PsiAssignmentExpression) parent).getLExpression(); - PsiElement target = lExpression instanceof PsiReferenceExpression ? ((PsiReferenceExpression) lExpression).resolve() : null; - return tryCast(target, PsiField.class); - } - return null; - } - - private void reportCallMayProduceNpe(ProblemReporter reporter, String message, PsiMethodCallExpression callExpression) { - PsiReferenceExpression methodExpression = callExpression.getMethodExpression(); - List fixes = createNPEFixes(methodExpression.getQualifierExpression(), callExpression, reporter.isOnTheFly()); - ContainerUtil.addIfNotNull(fixes, ReplaceWithObjectsEqualsFix.createFix(callExpression, methodExpression)); - - PsiElement toHighlight = getElementToHighlight(callExpression); - reporter.registerProblem(toHighlight, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } - - private void reportFailingCasts(@Nonnull ProblemReporter reporter, - @Nonnull DataFlowInstructionVisitor visitor, - @Nonnull Map constantExpressions) { - visitor.getFailingCastExpressions().forKeyValue((typeCast, info) -> { - boolean alwaysFails = info.getFirst(); - PsiType realType = info.getSecond(); - if (!REPORT_UNSOUND_WARNINGS && !alwaysFails) { - return; - } - PsiExpression operand = typeCast.getOperand(); - PsiTypeElement castType = typeCast.getCastType(); - ConstantResult result = constantExpressions.get(PsiUtil.skipParenthesizedExprDown(operand)); - // Skip reporting if cast operand is always null: null can be cast to anything - if (result == ConstantResult.NULL || ExpressionUtils.isNullLiteral(operand)) { - return; - } - assert castType != null; - assert operand != null; - List fixes = new ArrayList<>(createCastFixes(typeCast, realType, reporter.isOnTheFly(), alwaysFails)); - if (reporter.isOnTheFly()) { - fixes.add(createExplainFix(typeCast, new TrackingRunner.CastDfaProblemType())); - } - String text = PsiExpressionTrimRenderer.render(operand); - String message = alwaysFails ? - JavaAnalysisBundle.message("dataflow.message.cce.always", text) : - JavaAnalysisBundle.message("dataflow.message.cce", text); - reporter.registerProblem(castType, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - }); - } - - private void reportConstantBoolean(ProblemReporter reporter, PsiElement psiAnchor, boolean evaluatesToTrue) { - while (psiAnchor instanceof PsiParenthesizedExpression) { - psiAnchor = ((PsiParenthesizedExpression) psiAnchor).getExpression(); - } - if (psiAnchor == null || shouldBeSuppressed(psiAnchor)) { - return; - } - boolean isAssertion = isAssertionEffectively(psiAnchor, evaluatesToTrue); - if (DONT_REPORT_TRUE_ASSERT_STATEMENTS && isAssertion) { - return; - } - - PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()); - if (parent instanceof PsiAssignmentExpression && - PsiTreeUtil.isAncestor(((PsiAssignmentExpression) parent).getLExpression(), psiAnchor, false)) { - reporter.registerProblem( - psiAnchor, - JavaAnalysisBundle.message("dataflow.message.pointless.assignment.expression", Boolean.toString(evaluatesToTrue)), - createConditionalAssignmentFixes(evaluatesToTrue, (PsiAssignmentExpression) parent, reporter.isOnTheFly()) - ); - return; - } - - List fixes = new ArrayList<>(); - if (!isCoveredBySurroundingFix(psiAnchor, evaluatesToTrue)) { - ContainerUtil.addIfNotNull(fixes, createSimplifyBooleanExpressionFix(psiAnchor, evaluatesToTrue)); - if (isAssertion && reporter.isOnTheFly()) { - fixes.add(new SetInspectionOptionFix<>(this, (i, v) -> i.DONT_REPORT_TRUE_ASSERT_STATEMENTS = v, - JavaAnalysisBundle.message("inspection.data.flow.turn.off.true.asserts.quickfix"), true)); - } - ContainerUtil.addIfNotNull(fixes, createReplaceWithNullCheckFix(psiAnchor, evaluatesToTrue)); - } - if (reporter.isOnTheFly() && psiAnchor instanceof PsiExpression) { - ContainerUtil.addIfNotNull(fixes, createExplainFix( - (PsiExpression) psiAnchor, new TrackingRunner.ValueDfaProblemType(evaluatesToTrue))); - } - String message = JavaAnalysisBundle.message(isAtRHSOfBooleanAnd(psiAnchor) ? - "dataflow.message.constant.condition.when.reached" : - "dataflow.message.constant.condition", evaluatesToTrue ? 1 : 0); - reporter.registerProblem(psiAnchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); - } - - protected - @Nullable - LocalQuickFix createExplainFix(PsiExpression anchor, TrackingRunner.DfaProblemType problemType) { - return null; - } - - private static boolean isCoveredBySurroundingFix(PsiElement anchor, boolean evaluatesToTrue) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent()); - if (parent instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression) parent).getOperationTokenType(); - return tokenType.equals(JavaTokenType.ANDAND) && !evaluatesToTrue || - tokenType.equals(JavaTokenType.OROR) && evaluatesToTrue; - } - return parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression) parent); - } - - @Contract("null -> false") - private static boolean shouldBeSuppressed(PsiElement anchor) { - if (!(anchor instanceof PsiExpression)) { - return false; - } - // Don't report System.out.println(b = false) or doSomething((Type)null) - if (anchor instanceof PsiAssignmentExpression || anchor instanceof PsiTypeCastExpression) { - return true; - } - // For conditional the root cause (constant condition or both branches constant) should be already reported for branches - if (anchor instanceof PsiConditionalExpression) { - return true; - } - PsiExpression expression = (PsiExpression) anchor; - if (expression instanceof PsiReferenceExpression) { - PsiReferenceExpression ref = (PsiReferenceExpression) expression; - if ("TRUE".equals(ref.getReferenceName()) || "FALSE".equals(ref.getReferenceName())) { - PsiElement target = ref.resolve(); - if (target instanceof PsiField) { - PsiClass containingClass = ((PsiField) target).getContainingClass(); - if (containingClass != null && CommonClassNames.JAVA_LANG_BOOLEAN.equals(containingClass.getQualifiedName())) { + } + if (shouldBeSuppressed(ref) || constant == ConstantResult.UNKNOWN) { + return; + } + if (Integer.valueOf(0).equals(constant.value()) && !shouldReportZero(ref)) { + return; + } + boolean isAssertion = isAssertionEffectively(ref, constant); + if (isAssertion && state.DONT_REPORT_TRUE_ASSERT_STATEMENTS) { + return; + } + String presentableName = constant.toString(); + ProblemBuilder pBuilder; + if (ref instanceof PsiMethodCallExpression || ref instanceof PsiPolyadicExpression) { + pBuilder = reporter.newProblem(JavaAnalysisLocalize.dataflowMessageConstantExpression(presentableName)); + } + else { + pBuilder = reporter.newProblem(JavaAnalysisLocalize.dataflowMessageConstantValue(presentableName)) + .highlightType(ProblemHighlightType.WEAK_WARNING); + } + pBuilder.range(ref); + if (constant.value() instanceof Boolean booleanValue) { + pBuilder.withOptionalFix(createSimplifyBooleanExpressionFix(ref, booleanValue)); + } + else { + pBuilder.withFix(new ReplaceWithConstantValueFix(presentableName, presentableName)); + } + Object value = constant.value(); + if (value instanceof Boolean booleanValue) { + pBuilder.withOptionalFix(createReplaceWithNullCheckFix(ref, booleanValue)); + } + if (reporter.isOnTheFly()) { + if (ref instanceof PsiReferenceExpression) { + pBuilder.withFix(new SetInspectionOptionFix<>( + this, + (i, v) -> i.REPORT_CONSTANT_REFERENCE_VALUES = v, + JavaAnalysisLocalize.inspectionDataFlowTurnOffConstantReferencesQuickfix(), + false + )); + } + if (isAssertion) { + pBuilder.withFix(new SetInspectionOptionFix<>( + this, + (i, v) -> i.DONT_REPORT_TRUE_ASSERT_STATEMENTS = v, + JavaAnalysisLocalize.inspectionDataFlowTurnOffTrueAssertsQuickfix(), + true + )); + } + } + if (reporter.isOnTheFly()) { + pBuilder.withOptionalFix(createExplainFix(ref, new TrackingRunner.ValueDfaProblemType(value), state)); + } + + pBuilder.create(); + } + + private static boolean shouldReportZero(PsiExpression ref) { + if (ref instanceof PsiPolyadicExpression polyadic) { + if (PsiUtil.isConstantExpression(polyadic)) { + return false; + } + IElementType tokenType = polyadic.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.ASTERISK)) { + PsiMethod method = PsiTreeUtil.getParentOfType(polyadic, PsiMethod.class, true, PsiLambdaExpression.class, PsiClass.class); + if (MethodUtils.isHashCode(method)) { + // Standard hashCode template generates int result = 0; result = result * 31 + ...; + // so annoying warnings might be produced there + return false; + } + } + } + else if (ref instanceof PsiMethodCallExpression call) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (PsiUtil.isConstantExpression(qualifier) + && ContainerUtil.and(call.getArgumentList().getExpressions(), PsiUtil::isConstantExpression)) { + return false; + } + } + else { + return false; + } + PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); + PsiBinaryExpression binOp = tryCast(parent, PsiBinaryExpression.class); + return binOp == null || !ComparisonUtils.isEqualityComparison(binOp) + || (!ExpressionUtils.isZero(binOp.getLOperand()) && !ExpressionUtils.isZero(binOp.getROperand())); + } + + private static void reportPointlessSameArguments(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { + visitor.pointlessSameArguments().forKeyValue((expr, eq) -> { + PsiElement name = expr.getReferenceNameElement(); + if (name == null) { + return; + } + PsiExpression[] expressions = PsiExpression.EMPTY_ARRAY; + if (expr.getParent() instanceof PsiMethodCallExpression call) { + expressions = call.getArgumentList().getExpressions(); + if (expressions.length == 2 + && PsiUtil.isConstantExpression(expressions[0]) + && PsiUtil.isConstantExpression(expressions[1]) + && !EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(expressions[0], expressions[1])) { + return; + } + } + if (eq.firstArgEqualToResult) { + LocalizeValue message = eq.argsEqual + ? JavaAnalysisLocalize.dataflowMessagePointlessSameArguments() + : JavaAnalysisLocalize.dataflowMessagePointlessSameArgumentAndResult(1); + reporter.newProblem(message) + .range(name) + .withOptionalFix(expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[0], 0) : null) + .create(); + } + else if (eq.argsEqual) { + reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePointlessSameArguments()) + .range(name) + .create(); + } + else if (eq.secondArgEqualToResult) { + reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePointlessSameArgumentAndResult(2)) + .range(name) + .withOptionalFix(expressions.length == 2 ? new ReplaceWithArgumentFix(expressions[1], 1) : null) + .create(); + } + }); + } + + @RequiredReadAction + private void reportDuplicateAssignments(ProblemReporter reporter, DataFlowInstructionVisitor visitor) { + visitor.sameValueAssignments().forEach(expr -> { + expr = PsiUtil.skipParenthesizedExprDown(expr); + if (expr == null) { + return; + } + PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(expr, PsiAssignmentExpression.class); + PsiElement context = PsiTreeUtil.getParentOfType(expr, PsiForStatement.class, PsiClassInitializer.class); + if (context instanceof PsiForStatement forStatement && PsiTreeUtil.isAncestor(forStatement.getInitialization(), expr, true)) { + return; + } + if (context instanceof PsiClassInitializer classInitializer + && expr instanceof PsiReferenceExpression ref + && assignment != null) { + Object constValue = ExpressionUtils.computeConstantExpression(assignment.getRExpression()); + if (constValue == PsiTypesUtil.getDefaultValue(expr.getType()) + && ref.resolve() instanceof PsiField field + && (field.isStatic() || ExpressionUtil.isEffectivelyUnqualified(ref)) + && field.getContainingClass() == classInitializer.getContainingClass()) { + return; + } + } + LocalizeValue message = assignment != null && !JavaTokenType.EQ.equals(assignment.getOperationTokenType()) + ? JavaAnalysisLocalize.dataflowMessageRedundantUpdate() + : JavaAnalysisLocalize.dataflowMessageRedundantAssignment(); + reporter.newProblem(message) + .range(expr) + .withOptionalFix(createRemoveAssignmentFix(assignment)) + .create(); + }); + } + + private void reportMutabilityViolations(ProblemsHolder holder, Set violations, LocalizeValue message) { + for (PsiElement violation : violations) { + holder.newProblem(message) + .range(violation) + .withOptionalFix(createMutabilityViolationFix(violation, holder.isOnTheFly())) + .create(); + } + } + + @Nullable + protected LocalQuickFix createMutabilityViolationFix(PsiElement violation, boolean onTheFly) { + return null; + } + + @RequiredReadAction + protected void reportNullabilityProblems( + ProblemReporter reporter, + List> problems, + Map expressions, + DataFlowInspectionStateBase state + ) { + for (NullabilityProblem problem : problems) { + PsiExpression expression = problem.getDereferencedExpression(); + if (!state.REPORT_UNSOUND_WARNINGS) { + if (expression == null) { + continue; + } + PsiExpression unwrapped = PsiUtil.skipParenthesizedExprDown(expression); + if (!ExpressionUtils.isNullLiteral(unwrapped) && expressions.get(expression) != ConstantResult.NULL) { + continue; + } + } + NullabilityProblemKind.innerClassNPE.ifMyProblem( + problem, + newExpression -> reporter.newProblem(problem.getMessage(expressions)) + .range(getElementToHighlight(newExpression)) + .withFixes(createNPEFixes(newExpression.getQualifier(), newExpression, reporter.isOnTheFly(), state)) + .create() + ); + NullabilityProblemKind.callMethodRefNPE.ifMyProblem( + problem, + methodRef -> reporter.newProblem(JavaAnalysisLocalize.dataflowMessageNpeMethodrefInvocation()) + .range(methodRef) + .withFixes(createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly())) + .create() + ); + NullabilityProblemKind.callNPE.ifMyProblem( + problem, + call -> reportCallMayProduceNpe(reporter, problem.getMessage(expressions), call, state) + ); + NullabilityProblemKind.passingToNotNullParameter.ifMyProblem( + problem, + expr -> reporter.newProblem(problem.getMessage(expressions)) + .range(expression) + .withFixes(createNPEFixes(expression, expression, reporter.isOnTheFly(), state)) + .create() + ); + NullabilityProblemKind.passingToNotNullMethodRefParameter.ifMyProblem( + problem, + methodRef -> reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePassingNullableArgumentMethodref()) + .range(methodRef) + .withFixes(createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly())) + .create() + ); + NullabilityProblemKind.unboxingMethodRefParameter.ifMyProblem( + problem, + methodRef -> reporter.newProblem(JavaAnalysisLocalize.dataflowMessageUnboxingNullableArgumentMethodref()) + .range(methodRef) + .withFixes(createMethodReferenceNPEFixes(methodRef, reporter.isOnTheFly())) + .create() + ); + NullabilityProblemKind.arrayAccessNPE.ifMyProblem( + problem, + arrayAccess -> reporter.newProblem(problem.getMessage(expressions)) + .range(arrayAccess) + .withFixes(createNPEFixes( + arrayAccess.getArrayExpression(), + arrayAccess, + reporter.isOnTheFly(), + state + )) + .create() + ); + NullabilityProblemKind.fieldAccessNPE.ifMyProblem( + problem, + element -> { + PsiExpression fieldAccess = element.getParent() instanceof PsiReferenceExpression refExpr ? refExpr : element; + reporter.newProblem(problem.getMessage(expressions)) + .range(element) + .withFixes(createNPEFixes(element, fieldAccess, reporter.isOnTheFly(), state)) + .create(); + } + ); + NullabilityProblemKind.unboxingNullable.ifMyProblem( + problem, + element -> { + PsiExpression anchor = expression; + if (anchor instanceof PsiTypeCastExpression typeCastExpression && anchor.getType() instanceof PsiPrimitiveType) { + anchor = Objects.requireNonNull(typeCastExpression.getOperand()); + } + reporter.newProblem(problem.getMessage(expressions)) + .range(anchor) + .create(); + } + ); + NullabilityProblemKind.nullableFunctionReturn.ifMyProblem( + problem, + expr -> reporter.newProblem(problem.getMessage(expressions)) + .range(expression == null ? expr : expression) + .create() + ); + Consumer reportNullability = expr -> reportNullabilityProblem(reporter, problem, expression, expressions, state); + NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, reportNullability); + NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, reportNullability); + if (state.SUGGEST_NULLABLE_ANNOTATIONS) { + NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.ifMyProblem( + problem, + methodRef -> reporter.newProblem(problem.getMessage(expressions)) + .range(methodRef) + .create() + ); + NullabilityProblemKind.passingToNonAnnotatedParameter.ifMyProblem( + problem, + top -> reportNullableArgumentsPassedToNonAnnotated(reporter, problem.getMessage(expressions), expression, top, state) + ); + NullabilityProblemKind.assigningToNonAnnotatedField.ifMyProblem( + problem, + top -> reportNullableAssignedToNonAnnotatedField(reporter, top, expression, problem.getMessage(expressions), state) + ); + } + } + } + + private void reportNullabilityProblem( + ProblemReporter reporter, + NullabilityProblem problem, + PsiExpression expr, + Map expressions, DataFlowInspectionStateBase state + ) { + reporter.newProblem(problem.getMessage(expressions)) + .range(expr) + .withFixes(createNPEFixes(expr, expr, reporter.isOnTheFly(), state)) + .create(); + } + + private static void reportArrayAccessProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { + visitor.outOfBoundsArrayAccesses().forEach(access -> { + PsiExpression indexExpression = access.getIndexExpression(); + if (indexExpression != null) { + holder.newProblem(JavaAnalysisLocalize.dataflowMessageArrayIndexOutOfBounds()) + .range(indexExpression) + .create(); + } + }); + } + + private static void reportArrayStoreProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { + visitor.getArrayStoreProblems().forEach( + (assignment, types) -> + holder.newProblem(JavaAnalysisLocalize.dataflowMessageArraystore( + types.getFirst().getCanonicalText(), + types.getSecond().getCanonicalText() + )) + .range(assignment.getOperationSign()) + .create() + ); + } + + private void reportMethodReferenceProblems(ProblemsHolder holder, DataFlowInstructionVisitor visitor) { + visitor.getMethodReferenceResults().forEach((methodRef, result) -> { + if (result != ConstantResult.UNKNOWN) { + Object value = result.value(); + holder.newProblem(JavaAnalysisLocalize.dataflowMessageConstantMethodReference(value)) + .range(methodRef) + .withFix(createReplaceWithTrivialLambdaFix(value)) + .create(); + } + }); + } + + @RequiredReadAction + private void reportAlwaysReturnsNotNull(ProblemsHolder holder, PsiElement scope) { + if (!(scope.getParent() instanceof PsiMethod method && !PsiUtil.canBeOverridden(method))) { + return; + } + + NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(scope.getProject()).findOwnNullabilityInfo(method); + if (info == null || info.getNullability() != Nullability.NULLABLE) { + return; + } + + PsiAnnotation annotation = info.getAnnotation(); + if (!annotation.isPhysical() || alsoAppliesToInternalSubType(annotation, method)) { + return; + } + + PsiJavaCodeReferenceElement annoName = annotation.getNameReferenceElement(); + assert annoName != null; + holder.newProblem(JavaAnalysisLocalize.dataflowMessageReturnNotnullFromNullable( + NullableStuffInspectionBase.getPresentableAnnoName(annotation), + method.getName() + )) + .range(annoName) + .withOptionalFix(AddAnnotationPsiFix.createAddNotNullFix(method)) + .withOptionalFix( + holder.isOnTheFly() + ? new SetInspectionOptionFix<>( + this, + (i, v) -> i.REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = v, + JavaAnalysisLocalize.inspectionDataFlowTurnOffNullableReturningNotnullQuickfix(), + false + ) + : null + ) + .create(); + } + + @RequiredReadAction + private static boolean alsoAppliesToInternalSubType(PsiAnnotation annotation, PsiMethod method) { + return AnnotationTargetUtil.isTypeAnnotation(annotation) && method.getReturnType() instanceof PsiArrayType; + } + + private void reportAlwaysFailingCalls(ProblemReporter reporter, DataFlowInstructionVisitor visitor, DataFlowInspectionStateBase state) { + visitor.alwaysFailingCalls() + .remove(TestUtils::isExceptionExpected) + .forEach(call -> reporter.newProblem(getContractMessage(JavaMethodContractUtil.getMethodCallContracts(call))) + .range(getElementToHighlight(call)) + .withOptionalFix( + reporter.isOnTheFly() ? createExplainFix(call, new TrackingRunner.FailingCallDfaProblemType(), state) : null + ) + .create() + ); + } + + private static LocalizeValue getContractMessage(List contracts) { + return contracts.stream().allMatch(mc -> mc.getConditions().stream().allMatch(ContractValue::isBoundCheckingCondition)) + ? JavaAnalysisLocalize.dataflowMessageContractFailIndex() + : JavaAnalysisLocalize.dataflowMessageContractFail(); + } + + private static PsiElement getElementToHighlight(PsiCall call) { + PsiJavaCodeReferenceElement ref; + if (call instanceof PsiNewExpression newExpression) { + ref = newExpression.getClassReference(); + } + else if (call instanceof PsiMethodCallExpression methodCall) { + ref = methodCall.getMethodExpression(); + } + else { + return call; + } + if (ref != null) { + PsiElement name = ref.getReferenceNameElement(); + return name != null ? name : ref; + } + return call; + } + + private static void reportOptionalOfNullableImprovements(ProblemReporter reporter, Map nullArgs) { + nullArgs.forEach((anchor, alwaysPresent) -> { + if (alwaysPresent == ThreeState.UNSURE) { + return; + } + if (alwaysPresent.toBoolean()) { + reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePassingNonNullArgumentToOptional()) + .range(anchor) + .withOptionalFix(DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(anchor)) + .create(); + } + else { + reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePassingNullArgumentToOptional()) + .range(anchor) + .withOptionalFix(DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(anchor)) + .create(); + } + }); + } + + @RequiredReadAction + private void reportNullableArgumentsPassedToNonAnnotated( + ProblemReporter reporter, + LocalizeValue message, + PsiExpression expression, + PsiExpression top, + DataFlowInspectionStateBase state + ) { + PsiParameter parameter = MethodCallUtils.getParameterForArgument(top); + if (parameter != null && BaseIntentionAction.canModify(parameter) && AnnotationUtil.isAnnotatingApplicable(parameter)) { + reporter.newProblem(message) + .range(expression) + .withFixes(createNPEFixes(expression, top, reporter.isOnTheFly(), state)) + .withOptionalFix(AddAnnotationPsiFix.createAddNullableFix(parameter)) + .create(); + } + } + + @RequiredReadAction + private void reportNullableAssignedToNonAnnotatedField( + ProblemReporter reporter, + PsiExpression top, + PsiExpression expression, + LocalizeValue message, + DataFlowInspectionStateBase state + ) { + PsiField field = getAssignedField(top); + if (field != null) { + reporter.newProblem(message) + .range(expression) + .withFixes(createNPEFixes(expression, top, reporter.isOnTheFly(), state)) + .withOptionalFix(AddAnnotationPsiFix.createAddNullableFix(field)) + .create(); + } + } + + @Nullable + @RequiredReadAction + private static PsiField getAssignedField(PsiElement assignedValue) { + if (PsiUtil.skipParenthesizedExprUp(assignedValue.getParent()) instanceof PsiAssignmentExpression assignment) { + PsiElement target = assignment.getLExpression() instanceof PsiReferenceExpression refExpr ? refExpr.resolve() : null; + return tryCast(target, PsiField.class); + } + return null; + } + + private void reportCallMayProduceNpe( + ProblemReporter reporter, + LocalizeValue message, + PsiMethodCallExpression callExpression, + DataFlowInspectionStateBase state + ) { + PsiReferenceExpression methodExpression = callExpression.getMethodExpression(); + + reporter.newProblem(message) + .range(getElementToHighlight(callExpression)) + .withFixes(createNPEFixes(methodExpression.getQualifierExpression(), callExpression, reporter.isOnTheFly(), state)) + .withOptionalFix(ReplaceWithObjectsEqualsFix.createFix(callExpression, methodExpression)) + .create(); + } + + private void reportFailingCasts( + ProblemReporter reporter, + DataFlowInstructionVisitor visitor, + Map constantExpressions, + DataFlowInspectionStateBase state + ) { + visitor.getFailingCastExpressions().forKeyValue((typeCast, info) -> { + boolean alwaysFails = info.getFirst(); + PsiType realType = info.getSecond(); + if (!state.REPORT_UNSOUND_WARNINGS && !alwaysFails) { + return; + } + PsiExpression operand = typeCast.getOperand(); + PsiTypeElement castType = typeCast.getCastType(); + ConstantResult result = constantExpressions.get(PsiUtil.skipParenthesizedExprDown(operand)); + // Skip reporting if cast operand is always null: null can be cast to anything + if (result == ConstantResult.NULL || ExpressionUtils.isNullLiteral(operand)) { + return; + } + assert castType != null; + assert operand != null; + String text = PsiExpressionTrimRenderer.render(operand); + LocalizeValue message = alwaysFails + ? JavaAnalysisLocalize.dataflowMessageCceAlways(text) + : JavaAnalysisLocalize.dataflowMessageCce(text); + reporter.newProblem(message) + .range(castType) + .withFixes(createCastFixes(typeCast, realType, reporter.isOnTheFly(), alwaysFails)) + .withOptionalFix(reporter.isOnTheFly() ? createExplainFix(typeCast, new TrackingRunner.CastDfaProblemType(), state) : null) + .create(); + }); + } + + @RequiredReadAction + private void reportConstantBoolean( + ProblemReporter reporter, + PsiElement psiAnchor, + boolean evaluatesToTrue, + DataFlowInspectionStateBase state + ) { + while (psiAnchor instanceof PsiParenthesizedExpression parenthesized) { + psiAnchor = parenthesized.getExpression(); + } + if (psiAnchor == null || shouldBeSuppressed(psiAnchor)) { + return; + } + boolean isAssertion = isAssertionEffectively(psiAnchor, evaluatesToTrue); + if (state.DONT_REPORT_TRUE_ASSERT_STATEMENTS && isAssertion) { + return; + } + + PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()); + if (parent instanceof PsiAssignmentExpression assignment + && PsiTreeUtil.isAncestor(assignment.getLExpression(), psiAnchor, false)) { + reporter.newProblem(JavaAnalysisLocalize.dataflowMessagePointlessAssignmentExpression(Boolean.toString(evaluatesToTrue))) + .range(psiAnchor) + .withFixes(createConditionalAssignmentFixes(evaluatesToTrue, assignment, reporter.isOnTheFly())) + .create(); + return; + } + + LocalizeValue message = isAtRHSOfBooleanAnd(psiAnchor) + ? JavaAnalysisLocalize.dataflowMessageConstantConditionWhenReached(evaluatesToTrue ? 1 : 0) + : JavaAnalysisLocalize.dataflowMessageConstantCondition(evaluatesToTrue ? 1 : 0); + ProblemBuilder pBuilder = reporter.newProblem(message).range(psiAnchor); + if (!isCoveredBySurroundingFix(psiAnchor, evaluatesToTrue)) { + pBuilder.withOptionalFix(createSimplifyBooleanExpressionFix(psiAnchor, evaluatesToTrue)); + if (isAssertion && reporter.isOnTheFly()) { + pBuilder.withFix(new SetInspectionOptionFix<>( + this, + (i, v) -> i.DONT_REPORT_TRUE_ASSERT_STATEMENTS = v, + JavaAnalysisLocalize.inspectionDataFlowTurnOffTrueAssertsQuickfix(), + true + )); + } + pBuilder.withOptionalFix(createReplaceWithNullCheckFix(psiAnchor, evaluatesToTrue)); + } + if (reporter.isOnTheFly() && psiAnchor instanceof PsiExpression expr) { + pBuilder.withOptionalFix(createExplainFix(expr, new TrackingRunner.ValueDfaProblemType(evaluatesToTrue), state)); + } + pBuilder.create(); + } + + @Nullable + protected LocalQuickFix createExplainFix( + PsiExpression anchor, + TrackingRunner.DfaProblemType problemType, + DataFlowInspectionStateBase state + ) { + return null; + } + + private static boolean isCoveredBySurroundingFix(PsiElement anchor, boolean evaluatesToTrue) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent()); + if (parent instanceof PsiPolyadicExpression polyadic) { + IElementType tokenType = polyadic.getOperationTokenType(); + return JavaTokenType.ANDAND.equals(tokenType) && !evaluatesToTrue + || JavaTokenType.OROR.equals(tokenType) && evaluatesToTrue; + } + return parent instanceof PsiExpression expr && BoolUtils.isNegation(expr); + } + + @Contract("null -> false") + @RequiredReadAction + private static boolean shouldBeSuppressed(PsiElement anchor) { + if (!(anchor instanceof PsiExpression expression)) { + return false; + } + // Don't report System.out.println(b = false) or doSomething((Type)null) + if (anchor instanceof PsiAssignmentExpression || anchor instanceof PsiTypeCastExpression) { + return true; + } + // For conditional the root cause (constant condition or both branches constant) should be already reported for branches + if (anchor instanceof PsiConditionalExpression) { + return true; + } + if (expression instanceof PsiReferenceExpression ref + && TRUE_OR_FALSE.contains(ref.getReferenceName()) + && ref.resolve() instanceof PsiField field) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass != null && CommonClassNames.JAVA_LANG_BOOLEAN.equals(containingClass.getQualifiedName())) { + return true; + } + } + if (expression instanceof PsiInstanceOfExpression instanceOfExpression) { + PsiType type = instanceOfExpression.getOperand().getType(); + if (type == null || !TypeConstraints.instanceOf(type).isResolved()) { + return true; + } + } + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + // Don't report "x" in "x == null" as will be anyways reported as "always true" + if (parent instanceof PsiBinaryExpression binaryExpression && ExpressionUtils.getValueComparedWithNull(binaryExpression) != null) { return true; - } - } - } - } - if (expression instanceof PsiInstanceOfExpression) { - PsiType type = ((PsiInstanceOfExpression) expression).getOperand().getType(); - if (type == null || !TypeConstraints.instanceOf(type).isResolved()) { - return true; - } - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - // Don't report "x" in "x == null" as will be anyways reported as "always true" - if (parent instanceof PsiBinaryExpression && ExpressionUtils.getValueComparedWithNull((PsiBinaryExpression) parent) != null) { - return true; - } - // Dereference of null will be covered by other warning - if (ExpressionUtils.isVoidContext(expression) || isDereferenceContext(expression)) { - return true; - } - // We assume all Void variables as null because you cannot instantiate it without dirty hacks - // However reporting them as "always null" looks redundant (dereferences or comparisons will be reported though). - if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_VOID, expression.getType())) { - return true; - } - if (isFlagCheck(anchor)) { - return true; - } - boolean condition = isCondition(expression); - if (!condition && expression instanceof PsiReferenceExpression) { - PsiVariable variable = tryCast(((PsiReferenceExpression) expression).resolve(), PsiVariable.class); - if (variable instanceof PsiField && - variable.hasModifierProperty(PsiModifier.STATIC) && - ExpressionUtils.isNullLiteral(variable.getInitializer())) { - return true; - } - return variable instanceof PsiLocalVariable && variable.hasModifierProperty(PsiModifier.FINAL) && - PsiUtil.isCompileTimeConstant(variable); - } - if (!condition && expression instanceof PsiMethodCallExpression) { - List contracts = JavaMethodContractUtil.getMethodCallContracts((PsiCallExpression) expression); - ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(contracts); - if (value != null) { - return true; - } - if (!(parent instanceof PsiAssignmentExpression) && !(parent instanceof PsiVariable) && - !(parent instanceof PsiReturnStatement)) { - PsiMethod method = ((PsiMethodCallExpression) expression).resolveMethod(); - if (method == null || !JavaMethodContractUtil.isPure(method)) { - return true; - } - } - } - while (expression != null && BoolUtils.isNegation(expression)) { - expression = BoolUtils.getNegated(expression); - } - PsiMethodCallExpression call = tryCast(expression, PsiMethodCallExpression.class); - // Reported by "Equals with itself" inspection; avoid double reporting - return call != null && EqualsWithItselfInspection.isEqualsWithItself(call); - } - - private static boolean isDereferenceContext(PsiExpression ref) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); - return parent instanceof PsiReferenceExpression || parent instanceof PsiArrayAccessExpression - || parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement; - } - - private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) { - if (evaluatesToTrue) { - return null; - } - if (!(psiAnchor instanceof PsiMethodCallExpression)) { - return null; - } - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) psiAnchor; - if (!MethodCallUtils.isEqualsCall(methodCallExpression)) { - return null; - } - PsiExpression arg = ArrayUtil.getFirstElement(methodCallExpression.getArgumentList().getExpressions()); - if (!ExpressionUtils.isNullLiteral(arg)) { - return null; - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()); - return EqualsToEqualityFix.buildFix(methodCallExpression, parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression) parent)); - } - - protected LocalQuickFix[] createConditionalAssignmentFixes(boolean evaluatesToTrue, PsiAssignmentExpression parent, final boolean onTheFly) { - return LocalQuickFix.EMPTY_ARRAY; - } - - private static - @Nullable - PsiMethod getScopeMethod(PsiElement block) { - PsiElement parent = block.getParent(); - if (parent instanceof PsiMethod) { - return (PsiMethod) parent; - } - if (parent instanceof PsiLambdaExpression) { - return LambdaUtil.getFunctionalInterfaceMethod(((PsiLambdaExpression) parent).getFunctionalInterfaceType()); - } - return null; - } - - private void reportNullableReturns(ProblemReporter reporter, - List> problems, - Map expressions, - @Nonnull PsiElement block) { - final PsiMethod method = getScopeMethod(block); - if (method == null) { - return; - } - NullableNotNullManager manager = NullableNotNullManager.getInstance(method.getProject()); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(method); - if (info == null) { - info = DfaPsiUtil.getTypeNullabilityInfo(PsiTypesUtil.getMethodReturnType(block)); - } - PsiAnnotation anno = info == null ? null : info.getAnnotation(); - Nullability nullability = info == null ? Nullability.UNKNOWN : info.getNullability(); - if (nullability == Nullability.NULLABLE) { - if (!AnnotationUtil.isInferredAnnotation(anno)) { - return; - } - if (DfaPsiUtil.getTypeNullability(method.getReturnType()) == Nullability.NULLABLE) { - return; - } - } - - if (nullability != Nullability.NOT_NULL && (!SUGGEST_NULLABLE_ANNOTATIONS || block.getParent() instanceof PsiLambdaExpression)) { - return; - } - - PsiType returnType = method.getReturnType(); - // no warnings in void lambdas, where the expression is not returned anyway - if (block instanceof PsiExpression && block.getParent() instanceof PsiLambdaExpression && PsiType.VOID.equals(returnType)) { - return; - } - - // no warnings for Void methods, where only null can be possibly returned - if (returnType == null || returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID)) { - return; - } - - for (NullabilityProblem problem : StreamEx.of(problems).map(NullabilityProblemKind.nullableReturn::asMyProblem).nonNull()) { - final PsiExpression anchor = problem.getAnchor(); - PsiExpression expr = problem.getDereferencedExpression(); - - boolean exactlyNull = isNullLiteralExpression(expr) || expressions.get(expr) == ConstantResult.NULL; - if (!REPORT_UNSOUND_WARNINGS && !exactlyNull) { - continue; - } - if (nullability == Nullability.NOT_NULL) { - String presentable = NullableStuffInspectionBase.getPresentableAnnoName(anno); - final String text = exactlyNull - ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnull", presentable) - : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnull", presentable); - reporter.registerProblem(expr, text, createNPEFixes(expr, expr, reporter.isOnTheFly()).toArray(LocalQuickFix.EMPTY_ARRAY)); - } else if (AnnotationUtil.isAnnotatingApplicable(anchor)) { - final String defaultNullable = manager.getDefaultNullable(); - final String presentableNullable = StringUtil.getShortName(defaultNullable); - final String text = exactlyNull - ? JavaAnalysisBundle.message("dataflow.message.return.null.from.notnullable", presentableNullable) - : JavaAnalysisBundle.message("dataflow.message.return.nullable.from.notnullable", presentableNullable); - PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); - final LocalQuickFix fix = surroundingMethod == null ? null : - new AddAnnotationPsiFix(defaultNullable, surroundingMethod, - ArrayUtil.toStringArray(manager.getNotNulls())); - reporter.registerProblem(expr, text, fix); - } - } - } - - private static boolean isAssertionEffectively(@Nonnull PsiElement anchor, ConstantResult result) { - Object value = result.value(); - if (value instanceof Boolean) { - return isAssertionEffectively(anchor, (Boolean) value); - } - if (value != null) { - return false; - } - return isAssertCallArgument(anchor, ContractValue.nullValue()); - } - - private static boolean isAssertionEffectively(@Nonnull PsiElement anchor, boolean evaluatesToTrue) { - PsiElement parent; - while (true) { - parent = anchor.getParent(); - if (parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression) parent)) { - evaluatesToTrue = !evaluatesToTrue; - anchor = parent; - continue; - } - if (parent instanceof PsiParenthesizedExpression) { - anchor = parent; - continue; - } - if (parent instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression) parent).getOperationTokenType(); - if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) { - // always true operand makes always true OR-chain and does not affect the result of AND-chain - // Note that in `assert unknownExpression && trueExpression;` the trueExpression should not be reported - // because this assert is essentially the shortened `assert unknownExpression; assert trueExpression;` - // which is not reported. - boolean causesShortCircuit = (tokenType.equals(JavaTokenType.OROR) == evaluatesToTrue) && - ArrayUtil.getLastElement(((PsiPolyadicExpression) parent).getOperands()) != anchor; - if (!causesShortCircuit) { - // We still report `assert trueExpression || unknownExpression`, because here `unknownExpression` is never checked - // which is probably not intended. - anchor = parent; - continue; - } - } - } - break; - } - if (parent instanceof PsiAssertStatement) { - return evaluatesToTrue; - } - if (parent instanceof PsiIfStatement && anchor == ((PsiIfStatement) parent).getCondition()) { - PsiStatement thenBranch = ControlFlowUtils.stripBraces(((PsiIfStatement) parent).getThenBranch()); - if (thenBranch instanceof PsiThrowStatement) { - return !evaluatesToTrue; - } - } - return isAssertCallArgument(anchor, ContractValue.booleanValue(evaluatesToTrue)); - } - - private static boolean isAssertCallArgument(@Nonnull PsiElement anchor, @Nonnull ContractValue wantedConstraint) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent()); - if (parent instanceof PsiExpressionList) { - int index = ArrayUtil.indexOf(((PsiExpressionList) parent).getExpressions(), anchor); - if (index >= 0) { - PsiMethodCallExpression call = tryCast(parent.getParent(), PsiMethodCallExpression.class); - if (call != null) { - MethodContract contract = ContainerUtil.getOnlyItem(JavaMethodContractUtil.getMethodCallContracts(call)); - if (contract != null && contract.getReturnValue().isFail()) { - ContractValue condition = ContainerUtil.getOnlyItem(contract.getConditions()); - if (condition != null) { - return condition.getArgumentComparedTo(wantedConstraint, false).orElse(-1) == index; - } - } - } - } - } - return false; - } - - private static boolean isAtRHSOfBooleanAnd(PsiElement expr) { - PsiElement cur = expr; - - while (cur != null && !(cur instanceof PsiMember)) { - PsiElement parent = cur.getParent(); - - if (parent instanceof PsiBinaryExpression && cur == ((PsiBinaryExpression) parent).getROperand()) { - return true; - } - - cur = parent; - } - - return false; - } - - private static boolean isFlagCheck(PsiElement element) { - PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class); - PsiExpression topExpression = scope instanceof PsiIfStatement ? ((PsiIfStatement) scope).getCondition() : - scope instanceof PsiVariable ? ((PsiVariable) scope).getInitializer() : - null; - if (!PsiTreeUtil.isAncestor(topExpression, element, false)) { - return false; - } - - return StreamEx.ofTree(topExpression, e -> StreamEx.of(e.getChildren())) - .anyMatch(DataFlowInspectionBase::isCompileTimeFlagCheck); - } - - private static boolean isCompileTimeFlagCheck(PsiElement element) { - if (element instanceof PsiBinaryExpression) { - PsiBinaryExpression binOp = (PsiBinaryExpression) element; - if (ComparisonUtils.isComparisonOperation(binOp.getOperationTokenType())) { - PsiExpression comparedWith = null; - if (ExpressionUtils.isLiteral(binOp.getROperand())) { - comparedWith = binOp.getLOperand(); - } else if (ExpressionUtils.isLiteral(binOp.getLOperand())) { - comparedWith = binOp.getROperand(); - } - comparedWith = PsiUtil.skipParenthesizedExprDown(comparedWith); - if (isConstantOfType(comparedWith, PsiType.INT, PsiType.LONG)) { - // like "if(DEBUG_LEVEL > 2)" - return true; - } - if (comparedWith instanceof PsiBinaryExpression) { - PsiBinaryExpression subOp = (PsiBinaryExpression) comparedWith; - if (subOp.getOperationTokenType().equals(JavaTokenType.AND)) { - PsiExpression left = PsiUtil.skipParenthesizedExprDown(subOp.getLOperand()); - PsiExpression right = PsiUtil.skipParenthesizedExprDown(subOp.getROperand()); - if (isConstantOfType(left, PsiType.INT, PsiType.LONG) || - isConstantOfType(right, PsiType.INT, PsiType.LONG)) { - // like "if((FLAGS & SOME_FLAG) != 0)" - return true; - } - } - } - } - } - // like "if(DEBUG)" - return isConstantOfType(element, PsiType.BOOLEAN); - } - - private static boolean isConstantOfType(PsiElement element, PsiPrimitiveType... types) { - PsiElement resolved = element instanceof PsiReferenceExpression ? ((PsiReferenceExpression) element).resolve() : null; - if (!(resolved instanceof PsiField)) { - return false; - } - PsiVariable field = (PsiVariable) resolved; - return field.hasModifierProperty(PsiModifier.STATIC) && PsiUtil.isCompileTimeConstant(field) && - ArrayUtil.contains(field.getType(), types); - } - - private static boolean isNullLiteralExpression(PsiElement expr) { - return expr instanceof PsiExpression && ExpressionUtils.isNullLiteral((PsiExpression) expr); - } - - private - @Nullable - LocalQuickFix createSimplifyBooleanExpressionFix(PsiElement element, final boolean value) { - LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(element, value); - if (fix == null) { - return null; - } - final String text = fix.getText(); - return new LocalQuickFix() { - @Override - public - @Nonnull - String getName() { - return text; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement psiElement = descriptor.getPsiElement(); - if (psiElement == null) { - return; - } - final LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(psiElement, value); + } + // Dereference of null will be covered by other warning + if (ExpressionUtils.isVoidContext(expression) || isDereferenceContext(expression)) { + return true; + } + // We assume all Void variables as null because you cannot instantiate it without dirty hacks + // However reporting them as "always null" looks redundant (dereferences or comparisons will be reported though). + if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_VOID, expression.getType())) { + return true; + } + if (isFlagCheck(anchor)) { + return true; + } + boolean condition = isCondition(expression); + if (!condition && expression instanceof PsiReferenceExpression referenceExpression) { + PsiVariable variable = tryCast(referenceExpression.resolve(), PsiVariable.class); + return variable instanceof PsiField field && field.isStatic() + && ExpressionUtils.isNullLiteral(field.getInitializer()) + || variable instanceof PsiLocalVariable localVar && localVar.hasModifierProperty(PsiModifier.FINAL) + && PsiUtil.isCompileTimeConstant(localVar); + } + if (!condition && expression instanceof PsiMethodCallExpression methodCall) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(methodCall); + ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(contracts); + if (value != null) { + return true; + } + if (!(parent instanceof PsiAssignmentExpression) + && !(parent instanceof PsiVariable) + && !(parent instanceof PsiReturnStatement)) { + PsiMethod method = methodCall.resolveMethod(); + if (method == null || !JavaMethodContractUtil.isPure(method)) { + return true; + } + } + } + while (expression != null && BoolUtils.isNegation(expression)) { + expression = BoolUtils.getNegated(expression); + } + PsiMethodCallExpression call = tryCast(expression, PsiMethodCallExpression.class); + // Reported by "Equals with itself" inspection; avoid double reporting + return call != null && EqualsWithItselfInspection.isEqualsWithItself(call); + } + + private static boolean isDereferenceContext(PsiExpression ref) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); + return parent instanceof PsiReferenceExpression || parent instanceof PsiArrayAccessExpression + || parent instanceof PsiSwitchStatement || parent instanceof PsiSynchronizedStatement; + } + + private static LocalQuickFix createReplaceWithNullCheckFix(PsiElement psiAnchor, boolean evaluatesToTrue) { + if (evaluatesToTrue) { + return null; + } + if (!(psiAnchor instanceof PsiMethodCallExpression methodCallExpression)) { + return null; + } + if (!MethodCallUtils.isEqualsCall(methodCallExpression)) { + return null; + } + PsiExpression arg = ArrayUtil.getFirstElement(methodCallExpression.getArgumentList().getExpressions()); + if (!ExpressionUtils.isNullLiteral(arg)) { + return null; + } + PsiElement parent = PsiUtil.skipParenthesizedExprUp(psiAnchor.getParent()); + return EqualsToEqualityFix.buildFix( + methodCallExpression, + parent instanceof PsiExpression expr && BoolUtils.isNegation(expr) + ); + } + + protected LocalQuickFix[] createConditionalAssignmentFixes(boolean evaluatesToTrue, PsiAssignmentExpression parent, boolean onTheFly) { + return LocalQuickFix.EMPTY_ARRAY; + } + + @Nullable + private static PsiMethod getScopeMethod(PsiElement block) { + PsiElement parent = block.getParent(); + if (parent instanceof PsiMethod method) { + return method; + } + if (parent instanceof PsiLambdaExpression lambda) { + return LambdaUtil.getFunctionalInterfaceMethod(lambda.getFunctionalInterfaceType()); + } + return null; + } + + @RequiredReadAction + private void reportNullableReturns( + ProblemReporter reporter, + List> problems, + Map expressions, + PsiElement block, DataFlowInspectionStateBase state + ) { + PsiMethod method = getScopeMethod(block); + if (method == null) { + return; + } + NullableNotNullManager manager = NullableNotNullManager.getInstance(method.getProject()); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(method); + if (info == null) { + info = DfaPsiUtil.getTypeNullabilityInfo(PsiTypesUtil.getMethodReturnType(block)); + } + PsiAnnotation anno = info == null ? null : info.getAnnotation(); + Nullability nullability = info == null ? Nullability.UNKNOWN : info.getNullability(); + if (nullability == Nullability.NULLABLE) { + if (!AnnotationUtil.isInferredAnnotation(anno)) { + return; + } + if (DfaPsiUtil.getTypeNullability(method.getReturnType()) == Nullability.NULLABLE) { + return; + } + } + + if (nullability != Nullability.NOT_NULL && (!state.SUGGEST_NULLABLE_ANNOTATIONS || block.getParent() instanceof PsiLambdaExpression)) { + return; + } + + PsiType returnType = method.getReturnType(); + // no warnings in void lambdas, where the expression is not returned anyway + if (block instanceof PsiExpression && block.getParent() instanceof PsiLambdaExpression && PsiType.VOID.equals(returnType)) { + return; + } + + // no warnings for Void methods, where only null can be possibly returned + if (returnType == null || returnType.equalsToText(CommonClassNames.JAVA_LANG_VOID)) { + return; + } + + StreamEx> nullabilityProblems = + StreamEx.of(problems).map(NullabilityProblemKind.nullableReturn::asMyProblem).nonNull(); + for (NullabilityProblem problem : nullabilityProblems) { + PsiExpression anchor = problem.getAnchor(); + PsiExpression expr = problem.getDereferencedExpression(); + + boolean exactlyNull = isNullLiteralExpression(expr) || expressions.get(expr) == ConstantResult.NULL; + if (!state.REPORT_UNSOUND_WARNINGS && !exactlyNull) { + continue; + } + if (nullability == Nullability.NOT_NULL) { + String presentable = NullableStuffInspectionBase.getPresentableAnnoName(anno); + LocalizeValue text = exactlyNull + ? JavaAnalysisLocalize.dataflowMessageReturnNullFromNotnull(presentable) + : JavaAnalysisLocalize.dataflowMessageReturnNullableFromNotnull(presentable); + reporter.newProblem(text) + .range(expr) + .withFixes(createNPEFixes(expr, expr, reporter.isOnTheFly(), state)) + .create(); + } + else if (AnnotationUtil.isAnnotatingApplicable(anchor)) { + String defaultNullable = manager.getDefaultNullable(); + String presentableNullable = StringUtil.getShortName(defaultNullable); + LocalizeValue text = exactlyNull + ? JavaAnalysisLocalize.dataflowMessageReturnNullFromNotnullable(presentableNullable) + : JavaAnalysisLocalize.dataflowMessageReturnNullableFromNotnullable(presentableNullable); + PsiMethod surroundingMethod = PsiTreeUtil.getParentOfType(anchor, PsiMethod.class, true, PsiLambdaExpression.class); + reporter.newProblem(text) + .range(expr) + .withOptionalFix( + surroundingMethod == null + ? null + : new AddAnnotationPsiFix(defaultNullable, surroundingMethod, ArrayUtil.toStringArray(manager.getNotNulls())) + ) + .create(); + } + } + } + + private static boolean isAssertionEffectively(PsiElement anchor, ConstantResult result) { + Object value = result.value(); + if (value instanceof Boolean booleanValue) { + return isAssertionEffectively(anchor, booleanValue); + } + return value == null && isAssertCallArgument(anchor, ContractValue.nullValue()); + } + + private static boolean isAssertionEffectively(PsiElement anchor, boolean evaluatesToTrue) { + PsiElement parent; + while (true) { + parent = anchor.getParent(); + if (parent instanceof PsiExpression expression && BoolUtils.isNegation(expression)) { + evaluatesToTrue = !evaluatesToTrue; + anchor = parent; + continue; + } + if (parent instanceof PsiParenthesizedExpression) { + anchor = parent; + continue; + } + if (parent instanceof PsiPolyadicExpression polyadicExpression) { + IElementType tokenType = polyadicExpression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) { + // always true operand makes always true OR-chain and does not affect the result of AND-chain + // Note that in `assert unknownExpression && trueExpression;` the trueExpression should not be reported + // because this assert is essentially the shortened `assert unknownExpression; assert trueExpression;` + // which is not reported. + boolean causesShortCircuit = (tokenType.equals(JavaTokenType.OROR) == evaluatesToTrue) && + ArrayUtil.getLastElement(((PsiPolyadicExpression) parent).getOperands()) != anchor; + if (!causesShortCircuit) { + // We still report `assert trueExpression || unknownExpression`, because here `unknownExpression` is never checked + // which is probably not intended. + anchor = parent; + continue; + } + } + } + break; + } + if (parent instanceof PsiAssertStatement) { + return evaluatesToTrue; + } + if (parent instanceof PsiIfStatement && anchor == ((PsiIfStatement) parent).getCondition()) { + PsiStatement thenBranch = ControlFlowUtils.stripBraces(((PsiIfStatement) parent).getThenBranch()); + if (thenBranch instanceof PsiThrowStatement) { + return !evaluatesToTrue; + } + } + return isAssertCallArgument(anchor, ContractValue.booleanValue(evaluatesToTrue)); + } + + private static boolean isAssertCallArgument(PsiElement anchor, ContractValue wantedConstraint) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent()); + if (parent instanceof PsiExpressionList expressionList) { + int index = ArrayUtil.indexOf(expressionList.getExpressions(), anchor); + if (index >= 0) { + PsiMethodCallExpression call = tryCast(parent.getParent(), PsiMethodCallExpression.class); + if (call != null) { + MethodContract contract = ContainerUtil.getOnlyItem(JavaMethodContractUtil.getMethodCallContracts(call)); + if (contract != null && contract.getReturnValue().isFail()) { + ContractValue condition = ContainerUtil.getOnlyItem(contract.getConditions()); + if (condition != null) { + return condition.getArgumentComparedTo(wantedConstraint, false).orElse(-1) == index; + } + } + } + } + } + return false; + } + + private static boolean isAtRHSOfBooleanAnd(PsiElement expr) { + PsiElement cur = expr; + + while (cur != null && !(cur instanceof PsiMember)) { + PsiElement parent = cur.getParent(); + + if (parent instanceof PsiBinaryExpression && cur == ((PsiBinaryExpression) parent).getROperand()) { + return true; + } + + cur = parent; + } + + return false; + } + + private static boolean isFlagCheck(PsiElement element) { + PsiElement scope = PsiTreeUtil.getParentOfType(element, PsiStatement.class, PsiVariable.class); + PsiExpression topExpression = scope instanceof PsiIfStatement ifStatement + ? ifStatement.getCondition() + : scope instanceof PsiVariable variable + ? variable.getInitializer() + : null; + return PsiTreeUtil.isAncestor(topExpression, element, false) + && StreamEx.ofTree(topExpression, e -> StreamEx.of(e.getChildren())) + .anyMatch(DataFlowInspectionBase::isCompileTimeFlagCheck); + } + + @RequiredReadAction + private static boolean isCompileTimeFlagCheck(PsiElement element) { + if (element instanceof PsiBinaryExpression binOp && ComparisonUtils.isComparisonOperation(binOp.getOperationTokenType())) { + PsiExpression comparedWith = null; + if (ExpressionUtils.isLiteral(binOp.getROperand())) { + comparedWith = binOp.getLOperand(); + } + else if (ExpressionUtils.isLiteral(binOp.getLOperand())) { + comparedWith = binOp.getROperand(); + } + comparedWith = PsiUtil.skipParenthesizedExprDown(comparedWith); + if (isConstantOfType(comparedWith, PsiType.INT, PsiType.LONG)) { + // like "if (DEBUG_LEVEL > 2)" + return true; + } + if (comparedWith instanceof PsiBinaryExpression subOp && subOp.getOperationTokenType().equals(JavaTokenType.AND)) { + PsiExpression left = PsiUtil.skipParenthesizedExprDown(subOp.getLOperand()); + PsiExpression right = PsiUtil.skipParenthesizedExprDown(subOp.getROperand()); + if (isConstantOfType(left, PsiType.INT, PsiType.LONG) + || isConstantOfType(right, PsiType.INT, PsiType.LONG)) { + // like "if ((FLAGS & SOME_FLAG) != 0)" + return true; + } + } + } + // like "if (DEBUG)" + return isConstantOfType(element, PsiType.BOOLEAN); + } + + @RequiredReadAction + private static boolean isConstantOfType(PsiElement element, PsiPrimitiveType... types) { + PsiElement resolved = element instanceof PsiReferenceExpression refExpr ? refExpr.resolve() : null; + return resolved instanceof PsiField field + && field.isStatic() + && PsiUtil.isCompileTimeConstant((PsiVariable) field) + && ArrayUtil.contains(field.getType(), types); + } + + private static boolean isNullLiteralExpression(PsiElement expr) { + return expr instanceof PsiExpression expression && ExpressionUtils.isNullLiteral(expression); + } + + private + @Nullable + LocalQuickFix createSimplifyBooleanExpressionFix(PsiElement element, final boolean value) { + LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(element, value); if (fix == null) { - return; - } - try { - LOG.assertTrue(psiElement.isValid()); - fix.applyFix(); - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Override - public - @Nonnull - String getFamilyName() { - return JavaAnalysisBundle.message("inspection.data.flow.simplify.boolean.expression.quickfix"); - } - }; - } - - protected static - @Nonnull - LocalQuickFix createSimplifyToAssignmentFix() { - return new SimplifyToAssignmentFix(); - } - - protected LocalQuickFixOnPsiElement createSimplifyBooleanFix(PsiElement element, boolean value) { - return null; - } - - @Override - public - @Nonnull - String getGroupDisplayName() { - return InspectionsBundle.message("group.names.probable.bugs"); - } - - @Override - public - @Nonnull - String getShortName() { - return SHORT_NAME; - } - - protected enum ConstantResult { - TRUE, - FALSE, - NULL, - ZERO, - UNKNOWN; + return null; + } + final LocalizeValue text = fix.getText(); + return new LocalQuickFix() { + @Override + public LocalizeValue getName() { + return text; + } + + @Override + @RequiredReadAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement psiElement = descriptor.getPsiElement(); + if (psiElement == null) { + return; + } + LocalQuickFixOnPsiElement fix = createSimplifyBooleanFix(psiElement, value); + if (fix == null) { + return; + } + try { + LOG.assertTrue(psiElement.isValid()); + fix.applyFix(); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + }; + } + + protected static LocalQuickFix createSimplifyToAssignmentFix() { + return new SimplifyToAssignmentFix(); + } + + protected LocalQuickFixOnPsiElement createSimplifyBooleanFix(PsiElement element, boolean value) { + return null; + } + + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesProbableBugs(); + } @Override public - @Nonnull - String toString() { - return this == ZERO ? "0" : StringUtil.toLowerCase(name()); - } - - public Object value() { - switch (this) { - case TRUE: - return Boolean.TRUE; - case FALSE: - return Boolean.FALSE; - case ZERO: - return 0; - case NULL: - return null; - default: - throw new UnsupportedOperationException(); - } - } - - static - @Nonnull - ConstantResult fromDfType(@Nonnull DfType dfType) { - if (dfType == DfTypes.NULL) { - return NULL; - } - if (dfType == DfTypes.TRUE) { - return TRUE; - } - if (dfType == DfTypes.FALSE) { - return FALSE; - } - if (DfConstantType.isConst(dfType, 0) || DfConstantType.isConst(dfType, 0L)) { - return ZERO; - } - return UNKNOWN; - } - - static - @Nonnull - ConstantResult mergeValue(@Nullable ConstantResult state, @Nonnull DfaMemoryState memState, @Nullable DfaValue value) { - if (state == UNKNOWN || value == null) { - return UNKNOWN; - } - ConstantResult nextState = fromDfType(memState.getUnboxedDfType(value)); - return state == null || state == nextState ? nextState : UNKNOWN; - } - } - - /** - * {@link ProblemsHolder} wrapper to avoid reporting two problems on the same anchor - */ - protected static class ProblemReporter { - private final Set myReportedAnchors = new HashSet<>(); - private final ProblemsHolder myHolder; - private final PsiElement myScope; - - ProblemReporter(ProblemsHolder holder, PsiElement scope) { - myHolder = holder; - myScope = scope; - } - - public void registerProblem(PsiElement element, String message, LocalQuickFix... fixes) { - if (register(element)) { - myHolder.registerProblem(element, message, fixes); - } - } - - void registerProblem(PsiElement element, String message, ProblemHighlightType type, LocalQuickFix... fixes) { - if (register(element)) { - myHolder.registerProblem(element, message, type, fixes); - } - } - - void registerProblem(PsiElement element, TextRange range, String message, LocalQuickFix... fixes) { - if (range == null) { - registerProblem(element, message, fixes); - } else { - myHolder.registerProblem(element, range, message, fixes); - } - } - - private boolean register(PsiElement element) { - // Suppress reporting for inlined simple methods - if (!PsiTreeUtil.isAncestor(myScope, element, false)) { - return false; - } - if (myScope instanceof PsiClass) { - PsiMember member = PsiTreeUtil.getParentOfType(element, PsiMember.class); - if (member instanceof PsiMethod && !((PsiMethod) member).isConstructor()) { - return false; - } - } - if (!myReportedAnchors.add(element)) { - return false; - } - if (element instanceof PsiParenthesizedExpression) { - PsiExpression deparenthesized = PsiUtil.skipParenthesizedExprDown((PsiExpression) element); - if (deparenthesized != null) { - myReportedAnchors.add(deparenthesized); + String getShortName() { + return SHORT_NAME; + } + + protected enum ConstantResult { + TRUE(Boolean.TRUE), + FALSE(Boolean.FALSE), + NULL(null), + ZERO(0) { + @Override + public String toString() { + return "0"; + } + }, + UNKNOWN(Void.TYPE); + + private final Object myValue; + + ConstantResult(Object value) { + myValue = value; + } + + public Object value() { + return myValue; + } + + @Override + public String toString() { + return StringUtil.toLowerCase(name()); + } + + static ConstantResult fromDfType(DfType dfType) { + if (dfType == DfTypes.NULL) { + return NULL; + } + if (dfType == DfTypes.TRUE) { + return TRUE; + } + if (dfType == DfTypes.FALSE) { + return FALSE; + } + if (DfConstantType.isConst(dfType, 0) || DfConstantType.isConst(dfType, 0L)) { + return ZERO; + } + return UNKNOWN; + } + + static ConstantResult mergeValue(@Nullable ConstantResult state, DfaMemoryState memState, @Nullable DfaValue value) { + if (state == UNKNOWN || value == null) { + return UNKNOWN; + } + ConstantResult nextState = fromDfType(memState.getUnboxedDfType(value)); + return state == null || state == nextState ? nextState : UNKNOWN; } - } - return true; } - boolean isOnTheFly() { - return myHolder.isOnTheFly(); + /** + * {@link ProblemsHolder} wrapper to avoid reporting two problems on the same anchor + */ + protected static class ProblemReporter { + private class MyProblemBuilder extends ProblemBuilderWrapper { + private Boolean registered = null; + + private MyProblemBuilder(ProblemBuilder subBuilder) { + super(subBuilder); + } + + @Override + public ProblemBuilder range(PsiElement element) { + checkRegistered(element); + return super.range(element); + } + + @Override + public void create() { + if (registered == null) { + throw new IllegalStateException("Range was not set"); + } + else if (registered == Boolean.TRUE) { + super.create(); + } + } + + private void checkRegistered(PsiElement element) { + if (registered != null) { + throw new IllegalStateException("Range was already set"); + } + registered = register(element); + } + } + + private final Set myReportedAnchors = new HashSet<>(); + private final ProblemsHolder myHolder; + private final PsiElement myScope; + + ProblemReporter(ProblemsHolder holder, PsiElement scope) { + myHolder = holder; + myScope = scope; + } + + public ProblemBuilder newProblem(LocalizeValue message) { + return new MyProblemBuilder(myHolder.newProblem(message)); + } + + private boolean register(PsiElement element) { + // Suppress reporting for inlined simple methods + if (!PsiTreeUtil.isAncestor(myScope, element, false)) { + return false; + } + if (myScope instanceof PsiClass) { + PsiMember member = PsiTreeUtil.getParentOfType(element, PsiMember.class); + if (member instanceof PsiMethod method && !method.isConstructor()) { + return false; + } + } + if (!myReportedAnchors.add(element)) { + return false; + } + if (element instanceof PsiParenthesizedExpression parenthesizedExpression) { + PsiExpression deparenthesized = PsiUtil.skipParenthesizedExprDown(parenthesizedExpression); + if (deparenthesized != null) { + myReportedAnchors.add(deparenthesized); + } + } + return true; + } + + boolean isOnTheFly() { + return myHolder.isOnTheFly(); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionStateBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionStateBase.java new file mode 100644 index 0000000000..733311a6fd --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInspectionStateBase.java @@ -0,0 +1,16 @@ +package com.intellij.java.analysis.impl.codeInspection.dataFlow; + +/** + * @author VISTALL + * @since 2023-03-19 + */ +public class DataFlowInspectionStateBase { + public boolean SUGGEST_NULLABLE_ANNOTATIONS; + public boolean DONT_REPORT_TRUE_ASSERT_STATEMENTS; + public boolean TREAT_UNKNOWN_MEMBERS_AS_NULLABLE; + public boolean IGNORE_ASSERT_STATEMENTS; + public boolean REPORT_CONSTANT_REFERENCE_VALUES = true; + public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; + public boolean REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true; + public boolean REPORT_UNSOUND_WARNINGS = true; +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java index 850fe6e33f..46c7d7cc69 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowInstructionVisitor.java @@ -14,454 +14,489 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.TypeUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.Application; -import consulo.application.ApplicationManager; import consulo.document.util.TextRange; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.logging.Logger; import consulo.util.collection.ContainerUtil; +import consulo.util.lang.Couple; import consulo.util.lang.Pair; import consulo.util.lang.ThreeState; +import org.jspecify.annotations.Nullable; import one.util.streamex.EntryStream; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.stream.Stream; import static consulo.util.lang.ObjectUtil.tryCast; final class DataFlowInstructionVisitor extends StandardInstructionVisitor { - private static final Logger LOG = Logger.getInstance(DataFlowInstructionVisitor.class); - private final Map, StateInfo> myStateInfos = new LinkedHashMap<>(); - private final Map myClassCastProblems = new HashMap<>(); - private final Map myRealOperandTypes = new HashMap<>(); - private final Map myFailingCalls = new HashMap<>(); - private final Map myConstantExpressions = new HashMap<>(); - private final Map myOfNullableCalls = new HashMap<>(); - private final Map> myArrayStoreProblems = new HashMap<>(); - private final Map myMethodReferenceResults = new HashMap<>(); - private final Map myOutOfBoundsArrayAccesses = new HashMap<>(); - private final Set myReceiverMutabilityViolation = new HashSet<>(); - private final Set myArgumentMutabilityViolation = new HashSet<>(); - private final Map mySameValueAssigned = new HashMap<>(); - private final Map mySameArguments = new HashMap<>(); - private final Map mySwitchLabelsReachability = new HashMap<>(); - private boolean myAlwaysReturnsNotNull = true; - private final List myEndOfInitializerStates = new ArrayList<>(); - - private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf( - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "min", "max").parameterCount(2), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "min", "max").parameterCount(2), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "replace").parameterCount(2) - ); - - @Override - public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - PsiExpression left = instruction.getLExpression(); - if (left != null && !Boolean.FALSE.equals(mySameValueAssigned.get(left))) { - if (!left.isPhysical()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Non-physical element in assignment instruction: " + left.getParent().getText(), new Throwable()); - } - } else { - DfaValue value = memState.peek(); - DfaValue target = memState.getStackValue(1); - DfType dfType = memState.getDfType(value); - if (target != null && memState.areEqual(value, target) && - !(dfType instanceof DfConstantType && isFloatingZero(((DfConstantType) dfType).getValue())) && - // Reporting strings is skipped because string reassignment might be intentionally used to deduplicate the heap objects - // (we compare strings by contents) - !(TypeUtils.isJavaLangString(left.getType()) && !memState.isNull(value)) && - !isAssignmentToDefaultValueInConstructor(instruction, runner, target)) { - mySameValueAssigned.merge(left, Boolean.TRUE, Boolean::logicalAnd); - } else { - mySameValueAssigned.put(left, Boolean.FALSE); - } - } - } - return super.visitAssign(instruction, runner, memState); - } - - @Override - protected void beforeConditionalJump(ConditionalGotoInstruction instruction, boolean isTrueBranch) { - PsiExpression anchor = instruction.getPsiAnchor(); - if (anchor != null && PsiImplUtil.getSwitchLabel(anchor) != null) { - mySwitchLabelsReachability.merge(anchor, ThreeState.fromBoolean(isTrueBranch), ThreeState::merge); - } - } - - private static boolean isAssignmentToDefaultValueInConstructor(AssignInstruction instruction, DataFlowRunner runner, DfaValue target) { - if (!(target instanceof DfaVariableValue)) { - return false; - } - DfaVariableValue var = (DfaVariableValue) target; - if (!(var.getPsiVariable() instanceof PsiField) || var.getQualifier() == null || - !(var.getQualifier().getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor)) { - return false; - } - - // chained assignment like this.a = this.b = 0; is also supported - PsiExpression rExpression = instruction.getRExpression(); - while (rExpression instanceof PsiAssignmentExpression && - ((PsiAssignmentExpression) rExpression).getOperationTokenType().equals(JavaTokenType.EQ)) { - rExpression = ((PsiAssignmentExpression) rExpression).getRExpression(); - } - DfaValue dest = runner.getFactory().createValue(rExpression); - if (dest == null) { - return false; - } - DfType dfType = dest.getDfType(); - - PsiType type = var.getType(); - boolean isDefaultValue = DfConstantType.isConst(dfType, PsiTypesUtil.getDefaultValue(type)) || - DfConstantType.isConst(dfType, 0) && TypeConversionUtil.isIntegralNumberType(type); - if (!isDefaultValue) { - return false; - } - PsiMethod method = PsiTreeUtil.getParentOfType(rExpression, PsiMethod.class); - return method != null && method.isConstructor(); - } - - // Reporting of floating zero is skipped, because this produces false-positives on the code like - // if(x == -0.0) x = 0.0; - private static boolean isFloatingZero(Object value) { - if (value instanceof Double) { - return ((Double) value).doubleValue() == 0.0; - } - if (value instanceof Float) { - return ((Float) value).floatValue() == 0.0f; - } - return false; - } - - StreamEx sameValueAssignments() { - return StreamEx.ofKeys(mySameValueAssigned, Boolean::booleanValue); - } - - EntryStream pointlessSameArguments() { - return EntryStream.of(mySameArguments).filterValues(ArgResultEquality::hasEquality); - } - - @Override - protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) { - myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible); - } - - StreamEx> problems() { - return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport); - } - - public Map> getArrayStoreProblems() { - return myArrayStoreProblems; - } - - Map getOfNullableCalls() { - return myOfNullableCalls; - } - - Map getConstantExpressions() { - return EntryStream.of(myConstantExpressions).filterKeys(chunk -> chunk.myRange == null) - .mapKeys(chunk -> chunk.myExpression).toMap(); - } - - Map getConstantExpressionChunks() { - return myConstantExpressions; - } - - Map getMethodReferenceResults() { - return myMethodReferenceResults; - } - - Map getSwitchLabelsReachability() { - return mySwitchLabelsReachability; - } - - EntryStream> getFailingCastExpressions() { - return EntryStream.of(myClassCastProblems).filterValues(StateInfo::shouldReport).mapToValue( - (cast, info) -> Pair.create(info.alwaysFails(), myRealOperandTypes.getOrDefault(cast, TypeConstraints.TOP).getPsiType(cast.getProject()))); - } - - Set getMutabilityViolations(boolean receiver) { - return receiver ? myReceiverMutabilityViolation : myArgumentMutabilityViolation; - } - - public List getEndOfInitializerStates() { - return myEndOfInitializerStates; - } - - Stream outOfBoundsArrayAccesses() { - return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals); - } - - StreamEx alwaysFailingCalls() { - return StreamEx.ofKeys(myFailingCalls, v -> v); - } - - boolean isAlwaysReturnsNotNull(Instruction[] instructions) { - return myAlwaysReturnsNotNull && - ContainerUtil.exists(instructions, i -> i instanceof ReturnInstruction && ((ReturnInstruction) i).getAnchor() instanceof PsiReturnStatement); - } - - public boolean isInstanceofRedundant(InstanceofInstruction instruction) { - PsiExpression expression = instruction.getExpression(); - if (expression == null || myUsefulInstanceofs.contains(instruction) || !myReachable.contains(instruction)) { - return false; - } - ConstantResult result = expression instanceof PsiMethodReferenceExpression ? - myMethodReferenceResults.get(expression) : myConstantExpressions.get(new ExpressionChunk(expression, null)); - return result != ConstantResult.TRUE && result != ConstantResult.FALSE; - } - - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState memState) { - if (!expression.isPhysical()) { - Application application = ApplicationManager.getApplication(); - if (application.isEAP() || application.isInternal() || application.isUnitTestMode()) { - throw new IllegalStateException("Non-physical expression is passed"); - } - } - expression.accept(new ExpressionVisitor(value, memState)); - PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (parent instanceof PsiTypeCastExpression) { - TypeConstraint fact = TypeConstraint.fromDfType(memState.getDfType(value)); - myRealOperandTypes.merge((PsiTypeCastExpression) parent, fact, TypeConstraint::join); - } - reportConstantExpressionValue(value, memState, expression, range); - } - - @Override - protected void onMethodCall(@Nonnull DfaValue result, - @Nonnull PsiExpression expression, - @Nonnull DfaCallArguments arguments, - @Nonnull DfaMemoryState memState) { - PsiReferenceExpression reference = USELESS_SAME_ARGUMENTS.getReferenceIfMatched(expression); - if (reference != null) { - ArgResultEquality equality = new ArgResultEquality( - memState.areEqual(arguments.myArguments[0], arguments.myArguments[1]), - memState.areEqual(result, arguments.myArguments[0]), - memState.areEqual(result, arguments.myArguments[1])); - mySameArguments.merge(reference, equality, ArgResultEquality::merge); - } - } - - @Override - protected void beforeMethodReferenceResultPush(@Nonnull DfaValue value, - @Nonnull PsiMethodReferenceExpression methodRef, - @Nonnull DfaMemoryState state) { - if (OptionalUtil.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) { - processOfNullableResult(value, state, methodRef.getReferenceNameElement()); - } - PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class); - if (method != null && JavaMethodContractUtil.isPure(method)) { - List contracts = JavaMethodContractUtil.getMethodContracts(method); - if (contracts.isEmpty() || !contracts.get(0).isTrivial()) { - myMethodReferenceResults.compute(methodRef, (mr, curState) -> ConstantResult.mergeValue(curState, state, value)); - } - } - } - - private void processOfNullableResult(@Nonnull DfaValue value, @Nonnull DfaMemoryState memState, PsiElement anchor) { - DfaValueFactory factory = value.getFactory(); - DfaValue optionalValue = SpecialField.OPTIONAL_VALUE.createValue(factory, value); - ThreeState present; - if (memState.isNull(optionalValue)) { - present = ThreeState.NO; - } else if (memState.isNotNull(optionalValue)) { - present = ThreeState.YES; - } else { - present = ThreeState.UNSURE; - } - myOfNullableCalls.merge(anchor, present, ThreeState::merge); - } - - @Override - protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) { - myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge); - } - - @Override - protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { - if (assignmentExpression != null) { - myArrayStoreProblems.put(assignmentExpression, Pair.create(fromType, toType)); - } - } - - @Override - public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction instruction, DataFlowRunner runner, DfaMemoryState state) { - if (!instruction.isStatic()) { - myEndOfInitializerStates.add(state.createCopy()); - } - return super.visitEndOfInitializer(instruction, runner, state); - } - - private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(call); - return !contracts.isEmpty() && - contracts.stream().anyMatch(contract -> contract.getReturnValue().isFail() && !contract.isTrivial()); - } - - private void reportConstantExpressionValue(DfaValue value, DfaMemoryState memState, PsiExpression expression, TextRange range) { - if (expression instanceof PsiLiteralExpression) { - return; - } - ExpressionChunk chunk = new ExpressionChunk(expression, range); - myConstantExpressions.compute(chunk, (c, curState) -> ConstantResult.mergeValue(curState, memState, value)); - } - - @Override - protected boolean checkNotNullable(DfaMemoryState state, @Nonnull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem problem) { - if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) { - myAlwaysReturnsNotNull = false; - } - - boolean ok = super.checkNotNullable(state, value, problem); - if (problem == null) { - return ok; - } - StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo()); - info.update(state, ok); - return ok; - } - - @Override - protected void reportMutabilityViolation(boolean receiver, @Nonnull PsiElement anchor) { - if (receiver) { - if (anchor instanceof PsiMethodReferenceExpression) { - anchor = ((PsiMethodReferenceExpression) anchor).getReferenceNameElement(); - } else if (anchor instanceof PsiMethodCallExpression) { - anchor = ((PsiMethodCallExpression) anchor).getMethodExpression().getReferenceNameElement(); - } - if (anchor != null) { - myReceiverMutabilityViolation.add(anchor); - } - } else { - myArgumentMutabilityViolation.add(anchor); - } - } - - private static class StateInfo { - boolean ephemeralException; - boolean normalException; - boolean normalOk; - - void update(DfaMemoryState state, boolean ok) { - if (state.isEphemeral()) { - if (!ok) { - ephemeralException = true; - } - } else { - if (ok) { - normalOk = true; - } else { - normalException = true; - } - } + private static final Logger LOG = Logger.getInstance(DataFlowInstructionVisitor.class); + private final Map, StateInfo> myStateInfos = new LinkedHashMap<>(); + private final Map myClassCastProblems = new HashMap<>(); + private final Map myRealOperandTypes = new HashMap<>(); + private final Map myFailingCalls = new HashMap<>(); + private final Map myConstantExpressions = new HashMap<>(); + private final Map myOfNullableCalls = new HashMap<>(); + private final Map> myArrayStoreProblems = new HashMap<>(); + private final Map myMethodReferenceResults = new HashMap<>(); + private final Map myOutOfBoundsArrayAccesses = new HashMap<>(); + private final Set myReceiverMutabilityViolation = new HashSet<>(); + private final Set myArgumentMutabilityViolation = new HashSet<>(); + private final Map mySameValueAssigned = new HashMap<>(); + private final Map mySameArguments = new HashMap<>(); + private final Map mySwitchLabelsReachability = new HashMap<>(); + private boolean myAlwaysReturnsNotNull = true; + private final List myEndOfInitializerStates = new ArrayList<>(); + + private static final CallMatcher USELESS_SAME_ARGUMENTS = CallMatcher.anyOf( + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "min", "max").parameterCount(2), + CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "min", "max").parameterCount(2), + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "replace").parameterCount(2) + ); + + @Override + @RequiredReadAction + public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + PsiExpression left = instruction.getLExpression(); + if (left != null && !Boolean.FALSE.equals(mySameValueAssigned.get(left))) { + if (!left.isPhysical()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Non-physical element in assignment instruction: " + left.getParent().getText(), new Throwable()); + } + } + else { + DfaValue value = memState.peek(); + DfaValue target = memState.getStackValue(1); + DfType dfType = memState.getDfType(value); + if (target != null && memState.areEqual(value, target) + && !(dfType instanceof DfConstantType && isFloatingZero(((DfConstantType)dfType).getValue())) + // Reporting strings is skipped because string reassignment might be intentionally used to deduplicate the heap objects + // (we compare strings by contents) + && !(TypeUtils.isJavaLangString(left.getType()) && !memState.isNull(value)) + && !isAssignmentToDefaultValueInConstructor(instruction, runner, target)) { + mySameValueAssigned.merge(left, Boolean.TRUE, Boolean::logicalAnd); + } + else { + mySameValueAssigned.put(left, Boolean.FALSE); + } + } + } + return super.visitAssign(instruction, runner, memState); + } + + @Override + protected void beforeConditionalJump(ConditionalGotoInstruction instruction, boolean isTrueBranch) { + PsiExpression anchor = instruction.getPsiAnchor(); + if (anchor != null && PsiImplUtil.getSwitchLabel(anchor) != null) { + mySwitchLabelsReachability.merge(anchor, ThreeState.fromBoolean(isTrueBranch), ThreeState::merge); + } } - boolean shouldReport() { - // non-ephemeral exceptions should be reported - // ephemeral exceptions should also be reported if only ephemeral states have reached a particular problematic instruction - // (e.g. if it's inside "if (var == null)" check after contract method invocation - return normalException || ephemeralException && !normalOk; - } + @RequiredReadAction + private static boolean isAssignmentToDefaultValueInConstructor(AssignInstruction instruction, DataFlowRunner runner, DfaValue target) { + if (!(target instanceof DfaVariableValue varValue)) { + return false; + } + if (!(varValue.getPsiVariable() instanceof PsiField) + || varValue.getQualifier() == null + || !(varValue.getQualifier().getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor)) { + return false; + } + + // chained assignment like this.a = this.b = 0; is also supported + PsiExpression rExpression = instruction.getRExpression(); + while (rExpression instanceof PsiAssignmentExpression rAssignment + && rAssignment.getOperationTokenType().equals(JavaTokenType.EQ)) { + rExpression = rAssignment.getRExpression(); + } + DfaValue dest = runner.getFactory().createValue(rExpression); + if (dest == null) { + return false; + } + DfType dfType = dest.getDfType(); + + PsiType type = varValue.getType(); + boolean isDefaultValue = DfConstantType.isConst(dfType, PsiTypesUtil.getDefaultValue(type)) || + DfConstantType.isConst(dfType, 0) && TypeConversionUtil.isIntegralNumberType(type); + if (!isDefaultValue) { + return false; + } + PsiMethod method = PsiTreeUtil.getParentOfType(rExpression, PsiMethod.class); + return method != null && method.isConstructor(); + } + + // Reporting of floating zero is skipped, because this produces false-positives on the code like + // if(x == -0.0) x = 0.0; + private static boolean isFloatingZero(Object value) { + if (value instanceof Double doubleValue) { + return doubleValue == 0.0; + } + //noinspection SimplifiableIfStatement + if (value instanceof Float floatValue) { + return floatValue == 0.0f; + } + return false; + } + + StreamEx sameValueAssignments() { + return StreamEx.ofKeys(mySameValueAssigned, Boolean::booleanValue); + } + + EntryStream pointlessSameArguments() { + return EntryStream.of(mySameArguments).filterValues(ArgResultEquality::hasEquality); + } + + @Override + protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) { + myClassCastProblems.computeIfAbsent(castExpression, e -> new StateInfo()).update(state, castPossible); + } - boolean alwaysFails() { - return (normalException || ephemeralException) && !normalOk; + StreamEx> problems() { + return StreamEx.ofKeys(myStateInfos, StateInfo::shouldReport); } - } - private class ExpressionVisitor extends JavaElementVisitor { - private final DfaValue myValue; - private final DfaMemoryState myMemState; + public Map> getArrayStoreProblems() { + return myArrayStoreProblems; + } + + Map getOfNullableCalls() { + return myOfNullableCalls; + } + + Map getConstantExpressions() { + return EntryStream.of(myConstantExpressions).filterKeys(chunk -> chunk.myRange == null) + .mapKeys(chunk -> chunk.myExpression).toMap(); + } + + Map getConstantExpressionChunks() { + return myConstantExpressions; + } + + Map getMethodReferenceResults() { + return myMethodReferenceResults; + } + + Map getSwitchLabelsReachability() { + return mySwitchLabelsReachability; + } + + EntryStream> getFailingCastExpressions() { + return EntryStream.of(myClassCastProblems).filterValues(StateInfo::shouldReport).mapToValue( + (cast, info) -> Pair.create( + info.alwaysFails(), + myRealOperandTypes.getOrDefault(cast, TypeConstraints.TOP).getPsiType(cast.getProject()) + )); + } + + Set getMutabilityViolations(boolean receiver) { + return receiver ? myReceiverMutabilityViolation : myArgumentMutabilityViolation; + } + + public List getEndOfInitializerStates() { + return myEndOfInitializerStates; + } + + Stream outOfBoundsArrayAccesses() { + return StreamEx.ofKeys(myOutOfBoundsArrayAccesses, ThreeState.YES::equals); + } + + StreamEx alwaysFailingCalls() { + return StreamEx.ofKeys(myFailingCalls, v -> v); + } + + boolean isAlwaysReturnsNotNull(Instruction[] instructions) { + return myAlwaysReturnsNotNull + && ContainerUtil.exists( + instructions, + i -> i instanceof ReturnInstruction returnInsn && returnInsn.getAnchor() instanceof PsiReturnStatement + ); + } + + public boolean isInstanceofRedundant(InstanceofInstruction instruction) { + PsiExpression expression = instruction.getExpression(); + if (expression == null || myUsefulInstanceofs.contains(instruction) || !myReachable.contains(instruction)) { + return false; + } + ConstantResult result = expression instanceof PsiMethodReferenceExpression + ? myMethodReferenceResults.get(expression) + : myConstantExpressions.get(new ExpressionChunk(expression, null)); + return result != ConstantResult.TRUE && result != ConstantResult.FALSE; + } - ExpressionVisitor(DfaValue value, DfaMemoryState memState) { - myValue = value; - myMemState = memState; + @Override + protected void beforeExpressionPush( + DfaValue value, + PsiExpression expression, + @Nullable TextRange range, + DfaMemoryState memState + ) { + if (!expression.isPhysical()) { + Application application = Application.get(); + if (application.isInternal() || application.isUnitTestMode()) { + throw new IllegalStateException("Non-physical expression is passed"); + } + } + expression.accept(new ExpressionVisitor(value, memState)); + PsiElement parent = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (parent instanceof PsiTypeCastExpression typeCastExpr) { + TypeConstraint fact = TypeConstraint.fromDfType(memState.getDfType(value)); + myRealOperandTypes.merge(typeCastExpr, fact, TypeConstraint::join); + } + reportConstantExpressionValue(value, memState, expression, range); } @Override - public void visitMethodCallExpression(PsiMethodCallExpression call) { - super.visitMethodCallExpression(call); - if (OptionalUtil.OPTIONAL_OF_NULLABLE.test(call)) { - processOfNullableResult(myValue, myMemState, call.getArgumentList().getExpressions()[0]); - } + protected void onMethodCall( + DfaValue result, + PsiExpression expression, + DfaCallArguments arguments, + DfaMemoryState memState + ) { + PsiReferenceExpression reference = USELESS_SAME_ARGUMENTS.getReferenceIfMatched(expression); + if (reference != null) { + ArgResultEquality equality = new ArgResultEquality( + memState.areEqual(arguments.myArguments[0], arguments.myArguments[1]), + memState.areEqual(result, arguments.myArguments[0]), + memState.areEqual(result, arguments.myArguments[1]) + ); + mySameArguments.merge(reference, equality, ArgResultEquality::merge); + } } @Override - public void visitCallExpression(PsiCallExpression call) { - super.visitCallExpression(call); - Boolean isFailing = myFailingCalls.get(call); - if (isFailing != null || hasNonTrivialFailingContracts(call)) { - myFailingCalls.put(call, DfaTypeValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing)); - } + @RequiredReadAction + protected void beforeMethodReferenceResultPush( + DfaValue value, + PsiMethodReferenceExpression methodRef, + DfaMemoryState state + ) { + if (OptionalUtil.OPTIONAL_OF_NULLABLE.methodReferenceMatches(methodRef)) { + processOfNullableResult(value, state, methodRef.getReferenceNameElement()); + } + PsiMethod method = tryCast(methodRef.resolve(), PsiMethod.class); + if (method != null && JavaMethodContractUtil.isPure(method)) { + List contracts = JavaMethodContractUtil.getMethodContracts(method); + if (contracts.isEmpty() || !contracts.get(0).isTrivial()) { + myMethodReferenceResults.compute(methodRef, (mr, curState) -> ConstantResult.mergeValue(curState, state, value)); + } + } } - } - static class ExpressionChunk { - final - @Nonnull - PsiExpression myExpression; - final - @Nullable - TextRange myRange; + private void processOfNullableResult(DfaValue value, DfaMemoryState memState, PsiElement anchor) { + DfaValueFactory factory = value.getFactory(); + DfaValue optionalValue = SpecialField.OPTIONAL_VALUE.createValue(factory, value); + ThreeState present; + if (memState.isNull(optionalValue)) { + present = ThreeState.NO; + } + else if (memState.isNotNull(optionalValue)) { + present = ThreeState.YES; + } + else { + present = ThreeState.UNSURE; + } + myOfNullableCalls.merge(anchor, present, ThreeState::merge); + } - ExpressionChunk(@Nonnull PsiExpression expression, @Nullable TextRange range) { - myExpression = expression; - myRange = range; + @Override + protected void processArrayAccess(PsiArrayAccessExpression expression, boolean alwaysOutOfBounds) { + myOutOfBoundsArrayAccesses.merge(expression, ThreeState.fromBoolean(alwaysOutOfBounds), ThreeState::merge); } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExpressionChunk chunk = (ExpressionChunk) o; - return myExpression.equals(chunk.myExpression) && - Objects.equals(myRange, chunk.myRange); + protected void processArrayStoreTypeMismatch(PsiAssignmentExpression assignmentExpression, PsiType fromType, PsiType toType) { + if (assignmentExpression != null) { + myArrayStoreProblems.put(assignmentExpression, Couple.of(fromType, toType)); + } } @Override - public int hashCode() { - return 31 * myExpression.hashCode() + Objects.hashCode(myRange); + public DfaInstructionState[] visitEndOfInitializer( + EndOfInitializerInstruction instruction, + DataFlowRunner runner, + DfaMemoryState state + ) { + if (!instruction.isStatic()) { + myEndOfInitializerStates.add(state.createCopy()); + } + return super.visitEndOfInitializer(instruction, runner, state); + } + + private static boolean hasNonTrivialFailingContracts(PsiCallExpression call) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(call); + return !contracts.isEmpty() && + contracts.stream().anyMatch(contract -> contract.getReturnValue().isFail() && !contract.isTrivial()); + } + + private void reportConstantExpressionValue(DfaValue value, DfaMemoryState memState, PsiExpression expression, TextRange range) { + if (expression instanceof PsiLiteralExpression) { + return; + } + ExpressionChunk chunk = new ExpressionChunk(expression, range); + myConstantExpressions.compute(chunk, (c, curState) -> ConstantResult.mergeValue(curState, memState, value)); } @Override - public String toString() { - String text = myExpression.getText(); - return myRange == null ? text : myRange.substring(text); + protected boolean checkNotNullable( + DfaMemoryState state, + DfaValue value, + NullabilityProblemKind.@Nullable NullabilityProblem problem + ) { + if (problem != null && problem.getKind() == NullabilityProblemKind.nullableReturn && !state.isNotNull(value)) { + myAlwaysReturnsNotNull = false; + } + + boolean ok = super.checkNotNullable(state, value, problem); + if (problem == null) { + return ok; + } + StateInfo info = myStateInfos.computeIfAbsent(problem, k -> new StateInfo()); + info.update(state, ok); + return ok; } - } - static class ArgResultEquality { - boolean argsEqual; - boolean firstArgEqualToResult; - boolean secondArgEqualToResult; + @Override + protected void reportMutabilityViolation(boolean receiver, PsiElement anchor) { + if (receiver) { + if (anchor instanceof PsiMethodReferenceExpression methodRefExpr) { + anchor = methodRefExpr.getReferenceNameElement(); + } + else if (anchor instanceof PsiMethodCallExpression methodCall) { + anchor = methodCall.getMethodExpression().getReferenceNameElement(); + } + if (anchor != null) { + myReceiverMutabilityViolation.add(anchor); + } + } + else { + myArgumentMutabilityViolation.add(anchor); + } + } - ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) { - this.argsEqual = argsEqual; - this.firstArgEqualToResult = firstArgEqualToResult; - this.secondArgEqualToResult = secondArgEqualToResult; + private static class StateInfo { + boolean ephemeralException; + boolean normalException; + boolean normalOk; + + void update(DfaMemoryState state, boolean ok) { + if (state.isEphemeral()) { + if (!ok) { + ephemeralException = true; + } + } + else if (ok) { + normalOk = true; + } + else { + normalException = true; + } + } + + boolean shouldReport() { + // non-ephemeral exceptions should be reported + // ephemeral exceptions should also be reported if only ephemeral states have reached a particular problematic instruction + // (e.g. if it's inside "if (var == null)" check after contract method invocation + return normalException || ephemeralException && !normalOk; + } + + boolean alwaysFails() { + return (normalException || ephemeralException) && !normalOk; + } } - ArgResultEquality merge(ArgResultEquality other) { - return new ArgResultEquality(argsEqual && other.argsEqual, firstArgEqualToResult && other.firstArgEqualToResult, - secondArgEqualToResult && other.secondArgEqualToResult); + private class ExpressionVisitor extends JavaElementVisitor { + private final DfaValue myValue; + private final DfaMemoryState myMemState; + + ExpressionVisitor(DfaValue value, DfaMemoryState memState) { + myValue = value; + myMemState = memState; + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + super.visitMethodCallExpression(call); + if (OptionalUtil.OPTIONAL_OF_NULLABLE.test(call)) { + processOfNullableResult(myValue, myMemState, call.getArgumentList().getExpressions()[0]); + } + } + + @Override + public void visitCallExpression(PsiCallExpression call) { + super.visitCallExpression(call); + Boolean isFailing = myFailingCalls.get(call); + if (isFailing != null || hasNonTrivialFailingContracts(call)) { + myFailingCalls.put(call, DfaTypeValue.isContractFail(myValue) && !Boolean.FALSE.equals(isFailing)); + } + } + } + + static class ExpressionChunk { + final + PsiExpression myExpression; + final + @Nullable + TextRange myRange; + + ExpressionChunk(PsiExpression expression, @Nullable TextRange range) { + myExpression = expression; + myRange = range; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExpressionChunk chunk = (ExpressionChunk)o; + return myExpression.equals(chunk.myExpression) && + Objects.equals(myRange, chunk.myRange); + } + + @Override + public int hashCode() { + return 31 * myExpression.hashCode() + Objects.hashCode(myRange); + } + + @Override + @RequiredReadAction + public String toString() { + String text = myExpression.getText(); + return myRange == null ? text : myRange.substring(text); + } } - boolean hasEquality() { - return argsEqual || firstArgEqualToResult || secondArgEqualToResult; + static class ArgResultEquality { + boolean argsEqual; + boolean firstArgEqualToResult; + boolean secondArgEqualToResult; + + ArgResultEquality(boolean argsEqual, boolean firstArgEqualToResult, boolean secondArgEqualToResult) { + this.argsEqual = argsEqual; + this.firstArgEqualToResult = firstArgEqualToResult; + this.secondArgEqualToResult = secondArgEqualToResult; + } + + ArgResultEquality merge(ArgResultEquality other) { + return new ArgResultEquality( + argsEqual && other.argsEqual, + firstArgEqualToResult && other.firstArgEqualToResult, + secondArgEqualToResult && other.secondArgEqualToResult + ); + } + + boolean hasEquality() { + return argsEqual || firstArgEqualToResult || secondArgEqualToResult; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowRunner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowRunner.java index b2d8c1cd6f..811c5475f2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowRunner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DataFlowRunner.java @@ -13,7 +13,8 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.VariableAccessUtils; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.Application; import consulo.application.progress.ProgressManager; import consulo.application.util.registry.Registry; import consulo.component.ProcessCanceledException; @@ -30,12 +31,11 @@ import consulo.util.collection.MultiMap; import consulo.util.lang.ObjectUtil; import consulo.util.lang.ThreeState; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; +import org.jspecify.annotations.Nullable; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.util.*; @@ -44,701 +44,727 @@ import java.util.stream.Collectors; public class DataFlowRunner { - private static final Logger LOG = Logger.getInstance(DataFlowRunner.class); - private static final int MERGING_BACK_BRANCHES_THRESHOLD = 50; - // Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction - // is executed more than this limit times. - public static final int MAX_STATES_PER_BRANCH = 300; - - private Instruction[] myInstructions; - private final - @Nonnull - MultiMap myNestedClosures = new MultiMap<>(); - private final - @Nonnull - DfaValueFactory myValueFactory; - private final - @Nonnull - ThreeState myIgnoreAssertions; - private boolean myInlining = true; - private boolean myCancelled = false; - private boolean myWasForciblyMerged = false; - private final TimeStats myStats = createStatistics(); - - public DataFlowRunner(@Nonnull Project project) { - this(project, null); - } - - public DataFlowRunner(@Nonnull Project project, @Nullable PsiElement context) { - this(project, context, false, ThreeState.NO); - } - - /** - * @param project current project - * @param context analysis context element (code block, class, expression, etc.); used to determine whether we can trust - * field initializers (e.g. we usually cannot if context is a constructor) - * @param unknownMembersAreNullable if true every parameter or method return value without nullity annotation is assumed to be nullable - * @param ignoreAssertions if true, assertion statements will be ignored, as if JVM is started with -da. - */ - public DataFlowRunner(@Nonnull Project project, - @Nullable PsiElement context, - boolean unknownMembersAreNullable, - @Nonnull ThreeState ignoreAssertions) { - myValueFactory = new DfaValueFactory(project, context, unknownMembersAreNullable); - myIgnoreAssertions = ignoreAssertions; - } - - public - @Nonnull - DfaValueFactory getFactory() { - return myValueFactory; - } - - /** - * Call this method from the visitor to cancel analysis (e.g. if wanted fact is already established and subsequent analysis - * is useless). In this case {@link RunnerResult#CANCELLED} will be returned. - */ - public final void cancel() { - myCancelled = true; - } - - private - @Nullable - Collection createInitialStates(@Nonnull PsiElement psiBlock, - @Nonnull InstructionVisitor visitor, - boolean allowInlining) { - PsiElement container = PsiTreeUtil.getParentOfType(psiBlock, PsiClass.class, PsiLambdaExpression.class); - if (container != null && (!(container instanceof PsiClass) || PsiUtil.isLocalOrAnonymousClass((PsiClass) container))) { - PsiElement block = DfaPsiUtil.getTopmostBlockInSameClass(container.getParent()); - if (block != null) { - final RunnerResult result; + private static final Logger LOG = Logger.getInstance(DataFlowRunner.class); + private static final int MERGING_BACK_BRANCHES_THRESHOLD = 50; + // Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction + // is executed more than this limit times. + public static final int MAX_STATES_PER_BRANCH = 300; + + private Instruction[] myInstructions; + private final + MultiMap myNestedClosures = new MultiMap<>(); + private final + DfaValueFactory myValueFactory; + private final + ThreeState myIgnoreAssertions; + private boolean myInlining = true; + private boolean myCancelled = false; + private boolean myWasForciblyMerged = false; + private final TimeStats myStats = createStatistics(); + + public DataFlowRunner(Project project) { + this(project, null); + } + + public DataFlowRunner(Project project, @Nullable PsiElement context) { + this(project, context, false, ThreeState.NO); + } + + /** + * @param project current project + * @param context analysis context element (code block, class, expression, etc.); used to determine whether we can trust + * field initializers (e.g. we usually cannot if context is a constructor) + * @param unknownMembersAreNullable if true every parameter or method return value without nullity annotation is assumed to be nullable + * @param ignoreAssertions if true, assertion statements will be ignored, as if JVM is started with -da. + */ + public DataFlowRunner( + Project project, + @Nullable PsiElement context, + boolean unknownMembersAreNullable, + ThreeState ignoreAssertions + ) { + myValueFactory = new DfaValueFactory(project, context, unknownMembersAreNullable); + myIgnoreAssertions = ignoreAssertions; + } + + public DfaValueFactory getFactory() { + return myValueFactory; + } + + /** + * Call this method from the visitor to cancel analysis (e.g. if wanted fact is already established and subsequent analysis + * is useless). In this case {@link RunnerResult#CANCELLED} will be returned. + */ + public final void cancel() { + myCancelled = true; + } + + @Nullable + @RequiredReadAction + private Collection createInitialStates( + PsiElement psiBlock, + InstructionVisitor visitor, + boolean allowInlining + ) { + PsiElement container = PsiTreeUtil.getParentOfType(psiBlock, PsiClass.class, PsiLambdaExpression.class); + if (container != null && !(container instanceof PsiClass psiClass && !PsiUtil.isLocalOrAnonymousClass(psiClass))) { + PsiElement block = DfaPsiUtil.getTopmostBlockInSameClass(container.getParent()); + if (block != null) { + final RunnerResult result; + try { + myInlining = allowInlining; + result = analyzeMethod(block, visitor); + } + finally { + myInlining = true; + } + if (result == RunnerResult.OK) { + final Collection closureStates = myNestedClosures.get(DfaPsiUtil.getTopmostBlockInSameClass(psiBlock)); + if (allowInlining || !closureStates.isEmpty()) { + return closureStates; + } + } + return null; + } + } + + return Collections.singletonList(createMemoryState()); + } + + /** + * Analyze this particular method (lambda, class initializer) without inlining this method into parent one. + * E.g. if supplied method is a lambda within Stream API call chain, it still will be analyzed as separate method. + * On the other hand, inlining will normally work inside the supplied method. + * + * @param psiBlock method/lambda/class initializer body + * @param visitor a visitor to use + * @return result status + */ + @RequiredReadAction + public final RunnerResult analyzeMethod(PsiElement psiBlock, InstructionVisitor visitor) { + Collection initialStates = createInitialStates(psiBlock, visitor, false); + return initialStates == null ? RunnerResult.NOT_APPLICABLE : analyzeMethod(psiBlock, visitor, initialStates); + } + + /** + * Analyze this particular method (lambda, class initializer) trying to inline it into outer scope if possible. + * Usually inlining works, e.g. for lambdas inside stream API calls. + * + * @param psiBlock method/lambda/class initializer body + * @param visitor a visitor to use + * @return result status + */ + @RequiredReadAction + public final RunnerResult analyzeMethodWithInlining(PsiElement psiBlock, InstructionVisitor visitor) { + Collection initialStates = createInitialStates(psiBlock, visitor, true); + if (initialStates == null) { + return RunnerResult.NOT_APPLICABLE; + } + if (initialStates.isEmpty()) { + return RunnerResult.OK; + } + return analyzeMethod(psiBlock, visitor, initialStates); + } + + /** + * Analyze given code-block without analyzing any parent and children context + * + * @param block block to analyze + * @param visitor visitor to use + * @return result status + */ + @RequiredReadAction + public final RunnerResult analyzeCodeBlock(PsiCodeBlock block, InstructionVisitor visitor) { + return analyzeMethod(block, visitor, Collections.singleton(createMemoryState())); + } + + @RequiredReadAction + final RunnerResult analyzeMethod( + PsiElement psiBlock, + InstructionVisitor visitor, + Collection initialStates + ) { + ControlFlow flow = buildFlow(psiBlock); + if (flow == null) { + return RunnerResult.NOT_APPLICABLE; + } + List startingStates = createInitialInstructionStates(psiBlock, initialStates, flow); + if (startingStates.isEmpty()) { + return RunnerResult.ABORTED; + } + + return interpret(psiBlock, visitor, flow, startingStates); + } + + @Nullable + @RequiredReadAction + protected final ControlFlow buildFlow(PsiElement psiBlock) { + ControlFlow flow = null; try { - myInlining = allowInlining; - result = analyzeMethod(block, visitor); - } finally { - myInlining = true; - } - if (result == RunnerResult.OK) { - final Collection closureStates = myNestedClosures.get(DfaPsiUtil.getTopmostBlockInSameClass(psiBlock)); - if (allowInlining || !closureStates.isEmpty()) { - return closureStates; - } - } - return null; - } - } - - return Collections.singletonList(createMemoryState()); - } - - /** - * Analyze this particular method (lambda, class initializer) without inlining this method into parent one. - * E.g. if supplied method is a lambda within Stream API call chain, it still will be analyzed as separate method. - * On the other hand, inlining will normally work inside the supplied method. - * - * @param psiBlock method/lambda/class initializer body - * @param visitor a visitor to use - * @return result status - */ - public final - @Nonnull - RunnerResult analyzeMethod(@Nonnull PsiElement psiBlock, @Nonnull InstructionVisitor visitor) { - Collection initialStates = createInitialStates(psiBlock, visitor, false); - return initialStates == null ? RunnerResult.NOT_APPLICABLE : analyzeMethod(psiBlock, visitor, initialStates); - } - - /** - * Analyze this particular method (lambda, class initializer) trying to inline it into outer scope if possible. - * Usually inlining works, e.g. for lambdas inside stream API calls. - * - * @param psiBlock method/lambda/class initializer body - * @param visitor a visitor to use - * @return result status - */ - public final - @Nonnull - RunnerResult analyzeMethodWithInlining(@Nonnull PsiElement psiBlock, @Nonnull InstructionVisitor visitor) { - Collection initialStates = createInitialStates(psiBlock, visitor, true); - if (initialStates == null) { - return RunnerResult.NOT_APPLICABLE; - } - if (initialStates.isEmpty()) { - return RunnerResult.OK; - } - return analyzeMethod(psiBlock, visitor, initialStates); - } - - /** - * Analyze given code-block without analyzing any parent and children context - * - * @param block block to analyze - * @param visitor visitor to use - * @return result status - */ - public final RunnerResult analyzeCodeBlock(@Nonnull PsiCodeBlock block, @Nonnull InstructionVisitor visitor) { - return analyzeMethod(block, visitor, Collections.singleton(createMemoryState())); - } - - final - @Nonnull - RunnerResult analyzeMethod(@Nonnull PsiElement psiBlock, - @Nonnull InstructionVisitor visitor, - @Nonnull Collection initialStates) { - ControlFlow flow = buildFlow(psiBlock); - if (flow == null) { - return RunnerResult.NOT_APPLICABLE; - } - List startingStates = createInitialInstructionStates(psiBlock, initialStates, flow); - if (startingStates.isEmpty()) { - return RunnerResult.ABORTED; - } - - return interpret(psiBlock, visitor, flow, startingStates); - } - - protected final - @Nullable - ControlFlow buildFlow(@Nonnull PsiElement psiBlock) { - ControlFlow flow = null; - try { - myStats.reset(); - flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, myInlining).buildControlFlow(); - myStats.endFlow(); - - if (flow != null) { - new LiveVariablesAnalyzer(flow, myValueFactory).flushDeadVariablesOnStatementFinish(); - } - myStats.endLVA(); - } catch (ProcessCanceledException ex) { - throw ex; - } catch (RuntimeException | AssertionError e) { - reportDfaProblem(psiBlock, flow, null, e); - } - return flow; - } - - protected final - @Nonnull - RunnerResult interpret(@Nonnull PsiElement psiBlock, - @Nonnull InstructionVisitor visitor, - @Nonnull ControlFlow flow, - @Nonnull List startingStates) { - int endOffset = flow.getInstructionCount(); - myInstructions = flow.getInstructions(); - DfaInstructionState lastInstructionState = null; - myNestedClosures.clear(); - myWasForciblyMerged = false; - - final StateQueue queue = new StateQueue(); - for (DfaInstructionState state : startingStates) { - queue.offer(state); - } - - MultiMap processedStates = MultiMap.createSet(); - MultiMap incomingStates = MultiMap.createSet(); - try { - Set joinInstructions = getJoinInstructions(); - int[] loopNumber = flow.getLoopNumbers(); - - int stateLimit = Registry.intValue("ide.dfa.state.limit", 50000); - int count = 0; - while (!queue.isEmpty()) { - myStats.startMerge(); - List states = queue.getNextInstructionStates(joinInstructions); - myStats.endMerge(); - if (states.size() > MAX_STATES_PER_BRANCH) { - LOG.trace("Too complex because too many different possible states"); - return RunnerResult.TOO_COMPLEX; - } - assert !states.isEmpty(); - Instruction instruction = states.get(0).getInstruction(); - beforeInstruction(instruction); - for (DfaInstructionState instructionState : states) { - lastInstructionState = instructionState; - if (count++ > stateLimit) { - LOG.trace("Too complex data flow: too many instruction states processed"); - return RunnerResult.TOO_COMPLEX; - } - ProgressManager.checkCanceled(); - - if (LOG.isTraceEnabled()) { - LOG.trace(instructionState.toString()); - } - - if (instruction instanceof BranchingInstruction) { - BranchingInstruction branching = (BranchingInstruction) instruction; - Collection processed = processedStates.get(branching); - if (containsState(processed, instructionState)) { - continue; + myStats.reset(); + flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, myInlining).buildControlFlow(); + myStats.endFlow(); + + if (flow != null) { + new LiveVariablesAnalyzer(flow, myValueFactory).flushDeadVariablesOnStatementFinish(); } - if (processed.size() > MERGING_BACK_BRANCHES_THRESHOLD) { - myStats.startMerge(); - instructionState = mergeBackBranches(instructionState, processed); - myStats.endMerge(); - if (containsState(processed, instructionState)) { - continue; - } + myStats.endLVA(); + } + catch (ProcessCanceledException ex) { + throw ex; + } + catch (RuntimeException | AssertionError e) { + reportDfaProblem(psiBlock, flow, null, e); + } + return flow; + } + + @RequiredReadAction + protected final RunnerResult interpret( + PsiElement psiBlock, + InstructionVisitor visitor, + ControlFlow flow, + List startingStates + ) { + int endOffset = flow.getInstructionCount(); + myInstructions = flow.getInstructions(); + DfaInstructionState lastInstructionState = null; + myNestedClosures.clear(); + myWasForciblyMerged = false; + + final StateQueue queue = new StateQueue(); + for (DfaInstructionState state : startingStates) { + queue.offer(state); + } + + MultiMap processedStates = MultiMap.createSet(); + MultiMap incomingStates = MultiMap.createSet(); + try { + Set joinInstructions = getJoinInstructions(); + int[] loopNumber = flow.getLoopNumbers(); + + int stateLimit = Registry.intValue("ide.dfa.state.limit", 50000); + int count = 0; + while (!queue.isEmpty()) { + myStats.startMerge(); + List states = queue.getNextInstructionStates(joinInstructions); + myStats.endMerge(); + if (states.size() > MAX_STATES_PER_BRANCH) { + LOG.trace("Too complex because too many different possible states"); + return RunnerResult.TOO_COMPLEX; + } + assert !states.isEmpty(); + Instruction instruction = states.get(0).getInstruction(); + beforeInstruction(instruction); + for (DfaInstructionState instructionState : states) { + lastInstructionState = instructionState; + if (count++ > stateLimit) { + LOG.trace("Too complex data flow: too many instruction states processed"); + return RunnerResult.TOO_COMPLEX; + } + ProgressManager.checkCanceled(); + + if (LOG.isTraceEnabled()) { + LOG.trace(instructionState.toString()); + } + + if (instruction instanceof BranchingInstruction branching) { + Collection processed = processedStates.get(branching); + if (containsState(processed, instructionState)) { + continue; + } + if (processed.size() > MERGING_BACK_BRANCHES_THRESHOLD) { + myStats.startMerge(); + instructionState = mergeBackBranches(instructionState, processed); + myStats.endMerge(); + if (containsState(processed, instructionState)) { + continue; + } + } + if (processed.size() > MAX_STATES_PER_BRANCH) { + LOG.trace("Too complex because too many different possible states"); + return RunnerResult.TOO_COMPLEX; + } + if (loopNumber[branching.getIndex()] != 0) { + processedStates.putValue(branching, instructionState.getMemoryState().createCopy()); + } + } + + DfaInstructionState[] after = acceptInstruction(visitor, instructionState); + if (LOG.isDebugEnabled() && instruction instanceof ControlTransferInstruction && after.length == 0) { + DfaMemoryState memoryState = instructionState.getMemoryState(); + if (!memoryState.isEmptyStack()) { + // can pop safely as this memory state is unnecessary anymore (after is empty) + DfaValue topValue = memoryState.pop(); + if (!(topValue instanceof DfaControlTransferValue || psiBlock instanceof PsiCodeFragment && memoryState.isEmptyStack())) { + // push back so error report includes this entry + memoryState.push(topValue); + reportDfaProblem(psiBlock, flow, instructionState, new RuntimeException("Stack is corrupted")); + } + } + } + for (DfaInstructionState state : after) { + Instruction nextInstruction = state.getInstruction(); + if (nextInstruction.getIndex() >= endOffset) { + continue; + } + handleStepOutOfLoop( + instruction, + nextInstruction, + loopNumber, + processedStates, + incomingStates, + states, + after, + queue + ); + if (nextInstruction instanceof BranchingInstruction branching) { + if (containsState(processedStates.get(branching), state) + || containsState(incomingStates.get(branching), state)) { + continue; + } + if (loopNumber[branching.getIndex()] != 0) { + incomingStates.putValue(branching, state.getMemoryState().createCopy()); + } + } + queue.offer(state); + } + } + afterInstruction(instruction); + if (myCancelled) { + return RunnerResult.CANCELLED; + } } - if (processed.size() > MAX_STATES_PER_BRANCH) { - LOG.trace("Too complex because too many different possible states"); - return RunnerResult.TOO_COMPLEX; + + myWasForciblyMerged |= queue.wasForciblyMerged(); + myStats.endProcess(); + if (myStats.isTooSlow()) { + String message = "Too slow DFA\nIf you report this problem, please consider including the attachments\n" + myStats + + "\nControl flow size: " + flow.getInstructionCount(); + reportDfaProblem(psiBlock, flow, null, new RuntimeException(message)); } - if (loopNumber[branching.getIndex()] != 0) { - processedStates.putValue(branching, instructionState.getMemoryState().createCopy()); + return RunnerResult.OK; + } + catch (ProcessCanceledException ex) { + throw ex; + } + catch (RuntimeException | AssertionError e) { + reportDfaProblem(psiBlock, flow, lastInstructionState, e); + return RunnerResult.ABORTED; + } + } + + protected List createInitialInstructionStates( + PsiElement psiBlock, + Collection memStates, + ControlFlow flow + ) { + initializeVariables(psiBlock, memStates, flow); + return ContainerUtil.map(memStates, s -> new DfaInstructionState(flow.getInstruction(0), s)); + } + + protected void beforeInstruction(Instruction instruction) { + } + + protected void afterInstruction(Instruction instruction) { + } + + private DfaInstructionState mergeBackBranches(DfaInstructionState instructionState, Collection processed) { + DfaMemoryStateImpl curState = (DfaMemoryStateImpl)instructionState.getMemoryState(); + Object key = curState.getMergeabilityKey(); + DfaMemoryStateImpl mergedState = StreamEx.of(processed) + .select(DfaMemoryStateImpl.class) + .filterBy(DfaMemoryStateImpl::getMergeabilityKey, key) + .foldLeft( + curState, + (s1, s2) -> { + s1.merge(s2); + return s1; + } + ); + instructionState = new DfaInstructionState(instructionState.getInstruction(), mergedState); + myWasForciblyMerged = true; + return instructionState; + } + + boolean wasForciblyMerged() { + return myWasForciblyMerged; + } + + private Set getJoinInstructions() { + Set joinInstructions = new HashSet<>(); + for (int index = 0; index < myInstructions.length; index++) { + Instruction instruction = myInstructions[index]; + if (instruction instanceof GotoInstruction gotoInsn) { + joinInstructions.add(myInstructions[gotoInsn.getOffset()]); } - } - - DfaInstructionState[] after = acceptInstruction(visitor, instructionState); - if (LOG.isDebugEnabled() && instruction instanceof ControlTransferInstruction && after.length == 0) { - DfaMemoryState memoryState = instructionState.getMemoryState(); - if (!memoryState.isEmptyStack()) { - // can pop safely as this memory state is unnecessary anymore (after is empty) - DfaValue topValue = memoryState.pop(); - if (!(topValue instanceof DfaControlTransferValue || psiBlock instanceof PsiCodeFragment && memoryState.isEmptyStack())) { - // push back so error report includes this entry - memoryState.push(topValue); - reportDfaProblem(psiBlock, flow, instructionState, new RuntimeException("Stack is corrupted")); - } + else if (instruction instanceof ConditionalGotoInstruction condGotoInsn) { + joinInstructions.add(myInstructions[condGotoInsn.getOffset()]); } - } - for (DfaInstructionState state : after) { - Instruction nextInstruction = state.getInstruction(); - if (nextInstruction.getIndex() >= endOffset) { - continue; + else if (instruction instanceof ControlTransferInstruction controlTransferInsn) { + IntStreamEx.of(controlTransferInsn.getPossibleTargetIndices()).elements(myInstructions).into(joinInstructions); } - handleStepOutOfLoop(instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue); - if (nextInstruction instanceof BranchingInstruction) { - BranchingInstruction branching = (BranchingInstruction) nextInstruction; - if (containsState(processedStates.get(branching), state) || - containsState(incomingStates.get(branching), state)) { - continue; - } - if (loopNumber[branching.getIndex()] != 0) { - incomingStates.putValue(branching, state.getMemoryState().createCopy()); - } + else if (instruction instanceof MethodCallInstruction methodCallInsn && !methodCallInsn.getContracts().isEmpty()) { + joinInstructions.add(myInstructions[index + 1]); } - queue.offer(state); - } - } - afterInstruction(instruction); - if (myCancelled) { - return RunnerResult.CANCELLED; - } - } - - myWasForciblyMerged |= queue.wasForciblyMerged(); - myStats.endProcess(); - if (myStats.isTooSlow()) { - String message = "Too slow DFA\nIf you report this problem, please consider including the attachments\n" + myStats + - "\nControl flow size: " + flow.getInstructionCount(); - reportDfaProblem(psiBlock, flow, null, new RuntimeException(message)); - } - return RunnerResult.OK; - } catch (ProcessCanceledException ex) { - throw ex; - } catch (RuntimeException | AssertionError e) { - reportDfaProblem(psiBlock, flow, lastInstructionState, e); - return RunnerResult.ABORTED; - } - } - - protected - @Nonnull - List createInitialInstructionStates(@Nonnull PsiElement psiBlock, - @Nonnull Collection memStates, - @Nonnull ControlFlow flow) { - initializeVariables(psiBlock, memStates, flow); - return ContainerUtil.map(memStates, s -> new DfaInstructionState(flow.getInstruction(0), s)); - } - - protected void beforeInstruction(Instruction instruction) { - - } - - protected void afterInstruction(Instruction instruction) { - - } - - private - @Nonnull - DfaInstructionState mergeBackBranches(DfaInstructionState instructionState, Collection processed) { - DfaMemoryStateImpl curState = (DfaMemoryStateImpl) instructionState.getMemoryState(); - Object key = curState.getMergeabilityKey(); - DfaMemoryStateImpl mergedState = - StreamEx.of(processed).select(DfaMemoryStateImpl.class).filterBy(DfaMemoryStateImpl::getMergeabilityKey, key) - .foldLeft(curState, (s1, s2) -> { - s1.merge(s2); - return s1; - }); - instructionState = new DfaInstructionState(instructionState.getInstruction(), mergedState); - myWasForciblyMerged = true; - return instructionState; - } - - boolean wasForciblyMerged() { - return myWasForciblyMerged; - } - - private - @Nonnull - Set getJoinInstructions() { - Set joinInstructions = new HashSet<>(); - for (int index = 0; index < myInstructions.length; index++) { - Instruction instruction = myInstructions[index]; - if (instruction instanceof GotoInstruction) { - joinInstructions.add(myInstructions[((GotoInstruction) instruction).getOffset()]); - } else if (instruction instanceof ConditionalGotoInstruction) { - joinInstructions.add(myInstructions[((ConditionalGotoInstruction) instruction).getOffset()]); - } else if (instruction instanceof ControlTransferInstruction) { - IntStreamEx.of(((ControlTransferInstruction) instruction).getPossibleTargetIndices()).elements(myInstructions) - .into(joinInstructions); - } else if (instruction instanceof MethodCallInstruction && !((MethodCallInstruction) instruction).getContracts().isEmpty()) { - joinInstructions.add(myInstructions[index + 1]); - } else if (instruction instanceof FinishElementInstruction && !((FinishElementInstruction) instruction).getVarsToFlush().isEmpty()) { - // Good chances to squash something after some vars are flushed - joinInstructions.add(myInstructions[index + 1]); - } - } - return joinInstructions; - } - - private static void reportDfaProblem(@Nonnull PsiElement psiBlock, - ControlFlow flow, - DfaInstructionState lastInstructionState, Throwable e) { - Attachment[] attachments = {AttachmentFactory.get().create("method_body.txt", psiBlock.getText())}; - if (flow != null) { - String flowText = flow.toString(); - if (lastInstructionState != null) { - int index = lastInstructionState.getInstruction().getIndex(); - flowText = flowText.replaceAll("(?m)^", " "); - flowText = flowText.replaceFirst("(?m)^ {2}" + index + ": ", "* " + index + ": "); - } - attachments = ArrayUtil.append(attachments, AttachmentFactory.get().create("flow.txt", flowText)); - if (lastInstructionState != null) { - DfaMemoryState memoryState = lastInstructionState.getMemoryState(); - String memStateText = null; - try { - memStateText = memoryState.toString(); - } catch (RuntimeException second) { - e.addSuppressed(second); - } - if (memStateText != null) { - attachments = ArrayUtil.append(attachments, AttachmentFactory.get().create("memory_state.txt", memStateText)); - } - } - } - if (e instanceof RuntimeExceptionWithAttachments) { - attachments = ArrayUtil.mergeArrays(attachments, (Attachment[]) ((RuntimeExceptionWithAttachments) e).getAttachments()); - } - LOG.error(new RuntimeExceptionWithAttachments(e, attachments)); - } - - public - @Nonnull - RunnerResult analyzeMethodRecursively(@Nonnull PsiElement block, @Nonnull StandardInstructionVisitor visitor) { - Collection states = createInitialStates(block, visitor, false); - if (states == null) { - return RunnerResult.NOT_APPLICABLE; - } - return analyzeBlockRecursively(block, states, visitor); - } - - public - @Nonnull - RunnerResult analyzeBlockRecursively(@Nonnull PsiElement block, - @Nonnull Collection states, - @Nonnull StandardInstructionVisitor visitor) { - RunnerResult result = analyzeMethod(block, visitor, states); - if (result != RunnerResult.OK) { - return result; - } - - Ref ref = Ref.create(RunnerResult.OK); - forNestedClosures((closure, nestedStates) -> { - RunnerResult res = analyzeBlockRecursively(closure, nestedStates, visitor); - if (res != RunnerResult.OK) { - ref.set(res); - } - }); - return ref.get(); - } - - private void initializeVariables(@Nonnull PsiElement psiBlock, - @Nonnull Collection initialStates, - @Nonnull ControlFlow flow) { - List vars = flow.accessedVariables().collect(Collectors.toList()); - DfaVariableValue assertionStatus = myValueFactory.getAssertionDisabled(); - if (assertionStatus != null && myIgnoreAssertions != ThreeState.UNSURE) { - for (DfaMemoryState state : initialStates) { - state.applyCondition(assertionStatus.eq(myValueFactory.getBoolean(myIgnoreAssertions.toBoolean()))); - } - } - if (psiBlock instanceof PsiClass) { - DfaVariableValue thisValue = getFactory().getVarFactory().createThisValue((PsiClass) psiBlock); - // In class initializer this variable is local until escaped - for (DfaMemoryState state : initialStates) { - state.meetDfType(thisValue, DfTypes.LOCAL_OBJECT); - } - return; - } - PsiElement parent = psiBlock.getParent(); - if (parent instanceof PsiMethod && !(((PsiMethod) parent).isConstructor())) { - Map initialValues = StreamEx.of(vars).mapToEntry( - var -> makeInitialValue(var, (PsiMethod) parent)).nonNullValues().toMap(); - for (DfaMemoryState state : initialStates) { - initialValues.forEach(state::setVarValue); - } - } - } - - private static - @Nullable - DfaValue makeInitialValue(DfaVariableValue var, @Nonnull PsiMethod method) { - DfaValueFactory factory = var.getFactory(); - if (var.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor && var.getType() != null) { - PsiClass aClass = ((DfaExpressionFactory.ThisDescriptor) var.getDescriptor()).getPsiElement(); - if (method.getContainingClass() == aClass && MutationSignature.fromMethod(method).preservesThis()) { - // Unmodifiable view, because we cannot call mutating methods, but it's not guaranteed that all fields are stable - // as fields may not contribute to the visible state - DfType dfType = DfTypes.typedObject(var.getType(), Nullability.NOT_NULL).meet(Mutability.UNMODIFIABLE_VIEW.asDfType()); - return factory.fromDfType(dfType); - } - return null; - } - if (!DfaUtil.isEffectivelyUnqualified(var)) { - return null; - } - PsiField field = ObjectUtil.tryCast(var.getPsiVariable(), PsiField.class); - if (field == null || DfaUtil.ignoreInitializer(field) || DfaUtil.hasInitializationHacks(field)) { - return null; - } - return DfaUtil.getPossiblyNonInitializedValue(factory, field, method); - } - - private static boolean containsState(Collection processed, - DfaInstructionState instructionState) { - if (processed.contains(instructionState.getMemoryState())) { - return true; - } - for (DfaMemoryState state : processed) { - if (((DfaMemoryStateImpl) state).isSuperStateOf((DfaMemoryStateImpl) instructionState.getMemoryState())) { - return true; - } - } - return false; - } - - private void handleStepOutOfLoop(@Nonnull Instruction prevInstruction, - @Nonnull Instruction nextInstruction, - @Nonnull int[] loopNumber, - @Nonnull MultiMap processedStates, - @Nonnull MultiMap incomingStates, - @Nonnull List inFlightStates, - @Nonnull DfaInstructionState[] afterStates, - @Nonnull StateQueue queue) { - if (loopNumber[prevInstruction.getIndex()] == 0 || inSameLoop(prevInstruction, nextInstruction, loopNumber)) { - return; - } - // stepped out of loop. destroy all memory states from the loop, we don't need them anymore - - // but do not touch yet states being handled right now - for (DfaInstructionState state : inFlightStates) { - Instruction instruction = state.getInstruction(); - if (inSameLoop(prevInstruction, instruction, loopNumber)) { - return; - } - } - for (DfaInstructionState state : afterStates) { - Instruction instruction = state.getInstruction(); - if (inSameLoop(prevInstruction, instruction, loopNumber)) { - return; - } - } - // and still in queue - if (!queue.processAll(state -> { - Instruction instruction = state.getInstruction(); - return !inSameLoop(prevInstruction, instruction, loopNumber); - })) { - return; - } - - // now remove obsolete memory states - final Set mayRemoveStatesFor = new HashSet<>(); - for (Instruction instruction : myInstructions) { - if (inSameLoop(prevInstruction, instruction, loopNumber) && instruction instanceof BranchingInstruction) { - mayRemoveStatesFor.add((BranchingInstruction) instruction); - } - } - - for (BranchingInstruction instruction : mayRemoveStatesFor) { - processedStates.remove(instruction); - incomingStates.remove(instruction); - } - } - - private static boolean inSameLoop(@Nonnull Instruction prevInstruction, @Nonnull Instruction nextInstruction, @Nonnull int[] loopNumber) { - return loopNumber[nextInstruction.getIndex()] == loopNumber[prevInstruction.getIndex()]; - } - - @Nonnull - protected DfaInstructionState[] acceptInstruction(@Nonnull InstructionVisitor visitor, @Nonnull DfaInstructionState instructionState) { - Instruction instruction = instructionState.getInstruction(); - DfaInstructionState[] states = instruction.accept(this, instructionState.getMemoryState(), visitor); - - if (instruction instanceof ClosureInstruction) { - PsiElement closure = ((ClosureInstruction) instruction).getClosureElement(); - if (closure instanceof PsiClass) { - registerNestedClosures(instructionState, (PsiClass) closure); - } else if (closure instanceof PsiLambdaExpression) { - registerNestedClosures(instructionState, (PsiLambdaExpression) closure); - } - } - - return states; - } - - private void registerNestedClosures(@Nonnull DfaInstructionState instructionState, @Nonnull PsiClass nestedClass) { - DfaMemoryState state = instructionState.getMemoryState(); - for (PsiMethod method : nestedClass.getMethods()) { - PsiCodeBlock body = method.getBody(); - if (body != null && (method.isPhysical() || !nestedClass.isPhysical())) { - // Skip analysis of non-physical methods of physical class (possibly autogenerated by some plugin like Lombok) - createClosureState(body, state); - } - } - for (PsiClassInitializer initializer : nestedClass.getInitializers()) { - createClosureState(initializer.getBody(), state); - } - for (PsiField field : nestedClass.getFields()) { - createClosureState(field, state); - } - } - - private void registerNestedClosures(@Nonnull DfaInstructionState instructionState, @Nonnull PsiLambdaExpression expr) { - DfaMemoryState state = instructionState.getMemoryState(); - PsiElement body = expr.getBody(); - if (body != null) { - createClosureState(body, state); - } - } - - private void createClosureState(PsiElement anchor, DfaMemoryState state) { - myNestedClosures.putValue(anchor, state.createClosureState()); - } - - protected - @Nonnull - TimeStats createStatistics() { - return new TimeStats(); - } - - protected - @Nonnull - DfaMemoryState createMemoryState() { - return new DfaMemoryStateImpl(myValueFactory); - } - - @Nonnull - public Instruction[] getInstructions() { - return myInstructions; - } - - public - @Nonnull - Instruction getInstruction(int index) { - return myInstructions[index]; - } - - public void forNestedClosures(BiConsumer> consumer) { - // Copy to avoid concurrent modifications - MultiMap closures = new MultiMap<>(myNestedClosures); - for (PsiElement closure : closures.keySet()) { - List unusedVars = StreamEx.of(getFactory().getValues()) - .select(DfaVariableValue.class) - .filter(var -> var.getQualifier() == null) - .filter(var -> var.getPsiVariable() instanceof PsiVariable && - !VariableAccessUtils.variableIsUsed((PsiVariable) var.getPsiVariable(), closure)) - .toList(); - Collection states = closures.get(closure); - if (!unusedVars.isEmpty()) { - List stateList = StreamEx.of(states) - .peek(state -> unusedVars.forEach(state::flushVariable)) - .map(state -> (DfaMemoryStateImpl) state).distinct().toList(); - states = StateQueue.mergeGroup(stateList); - } - consumer.accept(closure, states); - } - } - - protected static class TimeStats { - private static final long DFA_EXECUTION_TIME_TO_REPORT_NANOS = TimeUnit.SECONDS.toNanos(30); - private final + else if (instruction instanceof FinishElementInstruction finishElementInsn && !finishElementInsn.getVarsToFlush().isEmpty()) { + // Good chances to squash something after some vars are flushed + joinInstructions.add(myInstructions[index + 1]); + } + } + return joinInstructions; + } + + @RequiredReadAction + private static void reportDfaProblem( + PsiElement psiBlock, + ControlFlow flow, + DfaInstructionState lastInstructionState, Throwable e + ) { + Attachment[] attachments = {AttachmentFactory.get().create("method_body.txt", psiBlock.getText())}; + if (flow != null) { + String flowText = flow.toString(); + if (lastInstructionState != null) { + int index = lastInstructionState.getInstruction().getIndex(); + flowText = flowText.replaceAll("(?m)^", " "); + flowText = flowText.replaceFirst("(?m)^ {2}" + index + ": ", "* " + index + ": "); + } + attachments = ArrayUtil.append(attachments, AttachmentFactory.get().create("flow.txt", flowText)); + if (lastInstructionState != null) { + DfaMemoryState memoryState = lastInstructionState.getMemoryState(); + String memStateText = null; + try { + memStateText = memoryState.toString(); + } + catch (RuntimeException second) { + e.addSuppressed(second); + } + if (memStateText != null) { + attachments = ArrayUtil.append(attachments, AttachmentFactory.get().create("memory_state.txt", memStateText)); + } + } + } + if (e instanceof RuntimeExceptionWithAttachments runtimeExceptionWithAttachments) { + attachments = ArrayUtil.mergeArrays(attachments, runtimeExceptionWithAttachments.getAttachments()); + } + LOG.error(new RuntimeExceptionWithAttachments(e, attachments)); + } + + @RequiredReadAction + public RunnerResult analyzeMethodRecursively(PsiElement block, StandardInstructionVisitor visitor) { + Collection states = createInitialStates(block, visitor, false); + if (states == null) { + return RunnerResult.NOT_APPLICABLE; + } + return analyzeBlockRecursively(block, states, visitor); + } + + @RequiredReadAction + public RunnerResult analyzeBlockRecursively( + PsiElement block, + Collection states, + StandardInstructionVisitor visitor + ) { + RunnerResult result = analyzeMethod(block, visitor, states); + if (result != RunnerResult.OK) { + return result; + } + + SimpleReference ref = SimpleReference.create(RunnerResult.OK); + forNestedClosures((closure, nestedStates) -> { + RunnerResult res = analyzeBlockRecursively(closure, nestedStates, visitor); + if (res != RunnerResult.OK) { + ref.set(res); + } + }); + return ref.get(); + } + + private void initializeVariables( + PsiElement psiBlock, + Collection initialStates, + ControlFlow flow + ) { + List vars = flow.accessedVariables().collect(Collectors.toList()); + DfaVariableValue assertionStatus = myValueFactory.getAssertionDisabled(); + if (assertionStatus != null && myIgnoreAssertions != ThreeState.UNSURE) { + for (DfaMemoryState state : initialStates) { + state.applyCondition(assertionStatus.eq(myValueFactory.getBoolean(myIgnoreAssertions.toBoolean()))); + } + } + if (psiBlock instanceof PsiClass psiClass) { + DfaVariableValue thisValue = getFactory().getVarFactory().createThisValue(psiClass); + // In class initializer this variable is local until escaped + for (DfaMemoryState state : initialStates) { + state.meetDfType(thisValue, DfTypes.LOCAL_OBJECT); + } + return; + } + if (psiBlock.getParent() instanceof PsiMethod method && !method.isConstructor()) { + Map initialValues = StreamEx.of(vars) + .mapToEntry(var -> makeInitialValue(var, method)) + .nonNullValues() + .toMap(); + for (DfaMemoryState state : initialStates) { + initialValues.forEach(state::setVarValue); + } + } + } + @Nullable - ThreadMXBean myMxBean; - private long myStart; - private long myMergeStart, myFlowTime, myLVATime, myMergeTime, myProcessTime; + private static DfaValue makeInitialValue(DfaVariableValue var, PsiMethod method) { + DfaValueFactory factory = var.getFactory(); + if (var.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor thisDescriptor && var.getType() != null) { + PsiClass aClass = thisDescriptor.getPsiElement(); + if (method.getContainingClass() == aClass && MutationSignature.fromMethod(method).preservesThis()) { + // Unmodifiable view, because we cannot call mutating methods, but it's not guaranteed that all fields are stable + // as fields may not contribute to the visible state + DfType dfType = DfTypes.typedObject(var.getType(), Nullability.NOT_NULL).meet(Mutability.UNMODIFIABLE_VIEW.asDfType()); + return factory.fromDfType(dfType); + } + return null; + } + if (!DfaUtil.isEffectivelyUnqualified(var)) { + return null; + } + PsiField field = ObjectUtil.tryCast(var.getPsiVariable(), PsiField.class); + if (field == null || DfaUtil.ignoreInitializer(field) || DfaUtil.hasInitializationHacks(field)) { + return null; + } + return DfaUtil.getPossiblyNonInitializedValue(factory, field, method); + } + + private static boolean containsState( + Collection processed, + DfaInstructionState instructionState + ) { + if (processed.contains(instructionState.getMemoryState())) { + return true; + } + for (DfaMemoryState state : processed) { + if (((DfaMemoryStateImpl)state).isSuperStateOf((DfaMemoryStateImpl)instructionState.getMemoryState())) { + return true; + } + } + return false; + } + + private void handleStepOutOfLoop( + Instruction prevInstruction, + Instruction nextInstruction, + int[] loopNumber, + MultiMap processedStates, + MultiMap incomingStates, + List inFlightStates, + DfaInstructionState[] afterStates, + StateQueue queue + ) { + if (loopNumber[prevInstruction.getIndex()] == 0 || inSameLoop(prevInstruction, nextInstruction, loopNumber)) { + return; + } + // stepped out of loop. destroy all memory states from the loop, we don't need them anymore + + // but do not touch yet states being handled right now + for (DfaInstructionState state : inFlightStates) { + Instruction instruction = state.getInstruction(); + if (inSameLoop(prevInstruction, instruction, loopNumber)) { + return; + } + } + for (DfaInstructionState state : afterStates) { + Instruction instruction = state.getInstruction(); + if (inSameLoop(prevInstruction, instruction, loopNumber)) { + return; + } + } + // and still in queue + if (!queue.processAll(state -> { + Instruction instruction = state.getInstruction(); + return !inSameLoop(prevInstruction, instruction, loopNumber); + })) { + return; + } + + // now remove obsolete memory states + final Set mayRemoveStatesFor = new HashSet<>(); + for (Instruction instruction : myInstructions) { + if (inSameLoop(prevInstruction, instruction, loopNumber) && instruction instanceof BranchingInstruction branchingInsn) { + mayRemoveStatesFor.add(branchingInsn); + } + } + + for (BranchingInstruction instruction : mayRemoveStatesFor) { + processedStates.remove(instruction); + incomingStates.remove(instruction); + } + } + + private static boolean inSameLoop( + Instruction prevInstruction, + Instruction nextInstruction, + int[] loopNumber + ) { + return loopNumber[nextInstruction.getIndex()] == loopNumber[prevInstruction.getIndex()]; + } + + protected DfaInstructionState[] acceptInstruction(InstructionVisitor visitor, DfaInstructionState instructionState) { + Instruction instruction = instructionState.getInstruction(); + DfaInstructionState[] states = instruction.accept(this, instructionState.getMemoryState(), visitor); + + if (instruction instanceof ClosureInstruction closureInsn) { + PsiElement closure = closureInsn.getClosureElement(); + if (closure instanceof PsiClass psiClass) { + registerNestedClosures(instructionState, psiClass); + } + else if (closure instanceof PsiLambdaExpression lambda) { + registerNestedClosures(instructionState, lambda); + } + } - TimeStats() { - this(ApplicationManager.getApplication().isInternal()); + return states; } - public TimeStats(boolean record) { - myMxBean = record ? ManagementFactory.getThreadMXBean() : null; - reset(); + private void registerNestedClosures(DfaInstructionState instructionState, PsiClass nestedClass) { + DfaMemoryState state = instructionState.getMemoryState(); + for (PsiMethod method : nestedClass.getMethods()) { + PsiCodeBlock body = method.getBody(); + if (body != null && (method.isPhysical() || !nestedClass.isPhysical())) { + // Skip analysis of non-physical methods of physical class (possibly autogenerated by some plugin like Lombok) + createClosureState(body, state); + } + } + for (PsiClassInitializer initializer : nestedClass.getInitializers()) { + createClosureState(initializer.getBody(), state); + } + for (PsiField field : nestedClass.getFields()) { + createClosureState(field, state); + } } - void reset() { - if (myMxBean == null) { - myStart = 0; - } else { - myStart = myMxBean.getCurrentThreadCpuTime(); - } - myMergeStart = myFlowTime = myLVATime = myMergeTime = myProcessTime = 0; + private void registerNestedClosures(DfaInstructionState instructionState, PsiLambdaExpression expr) { + DfaMemoryState state = instructionState.getMemoryState(); + PsiElement body = expr.getBody(); + if (body != null) { + createClosureState(body, state); + } } - void endFlow() { - if (myMxBean != null) { - myFlowTime = myMxBean.getCurrentThreadCpuTime() - myStart; - } + private void createClosureState(PsiElement anchor, DfaMemoryState state) { + myNestedClosures.putValue(anchor, state.createClosureState()); } - void endLVA() { - if (myMxBean != null) { - myLVATime = myMxBean.getCurrentThreadCpuTime() - myStart - myFlowTime; - } + protected TimeStats createStatistics() { + return new TimeStats(); } - void startMerge() { - if (myMxBean != null) { - myMergeStart = System.nanoTime(); - } + protected DfaMemoryState createMemoryState() { + return new DfaMemoryStateImpl(myValueFactory); } - void endMerge() { - if (myMxBean != null) { - myMergeTime += System.nanoTime() - myMergeStart; - } + public Instruction[] getInstructions() { + return myInstructions; } - void endProcess() { - if (myMxBean != null) { - myProcessTime = myMxBean.getCurrentThreadCpuTime() - myStart; - } + public Instruction getInstruction(int index) { + return myInstructions[index]; } - boolean isTooSlow() { - return myProcessTime > DFA_EXECUTION_TIME_TO_REPORT_NANOS; + @RequiredReadAction + public void forNestedClosures( + @RequiredReadAction BiConsumer> consumer + ) { + // Copy to avoid concurrent modifications + MultiMap closures = new MultiMap<>(myNestedClosures); + for (PsiElement closure : closures.keySet()) { + List unusedVars = StreamEx.of(getFactory().getValues()) + .select(DfaVariableValue.class) + .filter(var -> var.getQualifier() == null) + .filter( + var -> var.getPsiVariable() instanceof PsiVariable variable + && !VariableAccessUtils.variableIsUsed(variable, closure) + ) + .toList(); + Collection states = closures.get(closure); + if (!unusedVars.isEmpty()) { + List stateList = StreamEx.of(states) + .peek(state -> unusedVars.forEach(state::flushVariable)) + .map(state -> (DfaMemoryStateImpl)state).distinct().toList(); + states = StateQueue.mergeGroup(stateList); + } + consumer.accept(closure, states); + } } - @Override - public String toString() { - double flowTime = myFlowTime / 1e9; - double lvaTime = myLVATime / 1e9; - double mergeTime = myMergeTime / 1e9; - double interpretTime = (myProcessTime - myFlowTime - myLVATime - myMergeTime) / 1e9; - double totalTime = myProcessTime / 1e9; - String format = "Building ControlFlow: %.2fs\nLiveVariableAnalyzer: %.2fs\nMerging states: %.2fs\nInterpreting: %.2fs\nTotal: %.2fs"; - return String.format(Locale.ENGLISH, format, flowTime, lvaTime, mergeTime, interpretTime, totalTime); + protected static class TimeStats { + private static final long DFA_EXECUTION_TIME_TO_REPORT_NANOS = TimeUnit.SECONDS.toNanos(30); + @Nullable + private final ThreadMXBean myMxBean; + private long myStart; + private long myMergeStart, myFlowTime, myLVATime, myMergeTime, myProcessTime; + + TimeStats() { + this(Application.get().isInternal()); + } + + public TimeStats(boolean record) { + myMxBean = record ? ManagementFactory.getThreadMXBean() : null; + reset(); + } + + void reset() { + if (myMxBean == null) { + myStart = 0; + } + else { + myStart = myMxBean.getCurrentThreadCpuTime(); + } + myMergeStart = myFlowTime = myLVATime = myMergeTime = myProcessTime = 0; + } + + void endFlow() { + if (myMxBean != null) { + myFlowTime = myMxBean.getCurrentThreadCpuTime() - myStart; + } + } + + void endLVA() { + if (myMxBean != null) { + myLVATime = myMxBean.getCurrentThreadCpuTime() - myStart - myFlowTime; + } + } + + void startMerge() { + if (myMxBean != null) { + myMergeStart = System.nanoTime(); + } + } + + void endMerge() { + if (myMxBean != null) { + myMergeTime += System.nanoTime() - myMergeStart; + } + } + + void endProcess() { + if (myMxBean != null) { + myProcessTime = myMxBean.getCurrentThreadCpuTime() - myStart; + } + } + + boolean isTooSlow() { + return myProcessTime > DFA_EXECUTION_TIME_TO_REPORT_NANOS; + } + + @Override + public String toString() { + double flowTime = myFlowTime / 1e9; + double lvaTime = myLVATime / 1e9; + double mergeTime = myMergeTime / 1e9; + double interpretTime = (myProcessTime - myFlowTime - myLVATime - myMergeTime) / 1e9; + double totalTime = myProcessTime / 1e9; + String format = + "Building ControlFlow: %.2fs\nLiveVariableAnalyzer: %.2fs\nMerging states: %.2fs\nInterpreting: %.2fs\nTotal: %.2fs"; + return String.format(Locale.ENGLISH, format, flowTime, lvaTime, mergeTime, interpretTime, totalTime); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallArguments.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallArguments.java index a041a49f72..59189e12d9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallArguments.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallArguments.java @@ -19,8 +19,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.*; import com.siyeh.ig.psiutils.MethodCallUtils; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.HashSet; @@ -32,10 +31,9 @@ final DfaValue myQualifier; final DfaValue[] myArguments; final - @Nonnull MutationSignature myMutation; - DfaCallArguments(DfaValue qualifier, DfaValue[] arguments, @Nonnull MutationSignature mutation) + DfaCallArguments(DfaValue qualifier, DfaValue[] arguments, MutationSignature mutation) { myQualifier = qualifier; myArguments = arguments; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallState.java index b4de67b57d..dc240a05fc 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallState.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaCallState.java @@ -1,16 +1,13 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import javax.annotation.Nonnull; final class DfaCallState { - @Nonnull final DfaMemoryState myMemoryState; - @Nonnull final DfaCallArguments myCallArguments; - DfaCallState(@Nonnull DfaMemoryState state, @Nonnull DfaCallArguments arguments) + DfaCallState(DfaMemoryState state, DfaCallArguments arguments) { myMemoryState = state; myCallArguments = arguments; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaControlTransferValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaControlTransferValue.java index 28b4c65c51..9925a12f80 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaControlTransferValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaControlTransferValue.java @@ -21,7 +21,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import consulo.util.collection.FList; -import javax.annotation.Nonnull; import java.util.List; /** @@ -30,10 +29,9 @@ public class DfaControlTransferValue extends DfaValue { private TransferTarget myTarget; - @Nonnull private FList myTraps; - public DfaControlTransferValue(DfaValueFactory factory, TransferTarget target, @Nonnull FList traps) + public DfaControlTransferValue(DfaValueFactory factory, TransferTarget target, FList traps) { super(factory); myTarget = target; @@ -50,7 +48,6 @@ public TransferTarget getTarget() return myTarget; } - @Nonnull public FList getTraps() { return myTraps; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaInstructionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaInstructionState.java index fd800175e2..b8ceb5b526 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaInstructionState.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaInstructionState.java @@ -10,7 +10,6 @@ import consulo.util.collection.MultiMap; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.util.*; public class DfaInstructionState implements Comparable { @@ -18,17 +17,15 @@ public class DfaInstructionState implements Comparable { private final DfaMemoryState myBeforeMemoryState; private final Instruction myInstruction; - public DfaInstructionState(@Nonnull Instruction myInstruction, @Nonnull DfaMemoryState myBeforeMemoryState) { + public DfaInstructionState(Instruction myInstruction, DfaMemoryState myBeforeMemoryState) { this.myBeforeMemoryState = myBeforeMemoryState; this.myInstruction = myInstruction; } - @Nonnull public Instruction getInstruction() { return myInstruction; } - @Nonnull public DfaMemoryState getMemoryState() { return myBeforeMemoryState; } @@ -38,7 +35,7 @@ public String toString() { } @Override - public int compareTo(@Nonnull DfaInstructionState o) { + public int compareTo(DfaInstructionState o) { return Integer.compare(myInstruction.getIndex(), o.myInstruction.getIndex()); } } @@ -59,7 +56,7 @@ boolean isEmpty() { return myQueue.isEmpty(); } - boolean processAll(@Nonnull Processor processor) { + boolean processAll(Processor processor) { for (DfaInstructionState state : myQueue) { if (!processor.process(state)) { return false; @@ -68,7 +65,6 @@ boolean processAll(@Nonnull Processor processor) { return true; } - @Nonnull List getNextInstructionStates(Set joinInstructions) { DfaInstructionState state = myQueue.remove(); final Instruction instruction = state.getInstruction(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryState.java index 50ae1ee552..6e6a7a8ed8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryState.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryState.java @@ -22,18 +22,15 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.RelationType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.EmptyStackException; import java.util.Set; public interface DfaMemoryState { - @Nonnull DfaMemoryState createCopy(); - @Nonnull DfaMemoryState createClosureState(); /** @@ -42,7 +39,6 @@ public interface DfaMemoryState * @return popped value * @throws EmptyStackException if stack is empty */ - @Nonnull DfaValue pop(); /** @@ -51,7 +47,6 @@ public interface DfaMemoryState * @return top of stack value * @throws EmptyStackException if stack is empty */ - @Nonnull DfaValue peek(); /** @@ -69,7 +64,7 @@ public interface DfaMemoryState * * @param value to push */ - void push(@Nonnull DfaValue value); + void push(DfaValue value); void emptyStack(); @@ -94,7 +89,7 @@ public interface DfaMemoryState * @param value2 second value to check * @return true if they are equal; false if not equal or not known */ - boolean areEqual(@Nonnull DfaValue value1, @Nonnull DfaValue value2); + boolean areEqual(DfaValue value1, DfaValue value2); boolean applyContractCondition(DfaCondition dfaCond); @@ -106,7 +101,7 @@ public interface DfaMemoryState * @param dfType wanted type * @return true if update was successful. If false was returned the memory state may be in inconsistent state. */ - boolean meetDfType(@Nonnull DfaValue value, @Nonnull DfType dfType); + boolean meetDfType(DfaValue value, DfType dfType); /** * Forcibly sets the supplied dfType to given value if given value state can be memoized. @@ -116,38 +111,36 @@ public interface DfaMemoryState * @param value value to update. * @param dfType type to assign to value. Note that type might be adjusted, e.g. to be compatible with value declared PsiType. */ - void setDfType(@Nonnull DfaValue value, @Nonnull DfType dfType); + void setDfType(DfaValue value, DfType dfType); /** * @param value value to get the type of * @return the DfType of the value within this memory state */ - @Nonnull - DfType getDfType(@Nonnull DfaValue value); + DfType getDfType(DfaValue value); /** * @param value value to get the type of; if value is a primitive wrapper, it will be unboxed before fetching the DfType * @return the DfType of the value within this memory state */ - @Nonnull - DfType getUnboxedDfType(@Nonnull DfaValue value); + DfType getUnboxedDfType(DfaValue value); /** * @param value value to get the type of * @return the PsiType of given value, could be more precise than the declared type. May return null if not known. */ @Nullable - PsiType getPsiType(@Nonnull DfaValue value); + PsiType getPsiType(DfaValue value); - void flushFieldsQualifiedBy(@Nonnull Set qualifiers); + void flushFieldsQualifiedBy(Set qualifiers); void flushFields(); - void flushVariable(@Nonnull DfaVariableValue variable); + void flushVariable(DfaVariableValue variable); boolean isNull(DfaValue dfaVar); - boolean checkNotNullable(@Nonnull DfaValue value); + boolean checkNotNullable(DfaValue value); boolean isNotNull(DfaValue dfaVar); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryStateImpl.java index d31b3a5ff2..01ba7ff7e5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -11,7 +11,6 @@ import com.intellij.java.language.psi.util.PropertyUtilBase; import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.application.ApplicationManager; import consulo.application.progress.ProgressManager; import consulo.logging.Logger; import consulo.util.collection.ContainerUtil; @@ -24,10 +23,9 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; @@ -75,21 +73,18 @@ protected DfaMemoryStateImpl(DfaMemoryStateImpl toCopy) { myCachedHash = toCopy.myCachedHash; } - @Nonnull public DfaValueFactory getFactory() { return myFactory; } @Override public - @Nonnull DfaMemoryStateImpl createCopy() { return new DfaMemoryStateImpl(this); } @Override public - @Nonnull DfaMemoryStateImpl createClosureState() { DfaMemoryStateImpl copy = createCopy(); forRecordedVariableTypes((value, dfType) -> { @@ -186,7 +181,6 @@ public String toString() { @Override public - @Nonnull DfaValue pop() { myCachedHash = null; return myStack.pop(); @@ -194,7 +188,6 @@ DfaValue pop() { @Override public - @Nonnull DfaValue peek() { return myStack.peek(); } @@ -208,7 +201,7 @@ DfaValue getStackValue(int offset) { } @Override - public void push(@Nonnull DfaValue value) { + public void push(DfaValue value) { myCachedHash = null; myStack.push(value); } @@ -254,7 +247,7 @@ public void setVarValue(DfaVariableValue var, DfaValue value) { } } - protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @Nonnull DfType dfType) { + protected DfType filterDfTypeOnAssignment(DfaVariableValue var, DfType dfType) { return dfType; } @@ -276,7 +269,7 @@ private DfaValue handleStackValueOnVariableFlush(DfaValue value, return value; } - private int getOrCreateEqClassIndex(@Nonnull DfaVariableValue dfaValue) { + private int getOrCreateEqClassIndex(DfaVariableValue dfaValue) { int i = getEqClassIndex(dfaValue); if (i != -1) { return i; @@ -439,7 +432,7 @@ EqClass getEqClass(DfaValue value) { * @param dfaValue value to find a class for * @return class index or -1 if not found */ - int getEqClassIndex(@Nonnull DfaValue dfaValue) { + int getEqClassIndex(DfaValue dfaValue) { Integer classIndex = myIdToEqClassesIndices.get(dfaValue.getID()); if (classIndex == null) { dfaValue = canonicalize(dfaValue); @@ -550,7 +543,7 @@ private boolean convertQualifiers(DfaVariableValue from, DfaVariableValue to) { } private void checkInvariants() { - if (!LOG.isDebugEnabled() && !ApplicationManager.getApplication().isEAP()) { + if (!LOG.isDebugEnabled()) { return; } myIdToEqClassesIndices.forEach((id, classIndex) -> { @@ -634,7 +627,7 @@ private void convertReferenceEqualityToValueEquality(DfaValue value) { } @Override - public void setDfType(@Nonnull DfaValue value, @Nonnull DfType dfType) { + public void setDfType(DfaValue value, DfType dfType) { if (value instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) value; DfType type = getDfType(var); @@ -646,8 +639,7 @@ public void setDfType(@Nonnull DfaValue value, @Nonnull DfType dfType) { } private static - @Nonnull - DfType sanitizeNullability(@Nonnull DfType dfType) { + DfType sanitizeNullability(DfType dfType) { if (!(dfType instanceof DfReferenceType)) { return dfType; } @@ -659,7 +651,7 @@ DfType sanitizeNullability(@Nonnull DfType dfType) { } @Override - public boolean meetDfType(@Nonnull DfaValue value, @Nonnull DfType dfType) { + public boolean meetDfType(DfaValue value, DfType dfType) { if (dfType == DfTypes.TOP) { return true; } @@ -711,7 +703,7 @@ public boolean meetDfType(@Nonnull DfaValue value, @Nonnull DfType dfType) { return value.getDfType().meet(dfType) != DfTypes.BOTTOM; } - private boolean propagateRangeBack(@Nonnull LongRangeSet factValue, @Nonnull DfaBinOpValue binOp) { + private boolean propagateRangeBack(LongRangeSet factValue, DfaBinOpValue binOp) { boolean isLong = PsiType.LONG.equals(binOp.getType()); LongRangeSet appliedRange = isLong ? factValue : factValue.intersect(Objects.requireNonNull(LongRangeSet.fromType(PsiType.INT))); DfaVariableValue left = binOp.getLeft(); @@ -761,7 +753,7 @@ public boolean applyContractCondition(DfaCondition condition) { } @Override - public boolean areEqual(@Nonnull DfaValue value1, @Nonnull DfaValue value2) { + public boolean areEqual(DfaValue value1, DfaValue value2) { if (value1 instanceof DfaBinOpValue && value2 instanceof DfaBinOpValue) { DfaBinOpValue binOp1 = (DfaBinOpValue) value1; DfaBinOpValue binOp2 = (DfaBinOpValue) value2; @@ -811,7 +803,7 @@ public boolean applyCondition(DfaCondition dfaCond) { return applyRelationCondition((DfaRelation) dfaCond); } - private boolean applyRelationCondition(@Nonnull DfaRelation dfaRelation) { + private boolean applyRelationCondition(DfaRelation dfaRelation) { DfaValue dfaLeft = dfaRelation.getLeftOperand(); DfaValue dfaRight = dfaRelation.getRightOperand(); RelationType relationType = dfaRelation.getRelation(); @@ -987,7 +979,6 @@ private static RelationType correctRelation(RelationType relation, LongRangeSet } private static - @Nonnull LongRangeSet getIntegerSumOverflowValues(LongRangeSet left, LongRangeSet right) { if (left.isEmpty() || right.isEmpty()) { return LongRangeSet.empty(); @@ -1196,7 +1187,7 @@ private Couple getSpecialEquivalencePair(DfaVariableValue left, DfaVal return Couple.of(leftValue, rightValue); } - private boolean applySpecialFieldEquivalence(@Nonnull DfaValue left, @Nonnull DfaValue right) { + private boolean applySpecialFieldEquivalence(DfaValue left, DfaValue right) { Couple pair = left instanceof DfaVariableValue ? getSpecialEquivalencePair((DfaVariableValue) left, right) : right instanceof DfaVariableValue ? getSpecialEquivalencePair((DfaVariableValue) right, left) : null; if (pair == null || isNaN(pair.getFirst()) || isNaN(pair.getSecond())) { @@ -1205,7 +1196,7 @@ private boolean applySpecialFieldEquivalence(@Nonnull DfaValue left, @Nonnull Df return applyCondition(pair.getFirst().eq(pair.getSecond())); } - private boolean applyUnboxedRelation(@Nonnull DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { + private boolean applyUnboxedRelation(DfaValue dfaLeft, DfaValue dfaRight, boolean negated) { if (dfaLeft instanceof DfaVariableValue && !TypeConversionUtil.isPrimitiveWrapper(dfaLeft.getType()) || dfaRight instanceof DfaVariableValue && !TypeConversionUtil.isPrimitiveWrapper(dfaRight.getType())) { return true; @@ -1235,7 +1226,7 @@ private boolean applyUnboxedRelation(@Nonnull DfaValue dfaLeft, DfaValue dfaRigh @Override public @Nullable - PsiType getPsiType(@Nonnull DfaValue value) { + PsiType getPsiType(DfaValue value) { PsiType type = DfaTypeValue.toPsiType(getFactory().getProject(), getDfType(value)); return type == null ? value.getType() : type; } @@ -1244,7 +1235,7 @@ private static boolean isNaN(final DfaValue dfa) { return dfa != null && isNaN(dfa.getDfType()); } - private static boolean canBeNaN(@Nonnull DfType dfType) { + private static boolean canBeNaN(DfType dfType) { return dfType.isSuperType(DfTypes.floatValue(Float.NaN)) || dfType.isSuperType(DfTypes.doubleValue(Double.NaN)); } @@ -1252,7 +1243,7 @@ private static boolean isNaN(DfType type) { return type instanceof DfConstantType && DfaUtil.isNaN(((DfConstantType) type).getValue()); } - private boolean applyRelation(@Nonnull DfaValue dfaLeft, @Nonnull DfaValue dfaRight, boolean isNegated) { + private boolean applyRelation(DfaValue dfaLeft, DfaValue dfaRight, boolean isNegated) { if (!(dfaLeft instanceof DfaVariableValue) || !(dfaRight instanceof DfaVariableValue)) { return true; } @@ -1278,7 +1269,7 @@ private boolean applyRelation(@Nonnull DfaValue dfaLeft, @Nonnull DfaValue dfaRi return true; } - private boolean applyLessThanRelation(@Nonnull DfaValue dfaLeft, @Nonnull DfaValue dfaRight) { + private boolean applyLessThanRelation(DfaValue dfaLeft, DfaValue dfaRight) { if (!(dfaLeft instanceof DfaVariableValue) || !(dfaRight instanceof DfaVariableValue)) { return true; } @@ -1338,7 +1329,7 @@ RelationType getFloatingConstantRelation(DfType leftType, DfType rightType) { } @Override - public boolean checkNotNullable(@Nonnull DfaValue value) { + public boolean checkNotNullable(DfaValue value) { DfaNullability nullability = DfaNullability.fromDfType(getDfType(value)); return nullability != DfaNullability.NULL && nullability != DfaNullability.NULLABLE; } @@ -1373,8 +1364,7 @@ LongRangeSet getBinOpRange(DfaBinOpValue binOp) { @Override public - @Nonnull - DfType getUnboxedDfType(@Nonnull DfaValue value) { + DfType getUnboxedDfType(DfaValue value) { if (value instanceof DfaBoxedValue) { return getDfType(((DfaBoxedValue) value).getWrappedValue()); } @@ -1392,8 +1382,7 @@ DfType getUnboxedDfType(@Nonnull DfaValue value) { @Override public - @Nonnull - DfType getDfType(@Nonnull DfaValue value) { + DfType getDfType(DfaValue value) { if (value instanceof DfaBinOpValue) { LongRangeSet range = getBinOpRange((DfaBinOpValue) value); if (range == null) { @@ -1408,7 +1397,7 @@ DfType getDfType(@Nonnull DfaValue value) { return value.getDfType(); } - void recordVariableType(@Nonnull DfaVariableValue dfaVar, @Nonnull DfType type) { + void recordVariableType(DfaVariableValue dfaVar, DfType type) { dfaVar = canonicalize(dfaVar); if (type instanceof DfReferenceType) { type = ((DfReferenceType) type).dropSpecialField(); @@ -1437,8 +1426,7 @@ protected void updateEquivalentVariables(DfaVariableValue dfaVar, DfType type) { } private - @Nonnull - DfaValue canonicalize(@Nonnull DfaValue value) { + DfaValue canonicalize(DfaValue value) { if (value instanceof DfaVariableValue) { return canonicalize((DfaVariableValue) value); } @@ -1451,7 +1439,6 @@ DfaValue canonicalize(@Nonnull DfaValue value) { } private - @Nonnull DfaVariableValue canonicalize(DfaVariableValue var) { DfaVariableValue qualifier = var.getQualifier(); if (qualifier != null) { @@ -1483,7 +1470,7 @@ public void forRecordedVariableTypes(BiConsumer qualifiers) { + public void flushFieldsQualifiedBy(Set qualifiers) { flushFields(new QualifierStatusMap(qualifiers)); } @@ -1492,7 +1479,7 @@ public void flushFields() { flushFields(new QualifierStatusMap(null)); } - public void flushFields(@Nonnull DfaMemoryStateImpl.QualifierStatusMap qualifierStatusMap) { + public void flushFields(DfaMemoryStateImpl.QualifierStatusMap qualifierStatusMap) { Set vars = new LinkedHashSet<>(); for (DfaVariableValue value : myVariableTypes.keySet()) { if (value.isFlushableByCalls() && qualifierStatusMap.shouldFlush(value.getQualifier(), value.containsCalls())) { @@ -1523,7 +1510,7 @@ public void flushFields(@Nonnull DfaMemoryStateImpl.QualifierStatusMap qualifier }); } - private boolean shouldMarkFlushed(@Nonnull DfaVariableValue value) { + private boolean shouldMarkFlushed(DfaVariableValue value) { if (value.getInherentNullability() != Nullability.NULLABLE) { return false; } @@ -1531,11 +1518,11 @@ private boolean shouldMarkFlushed(@Nonnull DfaVariableValue value) { } @Override - public void flushVariable(@Nonnull DfaVariableValue variable) { + public void flushVariable(DfaVariableValue variable) { flushVariable(variable, false); } - protected void flushVariable(@Nonnull DfaVariableValue variable, boolean shouldMarkFlushed) { + protected void flushVariable(DfaVariableValue variable, boolean shouldMarkFlushed) { EqClass eqClass = variable.getDependentVariables().isEmpty() ? null : getEqClass(variable); DfaVariableValue newCanonical = eqClass == null ? null : StreamEx.of(eqClass.iterator()).without(variable).min(EqClass.CANONICAL_VARIABLE_COMPARATOR) @@ -1548,13 +1535,13 @@ protected void flushVariable(@Nonnull DfaVariableValue variable, boolean shouldM myCachedHash = null; } - void flushDependencies(@Nonnull DfaVariableValue variable) { + void flushDependencies(DfaVariableValue variable) { for (DfaVariableValue dependent : variable.getDependentVariables().toArray(new DfaVariableValue[0])) { doFlush(dependent, false); } } - private void flushQualifiedMethods(@Nonnull DfaVariableValue variable) { + private void flushQualifiedMethods(DfaVariableValue variable) { PsiModifierListOwner psiVariable = variable.getPsiVariable(); DfaVariableValue qualifier = variable.getQualifier(); if (psiVariable instanceof PsiField && qualifier != null) { @@ -1565,7 +1552,7 @@ private void flushQualifiedMethods(@Nonnull DfaVariableValue variable) { } } - void doFlush(@Nonnull DfaVariableValue var, boolean markFlushed) { + void doFlush(DfaVariableValue var, boolean markFlushed) { if (isNull(var)) { myStack.replaceAll(val -> val == var ? myFactory.getNull() : val); } @@ -1790,7 +1777,6 @@ private boolean retainEquivalences(EqClass eqClass, DfaMemoryStateImpl other) { * thus sum of resulting class sizes is equal to the original class size */ private - @Nonnull List splitEqClass(EqClass eqClass, DfaMemoryStateImpl other) { IntObjectMap groupsInClasses = IntMaps.newIntObjectHashMap(); List groups = new ArrayList<>(); @@ -1859,8 +1845,7 @@ boolean shouldFlush(@Nullable DfaValue qualifier, boolean hasCall) { } private - @Nonnull - QualifierStatus calculate(@Nonnull DfaValue qualifier) { + QualifierStatus calculate(DfaValue qualifier) { final DfReferenceType dfType = ObjectUtil.tryCast(getDfType(qualifier), DfReferenceType.class); if (dfType == null) { return QualifierStatus.SHOULD_FLUSH_ALWAYS; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaNullability.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaNullability.java index e3a29e3485..2a7eafb74c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaNullability.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaNullability.java @@ -1,16 +1,13 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfReferenceType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; -import com.intellij.java.analysis.JavaAnalysisBundle; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import java.util.function.Supplier; +import com.intellij.java.language.codeInsight.Nullability; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; /** * Represents a value nullability within DFA. Unlike {@link Nullability} may have more fine-grained @@ -18,138 +15,96 @@ * and want to check if it's nullable, or not, it's advised to convert it to {@link Nullability} first, * as more values could be introduced to this enum in future. */ -public enum DfaNullability -{ - /** - * Means: exactly null - */ - NULL("Null", JavaAnalysisBundle.messagePointer("nullability.null"), Nullability.NULLABLE), - NULLABLE("Nullable", JavaAnalysisBundle.messagePointer("nullability.nullable"), Nullability.NULLABLE), - NOT_NULL("Not-null", JavaAnalysisBundle.messagePointer("nullability.non.null"), Nullability.NOT_NULL), - UNKNOWN("Unknown", () -> "", Nullability.UNKNOWN), - /** - * Means: non-stable variable declared as Nullable was checked for nullity and flushed afterwards (e.g. by unknown method call), - * so we are unsure about its nullability anymore. - */ - FLUSHED("Flushed", () -> "", Nullability.UNKNOWN); +public enum DfaNullability { + /** + * Means: exactly null + */ + NULL("Null", JavaAnalysisLocalize.nullabilityNull(), Nullability.NULLABLE), + NULLABLE("Nullable", JavaAnalysisLocalize.nullabilityNullable(), Nullability.NULLABLE), + NOT_NULL("Not-null", JavaAnalysisLocalize.nullabilityNonNull(), Nullability.NOT_NULL), + UNKNOWN("Unknown", LocalizeValue.empty(), Nullability.UNKNOWN), + /** + * Means: non-stable variable declared as Nullable was checked for nullity and flushed afterwards (e.g. by unknown method call), + * so we are unsure about its nullability anymore. + */ + FLUSHED("Flushed", LocalizeValue.empty(), Nullability.UNKNOWN); - private final - @Nonnull - String myInternalName; - private final - @Nonnull - Supplier myPresentationalName; - private final - @Nonnull - Nullability myNullability; + private final String myInternalName; + private final LocalizeValue myPresentationalName; + private final Nullability myNullability; - DfaNullability(@Nonnull String internalName, @Nonnull Supplier< String> presentationalName, @Nonnull Nullability nullability) - { - myInternalName = internalName; - myPresentationalName = presentationalName; - myNullability = nullability; - } + DfaNullability(String internalName, LocalizeValue presentationalName, Nullability nullability) { + myInternalName = internalName; + myPresentationalName = presentationalName; + myNullability = nullability; + } - @Nonnull - public String getInternalName() - { - return myInternalName; - } + public String getInternalName() { + return myInternalName; + } - public - @Nonnull - @Nls - String getPresentationName() - { - return myPresentationalName.get(); - } + public LocalizeValue getPresentationName() { + return myPresentationalName; + } - @Nonnull - public DfaNullability unite(@Nonnull DfaNullability other) - { - if(this == other) - { - return this; - } - if(this == NULL || other == NULL || - this == NULLABLE || other == NULLABLE) - { - return NULLABLE; - } - if(this == FLUSHED || other == FLUSHED) - { - return FLUSHED; - } - return UNKNOWN; - } + public DfaNullability unite(DfaNullability other) { + if (this == other) { + return this; + } + if (this == NULL || other == NULL || + this == NULLABLE || other == NULLABLE) { + return NULLABLE; + } + if (this == FLUSHED || other == FLUSHED) { + return FLUSHED; + } + return UNKNOWN; + } - @Nullable - public DfaNullability intersect(@Nonnull DfaNullability right) - { - if(this == NOT_NULL) - { - return right == NULL ? null : NOT_NULL; - } - if(right == NOT_NULL) - { - return this == NULL ? null : NOT_NULL; - } - if(this == UNKNOWN) - { - return right; - } - if(right == UNKNOWN) - { - return this; - } - if(this == FLUSHED && toNullability(right) == Nullability.NULLABLE || - right == FLUSHED && toNullability(this) == Nullability.NULLABLE) - { - return NULLABLE; - } - return equals(right) ? this : null; - } + @Nullable + public DfaNullability intersect(DfaNullability right) { + if (this == NOT_NULL) { + return right == NULL ? null : NOT_NULL; + } + if (right == NOT_NULL) { + return this == NULL ? null : NOT_NULL; + } + if (this == UNKNOWN) { + return right; + } + if (right == UNKNOWN) { + return this; + } + if (this == FLUSHED && toNullability(right) == Nullability.NULLABLE + || right == FLUSHED && toNullability(this) == Nullability.NULLABLE) { + return NULLABLE; + } + return equals(right) ? this : null; + } - @Nonnull - public static Nullability toNullability(@Nullable DfaNullability dfaNullability) - { - return dfaNullability == null ? Nullability.UNKNOWN : dfaNullability.myNullability; - } + public static Nullability toNullability(@Nullable DfaNullability dfaNullability) { + return dfaNullability == null ? Nullability.UNKNOWN : dfaNullability.myNullability; + } - @Nonnull - public static DfaNullability fromNullability(@Nonnull Nullability nullability) - { - switch(nullability) - { - case NOT_NULL: - return NOT_NULL; - case NULLABLE: - return NULLABLE; - case UNKNOWN: - return UNKNOWN; - } - throw new IllegalStateException("Unknown nullability: " + nullability); - } + public static DfaNullability fromNullability(Nullability nullability) { + return switch (nullability) { + case NOT_NULL -> NOT_NULL; + case NULLABLE -> NULLABLE; + case UNKNOWN -> UNKNOWN; + default -> throw new IllegalStateException("Unknown nullability: " + nullability); + }; + } - @Nonnull - public DfReferenceType asDfType() - { - switch(this) - { - case NULL: - return DfTypes.NULL; - case NOT_NULL: - return DfTypes.NOT_NULL_OBJECT; - case UNKNOWN: - return DfTypes.OBJECT_OR_NULL; - default: - return DfTypes.customObject(TypeConstraints.TOP, this, Mutability.UNKNOWN, null, DfTypes.BOTTOM); - } - } + public DfReferenceType asDfType() { + return switch (this) { + case NULL -> DfTypes.NULL; + case NOT_NULL -> DfTypes.NOT_NULL_OBJECT; + case UNKNOWN -> DfTypes.OBJECT_OR_NULL; + default -> DfTypes.customObject(TypeConstraints.TOP, this, Mutability.UNKNOWN, null, DfTypes.BOTTOM); + }; + } - @Nonnull - public static DfaNullability fromDfType(@Nonnull DfType type) - { - return type instanceof DfReferenceType ? ((DfReferenceType) type).getNullability() : UNKNOWN; - } + public static DfaNullability fromDfType(DfType type) { + return type instanceof DfReferenceType refType ? refType.getNullability() : UNKNOWN; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java index d7f1cb3367..56ece993f9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaOptionalSupport.java @@ -7,107 +7,106 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.ExpressionUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.intention.CommonQuickFixBundle; +import consulo.language.editor.localize.CommonQuickFixLocalize; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author anet, peter */ public final class DfaOptionalSupport { - - @Nullable - public static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@Nonnull PsiExpression qualifier) { - final PsiMethodCallExpression call = findCallExpression(qualifier); - final PsiMethod method = call == null ? null : call.resolveMethod(); - final PsiClass containingClass = method == null ? null : method.getContainingClass(); - if (containingClass != null && "of".equals(method.getName())) { - final String qualifiedName = containingClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalCallFix("ofNullable", false); - } - if (OptionalUtil.GUAVA_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalCallFix("fromNullable", false); - } + @Nullable + public static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(PsiExpression qualifier) { + PsiMethodCallExpression call = findCallExpression(qualifier); + PsiMethod method = call == null ? null : call.resolveMethod(); + PsiClass containingClass = method == null ? null : method.getContainingClass(); + if (containingClass != null && "of".equals(method.getName())) { + String qualifiedName = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("ofNullable", false); + } + if (OptionalUtil.GUAVA_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("fromNullable", false); + } + } + return null; } - return null; - } - private static PsiMethodCallExpression findCallExpression(@Nonnull PsiElement anchor) { - final PsiElement argList = PsiUtil.skipParenthesizedExprUp(anchor).getParent(); - if (argList instanceof PsiExpressionList) { - final PsiElement parent = argList.getParent(); - if (parent instanceof PsiMethodCallExpression) { - return (PsiMethodCallExpression) parent; - } + private static PsiMethodCallExpression findCallExpression(PsiElement anchor) { + if (PsiUtil.skipParenthesizedExprUp(anchor).getParent() instanceof PsiExpressionList argList) { + PsiElement parent = argList.getParent(); + if (parent instanceof PsiMethodCallExpression methodCall) { + return methodCall; + } + } + return null; } - return null; - } - @Nullable - public static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@Nonnull PsiElement anchor) { - final PsiMethodCallExpression parent = findCallExpression(anchor); - if (parent == null) - return null; - boolean jdkOptional = OptionalUtil.JDK_OPTIONAL_OF_NULLABLE.test(parent); - return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true); - } + @Nullable + public static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(PsiElement anchor) { + PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) { + return null; + } + boolean jdkOptional = OptionalUtil.JDK_OPTIONAL_OF_NULLABLE.test(parent); + return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true); + } - @Nullable - public static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(@Nonnull PsiElement anchor) { - final PsiMethodCallExpression parent = findCallExpression(anchor); - if (parent == null) - return null; - return new ReplaceOptionalCallFix("of", false); - } + @Nullable + public static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(PsiElement anchor) { + PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) { + return null; + } + return new ReplaceOptionalCallFix("of", false); + } - /** - * Creates a DfType which represents present or absent optional (non-null) - * - * @param present whether the value should be present - * @return a DfType representing an Optional - */ - @Nonnull - public static DfType getOptionalValue(boolean present) { - DfType valueType = present ? DfTypes.NOT_NULL_OBJECT : DfTypes.NULL; - return SpecialField.OPTIONAL_VALUE.asDfType(valueType); - } + /** + * Creates a DfType which represents present or absent optional (non-null) + * + * @param present whether the value should be present + * @return a DfType representing an Optional + */ + public static DfType getOptionalValue(boolean present) { + DfType valueType = present ? DfTypes.NOT_NULL_OBJECT : DfTypes.NULL; + return SpecialField.OPTIONAL_VALUE.asDfType(valueType); + } - private static class ReplaceOptionalCallFix implements LocalQuickFix { - private final String myTargetMethodName; - private final boolean myClearArguments; + private static class ReplaceOptionalCallFix implements LocalQuickFix { + private final String myTargetMethodName; + private final boolean myClearArguments; - ReplaceOptionalCallFix(final String targetMethodName, boolean clearArguments) { - myTargetMethodName = targetMethodName; - myClearArguments = clearArguments; - } + ReplaceOptionalCallFix(String targetMethodName, boolean clearArguments) { + myTargetMethodName = targetMethodName; + myClearArguments = clearArguments; + } - @Nonnull - @Override - public String getFamilyName() { - return CommonQuickFixBundle.message("fix.replace.with.x", "." + myTargetMethodName + "()"); - } + @Override + public LocalizeValue getName() { + return CommonQuickFixLocalize.fixReplaceWithX("." + myTargetMethodName + "()"); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiMethodCallExpression - methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); - if (methodCallExpression != null) { - ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName); - if (myClearArguments) { - PsiExpressionList argList = methodCallExpression.getArgumentList(); - PsiExpression[] args = argList.getExpressions(); - if (args.length > 0) { - argList.deleteChildRange(args[0], args[args.length - 1]); - } + @Override + @RequiredReadAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiMethodCallExpression + methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); + if (methodCallExpression != null) { + ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName); + if (myClearArguments) { + PsiExpressionList argList = methodCallExpression.getArgumentList(); + PsiExpression[] args = argList.getExpressions(); + if (args.length > 0) { + argList.deleteChildRange(args[0], args[args.length - 1]); + } + } + } } - } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaPsiUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaPsiUtil.java index b641f47069..628511f7a2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaPsiUtil.java @@ -9,10 +9,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; import com.intellij.java.analysis.impl.codeInspection.util.OptionalUtil; import com.intellij.java.language.JavaLanguage; -import com.intellij.java.language.codeInsight.AnnotationUtil; -import com.intellij.java.language.codeInsight.Nullability; -import com.intellij.java.language.codeInsight.NullabilityAnnotationInfo; -import com.intellij.java.language.codeInsight.NullableNotNullManager; +import com.intellij.java.language.codeInsight.*; import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.search.searches.DeepestSuperMethodsSearch; @@ -28,13 +25,16 @@ import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.LanguageCachedValueUtil; import consulo.language.psi.util.PsiTreeUtil; +import consulo.project.DumbService; +import consulo.project.Project; import consulo.util.collection.Stack; import consulo.util.collection.*; import consulo.util.lang.ObjectUtil; import consulo.util.lang.ref.Ref; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; @@ -43,594 +43,656 @@ public final class DfaPsiUtil { - private static final CallMatcher NON_NULL_VAR_ARG = CallMatcher.anyOf( - staticCall(JAVA_UTIL_LIST, "of"), - staticCall(JAVA_UTIL_SET, "of"), - staticCall(JAVA_UTIL_MAP, "ofEntries")); - - public static boolean isFinalField(PsiVariable var) { - return var.hasModifierProperty(PsiModifier.FINAL) && !var.hasModifierProperty(PsiModifier.TRANSIENT) && var instanceof PsiField; - } - - @Nonnull - public static Nullability getElementNullability(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { - return getElementNullability(resultType, owner, false); - } - - @Nonnull - public static Nullability getElementNullabilityIgnoringParameterInference(@Nullable PsiType resultType, - @Nullable PsiModifierListOwner owner) { - return getElementNullability(resultType, owner, true); - } - - @Nonnull - private static Nullability getElementNullability(@Nullable PsiType resultType, - @Nullable PsiModifierListOwner owner, - boolean ignoreParameterNullabilityInference) { - if (owner == null) { - return getTypeNullability(resultType); - } + private static final CallMatcher NON_NULL_VAR_ARG = CallMatcher.anyOf( + staticCall(JAVA_UTIL_LIST, "of"), + staticCall(JAVA_UTIL_SET, "of"), + staticCall(JAVA_UTIL_MAP, "ofEntries")); + + public static boolean isFinalField(PsiVariable var) { + return var.hasModifierProperty(PsiModifier.FINAL) && !var.hasModifierProperty(PsiModifier.TRANSIENT) && var instanceof PsiField; + } + + /** + * Returns nullability of variable or method, when it's expected to read from it. + * This method takes into account various sources of nullability information, like method annotations, + * type annotations, container annotations, inferred annotations, or external annotations. + * Automatic inference of method parameter nullability is ignored, which is useful when analyzing the method body (as it's + * inferred from method body as well, so both analyses may produce conflicting results). + * + * @param resultType concrete type of particular variable access or method call (an instantiation of generic method return type, + * or variable type), if known. + * @param owner method or variable to get its nullability + * @return nullability of the owner; {@link Nullability#UNKNOWN} is both parameters are null. + */ + public static @NotNull Nullability getElementNullabilityForRead(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { + return getElementNullability(resultType, owner, true); + } + + public static Nullability getElementNullabilityIgnoringParameterInference(@Nullable PsiType resultType, + @Nullable PsiModifierListOwner owner) { + return getElementNullability(resultType, owner, true); + } + + + /** + * Returns nullability of variable or method. This method takes into account various sources of nullability information, + * like method annotations, type annotations, container annotations, inferred annotations, or external annotations. + * + * @param resultType concrete type of particular variable access or method call (an instantiation of generic method return type, + * or variable type), if known. + * @param owner method or variable to get its nullability + * @return nullability of the owner; {@link Nullability#UNKNOWN} is both parameters are null. + * @deprecated behaves like {@link #getElementNullabilityForWrite(PsiType, PsiModifierListOwner)}. + * Use either {@link #getElementNullabilityForWrite(PsiType, PsiModifierListOwner)} + * or {@link #getElementNullabilityForRead(PsiType, PsiModifierListOwner)} instead. + */ + @Deprecated + public static @NotNull Nullability getElementNullability(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { + return getElementNullability(resultType, owner, false); + } + + private static @NotNull Nullability getElementNullability(@Nullable PsiType resultType, + @Nullable PsiModifierListOwner owner, + boolean forRead) { + if (owner == null) return getTypeNullability(resultType, forRead); + + if (resultType instanceof PsiPrimitiveType) { + return Nullability.UNKNOWN; + } - if (resultType instanceof PsiPrimitiveType) { - return Nullability.UNKNOWN; - } + if (owner instanceof PsiEnumConstant) { + return Nullability.NOT_NULL; + } - if (owner instanceof PsiEnumConstant || PsiUtil.isAnnotationMethod(owner)) { - return Nullability.NOT_NULL; - } - if (owner instanceof PsiMethod && isEnumPredefinedMethod((PsiMethod) owner)) { - return Nullability.NOT_NULL; - } + // Annotation manager requires index + Project project = owner.getProject(); + if (DumbService.isDumb(project)) return Nullability.UNKNOWN; + NullabilityAnnotationInfo fromAnnotation = getNullabilityFromAnnotation(owner, forRead); + if (fromAnnotation != null) { + if (resultType != null && fromAnnotation.getNullability() != Nullability.NOT_NULL) { + PsiType type = PsiUtil.getTypeByPsiElement(owner); + if (type != null) { + PsiAnnotationOwner annotationOwner = fromAnnotation.getAnnotation().getOwner(); + if (PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter tp && + annotationOwner instanceof PsiType && annotationOwner != type && + !tp.equals(PsiUtil.resolveClassInClassTypeOnly(resultType))) { + // Nullable/Unknown from type hierarchy: should check the instantiation, as it could be more concrete + return getTypeNullability(resultType, forRead); + } + } + } + return fromAnnotation.getNullability(); + } - Nullability fromAnnotation = getNullabilityFromAnnotation(owner, ignoreParameterNullabilityInference); - if (fromAnnotation != Nullability.UNKNOWN) { - return fromAnnotation; - } + if (owner instanceof PsiMethod method) { + if (isEnumPredefinedMethod(method)) { + return Nullability.NOT_NULL; + } + if (isMapMethodWithUnknownNullity(method)) { + return getTypeNullability(resultType) == Nullability.NULLABLE ? Nullability.NULLABLE : Nullability.UNKNOWN; + } + } - if (owner instanceof PsiMethod && isMapMethodWithUnknownNullity((PsiMethod) owner)) { - return getTypeNullability(resultType) == Nullability.NULLABLE ? Nullability.NULLABLE : Nullability.UNKNOWN; - } + if (owner instanceof PsiParameter parameter) { + Nullability nullability = inferParameterNullability(parameter); + if (nullability != Nullability.UNKNOWN) { + return nullability; + } + } - Nullability fromType = getTypeNullability(resultType); - if (fromType != Nullability.UNKNOWN) { - return fromType; - } + Nullability fromType = getNullabilityFromType(resultType, owner); + if (fromType != null) return fromType; - if (owner instanceof PsiParameter) { - return inferParameterNullability((PsiParameter) owner); - } + if (owner instanceof PsiMethod method && method.getParameterList().isEmpty() && forRead) { + PsiField field = PropertyUtil.getFieldOfGetter(method); + if (field != null && getElementNullabilityForRead(resultType, field) == Nullability.NULLABLE) { + return Nullability.NULLABLE; + } + } - if (owner instanceof PsiMethod && ((PsiMethod) owner).getParameterList().isEmpty()) { - PsiField field = PropertyUtil.getFieldOfGetter((PsiMethod) owner); - if (field != null && getElementNullability(resultType, field) == Nullability.NULLABLE) { - return Nullability.NULLABLE; - } + return Nullability.UNKNOWN; + } + + private static @NotNull Nullability getTypeNullability(@Nullable PsiType type, boolean forRead) { + if (type == null) return Nullability.UNKNOWN; + if (type instanceof PsiCapturedWildcardType captured) { + if (!forRead) { + TypeNullability nullability = captured.getLowerBound().getNullability(); + if (nullability.source() instanceof NullabilitySource.ExtendsBound) { + PsiElement context = captured.getContext(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(context.getProject()); + if (manager != null) { + NullabilityAnnotationInfo defaultNullability = manager.findDefaultTypeUseNullability(context); + if (defaultNullability != null && defaultNullability.getNullability() == Nullability.NOT_NULL) { + return Nullability.NOT_NULL; + } + } + } + return nullability.nullability(); + } + } + return type.getNullability().nullability(); } - return Nullability.UNKNOWN; - } - - @Nonnull - private static Nullability getNullabilityFromAnnotation(PsiModifierListOwner owner, boolean ignoreParameterNullabilityInference) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); - if (info == null || shouldIgnoreAnnotation(info.getAnnotation())) { - return Nullability.UNKNOWN; - } - if (ignoreParameterNullabilityInference && owner instanceof PsiParameter && AnnotationUtil.isInferredAnnotation(info.getAnnotation())) { - List supers = AnnotationUtil.getSuperAnnotationOwners((PsiParameter) owner); - return ContainerUtil.exists(supers, each -> manager.isNullable(each, false)) ? Nullability.NULLABLE : Nullability.UNKNOWN; + private static @Nullable Nullability getNullabilityFromType(@Nullable PsiType resultType, @NotNull PsiModifierListOwner owner) { + if (resultType == null) return null; + TypeNullability typeNullability = resultType.getNullability(); + if (typeNullability.equals(TypeNullability.UNKNOWN)) return null; + Nullability fromType = typeNullability.nullability(); + if (fromType == Nullability.NOT_NULL && hasNullContract(owner)) { + return Nullability.UNKNOWN; + } + return fromType; } - return info.getNullability(); - } - private static boolean isMapMethodWithUnknownNullity(@Nonnull PsiMethod method) { - String name = method.getName(); - if (!"get".equals(name) && !"remove".equals(name)) { - return false; - } - PsiMethod superMethod = DeepestSuperMethodsSearch.search(method).findFirst(); - return ("java.util.Map." + name).equals(PsiUtil.getMemberQualifiedName(superMethod != null ? superMethod : method)); - } - - @Nonnull - public static Nullability inferParameterNullability(@Nonnull PsiParameter parameter) { - PsiElement parent = parameter.getParent(); - if (parent instanceof PsiParameterList) { - PsiElement gParent = parent.getParent(); - if (gParent instanceof PsiLambdaExpression) { - return getFunctionalParameterNullability((PsiLambdaExpression) gParent, ((PsiParameterList) parent).getParameterIndex(parameter)); - } else if (gParent instanceof PsiMethod && OptionalUtil.OPTIONAL_OF_NULLABLE.methodMatches((PsiMethod) gParent)) { - return Nullability.NULLABLE; - } - } - if (parent instanceof PsiForeachStatement) { - return getTypeNullability(inferLoopParameterTypeWithNullability((PsiForeachStatement) parent)); + private static boolean hasNullContract(@NotNull PsiModifierListOwner owner) { + if (owner instanceof PsiParameter parameter && parameter.getDeclarationScope() instanceof PsiMethod method) { + int index = method.getParameterList().getParameterIndex(parameter); + List contracts = JavaMethodContractUtil.getMethodContracts(method); + return ContainerUtil.exists(contracts, + c -> c.getParameterConstraint(index) == StandardMethodContract.ValueConstraint.NULL_VALUE); + } + return false; } - return Nullability.UNKNOWN; - } - - @Nullable - private static PsiType inferLoopParameterTypeWithNullability(PsiForeachStatement loop) { - PsiExpression iteratedValue = PsiUtil.skipParenthesizedExprDown(loop.getIteratedValue()); - if (iteratedValue == null) { - return null; + + private static @Nullable NullabilityAnnotationInfo getNullabilityFromAnnotation(@NotNull PsiModifierListOwner owner, + boolean ignoreParameterNullabilityInference) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + if (info == null || shouldIgnoreAnnotation(info.getAnnotation())) { + return null; + } + if (ignoreParameterNullabilityInference && owner instanceof PsiParameter && info.isInferred()) { + List supers = AnnotationUtil.getSuperAnnotationOwners((PsiParameter) owner); + return StreamEx.of(supers).map(param -> manager.findEffectiveNullabilityInfo(param)) + .findFirst(i -> i != null && i.getInheritedFrom() == null && i.getNullability() == Nullability.NULLABLE) + .orElse(null); + } + return info; } - PsiType iteratedType = iteratedValue.getType(); - if (iteratedValue instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) iteratedValue).resolve(); - if (target instanceof PsiParameter && target.getParent() instanceof PsiForeachStatement) { - PsiForeachStatement targetLoop = (PsiForeachStatement) target.getParent(); - if (PsiTreeUtil.isAncestor(targetLoop, loop, true) && - !HighlightControlFlowUtil.isReassigned((PsiParameter) target, new HashMap<>())) { - iteratedType = inferLoopParameterTypeWithNullability(targetLoop); + private static boolean isMapMethodWithUnknownNullity(PsiMethod method) { + String name = method.getName(); + if (!"get".equals(name) && !"remove".equals(name)) { + return false; } - } + PsiMethod superMethod = DeepestSuperMethodsSearch.search(method).findFirst(); + return ("java.util.Map." + name).equals(PsiUtil.getMemberQualifiedName(superMethod != null ? superMethod : method)); } - return JavaGenericsUtil.getCollectionItemType(iteratedType, iteratedValue.getResolveScope()); - } + public static Nullability inferParameterNullability(PsiParameter parameter) { + PsiElement parent = parameter.getParent(); + if (parent instanceof PsiParameterList) { + PsiElement gParent = parent.getParent(); + if (gParent instanceof PsiLambdaExpression) { + return getFunctionalParameterNullability((PsiLambdaExpression) gParent, ((PsiParameterList) parent).getParameterIndex(parameter)); + } + else if (gParent instanceof PsiMethod && OptionalUtil.OPTIONAL_OF_NULLABLE.methodMatches((PsiMethod) gParent)) { + return Nullability.NULLABLE; + } + } + if (parent instanceof PsiForeachStatement) { + return getTypeNullability(inferLoopParameterTypeWithNullability((PsiForeachStatement) parent)); + } + return Nullability.UNKNOWN; + } - @Nonnull - public static Nullability getTypeNullability(@Nullable PsiType type) { - NullabilityAnnotationInfo info = getTypeNullabilityInfo(type); - return info == null ? Nullability.UNKNOWN : info.getNullability(); - } + @Nullable + private static PsiType inferLoopParameterTypeWithNullability(PsiForeachStatement loop) { + PsiExpression iteratedValue = PsiUtil.skipParenthesizedExprDown(loop.getIteratedValue()); + if (iteratedValue == null) { + return null; + } - @Nullable - public static NullabilityAnnotationInfo getTypeNullabilityInfo(@Nullable PsiType type) { - if (type == null || type instanceof PsiPrimitiveType) { - return null; + PsiType iteratedType = iteratedValue.getType(); + if (iteratedValue instanceof PsiReferenceExpression) { + PsiElement target = ((PsiReferenceExpression) iteratedValue).resolve(); + if (target instanceof PsiParameter && target.getParent() instanceof PsiForeachStatement) { + PsiForeachStatement targetLoop = (PsiForeachStatement) target.getParent(); + if (PsiTreeUtil.isAncestor(targetLoop, loop, true) && + !HighlightControlFlowUtil.isReassigned((PsiParameter) target, new HashMap<>())) { + iteratedType = inferLoopParameterTypeWithNullability(targetLoop); + } + } + } + return JavaGenericsUtil.getCollectionItemType(iteratedType, iteratedValue.getResolveScope()); } - Ref result = Ref.create(null); - InheritanceUtil.processSuperTypes(type, true, eachType -> { - result.set(getTypeOwnNullability(eachType)); - return result.get() == null && - (!(type instanceof PsiClassType) || PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter); - }); - return result.get(); - } - - @Nullable - private static NullabilityAnnotationInfo getTypeOwnNullability(PsiType eachType) { - for (PsiAnnotation annotation : eachType.getAnnotations()) { - String qualifiedName = annotation.getQualifiedName(); - NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject()); - if (nnn.getNullables().contains(qualifiedName) && !shouldIgnoreAnnotation(annotation)) { - return new NullabilityAnnotationInfo(annotation, Nullability.NULLABLE, false); - } - if (nnn.getNotNulls().contains(qualifiedName)) { - return new NullabilityAnnotationInfo(annotation, Nullability.NOT_NULL, false); - } + public static @NotNull Nullability getTypeNullability(@Nullable PsiType type) { + if (type == null) return Nullability.UNKNOWN; + return type.getNullability().nullability(); } - if (eachType instanceof PsiClassType) { - PsiElement context = ((PsiClassType) eachType).getPsiContext(); - if (context != null) { - return NullableNotNullManager.getInstance(context.getProject()).findDefaultTypeUseNullability(context); - } - } - return null; - } - private static boolean shouldIgnoreAnnotation(PsiAnnotation annotation) { - PsiClass containingClass = ClassUtils.getContainingClass(annotation); - if (containingClass == null) { - return false; - } - String qualifiedName = containingClass.getQualifiedName(); - // We deliberately ignore nullability annotations on Guava functional interfaces to avoid noise warnings - // See IDEA-170548 for details - return "com.google.common.base.Predicate".equals(qualifiedName) || "com.google.common.base.Function".equals(qualifiedName); - } - - /** - * Returns the nullability of functional expression parameter - * - * @param function functional expression - * @param index parameter index - * @return nullability, defined by SAM parameter annotations or known otherwise - */ - @Nonnull - public static Nullability getFunctionalParameterNullability(PsiFunctionalExpression function, int index) { - Nullability nullability = inferLambdaParameterNullability(function, index); - if (nullability != Nullability.UNKNOWN) { - return nullability; - } - PsiClassType type = ObjectUtil.tryCast(LambdaUtil.getFunctionalInterfaceType(function, true), PsiClassType.class); - PsiMethod sam = LambdaUtil.getFunctionalInterfaceMethod(type); - if (sam != null) { - PsiParameter parameter = sam.getParameterList().getParameter(index); - if (parameter != null) { - nullability = getElementNullability(null, parameter); - if (nullability != Nullability.UNKNOWN) { - return nullability; - } - PsiType parameterType = type.resolveGenerics().getSubstitutor().substitute(parameter.getType()); - return getTypeNullability(GenericsUtil.eliminateWildcards(parameterType, false, true)); - } - } - return Nullability.UNKNOWN; - } - - @Nonnull - private static Nullability inferLambdaParameterNullability(PsiFunctionalExpression lambda, int parameterIndex) { - PsiElement expression = lambda; - PsiElement expressionParent = lambda.getParent(); - while (expressionParent instanceof PsiConditionalExpression || expressionParent instanceof PsiParenthesizedExpression) { - expression = expressionParent; - expressionParent = expressionParent.getParent(); - } - if (expressionParent instanceof PsiExpressionList) { - PsiExpressionList list = (PsiExpressionList) expressionParent; - PsiElement listParent = list.getParent(); - if (listParent instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression) listParent).resolveMethod(); - if (method != null) { - int expressionIndex = ArrayUtil.find(list.getExpressions(), expression); - return getLambdaParameterNullability(method, expressionIndex, parameterIndex); + @Nullable + public static NullabilityAnnotationInfo getTypeNullabilityInfo(@Nullable PsiType type) { + if (type == null || type instanceof PsiPrimitiveType) { + return null; } - } - } - return Nullability.UNKNOWN; - } - - @Nonnull - private static Nullability getLambdaParameterNullability(@Nonnull PsiMethod method, int parameterIndex, int lambdaParameterIndex) { - PsiClass type = method.getContainingClass(); - if (type != null) { - if (JAVA_UTIL_OPTIONAL.equals(type.getQualifiedName())) { - String methodName = method.getName(); - if ((methodName.equals("map") || methodName.equals("filter") || methodName.equals("ifPresent") || methodName.equals("flatMap")) - && parameterIndex == 0 && lambdaParameterIndex == 0) { - return Nullability.NOT_NULL; - } - } - } - return Nullability.UNKNOWN; - } - private static boolean isEnumPredefinedMethod(PsiMethod method) { - return CallMatcher.enumValueOf().methodMatches(method) || CallMatcher.enumValues().methodMatches(method); - } + Ref result = Ref.create(null); + InheritanceUtil.processSuperTypes(type, true, eachType -> { + result.set(getTypeOwnNullability(eachType)); + return result.get() == null && + (!(type instanceof PsiClassType) || PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter); + }); + return result.get(); + } - public static boolean isInitializedNotNull(PsiField field) { - PsiClass containingClass = field.getContainingClass(); - if (containingClass == null) { - return false; + @Nullable + private static NullabilityAnnotationInfo getTypeOwnNullability(PsiType eachType) { + for (PsiAnnotation annotation : eachType.getAnnotations()) { + String qualifiedName = annotation.getQualifiedName(); + NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject()); + if (nnn.getNullables().contains(qualifiedName) && !shouldIgnoreAnnotation(annotation)) { + return new NullabilityAnnotationInfo(annotation, Nullability.NULLABLE, false); + } + if (nnn.getNotNulls().contains(qualifiedName)) { + return new NullabilityAnnotationInfo(annotation, Nullability.NOT_NULL, false); + } + } + if (eachType instanceof PsiClassType) { + PsiElement context = ((PsiClassType) eachType).getPsiContext(); + if (context != null) { + return NullableNotNullManager.getInstance(context.getProject()).findDefaultTypeUseNullability(context); + } + } + return null; } - PsiMethod[] constructors = containingClass.getConstructors(); - if (constructors.length == 0) { - return false; + private static boolean shouldIgnoreAnnotation(PsiAnnotation annotation) { + PsiClass containingClass = ClassUtils.getContainingClass(annotation); + if (containingClass == null) { + return false; + } + String qualifiedName = containingClass.getQualifiedName(); + // We deliberately ignore nullability annotations on Guava functional interfaces to avoid noise warnings + // See IDEA-170548 for details + return "com.google.common.base.Predicate".equals(qualifiedName) || "com.google.common.base.Function".equals(qualifiedName); + } + + /** + * Returns the nullability of functional expression parameter + * + * @param function functional expression + * @param index parameter index + * @return nullability, defined by SAM parameter annotations or known otherwise + */ + public static Nullability getFunctionalParameterNullability(PsiFunctionalExpression function, int index) { + PsiClassType type = ObjectUtil.tryCast(LambdaUtil.getFunctionalInterfaceType(function, true), PsiClassType.class); + PsiMethod sam = LambdaUtil.getFunctionalInterfaceMethod(type); + if (sam != null) { + PsiParameter parameter = sam.getParameterList().getParameter(index); + if (parameter != null) { + PsiType parameterType = type.resolveGenerics().getSubstitutor().substitute(parameter.getType()); + if (parameterType instanceof PsiWildcardType wildcardType) { + parameterType = wildcardType.getBound(); + } + if (parameterType != null) { + TypeNullability typeNullability = parameterType.getNullability(); + if (!typeNullability.equals(TypeNullability.UNKNOWN)) { + return typeNullability.nullability(); + } + } + return getElementNullabilityForWrite(null, parameter); + } + } + return Nullability.UNKNOWN; } - for (PsiMethod method : constructors) { - if (!getNotNullInitializedFields(method, containingClass).contains(field)) { - return false; - } + /** + * Returns nullability of variable when it's expected to write to it, or nullability of method + * when it's expected to return a value from it. + * For method parameter, it's expected that either the method is called (so the parameter is written by a call), + * or the parameter is modified inside the method, like a local variable. + * This method takes into account various sources of nullability information, + * like method annotations, type annotations, container annotations, inferred annotations, or external annotations. + * + * @param resultType concrete type of particular variable access or method call (an instantiation of generic method return type, + * or variable type), if known. + * @param owner method or variable to get its nullability + * @return nullability of the owner; {@link Nullability#UNKNOWN} is both parameters are null. + */ + public static @NotNull Nullability getElementNullabilityForWrite(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { + return getElementNullability(resultType, owner, false); } - return true; - } - private static Set getNotNullInitializedFields(final PsiMethod constructor, final PsiClass containingClass) { - if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { - return Collections.emptySet(); + private static boolean isEnumPredefinedMethod(PsiMethod method) { + return CallMatcher.enumValueOf().methodMatches(method) || CallMatcher.enumValues().methodMatches(method); } - final PsiCodeBlock body = constructor.getBody(); - if (body == null) { - return Collections.emptySet(); + public static boolean isInitializedNotNull(PsiField field) { + PsiClass containingClass = field.getContainingClass(); + if (containingClass == null) { + return false; + } + + PsiMethod[] constructors = containingClass.getConstructors(); + if (constructors.length == 0) { + return false; + } + + for (PsiMethod method : constructors) { + if (!getNotNullInitializedFields(method, containingClass).contains(field)) { + return false; + } + } + return true; } - return LanguageCachedValueUtil.getCachedValue(constructor, new CachedValueProvider<>() { - @Nonnull - @Override - public Result> compute() { + private static Set getNotNullInitializedFields(final PsiMethod constructor, final PsiClass containingClass) { + if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { + return Collections.emptySet(); + } + final PsiCodeBlock body = constructor.getBody(); - final Map map = new HashMap<>(); - final DataFlowRunner dfaRunner = new DataFlowRunner(constructor.getProject()) { - PsiElement currentBlock; + if (body == null) { + return Collections.emptySet(); + } - private boolean isCallExposingNonInitializedFields(Instruction instruction) { - if (!(instruction instanceof MethodCallInstruction)) { - return false; + return LanguageCachedValueUtil.getCachedValue(constructor, new CachedValueProvider<>() { + @Override + public Result> compute() { + final PsiCodeBlock body = constructor.getBody(); + final Map map = new HashMap<>(); + final DataFlowRunner dfaRunner = new DataFlowRunner(constructor.getProject()) { + PsiElement currentBlock; + + private boolean isCallExposingNonInitializedFields(Instruction instruction) { + if (!(instruction instanceof MethodCallInstruction)) { + return false; + } + + PsiCall call = ((MethodCallInstruction) instruction).getCallExpression(); + if (call == null) { + return false; + } + + if (call instanceof PsiNewExpression && canAccessFields((PsiExpression) call)) { + return true; + } + + if (call instanceof PsiMethodCallExpression) { + PsiExpression qualifier = ((PsiMethodCallExpression) call).getMethodExpression().getQualifierExpression(); + if (qualifier == null || canAccessFields(qualifier)) { + return true; + } + } + + PsiExpressionList argumentList = call.getArgumentList(); + if (argumentList != null) { + for (PsiExpression expression : argumentList.getExpressions()) { + if (canAccessFields(expression)) { + return true; + } + } + } + + return false; + } + + private boolean canAccessFields(PsiExpression expression) { + PsiClass type = PsiUtil.resolveClassInClassTypeOnly(expression.getType()); + JBIterable typeContainers = + JBIterable.generate(type, PsiClass::getContainingClass).takeWhile(c -> !c.hasModifierProperty(PsiModifier.STATIC)); + return typeContainers.contains(containingClass); + } + + @Override + protected List createInitialInstructionStates(PsiElement psiBlock, + Collection memStates, + ControlFlow flow) { + currentBlock = psiBlock; + return super.createInitialInstructionStates(psiBlock, memStates, flow); + } + + @Override + protected DfaInstructionState[] acceptInstruction(InstructionVisitor visitor, + DfaInstructionState instructionState) { + Instruction instruction = instructionState.getInstruction(); + if (currentBlock == body && + (isCallExposingNonInitializedFields(instruction) || + instruction instanceof ReturnInstruction && !((ReturnInstruction) instruction).isViaException())) { + for (PsiField field : containingClass.getFields()) { + if (!instructionState.getMemoryState().isNotNull(getFactory().getVarFactory().createVariableValue(field))) { + map.put(field, false); + } + else if (!map.containsKey(field)) { + map.put(field, true); + } + } + return DfaInstructionState.EMPTY_ARRAY; + } + return super.acceptInstruction(visitor, instructionState); + } + }; + final RunnerResult rc = dfaRunner.analyzeMethod(body, new StandardInstructionVisitor()); + Set notNullFields = new HashSet<>(); + if (rc == RunnerResult.OK) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue()) { + notNullFields.add(entry.getKey()); + } + } + } + return Result.create(notNullFields, constructor, PsiModificationTracker.MODIFICATION_COUNT); } + }); + } - PsiCall call = ((MethodCallInstruction) instruction).getCallExpression(); - if (call == null) { - return false; - } + public static List findAllConstructorInitializers(PsiField field) { + final List result = Lists.newLockFreeCopyOnWriteList(); + ContainerUtil.addIfNotNull(result, field.getInitializer()); - if (call instanceof PsiNewExpression && canAccessFields((PsiExpression) call)) { - return true; - } + final PsiClass containingClass = field.getContainingClass(); + if (containingClass != null && !(containingClass instanceof PsiCompiledElement)) { + result.addAll(getAllConstructorFieldInitializers(containingClass).get(field)); + } + return result; + } - if (call instanceof PsiMethodCallExpression) { - PsiExpression qualifier = ((PsiMethodCallExpression) call).getMethodExpression().getQualifierExpression(); - if (qualifier == null || canAccessFields(qualifier)) { - return true; - } - } + private static MultiMap getAllConstructorFieldInitializers(final PsiClass psiClass) { + if (psiClass instanceof PsiCompiledElement) { + return MultiMap.empty(); + } - PsiExpressionList argumentList = call.getArgumentList(); - if (argumentList != null) { - for (PsiExpression expression : argumentList.getExpressions()) { - if (canAccessFields(expression)) { - return true; + return LanguageCachedValueUtil.getCachedValue(psiClass, new CachedValueProvider<>() { + @Override + public Result> compute() { + final Set fieldNames = new HashSet<>(); + for (PsiField field : psiClass.getFields()) { + ContainerUtil.addIfNotNull(fieldNames, field.getName()); } - } - } - return false; - } - - private boolean canAccessFields(PsiExpression expression) { - PsiClass type = PsiUtil.resolveClassInClassTypeOnly(expression.getType()); - JBIterable typeContainers = - JBIterable.generate(type, PsiClass::getContainingClass).takeWhile(c -> !c.hasModifierProperty(PsiModifier.STATIC)); - return typeContainers.contains(containingClass); - } - - @Override - protected - @Nonnull - List createInitialInstructionStates(@Nonnull PsiElement psiBlock, - @Nonnull Collection memStates, - @Nonnull ControlFlow flow) { - currentBlock = psiBlock; - return super.createInitialInstructionStates(psiBlock, memStates, flow); - } - - @Override - protected DfaInstructionState[] acceptInstruction(@Nonnull InstructionVisitor visitor, - @Nonnull DfaInstructionState instructionState) { - Instruction instruction = instructionState.getInstruction(); - if (currentBlock == body && - (isCallExposingNonInitializedFields(instruction) || - instruction instanceof ReturnInstruction && !((ReturnInstruction) instruction).isViaException())) { - for (PsiField field : containingClass.getFields()) { - if (!instructionState.getMemoryState().isNotNull(getFactory().getVarFactory().createVariableValue(field))) { - map.put(field, false); - } else if (!map.containsKey(field)) { - map.put(field, true); + final MultiMap result = new MultiMap<>(); + JavaRecursiveElementWalkingVisitor visitor = new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitAssignmentExpression(PsiAssignmentExpression assignment) { + super.visitAssignmentExpression(assignment); + PsiExpression lExpression = assignment.getLExpression(); + PsiExpression rExpression = assignment.getRExpression(); + if (rExpression != null && + lExpression instanceof PsiReferenceExpression && + fieldNames.contains(((PsiReferenceExpression) lExpression).getReferenceName())) { + PsiElement target = ((PsiReferenceExpression) lExpression).resolve(); + if (target instanceof PsiField && ((PsiField) target).getContainingClass() == psiClass) { + result.putValue((PsiField) target, rExpression); + } + } + } + }; + + for (PsiMethod constructor : psiClass.getConstructors()) { + if (constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { + constructor.accept(visitor); + } } - } - return DfaInstructionState.EMPTY_ARRAY; - } - return super.acceptInstruction(visitor, instructionState); - } - }; - final RunnerResult rc = dfaRunner.analyzeMethod(body, new StandardInstructionVisitor()); - Set notNullFields = new HashSet<>(); - if (rc == RunnerResult.OK) { - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue()) { - notNullFields.add(entry.getKey()); - } - } - } - return Result.create(notNullFields, constructor, PsiModificationTracker.MODIFICATION_COUNT); - } - }); - } - public static List findAllConstructorInitializers(PsiField field) { - final List result = Lists.newLockFreeCopyOnWriteList(); - ContainerUtil.addIfNotNull(result, field.getInitializer()); + return Result.create(result, psiClass); + } + }); + } - final PsiClass containingClass = field.getContainingClass(); - if (containingClass != null && !(containingClass instanceof PsiCompiledElement)) { - result.addAll(getAllConstructorFieldInitializers(containingClass).get(field)); + @Nullable + public static PsiElement getTopmostBlockInSameClass(PsiElement position) { + return JBIterable. + generate(position, PsiElement::getParent). + takeWhile(e -> !(e instanceof PsiMember || e instanceof PsiFile || e instanceof PsiLambdaExpression)). + filter(e -> e instanceof PsiCodeBlock || e instanceof PsiExpression && e.getParent() instanceof PsiLambdaExpression). + last(); + } + + public static Collection getVariableAssignmentsInFile(PsiVariable psiVariable, + final boolean literalsOnly, + final PsiElement place) { + Ref modificationRef = Ref.create(Boolean.FALSE); + PsiElement codeBlock = place == null ? null : getTopmostBlockInSameClass(place); + int placeOffset = codeBlock != null ? place.getTextRange().getStartOffset() : 0; + PsiFile containingFile = psiVariable.getContainingFile(); + LocalSearchScope scope = new LocalSearchScope(new PsiElement[]{containingFile}, null, true); + Collection references = ReferencesSearch.search(psiVariable, scope).findAll(); + List list = ContainerUtil.mapNotNull( + references, + (Function) psiReference -> { + if (modificationRef.get()) { + return null; + } + final PsiElement parent = psiReference.getElement().getParent(); + if (parent instanceof PsiAssignmentExpression) { + final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression) parent; + final IElementType operation = assignmentExpression.getOperationTokenType(); + if (assignmentExpression.getLExpression() == psiReference) { + if (JavaTokenType.EQ.equals(operation)) { + final PsiExpression rValue = assignmentExpression.getRExpression(); + if (!literalsOnly || allOperandsAreLiterals(rValue)) { + // if there's a codeBlock omit the values assigned later + if (PsiTreeUtil.isAncestor(codeBlock, parent, true) + && placeOffset < parent.getTextRange().getStartOffset()) { + return null; + } + return rValue; + } + else { + modificationRef.set(Boolean.TRUE); + } + } + else if (JavaTokenType.PLUSEQ.equals(operation)) { + modificationRef.set(Boolean.TRUE); + } + } + } + return null; + }); + if (modificationRef.get()) { + return Collections.emptyList(); + } + PsiExpression initializer = psiVariable.getInitializer(); + if (initializer != null && (!literalsOnly || allOperandsAreLiterals(initializer))) { + list = ContainerUtil.concat(list, Collections.singletonList(initializer)); + } + return list; } - return result; - } - private static MultiMap getAllConstructorFieldInitializers(final PsiClass psiClass) { - if (psiClass instanceof PsiCompiledElement) { - return MultiMap.empty(); + private static boolean allOperandsAreLiterals(@Nullable final PsiExpression expression) { + if (expression == null) { + return false; + } + if (expression instanceof PsiLiteralExpression) { + return true; + } + if (expression instanceof PsiPolyadicExpression) { + Stack stack = new Stack<>(); + stack.add(expression); + while (!stack.isEmpty()) { + PsiExpression psiExpression = stack.pop(); + if (psiExpression instanceof PsiPolyadicExpression) { + PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression) psiExpression; + for (PsiExpression op : binaryExpression.getOperands()) { + stack.push(op); + } + } + else if (!(psiExpression instanceof PsiLiteralExpression)) { + return false; + } + } + return true; + } + return false; } - return LanguageCachedValueUtil.getCachedValue(psiClass, new CachedValueProvider<>() { - @Nonnull - @Override - public Result> compute() { - final Set fieldNames = new HashSet<>(); - for (PsiField field : psiClass.getFields()) { - ContainerUtil.addIfNotNull(fieldNames, field.getName()); - } - - final MultiMap result = new MultiMap<>(); - JavaRecursiveElementWalkingVisitor visitor = new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitAssignmentExpression(PsiAssignmentExpression assignment) { - super.visitAssignmentExpression(assignment); - PsiExpression lExpression = assignment.getLExpression(); - PsiExpression rExpression = assignment.getRExpression(); - if (rExpression != null && - lExpression instanceof PsiReferenceExpression && - fieldNames.contains(((PsiReferenceExpression) lExpression).getReferenceName())) { - PsiElement target = ((PsiReferenceExpression) lExpression).resolve(); - if (target instanceof PsiField && ((PsiField) target).getContainingClass() == psiClass) { - result.putValue((PsiField) target, rExpression); - } + /** + * @param method method to check + * @return nullability of vararg parameter component; {@link Nullability#UNKNOWN} if not specified or method is not vararg method. + */ + static Nullability getVarArgComponentNullability(PsiMethod method) { + if (method != null) { + if (NON_NULL_VAR_ARG.methodMatches(method)) { + return Nullability.NOT_NULL; } - } - }; - - for (PsiMethod constructor : psiClass.getConstructors()) { - if (constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { - constructor.accept(visitor); - } - } - - return Result.create(result, psiClass); - } - }); - } - - @Nullable - public static PsiElement getTopmostBlockInSameClass(@Nonnull PsiElement position) { - return JBIterable. - generate(position, PsiElement::getParent). - takeWhile(e -> !(e instanceof PsiMember || e instanceof PsiFile || e instanceof PsiLambdaExpression)). - filter(e -> e instanceof PsiCodeBlock || e instanceof PsiExpression && e.getParent() instanceof PsiLambdaExpression). - last(); - } - - @Nonnull - public static Collection getVariableAssignmentsInFile(@Nonnull PsiVariable psiVariable, - final boolean literalsOnly, - final PsiElement place) { - Ref modificationRef = Ref.create(Boolean.FALSE); - PsiElement codeBlock = place == null ? null : getTopmostBlockInSameClass(place); - int placeOffset = codeBlock != null ? place.getTextRange().getStartOffset() : 0; - PsiFile containingFile = psiVariable.getContainingFile(); - LocalSearchScope scope = new LocalSearchScope(new PsiElement[]{containingFile}, null, true); - Collection references = ReferencesSearch.search(psiVariable, scope).findAll(); - List list = ContainerUtil.mapNotNull( - references, - (Function) psiReference -> { - if (modificationRef.get()) { - return null; - } - final PsiElement parent = psiReference.getElement().getParent(); - if (parent instanceof PsiAssignmentExpression) { - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression) parent; - final IElementType operation = assignmentExpression.getOperationTokenType(); - if (assignmentExpression.getLExpression() == psiReference) { - if (JavaTokenType.EQ.equals(operation)) { - final PsiExpression rValue = assignmentExpression.getRExpression(); - if (!literalsOnly || allOperandsAreLiterals(rValue)) { - // if there's a codeBlock omit the values assigned later - if (PsiTreeUtil.isAncestor(codeBlock, parent, true) - && placeOffset < parent.getTextRange().getStartOffset()) { - return null; - } - return rValue; - } else { - modificationRef.set(Boolean.TRUE); + PsiParameter varArg = ArrayUtil.getLastElement(method.getParameterList().getParameters()); + if (varArg != null) { + PsiType type = varArg.getType(); + if (type instanceof PsiEllipsisType) { + PsiType componentType = ((PsiEllipsisType) type).getComponentType(); + return getTypeNullability(componentType); } - } else if (JavaTokenType.PLUSEQ.equals(operation)) { - modificationRef.set(Boolean.TRUE); - } } - } - return null; - }); - if (modificationRef.get()) { - return Collections.emptyList(); - } - PsiExpression initializer = psiVariable.getInitializer(); - if (initializer != null && (!literalsOnly || allOperandsAreLiterals(initializer))) { - list = ContainerUtil.concat(list, Collections.singletonList(initializer)); + } + return Nullability.UNKNOWN; + } + + /** + * Try to restore type parameters based on the expression type + * + * @param expression expression which type is a supertype of the type to generify + * @param type a type to generify + * @return a generified type, or original type if generification is not possible + */ + public static PsiType tryGenerify(PsiExpression expression, PsiType type) { + if (!(type instanceof PsiClassType)) { + return type; + } + PsiClassType classType = (PsiClassType) type; + if (!classType.isRaw()) { + return classType; + } + PsiClass psiClass = classType.resolve(); + if (psiClass == null) { + return classType; + } + PsiType expressionType = expression.getType(); + if (!(expressionType instanceof PsiClassType)) { + return classType; + } + PsiClassType result = GenericsUtil.getExpectedGenericType(expression, psiClass, (PsiClassType) expressionType); + if (result.isRaw()) { + PsiClass aClass = result.resolve(); + if (aClass != null) { + int length = aClass.getTypeParameters().length; + PsiWildcardType wildcard = PsiWildcardType.createUnbounded(aClass.getManager()); + PsiType[] arguments = new PsiType[length]; + Arrays.fill(arguments, wildcard); + return JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass, arguments); + } + } + return result; } - return list; - } - private static boolean allOperandsAreLiterals(@Nullable final PsiExpression expression) { - if (expression == null) { - return false; - } - if (expression instanceof PsiLiteralExpression) { - return true; - } - if (expression instanceof PsiPolyadicExpression) { - Stack stack = new Stack<>(); - stack.add(expression); - while (!stack.isEmpty()) { - PsiExpression psiExpression = stack.pop(); - if (psiExpression instanceof PsiPolyadicExpression) { - PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression) psiExpression; - for (PsiExpression op : binaryExpression.getOperands()) { - stack.push(op); - } - } else if (!(psiExpression instanceof PsiLiteralExpression)) { - return false; - } - } - return true; - } - return false; - } - - /** - * @param method method to check - * @return nullability of vararg parameter component; {@link Nullability#UNKNOWN} if not specified or method is not vararg method. - */ - @Nonnull - static Nullability getVarArgComponentNullability(PsiMethod method) { - if (method != null) { - if (NON_NULL_VAR_ARG.methodMatches(method)) { - return Nullability.NOT_NULL; - } - PsiParameter varArg = ArrayUtil.getLastElement(method.getParameterList().getParameters()); - if (varArg != null) { - PsiType type = varArg.getType(); - if (type instanceof PsiEllipsisType) { - PsiType componentType = ((PsiEllipsisType) type).getComponentType(); - return getTypeNullability(componentType); - } - } - } - return Nullability.UNKNOWN; - } - - /** - * Try to restore type parameters based on the expression type - * - * @param expression expression which type is a supertype of the type to generify - * @param type a type to generify - * @return a generified type, or original type if generification is not possible - */ - public static PsiType tryGenerify(PsiExpression expression, PsiType type) { - if (!(type instanceof PsiClassType)) { - return type; - } - PsiClassType classType = (PsiClassType) type; - if (!classType.isRaw()) { - return classType; - } - PsiClass psiClass = classType.resolve(); - if (psiClass == null) { - return classType; - } - PsiType expressionType = expression.getType(); - if (!(expressionType instanceof PsiClassType)) { - return classType; - } - PsiClassType result = GenericsUtil.getExpectedGenericType(expression, psiClass, (PsiClassType) expressionType); - if (result.isRaw()) { - PsiClass aClass = result.resolve(); - if (aClass != null) { - int length = aClass.getTypeParameters().length; - PsiWildcardType wildcard = PsiWildcardType.createUnbounded(aClass.getManager()); - PsiType[] arguments = new PsiType[length]; - Arrays.fill(arguments, wildcard); - return JavaPsiFacade.getElementFactory(aClass.getProject()).createType(aClass, arguments); - } - } - return result; - } - - /** - * @param expr literal to create a constant type from - * @return a DfType that describes given literal - */ - @Nonnull - public static DfType fromLiteral(@Nonnull PsiLiteralExpression expr) { - PsiType type = expr.getType(); - if (type == null) { - return DfTypes.TOP; - } - if (PsiType.NULL.equals(type)) { - return DfTypes.NULL; - } - Object value = expr.getValue(); - if (value == null) { - return DfTypes.typedObject(type, Nullability.NOT_NULL); + /** + * @param expr literal to create a constant type from + * @return a DfType that describes given literal + */ + public static DfType fromLiteral(PsiLiteralExpression expr) { + PsiType type = expr.getType(); + if (type == null) { + return DfTypes.TOP; + } + if (PsiType.NULL.equals(type)) { + return DfTypes.NULL; + } + Object value = expr.getValue(); + if (value == null) { + return DfTypes.typedObject(type, Nullability.NOT_NULL); + } + return DfTypes.constant(value, type); } - return DfTypes.constant(value, type); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaUtil.java index aa2e78e117..a0a25b943b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DfaUtil.java @@ -1,27 +1,26 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfReferenceType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.*; import com.intellij.java.analysis.impl.psi.controlFlow.DefUseUtil; import com.intellij.java.language.codeInsight.Nullability; -import com.intellij.java.language.impl.codeInsight.ExpressionUtil; import com.intellij.java.language.impl.psi.impl.source.resolve.JavaResolveUtil; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.ExpressionUtil; import com.intellij.java.language.psi.util.PsiPrecedenceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.psiutils.ExpressionUtils; -import com.siyeh.ig.psiutils.ParenthesesUtils; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.Predicate; @@ -34,7 +33,6 @@ public final class DfaUtil { public static - @Nonnull Collection getVariableValues(@Nullable PsiVariable variable, @Nullable PsiElement context) { if (variable == null || context == null) { return Collections.emptyList(); @@ -134,7 +132,6 @@ private static PsiExpression unrollConcatenation(PsiAssignmentExpression assignm */ //@ApiStatus.ScheduledForRemoval(inVersion = "2020.2") @Deprecated(forRemoval = true) - @Nonnull public static Nullability checkNullability(final @Nullable PsiVariable variable, final @Nullable PsiElement context) { if (context instanceof PsiExpression) { return NullabilityUtil.getExpressionNullability((PsiExpression) context, true); @@ -143,8 +140,7 @@ public static Nullability checkNullability(final @Nullable PsiVariable variable, } public static - @Nonnull - Collection getPossibleInitializationElements(@Nonnull PsiElement qualifierExpression) { + Collection getPossibleInitializationElements(PsiElement qualifierExpression) { if (qualifierExpression instanceof PsiMethodCallExpression) { return Collections.singletonList((PsiMethodCallExpression) qualifierExpression); } @@ -166,7 +162,6 @@ Collection getPossibleInitializationElements(@Nonnull PsiElement } public static - @Nonnull Nullability inferMethodNullability(PsiMethod method) { if (PsiUtil.resolveClassInType(method.getReturnType()) == null) { return Nullability.UNKNOWN; @@ -189,7 +184,6 @@ private static boolean suppressNullable(PsiMethod method) { } public static - @Nonnull Nullability inferLambdaNullability(PsiLambdaExpression lambda) { if (LambdaUtil.getFunctionalInterfaceReturnType(lambda) == null) { return Nullability.UNKNOWN; @@ -199,8 +193,7 @@ Nullability inferLambdaNullability(PsiLambdaExpression lambda) { } private static - @Nonnull - Nullability inferBlockNullability(@Nonnull PsiParameterListOwner owner, boolean suppressNullable) { + Nullability inferBlockNullability(PsiParameterListOwner owner, boolean suppressNullable) { PsiElement body = owner.getBody(); if (body == null) { return Nullability.UNKNOWN; @@ -213,10 +206,10 @@ class BlockNullabilityVisitor extends StandardInstructionVisitor { boolean hasUnknowns = false; @Override - protected void checkReturnValue(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nonnull PsiParameterListOwner context, - @Nonnull DfaMemoryState state) { + protected void checkReturnValue(DfaValue value, + PsiExpression expression, + PsiParameterListOwner context, + DfaMemoryState state) { if (context == owner) { if (TypeConversionUtil.isPrimitiveAndNotNull(expression.getType()) || state.isNotNull(value)) { hasNotNulls = true; @@ -243,7 +236,7 @@ protected void checkReturnValue(@Nonnull DfaValue value, return Nullability.UNKNOWN; } - static DfaValue getPossiblyNonInitializedValue(@Nonnull DfaValueFactory factory, @Nonnull PsiField target, @Nonnull PsiElement context) { + static DfaValue getPossiblyNonInitializedValue(DfaValueFactory factory, PsiField target, PsiElement context) { if (target.getType() instanceof PsiPrimitiveType) { return null; } @@ -341,7 +334,7 @@ private static int getAccessOffset(PsiMethod referrer) { return Integer.MAX_VALUE; // accessed after initialization or at unknown moment } - public static boolean hasInitializationHacks(@Nonnull PsiField field) { + public static boolean hasInitializationHacks(PsiField field) { PsiClass containingClass = field.getContainingClass(); return containingClass != null && System.class.getName().equals(containingClass.getQualifiedName()); } @@ -433,9 +426,8 @@ public static DfaValue boxUnbox(DfaValue value, @Nullable PsiType type) { } public static - @Nonnull List addRangeContracts(@Nullable PsiMethod method, - @Nonnull List contracts) { + List contracts) { if (method == null) { return contracts; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DistinctPairSet.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DistinctPairSet.java index 484539ccd2..f86f35a8ec 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DistinctPairSet.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/DistinctPairSet.java @@ -8,9 +8,8 @@ import consulo.util.collection.primitive.longs.LongLists; import consulo.util.collection.primitive.longs.LongSet; import consulo.util.collection.primitive.longs.LongSets; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.AbstractSet; import java.util.Iterator; import java.util.List; @@ -307,7 +306,6 @@ private DistinctPair(int first, int second, boolean ordered, List list) } public - @Nonnull EqClass getFirst() { return myList.get(myFirst); @@ -319,7 +317,6 @@ public int getFirstIndex() } public - @Nonnull EqClass getSecond() { return myList.get(mySecond); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/EqClass.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/EqClass.java index 67c813354d..aff18cb4b5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/EqClass.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/EqClass.java @@ -4,10 +4,9 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; /** @@ -36,7 +35,7 @@ class EqClass extends SortedIntSet implements Iterable myFactory = factory; } - EqClass(@Nonnull EqClass toCopy) + EqClass(EqClass toCopy) { super(toCopy.toNativeArray()); myFactory = toCopy.myFactory; @@ -94,7 +93,6 @@ DfaVariableValue getCanonicalVariable() return StreamEx.of(iterator()).min(CANONICAL_VARIABLE_COMPARATOR).orElse(null); } - @Nonnull @Override public Iterator iterator() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/HardcodedContracts.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/HardcodedContracts.java index b479910ddb..4d19fa0cc7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/HardcodedContracts.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/HardcodedContracts.java @@ -14,9 +14,8 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.SyntaxTraverser; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.ContractReturnValue.*; @@ -149,7 +148,6 @@ static ContractProvider of(MethodContract... contracts) { .register(staticCall("java.lang.System", "arraycopy"), expression -> getArraycopyContract()); private static - @Nonnull ContractProvider getArraycopyContract() { ContractValue src = ContractValue.argument(0); ContractValue srcPos = ContractValue.argument(1); @@ -169,7 +167,7 @@ ContractProvider getArraycopyContract() { ); } - public static List getHardcodedContracts(@Nonnull PsiMethod method, @Nullable PsiMethodCallExpression call) { + public static List getHardcodedContracts(PsiMethod method, @Nullable PsiMethodCallExpression call) { PsiClass owner = method.getContainingClass(); if (owner == null) { return Collections.emptyList(); @@ -234,7 +232,6 @@ public static List getHardcodedContracts(@Nonnull PsiMethod meth } private static - @Nonnull List getSubstringContracts(boolean endLimited) { List contracts = new ArrayList<>(3); contracts.add(specialFieldRangeContract(0, RelationType.LE, SpecialField.STRING_LENGTH)); @@ -422,8 +419,7 @@ ValueConstraint constraintFromLiteral(PsiExpression arg, boolean negate) { } private static - @Nonnull - List handleAssertThat(int paramCount, @Nonnull PsiMethodCallExpression call) { + List handleAssertThat(int paramCount, PsiMethodCallExpression call) { PsiExpression[] args = call.getArgumentList().getExpressions(); if (args.length == paramCount) { for (int i = 1; i < args.length; i++) { @@ -498,7 +494,6 @@ MethodContract emptyCheck(PsiType type, boolean isEmpty) { } private static - @Nonnull List failIfNull(int argIndex, int argCount, boolean returnArg) { ValueConstraint[] constraints = createConstraintArray(argCount); constraints[argIndex] = NULL_VALUE; @@ -527,7 +522,7 @@ public static MutationSignature getHardcodedMutation(PsiMethod method) { } String name = method.getName(); - if ("java.util.Objects".equals(className) && "requireNonNull".equals(name)) { + if (CommonClassNames.JAVA_UTIL_OBJECTS.equals(className) && "requireNonNull".equals(name)) { PsiParameter[] parameters = method.getParameterList().getParameters(); if (parameters.length == 2 && parameters[1].getType().getCanonicalText().contains("Supplier")) { return MutationSignature.unknown(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionTransfer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionTransfer.java index f8e9cb6203..dacb64efe7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionTransfer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionTransfer.java @@ -18,7 +18,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -47,7 +46,6 @@ public List dispatch(DfaMemoryState state, DataFlowRunner r return List.of(new DfaInstructionState(runner.getInstruction(offset.getInstructionOffset()), state)); } - @Nonnull @Override public Collection getPossibleTargets() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionVisitor.java index 1875bb99c2..07a17adf73 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/InstructionVisitor.java @@ -26,8 +26,7 @@ import consulo.util.collection.ArrayUtil; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Objects; @@ -45,10 +44,10 @@ public abstract class InstructionVisitor { * @param range if not-null, specifies a part of expression which corresponds to the value (like "a ^ b" range in "a ^ b ^ c" expression). * @param state a memory state where expression is about to be pushed */ - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, + protected void beforeExpressionPush(DfaValue value, + PsiExpression expression, @Nullable TextRange range, - @Nonnull DfaMemoryState state) { + DfaMemoryState state) { } @@ -61,9 +60,9 @@ protected void beforeExpressionPush(@Nonnull DfaValue value, * @param methodRef a method reference * @param state a memory state */ - protected void beforeMethodReferenceResultPush(@Nonnull DfaValue value, - @Nonnull PsiMethodReferenceExpression methodRef, - @Nonnull DfaMemoryState state) { + protected void beforeMethodReferenceResultPush(DfaValue value, + PsiMethodReferenceExpression methodRef, + DfaMemoryState state) { } @@ -76,19 +75,19 @@ protected void beforeMethodReferenceResultPush(@Nonnull DfaValue value, * @param context a method or lambda which result is being returned * @param state a memory state */ - protected void checkReturnValue(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nonnull PsiParameterListOwner context, - @Nonnull DfaMemoryState state) { + protected void checkReturnValue(DfaValue value, + PsiExpression expression, + PsiParameterListOwner context, + DfaMemoryState state) { } protected void beforeConditionalJump(ConditionalGotoInstruction instruction, boolean isTrueBranch) { } - void pushExpressionResult(@Nonnull DfaValue value, - @Nonnull ExpressionPushingInstruction instruction, - @Nonnull DfaMemoryState state) { + void pushExpressionResult(DfaValue value, + ExpressionPushingInstruction instruction, + DfaMemoryState state) { PsiExpression anchor = instruction.getExpression(); if (isExpressionPush(instruction, anchor)) { if (anchor instanceof PsiMethodReferenceExpression && !(instruction instanceof MethodReferenceInstruction)) { @@ -100,7 +99,7 @@ void pushExpressionResult(@Nonnull DfaValue value, state.push(value); } - private static boolean isExpressionPush(@Nonnull ExpressionPushingInstruction instruction, PsiExpression anchor) { + private static boolean isExpressionPush(ExpressionPushingInstruction instruction, PsiExpression anchor) { if (anchor == null) { return false; } @@ -118,9 +117,9 @@ private static boolean isExpressionPush(@Nonnull ExpressionPushingInstruction return true; } - private void callBeforeExpressionPush(@Nonnull DfaValue value, - @Nonnull ExpressionPushingInstruction instruction, - @Nonnull DfaMemoryState state, PsiExpression anchor) { + private void callBeforeExpressionPush(DfaValue value, + ExpressionPushingInstruction instruction, + DfaMemoryState state, PsiExpression anchor) { beforeExpressionPush(value, anchor, instruction.getExpressionRange(), state); PsiElement parent = PsiUtil.skipParenthesizedExprUp(anchor.getParent()); if (parent instanceof PsiLambdaExpression) { @@ -189,9 +188,8 @@ public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instructi return nextInstruction(instruction, runner, memState); } - @Nonnull - public DfaInstructionState[] visitControlTransfer(@Nonnull ControlTransferInstruction controlTransferInstruction, - @Nonnull DataFlowRunner runner, @Nonnull DfaMemoryState state) { + public DfaInstructionState[] visitControlTransfer(ControlTransferInstruction controlTransferInstruction, + DataFlowRunner runner, DfaMemoryState state) { return controlTransferInstruction.getTransfer().dispatch(state, runner).toArray(DfaInstructionState.EMPTY_ARRAY); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/JavaMethodContractUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/JavaMethodContractUtil.java index f551b827e7..77f9a4f387 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/JavaMethodContractUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/JavaMethodContractUtil.java @@ -3,7 +3,9 @@ import com.intellij.java.analysis.impl.codeInsight.DefaultInferredAnnotationProvider; import com.intellij.java.language.codeInsight.AnnotationUtil; +import com.intellij.java.language.impl.psi.impl.light.LightRecordMethod; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import consulo.application.util.CachedValueProvider; @@ -12,11 +14,10 @@ import consulo.project.Project; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,8 +40,7 @@ private JavaMethodContractUtil() { * @param call a method call site. * @return list of contracts (empty list if no contracts found) */ - @Nonnull - public static List getMethodCallContracts(@Nonnull PsiCallExpression call) { + public static List getMethodCallContracts(PsiCallExpression call) { PsiMethod method = call.resolveMethod(); return method == null ? Collections.emptyList() : getMethodCallContracts(method, call); } @@ -53,11 +53,10 @@ public static List getMethodCallContracts(@Nonnull Psi * testing methods like assertThat(x, is(null)) * @return list of contracts (empty list if no contracts found) */ - @Nonnull - public static List getMethodCallContracts(@Nonnull final PsiMethod method, + public static List getMethodCallContracts(final PsiMethod method, @Nullable PsiCallExpression call) { List contracts = - HardcodedContracts.getHardcodedContracts(method, ObjectUtil.tryCast(call, PsiMethodCallExpression.class)); + HardcodedContracts.getHardcodedContracts(method, ObjectUtil.tryCast(call, PsiMethodCallExpression.class)); return !contracts.isEmpty() ? contracts : getMethodContracts(method); } @@ -67,8 +66,7 @@ public static List getMethodCallContracts(@Nonnull fin * @param method a method to check the contracts for * @return list of contracts (empty list if no contracts found) */ - @Nonnull - public static List getMethodContracts(@Nonnull final PsiMethod method) { + public static List getMethodContracts(final PsiMethod method) { return getContractInfo(method).getContracts(); } @@ -78,7 +76,7 @@ public static List getMethodContracts(@Nonnull final Psi * @param method method to check * @return true if method has explicit (non-inferred) contract annotation. */ - public static boolean hasExplicitContractAnnotation(@Nonnull PsiMethod method) { + public static boolean hasExplicitContractAnnotation(PsiMethod method) { return getContractInfo(method).isExplicit(); } @@ -102,24 +100,20 @@ public static PsiAnnotation updateContract(PsiAnnotation annotation, List myContracts; + private final List myContracts; private final boolean myPure; private final boolean myExplicit; - private final - @Nonnull - MutationSignature myMutationSignature; + private final MutationSignature myMutationSignature; - ContractInfo(@Nonnull List contracts, boolean pure, boolean explicit, @Nonnull MutationSignature signature) { + ContractInfo(List contracts, boolean pure, boolean explicit, MutationSignature signature) { myContracts = contracts; myPure = pure; myExplicit = explicit; myMutationSignature = signature; } - @Nonnull List getContracts() { return myContracts; } @@ -132,14 +126,15 @@ boolean isExplicit() { return myExplicit; } - @Nonnull MutationSignature getMutationSignature() { return myMutationSignature; } } - @Nonnull - static ContractInfo getContractInfo(@Nonnull PsiMethod method) { + static ContractInfo getContractInfo(PsiMethod method) { + if (PsiUtil.isAnnotationMethod(method) || method instanceof LightRecordMethod) { + return ContractInfo.PURE; + } return LanguageCachedValueUtil.getCachedValue(method, () -> { final PsiAnnotation contractAnno = findContractAnnotation(method); ContractInfo info = ContractInfo.EMPTY; @@ -149,12 +144,14 @@ static ContractInfo getContractInfo(@Nonnull PsiMethod method) { MutationSignature mutationSignature = MutationSignature.UNKNOWN; if (pure) { mutationSignature = MutationSignature.PURE; - } else { + } + else { String mutationText = AnnotationUtil.getStringAttributeValue(contractAnno, MutationSignature.ATTR_MUTATES); if (mutationText != null) { try { mutationSignature = MutationSignature.parse(mutationText); - } catch (IllegalArgumentException ignored) { + } + catch (IllegalArgumentException ignored) { } } } @@ -173,8 +170,7 @@ static ContractInfo getContractInfo(@Nonnull PsiMethod method) { * @param contractAnno a contract annotation * @return a list of parsed contracts */ - @Nonnull - public static List parseContracts(@Nonnull PsiMethod method, @Nullable PsiAnnotation contractAnno) { + public static List parseContracts(PsiMethod method, @Nullable PsiAnnotation contractAnno) { if (contractAnno == null) return Collections.emptyList(); String text = AnnotationUtil.getStringAttributeValue(contractAnno, null); @@ -185,7 +181,8 @@ public static List parseContracts(@Nonnull PsiMethod met if (parsed.stream().allMatch(c -> c.getParameterCount() == paramCount)) { return parsed; } - } catch (StandardMethodContract.ParseException ignored) { + } + catch (StandardMethodContract.ParseException ignored) { } } return Collections.emptyList(); @@ -198,7 +195,7 @@ public static List parseContracts(@Nonnull PsiMethod met * @return a found annotation (null if not found) */ @Nullable - public static PsiAnnotation findContractAnnotation(@Nonnull PsiMethod method) { + public static PsiAnnotation findContractAnnotation(PsiMethod method) { return AnnotationUtil.findAnnotationInHierarchy(method, Collections.singleton(ORG_JETBRAINS_ANNOTATIONS_CONTRACT)); } @@ -208,7 +205,7 @@ public static PsiAnnotation findContractAnnotation(@Nonnull PsiMethod method) { * @param method method to check * @return true if the method known to be pure (see {@link Contract#pure()} for details). */ - public static boolean isPure(@Nonnull PsiMethod method) { + public static boolean isPure(PsiMethod method) { return getContractInfo(method).myPure; } @@ -231,7 +228,8 @@ public static ContractReturnValue getNonFailingReturnValue(List getReadVariables(Instruction instruction) { if (instruction instanceof PushInstruction && !((PushInstruction) instruction).isReferenceWrite()) { DfaValue value = ((PushInstruction) instruction).getValue(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/LoopAnalyzer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/LoopAnalyzer.java index 50698491ab..b5110befd1 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/LoopAnalyzer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/LoopAnalyzer.java @@ -11,18 +11,16 @@ import consulo.util.collection.primitive.ints.IntMaps; import consulo.util.collection.primitive.ints.IntObjectMap; -import javax.annotation.Nonnull; import java.util.*; import java.util.function.IntConsumer; class LoopAnalyzer { private static class MyGraph implements Graph { - @Nonnull private final ControlFlow myFlow; private final Instruction[] myInstructions; private final IntObjectMap myIns = IntMaps.newIntObjectHashMap(); - private MyGraph(@Nonnull ControlFlow flow) { + private MyGraph(ControlFlow flow) { myFlow = flow; myInstructions = flow.getInstructions(); for (Instruction instruction : myInstructions) { @@ -40,20 +38,17 @@ private MyGraph(@Nonnull ControlFlow flow) { } } - @Nonnull @Override public Collection getNodes() { return Arrays.asList(myFlow.getInstructions()); } - @Nonnull @Override public Iterator getIn(Instruction n) { int[] ins = myIns.get(n.getIndex()); return indicesToInstructions(ins); } - @Nonnull @Override public Iterator getOut(Instruction instruction) { int fromIndex = instruction.getIndex(); @@ -61,7 +56,6 @@ public Iterator getOut(Instruction instruction) { return indicesToInstructions(next); } - @Nonnull private Iterator indicesToInstructions(int[] next) { if (next == null) { return Collections.emptyIterator(); @@ -99,7 +93,6 @@ public void accept(int size) { return loop; } - @Nonnull static int[] getSuccessorIndices(int i, Instruction[] myInstructions) { Instruction instruction = myInstructions[i]; if (instruction instanceof GotoInstruction) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MethodContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MethodContract.java index b2052dd17b..82173b6d51 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MethodContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MethodContract.java @@ -17,7 +17,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.RelationType; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -73,7 +72,6 @@ public List getConditions() { }; } - @Nonnull public static MethodContract singleConditionContract(ContractValue left, RelationType relationType, ContractValue right, @@ -81,7 +79,6 @@ public static MethodContract singleConditionContract(ContractValue left, return singleConditionContract(ContractValue.condition(left, relationType, right), returnValue); } - @Nonnull private static MethodContract singleConditionContract(ContractValue condition, ContractReturnValue returnValue) { return new MethodContract(returnValue) { @Override diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Mutability.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Mutability.java index e2dc05b12a..473eaa7f19 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Mutability.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Mutability.java @@ -3,7 +3,6 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.inference.JavaSourceInference; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfReferenceType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfType; @@ -13,211 +12,210 @@ import com.intellij.java.language.psi.*; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.CachedValue; import consulo.application.util.CachedValueProvider; import consulo.application.util.CachedValuesManager; import consulo.component.util.ModificationTracker; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.file.light.LightVirtualFile; import consulo.language.impl.psi.LightElement; import consulo.language.psi.PsiModificationTracker; import consulo.language.psi.util.LanguageCachedValueUtil; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.dataholder.Key; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.PropertyKey; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; public enum Mutability { - /** - * Mutability is not known; probably value can be mutated - */ - UNKNOWN("mutability.unknown", null), - /** - * A value is known to be mutable (e.g. elements are sometimes added to the collection) - */ - MUTABLE("mutability.modifiable", null), - /** - * A value is known to be an immutable view over a possibly mutable value: it cannot be mutated directly using this - * reference; however subsequent reads (e.g. {@link Collection#size}) may return different results if the - * underlying value is mutated by somebody else. - */ - UNMODIFIABLE_VIEW("mutability.unmodifiable.view", "org.jetbrains.annotations.UnmodifiableView"), - /** - * A value is known to be immutable. For collection no elements could be added, removed or altered (though if collection - * contains mutable elements, they still could be mutated). - */ - UNMODIFIABLE("mutability.unmodifiable", "org.jetbrains.annotations.Unmodifiable"); + /** + * Mutability is not known; probably value can be mutated + */ + UNKNOWN(JavaAnalysisLocalize.mutabilityUnknown(), null), + /** + * A value is known to be mutable (e.g. elements are sometimes added to the collection) + */ + MUTABLE(JavaAnalysisLocalize.mutabilityModifiable(), null), + /** + * A value is known to be an immutable view over a possibly mutable value: it cannot be mutated directly using this + * reference; however subsequent reads (e.g. {@link Collection#size}) may return different results if the + * underlying value is mutated by somebody else. + */ + UNMODIFIABLE_VIEW(JavaAnalysisLocalize.mutabilityUnmodifiableView(), "org.jetbrains.annotations.UnmodifiableView"), + /** + * A value is known to be immutable. For collection no elements could be added, removed or altered (though if collection + * contains mutable elements, they still could be mutated). + */ + UNMODIFIABLE(JavaAnalysisLocalize.mutabilityUnmodifiable(), "org.jetbrains.annotations.Unmodifiable"); - public static final - @Nonnull - String UNMODIFIABLE_ANNOTATION = UNMODIFIABLE.myAnnotation; - public static final - @Nonnull - String UNMODIFIABLE_VIEW_ANNOTATION = UNMODIFIABLE_VIEW.myAnnotation; - private final - @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) - String myResourceKey; - private final String myAnnotation; - private final Key> myKey; + public static final String UNMODIFIABLE_ANNOTATION = UNMODIFIABLE.myAnnotation; + public static final String UNMODIFIABLE_VIEW_ANNOTATION = UNMODIFIABLE_VIEW.myAnnotation; + private final LocalizeValue myPresentationName; + private final String myAnnotation; + private final Key> myKey; - Mutability(@PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String resourceKey, String annotation) { - myResourceKey = resourceKey; - myAnnotation = annotation; - myKey = annotation == null ? null : Key.create(annotation); - } - - public DfReferenceType asDfType() { - return DfTypes.customObject(TypeConstraints.TOP, DfaNullability.UNKNOWN, this, null, DfTypes.BOTTOM); - } - - public - @Nonnull - @Nls - String getPresentationName() { - return JavaAnalysisBundle.message(myResourceKey); - } - - public boolean isUnmodifiable() { - return this == UNMODIFIABLE || this == UNMODIFIABLE_VIEW; - } - - @Nonnull - public Mutability unite(Mutability other) { - if (this == other) { - return this; + Mutability(LocalizeValue presentationName, String annotation) { + myPresentationName = presentationName; + myAnnotation = annotation; + myKey = annotation == null ? null : Key.create(annotation); } - if (this == UNKNOWN || other == UNKNOWN) { - return UNKNOWN; - } - if (this == MUTABLE || other == MUTABLE) { - return MUTABLE; - } - if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) { - return UNMODIFIABLE_VIEW; - } - return UNMODIFIABLE; - } - @Nonnull - public Mutability intersect(Mutability other) { - if (this == other) { - return this; - } - if (this == UNMODIFIABLE || other == UNMODIFIABLE) { - return UNMODIFIABLE; + public DfReferenceType asDfType() { + return DfTypes.customObject(TypeConstraints.TOP, DfaNullability.UNKNOWN, this, null, DfTypes.BOTTOM); } - if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) { - return UNMODIFIABLE_VIEW; - } - if (this == MUTABLE || other == MUTABLE) { - return MUTABLE; - } - return UNKNOWN; - } - @Nullable - public PsiAnnotation asAnnotation(Project project) { - if (myAnnotation == null) { - return null; + public String getPresentationName() { + return myPresentationName.get(); } - return CachedValuesManager.getManager(project).getCachedValue(project, myKey, () -> { - PsiAnnotation annotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText("@" + myAnnotation, null); - ((LightVirtualFile) annotation.getContainingFile().getViewProvider().getVirtualFile()).setWritable(false); - return CachedValueProvider.Result.create(annotation, ModificationTracker.NEVER_CHANGED); - }, false); - } - /** - * Returns a mutability of the supplied element, if known. The element could be a method - * (in this case the return value mutability is returned), a method parameter - * (the returned mutability will reflect whether the method can mutate the parameter), - * or a field (in this case the mutability could be obtained from its initializer). - * - * @param owner an element to check the mutability - * @return a Mutability enum value; {@link #UNKNOWN} if cannot be determined or specified element type is not supported. - */ - @Nonnull - public static Mutability getMutability(@Nonnull PsiModifierListOwner owner) { - if (owner instanceof LightElement) { - return UNKNOWN; + public boolean isUnmodifiable() { + return this == UNMODIFIABLE || this == UNMODIFIABLE_VIEW; } - return LanguageCachedValueUtil.getCachedValue(owner, () -> - CachedValueProvider.Result.create(calcMutability(owner), owner, PsiModificationTracker.MODIFICATION_COUNT)); - } - @Nonnull - private static Mutability calcMutability(@Nonnull PsiModifierListOwner owner) { - if (owner instanceof PsiParameter && owner.getParent() instanceof PsiParameterList) { - PsiParameterList list = (PsiParameterList) owner.getParent(); - PsiMethod method = ObjectUtil.tryCast(list.getParent(), PsiMethod.class); - if (method != null) { - int index = list.getParameterIndex((PsiParameter) owner); - JavaMethodContractUtil.ContractInfo contractInfo = JavaMethodContractUtil.getContractInfo(method); - if (contractInfo.isExplicit()) { - MutationSignature signature = contractInfo.getMutationSignature(); - if (signature.mutatesArg(index)) { + public Mutability unite(Mutability other) { + if (this == other) { + return this; + } + if (this == UNKNOWN || other == UNKNOWN) { + return UNKNOWN; + } + if (this == MUTABLE || other == MUTABLE) { return MUTABLE; - } else if (signature.preservesArg(index) && - PsiTreeUtil.findChildOfAnyType(method.getBody(), PsiLambdaExpression.class, PsiClass.class) == null) { - // If method preserves argument, it still may return a lambda which captures an argument and changes it - // TODO: more precise check (at least differentiate parameters which are captured by lambdas or not) + } + if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) { return UNMODIFIABLE_VIEW; - } + } + return UNMODIFIABLE; + } + + public Mutability intersect(Mutability other) { + if (this == other) { + return this; + } + if (this == UNMODIFIABLE || other == UNMODIFIABLE) { + return UNMODIFIABLE; + } + if (this == UNMODIFIABLE_VIEW || other == UNMODIFIABLE_VIEW) { + return UNMODIFIABLE_VIEW; + } + if (this == MUTABLE || other == MUTABLE) { + return MUTABLE; } return UNKNOWN; - } } - if (AnnotationUtil.isAnnotated(owner, Collections.singleton(UNMODIFIABLE_ANNOTATION), - AnnotationUtil.CHECK_HIERARCHY | - AnnotationUtil.CHECK_EXTERNAL | - AnnotationUtil.CHECK_INFERRED)) { - return UNMODIFIABLE; + + @Nullable + public PsiAnnotation asAnnotation(Project project) { + if (myAnnotation == null) { + return null; + } + return CachedValuesManager.getManager(project).getCachedValue( + project, + myKey, + () -> { + PsiAnnotation annotation = + JavaPsiFacade.getElementFactory(project).createAnnotationFromText("@" + myAnnotation, null); + ((LightVirtualFile)annotation.getContainingFile().getViewProvider().getVirtualFile()).setWritable(false); + return CachedValueProvider.Result.create(annotation, ModificationTracker.NEVER_CHANGED); + }, + false + ); } - if (AnnotationUtil.isAnnotated(owner, Collections.singleton(UNMODIFIABLE_VIEW_ANNOTATION), - AnnotationUtil.CHECK_HIERARCHY | - AnnotationUtil.CHECK_EXTERNAL | - AnnotationUtil.CHECK_INFERRED)) { - return UNMODIFIABLE_VIEW; + + /** + * Returns a mutability of the supplied element, if known. The element could be a method + * (in this case the return value mutability is returned), a method parameter + * (the returned mutability will reflect whether the method can mutate the parameter), + * or a field (in this case the mutability could be obtained from its initializer). + * + * @param owner an element to check the mutability + * @return a Mutability enum value; {@link #UNKNOWN} if cannot be determined or specified element type is not supported. + */ + public static Mutability getMutability(PsiModifierListOwner owner) { + if (owner instanceof LightElement) { + return UNKNOWN; + } + return LanguageCachedValueUtil.getCachedValue( + owner, + () -> CachedValueProvider.Result.create(calcMutability(owner), owner, PsiModificationTracker.MODIFICATION_COUNT) + ); } - if (owner instanceof PsiField && owner.hasModifierProperty(PsiModifier.FINAL)) { - PsiField field = (PsiField) owner; - List initializers = ContainerUtil.createMaybeSingletonList(field.getInitializer()); - if (initializers.isEmpty() && !owner.hasModifierProperty(PsiModifier.STATIC)) { - initializers = DfaPsiUtil.findAllConstructorInitializers(field); - } - initializers = StreamEx.of(initializers).flatMap(ExpressionUtils::nonStructuralChildren).toList(); - if (initializers.isEmpty()) { - return UNKNOWN; - } - Mutability mutability = UNMODIFIABLE; - for (PsiExpression initializer : initializers) { - Mutability newMutability = UNKNOWN; - if (ClassUtils.isImmutable(initializer.getType())) { - newMutability = UNMODIFIABLE; - } else if (initializer instanceof PsiMethodCallExpression) { - PsiMethod method = ((PsiMethodCallExpression) initializer).resolveMethod(); - newMutability = method == null ? UNKNOWN : getMutability(method); + + @RequiredReadAction + private static Mutability calcMutability(PsiModifierListOwner owner) { + if (owner instanceof PsiParameter parameter && parameter.getParent() instanceof PsiParameterList list) { + PsiMethod method = ObjectUtil.tryCast(list.getParent(), PsiMethod.class); + if (method != null) { + int index = list.getParameterIndex(parameter); + JavaMethodContractUtil.ContractInfo contractInfo = JavaMethodContractUtil.getContractInfo(method); + if (contractInfo.isExplicit()) { + MutationSignature signature = contractInfo.getMutationSignature(); + if (signature.mutatesArg(index)) { + return MUTABLE; + } + else if (signature.preservesArg(index) + && PsiTreeUtil.findChildOfAnyType(method.getBody(), PsiLambdaExpression.class, PsiClass.class) == null) { + // If method preserves argument, it still may return a lambda which captures an argument and changes it + // TODO: more precise check (at least differentiate parameters which are captured by lambdas or not) + return UNMODIFIABLE_VIEW; + } + } + return UNKNOWN; + } } - mutability = mutability.unite(newMutability); - if (!mutability.isUnmodifiable()) { - break; + if (AnnotationUtil.isAnnotated( + owner, + Collections.singleton(UNMODIFIABLE_ANNOTATION), + AnnotationUtil.CHECK_HIERARCHY | AnnotationUtil.CHECK_EXTERNAL | AnnotationUtil.CHECK_INFERRED + )) { + return UNMODIFIABLE; } - } - return mutability; + if (AnnotationUtil.isAnnotated( + owner, + Collections.singleton(UNMODIFIABLE_VIEW_ANNOTATION), + AnnotationUtil.CHECK_HIERARCHY | AnnotationUtil.CHECK_EXTERNAL | AnnotationUtil.CHECK_INFERRED + )) { + return UNMODIFIABLE_VIEW; + } + if (owner instanceof PsiField && owner.hasModifierProperty(PsiModifier.FINAL)) { + PsiField field = (PsiField)owner; + List initializers = ContainerUtil.createMaybeSingletonList(field.getInitializer()); + if (initializers.isEmpty() && !owner.hasModifierProperty(PsiModifier.STATIC)) { + initializers = DfaPsiUtil.findAllConstructorInitializers(field); + } + initializers = StreamEx.of(initializers).flatMap(ExpressionUtils::nonStructuralChildren).toList(); + if (initializers.isEmpty()) { + return UNKNOWN; + } + Mutability mutability = UNMODIFIABLE; + for (PsiExpression initializer : initializers) { + Mutability newMutability = UNKNOWN; + if (ClassUtils.isImmutable(initializer.getType())) { + newMutability = UNMODIFIABLE; + } + else if (initializer instanceof PsiMethodCallExpression methodCall) { + PsiMethod method = methodCall.resolveMethod(); + newMutability = method == null ? UNKNOWN : getMutability(method); + } + mutability = mutability.unite(newMutability); + if (!mutability.isUnmodifiable()) { + break; + } + } + return mutability; + } + return owner instanceof PsiMethodImpl method ? JavaSourceInference.inferMutability(method) : UNKNOWN; } - return owner instanceof PsiMethodImpl ? JavaSourceInference.inferMutability((PsiMethodImpl) owner) : UNKNOWN; - } - public static Mutability fromDfType(DfType dfType) { - return dfType instanceof DfReferenceType ? ((DfReferenceType) dfType).getMutability() : UNKNOWN; - } + public static Mutability fromDfType(DfType dfType) { + return dfType instanceof DfReferenceType referenceType ? referenceType.getMutability() : UNKNOWN; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MutationSignature.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MutationSignature.java index 9f1dd01ff9..53156fe019 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MutationSignature.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/MutationSignature.java @@ -1,17 +1,18 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.language.psi.*; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.localize.LocalizeValue; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -21,288 +22,292 @@ * Represents method mutation signature */ public final class MutationSignature { - public static final String ATTR_MUTATES = "mutates"; - static final MutationSignature UNKNOWN = new MutationSignature(false, new boolean[0]); - static final MutationSignature PURE = new MutationSignature(false, new boolean[0]); - private static final MutationSignature MUTATES_THIS_ONLY = new MutationSignature(true, new boolean[0]); - private final boolean myThis; - private final boolean[] myParameters; + public static final String ATTR_MUTATES = "mutates"; + static final MutationSignature UNKNOWN = new MutationSignature(false, new boolean[0]); + static final MutationSignature PURE = new MutationSignature(false, new boolean[0]); + private static final MutationSignature MUTATES_THIS_ONLY = new MutationSignature(true, new boolean[0]); + private final boolean myThis; + private final boolean[] myParameters; - private MutationSignature(boolean mutatesThis, boolean[] params) { - myThis = mutatesThis; - myParameters = params; - } - - /** - * @return true if the instance method may mutate this object - */ - public boolean mutatesThis() { - return myThis; - } - - /** - * @param n argument number (zero-based) - * @return true if the method may mutate given argument - */ - public boolean mutatesArg(int n) { - return n < myParameters.length && myParameters[n]; - } - - /** - * @return true if the method is static or never mutates this object - */ - public boolean preservesThis() { - return this != UNKNOWN && !myThis; - } - - /** - * @param n argument number (zero-based) - * @return true if the method never mutates given argument - */ - public boolean preservesArg(int n) { - return this != UNKNOWN && !mutatesArg(n); - } - - /** - * @return a signature that is equivalent to this signature but may also mutate this object - */ - public MutationSignature alsoMutatesThis() { - return this == UNKNOWN || myThis ? this : - isPure() ? MUTATES_THIS_ONLY : new MutationSignature(true, myParameters); - } - - /** - * @param n argument number (zero-based) - * @return a signature that is equivalent to this signature but may also mutate n-th argument - */ - public MutationSignature alsoMutatesArg(int n) { - if (myParameters.length > n && myParameters[n]) { - return this; + private MutationSignature(boolean mutatesThis, boolean[] params) { + myThis = mutatesThis; + myParameters = params; } - boolean[] params = Arrays.copyOf(myParameters, Math.max(n + 1, myParameters.length)); - params[n] = true; - return new MutationSignature(myThis, params); - } - - /** - * @return true if this signature represents a pure method - */ - public boolean isPure() { - return this == PURE; - } - - @Override - public int hashCode() { - return (myThis ? 137 : 731) + Arrays.hashCode(myParameters); - } - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; + /** + * @return true if the instance method may mutate this object + */ + public boolean mutatesThis() { + return myThis; } - if ((this == UNKNOWN) != (obj == UNKNOWN)) { - return false; + + /** + * @param n argument number (zero-based) + * @return true if the method may mutate given argument + */ + public boolean mutatesArg(int n) { + return n < myParameters.length && myParameters[n]; } - return obj instanceof MutationSignature && ((MutationSignature) obj).myThis == myThis && - Arrays.equals(((MutationSignature) obj).myParameters, myParameters); - } - @Override - public String toString() { - if (isPure()) { - return "(pure)"; + /** + * @return true if the method is static or never mutates this object + */ + public boolean preservesThis() { + return this != UNKNOWN && !myThis; } - if (this == UNKNOWN) { - return "(unknown)"; + + /** + * @param n argument number (zero-based) + * @return true if the method never mutates given argument + */ + public boolean preservesArg(int n) { + return this != UNKNOWN && !mutatesArg(n); } - return IntStreamEx.range(myParameters.length).mapToEntry(idx -> "param" + (idx + 1), idx -> myParameters[idx]) - .prepend("this", myThis).filterValues(b -> b).keys().joining(","); - } - /** - * Returns a stream of expressions which are mutated by given signature assuming that supplied call - * resolves to the method with this signature. - * - * @param call a call which resolves to the method with this mutation signature - * @return a stream of expressions which are mutated by this call. If qualifier is omitted, but mutated, - * a non-physical {@link PsiThisExpression} might be returned. - */ - public Stream mutatedExpressions(PsiMethodCallExpression call) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - StreamEx elements = - IntStreamEx.range(Math.min(myParameters.length, MethodCallUtils.isVarArgCall(call) ? args.length - 1 : args.length)) - .filter(idx -> myParameters[idx]).elements(args); - if (myThis) { - PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); - if (qualifier != null) { - return elements.prepend(qualifier); - } + /** + * @return a signature that is equivalent to this signature but may also mutate this object + */ + public MutationSignature alsoMutatesThis() { + return this == UNKNOWN || myThis ? this : + isPure() ? MUTATES_THIS_ONLY : new MutationSignature(true, myParameters); } - return elements; - } - /** - * @return true if known to mutate any parameter or receiver; false if pure or not known - */ - public boolean mutatesAnything() { - if (myThis) { - return true; + /** + * @param n argument number (zero-based) + * @return a signature that is equivalent to this signature but may also mutate n-th argument + */ + public MutationSignature alsoMutatesArg(int n) { + if (myParameters.length > n && myParameters[n]) { + return this; + } + boolean[] params = Arrays.copyOf(myParameters, Math.max(n + 1, myParameters.length)); + params[n] = true; + return new MutationSignature(myThis, params); } - for (boolean parameter : myParameters) { - if (parameter) { - return true; - } + + /** + * @return true if this signature represents a pure method + */ + public boolean isPure() { + return this == PURE; } - return false; - } - /** - * @param signature to parse - * @return a parsed mutation signature - * @throws IllegalArgumentException if signature is invalid - */ - public static MutationSignature parse(@Nonnull String signature) { - if (signature.trim().isEmpty()) { - return UNKNOWN; + @Override + public int hashCode() { + return (myThis ? 137 : 731) + Arrays.hashCode(myParameters); } - boolean mutatesThis = false; - boolean[] args = {}; - for (String part : signature.split(",")) { - part = part.trim(); - if (part.equals("this")) { - mutatesThis = true; - } else if (part.equals("param")) { - if (args.length == 0) { - args = new boolean[]{true}; - } else { - args[0] = true; + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; } - } else if (part.startsWith("param")) { - int argNum = Integer.parseInt(part.substring("param".length())); - if (argNum < 0 || argNum > 255) { - throw new IllegalArgumentException(JavaAnalysisBundle.message("mutation.signature.problem.invalid.token", part)); + if ((this == UNKNOWN) != (obj == UNKNOWN)) { + return false; } - if (args.length < argNum) { - args = Arrays.copyOf(args, argNum); + return obj instanceof MutationSignature mutationSignature + && mutationSignature.myThis == myThis + && Arrays.equals(mutationSignature.myParameters, myParameters); + } + + @Override + public String toString() { + if (isPure()) { + return "(pure)"; } - args[argNum - 1] = true; - } else if (!part.isEmpty()) { - throw new IllegalArgumentException(JavaAnalysisBundle.message("mutation.signature.problem.invalid.token", part)); - } + if (this == UNKNOWN) { + return "(unknown)"; + } + return IntStreamEx.range(myParameters.length) + .mapToEntry(idx -> "param" + (idx + 1), idx -> myParameters[idx]) + .prepend("this", myThis) + .filterValues(b -> b) + .keys() + .joining(","); } - return new MutationSignature(mutatesThis, args); - } - /** - * Checks the mutation signature - * - * @param signature signature to check - * @param method a method to apply the signature - * @return error message or null if signature is valid - */ - @Nullable - public static String checkSignature(@Nonnull String signature, @Nonnull PsiMethod method) { - try { - MutationSignature ms = parse(signature); - if (ms.myThis && method.hasModifierProperty(PsiModifier.STATIC)) { - return JavaAnalysisBundle.message("mutation.signature.problem.static.method.cannot.mutate.this"); - } - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (ms.myParameters.length > parameters.length) { - return JavaAnalysisBundle.message("mutation.signature.problem.reference.to.parameter.invalid", ms.myParameters.length); - } - for (int i = 0; i < ms.myParameters.length; i++) { - if (ms.myParameters[i]) { - PsiType type = parameters[i].getType(); - if (ClassUtils.isImmutable(type)) { - return JavaAnalysisBundle.message("mutation.signature.problem.parameter.has.immutable.type", i + 1, type.getPresentableText()); - } + /** + * Returns a stream of expressions which are mutated by given signature assuming that supplied call + * resolves to the method with this signature. + * + * @param call a call which resolves to the method with this mutation signature + * @return a stream of expressions which are mutated by this call. If qualifier is omitted, but mutated, + * a non-physical {@link PsiThisExpression} might be returned. + */ + @RequiredReadAction + public Stream mutatedExpressions(PsiMethodCallExpression call) { + PsiExpression[] args = call.getArgumentList().getExpressions(); + StreamEx elements = + IntStreamEx.range(Math.min(myParameters.length, MethodCallUtils.isVarArgCall(call) ? args.length - 1 : args.length)) + .filter(idx -> myParameters[idx]).elements(args); + if (myThis) { + PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression()); + if (qualifier != null) { + return elements.prepend(qualifier); + } } - } - } catch (IllegalArgumentException ex) { - return ex.getMessage(); + return elements; } - return null; - } - public static - @Nonnull - MutationSignature fromMethod(@Nullable PsiMethod method) { - if (method == null) { - return UNKNOWN; + /** + * @return true if known to mutate any parameter or receiver; false if pure or not known + */ + public boolean mutatesAnything() { + if (myThis) { + return true; + } + for (boolean parameter : myParameters) { + if (parameter) { + return true; + } + } + return false; } - return JavaMethodContractUtil.getContractInfo(method).getMutationSignature(); - } - public static - @Nonnull - MutationSignature fromCall(@Nullable PsiCall call) { - if (call == null) { - return UNKNOWN; + /** + * @param signature to parse + * @return a parsed mutation signature + * @throws IllegalArgumentException if signature is invalid + */ + public static MutationSignature parse(String signature) { + if (signature.trim().isEmpty()) { + return UNKNOWN; + } + boolean mutatesThis = false; + boolean[] args = {}; + for (String part : signature.split(",")) { + part = part.trim(); + if (part.equals("this")) { + mutatesThis = true; + } + else if (part.equals("param")) { + if (args.length == 0) { + args = new boolean[]{true}; + } + else { + args[0] = true; + } + } + else if (part.startsWith("param")) { + int argNum = Integer.parseInt(part.substring("param".length())); + if (argNum < 0 || argNum > 255) { + throw new IllegalArgumentException(JavaAnalysisLocalize.mutationSignatureProblemInvalidToken(part).get()); + } + if (args.length < argNum) { + args = Arrays.copyOf(args, argNum); + } + args[argNum - 1] = true; + } + else if (!part.isEmpty()) { + throw new IllegalArgumentException(JavaAnalysisLocalize.mutationSignatureProblemInvalidToken(part).get()); + } + } + return new MutationSignature(mutatesThis, args); } - PsiMethod method = call.resolveMethod(); - if (method != null) { - if (SpecialField.findSpecialField(method) != null) { - return PURE; - } - return fromMethod(method); + + /** + * Checks the mutation signature + * + * @param signature signature to check + * @param method a method to apply the signature + * @return error message or null if signature is valid + */ + public static LocalizeValue checkSignature(String signature, PsiMethod method) { + try { + MutationSignature ms = parse(signature); + if (ms.myThis && method.isStatic()) { + return JavaAnalysisLocalize.mutationSignatureProblemStaticMethodCannotMutateThis(); + } + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (ms.myParameters.length > parameters.length) { + return JavaAnalysisLocalize.mutationSignatureProblemReferenceToParameterInvalid(ms.myParameters.length); + } + for (int i = 0; i < ms.myParameters.length; i++) { + if (ms.myParameters[i]) { + PsiType type = parameters[i].getType(); + if (ClassUtils.isImmutable(type)) { + return JavaAnalysisLocalize.mutationSignatureProblemParameterHasImmutableType(i + 1, type.getPresentableText()); + } + } + } + } + catch (IllegalArgumentException ex) { + return LocalizeValue.ofNullable(ex.getMessage()); + } + return LocalizeValue.empty(); } - if (call instanceof PsiNewExpression) { - PsiNewExpression newExpression = (PsiNewExpression) call; - if (newExpression.isArrayCreation()) { - return PURE; - } - if (newExpression.getArgumentList() == null || !newExpression.getArgumentList().isEmpty()) { - return UNKNOWN; - } - PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference(); - if (classReference == null) { - return UNKNOWN; - } - PsiClass clazz = ObjectUtil.tryCast(classReference.resolve(), PsiClass.class); - if (clazz == null) { - return UNKNOWN; - } - Set visited = new HashSet<>(); - while (true) { - for (PsiField field : clazz.getFields()) { - if (!field.hasModifierProperty(PsiModifier.STATIC) && field.hasInitializer()) { + + public static MutationSignature fromMethod(@Nullable PsiMethod method) { + if (method == null) { return UNKNOWN; - } } - for (PsiClassInitializer initializer : clazz.getInitializers()) { - if (!initializer.hasModifierProperty(PsiModifier.STATIC)) { + return JavaMethodContractUtil.getContractInfo(method).getMutationSignature(); + } + + @RequiredReadAction + public static MutationSignature fromCall(@Nullable PsiCall call) { + if (call == null) { return UNKNOWN; - } } - for (PsiMethod ctor : clazz.getConstructors()) { - if (ctor.getParameterList().isEmpty()) { - return fromMethod(ctor); - } + PsiMethod method = call.resolveMethod(); + if (method != null) { + if (SpecialField.findSpecialField(method) != null) { + return PURE; + } + return fromMethod(method); } - clazz = clazz.getSuperClass(); - if (clazz == null || !visited.add(clazz)) { - return unknown(); + if (call instanceof PsiNewExpression newExpression) { + if (newExpression.isArrayCreation()) { + return PURE; + } + if (newExpression.getArgumentList() == null || !newExpression.getArgumentList().isEmpty()) { + return UNKNOWN; + } + PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference(); + if (classReference == null) { + return UNKNOWN; + } + PsiClass clazz = ObjectUtil.tryCast(classReference.resolve(), PsiClass.class); + if (clazz == null) { + return UNKNOWN; + } + Set visited = new HashSet<>(); + while (true) { + for (PsiField field : clazz.getFields()) { + if (!field.isStatic() && field.hasInitializer()) { + return UNKNOWN; + } + } + for (PsiClassInitializer initializer : clazz.getInitializers()) { + if (!initializer.isStatic()) { + return UNKNOWN; + } + } + for (PsiMethod ctor : clazz.getConstructors()) { + if (ctor.getParameterList().isEmpty()) { + return fromMethod(ctor); + } + } + clazz = clazz.getSuperClass(); + if (clazz == null || !visited.add(clazz)) { + return unknown(); + } + } } - } + return UNKNOWN; } - return UNKNOWN; - } - /** - * @return a signature of the pure method, which doesn't mutate anything - */ - @Nonnull - public static MutationSignature pure() { - return PURE; - } + /** + * @return a signature of the pure method, which doesn't mutate anything + */ + public static MutationSignature pure() { + return PURE; + } - /** - * @return a signature of the unknown method, which may mutate anything - */ - @Nonnull - public static MutationSignature unknown() { - return UNKNOWN; - } + /** + * @return a signature of the unknown method, which may mutate anything + */ + public static MutationSignature unknown() { + return UNKNOWN; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullParameterConstraintChecker.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullParameterConstraintChecker.java index b87fe188fe..cee27ad87f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullParameterConstraintChecker.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullParameterConstraintChecker.java @@ -17,7 +17,6 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.analysis.impl.psi.impl.search.JavaNullMethodArgumentUtil; import consulo.util.collection.SmartList; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.HashSet; @@ -48,7 +47,6 @@ private NullParameterConstraintChecker(Project project, Collection myUsedParameters = new HashSet<>(); } - @Nonnull static PsiParameter [] checkMethodParameters(PsiMethod method) { if(method.getBody() == null) @@ -84,8 +82,7 @@ private NullParameterConstraintChecker(Project project, Collection } @Override - @Nonnull - protected DfaInstructionState [] acceptInstruction(@Nonnull InstructionVisitor visitor, @Nonnull DfaInstructionState instructionState) + protected DfaInstructionState [] acceptInstruction(InstructionVisitor visitor, DfaInstructionState instructionState) { Instruction instruction = instructionState.getInstruction(); if(instruction instanceof PushInstruction) @@ -134,7 +131,6 @@ private NullParameterConstraintChecker(Project project, Collection return super.acceptInstruction(visitor, instructionState); } - @Nonnull @Override protected DfaMemoryState createMemoryState() { @@ -160,7 +156,7 @@ protected MyDfaMemoryState(MyDfaMemoryState toCopy) } @Override - protected void flushVariable(@Nonnull DfaVariableValue variable, boolean shouldMarkFlushed) + protected void flushVariable(DfaVariableValue variable, boolean shouldMarkFlushed) { final PsiModifierListOwner psi = variable.getPsiVariable(); if(psi instanceof PsiParameter && myPossiblyViolatedParameters.contains(psi)) @@ -170,7 +166,6 @@ protected void flushVariable(@Nonnull DfaVariableValue variable, boolean shouldM super.flushVariable(variable, shouldMarkFlushed); } - @Nonnull @Override public DfaMemoryStateImpl createCopy() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityProblemKind.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityProblemKind.java index 649b60f63b..fcf2a38ac4 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityProblemKind.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityProblemKind.java @@ -1,7 +1,6 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiTypesUtil; @@ -11,21 +10,20 @@ import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import com.siyeh.ig.psiutils.TypeUtils; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.PropertyKey; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; -import static com.intellij.java.analysis.JavaAnalysisBundle.BUNDLE; import static com.intellij.java.language.psi.CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION; import static com.intellij.java.language.psi.CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION; @@ -35,533 +33,582 @@ * @param a type of anchor element which could be associated with given nullability problem kind */ public final class NullabilityProblemKind { - private static final String NPE = JAVA_LANG_NULL_POINTER_EXCEPTION; - private static final String RE = JAVA_LANG_RUNTIME_EXCEPTION; - - private final String myName; - private final Supplier myAlwaysNullMessage; - private final Supplier myNormalMessage; - private final - @Nullable - String myException; - - private NullabilityProblemKind(@Nullable String exception, @Nonnull String name) { - myException = exception; - myName = name; - myAlwaysNullMessage = null; - myNormalMessage = null; - } - - private NullabilityProblemKind(@Nullable String exception, @Nonnull String name, - @Nonnull @PropertyKey(resourceBundle = BUNDLE) String message) { - this(exception, name, message, message); - } - - private NullabilityProblemKind(@Nullable String exception, @Nonnull String name, - @Nonnull @PropertyKey(resourceBundle = BUNDLE) String alwaysNullMessage, - @Nonnull @PropertyKey(resourceBundle = BUNDLE) String normalMessage) { - myException = exception; - myName = name; - myAlwaysNullMessage = JavaAnalysisBundle.messagePointer(alwaysNullMessage); - myNormalMessage = JavaAnalysisBundle.messagePointer(normalMessage); - } - - public static final NullabilityProblemKind callNPE = - new NullabilityProblemKind<>(NPE, "callNPE", "dataflow.message.npe.method.invocation.sure", "dataflow.message.npe.method.invocation"); - public static final NullabilityProblemKind callMethodRefNPE = - new NullabilityProblemKind<>(NPE, "callMethodRefNPE", "dataflow.message.npe.methodref.invocation"); - public static final NullabilityProblemKind innerClassNPE = - new NullabilityProblemKind<>(NPE, "innerClassNPE", "dataflow.message.npe.inner.class.construction.sure", - "dataflow.message.npe.inner.class.construction"); - public static final NullabilityProblemKind fieldAccessNPE = - new NullabilityProblemKind<>(NPE, "fieldAccessNPE", "dataflow.message.npe.field.access.sure", "dataflow.message.npe.field.access"); - public static final NullabilityProblemKind arrayAccessNPE = - new NullabilityProblemKind<>(NPE, "arrayAccessNPE", "dataflow.message.npe.array.access.sure", "dataflow.message.npe.array.access"); - public static final NullabilityProblemKind unboxingNullable = - new NullabilityProblemKind<>(NPE, "unboxingNullable", "dataflow.message.unboxing"); - public static final NullabilityProblemKind assigningToNotNull = - new NullabilityProblemKind<>(null, "assigningToNotNull", "dataflow.message.assigning.null", "dataflow.message.assigning.nullable"); - public static final NullabilityProblemKind assigningToNonAnnotatedField = - new NullabilityProblemKind<>(null, "assigningToNonAnnotatedField", "dataflow.message.assigning.null.notannotated", - "dataflow.message.assigning.nullable.notannotated"); - public static final NullabilityProblemKind storingToNotNullArray = - new NullabilityProblemKind<>(null, "storingToNotNullArray", "dataflow.message.storing.array.null", - "dataflow.message.storing.array.nullable"); - public static final NullabilityProblemKind nullableReturn = new NullabilityProblemKind<>(null, "nullableReturn"); - public static final NullabilityProblemKind nullableFunctionReturn = - new NullabilityProblemKind<>(RE, "nullableFunctionReturn", "dataflow.message.return.nullable.from.notnull.function", - "dataflow.message.return.nullable.from.notnull.function"); - public static final NullabilityProblemKind passingToNotNullParameter = - new NullabilityProblemKind<>(RE, "passingToNotNullParameter", "dataflow.message.passing.null.argument", - "dataflow.message.passing.nullable.argument"); - public static final NullabilityProblemKind unboxingMethodRefParameter = - new NullabilityProblemKind<>(NPE, "unboxingMethodRefParameter", "dataflow.message.passing.nullable.argument.methodref"); - public static final NullabilityProblemKind passingToNotNullMethodRefParameter = - new NullabilityProblemKind<>(RE, "passingToNotNullMethodRefParameter", "dataflow.message.passing.nullable.argument.methodref"); - public static final NullabilityProblemKind passingToNonAnnotatedParameter = - new NullabilityProblemKind<>(null, "passingToNonAnnotatedParameter", "dataflow.message.passing.null.argument.nonannotated", - "dataflow.message.passing.nullable.argument.nonannotated"); - public static final NullabilityProblemKind passingToNonAnnotatedMethodRefParameter = - new NullabilityProblemKind<>(null, "passingToNonAnnotatedMethodRefParameter", - "dataflow.message.passing.nullable.argument.methodref.nonannotated"); - // assumeNotNull problem is not reported, just used to force the argument to be not null - public static final NullabilityProblemKind assumeNotNull = new NullabilityProblemKind<>(RE, "assumeNotNull"); - /** - * noProblem is not reported and used to override another problem - * - * @see ControlFlowAnalyzer#addCustomNullabilityProblem(PsiExpression, NullabilityProblemKind) - * @see CFGBuilder#pushExpression(PsiExpression, NullabilityProblemKind) - */ - public static final NullabilityProblemKind noProblem = new NullabilityProblemKind<>(null, "noProblem"); - - /** - * Creates a new {@link NullabilityProblem} of this kind using given anchor - * - * @param anchor anchor to bind the problem to - * @param expression shortest expression which is actually violates the nullability - * @return newly created problem or null if anchor is null - */ - @Contract("null, _ -> null") - @Nullable - public final NullabilityProblem problem(@Nullable T anchor, @Nullable PsiExpression expression) { - return anchor == null || this == noProblem ? null : new NullabilityProblem<>(this, anchor, expression); - } - - /** - * Returns the supplied problem with adjusted type parameter or null if supplied problem kind is not this kind - * - * @param problem problem to check - * @return the supplied problem or null - */ - @SuppressWarnings("unchecked") - @Nullable - public final NullabilityProblem asMyProblem(NullabilityProblem problem) { - return problem != null && problem.myKind == this ? (NullabilityProblem) problem : null; - } - - /** - * Executes given consumer if the supplied problem has the same kind as this kind - * - * @param problem a problem to check - * @param consumer a consumer to execute. A problem anchor is supplied as the consumer argument. - */ - public void ifMyProblem(NullabilityProblem problem, Consumer consumer) { - NullabilityProblem myProblem = asMyProblem(problem); - if (myProblem != null) { - consumer.accept(myProblem.getAnchor()); - } - } - - @Override - public String toString() { - return myName; - } - - @Nullable - public static NullabilityProblem fromContext(@Nonnull PsiExpression expression, - Map> customNullabilityProblems) { - if (TypeConversionUtil.isPrimitiveAndNotNull(expression.getType()) || - expression instanceof PsiReferenceExpression && ((PsiReferenceExpression) expression).resolve() instanceof PsiClass) { - return null; - } - PsiExpression context = findTopExpression(expression); - NullabilityProblemKind kind = customNullabilityProblems.get(context); - if (kind != null) { - return kind.problem(context, expression); - } - PsiElement parent = context.getParent(); - if (parent instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression) parent).resolve(); - if (resolved instanceof PsiMember && ((PsiMember) resolved).hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - PsiElement grandParent = parent.getParent(); - if (grandParent instanceof PsiMethodCallExpression) { - return callNPE.problem((PsiMethodCallExpression) grandParent, expression); - } - return fieldAccessNPE.problem(context, expression); - } - PsiType targetType = null; - if (parent instanceof PsiLambdaExpression) { - targetType = LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression) parent); - } else if (parent instanceof PsiReturnStatement) { - targetType = PsiTypesUtil.getMethodReturnType(parent); - } - if (targetType != null && !PsiType.VOID.equals(targetType)) { - if (TypeConversionUtil.isPrimitiveAndNotNull(targetType)) { - return createUnboxingProblem(context, expression); - } - return nullableReturn.problem(context, expression); - } - if (parent instanceof PsiVariable) { - PsiVariable var = (PsiVariable) parent; - if (var.getType() instanceof PsiPrimitiveType) { - return createUnboxingProblem(context, expression); - } - Nullability nullability = DfaPsiUtil.getElementNullability(var.getType(), var); - if (nullability == Nullability.NOT_NULL) { - return assigningToNotNull.problem(context, expression); - } - } - if (parent instanceof PsiAssignmentExpression) { - return getAssignmentProblem((PsiAssignmentExpression) parent, expression, context); - } - if (parent instanceof PsiExpressionList) { - return getExpressionListProblem((PsiExpressionList) parent, expression, context); - } - if (parent instanceof PsiArrayInitializerExpression) { - return getArrayInitializerProblem((PsiArrayInitializerExpression) parent, expression, context); - } - if (parent instanceof PsiTypeCastExpression) { - if (TypeConversionUtil.isAssignableFromPrimitiveWrapper(context.getType())) { - // Only casts to primitives are here; casts to objects were already traversed by findTopExpression - return unboxingNullable.problem(context, expression); - } - } else if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiDoWhileStatement || - parent instanceof PsiUnaryExpression || parent instanceof PsiConditionalExpression || - (parent instanceof PsiForStatement && ((PsiForStatement) parent).getCondition() == context) || - (parent instanceof PsiAssertStatement && ((PsiAssertStatement) parent).getAssertCondition() == context)) { - return createUnboxingProblem(context, expression); - } - if (parent instanceof PsiSwitchBlock) { - NullabilityProblem problem = createUnboxingProblem(context, expression); - return problem == null ? fieldAccessNPE.problem(context, expression) : problem; - } - if (parent instanceof PsiForeachStatement || parent instanceof PsiThrowStatement || - parent instanceof PsiSynchronizedStatement) { - return fieldAccessNPE.problem(context, expression); - } - if (parent instanceof PsiNewExpression) { - return innerClassNPE.problem((PsiNewExpression) parent, expression); - } - if (parent instanceof PsiPolyadicExpression) { - PsiPolyadicExpression polyadic = (PsiPolyadicExpression) parent; - IElementType type = polyadic.getOperationTokenType(); - boolean noUnboxing = (type == JavaTokenType.PLUS && TypeUtils.isJavaLangString(polyadic.getType())) || - ((type == JavaTokenType.EQEQ || type == JavaTokenType.NE) && - StreamEx.of(polyadic.getOperands()).noneMatch(op -> TypeConversionUtil.isPrimitiveAndNotNull(op.getType()))); - if (!noUnboxing) { - return createUnboxingProblem(context, expression); - } - } - if (parent instanceof PsiArrayAccessExpression) { - PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression) parent; - if (arrayAccessExpression.getArrayExpression() == context) { - return arrayAccessNPE.problem(arrayAccessExpression, expression); - } - return createUnboxingProblem(context, expression); - } - return null; - } - - @Nullable - private static NullabilityProblem getExpressionListProblem(@Nonnull PsiExpressionList expressionList, - @Nonnull PsiExpression expression, - @Nonnull PsiExpression context) { - if (expressionList.getParent() instanceof PsiSwitchLabelStatementBase) { - return fieldAccessNPE.problem(context, expression); - } - PsiParameter parameter = MethodCallUtils.getParameterForArgument(context); - PsiElement grandParent = expressionList.getParent(); - if (parameter != null) { - if (parameter.getType() instanceof PsiPrimitiveType) { - return createUnboxingProblem(context, expression); - } - if (grandParent instanceof PsiAnonymousClass) { - grandParent = grandParent.getParent(); - } - if (grandParent instanceof PsiCall) { - PsiSubstitutor substitutor = ((PsiCall) grandParent).resolveMethodGenerics().getSubstitutor(); - Nullability nullability = DfaPsiUtil.getElementNullability(substitutor.substitute(parameter.getType()), parameter); - if (nullability == Nullability.NOT_NULL) { - return passingToNotNullParameter.problem(context, expression); - } - if (nullability == Nullability.UNKNOWN) { - return passingToNonAnnotatedParameter.problem(context, expression); - } - } - } else if (grandParent instanceof PsiCall && MethodCallUtils.isVarArgCall((PsiCall) grandParent)) { - Nullability nullability = DfaPsiUtil.getVarArgComponentNullability(((PsiCall) grandParent).resolveMethod()); - if (nullability == Nullability.NOT_NULL) { - return passingToNotNullParameter.problem(context, expression); - } - } - return null; - } - - @Nullable - private static NullabilityProblem getArrayInitializerProblem(@Nonnull PsiArrayInitializerExpression initializer, - @Nonnull PsiExpression expression, - @Nonnull PsiExpression context) { - PsiType type = initializer.getType(); - if (type instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) type).getComponentType(); - if (TypeConversionUtil.isPrimitiveAndNotNull(componentType)) { - return createUnboxingProblem(context, expression); - } - Nullability nullability = DfaPsiUtil.getTypeNullability(componentType); - if (nullability == Nullability.UNKNOWN) { - if (initializer.getParent() instanceof PsiNewExpression) { - PsiType expectedType = ExpectedTypeUtils.findExpectedType((PsiExpression) initializer.getParent(), false); - if (expectedType instanceof PsiArrayType) { - nullability = DfaPsiUtil.getTypeNullability(((PsiArrayType) expectedType).getComponentType()); - } - } - } - if (nullability == Nullability.NOT_NULL) { - return storingToNotNullArray.problem(context, expression); - } + private static final String NPE = JAVA_LANG_NULL_POINTER_EXCEPTION; + private static final String RE = JAVA_LANG_RUNTIME_EXCEPTION; + + private final String myName; + private final LocalizeValue myAlwaysNullMessage; + private final LocalizeValue myNormalMessage; + private final + @Nullable + String myException; + + private NullabilityProblemKind(@Nullable String exception, String name) { + myException = exception; + myName = name; + myAlwaysNullMessage = null; + myNormalMessage = null; + } + + private NullabilityProblemKind( + @Nullable String exception, String name, + LocalizeValue message + ) { + this(exception, name, message, message); + } + + private NullabilityProblemKind( + @Nullable String exception, String name, + LocalizeValue alwaysNullMessage, + LocalizeValue normalMessage + ) { + myException = exception; + myName = name; + myAlwaysNullMessage = alwaysNullMessage; + myNormalMessage = normalMessage; + } + + public static final NullabilityProblemKind callNPE = new NullabilityProblemKind<>( + NPE, + "callNPE", + JavaAnalysisLocalize.dataflowMessageNpeMethodInvocationSure(), + JavaAnalysisLocalize.dataflowMessageNpeMethodInvocation() + ); + public static final NullabilityProblemKind callMethodRefNPE = + new NullabilityProblemKind<>(NPE, "callMethodRefNPE", JavaAnalysisLocalize.dataflowMessageNpeMethodrefInvocation()); + public static final NullabilityProblemKind innerClassNPE = new NullabilityProblemKind<>( + NPE, + "innerClassNPE", + JavaAnalysisLocalize.dataflowMessageNpeInnerClassConstructionSure(), + JavaAnalysisLocalize.dataflowMessageNpeInnerClassConstruction() + ); + public static final NullabilityProblemKind fieldAccessNPE = new NullabilityProblemKind<>( + NPE, + "fieldAccessNPE", + JavaAnalysisLocalize.dataflowMessageNpeFieldAccessSure(), + JavaAnalysisLocalize.dataflowMessageNpeFieldAccess() + ); + public static final NullabilityProblemKind arrayAccessNPE = new NullabilityProblemKind<>( + NPE, + "arrayAccessNPE", + JavaAnalysisLocalize.dataflowMessageNpeArrayAccessSure(), + JavaAnalysisLocalize.dataflowMessageNpeArrayAccess() + ); + public static final NullabilityProblemKind unboxingNullable = + new NullabilityProblemKind<>(NPE, "unboxingNullable", JavaAnalysisLocalize.dataflowMessageUnboxing()); + public static final NullabilityProblemKind assigningToNotNull = new NullabilityProblemKind<>( + null, + "assigningToNotNull", + JavaAnalysisLocalize.dataflowMessageAssigningNull(), + JavaAnalysisLocalize.dataflowMessageAssigningNullable() + ); + public static final NullabilityProblemKind assigningToNonAnnotatedField = new NullabilityProblemKind<>( + null, + "assigningToNonAnnotatedField", + JavaAnalysisLocalize.dataflowMessageAssigningNullNotannotated(), + JavaAnalysisLocalize.dataflowMessageAssigningNullableNotannotated() + ); + public static final NullabilityProblemKind storingToNotNullArray = new NullabilityProblemKind<>( + null, + "storingToNotNullArray", + JavaAnalysisLocalize.dataflowMessageStoringArrayNull(), + JavaAnalysisLocalize.dataflowMessageStoringArrayNullable() + ); + public static final NullabilityProblemKind nullableReturn = + new NullabilityProblemKind<>(null, "nullableReturn"); + public static final NullabilityProblemKind nullableFunctionReturn = new NullabilityProblemKind<>( + RE, + "nullableFunctionReturn", + JavaAnalysisLocalize.dataflowMessageReturnNullableFromNotnullFunction(), + JavaAnalysisLocalize.dataflowMessageReturnNullableFromNotnullFunction() + ); + public static final NullabilityProblemKind passingToNotNullParameter = new NullabilityProblemKind<>( + RE, + "passingToNotNullParameter", + JavaAnalysisLocalize.dataflowMessagePassingNullArgument(), + JavaAnalysisLocalize.dataflowMessagePassingNullableArgument() + ); + public static final NullabilityProblemKind unboxingMethodRefParameter = new NullabilityProblemKind<>( + NPE, + "unboxingMethodRefParameter", + JavaAnalysisLocalize.dataflowMessagePassingNullableArgumentMethodref() + ); + public static final NullabilityProblemKind passingToNotNullMethodRefParameter = + new NullabilityProblemKind<>( + RE, + "passingToNotNullMethodRefParameter", + JavaAnalysisLocalize.dataflowMessagePassingNullableArgumentMethodref() + ); + public static final NullabilityProblemKind passingToNonAnnotatedParameter = new NullabilityProblemKind<>( + null, + "passingToNonAnnotatedParameter", + JavaAnalysisLocalize.dataflowMessagePassingNullArgumentNonannotated(), + JavaAnalysisLocalize.dataflowMessagePassingNullableArgumentNonannotated() + ); + public static final NullabilityProblemKind passingToNonAnnotatedMethodRefParameter = + new NullabilityProblemKind<>( + null, + "passingToNonAnnotatedMethodRefParameter", + JavaAnalysisLocalize.dataflowMessagePassingNullableArgumentMethodrefNonannotated() + ); + // assumeNotNull problem is not reported, just used to force the argument to be not null + public static final NullabilityProblemKind assumeNotNull = new NullabilityProblemKind<>(RE, "assumeNotNull"); + /** + * noProblem is not reported and used to override another problem + * + * @see ControlFlowAnalyzer#addCustomNullabilityProblem(PsiExpression, NullabilityProblemKind) + * @see CFGBuilder#pushExpression(PsiExpression, NullabilityProblemKind) + */ + public static final NullabilityProblemKind noProblem = new NullabilityProblemKind<>(null, "noProblem"); + + /** + * Creates a new {@link NullabilityProblem} of this kind using given anchor + * + * @param anchor anchor to bind the problem to + * @param expression shortest expression which is actually violates the nullability + * @return newly created problem or null if anchor is null + */ + @Contract("null, _ -> null") + @Nullable + public final NullabilityProblem problem(@Nullable T anchor, @Nullable PsiExpression expression) { + return anchor == null || this == noProblem ? null : new NullabilityProblem<>(this, anchor, expression); } - return null; - } - - @Nullable - private static NullabilityProblem getAssignmentProblem(@Nonnull PsiAssignmentExpression assignment, - @Nonnull PsiExpression expression, - @Nonnull PsiExpression context) { - IElementType tokenType = assignment.getOperationTokenType(); - if (assignment.getRExpression() == context) { - PsiExpression lho = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()); - if (lho != null) { - PsiType type = lho.getType(); - if (tokenType.equals(JavaTokenType.PLUSEQ) && TypeUtils.isJavaLangString(type)) { - return null; - } - if (type instanceof PsiPrimitiveType) { - return createUnboxingProblem(context, expression); - } - Nullability nullability = Nullability.UNKNOWN; - PsiVariable target = null; - if (lho instanceof PsiReferenceExpression) { - target = ObjectUtil.tryCast(((PsiReferenceExpression) lho).resolve(), PsiVariable.class); - if (target != null) { - nullability = DfaPsiUtil.getElementNullability(type, target); - } - } else { - nullability = DfaPsiUtil.getTypeNullability(type); - } - boolean forceDeclaredNullity = !(target instanceof PsiParameter && target.getParent() instanceof PsiParameterList); - if (forceDeclaredNullity && nullability == Nullability.NOT_NULL) { - return (lho instanceof PsiArrayAccessExpression ? storingToNotNullArray : assigningToNotNull).problem(context, expression); - } - if (nullability == Nullability.UNKNOWN && lho instanceof PsiReferenceExpression) { - PsiField field = ObjectUtil.tryCast(((PsiReferenceExpression) lho).resolve(), PsiField.class); - if (field != null && !field.hasModifierProperty(PsiModifier.FINAL)) { - return assigningToNonAnnotatedField.problem(context, expression); - } - } - } + + /** + * Returns the supplied problem with adjusted type parameter or null if supplied problem kind is not this kind + * + * @param problem problem to check + * @return the supplied problem or null + */ + @SuppressWarnings("unchecked") + @Nullable + public final NullabilityProblem asMyProblem(NullabilityProblem problem) { + return problem != null && problem.myKind == this ? (NullabilityProblem)problem : null; } - return null; - } - - /** - * Looks for top expression with the same nullability as given expression. That is: skips casts or conditionals, which don't unbox; - * goes up from switch expression breaks or expression-branches. - * - * @param expression expression to find the top expression for - * @return the top expression - */ - @Nonnull - static PsiExpression findTopExpression(@Nonnull PsiExpression expression) { - PsiExpression context = expression; - while (true) { - PsiElement parent = context.getParent(); - if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression || - (parent instanceof PsiConditionalExpression && ((PsiConditionalExpression) parent).getCondition() != context)) { - if (TypeConversionUtil.isPrimitiveAndNotNull(((PsiExpression) parent).getType())) { - return context; - } - context = (PsiExpression) parent; - continue; - } - if (parent instanceof PsiExpressionStatement) { - PsiElement grandParent = parent.getParent(); - if (grandParent instanceof PsiSwitchLabeledRuleStatement) { - PsiSwitchBlock block = ((PsiSwitchLabeledRuleStatement) grandParent).getEnclosingSwitchBlock(); - if (block instanceof PsiSwitchExpression) { - context = (PsiExpression) block; - continue; - } - } - } - if (parent instanceof PsiYieldStatement) { - PsiSwitchExpression enclosing = ((PsiYieldStatement) parent).findEnclosingExpression(); - if (enclosing != null) { - context = enclosing; - continue; - } - } - return context; + + /** + * Executes given consumer if the supplied problem has the same kind as this kind + * + * @param problem a problem to check + * @param consumer a consumer to execute. A problem anchor is supplied as the consumer argument. + */ + @RequiredReadAction + public void ifMyProblem(NullabilityProblem problem, @RequiredReadAction Consumer consumer) { + NullabilityProblem myProblem = asMyProblem(problem); + if (myProblem != null) { + consumer.accept(myProblem.getAnchor()); + } } - } - private static NullabilityProblem createUnboxingProblem(@Nonnull PsiExpression context, - @Nonnull PsiExpression expression) { - if (!TypeConversionUtil.isPrimitiveWrapper(context.getType())) { - return null; + @Override + public String toString() { + return myName; } - return unboxingNullable.problem(context, expression); - } - - static List> postprocessNullabilityProblems(Collection> problems) { - List> unchanged = new ArrayList<>(); - Map> expressionToProblem = new HashMap<>(); - for (NullabilityProblem problem : problems) { - PsiExpression expression = problem.getDereferencedExpression(); - NullabilityProblemKind kind = problem.getKind(); - if (expression == null) { - unchanged.add(problem); - continue; - } - if (innerClassNPE == kind || callNPE == kind || arrayAccessNPE == kind || fieldAccessNPE == kind) { - // Qualifier-problems are reported on top-expression level for now as it's rare case to have - // something complex in qualifier and we highlight not the qualifier itself, but something else (e.g. called method name) - unchanged.add(problem.withExpression(findTopExpression(expression))); - continue; - } - // Merge ternary problems reported for both branches into single problem - while (true) { - PsiExpression top = skipParenthesesAndObjectCastsUp(expression); - PsiConditionalExpression ternary = ObjectUtil.tryCast(top.getParent(), PsiConditionalExpression.class); - if (ternary != null) { - PsiExpression otherBranch = null; - if (ternary.getThenExpression() == top) { - otherBranch = ternary.getElseExpression(); - } else if (ternary.getElseExpression() == top) { - otherBranch = ternary.getThenExpression(); - } - if (otherBranch != null) { - otherBranch = skipParenthesesAndObjectCastsDown(otherBranch); - NullabilityProblem otherBranchProblem = expressionToProblem.remove(otherBranch); - if (otherBranchProblem != null) { - expression = ternary; - problem = problem.withExpression(ternary); - continue; + + @Nullable + @RequiredReadAction + public static NullabilityProblem fromContext( + PsiExpression expression, + Map> customNullabilityProblems + ) { + if (TypeConversionUtil.isPrimitiveAndNotNull(expression.getType()) || + expression instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiClass) { + return null; + } + PsiExpression context = findTopExpression(expression); + NullabilityProblemKind kind = customNullabilityProblems.get(context); + if (kind != null) { + return kind.problem(context, expression); + } + PsiElement parent = context.getParent(); + if (parent instanceof PsiReferenceExpression refExpr) { + if (refExpr.resolve() instanceof PsiMember member && member.isStatic()) { + return null; + } + if (parent.getParent() instanceof PsiMethodCallExpression methodCall) { + return callNPE.problem(methodCall, expression); } - } + return fieldAccessNPE.problem(context, expression); } - break; - } - expressionToProblem.put(expression, problem); - } - return StreamEx.of(unchanged, expressionToProblem.values()).toFlatList(Function.identity()); - } - - private static PsiExpression skipParenthesesAndObjectCastsDown(PsiExpression expression) { - while (true) { - if (expression instanceof PsiParenthesizedExpression) { - expression = ((PsiParenthesizedExpression) expression).getExpression(); - } else if (expression instanceof PsiTypeCastExpression && !(expression.getType() instanceof PsiPrimitiveType)) { - expression = ((PsiTypeCastExpression) expression).getOperand(); - } else { - return expression; - } - } - } - - @Nonnull - private static PsiExpression skipParenthesesAndObjectCastsUp(PsiExpression expression) { - PsiExpression top = expression; - while (true) { - PsiElement parent = top.getParent(); - if (parent instanceof PsiParenthesizedExpression || - (parent instanceof PsiTypeCastExpression && !(((PsiTypeCastExpression) parent).getType() instanceof PsiPrimitiveType))) { - top = (PsiExpression) parent; - } else { - return top; - } + PsiType targetType = null; + if (parent instanceof PsiLambdaExpression lambda) { + targetType = LambdaUtil.getFunctionalInterfaceReturnType(lambda); + } + else if (parent instanceof PsiReturnStatement) { + targetType = PsiTypesUtil.getMethodReturnType(parent); + } + if (targetType != null && !PsiType.VOID.equals(targetType)) { + if (TypeConversionUtil.isPrimitiveAndNotNull(targetType)) { + return createUnboxingProblem(context, expression); + } + return nullableReturn.problem(context, expression); + } + if (parent instanceof PsiVariable var) { + if (var.getType() instanceof PsiPrimitiveType) { + return createUnboxingProblem(context, expression); + } + Nullability nullability = DfaPsiUtil.getElementNullability(var.getType(), var); + if (nullability == Nullability.NOT_NULL) { + return assigningToNotNull.problem(context, expression); + } + } + if (parent instanceof PsiAssignmentExpression assignment) { + return getAssignmentProblem(assignment, expression, context); + } + if (parent instanceof PsiExpressionList expressionList) { + return getExpressionListProblem(expressionList, expression, context); + } + if (parent instanceof PsiArrayInitializerExpression arrayInitializer) { + return getArrayInitializerProblem(arrayInitializer, expression, context); + } + if (parent instanceof PsiTypeCastExpression) { + if (TypeConversionUtil.isAssignableFromPrimitiveWrapper(context.getType())) { + // Only casts to primitives are here; casts to objects were already traversed by findTopExpression + return unboxingNullable.problem(context, expression); + } + } + else if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiDoWhileStatement + || parent instanceof PsiUnaryExpression || parent instanceof PsiConditionalExpression + || (parent instanceof PsiForStatement forStatement && forStatement.getCondition() == context) + || (parent instanceof PsiAssertStatement assertStatement && assertStatement.getAssertCondition() == context)) { + return createUnboxingProblem(context, expression); + } + if (parent instanceof PsiSwitchBlock) { + NullabilityProblem problem = createUnboxingProblem(context, expression); + return problem == null ? fieldAccessNPE.problem(context, expression) : problem; + } + if (parent instanceof PsiForeachStatement || parent instanceof PsiThrowStatement || parent instanceof PsiSynchronizedStatement) { + return fieldAccessNPE.problem(context, expression); + } + if (parent instanceof PsiNewExpression newExpression) { + return innerClassNPE.problem(newExpression, expression); + } + if (parent instanceof PsiPolyadicExpression polyadic) { + IElementType type = polyadic.getOperationTokenType(); + boolean noUnboxing = (type == JavaTokenType.PLUS && TypeUtils.isJavaLangString(polyadic.getType())) + || ((type == JavaTokenType.EQEQ || type == JavaTokenType.NE) + && StreamEx.of(polyadic.getOperands()).noneMatch(op -> TypeConversionUtil.isPrimitiveAndNotNull(op.getType()))); + if (!noUnboxing) { + return createUnboxingProblem(context, expression); + } + } + if (parent instanceof PsiArrayAccessExpression arrayAccess) { + return arrayAccess.getArrayExpression() == context + ? arrayAccessNPE.problem(arrayAccess, expression) + : createUnboxingProblem(context, expression); + } + return null; } - } - - /** - * Represents a concrete nullability problem on PSI which consists of PSI element (anchor) and {@link NullabilityProblemKind}. - * - * @param a type of anchor element - */ - public static final class NullabilityProblem { - private final - @Nonnull - NullabilityProblemKind myKind; - private final - @Nonnull - T myAnchor; - private final - @Nullable - PsiExpression myDereferencedExpression; - NullabilityProblem(@Nonnull NullabilityProblemKind kind, @Nonnull T anchor, @Nullable PsiExpression dereferencedExpression) { - myKind = kind; - myAnchor = anchor; - myDereferencedExpression = dereferencedExpression; + @Nullable + private static NullabilityProblem getExpressionListProblem( + PsiExpressionList expressionList, + PsiExpression expression, + PsiExpression context + ) { + if (expressionList.getParent() instanceof PsiSwitchLabelStatementBase) { + return fieldAccessNPE.problem(context, expression); + } + PsiParameter parameter = MethodCallUtils.getParameterForArgument(context); + PsiElement grandParent = expressionList.getParent(); + if (parameter != null) { + if (parameter.getType() instanceof PsiPrimitiveType) { + return createUnboxingProblem(context, expression); + } + if (grandParent instanceof PsiAnonymousClass) { + grandParent = grandParent.getParent(); + } + if (grandParent instanceof PsiCall call) { + PsiSubstitutor substitutor = call.resolveMethodGenerics().getSubstitutor(); + Nullability nullability = DfaPsiUtil.getElementNullability(substitutor.substitute(parameter.getType()), parameter); + if (nullability == Nullability.NOT_NULL) { + return passingToNotNullParameter.problem(context, expression); + } + if (nullability == Nullability.UNKNOWN) { + return passingToNonAnnotatedParameter.problem(context, expression); + } + } + } + else if (grandParent instanceof PsiCall call && MethodCallUtils.isVarArgCall(call)) { + Nullability nullability = DfaPsiUtil.getVarArgComponentNullability(call.resolveMethod()); + if (nullability == Nullability.NOT_NULL) { + return passingToNotNullParameter.problem(context, expression); + } + } + return null; } - @Nonnull - public T getAnchor() { - return myAnchor; + @Nullable + private static NullabilityProblem getArrayInitializerProblem( + PsiArrayInitializerExpression initializer, + PsiExpression expression, + PsiExpression context + ) { + PsiType type = initializer.getType(); + if (type instanceof PsiArrayType arrayType) { + PsiType componentType = arrayType.getComponentType(); + if (TypeConversionUtil.isPrimitiveAndNotNull(componentType)) { + return createUnboxingProblem(context, expression); + } + Nullability nullability = DfaPsiUtil.getTypeNullability(componentType); + if (nullability == Nullability.UNKNOWN + && initializer.getParent() instanceof PsiNewExpression newExpr + && ExpectedTypeUtils.findExpectedType(newExpr, false) instanceof PsiArrayType expectedArrayType) { + nullability = DfaPsiUtil.getTypeNullability(expectedArrayType.getComponentType()); + } + if (nullability == Nullability.NOT_NULL) { + return storingToNotNullArray.problem(context, expression); + } + } + return null; } - /** - * @return name of exception (or its superclass) which is thrown if violation occurs, - * or null if no exception is thrown (e.g. when assigning null to variable annotated as notnull). - */ @Nullable - public String thrownException() { - return myKind.myException; + @RequiredReadAction + private static NullabilityProblem getAssignmentProblem( + PsiAssignmentExpression assignment, + PsiExpression expression, + PsiExpression context + ) { + IElementType tokenType = assignment.getOperationTokenType(); + if (assignment.getRExpression() == context) { + PsiExpression lho = PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()); + if (lho != null) { + PsiType type = lho.getType(); + if (tokenType.equals(JavaTokenType.PLUSEQ) && TypeUtils.isJavaLangString(type)) { + return null; + } + if (type instanceof PsiPrimitiveType) { + return createUnboxingProblem(context, expression); + } + Nullability nullability = Nullability.UNKNOWN; + PsiVariable target = null; + if (lho instanceof PsiReferenceExpression lhoRefExpr) { + target = ObjectUtil.tryCast(lhoRefExpr.resolve(), PsiVariable.class); + if (target != null) { + nullability = DfaPsiUtil.getElementNullability(type, target); + } + } + else { + nullability = DfaPsiUtil.getTypeNullability(type); + } + boolean forceDeclaredNullity = !(target instanceof PsiParameter && target.getParent() instanceof PsiParameterList); + if (forceDeclaredNullity && nullability == Nullability.NOT_NULL) { + return (lho instanceof PsiArrayAccessExpression ? storingToNotNullArray : assigningToNotNull) + .problem(context, expression); + } + if (nullability == Nullability.UNKNOWN && lho instanceof PsiReferenceExpression lhoRefExpr) { + PsiField field = ObjectUtil.tryCast(lhoRefExpr.resolve(), PsiField.class); + if (field != null && !field.isFinal()) { + return assigningToNonAnnotatedField.problem(context, expression); + } + } + } + } + return null; } /** - * @return a minimal nullable expression which causes the problem + * Looks for top expression with the same nullability as given expression. That is: skips casts or conditionals, which don't unbox; + * goes up from switch expression breaks or expression-branches. + * + * @param expression expression to find the top expression for + * @return the top expression */ - @Nullable - public PsiExpression getDereferencedExpression() { - return myDereferencedExpression; + static PsiExpression findTopExpression(PsiExpression expression) { + PsiExpression context = expression; + while (true) { + PsiElement parent = context.getParent(); + if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression + || (parent instanceof PsiConditionalExpression conditional && conditional.getCondition() != context)) { + PsiExpression parentExpression = (PsiExpression)parent; + if (TypeConversionUtil.isPrimitiveAndNotNull(parentExpression.getType())) { + return context; + } + context = parentExpression; + continue; + } + if (parent instanceof PsiExpressionStatement parentExpression + && parentExpression.getParent() instanceof PsiSwitchLabeledRuleStatement switchLabeledRule + && switchLabeledRule.getEnclosingSwitchBlock() instanceof PsiSwitchExpression switchExpr) { + context = switchExpr; + continue; + } + if (parent instanceof PsiYieldStatement yieldStatement) { + PsiSwitchExpression enclosing = yieldStatement.findEnclosingExpression(); + if (enclosing != null) { + context = enclosing; + continue; + } + } + return context; + } } - @Nonnull - public String getMessage(Map expressions) { - if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) { - throw new IllegalStateException("This problem kind has no message associated: " + myKind); - } - PsiExpression expression = PsiUtil.skipParenthesizedExprDown(getDereferencedExpression()); - if (expression != null) { - if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInspectionBase.ConstantResult.NULL) { - return myKind.myAlwaysNullMessage.get(); - } - } - return myKind.myNormalMessage.get(); + private static NullabilityProblem createUnboxingProblem( + PsiExpression context, + PsiExpression expression + ) { + if (!TypeConversionUtil.isPrimitiveWrapper(context.getType())) { + return null; + } + return unboxingNullable.problem(context, expression); } - @Nonnull - public NullabilityProblemKind getKind() { - return myKind; + static List> postprocessNullabilityProblems(Collection> problems) { + List> unchanged = new ArrayList<>(); + Map> expressionToProblem = new HashMap<>(); + for (NullabilityProblem problem : problems) { + PsiExpression expression = problem.getDereferencedExpression(); + NullabilityProblemKind kind = problem.getKind(); + if (expression == null) { + unchanged.add(problem); + continue; + } + if (innerClassNPE == kind || callNPE == kind || arrayAccessNPE == kind || fieldAccessNPE == kind) { + // Qualifier-problems are reported on top-expression level for now as it's rare case to have + // something complex in qualifier and we highlight not the qualifier itself, but something else (e.g. called method name) + unchanged.add(problem.withExpression(findTopExpression(expression))); + continue; + } + // Merge ternary problems reported for both branches into single problem + while (true) { + PsiExpression top = skipParenthesesAndObjectCastsUp(expression); + PsiConditionalExpression ternary = ObjectUtil.tryCast(top.getParent(), PsiConditionalExpression.class); + if (ternary != null) { + PsiExpression otherBranch = null; + if (ternary.getThenExpression() == top) { + otherBranch = ternary.getElseExpression(); + } + else if (ternary.getElseExpression() == top) { + otherBranch = ternary.getThenExpression(); + } + if (otherBranch != null) { + otherBranch = skipParenthesesAndObjectCastsDown(otherBranch); + NullabilityProblem otherBranchProblem = expressionToProblem.remove(otherBranch); + if (otherBranchProblem != null) { + expression = ternary; + problem = problem.withExpression(ternary); + continue; + } + } + } + break; + } + expressionToProblem.put(expression, problem); + } + return StreamEx.of(unchanged, expressionToProblem.values()).toFlatList(Function.identity()); } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof NullabilityProblem)) { - return false; - } - NullabilityProblem problem = (NullabilityProblem) o; - return myKind.equals(problem.myKind) && myAnchor.equals(problem.myAnchor) && - Objects.equals(myDereferencedExpression, problem.myDereferencedExpression); + private static PsiExpression skipParenthesesAndObjectCastsDown(PsiExpression expression) { + while (true) { + if (expression instanceof PsiParenthesizedExpression parenthesized) { + expression = parenthesized.getExpression(); + } + else if (expression instanceof PsiTypeCastExpression typeCast && !(typeCast.getType() instanceof PsiPrimitiveType)) { + expression = typeCast.getOperand(); + } + else { + return expression; + } + } } - @Override - public int hashCode() { - return Objects.hash(myKind, myAnchor, myDereferencedExpression); + private static PsiExpression skipParenthesesAndObjectCastsUp(PsiExpression expression) { + PsiExpression top = expression; + while (true) { + PsiElement parent = top.getParent(); + if (parent instanceof PsiParenthesizedExpression + || (parent instanceof PsiTypeCastExpression typeCast && !(typeCast.getType() instanceof PsiPrimitiveType))) { + top = (PsiExpression)parent; + } + else { + return top; + } + } } - @Override - public String toString() { - return "[" + myKind + "] " + myAnchor.getText(); - } + /** + * Represents a concrete nullability problem on PSI which consists of PSI element (anchor) and {@link NullabilityProblemKind}. + * + * @param a type of anchor element + */ + public static final class NullabilityProblem { + private final NullabilityProblemKind myKind; + private final T myAnchor; + @Nullable + private final PsiExpression myDereferencedExpression; + + NullabilityProblem(NullabilityProblemKind kind, T anchor, @Nullable PsiExpression dereferencedExpression) { + myKind = kind; + myAnchor = anchor; + myDereferencedExpression = dereferencedExpression; + } + + public T getAnchor() { + return myAnchor; + } + + /** + * @return name of exception (or its superclass) which is thrown if violation occurs, + * or null if no exception is thrown (e.g. when assigning null to variable annotated as notnull). + */ + @Nullable + public String thrownException() { + return myKind.myException; + } - public NullabilityProblem withExpression(PsiExpression expression) { - return expression == myDereferencedExpression ? this : new NullabilityProblem<>(myKind, myAnchor, expression); + /** + * @return a minimal nullable expression which causes the problem + */ + @Nullable + public PsiExpression getDereferencedExpression() { + return myDereferencedExpression; + } + + public LocalizeValue getMessage(Map expressions) { + if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) { + throw new IllegalStateException("This problem kind has no message associated: " + myKind); + } + PsiExpression expression = PsiUtil.skipParenthesizedExprDown(getDereferencedExpression()); + if (expression != null) { + if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInspectionBase.ConstantResult.NULL) { + return myKind.myAlwaysNullMessage; + } + } + return myKind.myNormalMessage; + } + + public NullabilityProblemKind getKind() { + return myKind; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof NullabilityProblem)) { + return false; + } + NullabilityProblem problem = (NullabilityProblem)o; + return myKind.equals(problem.myKind) && myAnchor.equals(problem.myAnchor) + && Objects.equals(myDereferencedExpression, problem.myDereferencedExpression); + } + + @Override + public int hashCode() { + return Objects.hash(myKind, myAnchor, myDereferencedExpression); + } + + @Override + @RequiredReadAction + public String toString() { + return "[" + myKind + "] " + myAnchor.getText(); + } + + public NullabilityProblem withExpression(PsiExpression expression) { + return expression == myDereferencedExpression ? this : new NullabilityProblem<>(myKind, myAnchor, expression); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityUtil.java index 13579e3d1b..041fb783d9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/NullabilityUtil.java @@ -1,6 +1,7 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; +import com.intellij.java.analysis.codeInsight.daemon.JavaImplicitUsageProvider; import com.intellij.java.analysis.impl.codeInsight.JavaPsiEquivalenceUtil; import com.intellij.java.analysis.impl.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaExpressionFactory; @@ -8,10 +9,10 @@ import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.augment.PsiAugmentProvider; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import consulo.application.util.CachedValueProvider; -import consulo.application.util.CachedValuesManager; import consulo.content.scope.SearchScope; import consulo.language.editor.ImplicitUsageProvider; import consulo.language.psi.PsiElement; @@ -20,16 +21,15 @@ import consulo.language.psi.search.PsiSearchHelper; import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.LanguageCachedValueUtil; +import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; public final class NullabilityUtil { - @Nonnull public static DfaNullability calcCanBeNull(DfaVariableValue value) { if (value.getDescriptor() instanceof DfaExpressionFactory.ThisDescriptor) { return DfaNullability.NOT_NULL; @@ -69,7 +69,7 @@ public static DfaNullability calcCanBeNull(DfaVariableValue value) { } static Pair getNullabilityFromFieldInitializers(PsiField field, Nullability defaultNullability) { - if (DfaPsiUtil.isFinalField(field)) { + if (DfaPsiUtil.isFinalField(field) && PsiAugmentProvider.canTrustFieldInitializer(field)) { PsiExpression initializer = field.getInitializer(); if (initializer != null) { return Pair.create(initializer, getExpressionNullability(initializer)); @@ -103,7 +103,10 @@ private static boolean isOnlyImplicitlyInitialized(PsiField field) { } private static boolean isImplicitlyInitializedNotNull(PsiField field) { - return ContainerUtil.exists(ImplicitUsageProvider.EP_NAME.getExtensionList(), p -> p.isImplicitlyNotNullInitialized(field)); + Project project = field.getProject(); + return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { + return provider instanceof JavaImplicitUsageProvider javaImplicitUsageProvider && javaImplicitUsageProvider.isImplicitlyNotNullInitialized(field); + }) != null; } private static boolean weAreSureThereAreNoExplicitWrites(PsiField field) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/SpecialField.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/SpecialField.java index 32c411021f..b0c2ae66b9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/SpecialField.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/SpecialField.java @@ -1,7 +1,6 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfConstantType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfReferenceType; @@ -16,14 +15,13 @@ import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.TypeUtils; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.PropertyKey; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Objects; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.ContractReturnValue.returnFalse; @@ -37,437 +35,410 @@ * @author Tagir Valeev */ public enum SpecialField implements VariableDescriptor { - ARRAY_LENGTH("length", "special.field.array.length", true) { - @Override - boolean isMyQualifierType(PsiType type) { - return type instanceof PsiArrayType; - } + ARRAY_LENGTH("length", JavaAnalysisLocalize.specialFieldArrayLength(), true) { + @Override + boolean isMyQualifierType(PsiType type) { + return type instanceof PsiArrayType; + } - @Override - boolean isMyAccessor(PsiMember accessor) { - return accessor instanceof PsiField && "length".equals(accessor.getName()) && PsiUtil.isArrayClass(accessor.getContainingClass()); - } + @Override + boolean isMyAccessor(PsiMember accessor) { + return accessor instanceof PsiField && "length".equals(accessor.getName()) && PsiUtil.isArrayClass(accessor.getContainingClass()); + } - @Nonnull - @Override - DfType fromInitializer(PsiExpression initializer) { - if (initializer instanceof PsiArrayInitializerExpression) { - return DfTypes.intValue(((PsiArrayInitializerExpression) initializer).getInitializers().length); - } - if (initializer instanceof PsiNewExpression) { - PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression) initializer).getArrayInitializer(); - if (arrayInitializer != null) { - return DfTypes.intValue(arrayInitializer.getInitializers().length); - } - PsiExpression[] dimensions = ((PsiNewExpression) initializer).getArrayDimensions(); - if (dimensions.length > 0) { - Object length = ExpressionUtils.computeConstantExpression(dimensions[0]); - if (length instanceof Integer) { - return DfTypes.intValue(((Integer) length).intValue()); - } - } - } - return DfTypes.TOP; - } - }, - STRING_LENGTH("length", "special.field.string.length", true) { - @Nonnull - @Override - DfType fromInitializer(PsiExpression initializer) { - return fromConstant(ExpressionUtils.computeConstantExpression(initializer)); - } + @Override + DfType fromInitializer(PsiExpression initializer) { + if (initializer instanceof PsiArrayInitializerExpression arrayInitializer) { + return DfTypes.intValue(arrayInitializer.getInitializers().length); + } + if (initializer instanceof PsiNewExpression newExpr) { + PsiArrayInitializerExpression arrayInitializer = newExpr.getArrayInitializer(); + if (arrayInitializer != null) { + return DfTypes.intValue(arrayInitializer.getInitializers().length); + } + PsiExpression[] dimensions = newExpr.getArrayDimensions(); + if (dimensions.length > 0 && ExpressionUtils.computeConstantExpression(dimensions[0]) instanceof Integer length) { + return DfTypes.intValue(length); + } + } + return DfTypes.TOP; + } + }, + STRING_LENGTH("length", JavaAnalysisLocalize.specialFieldStringLength(), true) { + @Override + DfType fromInitializer(PsiExpression initializer) { + return fromConstant(ExpressionUtils.computeConstantExpression(initializer)); + } - @Override - boolean isMyQualifierType(PsiType type) { - return TypeUtils.isJavaLangString(type); - } + @Override + boolean isMyQualifierType(PsiType type) { + return TypeUtils.isJavaLangString(type); + } - @Override - boolean isMyAccessor(PsiMember accessor) { - if (!(accessor instanceof PsiMethod) || !"length".equals(accessor.getName()) || !((PsiMethod) accessor).getParameterList().isEmpty()) { - return false; - } - PsiClass containingClass = accessor.getContainingClass(); - return containingClass != null && JAVA_LANG_STRING.equals(containingClass.getQualifiedName()); - } + @Override + boolean isMyAccessor(PsiMember accessor) { + if (!(accessor instanceof PsiMethod) + || !"length".equals(accessor.getName()) + || !((PsiMethod)accessor).getParameterList().isEmpty()) { + return false; + } + PsiClass containingClass = accessor.getContainingClass(); + return containingClass != null && JAVA_LANG_STRING.equals(containingClass.getQualifiedName()); + } - @Nonnull - @Override - public DfType fromConstant(@Nullable Object obj) { - return obj instanceof String ? DfTypes.intValue(((String) obj).length()) : DfTypes.TOP; - } - }, - COLLECTION_SIZE("size", "special.field.collection.size", false) { - private final CallMatcher SIZE_METHODS = CallMatcher.anyOf(CallMatcher.instanceCall(JAVA_UTIL_COLLECTION, "size").parameterCount(0), - CallMatcher.instanceCall(JAVA_UTIL_MAP, "size").parameterCount(0)); - private final CallMatcher MAP_COLLECTIONS = CallMatcher.instanceCall(JAVA_UTIL_MAP, "keySet", "entrySet", "values") - .parameterCount(0); + @Override + public DfType fromConstant(@Nullable Object obj) { + return obj instanceof String string ? DfTypes.intValue(string.length()) : DfTypes.TOP; + } + }, + COLLECTION_SIZE("size", JavaAnalysisLocalize.specialFieldCollectionSize(), false) { + private final CallMatcher SIZE_METHODS = CallMatcher.anyOf( + CallMatcher.instanceCall(JAVA_UTIL_COLLECTION, "size").parameterCount(0), + CallMatcher.instanceCall(JAVA_UTIL_MAP, "size").parameterCount(0) + ); + private final CallMatcher MAP_COLLECTIONS = + CallMatcher.instanceCall(JAVA_UTIL_MAP, "keySet", "entrySet", "values").parameterCount(0); + + @Override + boolean isMyQualifierType(PsiType type) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + return psiClass != null && !InheritanceUtil.processSupers( + psiClass, + true, + cls -> { + String qualifiedName = cls.getQualifiedName(); + return !JAVA_UTIL_MAP.equals(qualifiedName) && !JAVA_UTIL_COLLECTION.equals(qualifiedName); + } + ); + } - @Override - boolean isMyQualifierType(PsiType type) { - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (psiClass == null) { - return false; - } - return !InheritanceUtil.processSupers(psiClass, true, cls -> { - String qualifiedName = cls.getQualifiedName(); - return !JAVA_UTIL_MAP.equals(qualifiedName) && !JAVA_UTIL_COLLECTION.equals(qualifiedName); - }); - } + @Override + boolean isMyAccessor(PsiMember accessor) { + return accessor instanceof PsiMethod && SIZE_METHODS.methodMatches((PsiMethod)accessor); + } - @Override - boolean isMyAccessor(PsiMember accessor) { - return accessor instanceof PsiMethod && SIZE_METHODS.methodMatches((PsiMethod) accessor); - } + @Override + public DfType fromConstant(@Nullable Object obj) { + if (obj instanceof PsiField && DfaUtil.isEmptyCollectionConstantField((PsiVariable)obj)) { + return DfTypes.intValue(0); + } + return super.fromConstant(obj); + } - @Nonnull - @Override - public DfType fromConstant(@Nullable Object obj) { - if (obj instanceof PsiField && DfaUtil.isEmptyCollectionConstantField((PsiVariable) obj)) { - return DfTypes.intValue(0); - } - return super.fromConstant(obj); - } + @Override + public DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (qualifier instanceof DfaVariableValue var + && var.getQualifier() != null + && var.getPsiVariable() instanceof PsiMethod method + && MAP_COLLECTIONS.methodMatches(method)) { + return super.createValue(factory, var.getQualifier(), forAccessor); + } + return super.createValue(factory, qualifier, forAccessor); + } + }, + UNBOX("value", JavaAnalysisLocalize.specialFieldUnboxedValue(), true) { + private final CallMatcher UNBOXING_CALL = CallMatcher.anyOf( + CallMatcher.exactInstanceCall(JAVA_LANG_INTEGER, "intValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_LONG, "longValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_SHORT, "shortValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_BYTE, "byteValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_CHARACTER, "charValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_BOOLEAN, "booleanValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_FLOAT, "floatValue").parameterCount(0), + CallMatcher.exactInstanceCall(JAVA_LANG_DOUBLE, "doubleValue").parameterCount(0) + ); + + @Override + public PsiPrimitiveType getType(DfaVariableValue variableValue) { + return PsiPrimitiveType.getUnboxedType(variableValue.getType()); + } - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (qualifier instanceof DfaVariableValue) { - DfaVariableValue var = (DfaVariableValue) qualifier; - PsiModifierListOwner owner = var.getPsiVariable(); - if (var.getQualifier() != null && owner instanceof PsiMethod && MAP_COLLECTIONS.methodMatches((PsiMethod) owner)) { - return super.createValue(factory, var.getQualifier(), forAccessor); - } - } - return super.createValue(factory, qualifier, forAccessor); - } - }, - UNBOX("value", "special.field.unboxed.value", true) { - private final CallMatcher UNBOXING_CALL = CallMatcher.anyOf( - CallMatcher.exactInstanceCall(JAVA_LANG_INTEGER, "intValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_LONG, "longValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_SHORT, "shortValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_BYTE, "byteValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_CHARACTER, "charValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_BOOLEAN, "booleanValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_FLOAT, "floatValue").parameterCount(0), - CallMatcher.exactInstanceCall(JAVA_LANG_DOUBLE, "doubleValue").parameterCount(0) - ); + @Override + public DfType getDefaultValue(boolean forAccessor) { + return DfTypes.TOP; + } - @Override - public PsiPrimitiveType getType(DfaVariableValue variableValue) { - return PsiPrimitiveType.getUnboxedType(variableValue.getType()); + @Override + public DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + return qualifier instanceof DfaBoxedValue boxedValue + ? boxedValue.getWrappedValue() + : super.createValue(factory, qualifier, forAccessor); + } + + @Override + boolean isMyQualifierType(PsiType type) { + return TypeConversionUtil.isPrimitiveWrapper(type); + } + + @Override + boolean isMyAccessor(PsiMember accessor) { + return accessor instanceof PsiMethod && UNBOXING_CALL.methodMatches((PsiMethod)accessor); + } + }, + OPTIONAL_VALUE("value", JavaAnalysisLocalize.specialFieldOptionalValue(), true) { + @Override + public PsiType getType(DfaVariableValue variableValue) { + PsiType optionalType = variableValue.getType(); + PsiType type = OptionalUtil.getOptionalElementType(optionalType); + return type instanceof PsiPrimitiveType primitiveType + ? primitiveType.getBoxedType(Objects.requireNonNull(((PsiClassType)optionalType).resolve())) + : type; + } + + @Override + public DfType getDefaultValue(boolean forAccessor) { + return (forAccessor ? DfaNullability.NOT_NULL : DfaNullability.NULLABLE).asDfType(); + } + + @Override + boolean isMyQualifierType(PsiType type) { + return TypeUtils.isOptional(type); + } + + @Override + public String getPresentationText(DfType dfType, @Nullable PsiType type) { + if (dfType == DfTypes.NULL) { + return JavaAnalysisLocalize.dftypePresentationEmptyOptional().get(); + } + if ((!dfType.isSuperType(DfTypes.NULL))) { + return JavaAnalysisLocalize.dftypePresentationPresentOptional().get(); + } + return ""; + } + + @Override + boolean isMyAccessor(PsiMember accessor) { + return accessor instanceof PsiMethod method && OptionalUtil.OPTIONAL_GET.methodMatches(method); + } + }; + + private static final SpecialField[] VALUES = values(); + private final String myTitle; + private final LocalizeValue myTitleValue; + private final boolean myFinal; + + SpecialField(String title, LocalizeValue titleValue, boolean isFinal) { + myTitle = title; + myTitleValue = titleValue; + myFinal = isFinal; } - @Nonnull @Override - public DfType getDefaultValue(boolean forAccessor) { - return DfTypes.TOP; + public boolean isStable() { + return myFinal; + } + + abstract boolean isMyQualifierType(PsiType type); + + /** + * Checks whether supplied accessor (field or method) can be used to read this special field + * + * @param accessor accessor to test to test + * @return true if supplied accessor can be used to read this special field + */ + abstract boolean isMyAccessor(PsiMember accessor); + + public + String getPresentationText(DfType dfType, @Nullable PsiType type) { + return getDefaultValue(false).equals(dfType) ? "" : dfType.toString(); + } + + /** + * Finds a special field which corresponds to given accessor (method or field) + * + * @param accessor accessor to find a special field for + * @return found special field or null if accessor cannot be used to access a special field + */ + @Contract("null -> null") + @Nullable + public static SpecialField findSpecialField(PsiElement accessor) { + if (!(accessor instanceof PsiMember)) { + return null; + } + PsiMember member = (PsiMember)accessor; + for (SpecialField sf : VALUES) { + if (sf.isMyAccessor(member)) { + return sf; + } + } + return null; } - @Nonnull + /** + * Returns a DfaValue which represents this special field + * + * @param factory a factory to create new values if necessary + * @param qualifier a known qualifier value + * @return a DfaValue which represents this special field + */ @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (qualifier instanceof DfaBoxedValue) { - return ((DfaBoxedValue) qualifier).getWrappedValue(); - } - return super.createValue(factory, qualifier, forAccessor); + public final DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier) { + return createValue(factory, qualifier, false); } @Override - boolean isMyQualifierType(PsiType type) { - return TypeConversionUtil.isPrimitiveWrapper(type); + public DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (qualifier instanceof DfaVariableValue variableValue) { + PsiModifierListOwner psiVariable = variableValue.getPsiVariable(); + if (psiVariable instanceof PsiField field + && factory.canTrustFieldInitializer(field) + && field.isStatic() && field.isFinal()) { + PsiExpression initializer = field.getInitializer(); + if (initializer != null) { + DfType dfType = fromInitializer(initializer); + if (dfType != DfTypes.TOP) { + return factory.fromDfType(dfType); + } + } + } + return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); + } + DfType dfType = qualifier == null ? DfTypes.TOP : getFromQualifier(qualifier.getDfType()); + return factory.fromDfType(dfType.meet(getDefaultValue(forAccessor))); } - @Override - boolean isMyAccessor(PsiMember accessor) { - return accessor instanceof PsiMethod && UNBOXING_CALL.methodMatches((PsiMethod) accessor); + /** + * Returns a dfType that describes any possible value this special field may have + * + * @param forAccessor if true, the default value for accessor result should be returned + * (may differ from internal representation of value) + * @return a dfType for the default value + */ + public DfType getDefaultValue(boolean forAccessor) { + return DfTypes.intRange(LongRangeSet.indexRange()); } - }, - OPTIONAL_VALUE("value", "special.field.optional.value", true) { + @Override public PsiType getType(DfaVariableValue variableValue) { - PsiType optionalType = variableValue.getType(); - PsiType type = OptionalUtil.getOptionalElementType(optionalType); - if (type instanceof PsiPrimitiveType) { - return ((PsiPrimitiveType) type).getBoxedType(Objects.requireNonNull(((PsiClassType) optionalType).resolve())); - } - return type; + return PsiType.INT; } - @Nonnull - @Override - public DfType getDefaultValue(boolean forAccessor) { - return (forAccessor ? DfaNullability.NOT_NULL : DfaNullability.NULLABLE).asDfType(); + DfType fromInitializer(PsiExpression initializer) { + return DfTypes.TOP; } - @Override - boolean isMyQualifierType(PsiType type) { - return TypeUtils.isOptional(type); + public DfType fromConstant(@Nullable Object obj) { + return DfTypes.TOP; + } + + /** + * @return a list of method contracts which equivalent to checking this special field for zero + */ + public MethodContract[] getEmptyContracts() { + ContractValue thisValue = ContractValue.qualifier().specialField(this); + return new MethodContract[]{ + MethodContract.singleConditionContract(thisValue, RelationType.EQ, ContractValue.zero(), returnTrue()), + MethodContract.trivialContract(returnFalse()) + }; + } + + public MethodContract[] getEqualsContracts() { + return new MethodContract[]{ + new StandardMethodContract(new StandardMethodContract.ValueConstraint[]{NULL_VALUE}, returnFalse()), + MethodContract.singleConditionContract( + ContractValue.qualifier().specialField(this), RelationType.NE, + ContractValue.argument(0).specialField(this), returnFalse() + ) + }; + } + + /** + * @param fieldValue dfType of the special field value + * @return a dfType that represents a value having this special field restricted to the supplied dfType + */ + public DfType asDfType(DfType fieldValue) { + DfType defaultType = this == OPTIONAL_VALUE ? DfTypes.OBJECT_OR_NULL : getDefaultValue(false); + DfType clamped = fieldValue.meet(defaultType); + if (clamped.equals(defaultType)) { + return DfTypes.NOT_NULL_OBJECT; + } + if (clamped.equals(DfTypes.BOTTOM)) { + return DfTypes.BOTTOM; + } + return DfTypes.customObject(TypeConstraints.TOP, DfaNullability.NOT_NULL, Mutability.UNKNOWN, this, clamped); + } + + /** + * @param fieldValue dfType of the special field value + * @param exactResultType exact PSI type of the result + * @return a dfType that represents a value having this special field restricted to the supplied dfType + */ + public DfType asDfType(DfType fieldValue, @Nullable PsiType exactResultType) { + DfType dfType = asDfType(fieldValue); + if (exactResultType == null) { + return dfType; + } + if (this == STRING_LENGTH && DfConstantType.isConst(fieldValue, 0)) { + return DfTypes.constant("", exactResultType); + } + return dfType.meet(TypeConstraints.exact(exactResultType).asDfType()); + } + + /** + * Returns a DfType from given DfType qualifier if it's bound to this special field + * + * @param dfType of the qualifier + * @return en extracted DfType + */ + public DfType getFromQualifier(DfType dfType) { + if (dfType == DfTypes.TOP) { + return DfTypes.TOP; + } + if (!(dfType instanceof DfReferenceType)) { + return DfTypes.BOTTOM; + } + SpecialField sf = ((DfReferenceType)dfType).getSpecialField(); + if (sf == null) { + return DfTypes.TOP; + } + if (sf != this) { + return DfTypes.BOTTOM; + } + return ((DfReferenceType)dfType).getSpecialFieldType(); + } + + /** + * Returns a special field which corresponds to given qualifier type + * (currently it's assumed that only one special field may exist for given qualifier type) + * + * @param type a qualifier type + * @return a special field; null if no special field is available for given type + */ + @Contract("null -> null") + @Nullable + public static SpecialField fromQualifierType(PsiType type) { + if (type == null) { + return null; + } + for (SpecialField value : VALUES) { + if (value.isMyQualifierType(type)) { + return value; + } + } + return null; + } + + /** + * Returns a special field which corresponds to given qualifier + * + * @param value a qualifier value + * @return a special field; null if no special field is detected to be related to given qualifier + */ + @Nullable + public static SpecialField fromQualifier(DfaValue value) { + DfReferenceType dfType = ObjectUtil.tryCast(value.getDfType(), DfReferenceType.class); + if (dfType != null && dfType.getSpecialField() != null) { + return dfType.getSpecialField(); + } + return fromQualifierType(value.getType()); } - @Override - public String getPresentationText(@Nonnull DfType dfType, @Nullable PsiType type) { - if (dfType == DfTypes.NULL) { - return JavaAnalysisBundle.message("dftype.presentation.empty.optional"); - } - if ((!dfType.isSuperType(DfTypes.NULL))) { - return JavaAnalysisBundle.message("dftype.presentation.present.optional"); - } - return ""; + public String getPresentationName() { + return myTitleValue.get(); } @Override - boolean isMyAccessor(PsiMember accessor) { - return accessor instanceof PsiMethod && OptionalUtil.OPTIONAL_GET.methodMatches((PsiMethod) accessor); - } - }; - - private static final SpecialField[] VALUES = values(); - private final String myTitle; - private final - @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) - String myTitleKey; - private final boolean myFinal; - - SpecialField(String title, @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String titleKey, boolean isFinal) { - myTitle = title; - myTitleKey = titleKey; - myFinal = isFinal; - } - - @Override - public boolean isStable() { - return myFinal; - } - - abstract boolean isMyQualifierType(PsiType type); - - /** - * Checks whether supplied accessor (field or method) can be used to read this special field - * - * @param accessor accessor to test to test - * @return true if supplied accessor can be used to read this special field - */ - abstract boolean isMyAccessor(PsiMember accessor); - - public - @Nls - String getPresentationText(@Nonnull DfType dfType, @Nullable PsiType type) { - if (getDefaultValue(false).equals(dfType)) { - return ""; - } - return dfType.toString(); - } - - /** - * Finds a special field which corresponds to given accessor (method or field) - * - * @param accessor accessor to find a special field for - * @return found special field or null if accessor cannot be used to access a special field - */ - @Contract("null -> null") - @Nullable - public static SpecialField findSpecialField(PsiElement accessor) { - if (!(accessor instanceof PsiMember)) { - return null; - } - PsiMember member = (PsiMember) accessor; - for (SpecialField sf : VALUES) { - if (sf.isMyAccessor(member)) { - return sf; - } - } - return null; - } - - /** - * Returns a DfaValue which represents this special field - * - * @param factory a factory to create new values if necessary - * @param qualifier a known qualifier value - * @return a DfaValue which represents this special field - */ - @Override - @Nonnull - public final DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier) { - return createValue(factory, qualifier, false); - } - - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (qualifier instanceof DfaVariableValue) { - DfaVariableValue variableValue = (DfaVariableValue) qualifier; - PsiModifierListOwner psiVariable = variableValue.getPsiVariable(); - if (psiVariable instanceof PsiField && - factory.canTrustFieldInitializer((PsiField) psiVariable) && - psiVariable.hasModifierProperty(PsiModifier.STATIC) && - psiVariable.hasModifierProperty(PsiModifier.FINAL)) { - PsiExpression initializer = ((PsiField) psiVariable).getInitializer(); - if (initializer != null) { - DfType dfType = fromInitializer(initializer); - if (dfType != DfTypes.TOP) { - return factory.fromDfType(dfType); - } - } - } - return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); - } - DfType dfType = qualifier == null ? DfTypes.TOP : getFromQualifier(qualifier.getDfType()); - return factory.fromDfType(dfType.meet(getDefaultValue(forAccessor))); - } - - /** - * Returns a dfType that describes any possible value this special field may have - * - * @param forAccessor if true, the default value for accessor result should be returned - * (may differ from internal representation of value) - * @return a dfType for the default value - */ - @Nonnull - public DfType getDefaultValue(boolean forAccessor) { - return DfTypes.intRange(LongRangeSet.indexRange()); - } - - @Override - public PsiType getType(DfaVariableValue variableValue) { - return PsiType.INT; - } - - @Nonnull - DfType fromInitializer(PsiExpression initializer) { - return DfTypes.TOP; - } - - @Nonnull - public DfType fromConstant(@Nullable Object obj) { - return DfTypes.TOP; - } - - /** - * @return a list of method contracts which equivalent to checking this special field for zero - */ - public MethodContract[] getEmptyContracts() { - ContractValue thisValue = ContractValue.qualifier().specialField(this); - return new MethodContract[]{ - MethodContract.singleConditionContract(thisValue, RelationType.EQ, ContractValue.zero(), returnTrue()), - MethodContract.trivialContract(returnFalse()) - }; - } - - public MethodContract[] getEqualsContracts() { - return new MethodContract[]{ - new StandardMethodContract(new StandardMethodContract.ValueConstraint[]{NULL_VALUE}, returnFalse()), - MethodContract.singleConditionContract( - ContractValue.qualifier().specialField(this), RelationType.NE, - ContractValue.argument(0).specialField(this), returnFalse()) - }; - } - - /** - * @param fieldValue dfType of the special field value - * @return a dfType that represents a value having this special field restricted to the supplied dfType - */ - @Nonnull - public DfType asDfType(@Nonnull DfType fieldValue) { - DfType defaultType = this == OPTIONAL_VALUE ? DfTypes.OBJECT_OR_NULL : getDefaultValue(false); - DfType clamped = fieldValue.meet(defaultType); - if (clamped.equals(defaultType)) { - return DfTypes.NOT_NULL_OBJECT; - } - if (clamped.equals(DfTypes.BOTTOM)) { - return DfTypes.BOTTOM; - } - return DfTypes.customObject(TypeConstraints.TOP, DfaNullability.NOT_NULL, Mutability.UNKNOWN, this, clamped); - } - - /** - * @param fieldValue dfType of the special field value - * @param exactResultType exact PSI type of the result - * @return a dfType that represents a value having this special field restricted to the supplied dfType - */ - @Nonnull - public DfType asDfType(@Nonnull DfType fieldValue, @Nullable PsiType exactResultType) { - DfType dfType = asDfType(fieldValue); - if (exactResultType == null) { - return dfType; - } - if (this == STRING_LENGTH && DfConstantType.isConst(fieldValue, 0)) { - return DfTypes.constant("", exactResultType); - } - return dfType.meet(TypeConstraints.exact(exactResultType).asDfType()); - } - - /** - * Returns a DfType from given DfType qualifier if it's bound to this special field - * - * @param dfType of the qualifier - * @return en extracted DfType - */ - @Nonnull - public DfType getFromQualifier(@Nonnull DfType dfType) { - if (dfType == DfTypes.TOP) { - return DfTypes.TOP; - } - if (!(dfType instanceof DfReferenceType)) { - return DfTypes.BOTTOM; - } - SpecialField sf = ((DfReferenceType) dfType).getSpecialField(); - if (sf == null) { - return DfTypes.TOP; - } - if (sf != this) { - return DfTypes.BOTTOM; - } - return ((DfReferenceType) dfType).getSpecialFieldType(); - } - - /** - * Returns a special field which corresponds to given qualifier type - * (currently it's assumed that only one special field may exist for given qualifier type) - * - * @param type a qualifier type - * @return a special field; null if no special field is available for given type - */ - @Contract("null -> null") - @Nullable - public static SpecialField fromQualifierType(PsiType type) { - if (type == null) { - return null; - } - for (SpecialField value : VALUES) { - if (value.isMyQualifierType(type)) { - return value; - } - } - return null; - } - - /** - * Returns a special field which corresponds to given qualifier - * - * @param value a qualifier value - * @return a special field; null if no special field is detected to be related to given qualifier - */ - @Nullable - public static SpecialField fromQualifier(@Nonnull DfaValue value) { - DfReferenceType dfType = ObjectUtil.tryCast(value.getDfType(), DfReferenceType.class); - if (dfType != null && dfType.getSpecialField() != null) { - return dfType.getSpecialField(); + public String toString() { + return myTitle; } - return fromQualifierType(value.getType()); - } - - public - @Nonnull - @Nls - String getPresentationName() { - return JavaAnalysisBundle.message(myTitleKey); - } - - @Override - public String toString() { - return myTitle; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardInstructionVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardInstructionVisitor.java index 4efe776c33..af669f100d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -20,8 +20,8 @@ import consulo.project.Project; import consulo.util.lang.ThreeState; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.*; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.*; @@ -101,10 +101,10 @@ public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlow return nextInstruction(instruction, runner, memState); } - private void checkArrayElementAssignability(@Nonnull DfaMemoryState memState, - @Nonnull DfaValue dfaSource, - @Nonnull DfaValue dfaDest, - @Nonnull PsiExpression lValue, + private void checkArrayElementAssignability(DfaMemoryState memState, + DfaValue dfaSource, + DfaValue dfaDest, + PsiExpression lValue, @Nullable PsiExpression rValue) { if (rValue == null) { return; @@ -210,9 +210,9 @@ public DfaInstructionState[] visitArrayAccess(ArrayAccessInstruction instruction return nextInstruction(instruction, runner, memState); } - private static boolean applyBoundsCheck(@Nonnull DfaMemoryState memState, - @Nonnull DfaValue array, - @Nonnull DfaValue index) { + private static boolean applyBoundsCheck(DfaMemoryState memState, + DfaValue array, + DfaValue index) { DfaValueFactory factory = index.getFactory(); DfaValue length = SpecialField.ARRAY_LENGTH.createValue(factory, array); DfaCondition lengthMoreThanZero = length.cond(RelationType.GT, factory.getInt(0)); @@ -285,7 +285,6 @@ private void handleMethodReference(DfaValue qualifier, } private static - @Nonnull DfaCallArguments getMethodReferenceCallArguments(PsiMethodReferenceExpression methodRef, DfaValue qualifier, DataFlowRunner runner, @@ -361,9 +360,9 @@ public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, Data return result.toArray(DfaInstructionState.EMPTY_ARRAY); } - private static boolean castTopOfStack(@Nonnull DfaValueFactory factory, - @Nonnull DfaMemoryState state, - @Nonnull TypeConstraint type) { + private static boolean castTopOfStack(DfaValueFactory factory, + DfaMemoryState state, + TypeConstraint type) { DfaValue value = state.peek(); DfType dfType = state.getDfType(value); DfType result = dfType.meet(type.asDfType()); @@ -382,10 +381,10 @@ private static boolean castTopOfStack(@Nonnull DfaValueFactory factory, protected void onTypeCast(PsiTypeCastExpression castExpression, DfaMemoryState state, boolean castPossible) { } - protected void onMethodCall(@Nonnull DfaValue result, - @Nonnull PsiExpression expression, - @Nonnull DfaCallArguments arguments, - @Nonnull DfaMemoryState memState) { + protected void onMethodCall(DfaValue result, + PsiExpression expression, + DfaCallArguments arguments, + DfaMemoryState memState) { } @@ -430,7 +429,6 @@ public DfaInstructionState[] visitMethodCall(final MethodCallInstruction instruc return result; } - @Nonnull protected DfaCallArguments popCall(MethodCallInstruction instruction, DfaValueFactory factory, DfaMemoryState memState) { DfaValue[] argValues = popCallArguments(instruction, factory, memState); final DfaValue qualifier = popQualifier(instruction, memState, argValues); @@ -503,11 +501,11 @@ private DfaValue[] popCallArguments(MethodCallInstruction instruction, return argValues; } - protected void reportMutabilityViolation(boolean receiver, @Nonnull PsiElement anchor) { + protected void reportMutabilityViolation(boolean receiver, PsiElement anchor) { } - private DfaValue popQualifier(@Nonnull MethodCallInstruction instruction, - @Nonnull DfaMemoryState memState, + private DfaValue popQualifier(MethodCallInstruction instruction, + DfaMemoryState memState, @Nullable DfaValue[] argValues) { DfaValue value = memState.pop(); if (instruction.getContext() instanceof PsiMethodReferenceExpression) { @@ -533,8 +531,8 @@ private DfaValue popQualifier(@Nonnull MethodCallInstruction instruction, return value; } - private static boolean mayLeakThis(@Nonnull MethodCallInstruction instruction, - @Nonnull DfaMemoryState memState, @Nullable DfaValue[] argValues) { + private static boolean mayLeakThis(MethodCallInstruction instruction, + DfaMemoryState memState, @Nullable DfaValue[] argValues) { MutationSignature signature = instruction.getMutationSignature(); if (signature == MutationSignature.unknown()) { return true; @@ -615,7 +613,7 @@ private Set addContractResults(MethodContract contract, private DfaValue dereference(DfaMemoryState memState, DfaValue value, - @Nullable NullabilityProblemKind.NullabilityProblem problem) { + NullabilityProblemKind.@Nullable NullabilityProblem problem) { boolean ok = checkNotNullable(memState, value, problem); if (value instanceof DfaTypeValue) { DfType dfType = value.getDfType().meet(NOT_NULL_OBJECT); @@ -639,9 +637,8 @@ private DfaValue dereference(DfaMemoryState memState, } private static - @Nonnull PsiMethod findSpecificMethod(PsiElement context, - @Nonnull PsiMethod method, + PsiMethod method, @Nullable PsiType qualifierType) { if (qualifierType == null || !PsiUtil.canBeOverridden(method)) { return method; @@ -659,9 +656,8 @@ PsiMethod findSpecificMethod(PsiElement context, } private static - @Nonnull DfaValue getMethodResultValue(MethodCallInstruction instruction, - @Nonnull DfaCallArguments callArguments, + DfaCallArguments callArguments, DfaMemoryState state, DfaValueFactory factory) { if (callArguments.myArguments != null) { PsiMethod method = instruction.getTargetMethod(); @@ -733,9 +729,8 @@ DfaValue getMethodResultValue(MethodCallInstruction instruction, } private static - @Nonnull - PsiType narrowReturnType(@Nonnull PsiType returnType, @Nullable PsiType qualifierType, - @Nonnull PsiMethod realMethod) { + PsiType narrowReturnType(PsiType returnType, @Nullable PsiType qualifierType, + PsiMethod realMethod) { PsiClass containingClass = realMethod.getContainingClass(); PsiType realReturnType = realMethod.getReturnType(); if (containingClass != null && qualifierType instanceof PsiClassType) { @@ -778,7 +773,7 @@ private static DfaValue getPrecalculatedResult(@Nullable DfaValue qualifierValue return precalculated; } - protected boolean checkNotNullable(DfaMemoryState state, @Nonnull DfaValue value, @Nullable NullabilityProblemKind.NullabilityProblem problem) { + protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblemKind.@Nullable NullabilityProblem problem) { boolean notNullable = state.checkNotNullable(value); if (notNullable && problem != null && problem.thrownException() != null) { state.applyCondition(value.cond(RelationType.NE, value.getFactory().getNull())); @@ -878,7 +873,6 @@ public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRu return nextInstruction(instruction, runner, memState); } - @Nonnull private DfaInstructionState[] handleAndOrBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, @@ -903,7 +897,6 @@ private DfaInstructionState[] handleAndOrBinop(BinopInstruction instruction, } private static - @Nonnull DfaValue concatStrings(DfaValue left, DfaValue right, DfaMemoryState memState, @@ -923,7 +916,6 @@ DfaValue concatStrings(DfaValue left, return factory.fromDfType(SpecialField.STRING_LENGTH.asDfType(intRange(resultRange), stringType)); } - @Nonnull private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, @@ -975,7 +967,6 @@ private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction, return states.toArray(DfaInstructionState.EMPTY_ARRAY); } - @Nonnull private static RelationType[] splitRelation(RelationType relationType) { switch (relationType) { case LT: @@ -1056,7 +1047,7 @@ public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, private DfaInstructionState makeBooleanResult(ExpressionPushingInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, - @Nonnull ThreeState result) { + ThreeState result) { DfaValue value = result == ThreeState.UNSURE ? runner.getFactory().getUnknown() : runner.getFactory().getBoolean(result.toBoolean()); pushExpressionResult(value, instruction, memState); return new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardMethodContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardMethodContract.java index 27beefc50e..6070643139 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardMethodContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StandardMethodContract.java @@ -1,18 +1,18 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.RelationType; import consulo.document.util.TextRange; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.localize.LocalizeValue; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -26,468 +26,473 @@ * @author peter */ public final class StandardMethodContract extends MethodContract { - @Nonnull - private final ValueConstraint[] myParameters; - - public StandardMethodContract(@Nonnull ValueConstraint[] parameters, @Nonnull ContractReturnValue returnValue) { - super(returnValue); - myParameters = parameters; - } - - public int getParameterCount() { - return myParameters.length; - } - - public ValueConstraint getParameterConstraint(int parameterIndex) { - return myParameters[parameterIndex]; - } - - public List getConstraints() { - return List.of(myParameters); - } - - public - @Nonnull - StandardMethodContract withReturnValue(@Nonnull ContractReturnValue returnValue) { - return returnValue.equals(getReturnValue()) ? this : new StandardMethodContract(myParameters, returnValue); - } - - public static StandardMethodContract trivialContract(int paramCount, @Nonnull ContractReturnValue returnValue) { - return new StandardMethodContract(createConstraintArray(paramCount), returnValue); - } - - /** - * Creates a new contract which is an intersection of this and supplied contracts - * - * @param contract a contract to intersect with - * @return intersection contract or null if no intersection is possible - */ - @Nullable - public StandardMethodContract intersect(StandardMethodContract contract) { - ValueConstraint[] result = myParameters.clone(); - assert contract.getParameterCount() == result.length; - for (int i = 0; i < result.length; i++) { - ValueConstraint condition = result[i]; - ValueConstraint constraint = contract.getParameterConstraint(i); - if (condition == constraint || condition == ValueConstraint.ANY_VALUE) { - result[i] = constraint; - } else if (constraint == ValueConstraint.ANY_VALUE) { - result[i] = condition; - } else if (condition == ValueConstraint.NOT_NULL_VALUE && - (constraint == ValueConstraint.TRUE_VALUE || constraint == ValueConstraint.FALSE_VALUE)) { - // java.lang.Boolean - result[i] = constraint; - } else if (constraint == ValueConstraint.NOT_NULL_VALUE && - (condition == ValueConstraint.TRUE_VALUE || condition == ValueConstraint.FALSE_VALUE)) { - // java.lang.Boolean - result[i] = condition; - } else { - return null; - } - } - return new StandardMethodContract(result, getReturnValue().intersect(contract.getReturnValue())); - } - - /** - * Creates a stream of contracts which describe all states covered by this contract but not covered by - * supplied contract. - * - * @param contract contract to exclude - * @return a stream of exclusion contracts (could be empty) - */ - @Nonnull - public Stream excludeContract(StandardMethodContract contract) { - assert contract.getParameterCount() == myParameters.length; - List constraints = contract.getConstraints(); - List template = StreamEx.constant(ValueConstraint.ANY_VALUE, myParameters.length).toList(); - List antiContracts = new ArrayList<>(); - for (int i = 0; i < constraints.size(); i++) { - ValueConstraint constraint = constraints.get(i); - if (constraint == ValueConstraint.ANY_VALUE) { - continue; - } - template.set(i, constraint.negate()); - antiContracts.add(new StandardMethodContract(template.toArray(new ValueConstraint[0]), getReturnValue())); - template.set(i, constraint); - } - return StreamEx.of(antiContracts).map(this::intersect).nonNull(); - } - - /** - * Try merge two contracts into one preserving their full meaning - * - * @param other other contract to merge into this - * @return merged contract or null if unable to merge - */ - public StandardMethodContract tryCollapse(StandardMethodContract other) { - if (!other.getReturnValue().equals(getReturnValue())) { - return null; - } - ValueConstraint[] thisParameters = this.myParameters; - ValueConstraint[] thatParameters = other.myParameters; - if (thatParameters.length != thisParameters.length) { - return null; - } - ValueConstraint[] result = null; - for (int i = 0; i < thisParameters.length; i++) { - ValueConstraint thisConstraint = thisParameters[i]; - ValueConstraint thatConstraint = thatParameters[i]; - if (thisConstraint != thatConstraint) { - if (result != null || !thisConstraint.canBeNegated() || thisConstraint.negate() != thatConstraint) { - return null; - } - result = thisParameters.clone(); - result[i] = ValueConstraint.ANY_VALUE; - } - } - return result == null ? null : new StandardMethodContract(result, getReturnValue()); - } - - /** - * Converts list of contracts which are equivalent to the passed list, but independent on the order - * (e.g. {@code "null -> null, _ -> !null"} will be converted to {@code "null -> null, !null -> !null"}). Also removes unreachable - * contracts if any. - * - * @param contracts list of input contracts to process (assumed that they are applied in the specified order) - * @return list of equivalent non-intersecting contracts or null if the result is too big or the input list contains errors - * (e.g. contracts with different parameter count). null When result is too big or contracts are erroneous - */ - @Nullable - public static List toNonIntersectingStandardContracts(List contracts) { - if (contracts.isEmpty()) { - return contracts; - } - int paramCount = contracts.get(0).getParameterCount(); - List result = new ArrayList<>(); - List leftovers = Collections.singletonList(trivialContract(paramCount, ContractReturnValue.returnAny())); - for (StandardMethodContract contract : contracts) { - if (contract.getParameterCount() != paramCount) { - return null; - } - StreamEx.of(leftovers).map(c -> c.intersect(contract)).nonNull().into(result); - if (result.size() >= DataFlowRunner.MAX_STATES_PER_BRANCH) { - return null; - } - leftovers = StreamEx.of(leftovers).flatMap(c -> c.excludeContract(contract)).toList(); - if (leftovers.isEmpty()) { - break; - } - } - return result; - } - - @Nonnull - public static ValueConstraint[] createConstraintArray(int paramCount) { - ValueConstraint[] args = new ValueConstraint[paramCount]; - Arrays.fill(args, ValueConstraint.ANY_VALUE); - return args; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || o.getClass() != getClass()) { - return false; - } + private final ValueConstraint[] myParameters; - StandardMethodContract contract = (StandardMethodContract) o; - return Arrays.equals(myParameters, contract.myParameters) && getReturnValue().equals(contract.getReturnValue()); - } - - @Override - public int hashCode() { - int result = 0; - for (ValueConstraint argument : myParameters) { - result = 31 * result + argument.ordinal(); - } - result = 31 * result + getReturnValue().hashCode(); - return result; - } - - @Override - String getArgumentsPresentation() { - return StringUtil.join(myParameters, ValueConstraint::toString, ", "); - } - - @Override - public List getConditions() { - return IntStreamEx.ofIndices(myParameters).mapToObj(idx -> myParameters[idx].getCondition(idx)).without(ContractValue.booleanValue(true)) - .toList(); - } - - public static List parseContract(@Nonnull String text) throws ParseException { - if (StringUtil.isEmptyOrSpaces(text)) { - return Collections.emptyList(); + public StandardMethodContract(ValueConstraint[] parameters, ContractReturnValue returnValue) { + super(returnValue); + myParameters = parameters; } - List result = new ArrayList<>(); - String[] split = StringUtil.replace(text, " ", "").split(";"); - for (int clauseIndex = 0; clauseIndex < split.length; clauseIndex++) { - String clause = split[clauseIndex]; - result.add(fromText(text, clauseIndex, clause)); - } - return result; - } - - /** - * Create single contract from text. Used to initialize some hard-coded contracts only. - * - * @param clause contract clause like "_, null -> false" - * @return created contract - * @throws RuntimeException in case of parse error - * @see HardcodedContracts - */ - static - @Nonnull - StandardMethodContract fromText(@Nonnull String clause) { - try { - return fromText(clause, 0, clause); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } - - private static - @Nonnull - StandardMethodContract fromText(@Nonnull String text, int clauseIndex, @Nonnull String clause) - throws ParseException { - String arrow = "->"; - int arrowIndex = clause.indexOf(arrow); - if (arrowIndex < 0) { - throw ParseException.forClause(JavaAnalysisBundle.message("inspection.contract.checker.clause.syntax"), text, clauseIndex); + public int getParameterCount() { + return myParameters.length; } - String beforeArrow = clause.substring(0, arrowIndex); - ValueConstraint[] args; - if (StringUtil.isNotEmpty(beforeArrow)) { - String[] argStrings = beforeArrow.split(","); - args = new ValueConstraint[argStrings.length]; - for (int i = 0; i < args.length; i++) { - args[i] = parseConstraint(argStrings[i], text, clauseIndex, i); - } - } else { - args = new ValueConstraint[0]; - } - String returnValueString = clause.substring(arrowIndex + arrow.length()); - ContractReturnValue returnValue = ContractReturnValue.valueOf(returnValueString); - if (returnValue == null) { - String possibleValues = "null, !null, true, false, this, new, paramN, fail, _"; - String message = JavaAnalysisBundle.message("inspection.contract.checker.unknown.return.value", possibleValues, returnValueString); - throw ParseException.forReturnValue(message, text, clauseIndex); + public ValueConstraint getParameterConstraint(int parameterIndex) { + return myParameters[parameterIndex]; } - return new StandardMethodContract(args, returnValue); - } - private static ValueConstraint parseConstraint(String name, String text, int clauseIndex, int constraintIndex) throws ParseException { - if (StringUtil.isEmpty(name)) { - throw new ParseException(JavaAnalysisBundle.message("inspection.contract.checker.empty.constraint")); - } - for (ValueConstraint constraint : ValueConstraint.values()) { - if (constraint.toString().equals(name)) { - return constraint; - } - } - String allowedClause = StreamEx.of(ValueConstraint.values()).joining(", "); - String message = JavaAnalysisBundle.message("inspection.contract.checker.unknown.constraint", allowedClause, name); - throw ParseException.forConstraint(message, text, clauseIndex, constraintIndex); - } - - public enum ValueConstraint { - ANY_VALUE("_", ContractReturnValue.returnAny()), - NULL_VALUE("null", ContractReturnValue.returnNull()), - NOT_NULL_VALUE("!null", ContractReturnValue.returnNotNull()), - TRUE_VALUE("true", ContractReturnValue.returnTrue()), - FALSE_VALUE("false", ContractReturnValue.returnFalse()); - - private final String myPresentableName; - private final ContractReturnValue myCorrespondingReturnValue; - - ValueConstraint(String presentableName, ContractReturnValue correspondingReturnValue) { - myPresentableName = presentableName; - myCorrespondingReturnValue = correspondingReturnValue; + public List getConstraints() { + return List.of(myParameters); } - public ContractReturnValue asReturnValue() { - return myCorrespondingReturnValue; + public + StandardMethodContract withReturnValue(ContractReturnValue returnValue) { + return returnValue.equals(getReturnValue()) ? this : new StandardMethodContract(myParameters, returnValue); } - @Nullable - DfaValue getComparisonValue(DfaValueFactory factory) { - if (this == NULL_VALUE || this == NOT_NULL_VALUE) { - return factory.getNull(); - } - if (this == TRUE_VALUE || this == FALSE_VALUE) { - return factory.getBoolean(true); - } - return null; + public static StandardMethodContract trivialContract(int paramCount, ContractReturnValue returnValue) { + return new StandardMethodContract(createConstraintArray(paramCount), returnValue); } - boolean shouldUseNonEqComparison() { - return this == NOT_NULL_VALUE || this == FALSE_VALUE; + /** + * Creates a new contract which is an intersection of this and supplied contracts + * + * @param contract a contract to intersect with + * @return intersection contract or null if no intersection is possible + */ + @Nullable + public StandardMethodContract intersect(StandardMethodContract contract) { + ValueConstraint[] result = myParameters.clone(); + assert contract.getParameterCount() == result.length; + for (int i = 0; i < result.length; i++) { + ValueConstraint condition = result[i]; + ValueConstraint constraint = contract.getParameterConstraint(i); + if (condition == constraint || condition == ValueConstraint.ANY_VALUE) { + result[i] = constraint; + } + else if (constraint == ValueConstraint.ANY_VALUE) { + result[i] = condition; + } + else if (condition == ValueConstraint.NOT_NULL_VALUE + && (constraint == ValueConstraint.TRUE_VALUE || constraint == ValueConstraint.FALSE_VALUE)) { + // java.lang.Boolean + result[i] = constraint; + } + else if (constraint == ValueConstraint.NOT_NULL_VALUE + && (condition == ValueConstraint.TRUE_VALUE || condition == ValueConstraint.FALSE_VALUE)) { + // java.lang.Boolean + result[i] = condition; + } + else { + return null; + } + } + return new StandardMethodContract(result, getReturnValue().intersect(contract.getReturnValue())); } /** - * Returns a condition value which should be applied to memory state to satisfy this constraint + * Creates a stream of contracts which describe all states covered by this contract but not covered by + * supplied contract. * - * @param argumentIndex argument number to test - * @return a condition + * @param contract contract to exclude + * @return a stream of exclusion contracts (could be empty) */ - public ContractValue getCondition(int argumentIndex) { - ContractValue left; - if (this == NULL_VALUE || this == NOT_NULL_VALUE) { - left = ContractValue.nullValue(); - } else if (this == TRUE_VALUE || this == FALSE_VALUE) { - left = ContractValue.booleanValue(true); - } else { - return ContractValue.booleanValue(true); - } - return ContractValue.condition(left, RelationType.equivalence(!shouldUseNonEqComparison()), ContractValue.argument(argumentIndex)); + public Stream excludeContract(StandardMethodContract contract) { + assert contract.getParameterCount() == myParameters.length; + List constraints = contract.getConstraints(); + List template = StreamEx.constant(ValueConstraint.ANY_VALUE, myParameters.length).toList(); + List antiContracts = new ArrayList<>(); + for (int i = 0; i < constraints.size(); i++) { + ValueConstraint constraint = constraints.get(i); + if (constraint == ValueConstraint.ANY_VALUE) { + continue; + } + template.set(i, constraint.negate()); + antiContracts.add(new StandardMethodContract(template.toArray(new ValueConstraint[0]), getReturnValue())); + template.set(i, constraint); + } + return StreamEx.of(antiContracts).map(this::intersect).nonNull(); } /** - * @return true if constraint can be negated - * @see #negate() + * Try merge two contracts into one preserving their full meaning + * + * @param other other contract to merge into this + * @return merged contract or null if unable to merge */ - public boolean canBeNegated() { - return this != ANY_VALUE; + public StandardMethodContract tryCollapse(StandardMethodContract other) { + if (!other.getReturnValue().equals(getReturnValue())) { + return null; + } + ValueConstraint[] thisParameters = this.myParameters; + ValueConstraint[] thatParameters = other.myParameters; + if (thatParameters.length != thisParameters.length) { + return null; + } + ValueConstraint[] result = null; + for (int i = 0; i < thisParameters.length; i++) { + ValueConstraint thisConstraint = thisParameters[i]; + ValueConstraint thatConstraint = thatParameters[i]; + if (thisConstraint != thatConstraint) { + if (result != null || !thisConstraint.canBeNegated() || thisConstraint.negate() != thatConstraint) { + return null; + } + result = thisParameters.clone(); + result[i] = ValueConstraint.ANY_VALUE; + } + } + return result == null ? null : new StandardMethodContract(result, getReturnValue()); } /** - * @return negated constraint - * @throws IllegalStateException if constraint cannot be negated - * @see #canBeNegated() + * Converts list of contracts which are equivalent to the passed list, but independent on the order + * (e.g. {@code "null -> null, _ -> !null"} will be converted to {@code "null -> null, !null -> !null"}). Also removes unreachable + * contracts if any. + * + * @param contracts list of input contracts to process (assumed that they are applied in the specified order) + * @return list of equivalent non-intersecting contracts or null if the result is too big or the input list contains errors + * (e.g. contracts with different parameter count). null When result is too big or contracts are erroneous */ - public ValueConstraint negate() { - switch (this) { - case NULL_VALUE: - return NOT_NULL_VALUE; - case NOT_NULL_VALUE: - return NULL_VALUE; - case TRUE_VALUE: - return FALSE_VALUE; - case FALSE_VALUE: - return TRUE_VALUE; - default: - throw new IllegalStateException("ValueConstraint = " + this); - } + @Nullable + public static List toNonIntersectingStandardContracts(List contracts) { + if (contracts.isEmpty()) { + return contracts; + } + int paramCount = contracts.get(0).getParameterCount(); + List result = new ArrayList<>(); + List leftovers = Collections.singletonList(trivialContract(paramCount, ContractReturnValue.returnAny())); + for (StandardMethodContract contract : contracts) { + if (contract.getParameterCount() != paramCount) { + return null; + } + StreamEx.of(leftovers).map(c -> c.intersect(contract)).nonNull().into(result); + if (result.size() >= DataFlowRunner.MAX_STATES_PER_BRANCH) { + return null; + } + leftovers = StreamEx.of(leftovers).flatMap(c -> c.excludeContract(contract)).toList(); + if (leftovers.isEmpty()) { + break; + } + } + return result; } - @Override - public String toString() { - return myPresentableName; + public static ValueConstraint[] createConstraintArray(int paramCount) { + ValueConstraint[] args = new ValueConstraint[paramCount]; + Arrays.fill(args, ValueConstraint.ANY_VALUE); + return args; } - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || o.getClass() != getClass()) { + return false; + } - public static class ParseException extends Exception { - private final - @Nullable - TextRange myRange; + StandardMethodContract contract = (StandardMethodContract)o; + return Arrays.equals(myParameters, contract.myParameters) && getReturnValue().equals(contract.getReturnValue()); + } - ParseException(String message) { - this(message, null); + @Override + public int hashCode() { + int result = 0; + for (ValueConstraint argument : myParameters) { + result = 31 * result + argument.ordinal(); + } + result = 31 * result + getReturnValue().hashCode(); + return result; } - ParseException(String message, @Nullable TextRange range) { - super(message); - myRange = range != null && range.isEmpty() ? null : range; + @Override + String getArgumentsPresentation() { + return StringUtil.join(myParameters, ValueConstraint::toString, ", "); } @Override - public String getMessage() { - return super.getMessage(); + public List getConditions() { + return IntStreamEx.ofIndices(myParameters) + .mapToObj(idx -> myParameters[idx].getCondition(idx)) + .without(ContractValue.booleanValue(true)) + .toList(); } - public - @Nullable - TextRange getRange() { - return myRange; + public static List parseContract(String text) throws ParseException { + if (StringUtil.isEmptyOrSpaces(text)) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + String[] split = StringUtil.replace(text, " ", "").split(";"); + for (int clauseIndex = 0; clauseIndex < split.length; clauseIndex++) { + String clause = split[clauseIndex]; + result.add(fromText(text, clauseIndex, clause)); + } + return result; } - public static ParseException forConstraint(String message, String text, int clauseNumber, int constraintNumber) { - TextRange range = findClauseRange(text, clauseNumber); - if (range == null) { - return new ParseException(message); - } - int start = range.getStartOffset(); - while (constraintNumber > 0) { - start = text.indexOf(',', start); - if (start == -1) { - return new ParseException(message, range); - } - start++; - constraintNumber--; - } - int end = text.indexOf(',', start); - if (end == -1 || end > range.getEndOffset()) { - end = text.indexOf("->", start); - if (end == -1 || end > range.getEndOffset()) { - end = range.getEndOffset(); - } - } - if (!text.substring(start, end).trim().isEmpty()) { - while (text.charAt(start) == ' ') { - start++; - } - while (end > start && text.charAt(end - 1) == ' ') { - end--; - } - } - return new ParseException(message, new TextRange(start, end)); + /** + * Create single contract from text. Used to initialize some hard-coded contracts only. + * + * @param clause contract clause like "_, null -> false" + * @return created contract + * @throws RuntimeException in case of parse error + * @see HardcodedContracts + */ + static + StandardMethodContract fromText(String clause) { + try { + return fromText(clause, 0, clause); + } + catch (ParseException e) { + throw new RuntimeException(e); + } } - public static ParseException forReturnValue(String message, String text, int clauseNumber) { - TextRange range = findClauseRange(text, clauseNumber); - if (range == null) { - return new ParseException(message); - } - int index = text.indexOf("->", range.getStartOffset()); - if (index == -1 || index > range.getEndOffset()) { - return new ParseException(message, range); - } - index += "->".length(); - while (index < range.getEndOffset() && text.charAt(index) == ' ') { - index++; - } - if (index == range.getEndOffset()) { - return new ParseException(message, range); - } - return new ParseException(message, new TextRange(index, range.getEndOffset())); + private static + StandardMethodContract fromText(String text, int clauseIndex, String clause) + throws ParseException { + String arrow = "->"; + int arrowIndex = clause.indexOf(arrow); + if (arrowIndex < 0) { + throw ParseException.forClause(JavaAnalysisLocalize.inspectionContractCheckerClauseSyntax(), text, clauseIndex); + } + + String beforeArrow = clause.substring(0, arrowIndex); + ValueConstraint[] args; + if (StringUtil.isNotEmpty(beforeArrow)) { + String[] argStrings = beforeArrow.split(","); + args = new ValueConstraint[argStrings.length]; + for (int i = 0; i < args.length; i++) { + args[i] = parseConstraint(argStrings[i], text, clauseIndex, i); + } + } + else { + args = new ValueConstraint[0]; + } + String returnValueString = clause.substring(arrowIndex + arrow.length()); + ContractReturnValue returnValue = ContractReturnValue.valueOf(returnValueString); + if (returnValue == null) { + String possibleValues = "null, !null, true, false, this, new, paramN, fail, _"; + LocalizeValue message = JavaAnalysisLocalize.inspectionContractCheckerUnknownReturnValue(possibleValues, returnValueString); + throw ParseException.forReturnValue(message, text, clauseIndex); + } + return new StandardMethodContract(args, returnValue); } - public static ParseException forClause(String message, String text, int clauseNumber) { - TextRange range = findClauseRange(text, clauseNumber); - return range == null ? new ParseException(message) : new ParseException(message, range); + private static ValueConstraint parseConstraint(String name, String text, int clauseIndex, int constraintIndex) throws ParseException { + if (StringUtil.isEmpty(name)) { + throw new ParseException(JavaAnalysisLocalize.inspectionContractCheckerEmptyConstraint()); + } + for (ValueConstraint constraint : ValueConstraint.values()) { + if (constraint.toString().equals(name)) { + return constraint; + } + } + String allowedClause = StreamEx.of(ValueConstraint.values()).joining(", "); + LocalizeValue message = JavaAnalysisLocalize.inspectionContractCheckerUnknownConstraint(allowedClause, name); + throw ParseException.forConstraint(message, text, clauseIndex, constraintIndex); + } + + public enum ValueConstraint { + ANY_VALUE("_", ContractReturnValue.returnAny()), + NULL_VALUE("null", ContractReturnValue.returnNull()), + NOT_NULL_VALUE("!null", ContractReturnValue.returnNotNull()), + TRUE_VALUE("true", ContractReturnValue.returnTrue()), + FALSE_VALUE("false", ContractReturnValue.returnFalse()); + + private final String myPresentableName; + private final ContractReturnValue myCorrespondingReturnValue; + + ValueConstraint(String presentableName, ContractReturnValue correspondingReturnValue) { + myPresentableName = presentableName; + myCorrespondingReturnValue = correspondingReturnValue; + } + + public ContractReturnValue asReturnValue() { + return myCorrespondingReturnValue; + } + + @Nullable + DfaValue getComparisonValue(DfaValueFactory factory) { + if (this == NULL_VALUE || this == NOT_NULL_VALUE) { + return factory.getNull(); + } + if (this == TRUE_VALUE || this == FALSE_VALUE) { + return factory.getBoolean(true); + } + return null; + } + + boolean shouldUseNonEqComparison() { + return this == NOT_NULL_VALUE || this == FALSE_VALUE; + } + + /** + * Returns a condition value which should be applied to memory state to satisfy this constraint + * + * @param argumentIndex argument number to test + * @return a condition + */ + public ContractValue getCondition(int argumentIndex) { + ContractValue left; + if (this == NULL_VALUE || this == NOT_NULL_VALUE) { + left = ContractValue.nullValue(); + } + else if (this == TRUE_VALUE || this == FALSE_VALUE) { + left = ContractValue.booleanValue(true); + } + else { + return ContractValue.booleanValue(true); + } + return ContractValue.condition( + left, + RelationType.equivalence(!shouldUseNonEqComparison()), + ContractValue.argument(argumentIndex) + ); + } + + /** + * @return true if constraint can be negated + * @see #negate() + */ + public boolean canBeNegated() { + return this != ANY_VALUE; + } + + /** + * @return negated constraint + * @throws IllegalStateException if constraint cannot be negated + * @see #canBeNegated() + */ + public ValueConstraint negate() { + switch (this) { + case NULL_VALUE: + return NOT_NULL_VALUE; + case NOT_NULL_VALUE: + return NULL_VALUE; + case TRUE_VALUE: + return FALSE_VALUE; + case FALSE_VALUE: + return TRUE_VALUE; + default: + throw new IllegalStateException("ValueConstraint = " + this); + } + } + + @Override + public String toString() { + return myPresentableName; + } } - private static TextRange findClauseRange(String text, int clauseNumber) { - int start = 0; - while (clauseNumber > 0) { - start = text.indexOf(';', start); - if (start == -1) { - return null; - } - start++; - clauseNumber--; - } - int end = text.indexOf(';', start); - if (end == -1) { - end = text.length(); - } - if (text.substring(start, end).trim().isEmpty()) { - return new TextRange(start, end); - } - - while (text.charAt(start) == ' ') { - start++; - } - while (end > start && text.charAt(end - 1) == ' ') { - end--; - } - - return new TextRange(start, end); + public static class ParseException extends Exception { + @Nullable + private final TextRange myRange; + + ParseException(LocalizeValue message) { + this(message, null); + } + + ParseException(LocalizeValue message, @Nullable TextRange range) { + super(message.get()); + myRange = range != null && range.isEmpty() ? null : range; + } + + @Override + public String getMessage() { + return super.getMessage(); + } + + @Nullable + public TextRange getRange() { + return myRange; + } + + public static ParseException forConstraint(LocalizeValue message, String text, int clauseNumber, int constraintNumber) { + TextRange range = findClauseRange(text, clauseNumber); + if (range == null) { + return new ParseException(message); + } + int start = range.getStartOffset(); + while (constraintNumber > 0) { + start = text.indexOf(',', start); + if (start == -1) { + return new ParseException(message, range); + } + start++; + constraintNumber--; + } + int end = text.indexOf(',', start); + if (end == -1 || end > range.getEndOffset()) { + end = text.indexOf("->", start); + if (end == -1 || end > range.getEndOffset()) { + end = range.getEndOffset(); + } + } + if (!text.substring(start, end).trim().isEmpty()) { + while (text.charAt(start) == ' ') { + start++; + } + while (end > start && text.charAt(end - 1) == ' ') { + end--; + } + } + return new ParseException(message, new TextRange(start, end)); + } + + public static ParseException forReturnValue(LocalizeValue message, String text, int clauseNumber) { + TextRange range = findClauseRange(text, clauseNumber); + if (range == null) { + return new ParseException(message); + } + int index = text.indexOf("->", range.getStartOffset()); + if (index == -1 || index > range.getEndOffset()) { + return new ParseException(message, range); + } + index += "->".length(); + while (index < range.getEndOffset() && text.charAt(index) == ' ') { + index++; + } + if (index == range.getEndOffset()) { + return new ParseException(message, range); + } + return new ParseException(message, new TextRange(index, range.getEndOffset())); + } + + public static ParseException forClause(LocalizeValue message, String text, int clauseNumber) { + TextRange range = findClauseRange(text, clauseNumber); + return range == null ? new ParseException(message) : new ParseException(message, range); + } + + private static TextRange findClauseRange(String text, int clauseNumber) { + int start = 0; + while (clauseNumber > 0) { + start = text.indexOf(';', start); + if (start == -1) { + return null; + } + start++; + clauseNumber--; + } + int end = text.indexOf(';', start); + if (end == -1) { + end = text.length(); + } + if (text.substring(start, end).trim().isEmpty()) { + return new TextRange(start, end); + } + + while (text.charAt(start) == ' ') { + start++; + } + while (end > start && text.charAt(end - 1) == ' ') { + end--; + } + + return new TextRange(start, end); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StateMerger.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StateMerger.java index b48a976144..9314ef6bb9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StateMerger.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/StateMerger.java @@ -13,8 +13,7 @@ import one.util.streamex.LongStreamEx; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; /** @@ -26,7 +25,7 @@ final class StateMerger { private final Map> myCopyCache = new IdentityHashMap<>(); @Nullable - List mergeByFacts(@Nonnull List states) { + List mergeByFacts(List states) { MultiMap statesByFact = createFactToStateMap(states); Set facts = statesByFact.keySet(); @@ -77,8 +76,7 @@ List mergeByFacts(@Nonnull List states) } private - @Nonnull - MultiMap createFactToStateMap(@Nonnull List states) { + MultiMap createFactToStateMap(List states) { MultiMap statesByFact = MultiMap.createLinked(); Map>> constantVars = new HashMap<>(); for (DfaMemoryStateImpl state : states) { @@ -140,10 +138,9 @@ private static boolean isComparisonOfVariablesComparedWithConstant(Fact fact, } private - @Nonnull - MultiMap mapByUnrelatedFacts(@Nonnull Fact fact, - @Nonnull Collection states, - @Nonnull Set interestingFacts) { + MultiMap mapByUnrelatedFacts(Fact fact, + Collection states, + Set interestingFacts) { MultiMap statesByUnrelatedFacts = MultiMap.createLinked(); for (DfaMemoryStateImpl state : states) { statesByUnrelatedFacts.putValue(getUnrelatedFacts(fact, state, interestingFacts), state); @@ -152,10 +149,9 @@ MultiMap mapByUnrelatedFacts(@Nonnull Fact f } private - @Nonnull - CompactFactSet getUnrelatedFacts(final @Nonnull Fact fact, - @Nonnull DfaMemoryStateImpl state, - @Nonnull Set interestingFacts) { + CompactFactSet getUnrelatedFacts(final Fact fact, + DfaMemoryStateImpl state, + Set interestingFacts) { final ArrayList result = new ArrayList<>(); for (Fact other : getFacts(state)) { if (!fact.invalidatesFact(other) && interestingFacts.contains(other)) { @@ -183,7 +179,6 @@ List mergeByRanges(List states) { } private static - @Nonnull Map> createRangeMap(List states) { Map> ranges = new LinkedHashMap<>(); for (DfaMemoryStateImpl state : states) { @@ -220,8 +215,7 @@ List mergeIndependentRanges(List states, } private - @Nonnull - DfaMemoryStateImpl copyWithoutVar(@Nonnull DfaMemoryStateImpl state, @Nonnull DfaVariableValue var) { + DfaMemoryStateImpl copyWithoutVar(DfaMemoryStateImpl state, DfaVariableValue var) { Map map = myCopyCache.computeIfAbsent(state, k -> new IdentityHashMap<>()); DfaMemoryStateImpl copy = map.get(var); if (copy == null) { @@ -234,13 +228,11 @@ DfaMemoryStateImpl copyWithoutVar(@Nonnull DfaMemoryStateImpl state, @Nonnull Df } private - @Nonnull - Set getFacts(@Nonnull DfaMemoryStateImpl state) { + Set getFacts(DfaMemoryStateImpl state) { return myFacts.computeIfAbsent(state, StateMerger::doGetFacts); } private static - @Nonnull Set doGetFacts(DfaMemoryStateImpl state) { Set result = new LinkedHashSet<>(); @@ -328,11 +320,10 @@ public String toString() { abstract static class Fact { final boolean myPositive; final - @Nonnull DfaVariableValue myVar; private final int myHash; - protected Fact(boolean positive, @Nonnull DfaVariableValue var, int hash) { + protected Fact(boolean positive, DfaVariableValue var, int hash) { myPositive = positive; myVar = var; myHash = hash; @@ -356,16 +347,15 @@ public final int hashCode() { } abstract - @Nonnull Fact getPositiveCounterpart(); DfaTypeValue comparedToConstant() { return null; } - abstract boolean invalidatesFact(@Nonnull Fact another); + abstract boolean invalidatesFact(Fact another); - abstract void removeFromState(@Nonnull DfaMemoryStateImpl state); + abstract void removeFromState(DfaMemoryStateImpl state); void restoreCommonState(DfaMemoryStateImpl stripped, Collection merged) { DfType commonType = StreamEx.of(merged).map(s -> s.getDfType(myVar)).foldLeft(DfTypes.BOTTOM, DfType::join); @@ -373,8 +363,7 @@ void restoreCommonState(DfaMemoryStateImpl stripped, Collection) dfType).getNotValues().size() == 1) { @@ -463,10 +450,9 @@ void removeFromState(@Nonnull DfaMemoryStateImpl state) { static final class InstanceofFact extends Fact { private final - @Nonnull DfaTypeValue myType; - private InstanceofFact(@Nonnull DfaVariableValue var, boolean positive, @Nonnull DfaTypeValue type) { + private InstanceofFact(DfaVariableValue var, boolean positive, DfaTypeValue type) { super(positive, var, (var.hashCode() * 31 + type.hashCode()) * 31 + (positive ? 1 : 0)); myType = type; } @@ -495,20 +481,19 @@ public String toString() { } @Override - @Nonnull Fact getPositiveCounterpart() { return new InstanceofFact(myVar, true, myType); } @Override - boolean invalidatesFact(@Nonnull Fact another) { + boolean invalidatesFact(Fact another) { return another instanceof InstanceofFact && myType == ((InstanceofFact) another).myType && myVar == another.myVar; } @Override - void removeFromState(@Nonnull DfaMemoryStateImpl state) { + void removeFromState(DfaMemoryStateImpl state) { DfType type = state.getDfType(myVar); if (type instanceof DfReferenceType) { state.recordVariableType(myVar, ((DfReferenceType) type).withoutType(TypeConstraint.fromDfType(myType.getDfType()))); @@ -518,12 +503,11 @@ void removeFromState(@Nonnull DfaMemoryStateImpl state) { private static final class Replacements { private final - @Nonnull List myAllStates; private final Set myRemovedStates = new HashSet<>(); private final List myMerged = new ArrayList<>(); - private Replacements(@Nonnull List allStates) { + private Replacements(List allStates) { myAllStates = allStates; } @@ -546,7 +530,7 @@ List getMergeResult() { return null; } - private void stripAndMerge(@Nonnull Collection group, @Nonnull Fact fact) { + private void stripAndMerge(Collection group, Fact fact) { if (group.size() <= 1) { return; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingDfaMemoryState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingDfaMemoryState.java index aca76de64c..5ddede15e4 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingDfaMemoryState.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingDfaMemoryState.java @@ -20,8 +20,7 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Predicate; @@ -38,7 +37,6 @@ protected TrackingDfaMemoryState(TrackingDfaMemoryState toCopy) { myHistory = toCopy.myHistory; } - @Nonnull @Override public TrackingDfaMemoryState createCopy() { return new TrackingDfaMemoryState(this); @@ -104,7 +102,6 @@ void recordChange(Instruction instruction, TrackingDfaMemoryState previous) { myHistory = MemoryStateChange.create(myHistory, instruction, result, value); } - @Nonnull private Map getChangeMap(TrackingDfaMemoryState previous) { Map changeMap = new HashMap<>(); Set varsToCheck = new HashSet<>(); @@ -188,13 +185,11 @@ void addBridge(Instruction instruction, List bridgeState static class Relation { final - @Nonnull RelationType myRelationType; final - @Nonnull DfaValue myCounterpart; - Relation(@Nonnull RelationType type, @Nonnull DfaValue counterpart) { + Relation(RelationType type, DfaValue counterpart) { myRelationType = type; myCounterpart = counterpart; } @@ -225,19 +220,15 @@ public String toString() { static final class Change { final - @Nonnull Set myRemovedRelations; final - @Nonnull Set myAddedRelations; final - @Nonnull DfType myOldType; final - @Nonnull DfType myNewType; - private Change(@Nonnull Set removedRelations, @Nonnull Set addedRelations, @Nonnull DfType oldType, @Nonnull DfType newType) { + private Change(Set removedRelations, Set addedRelations, DfType oldType, DfType newType) { myRemovedRelations = removedRelations.isEmpty() ? Collections.emptySet() : removedRelations; myAddedRelations = addedRelations.isEmpty() ? Collections.emptySet() : addedRelations; myOldType = oldType; @@ -282,27 +273,22 @@ public String toString() { static final class MemoryStateChange { private final - @Nonnull List myPrevious; final - @Nonnull Instruction myInstruction; final - @Nonnull Map myChanges; final - @Nonnull DfaValue myTopOfStack; final - @Nonnull Map myBridgeChanges; int myCursor = 0; - private MemoryStateChange(@Nonnull List previous, - @Nonnull Instruction instruction, - @Nonnull Map changes, - @Nonnull DfaValue topOfStack, - @Nonnull Map bridgeChanges) { + private MemoryStateChange(List previous, + Instruction instruction, + Map changes, + DfaValue topOfStack, + Map bridgeChanges) { myPrevious = previous; myInstruction = instruction; myChanges = changes; @@ -354,7 +340,7 @@ MemoryStateChange findSubExpressionPush(@Nullable PsiExpression expression) { }, false); } - MemoryStateChange findRelation(DfaVariableValue value, @Nonnull Predicate relationPredicate, boolean startFromSelf) { + MemoryStateChange findRelation(DfaVariableValue value, Predicate relationPredicate, boolean startFromSelf) { return findChange(change -> { if (change.myInstruction instanceof AssignInstruction && change.myTopOfStack == value) { return true; @@ -368,7 +354,6 @@ MemoryStateChange findRelation(DfaVariableValue value, @Nonnull Predicate FactDefinition findFact(DfaValue value, FactExtractor extractor) { if (value instanceof DfaVariableValue) { for (MemoryStateChange change = this; change != null; change = change.getPrevious()) { @@ -426,7 +411,7 @@ private static FactDefinition factFromChange(FactExtractor extractor, } @Nullable - private MemoryStateChange findChange(@Nonnull Predicate predicate, boolean startFromSelf) { + private MemoryStateChange findChange(Predicate predicate, boolean startFromSelf) { for (MemoryStateChange change = startFromSelf ? this : getPrevious(); change != null; change = change.getPrevious()) { if (predicate.test(change)) { return change; @@ -447,7 +432,6 @@ PsiExpression getExpression() { return null; } - @Nonnull public MemoryStateChange merge(MemoryStateChange change) { if (change == this) { return this; @@ -470,7 +454,7 @@ public MemoryStateChange merge(MemoryStateChange change) { Collections.emptyMap()); } - MemoryStateChange withBridge(@Nonnull Instruction instruction, @Nonnull Map bridge) { + MemoryStateChange withBridge(Instruction instruction, Map bridge) { if (myInstruction != instruction) { if (instruction instanceof ConditionalGotoInstruction && getExpression() == ((ConditionalGotoInstruction) instruction).getPsiAnchor()) { @@ -486,9 +470,9 @@ MemoryStateChange withBridge(@Nonnull Instruction instruction, @Nonnull Map result, - @Nonnull DfaValue value) { + Instruction instruction, + Map result, + DfaValue value) { if (result.isEmpty() && DfaTypeValue.isUnknown(value)) { return previous; } @@ -532,10 +516,9 @@ static class FactDefinition { @Nullable MemoryStateChange myChange; final - @Nonnull T myFact; - FactDefinition(@Nullable MemoryStateChange change, @Nonnull T fact) { + FactDefinition(@Nullable MemoryStateChange change, T fact) { myChange = change; myFact = fact; } @@ -547,7 +530,6 @@ public String toString() { } interface FactExtractor { - @Nonnull T extract(DfType type); static FactExtractor nullability() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingRunner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingRunner.java index b22f389b6a..1e8a6f4d17 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingRunner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TrackingRunner.java @@ -1,7 +1,6 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.TrackingDfaMemoryState.FactDefinition; import com.intellij.java.analysis.impl.codeInspection.dataFlow.TrackingDfaMemoryState.FactExtractor; import com.intellij.java.analysis.impl.codeInspection.dataFlow.TrackingDfaMemoryState.MemoryStateChange; @@ -21,1585 +20,1737 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.intellij.java.language.util.JavaPsiConstructorUtil; import com.siyeh.ig.psiutils.BoolUtils; -import com.siyeh.ig.psiutils.EquivalenceChecker; +import com.intellij.java.analysis.impl.codeInspection.EquivalenceChecker; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.progress.ProgressManager; import consulo.document.Document; import consulo.document.util.Segment; import consulo.document.util.TextRange; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.ast.ASTNode; import consulo.language.ast.IElementType; import consulo.language.impl.ast.CompositeElement; import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; import consulo.util.lang.ThreeState; -import consulo.util.lang.function.Condition; +import org.jspecify.annotations.Nullable; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Stream; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaUtil.hasImplicitImpureSuperCall; @SuppressWarnings("SuspiciousNameCombination") public final class TrackingRunner extends DataFlowRunner { - private MemoryStateChange myHistoryForContext = null; - private final PsiExpression myExpression; - private final List afterStates = new ArrayList<>(); - private final List killedStates = new ArrayList<>(); - - private TrackingRunner(@Nonnull PsiElement context, - PsiExpression expression, - boolean unknownMembersAreNullable, - boolean ignoreAssertions) { - super(context.getProject(), context, unknownMembersAreNullable, ThreeState.fromBoolean(ignoreAssertions)); - myExpression = expression; - } - - @Override - protected void beforeInstruction(Instruction instruction) { - afterStates.clear(); - killedStates.clear(); - } - - @Override - protected void afterInstruction(Instruction instruction) { - if (afterStates.size() <= 1 && killedStates.isEmpty()) { - return; + private MemoryStateChange myHistoryForContext = null; + private final PsiExpression myExpression; + private final List afterStates = new ArrayList<>(); + private final List killedStates = new ArrayList<>(); + + private TrackingRunner( + PsiElement context, + PsiExpression expression, + boolean unknownMembersAreNullable, + boolean ignoreAssertions + ) { + super(context.getProject(), context, unknownMembersAreNullable, ThreeState.fromBoolean(ignoreAssertions)); + myExpression = expression; } - Map> instructionToState = - StreamEx.of(afterStates).mapToEntry(s -> s.getInstruction(), s -> (TrackingDfaMemoryState) s.getMemoryState()).grouping(); - if (instructionToState.size() <= 1 && killedStates.isEmpty()) { - return; + + @Override + protected void beforeInstruction(Instruction instruction) { + afterStates.clear(); + killedStates.clear(); } - instructionToState.forEach((target, memStates) -> { - List bridgeChanges = - StreamEx.of(afterStates).filter(s -> s.getInstruction() != target) - .map(s -> ((TrackingDfaMemoryState) s.getMemoryState())) - .append(killedStates) - .toList(); - for (TrackingDfaMemoryState state : memStates) { - state.addBridge(instruction, bridgeChanges); - } - }); - } - - @Nonnull - @Override - protected DfaMemoryState createMemoryState() { - return new TrackingDfaMemoryState(getFactory()); - } - - @Override - @Nonnull - protected DfaInstructionState[] acceptInstruction(@Nonnull InstructionVisitor visitor, @Nonnull DfaInstructionState instructionState) { - Instruction instruction = instructionState.getInstruction(); - TrackingDfaMemoryState memState = (TrackingDfaMemoryState) instructionState.getMemoryState().createCopy(); - DfaInstructionState[] states = super.acceptInstruction(visitor, instructionState); - for (DfaInstructionState state : states) { - afterStates.add(state); - ((TrackingDfaMemoryState) state.getMemoryState()).recordChange(instruction, memState); + + @Override + protected void afterInstruction(Instruction instruction) { + if (afterStates.size() <= 1 && killedStates.isEmpty()) { + return; + } + Map> instructionToState = + StreamEx.of(afterStates).mapToEntry(s -> s.getInstruction(), s -> (TrackingDfaMemoryState)s.getMemoryState()).grouping(); + if (instructionToState.size() <= 1 && killedStates.isEmpty()) { + return; + } + instructionToState.forEach((target, memStates) -> { + List bridgeChanges = + StreamEx.of(afterStates).filter(s -> s.getInstruction() != target) + .map(s -> ((TrackingDfaMemoryState)s.getMemoryState())) + .append(killedStates) + .toList(); + for (TrackingDfaMemoryState state : memStates) { + state.addBridge(instruction, bridgeChanges); + } + }); } - if (states.length == 0) { - killedStates.add(memState); + + @Override + protected DfaMemoryState createMemoryState() { + return new TrackingDfaMemoryState(getFactory()); } - if (instruction instanceof ExpressionPushingInstruction) { - ExpressionPushingInstruction pushing = (ExpressionPushingInstruction) instruction; - if (pushing.getExpression() == myExpression && pushing.getExpressionRange() == null) { + + @Override + protected DfaInstructionState[] acceptInstruction(InstructionVisitor visitor, DfaInstructionState instructionState) { + Instruction instruction = instructionState.getInstruction(); + TrackingDfaMemoryState memState = (TrackingDfaMemoryState)instructionState.getMemoryState().createCopy(); + DfaInstructionState[] states = super.acceptInstruction(visitor, instructionState); for (DfaInstructionState state : states) { - MemoryStateChange history = ((TrackingDfaMemoryState) state.getMemoryState()).getHistory(); - myHistoryForContext = myHistoryForContext == null ? history : myHistoryForContext.merge(history); + afterStates.add(state); + ((TrackingDfaMemoryState)state.getMemoryState()).recordChange(instruction, memState); } - } - } - return states; - } - - @Nullable - public static CauseItem findProblemCause(boolean unknownAreNullables, - boolean ignoreAssertions, - PsiExpression expression, - DfaProblemType type) { - PsiElement body = DfaUtil.getDataflowContext(expression); - if (body == null) { - return null; - } - TrackingRunner runner = new TrackingRunner(body, expression, unknownAreNullables, ignoreAssertions); - if (!runner.analyze(expression, body)) { - return null; - } - return runner.findProblemCause(expression, type); - } - - private boolean analyze(PsiExpression expression, PsiElement body) { - List endOfInitializerStates = new ArrayList<>(); - StandardInstructionVisitor visitor = new StandardInstructionVisitor(true) { - @Override - public DfaInstructionState[] visitEndOfInitializer(EndOfInitializerInstruction instruction, - DataFlowRunner runner, - DfaMemoryState state) { - if (!instruction.isStatic()) { - endOfInitializerStates.add(state.createCopy()); - } - return super.visitEndOfInitializer(instruction, runner, state); - } - }; - RunnerResult result = analyzeMethodRecursively(body, visitor); - if (result != RunnerResult.OK) { - return false; - } - if (body instanceof PsiClass) { - PsiMethod ctor = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class); - if (ctor != null && ctor.isConstructor()) { - List initialStates; - PsiCodeBlock ctorBody = ctor.getBody(); - if (ctorBody != null) { - PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(ctor); - if (JavaPsiConstructorUtil.isChainedConstructorCall(call) || - (call == null && hasImplicitImpureSuperCall((PsiClass) body, ctor))) { - initialStates = Collections.singletonList(createMemoryState()); - } else { - initialStates = StreamEx.of(endOfInitializerStates).map(DfaMemoryState::createCopy).toList(); - } - return analyzeBlockRecursively(ctorBody, initialStates, visitor) == RunnerResult.OK; - } - } - } - return true; - } - - /* - TODO: 1. Find causes of other warnings: - Cause for AIOOBE - Cause for "modifying an immutable collection" - Cause for "Collection is always empty" (separate inspection now) - TODO: 2. Describe causes in more cases: - Warning caused by complex contracts - Warning caused by CustomMethodHandler - Warning caused by polyadic math - Warning caused by unary minus - Warning caused by string concatenation - Warning caused by java.lang.Void nullability - Warning caused by getClass() equality - Warning caused by inliners - TODO: 3. Check how it works with: - Boxed numbers - */ - @Nullable - private CauseItem findProblemCause(PsiExpression expression, DfaProblemType type) { - if (myHistoryForContext == null) { - return null; - } - CauseItem cause = null; - do { - CauseItem item = new CauseItem(type, expression); - MemoryStateChange history = myHistoryForContext.getNonMerge(); - if (history.getExpression() == expression) { - item.addChildren(type.findCauses(this, expression, history)); - } - if (cause == null) { - cause = item; - } else { - cause = cause.merge(item); - if (cause == null) { - return null; - } - } + if (states.length == 0) { + killedStates.add(memState); + } + if (instruction instanceof ExpressionPushingInstruction) { + ExpressionPushingInstruction pushing = (ExpressionPushingInstruction)instruction; + if (pushing.getExpression() == myExpression && pushing.getExpressionRange() == null) { + for (DfaInstructionState state : states) { + MemoryStateChange history = ((TrackingDfaMemoryState)state.getMemoryState()).getHistory(); + myHistoryForContext = myHistoryForContext == null ? history : myHistoryForContext.merge(history); + } + } + } + return states; } - while (myHistoryForContext.advance()); - return cause; - } - - public abstract static class DfaProblemType { - public abstract - @Nls - String toString(); - CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { - return new CauseItem[0]; + @Nullable + @RequiredReadAction + public static CauseItem findProblemCause( + boolean unknownAreNullables, + boolean ignoreAssertions, + PsiExpression expression, + DfaProblemType type + ) { + PsiElement body = DfaUtil.getDataflowContext(expression); + if (body == null) { + return null; + } + TrackingRunner runner = new TrackingRunner(body, expression, unknownAreNullables, ignoreAssertions); + if (!runner.analyze(expression, body)) { + return null; + } + return runner.findProblemCause(expression, type); + } + + @RequiredReadAction + private boolean analyze(PsiExpression expression, PsiElement body) { + List endOfInitializerStates = new ArrayList<>(); + StandardInstructionVisitor visitor = new StandardInstructionVisitor(true) { + @Override + public DfaInstructionState[] visitEndOfInitializer( + EndOfInitializerInstruction instruction, + DataFlowRunner runner, + DfaMemoryState state + ) { + if (!instruction.isStatic()) { + endOfInitializerStates.add(state.createCopy()); + } + return super.visitEndOfInitializer(instruction, runner, state); + } + }; + RunnerResult result = analyzeMethodRecursively(body, visitor); + if (result != RunnerResult.OK) { + return false; + } + if (body instanceof PsiClass psiClass) { + PsiMethod ctor = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class); + if (ctor != null && ctor.isConstructor()) { + List initialStates; + PsiCodeBlock ctorBody = ctor.getBody(); + if (ctorBody != null) { + PsiMethodCallExpression call = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(ctor); + if (JavaPsiConstructorUtil.isChainedConstructorCall(call) || + (call == null && hasImplicitImpureSuperCall(psiClass, ctor))) { + initialStates = Collections.singletonList(createMemoryState()); + } + else { + initialStates = StreamEx.of(endOfInitializerStates).map(DfaMemoryState::createCopy).toList(); + } + return analyzeBlockRecursively(ctorBody, initialStates, visitor) == RunnerResult.OK; + } + } + } + return true; } + /* + TODO: 1. Find causes of other warnings: + Cause for AIOOBE + Cause for "modifying an immutable collection" + Cause for "Collection is always empty" (separate inspection now) + TODO: 2. Describe causes in more cases: + Warning caused by complex contracts + Warning caused by CustomMethodHandler + Warning caused by polyadic math + Warning caused by unary minus + Warning caused by string concatenation + Warning caused by java.lang.Void nullability + Warning caused by getClass() equality + Warning caused by inliners + TODO: 3. Check how it works with: + Boxed numbers + */ @Nullable - DfaProblemType tryMerge(DfaProblemType other) { - return this.toString().equals(other.toString()) ? this : null; + @RequiredReadAction + private CauseItem findProblemCause(PsiExpression expression, DfaProblemType type) { + if (myHistoryForContext == null) { + return null; + } + CauseItem cause = null; + do { + CauseItem item = new CauseItem(type, expression); + MemoryStateChange history = myHistoryForContext.getNonMerge(); + if (history.getExpression() == expression) { + item.addChildren(type.findCauses(this, expression, history)); + } + if (cause == null) { + cause = item; + } + else { + cause = cause.merge(item); + if (cause == null) { + return null; + } + } + } + while (myHistoryForContext.advance()); + return cause; } - } - - public static class CauseItem { - private static final String PLACE_POINTER = "___PLACE___"; - final - @Nonnull - List myChildren; - final - @Nonnull - DfaProblemType myProblem; - final - @Nullable - SmartPsiFileRange myTarget; - private CauseItem(@Nonnull List children, @Nonnull DfaProblemType problem, @Nullable SmartPsiFileRange target) { - myChildren = children; - myProblem = problem; - myTarget = target; - } + public abstract static class DfaProblemType { + public LocalizeValue getMessage() { + return LocalizeValue.ofNullable(toString()); + } - CauseItem(@Nonnull @Nls String problem, @Nullable PsiElement target) { - this(new CustomDfaProblemType(problem), target); - } + @Override + public String toString() { + return getMessage().get(); + } - CauseItem(@Nonnull DfaProblemType problem, @Nullable PsiElement target) { - myChildren = new ArrayList<>(); - myProblem = problem; - if (target != null) { - PsiFile file = target.getContainingFile(); - myTarget = SmartPointerManager.getInstance(file.getProject()).createSmartPsiFileRangePointer(file, target.getTextRange()); - } else { - myTarget = null; - } - } + CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { + return new CauseItem[0]; + } - CauseItem(@Nonnull @Nls String problem, @Nonnull MemoryStateChange change) { - this(new CustomDfaProblemType(problem), change); + @Nullable + DfaProblemType tryMerge(DfaProblemType other) { + return this.getMessage().equals(other.getMessage()) ? this : null; + } } - CauseItem(@Nonnull DfaProblemType problem, @Nonnull MemoryStateChange change) { - this(problem, change.getExpression()); - } + public static class CauseItem { + private static final String PLACE_POINTER = "___PLACE___"; + final List myChildren; + final DfaProblemType myProblem; + @Nullable + final SmartPsiFileRange myTarget; - void addChildren(CauseItem... causes) { - ContainerUtil.addAllNotNull(myChildren, causes); - } + private CauseItem(List children, DfaProblemType problem, @Nullable SmartPsiFileRange target) { + myChildren = children; + myProblem = problem; + myTarget = target; + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CauseItem item = (CauseItem) o; - return myChildren.equals(item.myChildren) && - getProblemName().equals(item.getProblemName()) && - Objects.equals(myTarget, item.myTarget); - } + @RequiredReadAction + CauseItem(LocalizeValue problem, @Nullable PsiElement target) { + this(new CustomDfaProblemType(problem), target); + } - private - @Nls - String getProblemName() { - return myProblem.toString(); - } + @RequiredReadAction + CauseItem(DfaProblemType problem, @Nullable PsiElement target) { + myChildren = new ArrayList<>(); + myProblem = problem; + if (target != null) { + PsiFile file = target.getContainingFile(); + myTarget = SmartPointerManager.getInstance(file.getProject()).createSmartPsiFileRangePointer(file, target.getTextRange()); + } + else { + myTarget = null; + } + } - @Override - public int hashCode() { - return Objects.hash(myChildren, getProblemName(), myTarget); - } + @RequiredReadAction + CauseItem(LocalizeValue problem, MemoryStateChange change) { + this(new CustomDfaProblemType(problem), change); + } - public String dump(Document doc) { - return dump(doc, 0, null); - } + @RequiredReadAction + CauseItem(DfaProblemType problem, MemoryStateChange change) { + this(problem, change.getExpression()); + } - private String dump(Document doc, int indent, CauseItem parent) { - String text = null; - if (myTarget != null) { - Segment range = myTarget.getRange(); - if (range != null) { - text = doc.getText(TextRange.create(range)); - int lineNumber = doc.getLineNumber(range.getStartOffset()); - text += "; line#" + (lineNumber + 1); - } - } - return StringUtil.repeat(" ", indent) + render(doc, parent) + (text == null ? "" : " (" + text + ")") + "\n" + - StreamEx.of(myChildren).map(child -> child.dump(doc, indent + 1, this)).joining(); - } + void addChildren(CauseItem... causes) { + ContainerUtil.addAllNotNull(myChildren, causes); + } - public Stream children() { - return StreamEx.of(myChildren); - } + @Override + public boolean equals(Object o) { + return this == o + || o instanceof CauseItem that + && myChildren.equals(that.myChildren) + && getProblemName().equals(that.getProblemName()) + && Objects.equals(myTarget, that.myTarget); + } - @Nullable - public PsiFile getFile() { - return myTarget != null ? myTarget.getContainingFile() : null; - } + private + String getProblemName() { + return myProblem.toString(); + } - public Segment getTargetSegment() { - return myTarget == null ? null : myTarget.getRange(); - } + @Override + public int hashCode() { + return Objects.hash(myChildren, getProblemName(), myTarget); + } - public String render(Document doc, CauseItem parent) { - String title = null; - Segment range = getTargetSegment(); - if (range != null) { - String cause = getProblemName(); - if (cause.contains(PLACE_POINTER)) { - int offset = range.getStartOffset(); - int number = doc.getLineNumber(offset); - title = cause.replaceFirst(PLACE_POINTER, JavaAnalysisBundle.message("dfa.find.cause.place.line.number", number + 1)); - } - } - if (title == null) { - title = toString(); - } - int childIndex = parent == null ? 0 : parent.myChildren.indexOf(this); - if (childIndex > 0) { - title = (parent.myProblem instanceof PossibleExecutionDfaProblemType ? "or " : "and ") + title; - } else { - title = StringUtil.capitalize(title); - } - return title; - } + public String dump(Document doc) { + return dump(doc, 0, null); + } - @Override - public - @Nls - String toString() { - //noinspection HardCodedStringLiteral - return getProblemName().replaceFirst(PLACE_POINTER, JavaAnalysisBundle.message("dfa.find.cause.place.here")); - } + private String dump(Document doc, int indent, CauseItem parent) { + String text = null; + if (myTarget != null) { + Segment range = myTarget.getRange(); + if (range != null) { + text = doc.getText(TextRange.create(range)); + int lineNumber = doc.getLineNumber(range.getStartOffset()); + text += "; line#" + (lineNumber + 1); + } + } + return StringUtil.repeat(" ", indent) + render(doc, parent) + (text == null ? "" : " (" + text + ")") + "\n" + + StreamEx.of(myChildren).map(child -> child.dump(doc, indent + 1, this)).joining(); + } - public CauseItem merge(CauseItem other) { - if (this.equals(other)) { - return this; - } - if (Objects.equals(this.myTarget, other.myTarget)) { - if (myChildren.equals(other.myChildren)) { - DfaProblemType mergedProblem = myProblem.tryMerge(other.myProblem); - if (mergedProblem != null) { - return new CauseItem(myChildren, mergedProblem, myTarget); - } - } - if (getProblemName().equals(other.getProblemName())) { - if (tryMergeChildren(other.myChildren)) { - return this; - } - if (other.tryMergeChildren(this.myChildren)) { - return other; - } - } - } - return null; - } + public Stream children() { + return StreamEx.of(myChildren); + } - private boolean tryMergeChildren(List children) { - if (myChildren.isEmpty()) { - return false; - } - if (myChildren.size() != 1 || !(myChildren.get(0).myProblem instanceof PossibleExecutionDfaProblemType)) { - if (children.size() == myChildren.size()) { - List merged = StreamEx.zip(myChildren, children, CauseItem::merge).toList(); - if (!merged.contains(null)) { - myChildren.clear(); - myChildren.addAll(merged); + @Nullable + @RequiredReadAction + public PsiFile getFile() { + return myTarget != null ? myTarget.getContainingFile() : null; + } + + public Segment getTargetSegment() { + return myTarget == null ? null : myTarget.getRange(); + } + + public String render(Document doc, CauseItem parent) { + String title = null; + Segment range = getTargetSegment(); + if (range != null) { + String cause = getProblemName(); + if (cause.contains(PLACE_POINTER)) { + int offset = range.getStartOffset(); + int number = doc.getLineNumber(offset); + title = cause.replaceFirst(PLACE_POINTER, JavaAnalysisLocalize.dfaFindCausePlaceLineNumber(number + 1).get()); + } + } + if (title == null) { + title = toString(); + } + int childIndex = parent == null ? 0 : parent.myChildren.indexOf(this); + if (childIndex > 0) { + title = (parent.myProblem instanceof PossibleExecutionDfaProblemType ? "or " : "and ") + title; + } + else { + title = StringUtil.capitalize(title); + } + return title; + } + + @Override + public String toString() { + //noinspection HardCodedStringLiteral + return getProblemName().replaceFirst(PLACE_POINTER, JavaAnalysisLocalize.dfaFindCausePlaceHere().get()); + } + + @RequiredReadAction + public CauseItem merge(CauseItem other) { + if (this.equals(other)) { + return this; + } + if (Objects.equals(this.myTarget, other.myTarget)) { + if (myChildren.equals(other.myChildren)) { + DfaProblemType mergedProblem = myProblem.tryMerge(other.myProblem); + if (mergedProblem != null) { + return new CauseItem(myChildren, mergedProblem, myTarget); + } + } + if (getProblemName().equals(other.getProblemName())) { + if (tryMergeChildren(other.myChildren)) { + return this; + } + if (other.tryMergeChildren(this.myChildren)) { + return other; + } + } + } + return null; + } + + @RequiredReadAction + private boolean tryMergeChildren(List children) { + if (myChildren.isEmpty()) { + return false; + } + if (myChildren.size() != 1 || !(myChildren.get(0).myProblem instanceof PossibleExecutionDfaProblemType)) { + if (children.size() == myChildren.size()) { + List merged = StreamEx.zip(myChildren, children, CauseItem::merge).toList(); + if (!merged.contains(null)) { + myChildren.clear(); + myChildren.addAll(merged); + return true; + } + } + insertIntoHierarchy(new CauseItem(new PossibleExecutionDfaProblemType(), (PsiElement)null)); + } + CauseItem mergePoint = myChildren.get(0); + if (children.isEmpty()) { + ((PossibleExecutionDfaProblemType)mergePoint.myProblem).myComplete = false; + } + List mergeChildren = mergePoint.myChildren; + for (CauseItem child : children) { + if (!mergeChildren.contains(child)) { + boolean merged = false; + for (int i = 0; i < mergeChildren.size(); i++) { + CauseItem mergeChild = mergeChildren.get(i); + CauseItem result = mergeChild.merge(child); + if (result != null) { + mergeChildren.set(i, result); + merged = true; + break; + } + } + if (!merged) { + mergeChildren.add(child); + } + } + } return true; - } - } - insertIntoHierarchy(new CauseItem(new PossibleExecutionDfaProblemType(), (PsiElement) null)); - } - CauseItem mergePoint = myChildren.get(0); - if (children.isEmpty()) { - ((PossibleExecutionDfaProblemType) mergePoint.myProblem).myComplete = false; - } - List mergeChildren = mergePoint.myChildren; - for (CauseItem child : children) { - if (!mergeChildren.contains(child)) { - boolean merged = false; - for (int i = 0; i < mergeChildren.size(); i++) { - CauseItem mergeChild = mergeChildren.get(i); - CauseItem result = mergeChild.merge(child); - if (result != null) { - mergeChildren.set(i, result); - merged = true; - break; - } - } - if (!merged) { - mergeChildren.add(child); - } - } - } - return true; - } + } - private void insertIntoHierarchy(CauseItem intermediate) { - intermediate.myChildren.addAll(myChildren); - myChildren.clear(); - myChildren.add(intermediate); + private void insertIntoHierarchy(CauseItem intermediate) { + intermediate.myChildren.addAll(myChildren); + myChildren.clear(); + myChildren.add(intermediate); + } } - } - public static class CastDfaProblemType extends DfaProblemType { - @Override - public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { - if (expression instanceof PsiTypeCastExpression) { - PsiType expressionType = expression.getType(); - MemoryStateChange operandPush = history.findExpressionPush(((PsiTypeCastExpression) expression).getOperand()); - if (operandPush != null) { - return new CauseItem[]{findTypeCause(operandPush, expressionType, false)}; - } - } - return new CauseItem[0]; - } + public static class CastDfaProblemType extends DfaProblemType { + @Override + @RequiredReadAction + public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { + if (expression instanceof PsiTypeCastExpression typeCast) { + PsiType expressionType = expression.getType(); + MemoryStateChange operandPush = history.findExpressionPush(typeCast.getOperand()); + if (operandPush != null) { + return new CauseItem[]{findTypeCause(operandPush, expressionType, false)}; + } + } + return new CauseItem[0]; + } - public String toString() { - return JavaAnalysisBundle.message("dfa.find.cause.cast.may.fail"); + @Override + public LocalizeValue getMessage() { + return JavaAnalysisLocalize.dfaFindCauseCastMayFail(); + } } - } - public static class NullableDfaProblemType extends DfaProblemType { - @Override - public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { - FactDefinition nullability = history.findFact(history.myTopOfStack, FactExtractor.nullability()); - if (nullability.myFact == DfaNullability.NULLABLE || nullability.myFact == DfaNullability.NULL) { - return new CauseItem[]{runner.findNullabilityCause(history, nullability.myFact)}; - } - return new CauseItem[0]; - } + public static class NullableDfaProblemType extends DfaProblemType { + @Override + @RequiredReadAction + public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { + FactDefinition nullability = history.findFact(history.myTopOfStack, FactExtractor.nullability()); + if (nullability.myFact == DfaNullability.NULLABLE || nullability.myFact == DfaNullability.NULL) { + return new CauseItem[]{runner.findNullabilityCause(history, nullability.myFact)}; + } + return new CauseItem[0]; + } - public String toString() { - return JavaAnalysisBundle.message("dfa.find.cause.may.be.null"); + @Override + public LocalizeValue getMessage() { + return JavaAnalysisLocalize.dfaFindCauseMayBeNull(); + } } - } - public static class FailingCallDfaProblemType extends DfaProblemType { - @Override - CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { - if (expression instanceof PsiCallExpression) { - return new CauseItem[]{runner.fromCallContract(history, (PsiCallExpression) expression, ContractReturnValue.fail())}; - } - return super.findCauses(runner, expression, history); - } + public static class FailingCallDfaProblemType extends DfaProblemType { + @Override + @RequiredReadAction + CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { + return expression instanceof PsiCallExpression call + ? new CauseItem[]{runner.fromCallContract(history, call, ContractReturnValue.fail())} + : super.findCauses(runner, expression, history); + } - @Override - public String toString() { - return JavaAnalysisBundle.message("dfa.find.cause.call.always.fails"); + @Override + public LocalizeValue getMessage() { + return JavaAnalysisLocalize.dfaFindCauseCallAlwaysFails(); + } } - } - - static class PossibleExecutionDfaProblemType extends DfaProblemType { - boolean myComplete = true; + static class PossibleExecutionDfaProblemType extends DfaProblemType { + boolean myComplete = true; - @Override - public String toString() { - return myComplete ? - JavaAnalysisBundle.message("dfa.find.cause.one.of.the.following.happens") : - JavaAnalysisBundle.message("dfa.find.cause.an.execution.might.exist.where"); + @Override + public LocalizeValue getMessage() { + return myComplete ? + JavaAnalysisLocalize.dfaFindCauseOneOfTheFollowingHappens() : + JavaAnalysisLocalize.dfaFindCauseAnExecutionMightExistWhere(); + } } - } - - static class RangeDfaProblemType extends DfaProblemType { - final - @Nonnull - @Nls - String myTemplate; - final - @Nonnull - LongRangeSet myRangeSet; - final - @Nullable - PsiPrimitiveType myType; - RangeDfaProblemType(@Nonnull @Nls String template, @Nonnull LongRangeSet set, @Nullable PsiPrimitiveType type) { - myTemplate = template; - myRangeSet = set; - myType = type; - } + static class RangeDfaProblemType extends DfaProblemType { + final LocalizeValue myTemplate; + final LongRangeSet myRangeSet; + @Nullable + final PsiPrimitiveType myType; - @Nullable - @Override - DfaProblemType tryMerge(DfaProblemType other) { - if (other instanceof RangeDfaProblemType) { - RangeDfaProblemType rangeProblem = (RangeDfaProblemType) other; - if (myTemplate.equals(rangeProblem.myTemplate) && Objects.equals(myType, rangeProblem.myType)) { - return new RangeDfaProblemType(myTemplate, myRangeSet.unite(((RangeDfaProblemType) other).myRangeSet), myType); - } - } - return super.tryMerge(other); - } + RangeDfaProblemType(LocalizeValue template, LongRangeSet set, @Nullable PsiPrimitiveType type) { + myTemplate = template; + myRangeSet = set; + myType = type; + } - @Override - public String toString() { - return String.format(myTemplate, myRangeSet.getPresentationText(myType)); + @Nullable + @Override + DfaProblemType tryMerge(DfaProblemType other) { + return other instanceof RangeDfaProblemType rangeProblem + && myTemplate.equals(rangeProblem.myTemplate) + && Objects.equals(myType, rangeProblem.myType) + ? new RangeDfaProblemType(myTemplate, myRangeSet.unite(rangeProblem.myRangeSet), myType) + : super.tryMerge(other); + } + + @Override + public LocalizeValue getMessage() { + return LocalizeValue.of(String.format(myTemplate.get(), myRangeSet.getPresentationText(myType))); + } } - } + public static class ValueDfaProblemType extends DfaProblemType { + final Object myValue; - public static class ValueDfaProblemType extends DfaProblemType { - final Object myValue; + public ValueDfaProblemType(Object value) { + myValue = value; + } - public ValueDfaProblemType(Object value) { - myValue = value; - } + @Override + @RequiredReadAction + public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { + return runner.findConstantValueCause(expression, history, myValue); + } - @Override - public CauseItem[] findCauses(TrackingRunner runner, PsiExpression expression, MemoryStateChange history) { - return runner.findConstantValueCause(expression, history, myValue); + @Override + public LocalizeValue getMessage() { + return JavaAnalysisLocalize.dfaFindCauseValueIsAlwaysTheSame(myValue); + } } - @Override - public String toString() { - return JavaAnalysisBundle.message("dfa.find.cause.value.is.always.the.same", myValue); - } - } + static class CustomDfaProblemType extends DfaProblemType { + private final LocalizeValue myMessage; - static class CustomDfaProblemType extends DfaProblemType { - private final - @Nls - String myMessage; + CustomDfaProblemType(LocalizeValue message) { + myMessage = message; + } - CustomDfaProblemType(@Nls String message) { - myMessage = message; + @Override + public LocalizeValue getMessage() { + return myMessage; + } } - @Override - public String toString() { - return myMessage; + @RequiredReadAction + private CauseItem[] findConstantValueCause(PsiExpression expression, MemoryStateChange history, Object expectedValue) { + if (expression instanceof PsiLiteralExpression) { + return new CauseItem[0]; + } + Object constantExpressionValue = ExpressionUtils.computeConstantExpression(expression); + DfaValue value = history.myTopOfStack; + if (constantExpressionValue != null && constantExpressionValue.equals(expectedValue)) { + return new CauseItem[]{new CauseItem(JavaAnalysisLocalize.dfaFindCauseCompileTimeConstant(value), expression)}; + } + if (value.getDfType() instanceof DfConstantType) { + Object constValue = ((DfConstantType)value.getDfType()).getValue(); + if (Objects.equals(constValue, expectedValue) && constValue instanceof Boolean booleanValue) { + return findBooleanResultCauses(expression, history, booleanValue); + } + } + if (value instanceof DfaVariableValue dfaVarValue) { + MemoryStateChange change = history.findRelation( + dfaVarValue, + rel -> rel.myRelationType == RelationType.EQ && DfConstantType.isConst(rel.myCounterpart.getDfType(), expectedValue), + false + ); + if (change != null) { + PsiExpression varSourceExpression = change.getExpression(); + if (change.myInstruction instanceof AssignInstruction assignInsn && change.myTopOfStack == value) { + PsiExpression rValue = assignInsn.getRExpression(); + CauseItem item = createAssignmentCause(assignInsn, value); + MemoryStateChange push = change.findSubExpressionPush(rValue); + if (push != null) { + item.addChildren(findConstantValueCause(rValue, push, expectedValue)); + } + return new CauseItem[]{item}; + } + else if (varSourceExpression != null) { + return new CauseItem[]{ + new CauseItem( + JavaAnalysisLocalize.dfaFindCauseEqualityEstablishedFromCondition(value + " == " + expectedValue), + varSourceExpression + ) + }; + } + } + } + return new CauseItem[0]; } - } - @Nonnull - private CauseItem[] findConstantValueCause(PsiExpression expression, MemoryStateChange history, Object expectedValue) { - if (expression instanceof PsiLiteralExpression) { - return new CauseItem[0]; - } - Object constantExpressionValue = ExpressionUtils.computeConstantExpression(expression); - DfaValue value = history.myTopOfStack; - if (constantExpressionValue != null && constantExpressionValue.equals(expectedValue)) { - return new CauseItem[]{new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.compile.time.constant", value), expression)}; - } - if (value.getDfType() instanceof DfConstantType) { - Object constValue = ((DfConstantType) value.getDfType()).getValue(); - if (Objects.equals(constValue, expectedValue) && constValue instanceof Boolean) { - return findBooleanResultCauses(expression, history, ((Boolean) constValue).booleanValue()); - } - } - if (value instanceof DfaVariableValue) { - MemoryStateChange change = history.findRelation( - (DfaVariableValue) value, rel -> rel.myRelationType == RelationType.EQ && - DfConstantType.isConst(rel.myCounterpart.getDfType(), expectedValue), false); - if (change != null) { - PsiExpression varSourceExpression = change.getExpression(); - Instruction instruction = change.myInstruction; - if (instruction instanceof AssignInstruction && change.myTopOfStack == value) { - PsiExpression rValue = ((AssignInstruction) instruction).getRExpression(); - CauseItem item = createAssignmentCause((AssignInstruction) instruction, value); - MemoryStateChange push = change.findSubExpressionPush(rValue); - if (push != null) { - item.addChildren(findConstantValueCause(rValue, push, expectedValue)); - } - return new CauseItem[]{item}; - } else if (varSourceExpression != null) { - return new CauseItem[]{ - new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.equality.established.from.condition", value + " == " + expectedValue), - varSourceExpression) - }; - } - } - } - return new CauseItem[0]; - } - - @Nonnull - @Contract("_, _ -> new") - private static CauseItem createAssignmentCause(AssignInstruction instruction, DfaValue target) { - PsiExpression rExpression = instruction.getRExpression(); - PsiElement anchor = null; - String targetName = target.toString(); - if (rExpression != null) { - PsiElement parent = PsiUtil.skipParenthesizedExprUp(rExpression.getParent()); - if (parent instanceof PsiAssignmentExpression) { - anchor = ((PsiAssignmentExpression) parent).getOperationSign(); - targetName = ((PsiAssignmentExpression) parent).getLExpression().getText(); - } else if (parent instanceof PsiVariable) { - ASTNode node = parent.getNode(); - if (node instanceof CompositeElement) { - anchor = ((CompositeElement) node).findChildByRoleAsPsiElement(ChildRole.INITIALIZER_EQ); - } - targetName = ((PsiVariable) parent).getName(); - } - if (anchor == null) { - anchor = rExpression; - } - } - PsiExpression stripped = PsiUtil.skipParenthesizedExprDown(rExpression); - String message; - if (stripped instanceof PsiLiteralExpression) { - message = JavaAnalysisBundle - .message("dfa.find.cause.was.assigned.to", targetName, StringUtil.shortenTextWithEllipsis(stripped.getText(), 40, 5)); - } else { - message = JavaAnalysisBundle.message("dfa.find.cause.was.assigned", targetName); - } - return new CauseItem(message, anchor); - } - - private CauseItem[] findBooleanResultCauses(PsiExpression expression, - MemoryStateChange history, - boolean value) { - if (BoolUtils.isNegation(expression)) { - PsiExpression negated = BoolUtils.getNegated(expression); - if (negated != null) { - MemoryStateChange negatedPush = history.findExpressionPush(negated); - if (negatedPush != null) { - CauseItem cause = new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.value.x.is.always.the.same", negated.getText(), !value), negated); - cause.addChildren(findConstantValueCause(negated, negatedPush, !value)); - return new CauseItem[]{cause}; - } - } - } - if (expression instanceof PsiPolyadicExpression) { - IElementType tokenType = ((PsiPolyadicExpression) expression).getOperationTokenType(); - boolean and = tokenType.equals(JavaTokenType.ANDAND); - if (and || tokenType.equals(JavaTokenType.OROR)) { - if (value != and) { - MemoryStateChange push = history; - if (history.myInstruction instanceof ResultOfInstruction) { - MemoryStateChange previous = history.getPrevious(); - if (previous != null) { - previous = previous.getNonMerge(); - } - if (previous != null && previous.myInstruction instanceof GotoInstruction) { - previous = previous.getPrevious(); - } - if (previous != null) { - push = previous; - } - } - if (push.myInstruction instanceof PushValueInstruction && - DfConstantType.isConst(((PushValueInstruction) push.myInstruction).getValue(), value) && - ((PushValueInstruction) push.myInstruction).getExpression() == expression) { - push = push.getPrevious(); - } - if (push != null && push.myInstruction instanceof ConditionalGotoInstruction) { - push = push.getPrevious(); - } - if (push != null && push.myInstruction instanceof ExpressionPushingInstruction) { - ExpressionPushingInstruction instruction = (ExpressionPushingInstruction) push.myInstruction; - if (instruction.getExpressionRange() == null) { - PsiExpression operand = instruction.getExpression(); - if (operand != null && expression.equals(PsiUtil.skipParenthesizedExprUp(operand.getParent()))) { - int i = IntStreamEx.ofIndices(((PsiPolyadicExpression) expression).getOperands(), e -> PsiTreeUtil.isAncestor(e, operand, false)) - .findFirst().orElse(-1); - if (i >= 0) { - CauseItem cause = new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.operand.of.boolean.expression.is.the.same", i + 1, and ? 0 : 1, value), - operand); - cause.addChildren(findConstantValueCause(operand, push, value)); - return new CauseItem[]{cause}; + @Contract("_, _ -> new") + @RequiredReadAction + private static CauseItem createAssignmentCause(AssignInstruction instruction, DfaValue target) { + PsiExpression rExpression = instruction.getRExpression(); + PsiElement anchor = null; + String targetName = target.toString(); + if (rExpression != null) { + PsiElement parent = PsiUtil.skipParenthesizedExprUp(rExpression.getParent()); + if (parent instanceof PsiAssignmentExpression assignment) { + anchor = assignment.getOperationSign(); + targetName = assignment.getLExpression().getText(); + } + else if (parent instanceof PsiVariable variable) { + ASTNode node = parent.getNode(); + if (node instanceof CompositeElement compositeElement) { + anchor = compositeElement.findChildByRoleAsPsiElement(ChildRole.INITIALIZER_EQ); } - } - } - } - return new CauseItem[0]; - } - PsiExpression[] operands = ((PsiPolyadicExpression) expression).getOperands(); - List operandCauses = new ArrayList<>(); - for (int i = 0; i < operands.length; i++) { - PsiExpression operand = operands[i]; - operand = PsiUtil.skipParenthesizedExprDown(operand); - MemoryStateChange push = history.findExpressionPush(operand); - if (push != null && - ((push.myInstruction instanceof ConditionalGotoInstruction && - ((ConditionalGotoInstruction) push.myInstruction).isTarget(value, history.myInstruction)) || - DfConstantType.isConst(push.myTopOfStack.getDfType(), value))) { - int andVal = and ? 1 : 0; - CauseItem cause = new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.operand.of.boolean.expression.is.the.same", i + 1, andVal == 1 ? 0 : 1, value), - operand); - cause.addChildren(findBooleanResultCauses(operand, push, value)); - operandCauses.add(cause); - } - } - if (operandCauses.size() == operands.length) { - return operandCauses.toArray(new CauseItem[0]); - } - } + targetName = variable.getName(); + } + if (anchor == null) { + anchor = rExpression; + } + } + PsiExpression stripped = PsiUtil.skipParenthesizedExprDown(rExpression); + LocalizeValue message; + if (stripped instanceof PsiLiteralExpression literal) { + message = + JavaAnalysisLocalize.dfaFindCauseWasAssignedTo(targetName, StringUtil.shortenTextWithEllipsis(literal.getText(), 40, 5)); + } + else { + message = JavaAnalysisLocalize.dfaFindCauseWasAssigned(targetName); + } + return new CauseItem(message, anchor); } - if (expression instanceof PsiBinaryExpression) { - PsiBinaryExpression binOp = (PsiBinaryExpression) expression; - RelationType relationType = - RelationType.fromElementType(binOp.getOperationTokenType()); - if (relationType != null) { - if (!value) { - relationType = relationType.getNegated(); - } - PsiExpression leftOperand = PsiUtil.skipParenthesizedExprDown(binOp.getLOperand()); - PsiExpression rightOperand = PsiUtil.skipParenthesizedExprDown(binOp.getROperand()); - MemoryStateChange leftChange = history.findExpressionPush(leftOperand); - MemoryStateChange rightChange = history.findExpressionPush(rightOperand); - if (leftChange != null && rightChange != null) { - DfaValue leftValue = leftChange.myTopOfStack; - DfaValue rightValue = rightChange.myTopOfStack; - CauseItem[] causes = findRelationCause(relationType, leftChange, rightChange); - if (causes.length > 0) { - return causes; - } - if (leftValue == rightValue && - (leftValue instanceof DfaVariableValue || leftValue.getDfType() instanceof DfConstantType)) { - List constCauses = new ArrayList<>(); - CauseItem leftCause = constantInitializerCause(leftValue, leftChange.getExpression()); - CauseItem rightCause = constantInitializerCause(rightValue, rightChange.getExpression()); - ContainerUtil.addAllNotNull(constCauses, leftCause, rightCause); - if (constCauses.isEmpty()) { - return new CauseItem[]{ - new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.comparison.arguments.are.the.same"), binOp.getOperationSign()) - }; - } - return constCauses.toArray(new CauseItem[0]); - } - if (leftValue != rightValue && relationType.isInequality() && - leftValue.getDfType() instanceof DfConstantType && rightValue.getDfType() instanceof DfConstantType) { - CauseItem causeItem = new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.comparison.arguments.are.different.constants"), - binOp.getOperationSign()); - causeItem.addChildren(constantInitializerCause(leftValue, leftChange.getExpression()), - constantInitializerCause(rightValue, rightChange.getExpression())); - return new CauseItem[]{causeItem}; - } - } - } + + @RequiredReadAction + private CauseItem[] findBooleanResultCauses( + PsiExpression expression, + MemoryStateChange history, + boolean value + ) { + if (BoolUtils.isNegation(expression)) { + PsiExpression negated = BoolUtils.getNegated(expression); + if (negated != null) { + MemoryStateChange negatedPush = history.findExpressionPush(negated); + if (negatedPush != null) { + CauseItem cause = + new CauseItem(JavaAnalysisLocalize.dfaFindCauseValueXIsAlwaysTheSame(negated.getText(), !value), negated); + cause.addChildren(findConstantValueCause(negated, negatedPush, !value)); + return new CauseItem[]{cause}; + } + } + } + if (expression instanceof PsiPolyadicExpression polyadic) { + IElementType tokenType = polyadic.getOperationTokenType(); + boolean and = tokenType.equals(JavaTokenType.ANDAND); + if (and || tokenType.equals(JavaTokenType.OROR)) { + if (value != and) { + MemoryStateChange push = history; + if (history.myInstruction instanceof ResultOfInstruction) { + MemoryStateChange previous = history.getPrevious(); + if (previous != null) { + previous = previous.getNonMerge(); + } + if (previous != null && previous.myInstruction instanceof GotoInstruction) { + previous = previous.getPrevious(); + } + if (previous != null) { + push = previous; + } + } + if (push.myInstruction instanceof PushValueInstruction pushValueInsn + && DfConstantType.isConst(pushValueInsn.getValue(), value) + && pushValueInsn.getExpression() == expression) { + push = push.getPrevious(); + } + if (push != null && push.myInstruction instanceof ConditionalGotoInstruction) { + push = push.getPrevious(); + } + if (push != null && push.myInstruction instanceof ExpressionPushingInstruction) { + ExpressionPushingInstruction instruction = (ExpressionPushingInstruction)push.myInstruction; + if (instruction.getExpressionRange() == null) { + PsiExpression operand = instruction.getExpression(); + if (operand != null && expression.equals(PsiUtil.skipParenthesizedExprUp(operand.getParent()))) { + int i = IntStreamEx.ofIndices( + ((PsiPolyadicExpression)expression).getOperands(), + e -> PsiTreeUtil.isAncestor(e, operand, false) + ) + .findFirst().orElse(-1); + if (i >= 0) { + CauseItem cause = new CauseItem( + JavaAnalysisLocalize.dfaFindCauseOperandOfBooleanExpressionIsTheSame(i + 1, and ? 0 : 1, value), + operand + ); + cause.addChildren(findConstantValueCause(operand, push, value)); + return new CauseItem[]{cause}; + } + } + } + } + return new CauseItem[0]; + } + PsiExpression[] operands = ((PsiPolyadicExpression)expression).getOperands(); + List operandCauses = new ArrayList<>(); + for (int i = 0; i < operands.length; i++) { + PsiExpression operand = operands[i]; + operand = PsiUtil.skipParenthesizedExprDown(operand); + MemoryStateChange push = history.findExpressionPush(operand); + if (push != null + && ((push.myInstruction instanceof ConditionalGotoInstruction condGotoInsn + && condGotoInsn.isTarget(value, history.myInstruction)) + || DfConstantType.isConst(push.myTopOfStack.getDfType(), value))) { + int andVal = and ? 1 : 0; + CauseItem cause = new CauseItem( + JavaAnalysisLocalize.dfaFindCauseOperandOfBooleanExpressionIsTheSame(i + 1, andVal == 1 ? 0 : 1, value), + operand + ); + cause.addChildren(findBooleanResultCauses(operand, push, value)); + operandCauses.add(cause); + } + } + if (operandCauses.size() == operands.length) { + return operandCauses.toArray(new CauseItem[0]); + } + } + } + if (expression instanceof PsiBinaryExpression binOp) { + RelationType relationType = RelationType.fromElementType(binOp.getOperationTokenType()); + if (relationType != null) { + if (!value) { + relationType = relationType.getNegated(); + } + PsiExpression leftOperand = PsiUtil.skipParenthesizedExprDown(binOp.getLOperand()); + PsiExpression rightOperand = PsiUtil.skipParenthesizedExprDown(binOp.getROperand()); + MemoryStateChange leftChange = history.findExpressionPush(leftOperand); + MemoryStateChange rightChange = history.findExpressionPush(rightOperand); + if (leftChange != null && rightChange != null) { + DfaValue leftValue = leftChange.myTopOfStack; + DfaValue rightValue = rightChange.myTopOfStack; + CauseItem[] causes = findRelationCause(relationType, leftChange, rightChange); + if (causes.length > 0) { + return causes; + } + if (leftValue == rightValue && + (leftValue instanceof DfaVariableValue || leftValue.getDfType() instanceof DfConstantType)) { + List constCauses = new ArrayList<>(); + CauseItem leftCause = constantInitializerCause(leftValue, leftChange.getExpression()); + CauseItem rightCause = constantInitializerCause(rightValue, rightChange.getExpression()); + ContainerUtil.addAllNotNull(constCauses, leftCause, rightCause); + if (constCauses.isEmpty()) { + return new CauseItem[]{ + new CauseItem( + JavaAnalysisLocalize.dfaFindCauseComparisonArgumentsAreTheSame(), + binOp.getOperationSign() + ) + }; + } + return constCauses.toArray(new CauseItem[0]); + } + if (leftValue != rightValue + && relationType.isInequality() + && leftValue.getDfType() instanceof DfConstantType + && rightValue.getDfType() instanceof DfConstantType) { + CauseItem causeItem = new CauseItem( + JavaAnalysisLocalize.dfaFindCauseComparisonArgumentsAreDifferentConstants(), + binOp.getOperationSign() + ); + causeItem.addChildren( + constantInitializerCause(leftValue, leftChange.getExpression()), + constantInitializerCause(rightValue, rightChange.getExpression()) + ); + return new CauseItem[]{causeItem}; + } + } + } + } + if (expression instanceof PsiInstanceOfExpression instanceOf) { + PsiExpression operand = instanceOf.getOperand(); + MemoryStateChange operandHistory = history.findExpressionPush(operand); + if (operandHistory != null) { + DfaValue operandValue = operandHistory.myTopOfStack; + if (!value) { + FactDefinition nullability = operandHistory.findFact(operandValue, FactExtractor.nullability()); + if (nullability.myFact == DfaNullability.NULL) { + CauseItem causeItem = + new CauseItem(JavaAnalysisLocalize.dfaFindCauseValueXIsAlwaysTheSame(operand.getText(), "null"), operand); + causeItem.addChildren(findConstantValueCause(operand, operandHistory, null)); + return new CauseItem[]{causeItem}; + } + } + PsiTypeElement typeElement = instanceOf.getCheckType(); + if (typeElement != null) { + PsiType type = typeElement.getType(); + CauseItem causeItem = findTypeCause(operandHistory, type, value); + if (causeItem != null) { + return new CauseItem[]{causeItem}; + } + } + } + } + if (expression instanceof PsiMethodCallExpression methodCall) { + return new CauseItem[]{fromCallContract(history, methodCall, ContractReturnValue.returnBoolean(value))}; + } + return new CauseItem[0]; + } + + @RequiredReadAction + private static CauseItem constantInitializerCause(DfaValue value, PsiExpression ref) { + if (value.getDfType() instanceof DfConstantType + && ref instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiVariable targetVar + && targetVar.hasModifierProperty(PsiModifier.FINAL)) { + PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(targetVar.getInitializer()); + if (initializer != null) { + return new CauseItem( + JavaAnalysisLocalize.dfaFindCauseVariableIsInitialized( + JavaElementKind.fromElement(targetVar).subject(), + targetVar.getName(), + initializer.getText() + ), + initializer.getContainingFile() == ref.getContainingFile() ? initializer : ref + ); + } + } + return null; } - if (expression instanceof PsiInstanceOfExpression) { - PsiInstanceOfExpression instanceOfExpression = (PsiInstanceOfExpression) expression; - PsiExpression operand = instanceOfExpression.getOperand(); - MemoryStateChange operandHistory = history.findExpressionPush(operand); - if (operandHistory != null) { + + @Nullable + @RequiredReadAction + private static CauseItem findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) { + PsiExpression operand = Objects.requireNonNull(operandHistory.getExpression()); + TypeConstraint wanted = TypeConstraints.instanceOf(type); + PsiType operandType = operand.getType(); + if (operandType != null) { + TypeConstraint constraint = TypeConstraints.instanceOf(operandType); + LocalizeValue name = JavaAnalysisLocalize.dfaFindCauseObjectKindExpression(); + if (operand instanceof PsiMethodCallExpression) { + name = JavaAnalysisLocalize.dfaFindCauseObjectKindMethodReturn(); + } + else if (operand instanceof PsiReferenceExpression operandRefExpr) { + PsiElement target = operandRefExpr.resolve(); + if (target != null) { + name = JavaElementKind.fromElement(target).subject(); + } + } + LocalizeValue explanation = LocalizeValue.ofNullable(constraint.getAssignabilityExplanation(wanted, isInstance, name.get())); + if (explanation.isNotEmpty()) { + if (constraint.equals(wanted)) { + explanation = JavaAnalysisLocalize.dfaFindCauseTypeKnown(name, constraint.toShortString()); + } + return new CauseItem(explanation, operand); + } + } DfaValue operandValue = operandHistory.myTopOfStack; - if (!value) { - FactDefinition nullability = operandHistory.findFact(operandValue, FactExtractor.nullability()); - if (nullability.myFact == DfaNullability.NULL) { - CauseItem causeItem = new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.value.x.is.always.the.same", operand.getText(), "null"), operand); - causeItem.addChildren(findConstantValueCause(operand, operandHistory, null)); - return new CauseItem[]{causeItem}; - } - } - PsiTypeElement typeElement = instanceOfExpression.getCheckType(); - if (typeElement != null) { - PsiType type = typeElement.getType(); - CauseItem causeItem = findTypeCause(operandHistory, type, value); - if (causeItem != null) { - return new CauseItem[]{causeItem}; - } - } - } - } - if (expression instanceof PsiMethodCallExpression) { - return new CauseItem[]{fromCallContract(history, (PsiMethodCallExpression) expression, ContractReturnValue.returnBoolean(value))}; - } - return new CauseItem[0]; - } - private static CauseItem constantInitializerCause(DfaValue value, PsiExpression ref) { - if (!(value.getDfType() instanceof DfConstantType)) { - return null; - } - if (ref instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) ref).resolve(); - if (target instanceof PsiVariable && ((PsiVariable) target).hasModifierProperty(PsiModifier.FINAL)) { - PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(((PsiVariable) target).getInitializer()); - if (initializer != null) { - return new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.variable.is.initialized", JavaElementKind.fromElement(target).subject(), - ((PsiVariable) target).getName(), initializer.getText()), - initializer.getContainingFile() == ref.getContainingFile() ? initializer : ref); - } - } - } - return null; - } - - @Nullable - private static CauseItem findTypeCause(MemoryStateChange operandHistory, PsiType type, boolean isInstance) { - PsiExpression operand = Objects.requireNonNull(operandHistory.getExpression()); - TypeConstraint wanted = TypeConstraints.instanceOf(type); - PsiType operandType = operand.getType(); - if (operandType != null) { - TypeConstraint constraint = TypeConstraints.instanceOf(operandType); - String name = JavaAnalysisBundle.message("dfa.find.cause.object.kind.expression"); - if (operand instanceof PsiMethodCallExpression) { - name = JavaAnalysisBundle.message("dfa.find.cause.object.kind.method.return"); - } else if (operand instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) operand).resolve(); - if (target != null) { - name = JavaElementKind.fromElement(target).subject(); - } - } - String explanation = constraint.getAssignabilityExplanation(wanted, isInstance, name); - if (explanation != null) { - if (constraint.equals(wanted)) { - explanation = JavaAnalysisBundle.message("dfa.find.cause.type.known", name, constraint.toShortString()); - } - return new CauseItem(explanation, operand); - } - } - DfaValue operandValue = operandHistory.myTopOfStack; - - FactDefinition fact = operandHistory.findFact(operandValue, FactExtractor.constraint()); - String explanation = fact.myFact.getAssignabilityExplanation(wanted, isInstance, - JavaAnalysisBundle.message("dfa.find.cause.object.kind.generic")); - while (explanation != null) { - MemoryStateChange causeLocation = fact.myChange; - if (causeLocation == null) { - break; - } - MemoryStateChange prevHistory = causeLocation.getPrevious(); - if (prevHistory == null) { - break; - } - fact = prevHistory.findFact(operandValue, FactExtractor.constraint()); - TypeConstraint prevConstraint = fact.myFact; - String prevExplanation = prevConstraint.getAssignabilityExplanation(wanted, isInstance, - JavaAnalysisBundle.message("dfa.find.cause.object.kind.generic")); - if (prevExplanation == null) { - if (causeLocation.myInstruction instanceof AssignInstruction && causeLocation.myTopOfStack == operandValue) { - PsiExpression rExpression = ((AssignInstruction) causeLocation.myInstruction).getRExpression(); - if (rExpression != null) { - MemoryStateChange rValuePush = causeLocation.findSubExpressionPush(rExpression); - if (rValuePush != null) { - CauseItem assignmentItem = createAssignmentCause((AssignInstruction) causeLocation.myInstruction, operandValue); - assignmentItem.addChildren(findTypeCause(rValuePush, type, isInstance)); - return assignmentItem; - } - } - } - CauseItem causeItem = new CauseItem(explanation, operand); - causeItem.addChildren( - new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.type.is.known.from.place", operand.getText()), causeLocation)); - return causeItem; - } - explanation = prevExplanation; - } - return null; - } - - @Nonnull - private CauseItem[] findRelationCause(RelationType relationType, MemoryStateChange leftChange, MemoryStateChange rightChange) { - return findRelationCause(relationType, leftChange, leftChange.myTopOfStack, rightChange, rightChange.myTopOfStack); - } - - @Nonnull - private CauseItem[] findRelationCause(RelationType relationType, - MemoryStateChange leftChange, DfaValue leftValue, - MemoryStateChange rightChange, DfaValue rightValue) { - ProgressManager.checkCanceled(); - FactDefinition leftNullability = leftChange.findFact(leftValue, FactExtractor.nullability()); - FactDefinition rightNullability = rightChange.findFact(rightValue, FactExtractor.nullability()); - if ((leftNullability.myFact == DfaNullability.NULL && rightNullability.myFact == DfaNullability.NOT_NULL) || - (rightNullability.myFact == DfaNullability.NULL && leftNullability.myFact == DfaNullability.NOT_NULL)) { - return new CauseItem[]{ - findNullabilityCause(leftChange, leftNullability.myFact), - findNullabilityCause(rightChange, rightNullability.myFact) - }; - } + FactDefinition fact = operandHistory.findFact(operandValue, FactExtractor.constraint()); + LocalizeValue explanation = LocalizeValue.ofNullable(fact.myFact.getAssignabilityExplanation( + wanted, + isInstance, + JavaAnalysisLocalize.dfaFindCauseObjectKindGeneric().get() + )); + while (explanation.isNotEmpty()) { + MemoryStateChange causeLocation = fact.myChange; + if (causeLocation == null) { + break; + } + MemoryStateChange prevHistory = causeLocation.getPrevious(); + if (prevHistory == null) { + break; + } + fact = prevHistory.findFact(operandValue, FactExtractor.constraint()); + TypeConstraint prevConstraint = fact.myFact; + LocalizeValue prevExplanation = LocalizeValue.ofNullable(prevConstraint.getAssignabilityExplanation( + wanted, + isInstance, + JavaAnalysisLocalize.dfaFindCauseObjectKindGeneric().get() + )); + if (prevExplanation.isEmpty()) { + if (causeLocation.myInstruction instanceof AssignInstruction assignInsn && causeLocation.myTopOfStack == operandValue) { + PsiExpression rExpression = assignInsn.getRExpression(); + if (rExpression != null) { + MemoryStateChange rValuePush = causeLocation.findSubExpressionPush(rExpression); + if (rValuePush != null) { + CauseItem assignmentItem = createAssignmentCause((AssignInstruction)causeLocation.myInstruction, operandValue); + assignmentItem.addChildren(findTypeCause(rValuePush, type, isInstance)); + return assignmentItem; + } + } + } + CauseItem causeItem = new CauseItem(explanation, operand); + causeItem.addChildren(new CauseItem( + JavaAnalysisLocalize.dfaFindCauseTypeIsKnownFromPlace(operand.getText()), + causeLocation + )); + return causeItem; + } + explanation = prevExplanation; + } + return null; + } + + @RequiredReadAction + private CauseItem[] findRelationCause(RelationType relationType, MemoryStateChange leftChange, MemoryStateChange rightChange) { + return findRelationCause(relationType, leftChange, leftChange.myTopOfStack, rightChange, rightChange.myTopOfStack); + } + + @RequiredReadAction + private CauseItem[] findRelationCause( + RelationType relationType, + MemoryStateChange leftChange, + DfaValue leftValue, + MemoryStateChange rightChange, + DfaValue rightValue + ) { + ProgressManager.checkCanceled(); + FactDefinition leftNullability = leftChange.findFact(leftValue, FactExtractor.nullability()); + FactDefinition rightNullability = rightChange.findFact(rightValue, FactExtractor.nullability()); + if ((leftNullability.myFact == DfaNullability.NULL && rightNullability.myFact == DfaNullability.NOT_NULL) || + (rightNullability.myFact == DfaNullability.NULL && leftNullability.myFact == DfaNullability.NOT_NULL)) { + return new CauseItem[]{ + findNullabilityCause(leftChange, leftNullability.myFact), + findNullabilityCause(rightChange, rightNullability.myFact) + }; + } - FactDefinition leftRange = leftChange.findFact(leftValue, FactExtractor.range()); - FactDefinition rightRange = rightChange.findFact(rightValue, FactExtractor.range()); - LongRangeSet fromRelation = rightRange.myFact.fromRelation(relationType.getNegated()); - if (fromRelation != null && !fromRelation.intersects(leftRange.myFact)) { - return new CauseItem[]{ - findRangeCause(leftChange, leftValue, leftRange.myFact, JavaAnalysisBundle.message("dfa.find.cause.left.operand.range.template")), - findRangeCause(rightChange, rightValue, rightRange.myFact, - JavaAnalysisBundle.message("dfa.find.cause.right.operand.range.template")) - }; - } - if (leftValue instanceof DfaVariableValue) { - if (leftValue == rightValue) { - PsiExpression leftExpression = leftChange.getExpression(); - PsiExpression rightExpression = rightChange.getExpression(); - if (leftExpression instanceof PsiMethodCallExpression) { - CauseItem cause = fromCallContract(leftChange, (PsiMethodCallExpression) leftExpression, rightExpression); - if (cause != null) { - return new CauseItem[]{cause}; - } - } - if (rightExpression instanceof PsiMethodCallExpression) { - CauseItem cause = fromCallContract(rightChange, (PsiMethodCallExpression) rightExpression, leftExpression); - if (cause != null) { - return new CauseItem[]{cause}; - } - } - } - Relation relation = new Relation(relationType, rightValue); - MemoryStateChange change = findRelationAddedChange(leftChange, (DfaVariableValue) leftValue, relation); - if (change != null) { - CauseItem cause = findRelationCause(change, (DfaVariableValue) leftValue, relation, rightChange); - if (cause != null) { - Instruction instruction = change.myInstruction; - if (instruction instanceof AssignInstruction) { - PsiExpression expression = ((AssignInstruction) instruction).getRExpression(); - MemoryStateChange assignmentChange = change.findExpressionPush(expression); - if (assignmentChange != null) { - DfaValue target = change.myTopOfStack; - if (target == rightValue) { - return ArrayUtil.prepend(cause, findRelationCause(relationType, leftChange, assignmentChange)); - } - } - } - } - return new CauseItem[]{cause}; - } - } - if (rightValue instanceof DfaVariableValue) { - Relation relation = new Relation( - Objects.requireNonNull(relationType.getFlipped()), leftValue); - MemoryStateChange change = findRelationAddedChange(rightChange, (DfaVariableValue) rightValue, relation); - if (change != null) { - return new CauseItem[]{findRelationCause(change, (DfaVariableValue) rightValue, relation, leftChange)}; - } - } - if (relationType == RelationType.NE) { - SpecialField leftField = SpecialField.fromQualifier(leftValue); - SpecialField rightField = SpecialField.fromQualifier(rightValue); - if (leftField != null && leftField == rightField) { - DfaValue leftSpecial = leftField.createValue(getFactory(), leftValue); - DfaValue rightSpecial = rightField.createValue(getFactory(), rightValue); - CauseItem[] specialCause = findRelationCause(relationType, leftChange, leftSpecial, rightChange, rightSpecial); - if (specialCause.length > 0) { - CauseItem item = new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.values.cannot.be.equal.because", - leftValue + "." + leftField + " != " + rightValue + "." + rightField), - (PsiElement) null); - item.addChildren(specialCause); - return new CauseItem[]{item}; - } - } - } - return new CauseItem[0]; - } - - private CauseItem findRelationCause(MemoryStateChange change, DfaVariableValue value, - Relation relation, MemoryStateChange counterPartChange) { - Instruction instruction = change.myInstruction; - String condition = value + " " + relation; - if (instruction instanceof AssignInstruction) { - DfaValue target = change.myTopOfStack; - PsiExpression rValue = ((AssignInstruction) instruction).getRExpression(); - CauseItem item = createAssignmentCause((AssignInstruction) instruction, target); - if (target == value) { - MemoryStateChange rValuePush = change.findSubExpressionPush(rValue); - if (rValuePush != null) { - item.addChildren(findRelationCause(relation.myRelationType, rValuePush, counterPartChange)); + FactDefinition leftRange = leftChange.findFact(leftValue, FactExtractor.range()); + FactDefinition rightRange = rightChange.findFact(rightValue, FactExtractor.range()); + LongRangeSet fromRelation = rightRange.myFact.fromRelation(relationType.getNegated()); + if (fromRelation != null && !fromRelation.intersects(leftRange.myFact)) { + return new CauseItem[]{ + findRangeCause( + leftChange, + leftValue, + leftRange.myFact, + JavaAnalysisLocalize.dfaFindCauseLeftOperandRangeTemplate() + ), + findRangeCause( + rightChange, + rightValue, + rightRange.myFact, + JavaAnalysisLocalize.dfaFindCauseRightOperandRangeTemplate() + ) + }; } - return item; - } - if (target == relation.myCounterpart) { - return item; - } - } - PsiExpression expression = change.getExpression(); - if (expression != null) { - Collection relations = Collections.emptyList(); - if (expression instanceof PsiBinaryExpression) { - DfaRelation rel = getBinaryExpressionRelation(change, (PsiBinaryExpression) expression); - if (rel != null) { - if (isSameRelation(rel, value, relation)) { - return new CauseItem(new CustomDfaProblemType( - JavaAnalysisBundle.message("dfa.find.cause.condition.was.checked.before", condition)), expression); - } - relations = Collections.singleton(rel); - } - } - if (expression instanceof PsiCallExpression) { - relations = getCallRelations((PsiCallExpression) expression); - } - List chain = findDeductionChain(change, relations, value, relation); - if (!chain.isEmpty()) { - CauseItem[] result = new CauseItem[0]; - for (DfaRelation deduced : chain) { - CauseItem[] cause = - findRelationCause(deduced.getRelation(), change, deduced.getLeftOperand(), change, deduced.getRightOperand()); - result = ArrayUtil.mergeArrays(result, cause); - } - if (result.length > 1) { - CauseItem item = new CauseItem(new CustomDfaProblemType( - JavaAnalysisBundle.message("dfa.find.cause.condition.was.deduced", condition)), (PsiElement) null); - item.addChildren(result); - return item; - } - } - return new CauseItem(new CustomDfaProblemType(JavaAnalysisBundle.message("dfa.find.cause.condition.is.known.from.place", condition)), - expression); - } - return null; - } - - private static List findDeductionChain(MemoryStateChange change, - Collection knownRelations, - DfaVariableValue value, - Relation relation) { - for (DfaRelation rel : knownRelations) { - if (isSameRelation(rel, value, relation)) { - continue; - } - for (Map.Entry entry : change.myChanges.entrySet()) { - DfaVariableValue actualVar = entry.getKey(); - for (Relation actualRelation : entry.getValue().myAddedRelations) { - if (isSameRelation(rel, actualVar, actualRelation)) { - DfaValue left; - DfaValue right; - RelationType type; - if (actualRelation.myRelationType == RelationType.EQ || - (relation.myRelationType != RelationType.NE && relation.myRelationType == actualRelation.myRelationType)) { - type = relation.myRelationType; - } else if (relation.myRelationType == RelationType.EQ) { - type = actualRelation.myRelationType; - } else { - continue; - } - if (actualVar == value) { - left = actualRelation.myCounterpart; - right = relation.myCounterpart; - } else if (actualVar == relation.myCounterpart) { - left = value; - right = actualRelation.myCounterpart; - } else if (actualRelation.myCounterpart == relation.myCounterpart) { - left = value; - right = actualVar; - } else if (actualRelation.myCounterpart == value) { - left = actualVar; - right = relation.myCounterpart; - } else { - continue; - } - DfaRelation rel1 = DfaRelation.createRelation(left, type, right); - DfaRelation rel2 = DfaRelation.createRelation(actualVar, actualRelation.myRelationType, actualRelation.myCounterpart); - return StreamEx.of(rel1, rel2).nonNull().toImmutableList(); - } - } - } - } - return Collections.emptyList(); - } - - private static boolean isSameRelation(DfaRelation dfaRel, DfaVariableValue var, Relation relation) { - DfaValue counterpart; - RelationType type; - if (dfaRel.getLeftOperand() == var) { - type = dfaRel.getRelation(); - counterpart = dfaRel.getRightOperand(); - } else if (dfaRel.getRightOperand() == var) { - type = dfaRel.getRelation().getFlipped(); - counterpart = dfaRel.getLeftOperand(); - } else { - return false; - } - return counterpart == relation.myCounterpart && type != null; - } - - @Nullable - private static DfaRelation getBinaryExpressionRelation(MemoryStateChange change, PsiBinaryExpression binOp) { - PsiExpression lOperand = binOp.getLOperand(); - PsiExpression rOperand = binOp.getROperand(); - MemoryStateChange leftPos = change.findExpressionPush(lOperand); - MemoryStateChange rightPos = change.findExpressionPush(rOperand); - if (leftPos != null && rightPos != null) { - DfaValue leftValue = leftPos.myTopOfStack; - DfaValue rightValue = rightPos.myTopOfStack; - RelationType type = RelationType.fromElementType(binOp.getOperationTokenType()); - if (type != null) { - return DfaRelation.createRelation(leftValue, type, rightValue); - } - } - return null; - } - - private Collection getCallRelations(PsiCallExpression callExpression) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(callExpression); - Set results = new LinkedHashSet<>(); - for (MethodContract contract : contracts) { - for (ContractValue condition : contract.getConditions()) { - DfaCondition rel = condition.fromCall(getFactory(), callExpression); - ContainerUtil.addIfNotNull(results, ObjectUtil.tryCast(rel, DfaRelation.class)); - } - } - return results; - } - - private CauseItem findNullabilityCause(MemoryStateChange factUse, DfaNullability nullability) { - PsiExpression expression = factUse.getExpression(); - if (expression instanceof PsiTypeCastExpression) { - MemoryStateChange operandPush = factUse.findSubExpressionPush(((PsiTypeCastExpression) expression).getOperand()); - if (operandPush != null) { - return findNullabilityCause(operandPush, nullability); - } - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - PsiMethod method = call.resolveMethod(); - CauseItem causeItem = fromMemberNullability(nullability, method, JavaElementKind.METHOD, - call.getMethodExpression().getReferenceNameElement()); - if (causeItem == null) { - switch (nullability) { - case NULL: - case NULLABLE: - causeItem = fromCallContract(factUse, call, ContractReturnValue.returnNull()); - break; - case NOT_NULL: - causeItem = fromCallContract(factUse, call, ContractReturnValue.returnNotNull()); - break; - default: - } - } - if (causeItem != null) { - return causeItem; - } - } - if (expression instanceof PsiReferenceExpression) { - PsiVariable variable = ObjectUtil.tryCast(((PsiReferenceExpression) expression).resolve(), PsiVariable.class); - if (variable != null) { - CauseItem causeItem = fromMemberNullability(nullability, variable, JavaElementKind.fromElement(variable), expression); - if (causeItem != null) { - return causeItem; - } - } - } - FactDefinition info = factUse.findFact(factUse.myTopOfStack, FactExtractor.nullability()); - MemoryStateChange factDef = info.myFact == nullability ? info.myChange : null; - if (nullability == DfaNullability.NOT_NULL) { - String explanation = getObviouslyNonNullExplanation(expression); - if (explanation != null) { - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.obviously.non.null.expression", explanation), expression); - } - if (factDef != null) { - if (factDef.myInstruction instanceof CheckNotNullInstruction) { - NullabilityProblemKind.NullabilityProblem problem = ((CheckNotNullInstruction) factDef.myInstruction).getProblem(); - PsiExpression dereferenced = problem.getDereferencedExpression(); - String text = dereferenced == null ? factUse.myTopOfStack.toString() : dereferenced.getText(); - if (dereferenced != null && problem.getKind() == NullabilityProblemKind.passingToNotNullParameter) { - PsiExpression arg = dereferenced; - while (arg.getParent() instanceof PsiParenthesizedExpression) { - arg = (PsiExpression) arg.getParent(); - } - PsiParameter parameter = MethodCallUtils.getParameterForArgument(dereferenced); - if (parameter != null) { - CauseItem item = - new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.was.passed.as.non.null.parameter", text), dereferenced); - item.addChildren(fromMemberNullability(DfaNullability.NOT_NULL, parameter, JavaElementKind.PARAMETER, dereferenced)); - return item; - } - } - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.was.dereferenced", text), dereferenced); - } - if (factDef.myInstruction instanceof InstanceofInstruction) { - PsiExpression operand = ((InstanceofInstruction) factDef.myInstruction).getExpression(); - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.instanceof.implies.non.nullity"), operand); - } - } - } - if (factDef != null && expression != null) { - DfaValue value = factUse.myTopOfStack; - if (factDef.myInstruction instanceof AssignInstruction && factDef.myTopOfStack == value) { - PsiExpression rExpression = ((AssignInstruction) factDef.myInstruction).getRExpression(); - if (rExpression != null) { - MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression); - if (rValuePush != null) { - CauseItem assignmentItem = createAssignmentCause((AssignInstruction) factDef.myInstruction, value); - assignmentItem.addChildren(findNullabilityCause(rValuePush, nullability)); - return assignmentItem; - } - } - } - PsiExpression defExpression = factDef.getExpression(); - if (defExpression != null) { - return new CauseItem( - JavaAnalysisBundle.message("dfa.find.cause.value.is.known.from.place", expression.getText(), nullability.getPresentationName()), - defExpression); - } - } - return null; - } - - private CauseItem fromMemberNullability(DfaNullability nullability, PsiModifierListOwner owner, - JavaElementKind memberKind, PsiElement anchor) { - if (owner != null) { - NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(owner.getProject()).findEffectiveNullabilityInfo(owner); - String name = ((PsiNamedElement) owner).getName(); - if (info != null && DfaNullability.fromNullability(info.getNullability()) == nullability) { - String message; - if (info.isInferred()) { - if (owner instanceof PsiParameter && - anchor instanceof PsiReferenceExpression && - ((PsiReferenceExpression) anchor).isReferenceTo(owner)) { - // Do not use inference inside method itself - return null; - } - message = JavaAnalysisBundle - .message("dfa.find.cause.nullability.inferred", memberKind.subject(), name, nullability.getPresentationName()); - } else if (info.isExternal()) { - message = JavaAnalysisBundle - .message("dfa.find.cause.nullability.externally.annotated", memberKind.subject(), name, nullability.getPresentationName()); - } else if (info.isContainer()) { - PsiAnnotationOwner annoOwner = info.getAnnotation().getOwner(); - message = JavaAnalysisBundle.message("dfa.find.cause.nullability.inherited.from.container", - memberKind.subject(), name, nullability.getPresentationName()); - if (annoOwner instanceof PsiModifierList) { - PsiElement parent = ((PsiModifierList) annoOwner).getParent(); - if (parent instanceof PsiClass) { - PsiClass aClass = (PsiClass) parent; - message = JavaAnalysisBundle.message("dfa.find.cause.nullability.inherited.from.class", - memberKind.subject(), name, aClass.getName(), nullability.getPresentationName()); - if ("package-info".equals(aClass.getName())) { - PsiFile file = aClass.getContainingFile(); - if (file instanceof PsiJavaFile) { - message = JavaAnalysisBundle.message("dfa.find.cause.nullability.inherited.from.package", - memberKind.subject(), name, ((PsiJavaFile) file).getPackageName(), - nullability.getPresentationName()); + if (leftValue instanceof DfaVariableValue leftDfaVarValue) { + if (leftDfaVarValue == rightValue) { + PsiExpression leftExpression = leftChange.getExpression(); + PsiExpression rightExpression = rightChange.getExpression(); + if (leftExpression instanceof PsiMethodCallExpression leftExprMethodCall) { + CauseItem cause = fromCallContract(leftChange, leftExprMethodCall, rightExpression); + if (cause != null) { + return new CauseItem[]{cause}; + } + } + if (rightExpression instanceof PsiMethodCallExpression rightExprMethodCall) { + CauseItem cause = fromCallContract(rightChange, rightExprMethodCall, leftExpression); + if (cause != null) { + return new CauseItem[]{cause}; + } } - } - } - } - if (annoOwner instanceof PsiNamedElement) { - message = JavaAnalysisBundle.message("dfa.find.cause.nullability.inherited.from.named.element", - memberKind.subject(), name, ((PsiNamedElement) annoOwner).getName(), - nullability.getPresentationName()); - } - } else { - message = JavaAnalysisBundle.message("dfa.find.cause.nullability.explicitly.annotated", - memberKind.subject(), name, nullability.getPresentationName()); - } - if (info.getAnnotation().getContainingFile() == anchor.getContainingFile()) { - anchor = info.getAnnotation(); - } else if (owner.getContainingFile() == anchor.getContainingFile()) { - anchor = owner.getNavigationElement(); - if (anchor instanceof PsiNameIdentifierOwner) { - anchor = ((PsiNameIdentifierOwner) anchor).getNameIdentifier(); - } + } + Relation relation = new Relation(relationType, rightValue); + MemoryStateChange change = findRelationAddedChange(leftChange, leftDfaVarValue, relation); + if (change != null) { + CauseItem cause = findRelationCause(change, leftDfaVarValue, relation, rightChange); + if (cause != null && change.myInstruction instanceof AssignInstruction assignInsn) { + MemoryStateChange assignmentChange = change.findExpressionPush(assignInsn.getRExpression()); + if (assignmentChange != null) { + DfaValue target = change.myTopOfStack; + if (target == rightValue) { + return ArrayUtil.prepend(cause, findRelationCause(relationType, leftChange, assignmentChange)); + } + } + } + return new CauseItem[]{cause}; + } } - return new CauseItem(message, anchor); - } - if (owner instanceof PsiField && getFactory().canTrustFieldInitializer((PsiField) owner)) { - Pair fieldNullability = - NullabilityUtil.getNullabilityFromFieldInitializers((PsiField) owner, Nullability.UNKNOWN); - if (fieldNullability.second == DfaNullability.toNullability(nullability)) { - PsiExpression initializer = fieldNullability.first; - if (initializer != null) { - if (initializer.getContainingFile() == anchor.getContainingFile()) { - anchor = initializer; - } - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.field.initializer.nullability", name, - DfaNullability.fromNullability(fieldNullability.second).getPresentationName()), - anchor); - } - if (owner.getContainingFile() == anchor.getContainingFile()) { - anchor = owner; - } - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.field.assigned.nullability", name, - DfaNullability.fromNullability(fieldNullability.second).getPresentationName()), - anchor); - } - } - } - return null; - } - - private CauseItem fromCallContract(MemoryStateChange history, PsiMethodCallExpression call, PsiExpression target) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - for (int i = 0; i < args.length; i++) { - if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(args[i], target)) { - return fromCallContract(history, call, ContractReturnValue.returnParameter(i)); - } - } - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(qualifier, target)) { - return fromCallContract(history, call, ContractReturnValue.returnThis()); + if (rightValue instanceof DfaVariableValue rightDfaVarValue) { + Relation relation = new Relation( + Objects.requireNonNull(relationType.getFlipped()), leftValue); + MemoryStateChange change = findRelationAddedChange(rightChange, rightDfaVarValue, relation); + if (change != null) { + return new CauseItem[]{findRelationCause(change, rightDfaVarValue, relation, leftChange)}; + } + } + if (relationType == RelationType.NE) { + SpecialField leftField = SpecialField.fromQualifier(leftValue); + SpecialField rightField = SpecialField.fromQualifier(rightValue); + if (leftField != null && leftField == rightField) { + DfaValue leftSpecial = leftField.createValue(getFactory(), leftValue); + DfaValue rightSpecial = rightField.createValue(getFactory(), rightValue); + CauseItem[] specialCause = findRelationCause(relationType, leftChange, leftSpecial, rightChange, rightSpecial); + if (specialCause.length > 0) { + CauseItem item = new CauseItem( + JavaAnalysisLocalize.dfaFindCauseValuesCannotBeEqualBecause(leftValue + "." + leftField + " != " + rightValue + "." + rightField), + (PsiElement)null + ); + item.addChildren(specialCause); + return new CauseItem[]{item}; + } + } + } + return new CauseItem[0]; + } + + @RequiredReadAction + private CauseItem findRelationCause( + MemoryStateChange change, + DfaVariableValue value, + Relation relation, + MemoryStateChange counterPartChange + ) { + String condition = value + " " + relation; + if (change.myInstruction instanceof AssignInstruction assignInsn) { + DfaValue target = change.myTopOfStack; + PsiExpression rValue = assignInsn.getRExpression(); + CauseItem item = createAssignmentCause(assignInsn, target); + if (target == value) { + MemoryStateChange rValuePush = change.findSubExpressionPush(rValue); + if (rValuePush != null) { + item.addChildren(findRelationCause(relation.myRelationType, rValuePush, counterPartChange)); + } + return item; + } + if (target == relation.myCounterpart) { + return item; + } + } + PsiExpression expression = change.getExpression(); + if (expression != null) { + Collection relations = Collections.emptyList(); + if (expression instanceof PsiBinaryExpression binaryExpression) { + DfaRelation rel = getBinaryExpressionRelation(change, binaryExpression); + if (rel != null) { + if (isSameRelation(rel, value, relation)) { + return new CauseItem( + new CustomDfaProblemType(JavaAnalysisLocalize.dfaFindCauseConditionWasCheckedBefore(condition)), + binaryExpression + ); + } + relations = Collections.singleton(rel); + } + } + if (expression instanceof PsiCallExpression call) { + relations = getCallRelations(call); + } + List chain = findDeductionChain(change, relations, value, relation); + if (!chain.isEmpty()) { + CauseItem[] result = new CauseItem[0]; + for (DfaRelation deduced : chain) { + CauseItem[] cause = + findRelationCause(deduced.getRelation(), change, deduced.getLeftOperand(), change, deduced.getRightOperand()); + result = ArrayUtil.mergeArrays(result, cause); + } + if (result.length > 1) { + CauseItem item = new CauseItem( + new CustomDfaProblemType(JavaAnalysisLocalize.dfaFindCauseConditionWasDeduced(condition)), + (PsiElement)null + ); + item.addChildren(result); + return item; + } + } + return new CauseItem( + new CustomDfaProblemType(JavaAnalysisLocalize.dfaFindCauseConditionIsKnownFromPlace(condition)), + expression + ); + } + return null; + } + + private static List findDeductionChain( + MemoryStateChange change, + Collection knownRelations, + DfaVariableValue value, + Relation relation + ) { + for (DfaRelation rel : knownRelations) { + if (isSameRelation(rel, value, relation)) { + continue; + } + for (Map.Entry entry : change.myChanges.entrySet()) { + DfaVariableValue actualVar = entry.getKey(); + for (Relation actualRelation : entry.getValue().myAddedRelations) { + if (isSameRelation(rel, actualVar, actualRelation)) { + DfaValue left; + DfaValue right; + RelationType type; + if (actualRelation.myRelationType == RelationType.EQ || + (relation.myRelationType != RelationType.NE && relation.myRelationType == actualRelation.myRelationType)) { + type = relation.myRelationType; + } + else if (relation.myRelationType == RelationType.EQ) { + type = actualRelation.myRelationType; + } + else { + continue; + } + if (actualVar == value) { + left = actualRelation.myCounterpart; + right = relation.myCounterpart; + } + else if (actualVar == relation.myCounterpart) { + left = value; + right = actualRelation.myCounterpart; + } + else if (actualRelation.myCounterpart == relation.myCounterpart) { + left = value; + right = actualVar; + } + else if (actualRelation.myCounterpart == value) { + left = actualVar; + right = relation.myCounterpart; + } + else { + continue; + } + DfaRelation rel1 = DfaRelation.createRelation(left, type, right); + DfaRelation rel2 = + DfaRelation.createRelation(actualVar, actualRelation.myRelationType, actualRelation.myCounterpart); + return StreamEx.of(rel1, rel2).nonNull().toImmutableList(); + } + } + } + } + return Collections.emptyList(); } - return null; - } - private CauseItem fromCallContract(MemoryStateChange history, PsiCallExpression call, ContractReturnValue contractReturnValue) { - PsiMethod method = call.resolveMethod(); - if (method == null) { - return null; - } - List contracts = JavaMethodContractUtil.getMethodCallContracts(method, call); - if (contracts.isEmpty()) { - return null; - } - MethodContract contract = contracts.get(0); - if (call instanceof PsiMethodCallExpression) { - PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) call).getMethodExpression(); - String name = methodExpression.getReferenceName(); - if (contracts.size() == 1 && contract.isTrivial() && contractReturnValue.isSuperValueOf(contract.getReturnValue())) { - String message = JavaAnalysisBundle.message("dfa.find.cause.contract.trivial", getContractKind(call), - name, contract.getReturnValue()); - return new CauseItem(message, methodExpression.getReferenceNameElement()); - } - List nonIntersecting = MethodContract.toNonIntersectingContracts(contracts); - if (nonIntersecting != null) { - Condition condition = contractReturnValue instanceof ContractReturnValue.ParameterReturnValue ? - mc -> contractReturnValue.equals(mc.getReturnValue()) : - mc -> contractReturnValue.isSuperValueOf(mc.getReturnValue()); - MethodContract onlyContract = ContainerUtil.getOnlyItem(ContainerUtil.filter(nonIntersecting, condition)); - if (onlyContract != null) { - return fromSingleContract(history, (PsiMethodCallExpression) call, method, onlyContract); - } - } - for (MethodContract c : contracts) { - ThreeState applies = contractApplies((PsiMethodCallExpression) call, c); - if (applies == ThreeState.NO) { - continue; - } - if (applies == ThreeState.UNSURE) { - break; - } - if (applies == ThreeState.YES) { - if (contractReturnValue.isSuperValueOf(c.getReturnValue())) { - return fromSingleContract(history, (PsiMethodCallExpression) call, method, c); - } - } - } - } - return null; - } - - private static - @Nonnull - @Nls - String getContractKind(PsiCallExpression call) { - PsiMethod method = call.resolveMethod(); - if (method == null || JavaMethodContractUtil.hasExplicitContractAnnotation(method)) { - return JavaAnalysisBundle.message("dfa.find.cause.contract.kind.explicit"); - } - List contracts = JavaMethodContractUtil.getMethodCallContracts(method, call); - if (contracts.isEmpty()) { - return JavaAnalysisBundle.message("dfa.find.cause.contract.kind.explicit"); - } - if (contracts.stream().allMatch(c -> c instanceof StandardMethodContract)) { - return JavaAnalysisBundle.message("dfa.find.cause.contract.kind.inferred"); + private static boolean isSameRelation(DfaRelation dfaRel, DfaVariableValue var, Relation relation) { + DfaValue counterpart; + RelationType type; + if (dfaRel.getLeftOperand() == var) { + type = dfaRel.getRelation(); + counterpart = dfaRel.getRightOperand(); + } + else if (dfaRel.getRightOperand() == var) { + type = dfaRel.getRelation().getFlipped(); + counterpart = dfaRel.getLeftOperand(); + } + else { + return false; + } + return counterpart == relation.myCounterpart && type != null; } - return JavaAnalysisBundle.message("dfa.find.cause.contract.kind.hard.coded"); - } - - @Nonnull - private ThreeState contractApplies(@Nonnull PsiMethodCallExpression call, @Nonnull MethodContract contract) { - List conditions = contract.getConditions(); - for (ContractValue condition : conditions) { - DfaCondition cond = condition.fromCall(getFactory(), call); - if (cond == DfaCondition.getTrue()) { - return ThreeState.YES; - } - if (cond == DfaCondition.getFalse()) { - return ThreeState.NO; - } + + @Nullable + private static DfaRelation getBinaryExpressionRelation(MemoryStateChange change, PsiBinaryExpression binOp) { + PsiExpression lOperand = binOp.getLOperand(); + PsiExpression rOperand = binOp.getROperand(); + MemoryStateChange leftPos = change.findExpressionPush(lOperand); + MemoryStateChange rightPos = change.findExpressionPush(rOperand); + if (leftPos != null && rightPos != null) { + DfaValue leftValue = leftPos.myTopOfStack; + DfaValue rightValue = rightPos.myTopOfStack; + RelationType type = RelationType.fromElementType(binOp.getOperationTokenType()); + if (type != null) { + return DfaRelation.createRelation(leftValue, type, rightValue); + } + } + return null; } - return ThreeState.UNSURE; - } - - @Nonnull - private CauseItem fromSingleContract(@Nonnull MemoryStateChange history, @Nonnull PsiMethodCallExpression call, - @Nonnull PsiMethod method, @Nonnull MethodContract contract) { - List conditions = contract.getConditions(); - String conditionsText = StringUtil.join(conditions, c -> c.getPresentationText(method), - JavaAnalysisBundle.message("dfa.find.cause.condition.joiner")); - String message; - if (contract.getReturnValue().isFail()) { - message = JavaAnalysisBundle.message("dfa.find.cause.contract.throws.on.condition", - getContractKind(call), method.getName(), conditionsText); - } else { - message = JavaAnalysisBundle.message("dfa.find.cause.contract.returns.on.condition", - getContractKind(call), method.getName(), contract.getReturnValue(), conditionsText); + + private Collection getCallRelations(PsiCallExpression callExpression) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(callExpression); + Set results = new LinkedHashSet<>(); + for (MethodContract contract : contracts) { + for (ContractValue condition : contract.getConditions()) { + DfaCondition rel = condition.fromCall(getFactory(), callExpression); + ContainerUtil.addIfNotNull(results, ObjectUtil.tryCast(rel, DfaRelation.class)); + } + } + return results; } - CauseItem causeItem = new CauseItem(message, call.getMethodExpression().getReferenceNameElement()); - for (ContractValue contractValue : conditions) { - if (!(contractValue instanceof ContractValue.Condition)) { - continue; - } - ContractValue.Condition condition = (ContractValue.Condition) contractValue; - ContractValue leftVal = condition.getLeft(); - ContractValue rightVal = condition.getRight(); - RelationType type = condition.getRelationType(); - DfaCallArguments arguments = DfaCallArguments.fromCall(getFactory(), call); - PsiExpression leftPlace = leftVal.findPlace(call); - MemoryStateChange leftPush = history.findSubExpressionPush(leftPlace); - if (leftPush == null && arguments != null) { - DfaValue left = leftVal.makeDfaValue(getFactory(), arguments); - leftPush = MemoryStateChange.create(history.getPrevious(), new PushInstruction(left, null), Collections.emptyMap(), left); - } - PsiExpression rightPlace = rightVal.findPlace(call); - MemoryStateChange rightPush = history.findSubExpressionPush(rightPlace); - if (rightPush == null && arguments != null) { - DfaValue right = rightVal.makeDfaValue(getFactory(), arguments); - rightPush = MemoryStateChange.create(history.getPrevious(), new PushInstruction(right, null), Collections.emptyMap(), right); - } - if (leftPush != null && rightPush != null) { - causeItem.addChildren(findRelationCause(type, leftPush, rightPush)); - } + + @RequiredReadAction + private CauseItem findNullabilityCause(MemoryStateChange factUse, DfaNullability nullability) { + PsiExpression expression = factUse.getExpression(); + if (expression instanceof PsiTypeCastExpression typeCast) { + MemoryStateChange operandPush = factUse.findSubExpressionPush(typeCast.getOperand()); + if (operandPush != null) { + return findNullabilityCause(operandPush, nullability); + } + } + if (expression instanceof PsiMethodCallExpression call) { + PsiMethod method = call.resolveMethod(); + CauseItem causeItem = fromMemberNullability( + nullability, + method, + JavaElementKind.METHOD, + call.getMethodExpression().getReferenceNameElement() + ); + if (causeItem == null) { + switch (nullability) { + case NULL: + case NULLABLE: + causeItem = fromCallContract(factUse, call, ContractReturnValue.returnNull()); + break; + case NOT_NULL: + causeItem = fromCallContract(factUse, call, ContractReturnValue.returnNotNull()); + break; + default: + } + } + if (causeItem != null) { + return causeItem; + } + } + if (expression instanceof PsiReferenceExpression refExpr) { + PsiVariable variable = ObjectUtil.tryCast(refExpr.resolve(), PsiVariable.class); + if (variable != null) { + CauseItem causeItem = fromMemberNullability(nullability, variable, JavaElementKind.fromElement(variable), expression); + if (causeItem != null) { + return causeItem; + } + } + } + FactDefinition info = factUse.findFact(factUse.myTopOfStack, FactExtractor.nullability()); + MemoryStateChange factDef = info.myFact == nullability ? info.myChange : null; + if (nullability == DfaNullability.NOT_NULL) { + LocalizeValue explanation = getObviouslyNonNullExplanation(expression); + if (explanation.isNotEmpty()) { + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseObviouslyNonNullExpression(explanation), expression); + } + if (factDef != null) { + if (factDef.myInstruction instanceof CheckNotNullInstruction checkNotNullInsn) { + NullabilityProblemKind.NullabilityProblem problem = checkNotNullInsn.getProblem(); + PsiExpression dereferenced = problem.getDereferencedExpression(); + String text = dereferenced == null ? factUse.myTopOfStack.toString() : dereferenced.getText(); + if (dereferenced != null && problem.getKind() == NullabilityProblemKind.passingToNotNullParameter) { + PsiExpression arg = dereferenced; + while (arg.getParent() instanceof PsiParenthesizedExpression parenthesized) { + arg = parenthesized; + } + PsiParameter parameter = MethodCallUtils.getParameterForArgument(dereferenced); + if (parameter != null) { + CauseItem item = new CauseItem( + JavaAnalysisLocalize.dfaFindCauseWasPassedAsNonNullParameter(text), + dereferenced + ); + item.addChildren(fromMemberNullability( + DfaNullability.NOT_NULL, + parameter, + JavaElementKind.PARAMETER, + dereferenced + )); + return item; + } + } + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseWasDereferenced(text), dereferenced); + } + if (factDef.myInstruction instanceof InstanceofInstruction instanceofInsn) { + PsiExpression operand = instanceofInsn.getExpression(); + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseInstanceofImpliesNonNullity(), operand); + } + } + } + if (factDef != null && expression != null) { + DfaValue value = factUse.myTopOfStack; + if (factDef.myInstruction instanceof AssignInstruction assignInsn && factDef.myTopOfStack == value) { + PsiExpression rExpression = assignInsn.getRExpression(); + if (rExpression != null) { + MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression); + if (rValuePush != null) { + CauseItem assignmentItem = createAssignmentCause((AssignInstruction)factDef.myInstruction, value); + assignmentItem.addChildren(findNullabilityCause(rValuePush, nullability)); + return assignmentItem; + } + } + } + PsiExpression defExpression = factDef.getExpression(); + if (defExpression != null) { + return new CauseItem( + JavaAnalysisLocalize.dfaFindCauseValueIsKnownFromPlace( + expression.getText(), + nullability.getPresentationName() + ), + defExpression + ); + } + } + return null; + } + + @RequiredReadAction + private CauseItem fromMemberNullability( + DfaNullability nullability, + PsiModifierListOwner owner, + JavaElementKind memberKind, + PsiElement anchor + ) { + if (owner != null) { + NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(owner.getProject()).findEffectiveNullabilityInfo(owner); + String name = ((PsiNamedElement)owner).getName(); + if (info != null && DfaNullability.fromNullability(info.getNullability()) == nullability) { + LocalizeValue message; + if (info.isInferred()) { + if (owner instanceof PsiParameter + && anchor instanceof PsiReferenceExpression refExpr + && refExpr.isReferenceTo(owner)) { + // Do not use inference inside method itself + return null; + } + message = JavaAnalysisLocalize.dfaFindCauseNullabilityInferred( + memberKind.subject(), + name, + nullability.getPresentationName() + ); + } + else if (info.isExternal()) { + message = JavaAnalysisLocalize.dfaFindCauseNullabilityExternallyAnnotated( + memberKind.subject(), + name, + nullability.getPresentationName().get() + ); + } + else if (info.isContainer()) { + PsiAnnotationOwner annoOwner = info.getAnnotation().getOwner(); + message = JavaAnalysisLocalize.dfaFindCauseNullabilityInheritedFromContainer( + memberKind.subject(), + name, + nullability.getPresentationName() + ); + if (annoOwner instanceof PsiModifierList modifierList && modifierList.getParent() instanceof PsiClass aClass) { + message = JavaAnalysisLocalize.dfaFindCauseNullabilityInheritedFromClass( + memberKind.subject(), + name, + aClass.getName(), + nullability.getPresentationName() + ); + if ("package-info".equals(aClass.getName()) && aClass.getContainingFile() instanceof PsiJavaFile javaFile) { + message = JavaAnalysisLocalize.dfaFindCauseNullabilityInheritedFromPackage( + memberKind.subject(), + name, + javaFile.getPackageName(), + nullability.getPresentationName() + ); + } + } + if (annoOwner instanceof PsiNamedElement namedElement) { + message = JavaAnalysisLocalize.dfaFindCauseNullabilityInheritedFromNamedElement( + memberKind.subject(), + name, + namedElement.getName(), + nullability.getPresentationName() + ); + } + } + else { + message = JavaAnalysisLocalize.dfaFindCauseNullabilityExplicitlyAnnotated( + memberKind.subject(), + name, + nullability.getPresentationName() + ); + } + if (info.getAnnotation().getContainingFile() == anchor.getContainingFile()) { + anchor = info.getAnnotation(); + } + else if (owner.getContainingFile() == anchor.getContainingFile()) { + anchor = owner.getNavigationElement(); + if (anchor instanceof PsiNameIdentifierOwner nameIdentifierOwner) { + anchor = nameIdentifierOwner.getNameIdentifier(); + } + } + return new CauseItem(message, anchor); + } + if (owner instanceof PsiField field && getFactory().canTrustFieldInitializer(field)) { + Pair fieldNullability = + NullabilityUtil.getNullabilityFromFieldInitializers(field, Nullability.UNKNOWN); + if (fieldNullability.second == DfaNullability.toNullability(nullability)) { + PsiExpression initializer = fieldNullability.first; + if (initializer != null) { + if (initializer.getContainingFile() == anchor.getContainingFile()) { + anchor = initializer; + } + return new CauseItem( + JavaAnalysisLocalize.dfaFindCauseFieldInitializerNullability( + name, + DfaNullability.fromNullability(fieldNullability.second).getPresentationName() + ), + anchor + ); + } + if (field.getContainingFile() == anchor.getContainingFile()) { + anchor = field; + } + return new CauseItem( + JavaAnalysisLocalize.dfaFindCauseFieldAssignedNullability( + name, + DfaNullability.fromNullability(fieldNullability.second).getPresentationName() + ), + anchor + ); + } + } + } + return null; } - return causeItem; - } - - private static CauseItem findRangeCause(MemoryStateChange factUse, DfaValue value, LongRangeSet range, @Nls String template) { - if (value instanceof DfaVariableValue) { - VariableDescriptor descriptor = ((DfaVariableValue) value).getDescriptor(); - if (descriptor instanceof SpecialField && range.equals(LongRangeSet.indexRange())) { - switch (((SpecialField) descriptor)) { - case ARRAY_LENGTH: - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.array.length.is.always.non.negative"), factUse); - case STRING_LENGTH: - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.string.length.is.always.non.negative"), factUse); - case COLLECTION_SIZE: - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.collection.size.is.always.non.negative"), factUse); - default: - } - } + + @RequiredReadAction + private CauseItem fromCallContract(MemoryStateChange history, PsiMethodCallExpression call, PsiExpression target) { + PsiExpression[] args = call.getArgumentList().getExpressions(); + for (int i = 0; i < args.length; i++) { + if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(args[i], target)) { + return fromCallContract(history, call, ContractReturnValue.returnParameter(i)); + } + } + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(qualifier, target)) { + return fromCallContract(history, call, ContractReturnValue.returnThis()); + } + return null; } - PsiExpression expression = factUse.myTopOfStack == value ? factUse.getExpression() : null; - if (expression != null) { - PsiType type = expression.getType(); - if (expression instanceof PsiLiteralExpression) { - return null; // Literal range is quite evident - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; + + @RequiredReadAction + private CauseItem fromCallContract(MemoryStateChange history, PsiCallExpression call, ContractReturnValue contractReturnValue) { PsiMethod method = call.resolveMethod(); - if (method != null) { - LongRangeSet fromAnnotation = LongRangeSet.fromPsiElement(method); - if (fromAnnotation.equals(range)) { - return new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.range.is.specified.by.annotation", method.getName(), range), - call.getMethodExpression().getReferenceNameElement()); - } - } - } - if (expression instanceof PsiTypeCastExpression && type instanceof PsiPrimitiveType && TypeConversionUtil.isNumericType(type)) { - PsiExpression operand = ((PsiTypeCastExpression) expression).getOperand(); - MemoryStateChange operandPush = factUse.findExpressionPush(operand); - if (operandPush != null) { - DfaValue castedValue = operandPush.myTopOfStack; - FactDefinition operandInfo = operandPush.findFact(castedValue, FactExtractor.range()); - LongRangeSet operandRange = operandInfo.myFact; - LongRangeSet result = operandRange.castTo((PsiPrimitiveType) type); - if (range.equals(result)) { - CauseItem cause = - new CauseItem(new RangeDfaProblemType( - JavaAnalysisBundle.message("dfa.find.cause.result.of.primitive.cast.template", type.getCanonicalText()), range, null), - expression); - if (!operandRange.equals(LongRangeSet.fromType(operand.getType()))) { - cause.addChildren(findRangeCause(operandPush, castedValue, operandRange, - JavaAnalysisBundle.message("dfa.find.cause.numeric.cast.operand.template"))); - } - return cause; - } - } - } - if (range.equals(LongRangeSet.fromType(type))) { - return null; // Range is any value of given type: no need to explain (except narrowing cast) - } - if (PsiType.LONG.equals(type) || PsiType.INT.equals(type)) { - if (expression instanceof PsiBinaryExpression) { - boolean isLong = PsiType.LONG.equals(type); - PsiBinaryExpression binOp = (PsiBinaryExpression) expression; - PsiExpression left = PsiUtil.skipParenthesizedExprDown(binOp.getLOperand()); - PsiExpression right = PsiUtil.skipParenthesizedExprDown(binOp.getROperand()); - MemoryStateChange leftPush = factUse.findExpressionPush(left); - MemoryStateChange rightPush = factUse.findExpressionPush(right); - if (leftPush != null && rightPush != null) { - DfaValue leftVal = leftPush.myTopOfStack; - FactDefinition leftSet = leftPush.findFact(leftVal, FactExtractor.range()); - DfaValue rightVal = rightPush.myTopOfStack; - FactDefinition rightSet = rightPush.findFact(rightVal, FactExtractor.range()); - LongRangeSet fromType = Objects.requireNonNull(LongRangeSet.fromType(type)); - LongRangeSet leftRange = leftSet.myFact.intersect(fromType); - LongRangeSet rightRange = rightSet.myFact.intersect(fromType); - LongRangeBinOp op = LongRangeBinOp.fromToken(binOp.getOperationTokenType()); - if (op != null) { - LongRangeSet result = op.eval(leftRange, rightRange, isLong); - if (range.equals(result)) { - String sign = binOp.getOperationSign().getText(); - CauseItem cause = new CauseItem(new RangeDfaProblemType( - JavaAnalysisBundle.message("dfa.find.cause.result.of.numeric.operation.template", sign.equals("%") ? "%%" : sign), - range, ObjectUtil.tryCast(type, PsiPrimitiveType.class)), factUse); - CauseItem leftCause = null, rightCause = null; - if (!leftRange.equals(fromType)) { - leftCause = findRangeCause(leftPush, leftVal, leftRange, - JavaAnalysisBundle.message("dfa.find.cause.left.operand.range.template")); + if (method == null) { + return null; + } + List contracts = JavaMethodContractUtil.getMethodCallContracts(method, call); + if (contracts.isEmpty()) { + return null; + } + MethodContract contract = contracts.get(0); + if (call instanceof PsiMethodCallExpression methodCall) { + PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + String name = methodExpression.getReferenceName(); + if (contracts.size() == 1 && contract.isTrivial() && contractReturnValue.isSuperValueOf(contract.getReturnValue())) { + LocalizeValue message = + JavaAnalysisLocalize.dfaFindCauseContractTrivial(getContractKind(methodCall), name, contract.getReturnValue()); + return new CauseItem(message, methodExpression.getReferenceNameElement()); + } + List nonIntersecting = MethodContract.toNonIntersectingContracts(contracts); + if (nonIntersecting != null) { + Predicate condition = contractReturnValue instanceof ContractReturnValue.ParameterReturnValue + ? mc -> contractReturnValue.equals(mc.getReturnValue()) + : mc -> contractReturnValue.isSuperValueOf(mc.getReturnValue()); + MethodContract onlyContract = ContainerUtil.getOnlyItem(ContainerUtil.filter(nonIntersecting, condition)); + if (onlyContract != null) { + return fromSingleContract(history, methodCall, method, onlyContract); + } + } + for (MethodContract c : contracts) { + ThreeState applies = contractApplies(methodCall, c); + if (applies == ThreeState.NO) { + continue; } - if (!rightRange.equals(fromType)) { - rightCause = findRangeCause(rightPush, rightVal, rightRange, - JavaAnalysisBundle.message("dfa.find.cause.right.operand.range.template")); + if (applies == ThreeState.UNSURE) { + break; + } + if (applies == ThreeState.YES && contractReturnValue.isSuperValueOf(c.getReturnValue())) { + return fromSingleContract(history, methodCall, method, c); } - cause.addChildren(leftCause, rightCause); - return cause; - } } - } } - } - } - PsiPrimitiveType type = expression != null ? ObjectUtil.tryCast(expression.getType(), PsiPrimitiveType.class) : null; - CauseItem item = new CauseItem(new RangeDfaProblemType(template, range, type), factUse); - FactDefinition info = factUse.findFact(value, FactExtractor.range()); - MemoryStateChange factDef = range.equals(info.myFact) ? info.myChange : null; - if (factDef != null) { - if (factDef.myInstruction instanceof AssignInstruction && factDef.myTopOfStack == value) { - PsiExpression rExpression = ((AssignInstruction) factDef.myInstruction).getRExpression(); - if (rExpression != null) { - MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression); - if (rValuePush != null) { - CauseItem assignmentItem = createAssignmentCause((AssignInstruction) factDef.myInstruction, value); - assignmentItem.addChildren(findRangeCause(rValuePush, rValuePush.myTopOfStack, range, - JavaAnalysisBundle.message("dfa.find.cause.numeric.range.generic.template"))); - item.addChildren(assignmentItem); - return item; - } - } - } - PsiExpression defExpression = factDef.getExpression(); - if (defExpression != null) { - item.addChildren(new CauseItem(JavaAnalysisBundle.message("dfa.find.cause.range.is.known.from.place"), defExpression)); - } - } - return item; - } - - @Nullable - public static - @Nls - String getObviouslyNonNullExplanation(PsiExpression arg) { - if (arg == null || ExpressionUtils.isNullLiteral(arg)) { - return null; - } - if (arg instanceof PsiNewExpression) { - return JavaAnalysisBundle.message("dfa.find.cause.nonnull.expression.kind.newly.created.object"); + return null; } - if (arg instanceof PsiLiteralExpression) { - return JavaAnalysisBundle.message("dfa.find.cause.nonnull.expression.kind.literal"); - } - if (arg.getType() instanceof PsiPrimitiveType) { - return JavaAnalysisBundle.message("dfa.find.cause.nonnull.expression.kind.primitive.type", arg.getType().getCanonicalText()); + + private static LocalizeValue getContractKind(PsiCallExpression call) { + PsiMethod method = call.resolveMethod(); + if (method == null || JavaMethodContractUtil.hasExplicitContractAnnotation(method)) { + return JavaAnalysisLocalize.dfaFindCauseContractKindExplicit(); + } + List contracts = JavaMethodContractUtil.getMethodCallContracts(method, call); + if (contracts.isEmpty()) { + return JavaAnalysisLocalize.dfaFindCauseContractKindExplicit(); + } + if (contracts.stream().allMatch(c -> c instanceof StandardMethodContract)) { + return JavaAnalysisLocalize.dfaFindCauseContractKindInferred(); + } + return JavaAnalysisLocalize.dfaFindCauseContractKindHardCoded(); } - if (arg instanceof PsiPolyadicExpression && ((PsiPolyadicExpression) arg).getOperationTokenType() == JavaTokenType.PLUS) { - return JavaAnalysisBundle.message("dfa.find.cause.nonnull.expression.kind.concatenation"); + + private ThreeState contractApplies(PsiMethodCallExpression call, MethodContract contract) { + List conditions = contract.getConditions(); + for (ContractValue condition : conditions) { + DfaCondition cond = condition.fromCall(getFactory(), call); + if (cond == DfaCondition.getTrue()) { + return ThreeState.YES; + } + if (cond == DfaCondition.getFalse()) { + return ThreeState.NO; + } + } + return ThreeState.UNSURE; + } + + @RequiredReadAction + private CauseItem fromSingleContract( + MemoryStateChange history, + PsiMethodCallExpression call, + PsiMethod method, + MethodContract contract + ) { + List conditions = contract.getConditions(); + String conditionsText = StringUtil.join( + conditions, + c -> c.getPresentationText(method), + JavaAnalysisLocalize.dfaFindCauseConditionJoiner().get() + ); + LocalizeValue message; + if (contract.getReturnValue().isFail()) { + message = + JavaAnalysisLocalize.dfaFindCauseContractThrowsOnCondition(getContractKind(call), method.getName(), conditionsText); + } + else { + message = JavaAnalysisLocalize.dfaFindCauseContractReturnsOnCondition(getContractKind(call), + method.getName(), + contract.getReturnValue(), + conditionsText + ); + } + CauseItem causeItem = new CauseItem(message, call.getMethodExpression().getReferenceNameElement()); + for (ContractValue contractValue : conditions) { + if (!(contractValue instanceof ContractValue.Condition)) { + continue; + } + ContractValue.Condition condition = (ContractValue.Condition)contractValue; + ContractValue leftVal = condition.getLeft(); + ContractValue rightVal = condition.getRight(); + RelationType type = condition.getRelationType(); + DfaCallArguments arguments = DfaCallArguments.fromCall(getFactory(), call); + PsiExpression leftPlace = leftVal.findPlace(call); + MemoryStateChange leftPush = history.findSubExpressionPush(leftPlace); + if (leftPush == null && arguments != null) { + DfaValue left = leftVal.makeDfaValue(getFactory(), arguments); + leftPush = MemoryStateChange.create(history.getPrevious(), new PushInstruction(left, null), Collections.emptyMap(), left); + } + PsiExpression rightPlace = rightVal.findPlace(call); + MemoryStateChange rightPush = history.findSubExpressionPush(rightPlace); + if (rightPush == null && arguments != null) { + DfaValue right = rightVal.makeDfaValue(getFactory(), arguments); + rightPush = + MemoryStateChange.create(history.getPrevious(), new PushInstruction(right, null), Collections.emptyMap(), right); + } + if (leftPush != null && rightPush != null) { + causeItem.addChildren(findRelationCause(type, leftPush, rightPush)); + } + } + return causeItem; } - if (arg instanceof PsiThisExpression) { - return JavaAnalysisBundle.message("dfa.find.cause.nonnull.expression.kind.this.object"); + + @RequiredReadAction + private static CauseItem findRangeCause(MemoryStateChange factUse, DfaValue value, LongRangeSet range, LocalizeValue template) { + if (value instanceof DfaVariableValue variableValue) { + if (variableValue.getDescriptor() instanceof SpecialField specialField && range.equals(LongRangeSet.indexRange())) { + switch (specialField) { + case ARRAY_LENGTH: + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseArrayLengthIsAlwaysNonNegative(), factUse); + case STRING_LENGTH: + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseStringLengthIsAlwaysNonNegative(), factUse); + case COLLECTION_SIZE: + return new CauseItem(JavaAnalysisLocalize.dfaFindCauseCollectionSizeIsAlwaysNonNegative(), factUse); + default: + } + } + } + PsiExpression expression = factUse.myTopOfStack == value ? factUse.getExpression() : null; + if (expression != null) { + PsiType type = expression.getType(); + if (expression instanceof PsiLiteralExpression) { + return null; // Literal range is quite evident + } + if (expression instanceof PsiMethodCallExpression call) { + PsiMethod method = call.resolveMethod(); + if (method != null) { + LongRangeSet fromAnnotation = LongRangeSet.fromPsiElement(method); + if (fromAnnotation.equals(range)) { + return new CauseItem( + JavaAnalysisLocalize.dfaFindCauseRangeIsSpecifiedByAnnotation(method.getName(), range), + call.getMethodExpression().getReferenceNameElement() + ); + } + } + } + if (expression instanceof PsiTypeCastExpression typeCast + && type instanceof PsiPrimitiveType primitiveType + && TypeConversionUtil.isNumericType(type)) { + + PsiExpression operand = typeCast.getOperand(); + MemoryStateChange operandPush = factUse.findExpressionPush(operand); + if (operandPush != null) { + DfaValue castedValue = operandPush.myTopOfStack; + FactDefinition operandInfo = operandPush.findFact(castedValue, FactExtractor.range()); + LongRangeSet operandRange = operandInfo.myFact; + LongRangeSet result = operandRange.castTo(primitiveType); + if (range.equals(result)) { + CauseItem cause = new CauseItem( + new RangeDfaProblemType( + JavaAnalysisLocalize.dfaFindCauseResultOfPrimitiveCastTemplate(primitiveType.getCanonicalText()), + range, + null + ), + expression + ); + if (!operandRange.equals(LongRangeSet.fromType(operand.getType()))) { + cause.addChildren(findRangeCause( + operandPush, + castedValue, + operandRange, + JavaAnalysisLocalize.dfaFindCauseNumericCastOperandTemplate() + )); + } + return cause; + } + } + } + if (range.equals(LongRangeSet.fromType(type))) { + return null; // Range is any value of given type: no need to explain (except narrowing cast) + } + if ((PsiType.LONG.equals(type) || PsiType.INT.equals(type)) && expression instanceof PsiBinaryExpression binOp) { + boolean isLong = PsiType.LONG.equals(type); + PsiExpression left = PsiUtil.skipParenthesizedExprDown(binOp.getLOperand()); + PsiExpression right = PsiUtil.skipParenthesizedExprDown(binOp.getROperand()); + MemoryStateChange leftPush = factUse.findExpressionPush(left); + MemoryStateChange rightPush = factUse.findExpressionPush(right); + if (leftPush != null && rightPush != null) { + DfaValue leftVal = leftPush.myTopOfStack; + FactDefinition leftSet = leftPush.findFact(leftVal, FactExtractor.range()); + DfaValue rightVal = rightPush.myTopOfStack; + FactDefinition rightSet = rightPush.findFact(rightVal, FactExtractor.range()); + LongRangeSet fromType = Objects.requireNonNull(LongRangeSet.fromType(type)); + LongRangeSet leftRange = leftSet.myFact.intersect(fromType); + LongRangeSet rightRange = rightSet.myFact.intersect(fromType); + LongRangeBinOp op = LongRangeBinOp.fromToken(binOp.getOperationTokenType()); + if (op != null) { + LongRangeSet result = op.eval(leftRange, rightRange, isLong); + if (range.equals(result)) { + String sign = binOp.getOperationSign().getText(); + CauseItem cause = new CauseItem( + new RangeDfaProblemType( + JavaAnalysisLocalize.dfaFindCauseResultOfNumericOperationTemplate(sign.equals("%") ? "%%" : sign), + range, + ObjectUtil.tryCast(type, PsiPrimitiveType.class) + ), + factUse + ); + CauseItem leftCause = null, rightCause = null; + if (!leftRange.equals(fromType)) { + leftCause = findRangeCause( + leftPush, + leftVal, + leftRange, + JavaAnalysisLocalize.dfaFindCauseLeftOperandRangeTemplate() + ); + } + if (!rightRange.equals(fromType)) { + rightCause = findRangeCause( + rightPush, + rightVal, + rightRange, + JavaAnalysisLocalize.dfaFindCauseRightOperandRangeTemplate() + ); + } + cause.addChildren(leftCause, rightCause); + return cause; + } + } + } + } + } + PsiPrimitiveType type = expression != null ? ObjectUtil.tryCast(expression.getType(), PsiPrimitiveType.class) : null; + CauseItem item = new CauseItem(new RangeDfaProblemType(template, range, type), factUse); + FactDefinition info = factUse.findFact(value, FactExtractor.range()); + MemoryStateChange factDef = range.equals(info.myFact) ? info.myChange : null; + if (factDef != null) { + if (factDef.myInstruction instanceof AssignInstruction assignInsn && factDef.myTopOfStack == value) { + PsiExpression rExpression = assignInsn.getRExpression(); + if (rExpression != null) { + MemoryStateChange rValuePush = factDef.findSubExpressionPush(rExpression); + if (rValuePush != null) { + CauseItem assignmentItem = createAssignmentCause((AssignInstruction)factDef.myInstruction, value); + assignmentItem.addChildren(findRangeCause( + rValuePush, + rValuePush.myTopOfStack, + range, + JavaAnalysisLocalize.dfaFindCauseNumericRangeGenericTemplate() + )); + item.addChildren(assignmentItem); + return item; + } + } + } + PsiExpression defExpression = factDef.getExpression(); + if (defExpression != null) { + item.addChildren(new CauseItem(JavaAnalysisLocalize.dfaFindCauseRangeIsKnownFromPlace(), defExpression)); + } + } + return item; } - return null; - } - - private static MemoryStateChange findRelationAddedChange(MemoryStateChange history, DfaVariableValue var, Relation relation) { - if (relation.myRelationType == RelationType.NE && relation.myCounterpart.getDfType() instanceof DfConstantType) { - return history.findRelation(var, rel -> rel.equals(relation) || - rel.myRelationType == RelationType.EQ && - rel.myCounterpart.getDfType() instanceof DfConstantType, - true); + + public static LocalizeValue getObviouslyNonNullExplanation(PsiExpression arg) { + if (arg == null || ExpressionUtils.isNullLiteral(arg)) { + return LocalizeValue.empty(); + } + if (arg instanceof PsiNewExpression) { + return JavaAnalysisLocalize.dfaFindCauseNonnullExpressionKindNewlyCreatedObject(); + } + if (arg instanceof PsiLiteralExpression) { + return JavaAnalysisLocalize.dfaFindCauseNonnullExpressionKindLiteral(); + } + if (arg.getType() instanceof PsiPrimitiveType) { + return JavaAnalysisLocalize.dfaFindCauseNonnullExpressionKindPrimitiveType(arg.getType().getCanonicalText()); + } + if (arg instanceof PsiPolyadicExpression polyadic && polyadic.getOperationTokenType() == JavaTokenType.PLUS) { + return JavaAnalysisLocalize.dfaFindCauseNonnullExpressionKindConcatenation(); + } + if (arg instanceof PsiThisExpression) { + return JavaAnalysisLocalize.dfaFindCauseNonnullExpressionKindThisObject(); + } + return LocalizeValue.empty(); } - MemoryStateChange exact = history.findRelation(var, rel -> rel.myCounterpart == relation.myCounterpart && - relation.myRelationType.equals(rel.myRelationType), true); - if (exact != null) { - return exact; + + private static MemoryStateChange findRelationAddedChange(MemoryStateChange history, DfaVariableValue var, Relation relation) { + if (relation.myRelationType == RelationType.NE && relation.myCounterpart.getDfType() instanceof DfConstantType) { + return history.findRelation( + var, + rel -> rel.equals(relation) + || rel.myRelationType == RelationType.EQ && rel.myCounterpart.getDfType() instanceof DfConstantType, + true + ); + } + MemoryStateChange exact = history.findRelation( + var, + rel -> rel.myCounterpart == relation.myCounterpart && relation.myRelationType.equals(rel.myRelationType), + true + ); + if (exact != null) { + return exact; + } + return history.findRelation( + var, + rel -> rel.myCounterpart == relation.myCounterpart && relation.myRelationType.isSubRelation(rel.myRelationType), + true + ); } - return history.findRelation(var, rel -> rel.myCounterpart == relation.myCounterpart && - relation.myRelationType.isSubRelation(rel.myRelationType), true); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TransferTarget.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TransferTarget.java index 1123a21328..2e1272b3d5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TransferTarget.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TransferTarget.java @@ -16,8 +16,6 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.List; @@ -28,7 +26,6 @@ public interface TransferTarget { /** * @return list of possible instruction offsets for given target */ - @Nonnull default Collection getPossibleTargets() { return List.of(); } @@ -36,7 +33,6 @@ default Collection getPossibleTargets() { /** * @return next instruction states assuming no traps */ - @Nonnegative default List dispatch(DfaMemoryState state, DataFlowRunner runner) { return List.of(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Trap.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Trap.java index ce1a9ae437..aed182b0ea 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Trap.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/Trap.java @@ -22,7 +22,6 @@ import com.intellij.java.language.psi.PsiTryStatement; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; import java.util.*; import java.util.stream.Collectors; @@ -157,11 +156,10 @@ List dispatch(ControlTransferHandler handler) { private final PsiElement anchor; - public Trap(@Nonnull PsiElement anchor) { + public Trap(PsiElement anchor) { this.anchor = anchor; } - @Nonnull public PsiElement getAnchor() { return anchor; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraint.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraint.java index 29040d2055..27f57521f6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraint.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraint.java @@ -1,22 +1,20 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfReferenceType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; import com.intellij.java.language.psi.PsiIntersectionType; import com.intellij.java.language.psi.PsiType; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.project.Project; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.EntryStream; import one.util.streamex.MoreCollectors; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; /** @@ -31,609 +29,623 @@ * Null or primitive types are not handled here. */ public interface TypeConstraint { - /** - * @param other other constraint to join with - * @return joined constraint. If some type satisfies either this or other constraint, it also satisfies the resulting constraint. - */ - @Nonnull - TypeConstraint join(@Nonnull TypeConstraint other); - - /** - * @param other other constraint to meet with - * @return intersection constraint. If some type satisfies the resulting constraint, it also satisfies both this and other constraints. - */ - @Nonnull - TypeConstraint meet(@Nonnull TypeConstraint other); - - /** - * @param other other constraint to check - * @return true if every type satisfied by other constraint is also satisfied by this constraint. - */ - boolean isSuperConstraintOf(@Nonnull TypeConstraint other); - - /** - * @return negated constraint (a constraint that satisfied only by types not satisfied by this constraint). - * Null if such a constraint cannot be created. - */ - default - @Nullable - TypeConstraint tryNegate() { - return null; - } - - /** - * @param project current project - * @return the narrowest PsiType that contains all the values satisfied by this constraint. - */ - @Nullable - default PsiType getPsiType(Project project) { - return null; - } - - /** - * @param type declared PsiType of the value - * @return presentation text that tells about additional constraints; can be empty if no additional constraints are known - */ - @Nonnull - default String getPresentationText(@Nullable PsiType type) { - return toShortString(); - } - - /** - * @return true if this constraint represents an exact type - */ - default boolean isExact() { - return false; - } - - /** - * @return true if the types represented by this constraint are known to be compared by .equals() within DFA algorithm - */ - default boolean isComparedByEquals() { - return false; - } - - /** - * @return true if given type is resolved - */ - default boolean isResolved() { - return true; - } - - /** - * @param otherType other type - * @param expectedAssignable whether other type is expected to be assignable from this, or not - * @param elementTitle - * @return textual explanation about why expected assignability cannot be satisfied; null if it can be satisfied, or - * explanation cannot be found. - */ - default - @Nullable - @Nls - String getAssignabilityExplanation(@Nonnull TypeConstraint otherType, - boolean expectedAssignable, - @Nls String elementTitle) { - return null; - } - - /** - * @return stream of "instanceof" constraints of this type - */ - default StreamEx instanceOfTypes() { - return StreamEx.empty(); - } - - /** - * @return stream of "not-instanceof" constraints of this type - */ - default StreamEx notInstanceOfTypes() { - return StreamEx.empty(); - } - - /** - * @return a {@link DfType} that represents any object that satisfies this constraint, or null (nullability is unknown) - */ - default DfType asDfType() { - return this == TypeConstraints.BOTTOM ? DfTypes.BOTTOM : - DfTypes.customObject(this, DfaNullability.UNKNOWN, Mutability.UNKNOWN, null, DfTypes.BOTTOM); - } - - /** - * @return a short string representation - */ - default String toShortString() { - return toString(); - } - - /** - * @return an array component type for an array type; BOTTOM if this type is not always an array type or primitive array - */ - default - @Nonnull - TypeConstraint getArrayComponent() { - return TypeConstraints.BOTTOM; - } - - /** - * @param type {@link DfType} to extract {@link TypeConstraint} from - * @return an extracted type constraint - */ - static - @Nonnull - TypeConstraint fromDfType(DfType type) { - return type instanceof DfReferenceType ? ((DfReferenceType) type).getConstraint() : - type == DfTypes.BOTTOM ? TypeConstraints.BOTTOM : - TypeConstraints.TOP; - } - - /** - * Represents an exact type. It may also represent types that cannot be instantiated (e.g. interface types), so no object - * could satisfy them, but they are still useful as building blocks for {@link Constrained}. - */ - interface Exact extends TypeConstraint { - @Override + /** + * @param other other constraint to join with + * @return joined constraint. If some type satisfies either this or other constraint, it also satisfies the resulting constraint. + */ + TypeConstraint join(TypeConstraint other); + + /** + * @param other other constraint to meet with + * @return intersection constraint. If some type satisfies the resulting constraint, it also satisfies both this and other constraints. + */ + TypeConstraint meet(TypeConstraint other); + + /** + * @param other other constraint to check + * @return true if every type satisfied by other constraint is also satisfied by this constraint. + */ + boolean isSuperConstraintOf(TypeConstraint other); + + /** + * @return negated constraint (a constraint that satisfied only by types not satisfied by this constraint). + * Null if such a constraint cannot be created. + */ default - @Nonnull - TypeConstraint join(@Nonnull TypeConstraint other) { - if (other == TypeConstraints.BOTTOM || this.equals(other)) { - return this; - } - if (other == TypeConstraints.TOP) { - return other; - } - return new Constrained(Collections.singleton(this), Collections.emptySet()).join(other); + @Nullable + TypeConstraint tryNegate() { + return null; } - @Override - default - @Nonnull - TypeConstraint meet(@Nonnull TypeConstraint other) { - if (this.equals(other) || other.isSuperConstraintOf(this)) { - return this; - } - return TypeConstraints.BOTTOM; + /** + * @param project current project + * @return the narrowest PsiType that contains all the values satisfied by this constraint. + */ + @Nullable + default PsiType getPsiType(Project project) { + return null; } /** - * @return true if the type represented by this constraint cannot have subtypes + * @param type declared PsiType of the value + * @return presentation text that tells about additional constraints; can be empty if no additional constraints are known */ - boolean isFinal(); + default String getPresentationText(@Nullable PsiType type) { + return toShortString(); + } - @Override + /** + * @return true if this constraint represents an exact type + */ default boolean isExact() { - return true; + return false; } /** - * @return true if instances of this type can exist (i.e. the type is not abstract). + * @return true if the types represented by this constraint are known to be compared by .equals() within DFA algorithm */ - default boolean canBeInstantiated() { - return true; + default boolean isComparedByEquals() { + return false; } /** - * @return stream of supertypes + * @return true if given type is resolved */ - StreamEx superTypes(); + default boolean isResolved() { + return true; + } /** - * @param other type to test assignability - * @return true if this type is assignable from the other type + * @param otherType other type + * @param expectedAssignable whether other type is expected to be assignable from this, or not + * @param elementTitle + * @return textual explanation about why expected assignability cannot be satisfied; null if it can be satisfied, or + * explanation cannot be found. */ - boolean isAssignableFrom(@Nonnull Exact other); + default + @Nullable + String getAssignabilityExplanation( + TypeConstraint otherType, + boolean expectedAssignable, + String elementTitle + ) { + return null; + } /** - * @param other type to test convertibility - * @return true if this type is convertible from the other type + * @return stream of "instanceof" constraints of this type */ - boolean isConvertibleFrom(@Nonnull Exact other); - - @Override default StreamEx instanceOfTypes() { - return StreamEx.of(this); + return StreamEx.empty(); } - @Override - default String getAssignabilityExplanation(@Nonnull TypeConstraint otherType, - boolean expectedAssignable, - @Nls String elementTitle) { - Exact exact = otherType.instanceOfTypes().collect(MoreCollectors.onlyOne()).orElse(null); - if (exact == null) { - return null; - } - boolean actual = exact.isAssignableFrom(this); - if (actual != expectedAssignable) { - return null; - } - if (expectedAssignable) { - if (equals(exact)) { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.exact", elementTitle, toShortString()); - } - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.exact.subtype", elementTitle, toShortString(), exact.toShortString()); - } else { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.exact.not.subtype", elementTitle, toShortString(), exact.toShortString()); - } + /** + * @return stream of "not-instanceof" constraints of this type + */ + default StreamEx notInstanceOfTypes() { + return StreamEx.empty(); } - @Override - default boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - return other == TypeConstraints.BOTTOM || this.equals(other); + /** + * @return a {@link DfType} that represents any object that satisfies this constraint, or null (nullability is unknown) + */ + default DfType asDfType() { + return this == TypeConstraints.BOTTOM ? DfTypes.BOTTOM : + DfTypes.customObject(this, DfaNullability.UNKNOWN, Mutability.UNKNOWN, null, DfTypes.BOTTOM); } - @Override - default TypeConstraint tryNegate() { - return isFinal() ? notInstanceOf() : null; + /** + * @return a short string representation + */ + default String toShortString() { + return toString(); } /** - * @return a constraint that represents objects not only of this type but also of any subtypes. May return self if the type is final. + * @return an array component type for an array type; BOTTOM if this type is not always an array type or primitive array */ default - @Nonnull - TypeConstraint instanceOf() { - if (isFinal()) { - return canBeInstantiated() ? this : TypeConstraints.BOTTOM; - } - return new Constrained(Collections.singleton(this), Collections.emptySet()); + TypeConstraint getArrayComponent() { + return TypeConstraints.BOTTOM; } /** - * @return a constraint that represents objects that are not instanceof this type + * @param type {@link DfType} to extract {@link TypeConstraint} from + * @return an extracted type constraint */ - default - @Nonnull - TypeConstraint notInstanceOf() { - return new Constrained(Collections.emptySet(), Collections.singleton(this)); + static + TypeConstraint fromDfType(DfType type) { + return type instanceof DfReferenceType + ? ((DfReferenceType)type).getConstraint() + : type == DfTypes.BOTTOM + ? TypeConstraints.BOTTOM + : TypeConstraints.TOP; } - @Override - default String toShortString() { - return StringUtil.getShortName(toString()); - } + /** + * Represents an exact type. It may also represent types that cannot be instantiated (e.g. interface types), so no object + * could satisfy them, but they are still useful as building blocks for {@link Constrained}. + */ + interface Exact extends TypeConstraint { + @Override + default + TypeConstraint join(TypeConstraint other) { + if (other == TypeConstraints.BOTTOM || this.equals(other)) { + return this; + } + if (other == TypeConstraints.TOP) { + return other; + } + return new Constrained(Collections.singleton(this), Collections.emptySet()).join(other); + } - @Override - default - @Nonnull - String getPresentationText(@Nullable PsiType type) { - return type != null && TypeConstraints.exact(type).equals(this) ? "" : "exactly " + toShortString(); - } - } - - /** - * A non-exact, constrained type - */ - final class Constrained implements TypeConstraint { - private final - @Nonnull - Set myInstanceOf; - private final - @Nonnull - Set myNotInstanceOf; - - Constrained(@Nonnull Set instanceOf, @Nonnull Set notInstanceOf) { - assert !instanceOf.isEmpty() || !notInstanceOf.isEmpty(); - myInstanceOf = instanceOf; - myNotInstanceOf = notInstanceOf; - } + @Override + default + TypeConstraint meet(TypeConstraint other) { + if (this.equals(other) || other.isSuperConstraintOf(this)) { + return this; + } + return TypeConstraints.BOTTOM; + } - @Override - public boolean isResolved() { - return myInstanceOf.stream().allMatch(Exact::isResolved); - } + /** + * @return true if the type represented by this constraint cannot have subtypes + */ + boolean isFinal(); - @Override - public - @Nullable - PsiType getPsiType(Project project) { - PsiType[] conjuncts = StreamEx.of(myInstanceOf).map(exact -> exact.getPsiType(project)).nonNull().toArray(PsiType.EMPTY_ARRAY); - return conjuncts.length == 0 ? null : PsiIntersectionType.createIntersection(true, conjuncts); - } + @Override + default boolean isExact() { + return true; + } - @Override - public - @Nullable - TypeConstraint tryNegate() { - if (myInstanceOf.size() == 1 && myNotInstanceOf.isEmpty()) { - return myInstanceOf.iterator().next().notInstanceOf(); - } - if (myNotInstanceOf.size() == 1 && myInstanceOf.isEmpty()) { - return myNotInstanceOf.iterator().next().instanceOf(); - } - return null; - } + /** + * @return true if instances of this type can exist (i.e. the type is not abstract). + */ + default boolean canBeInstantiated() { + return true; + } - @Override - public - @Nonnull - TypeConstraint join(@Nonnull TypeConstraint other) { - if (isSuperConstraintOf(other)) { - return this; - } - if (other.isSuperConstraintOf(this)) { - return other; - } - if (other instanceof Constrained) { - return joinWithConstrained((Constrained) other); - } - if (other instanceof Exact) { - return joinWithConstrained(new Constrained(Collections.singleton((Exact) other), Collections.emptySet())); - } - return TypeConstraints.TOP; - } + /** + * @return stream of supertypes + */ + StreamEx superTypes(); + + /** + * @param other type to test assignability + * @return true if this type is assignable from the other type + */ + boolean isAssignableFrom(Exact other); + + /** + * @param other type to test convertibility + * @return true if this type is convertible from the other type + */ + boolean isConvertibleFrom(Exact other); + + @Override + default StreamEx instanceOfTypes() { + return StreamEx.of(this); + } - private - @Nonnull - TypeConstraint joinWithConstrained(@Nonnull Constrained other) { - Set notTypes = new HashSet<>(this.myNotInstanceOf); - notTypes.retainAll(other.myNotInstanceOf); - Set instanceOfTypes; - if (this.myInstanceOf.containsAll(other.myInstanceOf)) { - instanceOfTypes = other.myInstanceOf; - } else if (other.myInstanceOf.containsAll(this.myInstanceOf)) { - instanceOfTypes = this.myInstanceOf; - } else { - instanceOfTypes = withSuper(this.myInstanceOf); - instanceOfTypes.retainAll(withSuper(other.myInstanceOf)); - } - TypeConstraint constraint = TypeConstraints.TOP; - for (Exact type : instanceOfTypes) { - constraint = constraint.meet(type.instanceOf()); - } - for (Exact type : notTypes) { - constraint = constraint.meet(type.notInstanceOf()); - } - return constraint; - } + @Override + default String getAssignabilityExplanation( + TypeConstraint otherType, + boolean expectedAssignable, + String elementTitle + ) { + Exact exact = otherType.instanceOfTypes().collect(MoreCollectors.onlyOne()).orElse(null); + if (exact == null) { + return null; + } + boolean actual = exact.isAssignableFrom(this); + if (actual != expectedAssignable) { + return null; + } + if (expectedAssignable) { + return equals(exact) + ? JavaAnalysisLocalize.typeConstraintAssignabilityExplanationExact(elementTitle, toShortString()).get() + : JavaAnalysisLocalize.typeConstraintAssignabilityExplanationExactSubtype( + elementTitle, + toShortString(), + exact.toShortString() + ).get(); + } + else { + return JavaAnalysisLocalize.typeConstraintAssignabilityExplanationExactNotSubtype( + elementTitle, + toShortString(), + exact.toShortString() + ).get(); + } + } - private static - @Nonnull - Set withSuper(@Nonnull Set instanceofValues) { - return StreamEx.of(instanceofValues).flatMap(Exact::superTypes).append(instanceofValues).toSet(); - } + @Override + default boolean isSuperConstraintOf(TypeConstraint other) { + return other == TypeConstraints.BOTTOM || this.equals(other); + } - private - @Nullable - Constrained withInstanceofValue(@Nonnull Exact type) { - if (myInstanceOf.contains(type)) { - return this; - } - - for (Exact notInst : myNotInstanceOf) { - if (notInst.isAssignableFrom(type)) { - return null; + @Override + default TypeConstraint tryNegate() { + return isFinal() ? notInstanceOf() : null; } - } - List moreGeneric = new ArrayList<>(); - for (Exact alreadyInstanceof : myInstanceOf) { - if (type.isAssignableFrom(alreadyInstanceof)) { - return this; + /** + * @return a constraint that represents objects not only of this type but also of any subtypes. May return self if the type is final. + */ + default + TypeConstraint instanceOf() { + if (isFinal()) { + return canBeInstantiated() ? this : TypeConstraints.BOTTOM; + } + return new Constrained(Collections.singleton(this), Collections.emptySet()); } - if (!type.isConvertibleFrom(alreadyInstanceof)) { - return null; + + /** + * @return a constraint that represents objects that are not instanceof this type + */ + default + TypeConstraint notInstanceOf() { + return new Constrained(Collections.emptySet(), Collections.singleton(this)); } - if (alreadyInstanceof.isAssignableFrom(type)) { - moreGeneric.add(alreadyInstanceof); + + @Override + default String toShortString() { + return StringUtil.getShortName(toString()); } - } - Set newInstanceof = new HashSet<>(myInstanceOf); - newInstanceof.removeAll(moreGeneric); - newInstanceof.add(type); - return new Constrained(newInstanceof, myNotInstanceOf); + @Override + default + String getPresentationText(@Nullable PsiType type) { + return type != null && TypeConstraints.exact(type).equals(this) ? "" : "exactly " + toShortString(); + } } - @Nullable - private Constrained withNotInstanceofValue(Exact type) { - if (myNotInstanceOf.contains(type)) { - return this; - } - - for (Exact dfaTypeValue : myInstanceOf) { - if (type.isAssignableFrom(dfaTypeValue)) { - return null; + /** + * A non-exact, constrained type + */ + final class Constrained implements TypeConstraint { + private final + Set myInstanceOf; + private final + Set myNotInstanceOf; + + Constrained(Set instanceOf, Set notInstanceOf) { + assert !instanceOf.isEmpty() || !notInstanceOf.isEmpty(); + myInstanceOf = instanceOf; + myNotInstanceOf = notInstanceOf; } - } - List moreSpecific = new ArrayList<>(); - for (Exact alreadyNotInstanceof : myNotInstanceOf) { - if (alreadyNotInstanceof.isAssignableFrom(type)) { - return this; + @Override + public boolean isResolved() { + return myInstanceOf.stream().allMatch(Exact::isResolved); } - if (type.isAssignableFrom(alreadyNotInstanceof)) { - moreSpecific.add(alreadyNotInstanceof); - } - } - Set newNotInstanceof = new HashSet<>(myNotInstanceOf); - newNotInstanceof.removeAll(moreSpecific); - newNotInstanceof.add(type); - return new Constrained(myInstanceOf, newNotInstanceof); - } + @Override + public + @Nullable + PsiType getPsiType(Project project) { + PsiType[] conjuncts = StreamEx.of(myInstanceOf).map(exact -> exact.getPsiType(project)).nonNull().toArray(PsiType.EMPTY_ARRAY); + return conjuncts.length == 0 ? null : PsiIntersectionType.createIntersection(true, conjuncts); + } - @Override - public - @Nonnull - TypeConstraint meet(@Nonnull TypeConstraint other) { - if (this.isSuperConstraintOf(other)) { - return other; - } - if (other.isSuperConstraintOf(this)) { - return this; - } - if (!(other instanceof Constrained)) { - return TypeConstraints.BOTTOM; - } - Constrained right = (Constrained) other; - - Constrained result = this; - for (Exact type : right.myInstanceOf) { - result = result.withInstanceofValue(type); - if (result == null) { - return TypeConstraints.BOTTOM; + @Override + public + @Nullable + TypeConstraint tryNegate() { + if (myInstanceOf.size() == 1 && myNotInstanceOf.isEmpty()) { + return myInstanceOf.iterator().next().notInstanceOf(); + } + if (myNotInstanceOf.size() == 1 && myInstanceOf.isEmpty()) { + return myNotInstanceOf.iterator().next().instanceOf(); + } + return null; } - } - for (Exact type : right.myNotInstanceOf) { - result = result.withNotInstanceofValue(type); - if (result == null) { - return TypeConstraints.BOTTOM; + + @Override + public + TypeConstraint join(TypeConstraint other) { + if (isSuperConstraintOf(other)) { + return this; + } + if (other.isSuperConstraintOf(this)) { + return other; + } + if (other instanceof Constrained) { + return joinWithConstrained((Constrained)other); + } + if (other instanceof Exact) { + return joinWithConstrained(new Constrained(Collections.singleton((Exact)other), Collections.emptySet())); + } + return TypeConstraints.TOP; } - } - return result; - } - @Override - public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - if (other == TypeConstraints.BOTTOM) { - return true; - } - if (other instanceof Constrained) { - Constrained that = (Constrained) other; - if (!that.myNotInstanceOf.containsAll(myNotInstanceOf)) { - if (that.myInstanceOf.isEmpty()) { - return false; - } - for (Exact thisNotType : this.myNotInstanceOf) { - if (!that.myNotInstanceOf.contains(thisNotType)) { - for (Exact thatType : that.myInstanceOf) { - if (thisNotType.isConvertibleFrom(thatType)) { - return false; - } - } + private + TypeConstraint joinWithConstrained(Constrained other) { + Set notTypes = new HashSet<>(this.myNotInstanceOf); + notTypes.retainAll(other.myNotInstanceOf); + Set instanceOfTypes; + if (this.myInstanceOf.containsAll(other.myInstanceOf)) { + instanceOfTypes = other.myInstanceOf; + } + else if (other.myInstanceOf.containsAll(this.myInstanceOf)) { + instanceOfTypes = this.myInstanceOf; } - } + else { + instanceOfTypes = withSuper(this.myInstanceOf); + instanceOfTypes.retainAll(withSuper(other.myInstanceOf)); + } + TypeConstraint constraint = TypeConstraints.TOP; + for (Exact type : instanceOfTypes) { + constraint = constraint.meet(type.instanceOf()); + } + for (Exact type : notTypes) { + constraint = constraint.meet(type.notInstanceOf()); + } + return constraint; } - if (that.myInstanceOf.containsAll(myInstanceOf)) { - return true; + + private static + Set withSuper(Set instanceofValues) { + return StreamEx.of(instanceofValues).flatMap(Exact::superTypes).append(instanceofValues).toSet(); } - if (that.myInstanceOf.isEmpty()) { - return myInstanceOf.isEmpty(); + + private + @Nullable + Constrained withInstanceofValue(Exact type) { + if (myInstanceOf.contains(type)) { + return this; + } + + for (Exact notInst : myNotInstanceOf) { + if (notInst.isAssignableFrom(type)) { + return null; + } + } + + List moreGeneric = new ArrayList<>(); + for (Exact alreadyInstanceof : myInstanceOf) { + if (type.isAssignableFrom(alreadyInstanceof)) { + return this; + } + if (!type.isConvertibleFrom(alreadyInstanceof)) { + return null; + } + if (alreadyInstanceof.isAssignableFrom(type)) { + moreGeneric.add(alreadyInstanceof); + } + } + + Set newInstanceof = new HashSet<>(myInstanceOf); + newInstanceof.removeAll(moreGeneric); + newInstanceof.add(type); + return new Constrained(newInstanceof, myNotInstanceOf); } - for (Exact thatType : that.myInstanceOf) { - for (Exact thisType : this.myInstanceOf) { - if (!thisType.isAssignableFrom(thatType)) { - return false; + + @Nullable + private Constrained withNotInstanceofValue(Exact type) { + if (myNotInstanceOf.contains(type)) { + return this; + } + + for (Exact dfaTypeValue : myInstanceOf) { + if (type.isAssignableFrom(dfaTypeValue)) { + return null; + } + } + + List moreSpecific = new ArrayList<>(); + for (Exact alreadyNotInstanceof : myNotInstanceOf) { + if (alreadyNotInstanceof.isAssignableFrom(type)) { + return this; + } + if (type.isAssignableFrom(alreadyNotInstanceof)) { + moreSpecific.add(alreadyNotInstanceof); + } } - } + + Set newNotInstanceof = new HashSet<>(myNotInstanceOf); + newNotInstanceof.removeAll(moreSpecific); + newNotInstanceof.add(type); + return new Constrained(myInstanceOf, newNotInstanceof); } - return true; - } else if (other instanceof Exact) { - Exact otherType = (Exact) other; - for (Exact thisInstance : this.myInstanceOf) { - if (!thisInstance.isAssignableFrom(otherType)) { - return false; - } + + @Override + public + TypeConstraint meet(TypeConstraint other) { + if (this.isSuperConstraintOf(other)) { + return other; + } + if (other.isSuperConstraintOf(this)) { + return this; + } + if (!(other instanceof Constrained)) { + return TypeConstraints.BOTTOM; + } + Constrained right = (Constrained)other; + + Constrained result = this; + for (Exact type : right.myInstanceOf) { + result = result.withInstanceofValue(type); + if (result == null) { + return TypeConstraints.BOTTOM; + } + } + for (Exact type : right.myNotInstanceOf) { + result = result.withNotInstanceofValue(type); + if (result == null) { + return TypeConstraints.BOTTOM; + } + } + return result; } - for (Exact thisNotInstance : this.myNotInstanceOf) { - if (thisNotInstance.isAssignableFrom(otherType)) { + + @Override + public boolean isSuperConstraintOf(TypeConstraint other) { + if (other == TypeConstraints.BOTTOM) { + return true; + } + if (other instanceof Constrained) { + Constrained that = (Constrained)other; + if (!that.myNotInstanceOf.containsAll(myNotInstanceOf)) { + if (that.myInstanceOf.isEmpty()) { + return false; + } + for (Exact thisNotType : this.myNotInstanceOf) { + if (!that.myNotInstanceOf.contains(thisNotType)) { + for (Exact thatType : that.myInstanceOf) { + if (thisNotType.isConvertibleFrom(thatType)) { + return false; + } + } + } + } + } + if (that.myInstanceOf.containsAll(myInstanceOf)) { + return true; + } + if (that.myInstanceOf.isEmpty()) { + return myInstanceOf.isEmpty(); + } + for (Exact thatType : that.myInstanceOf) { + for (Exact thisType : this.myInstanceOf) { + if (!thisType.isAssignableFrom(thatType)) { + return false; + } + } + } + return true; + } + else if (other instanceof Exact) { + Exact otherType = (Exact)other; + for (Exact thisInstance : this.myInstanceOf) { + if (!thisInstance.isAssignableFrom(otherType)) { + return false; + } + } + for (Exact thisNotInstance : this.myNotInstanceOf) { + if (thisNotInstance.isAssignableFrom(otherType)) { + return false; + } + } + return true; + } return false; - } } - return true; - } - return false; - } - @Override - public String getAssignabilityExplanation(@Nonnull TypeConstraint otherType, - boolean expectedAssignable, - @Nls String elementTitle) { - Exact exact = otherType.instanceOfTypes().collect(MoreCollectors.onlyOne()).orElse(null); - if (exact == null) { - return null; - } - if (expectedAssignable) { - for (Exact inst : myInstanceOf) { - if (exact.isAssignableFrom(inst)) { - if (exact == inst) { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.exact", elementTitle, inst.toShortString()); - } else { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.subtype.of.subtype", - elementTitle, inst.toShortString(), exact.toShortString()); - } - } - } - } else { - for (Exact notInst : myNotInstanceOf) { - if (notInst.isAssignableFrom(exact)) { - if (exact == notInst) { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.not.instance.of", elementTitle, notInst.toShortString()); - } else { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.not.instance.of.supertype", elementTitle, notInst.toShortString(), exact.toShortString()); - } - } - } - for (Exact inst : myInstanceOf) { - if (!exact.isConvertibleFrom(inst)) { - return JavaAnalysisBundle.message("type.constraint.assignability.explanation.definitely.inconvertible", elementTitle, inst.toShortString(), exact.toShortString()); - } + @Override + public String getAssignabilityExplanation( + TypeConstraint otherType, + boolean expectedAssignable, + String elementTitle + ) { + Exact exact = otherType.instanceOfTypes().collect(MoreCollectors.onlyOne()).orElse(null); + if (exact == null) { + return null; + } + if (expectedAssignable) { + for (Exact inst : myInstanceOf) { + if (exact.isAssignableFrom(inst)) { + return exact == inst + ? JavaAnalysisLocalize.typeConstraintAssignabilityExplanationExact(elementTitle, inst.toShortString()).get() + : JavaAnalysisLocalize.typeConstraintAssignabilityExplanationSubtypeOfSubtype( + elementTitle, + inst.toShortString(), + exact.toShortString() + ).get(); + } + } + } + else { + for (Exact notInst : myNotInstanceOf) { + if (notInst.isAssignableFrom(exact)) { + return exact == notInst + ? JavaAnalysisLocalize.typeConstraintAssignabilityExplanationNotInstanceOf( + elementTitle, + notInst.toShortString() + ).get() + : JavaAnalysisLocalize.typeConstraintAssignabilityExplanationNotInstanceOfSupertype( + elementTitle, + notInst.toShortString(), + exact.toShortString() + ).get(); + } + } + for (Exact inst : myInstanceOf) { + if (!exact.isConvertibleFrom(inst)) { + return JavaAnalysisLocalize.typeConstraintAssignabilityExplanationDefinitelyInconvertible( + elementTitle, + inst.toShortString(), + exact.toShortString() + ).get(); + } + } + } + return null; } - } - return null; - } - @Override - public StreamEx instanceOfTypes() { - return StreamEx.of(myInstanceOf); - } + @Override + public StreamEx instanceOfTypes() { + return StreamEx.of(myInstanceOf); + } - @Override - public StreamEx notInstanceOfTypes() { - return StreamEx.of(myNotInstanceOf); - } + @Override + public StreamEx notInstanceOfTypes() { + return StreamEx.of(myNotInstanceOf); + } - @Override - public - @Nonnull - TypeConstraint getArrayComponent() { - return instanceOfTypes().map(Exact::getArrayComponent) - .map(type -> type instanceof Exact ? ((Exact) type).instanceOf() : TypeConstraints.BOTTOM) - .reduce(TypeConstraint::meet).orElse(TypeConstraints.BOTTOM); - } + @Override + public + TypeConstraint getArrayComponent() { + return instanceOfTypes().map(Exact::getArrayComponent) + .map(type -> type instanceof Exact exact ? exact.instanceOf() : TypeConstraints.BOTTOM) + .reduce(TypeConstraint::meet).orElse(TypeConstraints.BOTTOM); + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Constrained that = (Constrained) o; - return Objects.equals(myInstanceOf, that.myInstanceOf) && - Objects.equals(myNotInstanceOf, that.myNotInstanceOf); - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Constrained that = (Constrained)o; + return Objects.equals(myInstanceOf, that.myInstanceOf) + && Objects.equals(myNotInstanceOf, that.myNotInstanceOf); + } - @Override - public int hashCode() { - return Objects.hash(myInstanceOf, myNotInstanceOf); - } + @Override + public int hashCode() { + return Objects.hash(myInstanceOf, myNotInstanceOf); + } - @Override - @Nonnull - public String toString() { - return EntryStream.of("instanceof ", myInstanceOf, - "not instanceof ", myNotInstanceOf) - .removeValues(Set::isEmpty) - .mapKeyValue((prefix, set) -> StreamEx.of(set).joining(", ", prefix, "")) - .joining(" "); - } + @Override + public String toString() { + return EntryStream.of( + "instanceof ", myInstanceOf, + "not instanceof ", myNotInstanceOf + ) + .removeValues(Set::isEmpty) + .mapKeyValue((prefix, set) -> StreamEx.of(set).joining(", ", prefix, "")) + .joining(" "); + } - @Override - @Nonnull - public String getPresentationText(@Nullable PsiType type) { - Set instanceOfTypes = myInstanceOf; - Exact exact = type == null ? null : ObjectUtil.tryCast(TypeConstraints.exact(type), Exact.class); - if (exact != null) { - instanceOfTypes = StreamEx.of(instanceOfTypes) - .without(exact) - .toSet(); - } - return EntryStream.of("instanceof ", instanceOfTypes, - "not instanceof ", myNotInstanceOf) - .removeValues(Set::isEmpty) - .mapKeyValue((prefix, set) -> StreamEx.of(set).map(Exact::toShortString).sorted().joining(", ", prefix, "")) - .joining("\n"); + @Override + public String getPresentationText(@Nullable PsiType type) { + Set instanceOfTypes = myInstanceOf; + Exact exact = type == null ? null : ObjectUtil.tryCast(TypeConstraints.exact(type), Exact.class); + if (exact != null) { + instanceOfTypes = StreamEx.of(instanceOfTypes) + .without(exact) + .toSet(); + } + return EntryStream.of( + "instanceof ", instanceOfTypes, + "not instanceof ", myNotInstanceOf + ) + .removeValues(Set::isEmpty) + .mapKeyValue((prefix, set) -> StreamEx.of(set).map(Exact::toShortString).sorted().joining(", ", prefix, "")) + .joining("\n"); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java index 05d7395d30..d55507131c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/TypeConstraints.java @@ -5,610 +5,582 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.language.psi.PsiManager; import consulo.project.Project; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; public final class TypeConstraints { - /** - * Top constraint (no restriction; any non-primitive value satisfies this) - */ - public static final TypeConstraint TOP = new TypeConstraint() { - @Nonnull - @Override - public TypeConstraint join(@Nonnull TypeConstraint other) { - return this; - } + /** + * Top constraint (no restriction; any non-primitive value satisfies this) + */ + public static final TypeConstraint TOP = new TypeConstraint() { + @Override + public TypeConstraint join(TypeConstraint other) { + return this; + } - @Nonnull - @Override - public TypeConstraint meet(@Nonnull TypeConstraint other) { - return other; - } + @Override + public TypeConstraint meet(TypeConstraint other) { + return other; + } - @Override - public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - return true; - } + @Override + public boolean isSuperConstraintOf(TypeConstraint other) { + return true; + } - @Override - public TypeConstraint tryNegate() { - return BOTTOM; - } + @Override + public TypeConstraint tryNegate() { + return BOTTOM; + } - @Override - public String toString() { - return ""; - } - }; - /** - * Bottom constraint (no actual type satisfies this) - */ - public static final TypeConstraint BOTTOM = new TypeConstraint() { - @Nonnull - @Override - public TypeConstraint join(@Nonnull TypeConstraint other) { - return other; - } + @Override + public String toString() { + return ""; + } + }; + /** + * Bottom constraint (no actual type satisfies this) + */ + public static final TypeConstraint BOTTOM = new TypeConstraint() { + @Override + public TypeConstraint join(TypeConstraint other) { + return other; + } - @Nonnull - @Override - public TypeConstraint meet(@Nonnull TypeConstraint other) { - return this; - } + @Override + public TypeConstraint meet(TypeConstraint other) { + return this; + } - @Override - public boolean isSuperConstraintOf(@Nonnull TypeConstraint other) { - return other == this; - } + @Override + public boolean isSuperConstraintOf(TypeConstraint other) { + return other == this; + } - @Override - public TypeConstraint tryNegate() { - return TOP; - } + @Override + public TypeConstraint tryNegate() { + return TOP; + } - @Override - public String toString() { - return ""; - } - }; - - /** - * Exactly java.lang.Object class - */ - public static final TypeConstraint.Exact EXACTLY_OBJECT = new TypeConstraint.Exact() { - @Override - public StreamEx superTypes() { - return StreamEx.empty(); - } + @Override + public String toString() { + return ""; + } + }; + + /** + * Exactly java.lang.Object class + */ + public static final TypeConstraint.Exact EXACTLY_OBJECT = new TypeConstraint.Exact() { + @Override + public StreamEx superTypes() { + return StreamEx.empty(); + } - @Override - public boolean isFinal() { - return false; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return true; - } + @Override + public boolean isAssignableFrom(Exact other) { + return true; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return true; - } + @Override + public boolean isConvertibleFrom(Exact other) { + return true; + } - @Nonnull - @Override - public TypeConstraint instanceOf() { - return TOP; - } + @Override + public TypeConstraint instanceOf() { + return TOP; + } - @Nonnull - @Override - public TypeConstraint notInstanceOf() { - return BOTTOM; - } + @Override + public TypeConstraint notInstanceOf() { + return BOTTOM; + } - @Override - public String toString() { - return CommonClassNames.JAVA_LANG_OBJECT; - } + @Override + public String toString() { + return CommonClassNames.JAVA_LANG_OBJECT; + } - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT); - } - }; - - @Nullable - private static TypeConstraint.Exact createExact(@Nonnull PsiType type) { - if (type instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) type).getComponentType(); - if (componentType instanceof PsiPrimitiveType) { - for (PrimitiveArray p : PrimitiveArray.values()) { - if (p.getType().equals(componentType)) { - return p; - } + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT); + } + }; + + private static TypeConstraint.@Nullable Exact createExact(PsiType type) { + if (type instanceof PsiArrayType arrayType) { + PsiType componentType = arrayType.getComponentType(); + if (componentType instanceof PsiPrimitiveType) { + for (PrimitiveArray p : PrimitiveArray.values()) { + if (p.getType().equals(componentType)) { + return p; + } + } + return null; + } + TypeConstraint.Exact componentConstraint = createExact(componentType); + return componentConstraint == null ? null : new ExactArray(componentConstraint); + } + if (type instanceof PsiClassType classType) { + PsiClass psiClass = classType.resolve(); + if (psiClass == null) { + return new Unresolved(type.getCanonicalText()); + } + if (!(psiClass instanceof PsiTypeParameter)) { + return exactClass(psiClass); + } } return null; - } - TypeConstraint.Exact componentConstraint = createExact(componentType); - return componentConstraint == null ? null : new ExactArray(componentConstraint); } - if (type instanceof PsiClassType) { - PsiClass psiClass = ((PsiClassType) type).resolve(); - if (psiClass == null) { - return new Unresolved(type.getCanonicalText()); - } - if (!(psiClass instanceof PsiTypeParameter)) { - return exactClass(psiClass); - } - } - return null; - } - - /** - * @param type PsiType - * @return a constraint for the object that has exactly given PsiType; - * {@link #BOTTOM} if the object of given type cannot be instantiated. - */ - @Nonnull - @Contract(pure = true) - public static TypeConstraint exact(@Nonnull PsiType type) { - type = normalizeType(type); - TypeConstraint.Exact exact = createExact(type); - if (exact != null && exact.canBeInstantiated()) { - return exact; - } - return BOTTOM; - } - - /** - * @param type PsiType - * @return a constraint for the object whose type is the supplied type or any subtype - */ - @Nonnull - @Contract(pure = true) - public static TypeConstraint instanceOf(@Nonnull PsiType type) { - if (type instanceof PsiLambdaExpressionType || type instanceof PsiMethodReferenceType) { - return TOP; - } - type = normalizeType(type); - if (type instanceof PsiDisjunctionType) { - type = ((PsiDisjunctionType) type).getLeastUpperBound(); + + /** + * @param type PsiType + * @return a constraint for the object that has exactly given PsiType; + * {@link #BOTTOM} if the object of given type cannot be instantiated. + */ + @Contract(pure = true) + public static TypeConstraint exact(PsiType type) { + type = normalizeType(type); + TypeConstraint.Exact exact = createExact(type); + if (exact != null && exact.canBeInstantiated()) { + return exact; + } + return BOTTOM; } - if (type instanceof PsiIntersectionType) { - PsiType[] conjuncts = ((PsiIntersectionType) type).getConjuncts(); - TypeConstraint result = TOP; - for (PsiType conjunct : conjuncts) { - TypeConstraint.Exact exact = createExact(conjunct); + + /** + * @param type PsiType + * @return a constraint for the object whose type is the supplied type or any subtype + */ + @Contract(pure = true) + public static TypeConstraint instanceOf(PsiType type) { + if (type instanceof PsiLambdaExpressionType || type instanceof PsiMethodReferenceType) { + return TOP; + } + type = normalizeType(type); + if (type instanceof PsiDisjunctionType disjunctionType) { + type = disjunctionType.getLeastUpperBound(); + } + if (type instanceof PsiIntersectionType intersectionType) { + PsiType[] conjuncts = intersectionType.getConjuncts(); + TypeConstraint result = TOP; + for (PsiType conjunct : conjuncts) { + TypeConstraint.Exact exact = createExact(conjunct); + if (exact == null) { + return new Unresolved(type.getCanonicalText()).instanceOf(); + } + result = result.meet(exact.instanceOf()); + } + return result; + } + TypeConstraint.Exact exact = createExact(type); if (exact == null) { - return new Unresolved(type.getCanonicalText()).instanceOf(); + return new Unresolved(type.getCanonicalText()).instanceOf(); } - result = result.meet(exact.instanceOf()); - } - return result; - } - TypeConstraint.Exact exact = createExact(type); - if (exact == null) { - return new Unresolved(type.getCanonicalText()).instanceOf(); + return exact.instanceOf(); } - return exact.instanceOf(); - } - @Nonnull - private static PsiType normalizeType(@Nonnull PsiType psiType) { - if (psiType instanceof PsiArrayType) { - return PsiTypesUtil.createArrayType(normalizeType(psiType.getDeepComponentType()), psiType.getArrayDimensions()); - } - if (psiType instanceof PsiWildcardType) { - return normalizeType(((PsiWildcardType) psiType).getExtendsBound()); - } - if (psiType instanceof PsiCapturedWildcardType) { - return normalizeType(((PsiCapturedWildcardType) psiType).getUpperBound()); - } - if (psiType instanceof PsiIntersectionType) { - PsiType[] types = - StreamEx.of(((PsiIntersectionType) psiType).getConjuncts()).map(TypeConstraints::normalizeType).toArray(PsiType.EMPTY_ARRAY); - if (types.length > 0) { - return PsiIntersectionType.createIntersection(true, types); - } - } - if (psiType instanceof PsiClassType) { - return normalizeClassType((PsiClassType) psiType, new HashSet<>()); - } - return psiType; - } - - @Nonnull - private static PsiType normalizeClassType(@Nonnull PsiClassType psiType, Set processed) { - PsiClass aClass = psiType.resolve(); - if (aClass instanceof PsiTypeParameter) { - PsiClassType[] types = aClass.getExtendsListTypes(); - List result = new ArrayList<>(); - for (PsiClassType type : types) { - PsiClass resolved = type.resolve(); - if (resolved != null && processed.add(resolved)) { - PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(resolved); - result.add(normalizeClassType(classType, processed)); - } - } - if (!result.isEmpty()) { - return PsiIntersectionType.createIntersection(true, result.toArray(PsiType.EMPTY_ARRAY)); - } - return PsiType.getJavaLangObject(aClass.getManager(), aClass.getResolveScope()); - } - return psiType.rawType(); - } - - @Nonnull - private static TypeConstraint.Exact exactClass(@Nonnull PsiClass psiClass) { - String name = psiClass.getQualifiedName(); - if (name != null) { - switch (name) { - case CommonClassNames.JAVA_LANG_OBJECT: - return EXACTLY_OBJECT; - case CommonClassNames.JAVA_LANG_CLONEABLE: - return ArraySuperInterface.CLONEABLE; - case CommonClassNames.JAVA_IO_SERIALIZABLE: - return ArraySuperInterface.SERIALIZABLE; - } - } - return new ExactClass(psiClass); - } - - private enum PrimitiveArray implements TypeConstraint.Exact { - BOOLEAN(PsiType.BOOLEAN), - INT(PsiType.INT), - BYTE(PsiType.BYTE), - SHORT(PsiType.SHORT), - LONG(PsiType.LONG), - CHAR(PsiType.CHAR), - FLOAT(PsiType.FLOAT), - DOUBLE(PsiType.DOUBLE); - private final PsiPrimitiveType myType; - - PrimitiveArray(PsiPrimitiveType type) { - myType = type; - } + private static PsiType normalizeType(PsiType psiType) { + if (psiType instanceof PsiArrayType) { + return PsiTypesUtil.createArrayType(normalizeType(psiType.getDeepComponentType()), psiType.getArrayDimensions()); + } + if (psiType instanceof PsiWildcardType wildcardType) { + return normalizeType(wildcardType.getExtendsBound()); + } + if (psiType instanceof PsiCapturedWildcardType capturedWildcardType) { + return normalizeType(capturedWildcardType.getUpperBound()); + } + if (psiType instanceof PsiIntersectionType intersectionType) { + PsiType[] types = StreamEx.of(intersectionType.getConjuncts()) + .map(TypeConstraints::normalizeType) + .toArray(PsiType.EMPTY_ARRAY); + if (types.length > 0) { + return PsiIntersectionType.createIntersection(true, types); + } + } + if (psiType instanceof PsiClassType classType) { + return normalizeClassType(classType, new HashSet<>()); + } + return psiType; + } + + private static PsiType normalizeClassType(PsiClassType psiType, Set processed) { + PsiClass aClass = psiType.resolve(); + if (aClass instanceof PsiTypeParameter) { + PsiClassType[] types = aClass.getExtendsListTypes(); + List result = new ArrayList<>(); + for (PsiClassType type : types) { + PsiClass resolved = type.resolve(); + if (resolved != null && processed.add(resolved)) { + PsiClassType classType = JavaPsiFacade.getElementFactory(aClass.getProject()).createType(resolved); + result.add(normalizeClassType(classType, processed)); + } + } + if (!result.isEmpty()) { + return PsiIntersectionType.createIntersection(true, result.toArray(PsiType.EMPTY_ARRAY)); + } + return PsiType.getJavaLangObject(aClass.getManager(), aClass.getResolveScope()); + } + return psiType.rawType(); + } + + private static TypeConstraint.Exact exactClass(PsiClass psiClass) { + String name = psiClass.getQualifiedName(); + if (name != null) { + switch (name) { + case CommonClassNames.JAVA_LANG_OBJECT: + return EXACTLY_OBJECT; + case CommonClassNames.JAVA_LANG_CLONEABLE: + return ArraySuperInterface.CLONEABLE; + case CommonClassNames.JAVA_IO_SERIALIZABLE: + return ArraySuperInterface.SERIALIZABLE; + } + } + return new ExactClass(psiClass); + } + + private enum PrimitiveArray implements TypeConstraint.Exact { + BOOLEAN(PsiType.BOOLEAN), + INT(PsiType.INT), + BYTE(PsiType.BYTE), + SHORT(PsiType.SHORT), + LONG(PsiType.LONG), + CHAR(PsiType.CHAR), + FLOAT(PsiType.FLOAT), + DOUBLE(PsiType.DOUBLE); + private final PsiPrimitiveType myType; + + PrimitiveArray(PsiPrimitiveType type) { + myType = type; + } - @Nonnull - @Override - public PsiType getPsiType(Project project) { - return myType.createArrayType(); - } + @Override + public PsiType getPsiType(Project project) { + return myType.createArrayType(); + } - @Nonnull - @Override - public String toString() { - return myType.getCanonicalText() + "[]"; - } + @Override + public String toString() { + return myType.getCanonicalText() + "[]"; + } - PsiPrimitiveType getType() { - return myType; - } + PsiPrimitiveType getType() { + return myType; + } - @Override - public boolean isFinal() { - return true; - } + @Override + public boolean isFinal() { + return true; + } - @Override - public StreamEx superTypes() { - return StreamEx.of(ArraySuperInterface.values()).append(EXACTLY_OBJECT); - } + @Override + public StreamEx superTypes() { + return StreamEx.of(ArraySuperInterface.values()).append(EXACTLY_OBJECT); + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return other.equals(this); - } + @Override + public boolean isAssignableFrom(Exact other) { + return other.equals(this); + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return other.equals(this) || other.isAssignableFrom(this); + @Override + public boolean isConvertibleFrom(Exact other) { + return other.equals(this) || other.isAssignableFrom(this); + } } - } - private enum ArraySuperInterface implements TypeConstraint.Exact { - CLONEABLE(CommonClassNames.JAVA_LANG_CLONEABLE), - SERIALIZABLE(CommonClassNames.JAVA_IO_SERIALIZABLE); - private final - @Nonnull - String myReference; + private enum ArraySuperInterface implements TypeConstraint.Exact { + CLONEABLE(CommonClassNames.JAVA_LANG_CLONEABLE), + SERIALIZABLE(CommonClassNames.JAVA_IO_SERIALIZABLE); + private final String myReference; - ArraySuperInterface(@Nonnull String reference) { - myReference = reference; - } + ArraySuperInterface(String reference) { + myReference = reference; + } - @Nonnull - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(myReference); - } + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createTypeByFQClassName(myReference); + } - @Nonnull - @Override - public String toString() { - return myReference; - } + @Override + public String toString() { + return myReference; + } - @Override - public boolean isFinal() { - return false; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public StreamEx superTypes() { - return StreamEx.of(EXACTLY_OBJECT); - } + @Override + public StreamEx superTypes() { + return StreamEx.of(EXACTLY_OBJECT); + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (equals(other)) { - return true; - } - if (other instanceof PrimitiveArray || other instanceof ExactArray || other instanceof Unresolved) { - return true; - } - if (other instanceof ExactClass) { - return InheritanceUtil.isInheritor(((ExactClass) other).myClass, myReference); - } - return false; - } + @Override + public boolean isAssignableFrom(Exact other) { + if (equals(other)) { + return true; + } + if (other instanceof PrimitiveArray || other instanceof ExactArray || other instanceof Unresolved) { + return true; + } + //noinspection SimplifiableIfStatement + if (other instanceof ExactClass exactClass) { + return InheritanceUtil.isInheritor(exactClass.myClass, myReference); + } + return false; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return !other.isFinal() || isAssignableFrom(other); - } + @Override + public boolean isConvertibleFrom(Exact other) { + return !other.isFinal() || isAssignableFrom(other); + } - @Override - public boolean canBeInstantiated() { - return false; + @Override + public boolean canBeInstantiated() { + return false; + } } - } - private static final class ExactClass implements TypeConstraint.Exact { - private final - @Nonnull - PsiClass myClass; + private static final class ExactClass implements TypeConstraint.Exact { + private final + PsiClass myClass; - ExactClass(@Nonnull PsiClass aClass) { - assert !(aClass instanceof PsiTypeParameter); - myClass = aClass; - } + ExactClass(PsiClass aClass) { + assert !(aClass instanceof PsiTypeParameter); + myClass = aClass; + } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof ExactClass && - myClass.getManager().areElementsEquivalent(myClass, ((ExactClass) obj).myClass); - } + @Override + public boolean equals(Object obj) { + return obj == this + || obj instanceof ExactClass exactClass && myClass.getManager().areElementsEquivalent(myClass, exactClass.myClass); + } - @Override - public int hashCode() { - return Objects.hashCode(myClass.getName()); - } + @Override + @RequiredReadAction + public int hashCode() { + return Objects.hashCode(myClass.getName()); + } - @Override - public boolean canBeInstantiated() { - // Abstract final type is incorrect. We, however, assume that final wins: it can be instantiated - // otherwise TypeConstraints.instanceOf(type) would return impossible type - return (myClass.hasModifierProperty(PsiModifier.FINAL) || !myClass.hasModifierProperty(PsiModifier.ABSTRACT)) && - !CommonClassNames.JAVA_LANG_VOID.equals(myClass.getQualifiedName()); - } + @Override + public boolean canBeInstantiated() { + // Abstract final type is incorrect. We, however, assume that final wins: it can be instantiated + // otherwise TypeConstraints.instanceOf(type) would return impossible type + return (myClass.isFinal() || !myClass.isAbstract()) + && !CommonClassNames.JAVA_LANG_VOID.equals(myClass.getQualifiedName()); + } - @Override - public boolean isComparedByEquals() { - String name = myClass.getQualifiedName(); - return name != null && (CommonClassNames.JAVA_LANG_STRING.equals(name) || TypeConversionUtil.isPrimitiveWrapper(name)); - } + @Override + public boolean isComparedByEquals() { + String name = myClass.getQualifiedName(); + return name != null && (CommonClassNames.JAVA_LANG_STRING.equals(name) || TypeConversionUtil.isPrimitiveWrapper(name)); + } - @Nonnull - @Override - public PsiType getPsiType(Project project) { - return JavaPsiFacade.getElementFactory(project).createType(myClass); - } + @Override + public PsiType getPsiType(Project project) { + return JavaPsiFacade.getElementFactory(project).createType(myClass); + } - @Nonnull - @Override - public String toString() { - String name = myClass.getQualifiedName(); - if (name == null) { - name = myClass.getName(); - } - if (name == null && myClass instanceof PsiAnonymousClass) { - PsiClassType baseClassType = ((PsiAnonymousClass) myClass).getBaseClassType(); - name = "anonymous " + createExact(baseClassType); - } - return String.valueOf(name); - } + @Override + @RequiredReadAction + public String toString() { + String name = myClass.getQualifiedName(); + if (name == null) { + name = myClass.getName(); + } + if (name == null && myClass instanceof PsiAnonymousClass anonymousClass) { + PsiClassType baseClassType = anonymousClass.getBaseClassType(); + name = "anonymous " + createExact(baseClassType); + } + return String.valueOf(name); + } - @Override - public boolean isFinal() { - return myClass.hasModifierProperty(PsiModifier.FINAL); - } + @Override + public boolean isFinal() { + return myClass.isFinal(); + } - @Override - public StreamEx superTypes() { - List superTypes = new ArrayList<>(); - InheritanceUtil.processSupers(myClass, false, t -> { - if (!t.hasModifierProperty(PsiModifier.FINAL)) { - superTypes.add(exactClass(t)); + @Override + public StreamEx superTypes() { + List superTypes = new ArrayList<>(); + InheritanceUtil.processSupers(myClass, false, t -> { + if (!t.isFinal()) { + superTypes.add(exactClass(t)); + } + return true; + }); + return StreamEx.of(superTypes); } - return true; - }); - return StreamEx.of(superTypes); - } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (equals(other) || other instanceof Unresolved) { - return true; - } - if (other instanceof ExactClass) { - return InheritanceUtil.isInheritorOrSelf(((ExactClass) other).myClass, myClass, true); - } - return false; - } + @Override + public boolean isAssignableFrom(Exact other) { + if (equals(other) || other instanceof Unresolved) { + return true; + } + //noinspection SimplifiableIfStatement + if (other instanceof ExactClass exactClass) { + return InheritanceUtil.isInheritorOrSelf(exactClass.myClass, myClass, true); + } + return false; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - if (equals(other) || other instanceof Unresolved || other == EXACTLY_OBJECT) { - return true; - } - if (other instanceof ArraySuperInterface) { - if (myClass.isInterface()) { - return true; - } - if (!myClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - return InheritanceUtil.isInheritor(myClass, ((ArraySuperInterface) other).myReference); - } - if (other instanceof ExactClass) { - PsiClass otherClass = ((ExactClass) other).myClass; - if (myClass.isInterface() && otherClass.isInterface()) { - return true; - } - if (myClass.isInterface() && !otherClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - if (otherClass.isInterface() && !myClass.hasModifierProperty(PsiModifier.FINAL)) { - return true; - } - PsiManager manager = myClass.getManager(); - return manager.areElementsEquivalent(myClass, otherClass) || - otherClass.isInheritor(myClass, true) || - myClass.isInheritor(otherClass, true); - } - return false; + @Override + public boolean isConvertibleFrom(Exact other) { + if (equals(other) || other instanceof Unresolved || other == EXACTLY_OBJECT) { + return true; + } + if (other instanceof ArraySuperInterface arraySuperInterface) { + return myClass.isInterface() || !myClass.isFinal() + || InheritanceUtil.isInheritor(myClass, arraySuperInterface.myReference); + } + if (other instanceof ExactClass exactClass) { + PsiClass otherClass = exactClass.myClass; + if (myClass.isInterface() && otherClass.isInterface()) { + return true; + } + if (myClass.isInterface() && !otherClass.isFinal()) { + return true; + } + if (otherClass.isInterface() && !myClass.isFinal()) { + return true; + } + PsiManager manager = myClass.getManager(); + return manager.areElementsEquivalent(myClass, otherClass) + || otherClass.isInheritor(myClass, true) + || myClass.isInheritor(otherClass, true); + } + return false; + } } - } - private static final class ExactArray implements TypeConstraint.Exact { - private final - @Nonnull - Exact myComponent; + private static final class ExactArray implements TypeConstraint.Exact { + private final + Exact myComponent; - private ExactArray(@Nonnull Exact component) { - myComponent = component; - } + private ExactArray(Exact component) { + myComponent = component; + } - @Nullable - @Override - public PsiType getPsiType(Project project) { - PsiType componentType = myComponent.getPsiType(project); - return componentType == null ? null : componentType.createArrayType(); - } + @Nullable + @Override + public PsiType getPsiType(Project project) { + PsiType componentType = myComponent.getPsiType(project); + return componentType == null ? null : componentType.createArrayType(); + } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof ExactArray && myComponent.equals(((ExactArray) obj).myComponent); - } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof ExactArray exactArray && myComponent.equals(exactArray.myComponent); + } - @Override - public int hashCode() { - return myComponent.hashCode() * 31 + 1; - } + @Override + public int hashCode() { + return myComponent.hashCode() * 31 + 1; + } - @Nonnull - @Override - public String toString() { - return myComponent + "[]"; - } + @Override + public String toString() { + return myComponent + "[]"; + } - @Override - public boolean isFinal() { - return myComponent.isFinal(); - } + @Override + public boolean isFinal() { + return myComponent.isFinal(); + } - @Override - public StreamEx superTypes() { - return myComponent.superTypes().map(ExactArray::new).append(ArraySuperInterface.values()).append(EXACTLY_OBJECT); - } + @Override + public StreamEx superTypes() { + return myComponent.superTypes().map(ExactArray::new).append(ArraySuperInterface.values()).append(EXACTLY_OBJECT); + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - if (!(other instanceof ExactArray)) { - return false; - } - return myComponent.isAssignableFrom(((ExactArray) other).myComponent); - } + @Override + public boolean isAssignableFrom(Exact other) { + return other instanceof ExactArray exactArray && myComponent.isAssignableFrom(exactArray.myComponent); + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - if (other instanceof ExactArray) { - return myComponent.isConvertibleFrom(((ExactArray) other).myComponent); - } - if (other instanceof ArraySuperInterface) { - return true; - } - if (other instanceof ExactClass) { - return CommonClassNames.JAVA_LANG_OBJECT.equals(((ExactClass) other).myClass.getQualifiedName()); - } - return false; - } + @Override + public boolean isConvertibleFrom(Exact other) { + if (other instanceof ExactArray exactArray) { + return myComponent.isConvertibleFrom(exactArray.myComponent); + } + if (other instanceof ArraySuperInterface) { + return true; + } + //noinspection SimplifiableIfStatement + if (other instanceof ExactClass exactClass) { + return CommonClassNames.JAVA_LANG_OBJECT.equals(exactClass.myClass.getQualifiedName()); + } + return false; + } - @Override - public - @Nonnull - Exact getArrayComponent() { - return myComponent; + @Override + public + Exact getArrayComponent() { + return myComponent; + } } - } - private static final class Unresolved implements TypeConstraint.Exact { - private final - @Nonnull - String myReference; + private static final class Unresolved implements TypeConstraint.Exact { + private final + String myReference; - private Unresolved(@Nonnull String reference) { - myReference = reference; - } + private Unresolved(String reference) { + myReference = reference; + } - @Override - public boolean isResolved() { - return false; - } + @Override + public boolean isResolved() { + return false; + } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof Unresolved && myReference.equals(((Unresolved) obj).myReference); - } + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof Unresolved unresolved && myReference.equals(unresolved.myReference); + } - @Override - public int hashCode() { - return myReference.hashCode(); - } + @Override + public int hashCode() { + return myReference.hashCode(); + } - @Nonnull - @Override - public String toString() { - return " " + myReference; - } + @Override + public String toString() { + return " " + myReference; + } - @Override - public boolean isFinal() { - return false; - } + @Override + public boolean isFinal() { + return false; + } - @Override - public StreamEx superTypes() { - return StreamEx.of(EXACTLY_OBJECT); - } + @Override + public StreamEx superTypes() { + return StreamEx.of(EXACTLY_OBJECT); + } - @Override - public boolean isAssignableFrom(@Nonnull Exact other) { - return other instanceof Unresolved || other instanceof ExactClass; - } + @Override + public boolean isAssignableFrom(Exact other) { + return other instanceof Unresolved || other instanceof ExactClass; + } - @Override - public boolean isConvertibleFrom(@Nonnull Exact other) { - return other instanceof Unresolved || other instanceof ExactClass || other instanceof ArraySuperInterface; + @Override + public boolean isConvertibleFrom(Exact other) { + return other instanceof Unresolved || other instanceof ExactClass || other instanceof ArraySuperInterface; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java index 597eca4063..597ee3eed2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/DeleteSwitchLabelFix.java @@ -4,19 +4,18 @@ import com.intellij.java.language.impl.psi.impl.PsiImplUtil; import com.intellij.java.language.psi.*; import com.siyeh.ig.psiutils.CommentTracker; -import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.PsiElement; import consulo.language.psi.scope.LocalSearchScope; import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.lang.ObjectUtil; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -26,7 +25,7 @@ public class DeleteSwitchLabelFix implements LocalQuickFix { private final String myName; private final boolean myBranch; - public DeleteSwitchLabelFix(@Nonnull PsiExpression label) { + public DeleteSwitchLabelFix(PsiExpression label) { myName = label.getText(); PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(PsiImplUtil.getSwitchLabel(label)); PsiExpressionList values = labelStatement.getCaseValues(); @@ -46,24 +45,15 @@ private static boolean shouldRemoveBranch(PsiSwitchLabelStatementBase label) { return prevStatement == null || !ControlFlowUtils.statementMayCompleteNormally(prevStatement); } - @Nls(capitalization = Nls.Capitalization.Sentence) - @Nonnull @Override - public String getName() { - return myBranch ? + public LocalizeValue getName() { + return LocalizeValue.localizeTODO(myBranch ? "Remove switch branch '" + myName + "'" : - "Remove switch label '" + myName + "'"; + "Remove switch label '" + myName + "'"); } - @Nls(capitalization = Nls.Capitalization.Sentence) - @Nonnull @Override - public String getFamilyName() { - return "Remove switch label"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + public void applyFix(Project project, ProblemDescriptor descriptor) { PsiExpression expression = ObjectUtil.tryCast(descriptor.getStartElement(), PsiExpression.class); if (expression == null) { return; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/RedundantInstanceofFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/RedundantInstanceofFix.java index 10f87a51a9..8987abff7c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/RedundantInstanceofFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/RedundantInstanceofFix.java @@ -15,16 +15,16 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.fix; -import consulo.language.editor.inspection.InspectionsBundle; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import com.intellij.java.language.psi.JavaPsiFacade; -import consulo.language.psi.PsiElement; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiInstanceOfExpression; - -import javax.annotation.Nonnull; +import consulo.annotation.access.RequiredWriteAction; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; +import consulo.project.Project; /** * @author peter @@ -32,20 +32,20 @@ public class RedundantInstanceofFix implements LocalQuickFix { @Override - @Nonnull - public String getFamilyName() + public LocalizeValue getName() { - return InspectionsBundle.message("inspection.data.flow.redundant.instanceof.quickfix"); + return InspectionLocalize.inspectionDataFlowRedundantInstanceofQuickfix(); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { final PsiElement psiElement = descriptor.getPsiElement(); - if(psiElement instanceof PsiInstanceOfExpression) + if (psiElement instanceof PsiInstanceOfExpression instanceOfExpression) { - PsiExpression compareToNull = JavaPsiFacade.getInstance(psiElement.getProject()).getElementFactory(). - createExpressionFromText(((PsiInstanceOfExpression) psiElement).getOperand().getText() + " != null", psiElement.getParent()); + PsiExpression compareToNull = JavaPsiFacade.getInstance(psiElement.getProject()).getElementFactory() + .createExpressionFromText(instanceOfExpression.getOperand().getText() + " != null", psiElement.getParent()); psiElement.replace(compareToNull); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithArgumentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithArgumentFix.java index 35e687a7e9..4ac978d8ef 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithArgumentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithArgumentFix.java @@ -4,49 +4,38 @@ import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; import com.intellij.java.language.psi.util.PsiExpressionTrimRenderer; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.psiutils.CommentTracker; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.intention.CommonQuickFixBundle; +import consulo.language.editor.localize.CommonQuickFixLocalize; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import javax.annotation.Nonnull; - public class ReplaceWithArgumentFix implements LocalQuickFix { - private final String myText; - private final int myArgNum; - - public ReplaceWithArgumentFix(PsiExpression argument, int argNum) { - myText = PsiExpressionTrimRenderer.render(argument); - myArgNum = argNum; - } - - @Override - public - @Nonnull - String getName() { - return CommonQuickFixBundle.message("fix.replace.with.x", myText); - } + private final String myText; + private final int myArgNum; - @Override - public - @Nonnull - String getFamilyName() { - return InspectionGadgetsBundle.message("inspection.redundant.string.replace.with.arg.fix.name"); - } + public ReplaceWithArgumentFix(PsiExpression argument, int argNum) { + myText = PsiExpressionTrimRenderer.render(argument); + myArgNum = argNum; + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class); - if (call == null) { - return; + @Override + public LocalizeValue getName() { + return CommonQuickFixLocalize.fixReplaceWithX(myText); } - PsiExpression[] args = call.getArgumentList().getExpressions(); - if (args.length <= myArgNum) { - return; + + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class); + if (call == null) { + return; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + if (args.length <= myArgNum) { + return; + } + new CommentTracker().replaceAndRestoreComments(call, args[myArgNum]); } - new CommentTracker().replaceAndRestoreComments(call, args[myArgNum]); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithConstantValueFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithConstantValueFix.java index 46a71be6bf..cded297d35 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithConstantValueFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithConstantValueFix.java @@ -15,65 +15,50 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.fix; -import javax.annotation.Nonnull; - -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; +import com.intellij.java.analysis.impl.refactoring.extractMethod.ExtractMethodUtil; import com.intellij.java.language.psi.JavaPsiFacade; -import consulo.language.psi.PsiElement; import com.intellij.java.language.psi.PsiExpressionList; import com.intellij.java.language.psi.PsiMethod; import com.intellij.java.language.psi.PsiMethodCallExpression; -import com.intellij.java.analysis.impl.refactoring.extractMethod.ExtractMethodUtil; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; +import consulo.project.Project; /** * @author peter */ -public class ReplaceWithConstantValueFix implements LocalQuickFix -{ - private final String myPresentableName; - private final String myReplacementText; - - public ReplaceWithConstantValueFix(String presentableName, String replacementText) - { - myPresentableName = presentableName; - myReplacementText = replacementText; - } +public class ReplaceWithConstantValueFix implements LocalQuickFix { + private final String myPresentableName; + private final String myReplacementText; - @Nonnull - @Override - public String getName() - { - return "Replace with '" + myPresentableName + "'"; - } + public ReplaceWithConstantValueFix(String presentableName, String replacementText) { + myPresentableName = presentableName; + myReplacementText = replacementText; + } - @Nonnull - @Override - public String getFamilyName() - { - return "Replace with constant value"; - } + @Override + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Replace with '" + myPresentableName + "'"); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - PsiElement problemElement = descriptor.getPsiElement(); - if(problemElement == null) - { - return; - } + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement problemElement = descriptor.getPsiElement(); + if (problemElement == null) { + return; + } - PsiMethodCallExpression call = problemElement.getParent() instanceof PsiExpressionList && problemElement.getParent().getParent() instanceof PsiMethodCallExpression ? - (PsiMethodCallExpression) problemElement.getParent().getParent() : null; - PsiMethod targetMethod = call == null ? null : call.resolveMethod(); + PsiMethodCallExpression call = problemElement.getParent() instanceof PsiExpressionList && problemElement.getParent().getParent() instanceof PsiMethodCallExpression ? + (PsiMethodCallExpression) problemElement.getParent().getParent() : null; + PsiMethod targetMethod = call == null ? null : call.resolveMethod(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - problemElement.replace(facade.getElementFactory().createExpressionFromText(myReplacementText, null)); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + problemElement.replace(facade.getElementFactory().createExpressionFromText(myReplacementText, null)); - if(targetMethod != null) - { - ExtractMethodUtil.addCastsToEnsureResolveTarget(targetMethod, call); - } - } + if (targetMethod != null) { + ExtractMethodUtil.addCastsToEnsureResolveTarget(targetMethod, call); + } + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithObjectsEqualsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithObjectsEqualsFix.java index e660409005..99f000a976 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithObjectsEqualsFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/ReplaceWithObjectsEqualsFix.java @@ -15,25 +15,17 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.fix; -import javax.annotation.Nonnull; - -import org.jetbrains.annotations.Nls; - -import javax.annotation.Nullable; +import com.intellij.java.language.LanguageLevel; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.java.language.psi.util.PsiUtil; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import com.intellij.java.language.LanguageLevel; -import consulo.java.language.module.util.JavaClassNames; -import com.intellij.java.language.psi.JavaPsiFacade; import consulo.language.psi.PsiElement; -import com.intellij.java.language.psi.PsiExpression; -import com.intellij.java.language.psi.PsiMethod; -import com.intellij.java.language.psi.PsiMethodCallExpression; -import com.intellij.java.language.psi.PsiReferenceExpression; -import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import consulo.language.psi.util.PsiTreeUtil; -import com.intellij.java.language.psi.util.PsiUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -49,24 +41,14 @@ private ReplaceWithObjectsEqualsFix(String qualifierText, String replacementText myReplacementText = replacementText; } - @Nls - @Nonnull - @Override - public String getName() - { - return "Replace '" + myQualifierText + ".equals(...)' with 'Objects.equals(" + myReplacementText + ", ...)'"; - } - - @Nls - @Nonnull @Override - public String getFamilyName() + public LocalizeValue getName() { - return "Replace '.equals()' with 'Objects.equals()'"; + return LocalizeValue.localizeTODO("Replace '" + myQualifierText + ".equals(...)' with 'Objects.equals(" + myReplacementText + ", ...)'"); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) + public void applyFix(Project project, ProblemDescriptor descriptor) { PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); if(call == null) @@ -86,7 +68,7 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descri } @Nullable - public static ReplaceWithObjectsEqualsFix createFix(@Nonnull PsiMethodCallExpression call, @Nonnull PsiReferenceExpression methodExpression) + public static ReplaceWithObjectsEqualsFix createFix(PsiMethodCallExpression call, PsiReferenceExpression methodExpression) { if(!"equals".equals(methodExpression.getReferenceName()) || call.getArgumentList().getExpressions().length != 1 || !PsiUtil.getLanguageLevel(call).isAtLeast(LanguageLevel.JDK_1_7)) { @@ -101,7 +83,8 @@ public static ReplaceWithObjectsEqualsFix createFix(@Nonnull PsiMethodCallExpres } PsiMethod method = call.resolveMethod(); - if(method != null && method.getParameterList().getParametersCount() == 1 && method.getParameterList().getParameters()[0].getType().equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) + if(method != null && method.getParameterList().getParametersCount() == 1 + && method.getParameterList().getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { return new ReplaceWithObjectsEqualsFix(qualifier.getText(), noParens.getText()); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SimplifyToAssignmentFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SimplifyToAssignmentFix.java index a4865207b5..d472017b9a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SimplifyToAssignmentFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SimplifyToAssignmentFix.java @@ -15,48 +15,42 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.fix; -import javax.annotation.Nonnull; -import consulo.language.editor.inspection.InspectionsBundle; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import com.intellij.java.language.psi.JavaPsiFacade; import com.intellij.java.language.psi.PsiAssignmentExpression; -import consulo.language.psi.PsiElement; import com.intellij.java.language.psi.PsiElementFactory; import com.intellij.java.language.psi.PsiExpression; +import consulo.annotation.access.RequiredWriteAction; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; /** * @author peter */ public class SimplifyToAssignmentFix implements LocalQuickFix { - @Nonnull - @Override - public String getName() - { - return InspectionsBundle.message("inspection.data.flow.simplify.to.assignment.quickfix.name"); - } - - @Nonnull @Override - public String getFamilyName() + public LocalizeValue getName() { - return InspectionsBundle.message("inspection.data.flow.simplify.boolean.expression.quickfix"); + return InspectionLocalize.inspectionDataFlowSimplifyToAssignmentQuickfixName(); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { final PsiElement psiElement = descriptor.getPsiElement(); - if(psiElement == null) + if (psiElement == null) { return; } final PsiAssignmentExpression assignmentExpression = PsiTreeUtil.getParentOfType(psiElement, PsiAssignmentExpression.class); - if(assignmentExpression == null) + if (assignmentExpression == null) { return; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java index 41af4a1d5d..f1c9ee9857 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java @@ -15,55 +15,38 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.fix; -import javax.annotation.Nonnull; - -import org.jetbrains.annotations.Nls; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; import com.intellij.java.language.psi.JavaPsiFacade; import com.intellij.java.language.psi.PsiExpression; +import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.SmartPointerManager; import consulo.language.psi.SmartPsiElementPointer; -import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; -import consulo.java.analysis.impl.codeInsight.JavaInspectionsBundle; - -public class SurroundWithRequireNonNullFix implements LocalQuickFix -{ - private final String myText; - private final SmartPsiElementPointer myQualifierPointer; +import consulo.localize.LocalizeValue; +import consulo.project.Project; - public SurroundWithRequireNonNullFix(@Nonnull PsiExpression expressionToSurround) - { - myText = expressionToSurround.getText(); - myQualifierPointer = SmartPointerManager.getInstance(expressionToSurround.getProject()).createSmartPsiElementPointer(expressionToSurround); - } +public class SurroundWithRequireNonNullFix implements LocalQuickFix { + private final String myText; + private final SmartPsiElementPointer myQualifierPointer; - @Nls - @Nonnull - @Override - public String getName() - { - return JavaInspectionsBundle.message("inspection.surround.requirenonnull.quickfix", myText); - } + public SurroundWithRequireNonNullFix(PsiExpression expressionToSurround) { + myText = expressionToSurround.getText(); + myQualifierPointer = SmartPointerManager.getInstance(expressionToSurround.getProject()).createSmartPsiElementPointer(expressionToSurround); + } - @Nls - @Nonnull - @Override - public String getFamilyName() - { - return JavaInspectionsBundle.message("inspection.surround.requirenonnull.quickfix", ""); - } + @Override + public LocalizeValue getName() { + return JavaInspectionsLocalize.inspectionSurroundRequirenonnullQuickfix(myText); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - PsiExpression qualifier = myQualifierPointer.getElement(); - if(qualifier == null) - { - return; - } - PsiExpression replacement = JavaPsiFacade.getElementFactory(project).createExpressionFromText("java.util.Objects.requireNonNull(" + qualifier.getText() + ")", qualifier); - JavaCodeStyleManager.getInstance(project).shortenClassReferences(qualifier.replace(replacement)); - } + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiExpression qualifier = myQualifierPointer.getElement(); + if (qualifier == null) { + return; + } + PsiExpression replacement = JavaPsiFacade.getElementFactory(project).createExpressionFromText("java.util.Objects.requireNonNull(" + qualifier.getText() + ")", qualifier); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(qualifier.replace(replacement)); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceIndexKt.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceIndexKt.java index 070ca7385d..3147a7805c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceIndexKt.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceIndexKt.java @@ -28,8 +28,7 @@ import consulo.language.psi.stub.gist.PsiFileGist; import consulo.language.psi.util.LanguageCachedValueUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.Map; @@ -40,7 +39,6 @@ class ContractInferenceIndexKt { private static PsiFileGist> gist = GistManager.getInstance().newPsiFileGist("javaContractInference", 19, MethodDataExternalizer.INSTANCE, file -> indexFile(file.getNode ().getLighterAST())); - @Nonnull private static Map indexFile(LighterAST tree) { InferenceVisitor visitor = new InferenceVisitor(tree); visitor.visitNode(tree.getRoot()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceInterpreter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceInterpreter.java index 1e59e1551c..7dd1575233 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceInterpreter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ContractInferenceInterpreter.java @@ -9,8 +9,7 @@ import consulo.language.ast.*; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -37,13 +36,11 @@ class ContractInferenceInterpreter { myBody = body; } - @Nonnull List getParameters() { LighterASTNode paramList = firstChildOfType(myTree, myMethod, PARAMETER_LIST); return paramList != null ? getChildrenOfType(myTree, paramList, PARAMETER) : emptyList(); } - @Nonnull List inferContracts(List statements) { if (statements.isEmpty()) { return emptyList(); @@ -86,7 +83,6 @@ private LighterASTNode getCodeBlock(@Nullable LighterASTNode parent) { return firstChildOfType(myTree, parent, CODE_BLOCK); } - @Nonnull static List getStatements(@Nullable LighterASTNode codeBlock, LighterAST tree) { return codeBlock == null ? emptyList() : getChildrenOfType(tree, codeBlock, ElementType.JAVA_STATEMENT_BIT_SET); } @@ -126,7 +122,7 @@ class ReturnValueVisitor extends RecursiveLighterASTNodeWalkingVisitor { } @Override - public void visitNode(@Nonnull LighterASTNode element) { + public void visitNode(LighterASTNode element) { IElementType type = element.getTokenType(); if (type == CLASS || type == LAMBDA_EXPRESSION) { return; @@ -160,7 +156,6 @@ public void visitNode(@Nonnull LighterASTNode element) { super.visitNode(element); } - @Nonnull private ContractReturnValue expressionToReturnValue(LighterASTNode expression) { expression = skipParenthesesDown(myTree, expression); if (expression == null) { @@ -189,7 +184,6 @@ private ContractReturnValue expressionToReturnValue(LighterASTNode expression) { return visitor.returnValue; } - @Nonnull private List visitExpression(final List states, @Nullable LighterASTNode expr) { if (expr == null) { return emptyList(); @@ -274,8 +268,7 @@ private List visitExpression(final List states, return emptyList(); } - @Nonnull - private List visitPolyadic(List states, @Nonnull LighterASTNode expr) { + private List visitPolyadic(List states, LighterASTNode expr) { if (firstChildOfType(myTree, expr, JavaTokenType.PLUS) != null) { return asPreContracts(ContainerUtil.map(states, s -> new StandardMethodContract(s, returnNotNull()))); } @@ -294,7 +287,6 @@ private List visitPolyadic(List states, @Nonnull return emptyList(); } - @Nonnull private static List asPreContracts(List contracts) { return ContainerUtil.map(contracts, KnownContract::new); } @@ -383,7 +375,7 @@ void addAll(List contracts) { } } - void registerDeclaration(@Nonnull LighterASTNode declStatement, @Nonnull LighterAST tree, int scopeStart) { + void registerDeclaration(LighterASTNode declStatement, LighterAST tree, int scopeStart) { for (LighterASTNode var : getChildrenOfType(tree, declStatement, LOCAL_VARIABLE)) { LighterASTNode initializer = findExpressionChild(tree, var); if (initializer != null) { @@ -393,7 +385,6 @@ void registerDeclaration(@Nonnull LighterASTNode declStatement, @Nonnull Lighter } } - @Nonnull private List visitStatements(List states, List statements) { CodeBlockContracts result = new CodeBlockContracts(); for (LighterASTNode statement : statements) { @@ -445,8 +436,7 @@ private ValueConstraint getLiteralConstraint(@Nullable LighterASTNode expr) { return null; } - @Nonnull - static ValueConstraint getLiteralConstraint(@Nonnull IElementType literalTokenType) { + static ValueConstraint getLiteralConstraint(IElementType literalTokenType) { if (literalTokenType.equals(JavaTokenType.TRUE_KEYWORD)) { return TRUE_VALUE; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/DelegationContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/DelegationContract.java index d31933abcd..7df1d2a257 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/DelegationContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/DelegationContract.java @@ -28,8 +28,7 @@ import consulo.util.collection.Lists; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -54,7 +53,6 @@ public boolean isNegated() { return negated; } - @Nonnull @Override public List toContracts(PsiMethod method, Supplier body) { PsiMethodCallExpression call = ObjectUtil.tryCast(expression.restoreExpression(body.get()), PsiMethodCallExpression.class); @@ -145,8 +143,7 @@ private StandardMethodContract convertDelegatedMethodContract(PsiMethod callerMe return null; } - @Nullable - private static StandardMethodContract.ValueConstraint getLiteralConstraint(PsiExpression argument) { + private static StandardMethodContract.@Nullable ValueConstraint getLiteralConstraint(PsiExpression argument) { if (argument instanceof PsiLiteralExpression) { return ContractInferenceInterpreter.getLiteralConstraint(argument.getFirstChild().getNode().getElementType()); } else if (argument instanceof PsiNewExpression || argument instanceof PsiPolyadicExpression || argument instanceof PsiFunctionalExpression) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ExpressionRange.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ExpressionRange.java index cdd627cd4a..5b4ae19522 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ExpressionRange.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ExpressionRange.java @@ -22,7 +22,7 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.annotation.access.RequiredReadAction; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * from kotlin diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/InferenceVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/InferenceVisitor.java index b05c4c5cd3..645012ad5f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/InferenceVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/InferenceVisitor.java @@ -5,8 +5,7 @@ import com.intellij.java.language.psi.JavaTokenType; import consulo.language.ast.*; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; import java.util.function.Consumer; @@ -16,20 +15,19 @@ * from kotlin */ class InferenceVisitor extends RecursiveLighterASTNodeWalkingVisitor { - @Nonnull private final LighterAST tree; private Map result = new HashMap<>(); private int methodIndex = 0; private Map classData = new HashMap<>(); - InferenceVisitor(@Nonnull LighterAST tree) { + InferenceVisitor(LighterAST tree) { super(tree); this.tree = tree; } @Override - public void visitNode(@Nonnull LighterASTNode element) { + public void visitNode(LighterASTNode element) { IElementType tokenType = element.getTokenType(); if (tokenType == CLASS || tokenType == ANONYMOUS_CLASS) { classData.put(element, calcClassData(element)); @@ -144,7 +142,7 @@ private ClassData calcClassData(LighterASTNode aClass) { private void walkMethodBody(LighterASTNode root, Consumer processor) { new RecursiveLighterASTNodeWalkingVisitor(tree) { @Override - public void visitNode(@Nonnull LighterASTNode element) { + public void visitNode(LighterASTNode element) { IElementType type = element.getTokenType(); if (type == CLASS || type == FIELD || type == METHOD || type == ANNOTATION_METHOD || type == LAMBDA_EXPRESSION) { return; @@ -157,7 +155,6 @@ public void visitNode(@Nonnull LighterASTNode element) { }.visitNode(root); } - @Nonnull public Map getResult() { return result; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/JavaSourceInference.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/JavaSourceInference.java index 3900535a1d..77597ab759 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/JavaSourceInference.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/JavaSourceInference.java @@ -27,7 +27,6 @@ import consulo.util.lang.ObjectUtil; import consulo.virtualFileSystem.VirtualFile; -import javax.annotation.Nonnull; import java.util.BitSet; import java.util.Collections; import java.util.List; @@ -53,24 +52,20 @@ private static class MethodInferenceData { new MethodInferenceData(Mutability.UNKNOWN, Nullability.UNKNOWN, Collections.emptyList(), false, new BitSet()); final - @Nonnull Mutability myMutability; final - @Nonnull Nullability myNullability; final - @Nonnull List myContracts; final boolean myPure; final - @Nonnull BitSet myNotNullParameters; - MethodInferenceData(@Nonnull Mutability mutability, - @Nonnull Nullability nullability, - @Nonnull List contracts, + MethodInferenceData(Mutability mutability, + Nullability nullability, + List contracts, boolean pure, - @Nonnull BitSet parameters) { + BitSet parameters) { myMutability = mutability; myNullability = nullability; myContracts = contracts; @@ -79,7 +74,6 @@ private static class MethodInferenceData { } } - @Nonnull private static MethodInferenceData infer(PsiMethodImpl method) { InferenceMode mode = getInferenceMode(method); if (mode == InferenceMode.DISABLED || @@ -116,7 +110,6 @@ private static MethodInferenceData infer(PsiMethodImpl method) { return new MethodInferenceData(mutability, nullability, contracts, pure, notNullParameters); } - @Nonnull private static Nullability findNullability(PsiMethodImpl method, MethodData data) { PsiType type = method.getReturnType(); NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(method.getProject()).findExplicitNullability(method); @@ -135,8 +128,7 @@ private static Nullability findNullability(PsiMethodImpl method, MethodData data return nullability == null ? Nullability.UNKNOWN : nullability; } - @Nonnull - private static Mutability findMutability(@Nonnull PsiMethodImpl method, @Nonnull MethodData data) { + private static Mutability findMutability(PsiMethodImpl method, MethodData data) { PsiType type = method.getReturnType(); if (type == null || ClassUtils.isImmutable(type, false)) { return Mutability.UNKNOWN; @@ -150,7 +142,7 @@ private static Mutability findMutability(@Nonnull PsiMethodImpl method, @Nonnull return mutability == null ? Mutability.UNKNOWN : mutability; } - private static boolean findPurity(@Nonnull PsiMethodImpl method, @Nonnull MethodData data) { + private static boolean findPurity(PsiMethodImpl method, MethodData data) { PurityInferenceResult result = data.getPurity(); if (result == null) { return false; @@ -158,11 +150,10 @@ private static boolean findPurity(@Nonnull PsiMethodImpl method, @Nonnull Method return Boolean.TRUE.equals(RecursionManager.doPreventingRecursion(method, true, () -> result.isPure(method, data.methodBody(method)))); } - @Nonnull - private static List findContracts(@Nonnull PsiMethodImpl method, - @Nonnull MethodData data, - @Nonnull Nullability nullability, - @Nonnull IntPredicate notNullParameter) { + private static List findContracts(PsiMethodImpl method, + MethodData data, + Nullability nullability, + IntPredicate notNullParameter) { PsiAnnotation explicitContract = AnnotationUtil.findAnnotationInHierarchy( method, Collections.singleton(JavaMethodContractUtil.ORG_JETBRAINS_ANNOTATIONS_CONTRACT), true); if (explicitContract != null) { @@ -185,10 +176,9 @@ private static List findContracts(@Nonnull PsiMethodImpl return postProcessContracts(contracts, method, nullability, notNullParameter); } - @Nonnull - private static List postProcessContracts(List contracts, @Nonnull PsiMethod method, - @Nonnull Nullability nullability, - @Nonnull IntPredicate notNullParameter) { + private static List postProcessContracts(List contracts, PsiMethod method, + Nullability nullability, + IntPredicate notNullParameter) { final PsiType returnType = method.getReturnType(); if (returnType != null && !(returnType instanceof PsiPrimitiveType)) { contracts = boxReturnValues(contracts); @@ -215,7 +205,6 @@ private static List postProcessContracts(List inferContracts(@Nonnull PsiMethodImpl method) { + public static List inferContracts(PsiMethodImpl method) { return getInferenceData(method).myContracts; } @@ -292,11 +278,10 @@ public static List inferContracts(@Nonnull PsiMethodImpl * @param method method to analyze * @return true if method was inferred to be pure; false if method is not pure or cannot be analyzed */ - public static boolean inferPurity(@Nonnull PsiMethodImpl method) { + public static boolean inferPurity(PsiMethodImpl method) { return getInferenceData(method).myPure; } - @Nonnull private static List boxReturnValues(List contracts) { return ContainerUtil.mapNotNull(contracts, contract -> { if (contract.getReturnValue().isBoolean()) { @@ -306,7 +291,7 @@ private static List boxReturnValues(List toContracts(PsiMethod method, Supplier body) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodCallContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodCallContract.java index 802cce5664..ef620af113 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodCallContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodCallContract.java @@ -25,7 +25,6 @@ import com.intellij.java.language.psi.PsiMethodCallExpression; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -55,7 +54,6 @@ public List> getStates() return states; } - @Nonnull @Override public List toContracts(PsiMethod method, Supplier body) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodData.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodData.java index 1ebaacec78..e49ca19fc6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodData.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodData.java @@ -27,8 +27,7 @@ import consulo.language.psi.stub.gist.GistManager; import consulo.language.util.IncorrectOperationException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.BitSet; import java.util.List; import java.util.Objects; @@ -81,7 +80,6 @@ public int getBodyEnd() { return bodyEnd; } - @Nonnull public Supplier methodBody(PsiMethodImpl method) { return () -> { PsiMethodStub stub = method.getStub(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodDataExternalizer.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodDataExternalizer.java index c44126327e..e4047f2b27 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodDataExternalizer.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodDataExternalizer.java @@ -23,7 +23,6 @@ import consulo.index.io.data.DataExternalizer; import consulo.index.io.data.DataInputOutputUtil; -import javax.annotation.Nonnull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -61,7 +60,6 @@ public Map read(DataInput in) throws IOException return map; } - @Nonnull private static MethodData readMethod(DataInput in) throws IOException { MethodReturnInferenceResult nullity = DataInputOutputUtil.readNullable(in, () -> readNullity(in)); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceResult.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceResult.java index cfab3672fa..2e4781b407 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceResult.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceResult.java @@ -10,7 +10,6 @@ import consulo.util.collection.ContainerUtil; import com.siyeh.ig.psiutils.ClassUtils; -import javax.annotation.Nonnull; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -29,13 +28,11 @@ public Predefined(Nullability value) this.value = value; } - @Nonnull public Nullability getValue() { return value; } - @Nonnull @Override public Nullability getNullability(PsiMethod method, Supplier body) { @@ -85,7 +82,6 @@ public List getDelegateCalls() return delegateCalls; } - @Nonnull @Override public Nullability getNullability(PsiMethod method, Supplier body) { @@ -100,7 +96,6 @@ else if(ContainerUtil.all(delegateCalls, expressionRange -> isNotNullCall(expres return Nullability.UNKNOWN; } - @Nonnull @Override public Mutability getMutability(PsiMethod method, Supplier body) { @@ -168,10 +163,8 @@ public int hashCode() } } - @Nonnull Nullability getNullability(PsiMethod method, Supplier body); - @Nonnull default Mutability getMutability(PsiMethod method, Supplier body) { return Mutability.UNKNOWN; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceVisitor.java index 57f53f543b..1242779cb4 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/MethodReturnInferenceVisitor.java @@ -11,8 +11,7 @@ import consulo.language.ast.IElementType; import consulo.language.ast.TokenSet; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; @@ -72,7 +71,6 @@ else if(type == METHOD_CALL_EXPRESSION) } } - @Nonnull private ReturnValue getExpressionValue(@Nullable LighterASTNode expr) { expr = skipParenthesesCastsDown(tree, expr); @@ -119,7 +117,6 @@ else if(type == REFERENCE_EXPRESSION) return ReturnValue.UNKNOWN; } - @Nonnull private ReturnValue findVariableValue(LighterASTNode expr, LighterASTNode target) { LighterASTNode parent; @@ -165,7 +162,6 @@ private ReturnValue findVariableValue(LighterASTNode expr, LighterASTNode target return ReturnValue.UNKNOWN; } - @Nonnull private ReturnValue findValueBeforeStatement(LighterASTNode statement, LighterASTNode target) { LighterASTNode parent = tree.getParent(statement); @@ -504,7 +500,7 @@ private boolean isNullCheck(@Nullable LighterASTNode expr, LighterASTNode var, b return !negated && type == INSTANCE_OF_EXPRESSION && isReferenceToLocal(findExpressionChild(tree, expr), var); } - private boolean isReferenceToLocal(@Nullable LighterASTNode operand, @Nonnull LighterASTNode var) + private boolean isReferenceToLocal(@Nullable LighterASTNode operand, LighterASTNode var) { // We do not actually resolve here as this method is guaranteed to be called within the scope of var, // thus simply comparing names is enough @@ -515,7 +511,7 @@ private boolean isReferenceToLocal(@Nullable LighterASTNode operand, @Nonnull Li Objects.equals(getNameIdentifierText(tree, operand), getNameIdentifierText(tree, var)); } - private boolean isNullLiteral(@Nonnull LighterASTNode value) + private boolean isNullLiteral(LighterASTNode value) { return value.getTokenType() == LITERAL_EXPRESSION && tree.getChildren(value).get(0).getTokenType() == JavaTokenType.NULL_KEYWORD; } @@ -561,18 +557,16 @@ private static class ReturnValue static final ReturnValue NOT_NULL = new ReturnValue(EnumSet.of(Nullability.NOT_NULL), null, Collections.emptyList()); final - @Nonnull EnumSet myNullability; // empty = top final @Nullable String myCalledMethod; // null = top; empty = bottom final - @Nonnull List myRanges; - private ReturnValue(@Nonnull EnumSet nullability, + private ReturnValue(EnumSet nullability, @Nullable String method, - @Nonnull List ranges) + List ranges) { myNullability = nullability; myCalledMethod = method; @@ -591,12 +585,12 @@ public ReturnValue dropNullable() return new ReturnValue(copy, myCalledMethod, myRanges); } - static ReturnValue delegate(@Nonnull String method, @Nonnull ExpressionRange range) + static ReturnValue delegate(String method, ExpressionRange range) { return new ReturnValue(EnumSet.noneOf(Nullability.class), method, Collections.singletonList(range)); } - static ReturnValue merge(@Nonnull ReturnValue left, @Nonnull ReturnValue right) + static ReturnValue merge(ReturnValue left, ReturnValue right) { EnumSet nullability = EnumSet.copyOf(left.myNullability); nullability.addAll(right.myNullability); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/NegatingContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/NegatingContract.java index 3ec7f5344e..e388518e28 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/NegatingContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/NegatingContract.java @@ -22,7 +22,6 @@ import com.intellij.java.language.psi.PsiMethod; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -44,7 +43,6 @@ public PreContract getNegated() return negated; } - @Nonnull @Override public List toContracts(PsiMethod method, Supplier body) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java index 00a693e634..4313adb78e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/ParameterNullityInferenceKt.java @@ -6,9 +6,8 @@ import com.intellij.java.language.psi.JavaTokenType; import consulo.language.ast.*; import consulo.util.collection.ContainerUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.language.impl.psi.impl.source.tree.JavaElementType.*; @@ -17,194 +16,232 @@ * from kotlin */ class ParameterNullityInferenceKt { - static BitSet inferNotNullParameters(LighterAST tree, LighterASTNode method, List statements) { - List parameterNames = getParameterNames(tree, method); - return inferNotNullParameters(tree, parameterNames, statements); - } - - private static BitSet inferNotNullParameters(LighterAST tree, List parameterNames, List statements) { - Set canBeNulls = new HashSet<>(ContainerUtil.mapNotNull(parameterNames, it -> it)); - if (canBeNulls.isEmpty()) { - return new BitSet(); + static BitSet inferNotNullParameters(LighterAST tree, LighterASTNode method, List statements) { + List parameterNames = getParameterNames(tree, method); + return inferNotNullParameters(tree, parameterNames, statements); } - Set notNulls = new HashSet<>(); - Deque queue = new ArrayDeque<>(statements); - while (!queue.isEmpty() && !canBeNulls.isEmpty()) { - LighterASTNode element = queue.removeFirst(); - IElementType type = element.getTokenType(); - - if (type == CONDITIONAL_EXPRESSION || type == EXPRESSION_STATEMENT) { - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else if (type == RETURN_STATEMENT) { - queue.clear(); - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else if (type == FOR_STATEMENT) { - LighterASTNode condition = JavaLightTreeUtil.findExpressionChild(tree, element); - queue.clear(); - if (condition != null) { - queue.addFirst(condition); - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, ElementType.JAVA_STATEMENT_BIT_SET); - if (let != null) { - queue.addFirst(let); - } - } else { - // no condition == endless loop: we may analyze body (at least until break/return/if/etc.) - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } else if (type == WHILE_STATEMENT) { - queue.clear(); - LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); - if (expression != null && expression.getTokenType() == LITERAL_EXPRESSION && LightTreeUtil.firstChildOfType(tree, expression, JavaTokenType.TRUE_KEYWORD) != null) { - // while(true) == endless loop: we may analyze body (at least until break/return/if/etc.) - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else { - dereference(tree, expression, canBeNulls, notNulls, queue); - } - } else if (type == FOREACH_STATEMENT || type == SWITCH_STATEMENT || type == IF_STATEMENT || type == THROW_STATEMENT) { - queue.clear(); - LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, expression, canBeNulls, notNulls, queue); - } else if (type == BINARY_EXPRESSION || type == POLYADIC_EXPRESSION) { - if (LightTreeUtil.firstChildOfType(tree, element, TokenSet.create(JavaTokenType.ANDAND, JavaTokenType.OROR)) != null) { - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); - } - } else { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } else if (type == EMPTY_STATEMENT || type == ASSERT_STATEMENT || type == DO_WHILE_STATEMENT || type == DECLARATION_STATEMENT || type == BLOCK_STATEMENT) { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else if (type == SYNCHRONIZED_STATEMENT) { - LighterASTNode sync = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, sync, canBeNulls, notNulls, queue); - - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); - if (let != null) { - queue.addFirst(let); + private static BitSet inferNotNullParameters(LighterAST tree, List parameterNames, List statements) { + Set canBeNulls = new HashSet<>(ContainerUtil.mapNotNull(parameterNames, it -> it)); + if (canBeNulls.isEmpty()) { + return new BitSet(); } - } else if (type == FIELD || type == PARAMETER || type == LOCAL_VARIABLE) { - canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, element)); - LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); - if (let != null) { - queue.addFirst(let); + Set notNulls = new HashSet<>(); + Deque queue = new ArrayDeque<>(statements); + + while (!queue.isEmpty() && !canBeNulls.isEmpty()) { + LighterASTNode element = queue.removeFirst(); + IElementType type = element.getTokenType(); + + if (type == CONDITIONAL_EXPRESSION || type == EXPRESSION_STATEMENT) { + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == RETURN_STATEMENT) { + queue.clear(); + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == FOR_STATEMENT) { + LighterASTNode condition = JavaLightTreeUtil.findExpressionChild(tree, element); + queue.clear(); + if (condition != null) { + queue.addFirst(condition); + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, ElementType.JAVA_STATEMENT_BIT_SET); + if (let != null) { + queue.addFirst(let); + } + } + else { + // no condition == endless loop: we may analyze body (at least until break/return/if/etc.) + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + } + else if (type == WHILE_STATEMENT) { + queue.clear(); + LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); + if (expression != null && expression.getTokenType() == LITERAL_EXPRESSION + && LightTreeUtil.firstChildOfType(tree, expression, JavaTokenType.TRUE_KEYWORD) != null) { + // while(true) == endless loop: we may analyze body (at least until break/return/if/etc.) + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else { + dereference(tree, expression, canBeNulls, notNulls, queue); + } + } + else if (type == FOREACH_STATEMENT || type == SWITCH_STATEMENT || type == IF_STATEMENT || type == THROW_STATEMENT) { + queue.clear(); + LighterASTNode expression = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, expression, canBeNulls, notNulls, queue); + } + else if (type == BINARY_EXPRESSION || type == POLYADIC_EXPRESSION) { + if (LightTreeUtil.firstChildOfType(tree, element, TokenSet.create(JavaTokenType.ANDAND, JavaTokenType.OROR)) != null) { + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + } + else if (type == EMPTY_STATEMENT || type == ASSERT_STATEMENT || type == DO_WHILE_STATEMENT || type == DECLARATION_STATEMENT || type == BLOCK_STATEMENT) { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else if (type == SYNCHRONIZED_STATEMENT) { + LighterASTNode sync = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, sync, canBeNulls, notNulls, queue); + + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == FIELD || type == PARAMETER || type == LOCAL_VARIABLE) { + canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, element)); + LighterASTNode let = JavaLightTreeUtil.findExpressionChild(tree, element); + if (let != null) { + queue.addFirst(let); + } + } + else if (type == EXPRESSION_LIST) { + List children = JavaLightTreeUtil.getExpressionChildren(tree, element); + // When parameter is passed to another method, that method may have "null -> fail" contract, + // so without knowing this we cannot continue inference for the parameter + children.forEach(it -> ignore(tree, it, canBeNulls)); + ContainerUtil.reverse(children).forEach(queue::addFirst); + } + else if (type == ASSIGNMENT_EXPRESSION) { + LighterASTNode lvalue = JavaLightTreeUtil.findExpressionChild(tree, element); + ignore(tree, lvalue, canBeNulls); + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } + else if (type == ARRAY_ACCESS_EXPRESSION) { + JavaLightTreeUtil.getExpressionChildren(tree, element).forEach(it -> dereference(tree, it, canBeNulls, notNulls, queue)); + } + else if (type == METHOD_REF_EXPRESSION || type == REFERENCE_EXPRESSION) { + LighterASTNode qualifier = JavaLightTreeUtil.findExpressionChild(tree, element); + dereference(tree, qualifier, canBeNulls, notNulls, queue); + } + else if (type == CLASS || type == METHOD || type == LAMBDA_EXPRESSION) { + // Ignore classes, methods and lambda expression bodies as it's not known whether they will be instantiated/executed. + // For anonymous classes argument list, field initializers and instance initialization sections are checked. + } + else if (type == TRY_STATEMENT) { + queue.clear(); + + List params = ContainerUtil.mapNotNull( + LightTreeUtil.getChildrenOfType(tree, element, CATCH_SECTION), + it -> LightTreeUtil.firstChildOfType(tree, it, PARAMETER) + ); + + List paramTypes = + ContainerUtil.map(params, parameter -> LightTreeUtil.firstChildOfType(tree, parameter, TYPE)); + + boolean canCatchNpe = ContainerUtil.or(paramTypes, it -> canCatchNpe(tree, it)); + if (!canCatchNpe) { + LightTreeUtil.getChildrenOfType(tree, element, RESOURCE_LIST).forEach(queue::addFirst); + + LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); + if (let != null) { + queue.addFirst(let); + } + + // stop analysis after first try as we are not sure how execution goes further: + // whether or not it visit catch blocks, etc. + } + } + else if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type)) { + // Unknown/unprocessed statement: just stop processing the rest of the method + queue.clear(); + } + else { + ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); + } } - } else if (type == EXPRESSION_LIST) { - List children = JavaLightTreeUtil.getExpressionChildren(tree, element); - // When parameter is passed to another method, that method may have "null -> fail" contract, - // so without knowing this we cannot continue inference for the parameter - children.forEach(it -> ignore(tree, it, canBeNulls)); - ContainerUtil.reverse(children).forEach(queue::addFirst); - } else if (type == ASSIGNMENT_EXPRESSION) { - LighterASTNode lvalue = JavaLightTreeUtil.findExpressionChild(tree, element); - ignore(tree, lvalue, canBeNulls); - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } else if (type == ARRAY_ACCESS_EXPRESSION) { - JavaLightTreeUtil.getExpressionChildren(tree, element).forEach(it -> dereference(tree, it, canBeNulls, notNulls, queue)); - } else if (type == METHOD_REF_EXPRESSION || type == REFERENCE_EXPRESSION) { - LighterASTNode qualifier = JavaLightTreeUtil.findExpressionChild(tree, element); - dereference(tree, qualifier, canBeNulls, notNulls, queue); - } else if (type == CLASS || type == METHOD || type == LAMBDA_EXPRESSION) { - // Ignore classes, methods and lambda expression bodies as it's not known whether they will be instantiated/executed. - // For anonymous classes argument list, field initializers and instance initialization sections are checked. - } else if (type == TRY_STATEMENT) { - queue.clear(); - - List params = ContainerUtil.mapNotNull(LightTreeUtil.getChildrenOfType(tree, element, CATCH_SECTION), it -> LightTreeUtil.firstChildOfType(tree, it, PARAMETER)); - - List paramTypes = ContainerUtil.map(params, parameter -> LightTreeUtil.firstChildOfType(tree, parameter, TYPE)); - - boolean canCatchNpe = ContainerUtil.or(paramTypes, it -> canCatchNpe(tree, it)); - if (!canCatchNpe) { - LightTreeUtil.getChildrenOfType(tree, element, RESOURCE_LIST).forEach(queue::addFirst); - - LighterASTNode let = LightTreeUtil.firstChildOfType(tree, element, CODE_BLOCK); - if (let != null) { - queue.addFirst(let); - } - - // stop analysis after first try as we are not sure how execution goes further: - // whether or not it visit catch blocks, etc. - } - } else if (ElementType.JAVA_STATEMENT_BIT_SET.contains(type)) { - // Unknown/unprocessed statement: just stop processing the rest of the method - queue.clear(); - } else { - ContainerUtil.reverse(tree.getChildren(element)).forEach(queue::addFirst); - } - } - - BitSet notNullParameters = new BitSet(); - for (int index = 0; index < parameterNames.size(); index++) { - String parameterName = parameterNames.get(index); - - if (notNulls.contains(parameterName)) { - notNullParameters.set(index); - } - } + BitSet notNullParameters = new BitSet(); - return notNullParameters; - } + for (int index = 0; index < parameterNames.size(); index++) { + String parameterName = parameterNames.get(index); - private static final Set NPC_CATCHES = Set.of("Throwable", "Exception", "RuntimeException", "NullPointerException", - CommonClassNames.JAVA_LANG_THROWABLE, CommonClassNames.JAVA_LANG_EXCEPTION, - CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION); + if (notNulls.contains(parameterName)) { + notNullParameters.set(index); + } + } - private static boolean canCatchNpe(LighterAST tree, @Nullable LighterASTNode type) { - if (type == null) { - return false; - } - LighterASTNode codeRef = LightTreeUtil.firstChildOfType(tree, type, JAVA_CODE_REFERENCE); - String name = JavaLightTreeUtil.getNameIdentifierText(tree, codeRef); - if (name == null) { - // Multicatch - return ContainerUtil.or(LightTreeUtil.getChildrenOfType(tree, type, TYPE), it -> canCatchNpe(tree, it)); + return notNullParameters; } - return NPC_CATCHES.contains(name); - } - private static void ignore(LighterAST tree, @Nullable LighterASTNode expression, Set canBeNulls) { - if (expression != null && expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { - canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, expression)); + private static final Set NPC_CATCHES = Set.of( + "Throwable", + "Exception", + "RuntimeException", + "NullPointerException", + CommonClassNames.JAVA_LANG_THROWABLE, + CommonClassNames.JAVA_LANG_EXCEPTION, + CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, + CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION + ); + + private static boolean canCatchNpe(LighterAST tree, @Nullable LighterASTNode type) { + if (type == null) { + return false; + } + LighterASTNode codeRef = LightTreeUtil.firstChildOfType(tree, type, JAVA_CODE_REFERENCE); + String name = JavaLightTreeUtil.getNameIdentifierText(tree, codeRef); + if (name == null) { + // Multicatch + return ContainerUtil.or(LightTreeUtil.getChildrenOfType(tree, type, TYPE), it -> canCatchNpe(tree, it)); + } + return NPC_CATCHES.contains(name); } - } - private static void dereference(LighterAST tree, LighterASTNode expression, Set canBeNulls, Set notNulls, Deque queue) { - if (expression == null) { - return; + private static void ignore(LighterAST tree, @Nullable LighterASTNode expression, Set canBeNulls) { + if (expression != null && expression.getTokenType() == REFERENCE_EXPRESSION + && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { + canBeNulls.remove(JavaLightTreeUtil.getNameIdentifierText(tree, expression)); + } } - if (expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { - String name = JavaLightTreeUtil.getNameIdentifierText(tree, expression); - if (canBeNulls.remove(name)) { - notNulls.add(name); - } - } else { - queue.addFirst(expression); + private static void dereference( + LighterAST tree, + LighterASTNode expression, + Set canBeNulls, + Set notNulls, + Deque queue + ) { + if (expression == null) { + return; + } + + if (expression.getTokenType() == REFERENCE_EXPRESSION && JavaLightTreeUtil.findExpressionChild(tree, expression) == null) { + String name = JavaLightTreeUtil.getNameIdentifierText(tree, expression); + if (canBeNulls.remove(name)) { + notNulls.add(name); + } + } + else { + queue.addFirst(expression); + } } - } - @Nonnull - private static List getParameterNames(LighterAST tree, LighterASTNode method) { - LighterASTNode parameterList = LightTreeUtil.firstChildOfType(tree, method, PARAMETER_LIST); - if (parameterList == null) { - return Collections.emptyList(); + private static List getParameterNames(LighterAST tree, LighterASTNode method) { + LighterASTNode parameterList = LightTreeUtil.firstChildOfType(tree, method, PARAMETER_LIST); + if (parameterList == null) { + return Collections.emptyList(); + } + List parameters = LightTreeUtil.getChildrenOfType(tree, parameterList, PARAMETER); + + return ContainerUtil.map(parameters, it -> { + if (LightTreeUtil.firstChildOfType(tree, it, ElementType.PRIMITIVE_TYPE_BIT_SET) != null) { + return null; + } + return JavaLightTreeUtil.getNameIdentifierText(tree, it); + }); } - List parameters = LightTreeUtil.getChildrenOfType(tree, parameterList, PARAMETER); - - return ContainerUtil.map(parameters, it -> { - if (LightTreeUtil.firstChildOfType(tree, it, ElementType.PRIMITIVE_TYPE_BIT_SET) != null) { - return null; - } - return JavaLightTreeUtil.getNameIdentifierText(tree, it); - }); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PreContract.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PreContract.java index 0bd26d3f79..aff4cc9030 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PreContract.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PreContract.java @@ -20,8 +20,7 @@ import com.intellij.java.language.psi.PsiCodeBlock; import com.intellij.java.language.psi.PsiMethod; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.function.Supplier; @@ -30,7 +29,6 @@ */ interface PreContract { - @Nonnull List toContracts(PsiMethod method, Supplier body); @Nullable diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java index 6b30db69d0..9d2dc4fa87 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceResult.java @@ -25,130 +25,123 @@ import consulo.language.psi.scope.LocalSearchScope; import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.PsiTreeUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; +import java.util.Objects; import java.util.function.Supplier; /** * from kotlin */ class PurityInferenceResult { - private List mutableRefs; - @Nullable - private ExpressionRange singleCall; - - PurityInferenceResult(List mutableRefs, @Nullable ExpressionRange singleCall) { - this.mutableRefs = mutableRefs; - this.singleCall = singleCall; - } - - public boolean isPure(PsiMethod method, Supplier body) { - return !mutatesNonLocals(method, body) && callsOnlyPureMethods(method, body); - } - - public List getMutableRefs() { - return mutableRefs; - } - - @Nullable - public ExpressionRange getSingleCall() { - return singleCall; - } - - @RequiredReadAction - private boolean mutatesNonLocals(PsiMethod method, Supplier body) { - for (ExpressionRange range : mutableRefs) { - if (!isLocalVarReference(range.restoreExpression(body.get()), method)) { - return true; - } - } - return false; - } + private List mutableRefs; + @Nullable + private ExpressionRange singleCall; - @RequiredReadAction - private boolean callsOnlyPureMethods(PsiMethod currentMethod, Supplier body) { - if (singleCall == null) { - return true; + PurityInferenceResult(List mutableRefs, @Nullable ExpressionRange singleCall) { + this.mutableRefs = mutableRefs; + this.singleCall = singleCall; } - PsiCall psiCall = (PsiCall) singleCall.restoreExpression(body.get()); - assert psiCall != null; - PsiMethod method = psiCall.resolveMethod(); - if (method != null) { - return method.equals(currentMethod) || JavaMethodContractUtil.isPure(method); - } else if (psiCall instanceof PsiNewExpression && psiCall.getArgumentList() != null && psiCall.getArgumentList().getExpressionCount() == 0) { - - PsiJavaCodeReferenceElement classOrAnonymousClassReference = ((PsiNewExpression) psiCall).getClassOrAnonymousClassReference(); - PsiElement resolved = classOrAnonymousClassReference == null ? null : classOrAnonymousClassReference.resolve(); - - PsiClass psiClass = resolved instanceof PsiClass ? (PsiClass) resolved : null; - - if (psiClass != null) { - PsiClass superClass = psiClass.getSuperClass(); - return superClass == null || CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()); - } + + @RequiredReadAction + public boolean isPure(PsiMethod method, Supplier body) { + return !mutatesNonLocals(method, body) && callsOnlyPureMethods(method, body); } - return false; - } - - @RequiredReadAction - private boolean isLocalVarReference(PsiExpression expression, PsiMethod scope) { - if (expression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) expression).resolve(); - return resolve instanceof PsiLocalVariable || resolve instanceof PsiParameter; - } else if (expression instanceof PsiArrayAccessExpression) { - PsiExpression arrayExpression = ((PsiArrayAccessExpression) expression).getArrayExpression(); - if (arrayExpression instanceof PsiReferenceExpression) { - PsiElement resolve = ((PsiReferenceExpression) arrayExpression).resolve(); - return resolve instanceof PsiLocalVariable && isLocallyCreatedArray(scope, (PsiLocalVariable) resolve); - } + public List getMutableRefs() { + return mutableRefs; } - return false; - } - private boolean isLocallyCreatedArray(PsiMethod scope, PsiLocalVariable target) { - PsiExpression initializer = target.getInitializer(); - if (initializer != null & !(initializer instanceof PsiNewExpression)) { - return false; + @Nullable + public ExpressionRange getSingleCall() { + return singleCall; } - for (PsiReference ref : ReferencesSearch.search(target, new LocalSearchScope(scope)).findAll()) { - if (ref instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) ref)) { - PsiAssignmentExpression assign = PsiTreeUtil.getParentOfType((PsiReferenceExpression) ref, PsiAssignmentExpression.class); - if (assign == null || !(assign.getRExpression() instanceof PsiNewExpression)) { - return false; + @RequiredReadAction + private boolean mutatesNonLocals(PsiMethod method, Supplier body) { + for (ExpressionRange range : mutableRefs) { + if (!isLocalVarReference(range.restoreExpression(body.get()), method)) { + return true; + } } - } + return false; } - return true; - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; + @RequiredReadAction + private boolean callsOnlyPureMethods(PsiMethod currentMethod, Supplier body) { + if (singleCall == null) { + return true; + } + PsiCall psiCall = (PsiCall)singleCall.restoreExpression(body.get()); + assert psiCall != null; + PsiMethod method = psiCall.resolveMethod(); + if (method != null) { + return method.equals(currentMethod) || JavaMethodContractUtil.isPure(method); + } + else if (psiCall instanceof PsiNewExpression newExpr && psiCall.getArgumentList() != null + && psiCall.getArgumentList().getExpressionCount() == 0) { + + PsiJavaCodeReferenceElement classOrAnonymousClassReference = newExpr.getClassOrAnonymousClassReference(); + if (classOrAnonymousClassReference != null + && classOrAnonymousClassReference.resolve() instanceof PsiClass psiClass) { + PsiClass superClass = psiClass.getSuperClass(); + return superClass == null || CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()); + } + } + + return false; } - if (o == null || getClass() != o.getClass()) { - return false; + + @RequiredReadAction + private boolean isLocalVarReference(PsiExpression expression, PsiMethod scope) { + if (expression instanceof PsiReferenceExpression refExpr) { + PsiElement resolve = refExpr.resolve(); + return resolve instanceof PsiLocalVariable || resolve instanceof PsiParameter; + } + else if (expression instanceof PsiArrayAccessExpression arrayAccess) { + if (arrayAccess.getArrayExpression() instanceof PsiReferenceExpression arrayRefExpr) { + return arrayRefExpr.resolve() instanceof PsiLocalVariable localVar && isLocallyCreatedArray(scope, localVar); + } + } + return false; } - PurityInferenceResult that = (PurityInferenceResult) o; + private boolean isLocallyCreatedArray(PsiMethod scope, PsiLocalVariable target) { + PsiExpression initializer = target.getInitializer(); + if (initializer != null & !(initializer instanceof PsiNewExpression)) { + return false; + } - if (mutableRefs != null ? !mutableRefs.equals(that.mutableRefs) : that.mutableRefs != null) { - return false; - } - if (singleCall != null ? !singleCall.equals(that.singleCall) : that.singleCall != null) { - return false; + for (PsiReference ref : ReferencesSearch.search(target, new LocalSearchScope(scope)).findAll()) { + if (ref instanceof PsiReferenceExpression refExpr && PsiUtil.isAccessedForWriting(refExpr)) { + PsiAssignmentExpression assign = PsiTreeUtil.getParentOfType(refExpr, PsiAssignmentExpression.class); + if (assign == null || !(assign.getRExpression() instanceof PsiNewExpression)) { + return false; + } + } + } + return true; } - return true; - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PurityInferenceResult that = (PurityInferenceResult)o; - @Override - public int hashCode() { - int result = mutableRefs != null ? mutableRefs.hashCode() : 0; - result = 31 * result + (singleCall != null ? singleCall.hashCode() : 0); - return result; - } + return Objects.equals(mutableRefs, that.mutableRefs) + && Objects.equals(singleCall, that.singleCall); + } + + @Override + public int hashCode() { + int result = mutableRefs != null ? mutableRefs.hashCode() : 0; + return 31 * result + (singleCall != null ? singleCall.hashCode() : 0); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceVisitor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceVisitor.java index 5276148df3..956b8e6c0e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceVisitor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/PurityInferenceVisitor.java @@ -9,8 +9,7 @@ import consulo.language.ast.LightTreeUtil; import consulo.language.ast.IElementType; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -103,7 +102,7 @@ private void addMutation(LighterASTNode mutated) mutatedRefs.add(mutated); } - private boolean isCall(@Nonnull LighterASTNode element, IElementType type) + private boolean isCall(LighterASTNode element, IElementType type) { return type == NEW_EXPRESSION && (LightTreeUtil.firstChildOfType(tree, element, EXPRESSION_LIST) != null || @@ -111,7 +110,7 @@ private boolean isCall(@Nonnull LighterASTNode element, IElementType type) type == METHOD_CALL_EXPRESSION; } - private boolean isMutatingOperation(@Nonnull LighterASTNode element) + private boolean isMutatingOperation(LighterASTNode element) { return LightTreeUtil.firstChildOfType(tree, element, JavaTokenType.PLUSPLUS) != null || LightTreeUtil.firstChildOfType(tree, element, JavaTokenType.MINUSMINUS) != null; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/SideEffectFilter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/SideEffectFilter.java index 2635ed8c86..14f548a04a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/SideEffectFilter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inference/SideEffectFilter.java @@ -20,10 +20,9 @@ import com.intellij.java.language.psi.PsiCodeBlock; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethod; -import com.siyeh.ig.psiutils.SideEffectChecker; +import com.intellij.java.analysis.impl.codeInspection.SideEffectChecker; import consulo.annotation.access.RequiredReadAction; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -53,7 +52,6 @@ public List getContracts() return contracts; } - @Nonnull @Override @RequiredReadAction public List toContracts(PsiMethod method, Supplier body) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java index 4346b8848d..4604c22068 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssertAllInliner.java @@ -10,8 +10,6 @@ import com.siyeh.ig.psiutils.MethodCallUtils; import com.siyeh.ig.psiutils.TypeUtils; -import javax.annotation.Nonnull; - import static com.siyeh.ig.callMatcher.CallMatcher.anyOf; import static com.siyeh.ig.callMatcher.CallMatcher.staticCall; @@ -19,49 +17,49 @@ * JUnit5 Assertions.assertAll */ public class AssertAllInliner implements CallInliner { - private static final CallMatcher ASSERT_ALL = - anyOf( - staticCall("org.junit.jupiter.api.Assertions", "assertAll").parameterTypes("org.junit.jupiter.api.function.Executable..."), - staticCall("org.junit.jupiter.api.Assertions", "assertAll") - .parameterTypes(CommonClassNames.JAVA_LANG_STRING, "org.junit.jupiter.api.function.Executable...") - ); + private static final CallMatcher ASSERT_ALL = anyOf( + staticCall("org.junit.jupiter.api.Assertions", "assertAll") + .parameterTypes("org.junit.jupiter.api.function.Executable..."), + staticCall("org.junit.jupiter.api.Assertions", "assertAll") + .parameterTypes(CommonClassNames.JAVA_LANG_STRING, "org.junit.jupiter.api.function.Executable...") + ); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { - if (!ASSERT_ALL.matches(call) || !MethodCallUtils.isVarArgCall(call)) { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - for (int i = 0; i < args.length; i++) { - PsiExpression arg = args[i]; - if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { - builder.pushExpression(arg, NullabilityProblemKind.noProblem).pop(); - } else { - builder.evaluateFunction(arg); - } - } - DfaVariableValue result = builder.createTempVariable(PsiType.BOOLEAN); - builder.assignAndPop(result, DfTypes.FALSE); - for (int i = 0; i < args.length; i++) { - PsiExpression arg = args[i]; - if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { - continue; - } - builder - .doTry(call) - .invokeFunction(0, arg) - .catchAll() - .assignAndPop(result, DfTypes.TRUE) - .end(); + @Override + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { + if (!ASSERT_ALL.matches(call) || !MethodCallUtils.isVarArgCall(call)) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + for (int i = 0; i < args.length; i++) { + PsiExpression arg = args[i]; + if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { + builder.pushExpression(arg, NullabilityProblemKind.noProblem).pop(); + } + else { + builder.evaluateFunction(arg); + } + } + DfaVariableValue result = builder.createTempVariable(PsiType.BOOLEAN); + builder.assignAndPop(result, DfTypes.FALSE); + for (int i = 0; i < args.length; i++) { + PsiExpression arg = args[i]; + if (i == 0 && TypeUtils.isJavaLangString(arg.getType())) { + continue; + } + builder.doTry(call) + .invokeFunction(0, arg) + .catchAll() + .assignAndPop(result, DfTypes.TRUE) + .end(); + } + PsiType throwable = JavaPsiFacade.getElementFactory(call.getProject()) + .createTypeByFQClassName("org.opentest4j.MultipleFailuresError", call.getResolveScope()); + builder.push(result) + .ifConditionIs(true) + .doThrow(throwable) + .end() + .pushUnknown(); // void method result + return true; } - PsiType throwable = JavaPsiFacade.getElementFactory(call.getProject()) - .createTypeByFQClassName("org.opentest4j.MultipleFailuresError", call.getResolveScope()); - builder.push(result) - .ifConditionIs(true) - .doThrow(throwable) - .end() - .pushUnknown(); // void method result - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssumeInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssumeInliner.java index 7e4419e53d..4cdcaf15f8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssumeInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/AssumeInliner.java @@ -7,7 +7,6 @@ import com.intellij.java.language.psi.PsiMethodCallExpression; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.MethodCallUtils; -import javax.annotation.Nonnull; /** * JUnit4 Assume.assumeNotNull is a vararg method and each passed null will make the call failing @@ -17,7 +16,7 @@ public class AssumeInliner implements CallInliner private static final CallMatcher ASSUME_NOT_NULL = CallMatcher.staticCall("org.junit.Assume", "assumeNotNull"); @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { if(ASSUME_NOT_NULL.test(call) && MethodCallUtils.isVarArgCall(call)) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/BoxingInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/BoxingInliner.java index 758928c848..0c1812a9ca 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/BoxingInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/BoxingInliner.java @@ -8,7 +8,6 @@ import com.intellij.java.language.psi.PsiType; import com.siyeh.ig.callMatcher.CallMatcher; -import javax.annotation.Nonnull; import static com.intellij.java.language.psi.CommonClassNames.*; @@ -25,7 +24,7 @@ public class BoxingInliner implements CallInliner { ); @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { if (BOXING_CALL.test(call)) { PsiExpression arg = call.getArgumentList().getExpressions()[0]; PsiType type = PsiPrimitiveType.getUnboxedType(call.getType()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CallInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CallInliner.java index a5d305cb9e..3e5ae6aec3 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CallInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CallInliner.java @@ -18,7 +18,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.CFGBuilder; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; -import javax.annotation.Nonnull; /** * A CallInliner can recognize specific method calls and inline their implementation into current CFG @@ -34,13 +33,13 @@ public interface CallInliner * @return true if inlining is successful. In this case subsequent inliners are skipped and default processing is omitted. * If false is returned, inliner must not emit any instructions via builder. */ - boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call); + boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call); /** * @param expression expression to test * @return true if this inliner may add constraints on the precise type of given expression */ - default boolean mayInferPreciseType(@Nonnull PsiExpression expression) + default boolean mayInferPreciseType(PsiExpression expression) { return false; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ClassMethodsInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ClassMethodsInliner.java index bc33189e37..9161c68867 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ClassMethodsInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ClassMethodsInliner.java @@ -7,8 +7,6 @@ import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; -import javax.annotation.Nonnull; - import static com.intellij.java.language.psi.CommonClassNames.JAVA_LANG_CLASS; import static com.intellij.java.language.psi.CommonClassNames.JAVA_LANG_OBJECT; import static consulo.util.lang.ObjectUtil.tryCast; @@ -25,7 +23,7 @@ public class ClassMethodsInliner implements CallInliner { @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); if (qualifier == null) { return false; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CollectionMethodInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CollectionMethodInliner.java index b18f33341b..3ff956c8b9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CollectionMethodInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/CollectionMethodInliner.java @@ -8,7 +8,6 @@ import com.intellij.java.language.psi.PsiMethodCallExpression; import com.siyeh.ig.callMatcher.CallMatcher; -import javax.annotation.Nonnull; import static com.intellij.java.language.psi.CommonClassNames.JAVA_UTIL_COLLECTION; import static com.intellij.java.language.psi.CommonClassNames.JAVA_UTIL_MAP; @@ -20,7 +19,7 @@ public class CollectionMethodInliner implements CallInliner { instanceCall(JAVA_UTIL_MAP, "clear").parameterCount(0)); @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); if (qualifier == null) return false; @@ -31,7 +30,7 @@ public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCall return false; } - private static void inlineClear(@Nonnull CFGBuilder builder, @Nonnull PsiExpression qualifier) { + private static void inlineClear(CFGBuilder builder, PsiExpression qualifier) { DfaValueFactory factory = builder.getFactory(); builder .pushExpression(qualifier) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java index c491cb257f..23bb6bcb9c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/ComparatorModel.java @@ -20,10 +20,9 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; +import consulo.annotation.access.RequiredReadAction; import consulo.util.lang.ObjectUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static com.intellij.java.language.psi.CommonClassNames.JAVA_UTIL_COLLECTIONS; import static com.intellij.java.language.psi.CommonClassNames.JAVA_UTIL_COMPARATOR; @@ -33,139 +32,141 @@ * Simplified model for comparator: does not perform actual comparison, just executes key extractors, etc. */ abstract class ComparatorModel { - private static final CallMatcher KEY_EXTRACTOR = - anyOf(staticCall(JAVA_UTIL_COMPARATOR, "comparing", "comparingInt", "comparingLong", "comparingDouble").parameterCount(1), - staticCall(JAVA_UTIL_COMPARATOR, "comparing").parameterCount(2)); - private static final CallMatcher NULL_HOSTILE = anyOf(staticCall(JAVA_UTIL_COMPARATOR, "naturalOrder", "reverseOrder").parameterCount(0), - staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(0)); - private static final CallMatcher NULL_FRIENDLY = staticCall(JAVA_UTIL_COMPARATOR, "nullsFirst", "nullsLast").parameterCount(1); - private static final CallMatcher REVERSED = instanceCall(JAVA_UTIL_COMPARATOR, "reversed").parameterCount(0); - private static final CallMatcher REVERSE_ORDER = staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(1); - - private final boolean myFailsOnNull; + private static final CallMatcher KEY_EXTRACTOR = anyOf( + staticCall(JAVA_UTIL_COMPARATOR, "comparing", "comparingInt", "comparingLong", "comparingDouble").parameterCount(1), + staticCall(JAVA_UTIL_COMPARATOR, "comparing").parameterCount(2) + ); + private static final CallMatcher NULL_HOSTILE = anyOf( + staticCall(JAVA_UTIL_COMPARATOR, "naturalOrder", "reverseOrder").parameterCount(0), + staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(0) + ); + private static final CallMatcher NULL_FRIENDLY = staticCall(JAVA_UTIL_COMPARATOR, "nullsFirst", "nullsLast").parameterCount(1); + private static final CallMatcher REVERSED = instanceCall(JAVA_UTIL_COMPARATOR, "reversed").parameterCount(0); + private static final CallMatcher REVERSE_ORDER = staticCall(JAVA_UTIL_COLLECTIONS, "reverseOrder").parameterCount(1); - protected ComparatorModel(boolean failsOnNull) { - myFailsOnNull = failsOnNull; - } + private final boolean myFailsOnNull; - abstract void evaluate(CFGBuilder builder); + protected ComparatorModel(boolean failsOnNull) { + myFailsOnNull = failsOnNull; + } - abstract void invoke(CFGBuilder builder); + abstract void evaluate(CFGBuilder builder); - boolean failsOnNull() { - return myFailsOnNull; - } + abstract void invoke(CFGBuilder builder); - private static class NullHostile extends ComparatorModel { - NullHostile() { - super(true); + boolean failsOnNull() { + return myFailsOnNull; } - @Override - void evaluate(CFGBuilder builder) { - } + private static class NullHostile extends ComparatorModel { + NullHostile() { + super(true); + } - @Override - void invoke(CFGBuilder builder) { - builder.pop(); + @Override + void evaluate(CFGBuilder builder) { + } + + @Override + void invoke(CFGBuilder builder) { + builder.pop(); + } } - } - private static class Unknown extends ComparatorModel { - private final PsiExpression myExpression; + private static class Unknown extends ComparatorModel { + private final PsiExpression myExpression; - Unknown(PsiExpression expression) { - super(false); - myExpression = expression; - } + Unknown(PsiExpression expression) { + super(false); + myExpression = expression; + } - @Override - void evaluate(CFGBuilder builder) { - builder.evaluateFunction(myExpression); - } + @Override + void evaluate(CFGBuilder builder) { + builder.evaluateFunction(myExpression); + } - @Override - void invoke(CFGBuilder builder) { - builder.pushUnknown().invokeFunction(2, myExpression).pop(); + @Override + void invoke(CFGBuilder builder) { + builder.pushUnknown().invokeFunction(2, myExpression).pop(); + } } - } - private static class NullFriendly extends ComparatorModel { - private final ComparatorModel myDownstream; + private static class NullFriendly extends ComparatorModel { + private final ComparatorModel myDownstream; - NullFriendly(ComparatorModel downstream) { - super(false); - myDownstream = downstream; - } + NullFriendly(ComparatorModel downstream) { + super(false); + myDownstream = downstream; + } - @Override - void evaluate(CFGBuilder builder) { - myDownstream.evaluate(builder); - } + @Override + void evaluate(CFGBuilder builder) { + myDownstream.evaluate(builder); + } - @Override - void invoke(CFGBuilder builder) { - builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end(); + @Override + void invoke(CFGBuilder builder) { + builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end(); + } } - } - private static class KeyExtractor extends ComparatorModel { - private final PsiExpression myKeyExtractor; - private final ComparatorModel myDownstream; + private static class KeyExtractor extends ComparatorModel { + private final PsiExpression myKeyExtractor; + private final ComparatorModel myDownstream; - private KeyExtractor(PsiExpression keyExtractor, ComparatorModel downstream) { - super(false); - myKeyExtractor = keyExtractor; - myDownstream = downstream; - } + private KeyExtractor(PsiExpression keyExtractor, ComparatorModel downstream) { + super(false); + myKeyExtractor = keyExtractor; + myDownstream = downstream; + } - @Override - void evaluate(CFGBuilder builder) { - builder.evaluateFunction(myKeyExtractor); - myDownstream.evaluate(builder); - } + @Override + void evaluate(CFGBuilder builder) { + builder.evaluateFunction(myKeyExtractor); + myDownstream.evaluate(builder); + } - @Override - void invoke(CFGBuilder builder) { - builder.invokeFunction(1, myKeyExtractor, myDownstream.myFailsOnNull ? Nullability.NOT_NULL : Nullability.UNKNOWN) - .chain(myDownstream::invoke); + @Override + void invoke(CFGBuilder builder) { + builder.invokeFunction(1, myKeyExtractor, myDownstream.myFailsOnNull ? Nullability.NOT_NULL : Nullability.UNKNOWN) + .chain(myDownstream::invoke); + } } - } - @Nonnull - static ComparatorModel from(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression == null || NULL_HOSTILE.matches(expression)) { - return new NullHostile(); - } - if (expression instanceof PsiReferenceExpression) { - PsiReferenceExpression ref = (PsiReferenceExpression) expression; - if ("CASE_INSENSITIVE_ORDER".equals(ref.getReferenceName())) { - PsiField field = ObjectUtil.tryCast(ref.resolve(), PsiField.class); - if (field != null && field.getContainingClass() != null && - CommonClassNames.JAVA_LANG_STRING.equals(field.getContainingClass().getQualifiedName())) { - return new NullHostile(); - } - } - } - PsiMethodCallExpression call = ObjectUtil.tryCast(expression, PsiMethodCallExpression.class); - if (call == null) return new Unknown(expression); - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if (REVERSED.test(call) && qualifier != null) { - return from(qualifier); - } - if (REVERSE_ORDER.test(call)) { - return from(call.getArgumentList().getExpressions()[0]); - } - if (NULL_FRIENDLY.test(call) && qualifier != null) { - return new NullFriendly(from(qualifier)); - } - if (KEY_EXTRACTOR.test(call)) { - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression keyExtractor = args[0]; - ComparatorModel downstream = args.length == 2 ? from(args[1]) : new NullHostile(); - return new KeyExtractor(keyExtractor, downstream); + @RequiredReadAction + static ComparatorModel from(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression == null || NULL_HOSTILE.matches(expression)) { + return new NullHostile(); + } + if (expression instanceof PsiReferenceExpression ref && "CASE_INSENSITIVE_ORDER".equals(ref.getReferenceName())) { + PsiField field = ObjectUtil.tryCast(ref.resolve(), PsiField.class); + if (field != null && field.getContainingClass() != null + && CommonClassNames.JAVA_LANG_STRING.equals(field.getContainingClass().getQualifiedName())) { + return new NullHostile(); + } + } + PsiMethodCallExpression call = ObjectUtil.tryCast(expression, PsiMethodCallExpression.class); + if (call == null) { + return new Unknown(expression); + } + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (REVERSED.test(call) && qualifier != null) { + return from(qualifier); + } + if (REVERSE_ORDER.test(call)) { + return from(call.getArgumentList().getExpressions()[0]); + } + if (NULL_FRIENDLY.test(call) && qualifier != null) { + return new NullFriendly(from(qualifier)); + } + if (KEY_EXTRACTOR.test(call)) { + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression keyExtractor = args[0]; + ComparatorModel downstream = args.length == 2 ? from(args[1]) : new NullHostile(); + return new KeyExtractor(keyExtractor, downstream); + } + return new Unknown(expression); } - return new Unknown(expression); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/LambdaInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/LambdaInliner.java index 610c5e003b..b140a0c7de 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/LambdaInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/LambdaInliner.java @@ -20,7 +20,6 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import one.util.streamex.EntryStream; -import javax.annotation.Nonnull; /** * An inliner which is capable to inline a call like ((IntSupplier)(() -> 5)).getAsInt() to the lambda body. @@ -29,7 +28,7 @@ public class LambdaInliner implements CallInliner { @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()); if(qualifier == null) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java index fe48567856..23b5deed3d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/MapUpdateInliner.java @@ -3,146 +3,122 @@ */ package com.intellij.java.analysis.impl.codeInspection.dataFlow.inliner; -import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.analysis.impl.codeInspection.dataFlow.CFGBuilder; import com.intellij.java.analysis.impl.codeInspection.dataFlow.SpecialField; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; +import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.language.psi.CommonClassNames; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; import com.intellij.java.language.psi.PsiType; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpectedTypeUtils; -import javax.annotation.Nonnull; import java.util.Objects; -public class MapUpdateInliner implements CallInliner -{ - private static final CallMatcher MAP_COMPUTE = CallMatcher.instanceCall( - CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent", "computeIfPresent", "compute").parameterCount(2); - private static final CallMatcher MAP_MERGE = CallMatcher.instanceCall( - CommonClassNames.JAVA_UTIL_MAP, "merge").parameterCount(3); +public class MapUpdateInliner implements CallInliner { + private static final CallMatcher MAP_COMPUTE = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "computeIfAbsent", "computeIfPresent", "compute") + .parameterCount(2); + private static final CallMatcher MAP_MERGE = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "merge").parameterCount(3); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) - { - if(MAP_COMPUTE.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier == null) - { - return false; - } - PsiType type = call.getType(); - if(type == null) - { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression key = args[0]; - PsiExpression function = args[1]; - builder - .pushExpression(qualifier) // stack: .. qualifier - .pushExpression(key) // stack: .. qualifier; key - .evaluateFunction(function); - String name = Objects.requireNonNull(call.getMethodExpression().getReferenceName()); - switch(name) - { - case "computeIfAbsent": - inlineComputeIfAbsent(builder, function, type); - break; - case "computeIfPresent": - inlineComputeIfPresent(builder, function, type); - break; - case "compute": - inlineCompute(builder, function, type); - break; - default: - throw new IllegalStateException("Unsupported name: " + name); - } - builder.resultOf(call); - return true; - } - if(MAP_MERGE.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier == null) - { - return false; - } - PsiType type = call.getType(); - if(type == null) - { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - PsiExpression key = args[0]; - PsiExpression value = args[1]; - PsiExpression function = args[2]; - builder - .pushExpression(qualifier) // stack: .. qualifier - .pushExpression(key) // stack: .. qualifier; key - .pop() // stack: .. qualifier - .pushExpression(value) // stack: .. qualifier; value - .boxUnbox(value, ExpectedTypeUtils.findExpectedType(value, false)) - .evaluateFunction(function) - .pushUnknown() // stack: .. qualifier; value; get() result - .ifNotNull() // stack: .. qualifier; value - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) // stack: .. qualifier; value; get() result - .swap() // stack: .. qualifier; get() result; value - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .end() - .chain(b -> flushSize(b)) - .resultOf(call); - return true; - } - return false; - } + @Override + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { + if (MAP_COMPUTE.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) { + return false; + } + PsiType type = call.getType(); + if (type == null) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression key = args[0]; + PsiExpression function = args[1]; + builder.pushExpression(qualifier) // stack: .. qualifier + .pushExpression(key) // stack: .. qualifier; key + .evaluateFunction(function); + String name = Objects.requireNonNull(call.getMethodExpression().getReferenceName()); + switch (name) { + case "computeIfAbsent": + inlineComputeIfAbsent(builder, function, type); + break; + case "computeIfPresent": + inlineComputeIfPresent(builder, function, type); + break; + case "compute": + inlineCompute(builder, function, type); + break; + default: + throw new IllegalStateException("Unsupported name: " + name); + } + builder.resultOf(call); + return true; + } + if (MAP_MERGE.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) { + return false; + } + PsiType type = call.getType(); + if (type == null) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiExpression key = args[0]; + PsiExpression value = args[1]; + PsiExpression function = args[2]; + builder.pushExpression(qualifier) // stack: .. qualifier + .pushExpression(key) // stack: .. qualifier; key + .pop() // stack: .. qualifier + .pushExpression(value) // stack: .. qualifier; value + .boxUnbox(value, ExpectedTypeUtils.findExpectedType(value, false)) + .evaluateFunction(function) + .pushUnknown() // stack: .. qualifier; value; get() result + .ifNotNull() // stack: .. qualifier; value + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) // stack: .. qualifier; value; get() result + .swap() // stack: .. qualifier; get() result; value + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .end() + .chain(b -> flushSize(b)) + .resultOf(call); + return true; + } + return false; + } - private static void flushSize(CFGBuilder builder) - { - builder.swap().unwrap(SpecialField.COLLECTION_SIZE).pushUnknown().assign().pop(); - } + private static void flushSize(CFGBuilder builder) { + builder.swap().unwrap(SpecialField.COLLECTION_SIZE).pushUnknown().assign().pop(); + } - private static void inlineComputeIfAbsent(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .pushUnknown() // stack: .. qualifier; key; get() result - .ifNull() // stack: .. qualifier; key - .invokeFunction(1, function) // stack: .. qualifier; mapping_result - .chain(MapUpdateInliner::flushSize) - .elseBranch() - .splice(2) - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) - .end(); - } + private static void inlineComputeIfAbsent(CFGBuilder builder, PsiExpression function, PsiType type) { + builder.pushUnknown() // stack: .. qualifier; key; get() result + .ifNull() // stack: .. qualifier; key + .invokeFunction(1, function) // stack: .. qualifier; mapping_result + .chain(MapUpdateInliner::flushSize) + .elseBranch() + .splice(2) + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) + .end(); + } - private static void inlineComputeIfPresent(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .pushUnknown() // stack: .. qualifier; key; get() result - .ifNotNull() // stack: .. qualifier; key - .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .chain(MapUpdateInliner::flushSize) - .elseBranch() - .splice(2) - .pushNull() - .end(); - } + private static void inlineComputeIfPresent(CFGBuilder builder, PsiExpression function, PsiType type) { + builder.pushUnknown() // stack: .. qualifier; key; get() result + .ifNotNull() // stack: .. qualifier; key + .push(DfTypes.typedObject(type, Nullability.NOT_NULL)) + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .chain(MapUpdateInliner::flushSize) + .elseBranch() + .splice(2) + .pushNull() + .end(); + } - private static void inlineCompute(@Nonnull CFGBuilder builder, - PsiExpression function, - PsiType type) - { - builder - .push(DfTypes.typedObject(type, Nullability.NULLABLE)) - .invokeFunction(2, function) // stack: .. qualifier; mapping result - .chain(MapUpdateInliner::flushSize); - } + private static void inlineCompute(CFGBuilder builder, PsiExpression function, PsiType type) { + builder.push(DfTypes.typedObject(type, Nullability.NULLABLE)) + .invokeFunction(2, function) // stack: .. qualifier; mapping result + .chain(MapUpdateInliner::flushSize); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/OptionalChainInliner.java index a1324e297f..6ab48532ad 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/OptionalChainInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -33,7 +33,6 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; import java.util.function.BiConsumer; import static com.intellij.java.analysis.impl.codeInspection.util.OptionalUtil.*; @@ -161,7 +160,7 @@ public class OptionalChainInliner implements CallInliner { .register(GUAVA_TO_JAVA, (builder, stub) -> {/* no op */}); @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { BiConsumer terminalInliner = TERMINAL_MAPPER.mapFirst(call); if (terminalInliner != null) { PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression(); @@ -310,7 +309,7 @@ private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, Ps } @Override - public boolean mayInferPreciseType(@Nonnull PsiExpression expression) { + public boolean mayInferPreciseType(PsiExpression expression) { return InlinerUtil.isLambdaChainParameterReference(expression, TypeUtils::isOptional); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/SimpleMethodInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/SimpleMethodInliner.java index 712af00ae8..72f75dc3c7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/SimpleMethodInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/SimpleMethodInliner.java @@ -1,21 +1,19 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow.inliner; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; import com.intellij.java.analysis.impl.codeInspection.dataFlow.CFGBuilder; import com.intellij.java.analysis.impl.codeInspection.dataFlow.NullabilityProblemKind; import com.intellij.java.language.codeInsight.Nullability; import com.intellij.java.language.codeInsight.NullabilityAnnotationInfo; import com.intellij.java.language.codeInsight.NullableNotNullManager; -import com.intellij.java.language.impl.codeInsight.ExpressionUtil; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.ExpressionUtil; import com.intellij.java.language.psi.util.PsiUtil; -import com.siyeh.ig.psiutils.ControlFlowUtils; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.lang.ref.Ref; -import javax.annotation.Nonnull; - import static consulo.util.lang.ObjectUtil.tryCast; /** @@ -23,7 +21,7 @@ */ public class SimpleMethodInliner implements CallInliner { @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { if (!call.getArgumentList().isEmpty()) return false; if (!ExpressionUtil.isEffectivelyUnqualified(call.getMethodExpression())) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/StreamChainInliner.java index 44644e6a01..cb2710a473 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -32,8 +32,7 @@ import com.siyeh.ig.psiutils.StreamApiUtil; import consulo.util.collection.ArrayUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.function.UnaryOperator; @@ -201,7 +200,6 @@ void pushResult(CFGBuilder builder) { } } - @Nonnull Nullability getNullability() { return DfaPsiUtil.getElementNullability(myCall.getType(), myCall.resolveMethod()); } @@ -233,7 +231,6 @@ void iteration(CFGBuilder builder) { builder.pop().flushFields(); } - @Nonnull @Override Nullability getNullability() { return myNotNullResult ? Nullability.NOT_NULL : super.getNullability(); @@ -243,7 +240,7 @@ Nullability getNullability() { static abstract class TerminalStep extends Step { DfaVariableValue myResult; - TerminalStep(@Nonnull PsiMethodCallExpression call, PsiExpression function) { + TerminalStep(PsiMethodCallExpression call, PsiExpression function) { super(call, null, function); } @@ -266,7 +263,7 @@ void pushResult(CFGBuilder builder) { } static class LambdaTerminalStep extends Step { - LambdaTerminalStep(@Nonnull PsiMethodCallExpression call) { + LambdaTerminalStep(PsiMethodCallExpression call) { super(call, null, call.getArgumentList().getExpressions()[0]); } @@ -279,7 +276,7 @@ void iteration(CFGBuilder builder) { static class SumTerminalStep extends TerminalStep { private DfType myResultRange; - SumTerminalStep(@Nonnull PsiMethodCallExpression call) { + SumTerminalStep(PsiMethodCallExpression call) { super(call, null); } @@ -336,13 +333,11 @@ void iteration(CFGBuilder builder) { static class Collect3TerminalStep extends TerminalStep { private final - @Nonnull PsiExpression mySupplier; private final - @Nonnull PsiExpression myAccumulator; - Collect3TerminalStep(@Nonnull PsiMethodCallExpression call) { + Collect3TerminalStep(PsiMethodCallExpression call) { super(call, call.getArgumentList().getExpressions()[2]); PsiExpression[] args = call.getArgumentList().getExpressions(); mySupplier = args[0]; @@ -367,7 +362,7 @@ protected void pushInitialValue(CFGBuilder builder) { } static class OptionalTerminalStep extends TerminalStep { - OptionalTerminalStep(@Nonnull PsiMethodCallExpression call) { + OptionalTerminalStep(PsiMethodCallExpression call) { super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); } @@ -395,7 +390,7 @@ void iteration(CFGBuilder builder) { static class MinMaxTerminalStep extends TerminalStep { private final ComparatorModel myComparatorModel; - MinMaxTerminalStep(@Nonnull PsiMethodCallExpression call) { + MinMaxTerminalStep(PsiMethodCallExpression call) { super(call, null); myComparatorModel = ComparatorModel.from(call.getArgumentList().getExpressions()[0]); } @@ -425,7 +420,7 @@ boolean expectNotNull() { } static class MatchTerminalStep extends TerminalStep { - MatchTerminalStep(@Nonnull PsiMethodCallExpression call) { + MatchTerminalStep(PsiMethodCallExpression call) { super(call, call.getArgumentList().getExpressions()[0]); } @@ -445,7 +440,7 @@ void iteration(CFGBuilder builder) { } static class FilterStep extends Step { - FilterStep(@Nonnull PsiMethodCallExpression call, Step next) { + FilterStep(PsiMethodCallExpression call, Step next) { super(call, next, call.getArgumentList().getExpressions()[0]); } @@ -463,7 +458,7 @@ void iteration(CFGBuilder builder) { } static class MapStep extends Step { - MapStep(@Nonnull PsiMethodCallExpression call, Step next) { + MapStep(PsiMethodCallExpression call, Step next) { super(call, next, call.getArgumentList().getExpressions()[0]); } @@ -481,7 +476,7 @@ static class FlatMapStep extends Step { private final PsiParameter myParameter; private final PsiExpression myStreamSource; - FlatMapStep(@Nonnull PsiMethodCallExpression call, Step next) { + FlatMapStep(PsiMethodCallExpression call, Step next) { super(call, next, null); // Try to inline smoothly .flatMap(x -> stream().call().chain()) PsiLambdaExpression lambda = @@ -555,7 +550,7 @@ void iteration(CFGBuilder builder) { } static class PeekStep extends Step { - PeekStep(@Nonnull PsiMethodCallExpression call, Step next) { + PeekStep(PsiMethodCallExpression call, Step next) { super(call, next, call.getArgumentList().getExpressions()[0]); } @@ -575,7 +570,7 @@ boolean expectNotNull() { } static class StateFilterStep extends Step { - StateFilterStep(@Nonnull PsiMethodCallExpression call, Step next) { + StateFilterStep(PsiMethodCallExpression call, Step next) { super(call, next, null); } @@ -602,7 +597,7 @@ void iteration(CFGBuilder builder) { static class SortedStep extends Step { private final ComparatorModel myComparatorModel; - SortedStep(@Nonnull PsiMethodCallExpression call, Step next) { + SortedStep(PsiMethodCallExpression call, Step next) { super(call, next, null); myComparatorModel = ComparatorModel.from(ArrayUtil.getFirstElement(myCall.getArgumentList().getExpressions())); } @@ -627,7 +622,7 @@ boolean expectNotNull() { } static class BoxedStep extends Step { - BoxedStep(@Nonnull PsiMethodCallExpression call, Step next) { + BoxedStep(PsiMethodCallExpression call, Step next) { super(call, next, null); } @@ -645,7 +640,7 @@ void iteration(CFGBuilder builder) { abstract static class AbstractCollectionStep extends TerminalStep { final boolean myImmutable; - AbstractCollectionStep(@Nonnull PsiMethodCallExpression call, @Nullable PsiExpression supplier, boolean immutable) { + AbstractCollectionStep(PsiMethodCallExpression call, @Nullable PsiExpression supplier, boolean immutable) { super(call, supplier); myImmutable = immutable; } @@ -667,7 +662,7 @@ protected void pushInitialValue(CFGBuilder builder) { } static class ToCollectionStep extends AbstractCollectionStep { - ToCollectionStep(@Nonnull PsiMethodCallExpression call, @Nullable PsiExpression supplier, boolean immutable) { + ToCollectionStep(PsiMethodCallExpression call, @Nullable PsiExpression supplier, boolean immutable) { super(call, supplier, immutable); } @@ -690,7 +685,7 @@ boolean expectNotNull() { } static class ToArrayStep extends ToCollectionStep { - ToArrayStep(@Nonnull PsiMethodCallExpression call) { + ToArrayStep(PsiMethodCallExpression call) { super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()), false); } @@ -706,18 +701,16 @@ protected void pushInitialValue(CFGBuilder builder) { static class ToMapStep extends AbstractCollectionStep { private final - @Nonnull PsiExpression myKeyExtractor; private final - @Nonnull PsiExpression myValueExtractor; private final @Nullable PsiExpression myMerger; - ToMapStep(@Nonnull PsiMethodCallExpression call, - @Nonnull PsiExpression keyExtractor, - @Nonnull PsiExpression valueExtractor, + ToMapStep(PsiMethodCallExpression call, + PsiExpression keyExtractor, + PsiExpression valueExtractor, @Nullable PsiExpression merger, @Nullable PsiExpression supplier, boolean immutable) { @@ -760,7 +753,7 @@ void iteration(CFGBuilder builder) { } @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { if (TERMINAL_CALL.test(call)) { return inlineCompleteStream(builder, call); } else { @@ -768,7 +761,7 @@ public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCall } } - private static boolean inlinePartialStream(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + private static boolean inlinePartialStream(CFGBuilder builder, PsiMethodCallExpression call) { Step firstStep = buildChain(call, NULL_TERMINAL_STEP); if (firstStep == NULL_TERMINAL_STEP) { return false; @@ -785,7 +778,7 @@ private static boolean inlinePartialStream(@Nonnull CFGBuilder builder, @Nonnull return true; } - private static boolean inlineCompleteStream(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) { + private static boolean inlineCompleteStream(CFGBuilder builder, PsiMethodCallExpression call) { PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call); Step terminalStep = createTerminalStep(call); Step firstStep = buildChain(qualifierCall, terminalStep); @@ -939,7 +932,7 @@ private static Step createTerminalFromCollector(PsiMethodCallExpression call) { } @Override - public boolean mayInferPreciseType(@Nonnull PsiExpression expression) { + public boolean mayInferPreciseType(PsiExpression expression) { return InlinerUtil.isLambdaChainParameterReference(expression, type -> InheritanceUtil.isInheritor(type, JAVA_UTIL_STREAM_STREAM)); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java index 1649d013e3..86da1b1e79 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/inliner/TransformInliner.java @@ -6,44 +6,39 @@ import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; import com.siyeh.ig.callMatcher.CallMatcher; -import javax.annotation.Nonnull; -public class TransformInliner implements CallInliner -{ - private static final CallMatcher TRANSFORM_METHODS = CallMatcher.anyOf( - CallMatcher.instanceCall("reactor.core.publisher.Mono", "as").parameterCount(1), - CallMatcher.instanceCall("reactor.core.publisher.Flux", "as").parameterCount(1), - CallMatcher.instanceCall("reactor.core.publisher.ParallelFlux", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Completable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Flowable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Maybe", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Observable", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.Single", "as").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Completable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Flowable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Maybe", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Observable", "to").parameterCount(1), - CallMatcher.instanceCall("io.reactivex.rxjava3.core.Single", "to").parameterCount(1), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "transform").parameterCount(1), - CallMatcher.instanceCall("one.util.streamex.BaseStreamEx", "chain").parameterCount(1) - ); +public class TransformInliner implements CallInliner { + private static final CallMatcher TRANSFORM_METHODS = CallMatcher.anyOf( + CallMatcher.instanceCall("reactor.core.publisher.Mono", "as").parameterCount(1), + CallMatcher.instanceCall("reactor.core.publisher.Flux", "as").parameterCount(1), + CallMatcher.instanceCall("reactor.core.publisher.ParallelFlux", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Completable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Flowable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Maybe", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Observable", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.Single", "as").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Completable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Flowable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Maybe", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Observable", "to").parameterCount(1), + CallMatcher.instanceCall("io.reactivex.rxjava3.core.Single", "to").parameterCount(1), + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_STRING, "transform").parameterCount(1), + CallMatcher.instanceCall("one.util.streamex.BaseStreamEx", "chain").parameterCount(1) + ); - @Override - public boolean tryInlineCall(@Nonnull CFGBuilder builder, @Nonnull PsiMethodCallExpression call) - { - if(TRANSFORM_METHODS.test(call)) - { - PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); - if(qualifier != null) - { - PsiExpression fn = call.getArgumentList().getExpressions()[0]; - builder.pushExpression(qualifier) - .evaluateFunction(fn) - .invokeFunction(1, fn) - .resultOf(call); - return true; - } - } - return false; - } + @Override + public boolean tryInlineCall(CFGBuilder builder, PsiMethodCallExpression call) { + if (TRANSFORM_METHODS.test(call)) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier != null) { + PsiExpression fn = call.getArgumentList().getExpressions()[0]; + builder.pushExpression(qualifier) + .evaluateFunction(fn) + .invokeFunction(1, fn) + .resultOf(call); + return true; + } + } + return false; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java index a3a5014dee..ecd55a63ca 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ArrayAccessInstruction.java @@ -19,20 +19,18 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.language.psi.PsiArrayAccessExpression; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Objects; public class ArrayAccessInstruction extends ExpressionPushingInstruction { private final - @Nonnull DfaValue myValue; private final @Nullable DfaControlTransferValue myTransferValue; - public ArrayAccessInstruction(@Nonnull DfaValue value, - @Nonnull PsiArrayAccessExpression expression, + public ArrayAccessInstruction(DfaValue value, + PsiArrayAccessExpression expression, @Nullable DfaControlTransferValue transferValue) { super(expression); myValue = value; @@ -44,7 +42,6 @@ public DfaControlTransferValue getOutOfBoundsExceptionTransfer() { return myTransferValue; } - @Nonnull public DfaValue getValue() { return myValue; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/AssignInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/AssignInstruction.java index b9dbe67324..3b47b89e42 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/AssignInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/AssignInstruction.java @@ -27,7 +27,7 @@ import consulo.util.lang.ObjectUtil; import org.jetbrains.annotations.Contract; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class AssignInstruction extends ExpressionPushingInstruction { private final PsiExpression myRExpression; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BinopInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BinopInstruction.java index c311798775..69f9b7c155 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BinopInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BinopInstruction.java @@ -28,7 +28,7 @@ import consulo.language.ast.TokenSet; import consulo.language.psi.PsiElement; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static com.intellij.java.language.psi.JavaTokenType.*; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BoxingInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BoxingInstruction.java index 888d17efb8..561daef89f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BoxingInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/BoxingInstruction.java @@ -6,7 +6,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaMemoryState; import com.intellij.java.analysis.impl.codeInspection.dataFlow.InstructionVisitor; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class BoxingInstruction extends Instruction { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java index 1ba9b90bc5..c5d7f69569 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java @@ -17,18 +17,16 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.*; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class CheckNotNullInstruction extends Instruction { private final - @Nonnull NullabilityProblemKind.NullabilityProblem myProblem; private final @Nullable DfaControlTransferValue myTransferValue; - public CheckNotNullInstruction(@Nonnull NullabilityProblemKind.NullabilityProblem problem, + public CheckNotNullInstruction(NullabilityProblemKind.NullabilityProblem problem, @Nullable DfaControlTransferValue transferValue) { myProblem = problem; myTransferValue = transferValue; @@ -39,7 +37,6 @@ public DfaControlTransferValue getOnNullTransfer() { return myTransferValue; } - @Nonnull public NullabilityProblemKind.NullabilityProblem getProblem() { return myProblem; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ClosureInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ClosureInstruction.java index fa15a21555..306270b095 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ClosureInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ClosureInstruction.java @@ -21,22 +21,19 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaMemoryState; import com.intellij.java.analysis.impl.codeInspection.dataFlow.InstructionVisitor; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; /** * Indicates the closure (lambda or class) inside the analyzed code block */ public class ClosureInstruction extends Instruction { - @Nonnull private final PsiElement myClosure; - public ClosureInstruction(@Nonnull PsiElement closure) + public ClosureInstruction(PsiElement closure) { myClosure = closure; } - @Nonnull public PsiElement getClosureElement() { return myClosure; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java index 76a75a9966..e8aeedd98c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java @@ -19,7 +19,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.*; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class ConditionalGotoInstruction extends Instruction implements BranchingInstruction { private ControlFlow.ControlFlowOffset myOffset; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/EvalInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/EvalInstruction.java index cc3a331a68..66a119f037 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/EvalInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/EvalInstruction.java @@ -8,7 +8,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nonnull; /** * An instruction that takes fixed number of operands from the stack and computes a single result without branching. @@ -49,8 +48,7 @@ public final DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState * @return the evaluated value */ public abstract - @Nonnull - DfaValue eval(@Nonnull DfaValueFactory factory, - @Nonnull DfaMemoryState state, - @Nonnull DfaValue ... arguments); + DfaValue eval(DfaValueFactory factory, + DfaMemoryState state, + DfaValue ... arguments); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java index ad3be3cf8f..22f1df1fff 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ExpressionPushingInstruction.java @@ -3,7 +3,7 @@ import consulo.document.util.TextRange; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * An instruction which pushes a result of {@link PsiExpression} (or its part) evaluation to the stack diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/FlushVariableInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/FlushVariableInstruction.java index ec335033b7..c7a13846f8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/FlushVariableInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/FlushVariableInstruction.java @@ -21,7 +21,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaMemoryState; import com.intellij.java.analysis.impl.codeInspection.dataFlow.InstructionVisitor; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaVariableValue; -import javax.annotation.Nonnull; /** * Flush single variable @@ -29,18 +28,16 @@ public class FlushVariableInstruction extends Instruction { private final - @Nonnull DfaVariableValue myVariable; /** * @param variable variable to flush */ - public FlushVariableInstruction(@Nonnull DfaVariableValue variable) + public FlushVariableInstruction(DfaVariableValue variable) { myVariable = variable; } - @Nonnull public DfaVariableValue getVariable() { return myVariable; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/InstanceofInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/InstanceofInstruction.java index 43bd7d3856..7e5aa3aba5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/InstanceofInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/InstanceofInstruction.java @@ -23,8 +23,7 @@ import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author peter @@ -36,7 +35,7 @@ public class InstanceofInstruction extends BinopInstruction @Nullable private final PsiType myCastType; - public InstanceofInstruction(PsiExpression psiAnchor, @Nullable PsiExpression left, @Nonnull PsiType castType) + public InstanceofInstruction(PsiExpression psiAnchor, @Nullable PsiExpression left, PsiType castType) { super(JavaTokenType.INSTANCEOF_KEYWORD, psiAnchor, PsiType.BOOLEAN); myLeft = left; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/IsAssignableInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/IsAssignableInstruction.java index f20b7be5a8..a6e4453929 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/IsAssignableInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/IsAssignableInstruction.java @@ -9,7 +9,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.PsiMethodCallExpression; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; /** * A binary operation that takes two types from the stack and returns @@ -23,8 +22,7 @@ public IsAssignableInstruction(PsiMethodCallExpression expression) } @Override - @Nonnull - public DfaValue eval(@Nonnull DfaValueFactory factory, @Nonnull DfaMemoryState state, @Nonnull DfaValue... arguments) + public DfaValue eval(DfaValueFactory factory, DfaMemoryState state, DfaValue... arguments) { PsiType superClass = DfConstantType.getConstantOfType(state.getDfType(arguments[1]), PsiType.class); PsiType subClass = DfConstantType.getConstantOfType(state.getDfType(arguments[0]), PsiType.class); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodCallInstruction.java index e56d3fa851..805beed3c8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -24,8 +24,7 @@ import consulo.language.psi.PsiElement; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,10 +38,8 @@ public class MethodCallInstruction extends ExpressionPushingInstruction contracts) { + public MethodCallInstruction(PsiMethodReferenceExpression reference, List contracts) { super(reference); myContext = reference; JavaResolveResult resolveResult = reference.advancedResolve(false); @@ -85,7 +82,7 @@ public MethodCallInstruction(@Nonnull PsiMethodReferenceExpression reference, @N myMutation = MutationSignature.fromMethod(myTargetMethod); } - public MethodCallInstruction(@Nonnull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List contracts) { + public MethodCallInstruction(PsiCall call, @Nullable DfaValue precalculatedReturnValue, List contracts) { super(ObjectUtil.tryCast(call, PsiExpression.class)); myContext = call; myContracts = Collections.unmodifiableList(contracts); @@ -186,7 +183,6 @@ public int getArgCount() { } public - @Nonnull MutationSignature getMutationSignature() { return myMutation; } @@ -222,7 +218,6 @@ public PsiCall getCallExpression() { } public - @Nonnull PsiElement getContext() { return myContext; } @@ -234,7 +229,6 @@ DfaValue getPrecalculatedReturnValue() { } public - @Nonnull Nullability getReturnNullability() { return myReturnNullability; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodReferenceInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodReferenceInstruction.java index 457b91e4f1..01cfe1b3ae 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodReferenceInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/MethodReferenceInstruction.java @@ -20,7 +20,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaMemoryState; import com.intellij.java.analysis.impl.codeInspection.dataFlow.InstructionVisitor; import com.intellij.java.language.psi.PsiMethodReferenceExpression; -import javax.annotation.Nonnull; import java.util.Objects; @@ -29,7 +28,7 @@ */ public class MethodReferenceInstruction extends ExpressionPushingInstruction { - public MethodReferenceInstruction(@Nonnull PsiMethodReferenceExpression expression) + public MethodReferenceInstruction(PsiMethodReferenceExpression expression) { super(expression); } @@ -46,7 +45,6 @@ public String toString() } @Override - @Nonnull public PsiMethodReferenceExpression getExpression() { return Objects.requireNonNull(super.getExpression()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java index 5c55fa0e4a..a5736304be 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PrimitiveConversionInstruction.java @@ -14,9 +14,8 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.util.TypeConversionUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.rangeClamped; @@ -36,10 +35,9 @@ public PrimitiveConversionInstruction(@Nullable PsiPrimitiveType targetType, @Nu @Override public - @Nonnull - DfaValue eval(@Nonnull DfaValueFactory factory, - @Nonnull DfaMemoryState state, - @Nonnull DfaValue... arguments) + DfaValue eval(DfaValueFactory factory, + DfaMemoryState state, + DfaValue... arguments) { DfaValue value = arguments[0]; PsiPrimitiveType type = myTargetType; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushInstruction.java index b353a22266..be72d1bc35 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushInstruction.java @@ -20,7 +20,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nonnull; /** * An instruction that pushes given value to the stack @@ -28,16 +27,15 @@ public class PushInstruction extends EvalInstruction { private final - @Nonnull DfaValue myValue; private final boolean myReferenceWrite; - public PushInstruction(@Nonnull DfaValue value, PsiExpression place) + public PushInstruction(DfaValue value, PsiExpression place) { this(value, place, false); } - public PushInstruction(@Nonnull DfaValue value, PsiExpression place, final boolean isReferenceWrite) + public PushInstruction(DfaValue value, PsiExpression place, final boolean isReferenceWrite) { super(place, 0); myValue = value; @@ -49,7 +47,6 @@ public boolean isReferenceWrite() return myReferenceWrite; } - @Nonnull public DfaValue getValue() { return myValue; @@ -57,8 +54,7 @@ public DfaValue getValue() @Override public - @Nonnull - DfaValue eval(@Nonnull DfaValueFactory factory, @Nonnull DfaMemoryState state, @Nonnull DfaValue ... arguments) + DfaValue eval(DfaValueFactory factory, DfaMemoryState state, DfaValue ... arguments) { return myValue; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushValueInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushValueInstruction.java index bd832d7adb..a8caf87349 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushValueInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/PushValueInstruction.java @@ -21,7 +21,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nonnull; /** * An instruction that pushes the value of given DfType to the stack @@ -29,22 +28,20 @@ public class PushValueInstruction extends EvalInstruction { private final - @Nonnull DfType myValue; - public PushValueInstruction(@Nonnull DfType value, PsiExpression place) + public PushValueInstruction(DfType value, PsiExpression place) { super(place, 0); myValue = value; } - public PushValueInstruction(@Nonnull DfType value) + public PushValueInstruction(DfType value) { this(value, null); } public - @Nonnull DfType getValue() { return myValue; @@ -52,8 +49,7 @@ DfType getValue() @Override public - @Nonnull - DfaValue eval(@Nonnull DfaValueFactory factory, @Nonnull DfaMemoryState state, @Nonnull DfaValue... arguments) + DfaValue eval(DfaValueFactory factory, DfaMemoryState state, DfaValue... arguments) { return factory.fromDfType(myValue); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ResultOfInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ResultOfInstruction.java index efc93a264d..c37cc3a366 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ResultOfInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ResultOfInstruction.java @@ -6,7 +6,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.java.language.psi.PsiExpression; -import javax.annotation.Nonnull; import java.util.Objects; @@ -15,15 +14,14 @@ */ public class ResultOfInstruction extends EvalInstruction { - public ResultOfInstruction(@Nonnull PsiExpression expression) + public ResultOfInstruction(PsiExpression expression) { super(expression, 1); } @Override public - @Nonnull - DfaValue eval(@Nonnull DfaValueFactory factory, @Nonnull DfaMemoryState state, @Nonnull DfaValue ... arguments) + DfaValue eval(DfaValueFactory factory, DfaMemoryState state, DfaValue ... arguments) { return arguments[0]; } @@ -33,7 +31,6 @@ public String toString() return "RESULT_OF " + getExpression().getText(); } - @Nonnull @Override public PsiExpression getExpression() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ReturnInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ReturnInstruction.java index 0947400956..f957c37d42 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ReturnInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/ReturnInstruction.java @@ -20,14 +20,13 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaControlTransferValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.ExceptionTransfer; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class ReturnInstruction extends ControlTransferInstruction { private final PsiElement myAnchor; - public ReturnInstruction(@Nonnull DfaControlTransferValue transfer, @Nullable PsiElement anchor) + public ReturnInstruction(DfaControlTransferValue transfer, @Nullable PsiElement anchor) { super(transfer); myAnchor = anchor; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/TypeCastInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/TypeCastInstruction.java index d271278d81..abd84e3f89 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/TypeCastInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/TypeCastInstruction.java @@ -22,7 +22,7 @@ import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.PsiTypeCastExpression; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class TypeCastInstruction extends ExpressionPushingInstruction { private final PsiExpression myCasted; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/UnwrapSpecialFieldInstruction.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/UnwrapSpecialFieldInstruction.java index 08f584c48e..c594e315ba 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/UnwrapSpecialFieldInstruction.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/instructions/UnwrapSpecialFieldInstruction.java @@ -5,25 +5,22 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.SpecialField; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValue; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.DfaValueFactory; -import javax.annotation.Nonnull; /** * Instruction to push a field qualified by the value on the stack */ public class UnwrapSpecialFieldInstruction extends EvalInstruction { - @Nonnull private final SpecialField mySpecialField; - public UnwrapSpecialFieldInstruction(@Nonnull SpecialField specialField) + public UnwrapSpecialFieldInstruction(SpecialField specialField) { super(null, 1); mySpecialField = specialField; } @Override - @Nonnull - public DfaValue eval(@Nonnull DfaValueFactory factory, @Nonnull DfaMemoryState state, @Nonnull DfaValue ... arguments) + public DfaValue eval(DfaValueFactory factory, DfaMemoryState state, DfaValue ... arguments) { return mySpecialField.createValue(factory, arguments[0]); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeBinOp.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeBinOp.java index fbdbcc8482..d6271ad2a6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeBinOp.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeBinOp.java @@ -3,8 +3,7 @@ import com.intellij.java.language.psi.JavaTokenType; import consulo.language.ast.IElementType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Supported binary operations over long ranges. @@ -39,8 +38,7 @@ public enum LongRangeBinOp * @return the resulting LongRangeSet which covers possible results of the operation (probably including some more elements). */ public - @Nonnull - LongRangeSet eval(@Nonnull LongRangeSet left, @Nonnull LongRangeSet right, boolean isLong) + LongRangeSet eval(LongRangeSet left, LongRangeSet right, boolean isLong) { switch(this) { @@ -81,8 +79,7 @@ LongRangeSet eval(@Nonnull LongRangeSet left, @Nonnull LongRangeSet right, boole * @return the resulting LongRangeSet which covers possible results of the operation (probably including some more elements). */ public - @Nonnull - LongRangeSet evalWide(@Nonnull LongRangeSet left, @Nonnull LongRangeSet right, boolean isLong) + LongRangeSet evalWide(LongRangeSet left, LongRangeSet right, boolean isLong) { switch(this) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeSet.java index 2473833f1c..f573adeef5 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -2,21 +2,21 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet; import com.intellij.java.language.codeInsight.AnnotationUtil; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.RelationType; import com.intellij.java.language.psi.PsiAnnotation; import com.intellij.java.language.psi.PsiModifierListOwner; import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.util.TypeConversionUtil; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.localize.LocalizeValue; import consulo.util.lang.MathUtil; import consulo.util.lang.ThreeState; import one.util.streamex.IntStreamEx; import one.util.streamex.StreamEx; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.*; import java.util.stream.IntStream; import java.util.stream.LongStream; @@ -29,2471 +29,2431 @@ * @author Tagir Valeev */ public abstract class LongRangeSet { - private static final String JETBRAINS_RANGE = "org.jetbrains.annotations.Range"; - private static final String CHECKER_RANGE = "org.checkerframework.common.value.qual.IntRange"; - private static final String CHECKER_GTE_NEGATIVE_ONE = "org.checkerframework.checker.index.qual.GTENegativeOne"; - private static final String CHECKER_NON_NEGATIVE = "org.checkerframework.checker.index.qual.NonNegative"; - private static final String CHECKER_POSITIVE = "org.checkerframework.checker.index.qual.Positive"; - private static final String JSR305_NONNEGATIVE = "javax.annotation.Nonnegative"; - private static final String VALIDATION_MIN = "javax.validation.constraints.Min"; - private static final String VALIDATION_MAX = "javax.validation.constraints.Max"; - private static final List ANNOTATIONS = Arrays.asList(CHECKER_RANGE, - CHECKER_GTE_NEGATIVE_ONE, - CHECKER_NON_NEGATIVE, - CHECKER_POSITIVE, - JSR305_NONNEGATIVE, - VALIDATION_MIN, - VALIDATION_MAX); - - LongRangeSet() { - } - - /** - * Subtracts given set from the current. May return bigger set (containing some additional elements) if exact subtraction is impossible. - * - * @param other set to subtract - * @return a new set - */ - @Nonnull - public abstract LongRangeSet subtract(@Nonnull LongRangeSet other); - - public LongRangeSet without(long value) { - return subtract(point(value)); - } - - /** - * @return true if set is empty - */ - public boolean isEmpty() { - return this == Empty.EMPTY; - } - - /** - * Intersects current set with other - * - * @param other other set to intersect with - * @return a new set - */ - @Nonnull - public abstract LongRangeSet intersect(@Nonnull LongRangeSet other); - - /** - * Merge current set with other. May return bigger set if exact representation is impossible. - * - * @param other other set to merge with - * @return a new set - */ - @Nonnull - public abstract LongRangeSet unite(@Nonnull LongRangeSet other); - - /** - * @return a minimal value contained in the set - * @throws NoSuchElementException if set is empty - */ - public abstract long min(); - - /** - * @return a maximal value contained in the set - * @throws NoSuchElementException if set is empty - */ - public abstract long max(); - - /** - * @return a constant value if this set represents a constant; null otherwise - */ - @Nullable - public Long getConstantValue() { - return null; - } - - /** - * Checks if current set and other set have at least one common element - * - * @param other other set to check whether intersection exists - * @return true if this set intersects other set - */ - public abstract boolean intersects(LongRangeSet other); - - /** - * Checks whether current set contains given value - * - * @param value value to find - * @return true if current set contains given value - */ - public abstract boolean contains(long value); - - /** - * Checks whether current set contains all the values from other set - * - * @param other a sub-set candidate - * @return true if current set contains all the values from other - */ - public abstract boolean contains(@Nonnull LongRangeSet other); - - /** - * Creates a new set which contains all possible values satisfying given predicate regarding the current set. - *

- * E.g. if current set is {0..10} and relation is "GT", then result will be {1..Long.MAX_VALUE} (values which can be greater than - * some value from the current set) - * - * @param relation relation to be applied to current set (JavaTokenType.EQEQ/NE/GT/GE/LT/LE) - * @return new set or null if relation is unsupported - */ - public LongRangeSet fromRelation(@Nullable RelationType relation) { - if (isEmpty() || relation == null) { - return null; - } - switch (relation) { - case EQ: - return this; - case NE: { - long min = min(); - if (min == max()) { - return all().without(min); - } - return all(); - } - case GT: { - long min = min(); - return min == Long.MAX_VALUE ? empty() : range(min + 1, Long.MAX_VALUE); - } - case GE: - return range(min(), Long.MAX_VALUE); - case LE: - return range(Long.MIN_VALUE, max()); - case LT: { - long max = max(); - return max == Long.MIN_VALUE ? empty() : range(Long.MIN_VALUE, max - 1); - } - default: + private static final String JETBRAINS_RANGE = "org.jetbrains.annotations.Range"; + private static final String CHECKER_RANGE = "org.checkerframework.common.value.qual.IntRange"; + private static final String CHECKER_GTE_NEGATIVE_ONE = "org.checkerframework.checker.index.qual.GTENegativeOne"; + private static final String CHECKER_NON_NEGATIVE = "org.checkerframework.checker.index.qual.NonNegative"; + private static final String CHECKER_POSITIVE = "org.checkerframework.checker.index.qual.Positive"; + private static final String JSR305_NONNEGATIVE = "javax.annotation.Nonnegative"; + private static final String VALIDATION_MIN = "javax.validation.constraints.Min"; + private static final String VALIDATION_MAX = "javax.validation.constraints.Max"; + private static final List ANNOTATIONS = Arrays.asList( + CHECKER_RANGE, + CHECKER_GTE_NEGATIVE_ONE, + CHECKER_NON_NEGATIVE, + CHECKER_POSITIVE, + JSR305_NONNEGATIVE, + VALIDATION_MIN, + VALIDATION_MAX + ); + + LongRangeSet() { + } + + /** + * Subtracts given set from the current. May return bigger set (containing some additional elements) if exact subtraction is impossible. + * + * @param other set to subtract + * @return a new set + */ + public abstract LongRangeSet subtract(LongRangeSet other); + + public LongRangeSet without(long value) { + return subtract(point(value)); + } + + /** + * @return true if set is empty + */ + public boolean isEmpty() { + return this == Empty.EMPTY; + } + + /** + * Intersects current set with other + * + * @param other other set to intersect with + * @return a new set + */ + public abstract LongRangeSet intersect(LongRangeSet other); + + /** + * Merge current set with other. May return bigger set if exact representation is impossible. + * + * @param other other set to merge with + * @return a new set + */ + public abstract LongRangeSet unite(LongRangeSet other); + + /** + * @return a minimal value contained in the set + * @throws NoSuchElementException if set is empty + */ + public abstract long min(); + + /** + * @return a maximal value contained in the set + * @throws NoSuchElementException if set is empty + */ + public abstract long max(); + + /** + * @return a constant value if this set represents a constant; null otherwise + */ + @Nullable + public Long getConstantValue() { return null; } - } - - public abstract - @Nls - String getPresentationText(PsiType type); - @Nonnull - LongRangeSet mulWiden(LongRangeSet other, boolean isLong) { - if (Point.ZERO.equals(this)) { - return this; - } - if (Point.ZERO.equals(other)) { - return other; - } - if (Point.ONE.equals(this)) { - return other; - } - if (Point.ONE.equals(other)) { - return this; - } - if (Point.ZERO.equals(this.mod(point(2))) || Point.ZERO.equals(other.mod(point(2)))) { - return modRange(minValue(isLong), maxValue(isLong), 2, 1); - } - return isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } - - @Nonnull - LongRangeSet plusWiden(LongRangeSet other, boolean isLong) { - if (this instanceof Point && other instanceof Point) { - long val1 = ((Point) this).myValue; - long val2 = ((Point) other).myValue; - int tzb1 = val1 == 0 ? 0 : Long.numberOfTrailingZeros(val1); - int tzb2 = val2 == 0 ? 0 : Long.numberOfTrailingZeros(val2); - LongRangeSet constVal; - int mod; - if (tzb1 > tzb2) { - constVal = other; - mod = 1 << (Math.min(6, tzb1)); - } else { - constVal = this; - mod = 1 << (Math.min(6, tzb2)); - } - if (mod < 2) { - return isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } - return modRange(minValue(isLong), maxValue(isLong), mod, 1).plus(constVal, isLong); - } - if (this instanceof Point && other instanceof ModRange) { - ModRange modRange = (ModRange) other; - long value = ((Point) this).myValue; - if (value % modRange.myMod == 0) { - return modRange(minValue(isLong), maxValue(isLong), modRange.myMod, modRange.myBits); - } - if (modRange.myBits == 1) { - return this.plus(other, isLong); - } - if (value >= -64 && value < 64) { - int gcd = gcd(Math.abs((int) value), modRange.myMod); - if (gcd > 1) { - long count = modRange.myMod / gcd; - long bits = 0; - for (int i = 0; i < count; i++) { - bits |= (modRange.myBits >>> (i * gcd)) & ((1L << gcd) - 1); - } - return modRange(minValue(isLong), maxValue(isLong), gcd, bits); - } - } - } - if (other instanceof Point && this instanceof ModRange) { - return other.plusWiden(this, isLong); - } - return isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } - - public abstract boolean isCardinalityBigger(long cutoff); - - public abstract LongRangeSet castTo(PsiPrimitiveType type); - - /** - * Returns a range which represents all the possible values after applying {@link Math#abs(int)} or {@link Math#abs(long)} - * to the values from this set - * - * @param isLong whether {@link Math#abs(long)} is applied - * @return a new range - */ - @Nonnull - public abstract LongRangeSet abs(boolean isLong); - - /** - * Returns a range which represents all the possible values after applying unary minus - * to the values from this set - * - * @param isLong whether result should be truncated to {@code int} - * @return a new range - */ - @Nonnull - public abstract LongRangeSet negate(boolean isLong); - - /** - * Returns a range which represents all the possible values after performing an addition between any value from this range - * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. - * Guaranteed to be commutative. - * - * @param isLong whether result should be truncated to {@code int} - * @return a new range - */ - @Nonnull - public abstract LongRangeSet plus(LongRangeSet other, boolean isLong); - - /** - * Returns a range which represents all the possible values after performing an addition between any value from this range - * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. - * - * @param isLong whether result should be truncated to {@code int} - * @return a new range - */ - @Nonnull - public LongRangeSet minus(LongRangeSet other, boolean isLong) { - return plus(other.negate(isLong), isLong); - } - - /** - * Returns a range which represents all the possible values after applying {@code x | y} operation for - * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain - * some more values. - * - * @param other other set to perform bitwise-or with - * @return a new range - */ - @Nonnull - public LongRangeSet bitwiseOr(LongRangeSet other, boolean isLong) { - if (this.isEmpty() || other.isEmpty()) { - return empty(); - } - LongRangeSet result = fromBits(getBitwiseMask().or(other.getBitwiseMask())); - return isLong ? result : result.intersect(Range.INT_RANGE); - } - - /** - * Returns a range which represents all the possible values after applying {@code x ^ y} operation for - * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain - * some more values. - * - * @param other other set to perform bitwise-xor with - * @return a new range - */ - @Nonnull - public LongRangeSet bitwiseXor(LongRangeSet other, boolean isLong) { - if (this.isEmpty() || other.isEmpty()) { - return empty(); - } - LongRangeSet result = fromBits(getBitwiseMask().xor(other.getBitwiseMask())); - return isLong ? result : result.intersect(Range.INT_RANGE); - } - - /** - * Returns a range which represents all the possible values after applying {@code x & y} operation for - * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain - * some more values. - * - * @param other other set to perform bitwise-and with - * @return a new range - */ - @Nonnull - public LongRangeSet bitwiseAnd(LongRangeSet other) { - if (this.isEmpty() || other.isEmpty()) { - return empty(); - } - long[] left = splitAtZero(asRanges()); - long[] right = splitAtZero(other.asRanges()); - // More than three intervals --> convert to single interval to make result more compact (though probably less precise) - if (left.length > 6) { - left = splitAtZero(new long[]{ - left[0], - left[left.length - 1] - }); - } - if (right.length > 6) { - right = splitAtZero(new long[]{ - right[0], - right[right.length - 1] - }); - } - BitString globalMask = getBitwiseMask().and(other.getBitwiseMask()); - globalMask = new BitString(globalMask.myBits | ~globalMask.myMask, -1L); - LongRangeSet result = empty(); - for (int i = 0; i < left.length; i += 2) { - for (int j = 0; j < right.length; j += 2) { - result = result.unite(bitwiseAnd(left[i], left[i + 1], right[j], right[j + 1], globalMask)); - } + /** + * Checks if current set and other set have at least one common element + * + * @param other other set to check whether intersection exists + * @return true if this set intersects other set + */ + public abstract boolean intersects(LongRangeSet other); + + /** + * Checks whether current set contains given value + * + * @param value value to find + * @return true if current set contains given value + */ + public abstract boolean contains(long value); + + /** + * Checks whether current set contains all the values from other set + * + * @param other a sub-set candidate + * @return true if current set contains all the values from other + */ + public abstract boolean contains(LongRangeSet other); + + /** + * Creates a new set which contains all possible values satisfying given predicate regarding the current set. + *

+ * E.g. if current set is {0..10} and relation is "GT", then result will be {1..Long.MAX_VALUE} (values which can be greater than + * some value from the current set) + * + * @param relation relation to be applied to current set (JavaTokenType.EQEQ/NE/GT/GE/LT/LE) + * @return new set or null if relation is unsupported + */ + public LongRangeSet fromRelation(@Nullable RelationType relation) { + if (isEmpty() || relation == null) { + return null; + } + switch (relation) { + case EQ: + return this; + case NE: { + long min = min(); + if (min == max()) { + return all().without(min); + } + return all(); + } + case GT: { + long min = min(); + return min == Long.MAX_VALUE ? empty() : range(min + 1, Long.MAX_VALUE); + } + case GE: + return range(min(), Long.MAX_VALUE); + case LE: + return range(Long.MIN_VALUE, max()); + case LT: { + long max = max(); + return max == Long.MIN_VALUE ? empty() : range(Long.MIN_VALUE, max - 1); + } + default: + return null; + } } - return result; - } - - abstract public LongRangeSet mul(LongRangeSet multiplier, boolean isLong); - BitString getBitwiseMask() { - if (isEmpty()) { - return BitString.UNSURE; - } - return BitString.fromRange(min(), max()); - } - - /** - * Returns a range which represents all the possible values after applying {@code x / y} operation for - * all {@code x} from this set and for all {@code y} from the divisor set. The resulting set may contain - * some more values. Division by zero yields an empty set of possible results. - * - * @param divisor divisor set to divide by - * @param isLong whether the operation is performed on long type (if false, the int type is assumed). This only changes the special - * treatment of {@code MIN_VALUE/-1} division; other division results do not depend on the resulting type. - * @return a new range - */ - @Nonnull - public LongRangeSet div(LongRangeSet divisor, boolean isLong) { - if (divisor.isEmpty() || divisor.equals(Point.ZERO)) { - return empty(); - } - LongRangeSet dividend = this; - if (!isLong) { - divisor = divisor.intersect(Range.INT_RANGE); - dividend = dividend.intersect(Range.INT_RANGE); - } - long[] left = splitAtZero(dividend.asRanges()); - long[] right = splitAtZero(new long[]{ - divisor.min(), - divisor.max() - }); - LongRangeSet result = empty(); - for (int i = 0; i < left.length; i += 2) { - for (int j = 0; j < right.length; j += 2) { - result = result.unite(divide(left[i], left[i + 1], right[j], right[j + 1], isLong)); - } - } - return result; - } - - /** - * Checks whether subtraction of this and other range may overflow - * - * @param other range to subtract from this range - * @param isLong whether subtraction should be performed on long values (otherwise int is assumed) - * @return true if result may overflow, false if it never overflows - */ - public boolean subtractionMayOverflow(LongRangeSet other, boolean isLong) { - long leftMin = min(); - long leftMax = max(); - long rightMin = other.min(); - long rightMax = other.max(); - return isLong - ? overflowsLong(leftMin, rightMax) || overflowsLong(leftMax, rightMin) - : overflowsInt(leftMin, rightMax) || overflowsInt(leftMax, rightMin); - } - - private static boolean overflowsInt(long a, long b) { - long diff = a - b; - return diff < Integer.MIN_VALUE || diff > Integer.MAX_VALUE; - } - - private static boolean overflowsLong(long a, long b) { - long diff = a - b; - // Hacker's Delight 2nd Edition, 2-13 Overflow Detection - return ((a ^ b) & (a ^ diff)) < 0; - } - - @Nonnull - private static LongRangeSet divide(long dividendMin, long dividendMax, long divisorMin, long divisorMax, boolean isLong) { - if (divisorMin == 0) { - if (divisorMax == 0) { - return empty(); - } - divisorMin = 1; - } - if (dividendMin >= 0) { - return divisorMin > 0 - ? range(dividendMin / divisorMax, dividendMax / divisorMin) - : range(dividendMax / divisorMax, dividendMin / divisorMin); - } - if (divisorMin > 0) { - return range(dividendMin / divisorMin, dividendMax / divisorMax); - } - long minValue = minValue(isLong); - if (dividendMin == minValue && divisorMax == -1) { - // MIN_VALUE/-1 = MIN_VALUE - return point(minValue) - .unite(divisorMin == -1 ? empty() : range(dividendMin / divisorMin, dividendMin / (divisorMax - 1))) - .unite(dividendMax == minValue ? empty() : range(dividendMax / divisorMin, (dividendMin + 1) / divisorMax)); - } - return range(dividendMax / divisorMin, dividendMin / divisorMax); - } - - /** - * Returns a range which represents all the possible values after applying {@code x << y} operation for - * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain - * some more values. - * - * @param shiftSize set of possible shift sizes (number of bits to shift to the left) - * @param isLong whether the operation is performed on long type (if false, the int type is assumed). - * @return a new range - */ - @Nonnull - public LongRangeSet shiftLeft(LongRangeSet shiftSize, boolean isLong) { - if (isEmpty() || shiftSize.isEmpty()) { - return empty(); - } - if (shiftSize instanceof Point) { - long shift = ((Point) shiftSize).myValue & ((isLong ? Long.SIZE : Integer.SIZE) - 1); - return point(1L << shift).mul(this, isLong); - } - return isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } - - /** - * Returns a range which represents all the possible values after applying {@code x >> y} operation for - * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain - * some more values. - * - * @param shiftSize set of possible shift sizes (number of bits to shift to the right) - * @param isLong whether the operation is performed on long type (if false, the int type is assumed). - * @return a new range - */ - @Nonnull - public LongRangeSet shiftRight(LongRangeSet shiftSize, boolean isLong) { - return doShiftRight(shiftSize, isLong, false); - } - - /** - * Returns a range which represents all the possible values after applying {@code x >>> y} operation for - * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain - * some more values. - * - * @param shiftSize set of possible shift sizes (number of bits to shift to the right) - * @param isLong whether the operation is performed on long type (if false, the int type is assumed). - * @return a new range - */ - @Nonnull - public LongRangeSet unsignedShiftRight(LongRangeSet shiftSize, boolean isLong) { - return doShiftRight(shiftSize, isLong, true); - } - - private LongRangeSet doShiftRight(LongRangeSet shiftSize, boolean isLong, boolean unsigned) { - if (isEmpty() || shiftSize.isEmpty()) { - return empty(); - } - int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1; - if (shiftSize.min() < 0 || shiftSize.max() > maxShift) { - shiftSize = shiftSize.bitwiseAnd(point(maxShift)); - } - long min = shiftSize.min(); - long max = shiftSize.max(); - LongRangeSet negative = intersect(range(minValue(isLong), -1)); - LongRangeSet positive = intersect(range(0, maxValue(isLong))); - LongRangeSet positiveResult = positive.shrPositive(min, max, isLong); - LongRangeSet negativeResult; - LongRangeSet negativeComplement = point(-1).minus(negative, isLong); // -1-negative - if (unsigned) { - if (min == 0) { - positiveResult = positiveResult.unite(negative); - if (max == 0) { - return positiveResult; - } - min++; - } - // for x < 0, y > 0, x >>> y = (MAX_VALUE - ((-1-x) >> 1)) >> (y-1) - negativeResult = point(maxValue(isLong)).minus(negativeComplement.shrPositive(1, 1, isLong), isLong) - .shrPositive(min - 1, max - 1, isLong); - } else { - negativeResult = point(-1).minus(negativeComplement.shrPositive(min, max, isLong), isLong); - } - return positiveResult.unite(negativeResult); - } + public abstract + String getPresentationText(PsiType type); - private LongRangeSet shrPositive(long min, long max, boolean isLong) { - if (isEmpty()) { - return empty(); - } - int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1; - if (max == maxShift) { - return min == max ? Point.ZERO : Point.ZERO.unite(div(range(1L << min, 1L << (max - 1)), isLong)); - } - return div(range(1L << min, 1L << max), isLong); - } - - /** - * Returns a range which represents all the possible values after applying {@code x % y} operation for - * all {@code x} from this set and for all {@code y} from the divisor set. The resulting set may contain - * some more values. Division by zero yields an empty set of possible results. - * - * @param divisor divisor set to divide by - * @return a new range - */ - @Nonnull - abstract public LongRangeSet mod(LongRangeSet divisor); - - private static long[] splitAtZero(long[] ranges) { - for (int i = 0; i < ranges.length; i += 2) { - if (ranges[i] < 0 && ranges[i + 1] >= 0) { - long[] result = new long[ranges.length + 2]; - System.arraycopy(ranges, 0, result, 0, i + 1); - result[i + 1] = -1; - System.arraycopy(ranges, i + 1, result, i + 3, ranges.length - i - 1); - return result; - } + LongRangeSet mulWiden(LongRangeSet other, boolean isLong) { + if (Point.ZERO.equals(this)) { + return this; + } + if (Point.ZERO.equals(other)) { + return other; + } + if (Point.ONE.equals(this)) { + return other; + } + if (Point.ONE.equals(other)) { + return this; + } + if (Point.ZERO.equals(this.mod(point(2))) || Point.ZERO.equals(other.mod(point(2)))) { + return modRange(minValue(isLong), maxValue(isLong), 2, 1); + } + return isLong ? Range.LONG_RANGE : Range.INT_RANGE; } - return ranges; - } - private static LongRangeSet bitwiseAnd(long leftFrom, long leftTo, long rightFrom, long rightTo, BitString globalMask) { - if (leftFrom == leftTo && rightFrom == rightTo) { - return point(leftFrom & rightFrom & (globalMask.myBits | ~globalMask.myMask)); - } - if (leftFrom == leftTo && Long.bitCount(leftFrom + 1) == 1) { - return bitwiseMask(rightFrom, rightTo, leftFrom); - } - if (rightFrom == rightTo && Long.bitCount(rightFrom + 1) == 1) { - return bitwiseMask(leftFrom, leftTo, rightFrom); - } - BitString leftBits = BitString.fromRange(leftFrom, leftTo); - BitString rightBits = BitString.fromRange(rightFrom, rightTo); - return fromBits(leftBits.and(rightBits).and(globalMask)); - } - - /** - * Returns the range after applying the mask to the input range which looks like 0..01..1 in binary - * - * @param from input range start - * @param to input range end - * @param mask mask - * @return range set after applying the mask - */ - private static LongRangeSet bitwiseMask(long from, long to, long mask) { - if (to - from > mask) { - return range(0, mask); - } - long min = from & mask; - long max = to & mask; - assert min != max; - if (min < max) { - return range(min, max); - } - return new RangeSet(new long[]{ - 0, - max, - min, - mask - }); - } - - /** - * Creates a set which contains all the numbers satisfying the supplied BitString. The resulting set may - * contain more values than necessary. - * - * @param bits a BitString - * @return a new LongRangeSet - */ - private static LongRangeSet fromBits(BitString bits) { - if (bits.myMask == -1) { - return point(bits.myBits); - } - long from = 0; - int i = Long.SIZE - 1; - while (i >= 0 && bits.get(i) != ThreeState.UNSURE) { - if (bits.get(i) == ThreeState.YES) { - from = setBit(from, i); - } - i--; - } - long to = ((i == Long.SIZE - 1 ? 0 : 1L << (i + 1)) - 1) | from; - int j = 0; - while (j < i && bits.get(j) != ThreeState.UNSURE) { - if (bits.get(j) == ThreeState.NO) { - to = clearBit(to, j); - } - j++; - } - if (i == j) { - return point(from).unite(point(to)); - } - long modBits = -1; - for (int rem = 0; rem < Long.SIZE; rem++) { - for (int pos = 0; pos < 6; pos++) { - ThreeState bit = bits.get(pos); - if (bit == ThreeState.fromBoolean(!isSet(rem, pos))) { - modBits = clearBit(modBits, rem); - break; - } - } - } - if (from >= 0 && to < 0) { - from = Long.MIN_VALUE; - to = Long.MAX_VALUE; + LongRangeSet plusWiden(LongRangeSet other, boolean isLong) { + if (this instanceof Point && other instanceof Point) { + long val1 = ((Point)this).myValue; + long val2 = ((Point)other).myValue; + int tzb1 = val1 == 0 ? 0 : Long.numberOfTrailingZeros(val1); + int tzb2 = val2 == 0 ? 0 : Long.numberOfTrailingZeros(val2); + LongRangeSet constVal; + int mod; + if (tzb1 > tzb2) { + constVal = other; + mod = 1 << (Math.min(6, tzb1)); + } + else { + constVal = this; + mod = 1 << (Math.min(6, tzb2)); + } + if (mod < 2) { + return isLong ? Range.LONG_RANGE : Range.INT_RANGE; + } + return modRange(minValue(isLong), maxValue(isLong), mod, 1).plus(constVal, isLong); + } + if (this instanceof Point && other instanceof ModRange) { + ModRange modRange = (ModRange)other; + long value = ((Point)this).myValue; + if (value % modRange.myMod == 0) { + return modRange(minValue(isLong), maxValue(isLong), modRange.myMod, modRange.myBits); + } + if (modRange.myBits == 1) { + return this.plus(other, isLong); + } + if (value >= -64 && value < 64) { + int gcd = gcd(Math.abs((int)value), modRange.myMod); + if (gcd > 1) { + long count = modRange.myMod / gcd; + long bits = 0; + for (int i = 0; i < count; i++) { + bits |= (modRange.myBits >>> (i * gcd)) & ((1L << gcd) - 1); + } + return modRange(minValue(isLong), maxValue(isLong), gcd, bits); + } + } + } + if (other instanceof Point && this instanceof ModRange) { + return other.plusWiden(this, isLong); + } + return isLong ? Range.LONG_RANGE : Range.INT_RANGE; } - return from < to ? modRange(from, to, Long.SIZE, modBits) : modRange(to, from, Long.SIZE, modBits); - } - private static String formatNumber(long value) { - if (value == Long.MAX_VALUE) { - return "Long.MAX_VALUE"; - } - if (value == Long.MAX_VALUE - 1) { - return "Long.MAX_VALUE-1"; - } - if (value == Long.MIN_VALUE) { - return "Long.MIN_VALUE"; - } - if (value == Long.MIN_VALUE + 1) { - return "Long.MIN_VALUE+1"; - } - if (value == Integer.MAX_VALUE) { - return "Integer.MAX_VALUE"; - } - if (value == Integer.MAX_VALUE - 1) { - return "Integer.MAX_VALUE-1"; - } - if (value == Integer.MIN_VALUE) { - return "Integer.MIN_VALUE"; - } - if (value == Integer.MIN_VALUE + 1) { - return "Integer.MIN_VALUE+1"; - } - return String.valueOf(value); - } - - /** - * Returns a stream of all values from this range. Be careful: could be huge - * - * @return a new stream - */ - public abstract LongStream stream(); - - /** - * @return an empty set - */ - public static LongRangeSet empty() { - return Empty.EMPTY; - } - - /** - * @return a set containing all possible long values - */ - @Nonnull - public static LongRangeSet all() { - return Range.LONG_RANGE; - } - - /** - * Creates a set containing single given value - * - * @param value a value to be included into the set - * @return a new set - */ - public static LongRangeSet point(long value) { - return value == 0 ? Point.ZERO : value == 1 ? Point.ONE : new Point(value); - } - - /** - * Creates a set containing single value which is equivalent to supplied boxed constant (if its type is supported) - * - * @param val constant to create a set from - * @return new LongRangeSet or null if constant type is unsupported - */ - @Nullable - public static LongRangeSet fromConstant(Object val) { - if (val instanceof Byte || val instanceof Short || val instanceof Integer || val instanceof Long) { - return point(((Number) val).longValue()); - } else if (val instanceof Character) { - return point(((Character) val).charValue()); - } - return null; - } - - /** - * Creates a new set which contains all the numbers between from (inclusive) and to (inclusive) - * - * @param from lower bound - * @param to upper bound (must be greater or equal to {@code from}) - * @return a new LongRangeSet - */ - public static LongRangeSet range(long from, long to) { - return from == to ? point(from) : new Range(from, to); - } - - /** - * Creates a new set which contains all the numbers between from (inclusive) and to (inclusive) - * which are equal to any of the set bits in supplied bit-mask modulo supplied mod - * - * @param from lower bound - * @param to upper bound (must be greater or equal to {@code from}) - * @param mod modulus; only modulus up to 64 is supported; bigger modulus is not tracked (instead full range will be returned) - * @param bits a bit-mask which represents allowed remainders: 0b1 represents that only (x % mod == 0) numbers are included; - * 0b10 represents that only (x % mod == 1) numbers are included and so on. - * @return a new LongRangeSet - */ - public static LongRangeSet modRange(long from, long to, long mod, long bits) { - if (mod <= 0) { - throw new IllegalArgumentException(); - } - if (bits == 0) { - return empty(); - } - if (mod == 1 || mod > Long.SIZE) { - return range(from, to); - } - int intMod = (int) mod; - // Adjust from/to: they should point to minimal/maximal value which is actually allowed by mod bits - long rotatedFrom = rotateRemainders(bits, intMod, remainder(from, intMod)); - from += Long.numberOfTrailingZeros(rotatedFrom); - int toBit = (remainder(to, intMod) + 1) % intMod; - long rotatedTo = rotateRemainders(bits, intMod, toBit); - to -= intMod - (Long.SIZE - Long.numberOfLeadingZeros(rotatedTo)); - - if (from > to) { - return empty(); - } - if (from == to) { - return new Point(from); - } - // Try to reduce mod if the range is too small - long length = to - from; - if (length > 0 && length <= intMod / 2) { - for (int newMod = (int) length; newMod <= intMod / 2; newMod++) { - if (intMod % newMod == 0) { - long newBits = 0; - // `to` could be Long.MAX_VALUE; so `i >= from` condition is important to react on possible overflow - for (long i = from; i >= from && i <= to; i++) { - if (isSet(bits, remainder(i, intMod))) { - newBits = setBit(newBits, remainder(i, newMod)); - } - } - intMod = newMod; - bits = newBits; - if (bits == 0) { + public abstract boolean isCardinalityBigger(long cutoff); + + public abstract LongRangeSet castTo(PsiPrimitiveType type); + + /** + * Returns a range which represents all the possible values after applying {@link Math#abs(int)} or {@link Math#abs(long)} + * to the values from this set + * + * @param isLong whether {@link Math#abs(long)} is applied + * @return a new range + */ + public abstract LongRangeSet abs(boolean isLong); + + /** + * Returns a range which represents all the possible values after applying unary minus + * to the values from this set + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ + public abstract LongRangeSet negate(boolean isLong); + + /** + * Returns a range which represents all the possible values after performing an addition between any value from this range + * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. + * Guaranteed to be commutative. + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ + public abstract LongRangeSet plus(LongRangeSet other, boolean isLong); + + /** + * Returns a range which represents all the possible values after performing an addition between any value from this range + * and any value from other range. The resulting range may contain some more values which cannot be produced by addition. + * + * @param isLong whether result should be truncated to {@code int} + * @return a new range + */ + public LongRangeSet minus(LongRangeSet other, boolean isLong) { + return plus(other.negate(isLong), isLong); + } + + /** + * Returns a range which represents all the possible values after applying {@code x | y} operation for + * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain + * some more values. + * + * @param other other set to perform bitwise-or with + * @return a new range + */ + public LongRangeSet bitwiseOr(LongRangeSet other, boolean isLong) { + if (this.isEmpty() || other.isEmpty()) { return empty(); - } - break; } - } - } - // Try to reduce mod if bits are symmetrical - if (intMod % 2 == 0) { - int halfMod = intMod / 2; - long rightHalf = extractBits(bits, 0, halfMod); - long leftHalf = extractBits(bits, halfMod, halfMod); - if (rightHalf == leftHalf) { - return modRange(from, to, halfMod, leftHalf); - } - } - if (Long.bitCount(bits) == intMod) { - return range(from, to); + LongRangeSet result = fromBits(getBitwiseMask().or(other.getBitwiseMask())); + return isLong ? result : result.intersect(Range.INT_RANGE); } - ModRange range = new ModRange(from, to, intMod, bits); - LongRangeSet fullRange = range(from, to); - return range.contains(fullRange) ? fullRange : range; - } - - abstract long[] asRanges(); - - static String toString(long from, long to) { - return formatNumber(from) + (from == to ? "" : (to - from == 1 ? ", " : "..") + formatNumber(to)); - } - - static long minValue(boolean isLong) { - return isLong ? Long.MIN_VALUE : Integer.MIN_VALUE; - } - - static long maxValue(boolean isLong) { - return isLong ? Long.MAX_VALUE : Integer.MAX_VALUE; - } - - /** - * @return LongRangeSet describing possible array or string indices (from 0 to Integer.MAX_VALUE) - */ - public static LongRangeSet indexRange() { - return Range.INDEX_RANGE; - } - - /** - * Creates a range for given type (for primitives and boxed: values range) - * - * @param type type to create a range for - * @return a range or null if type is not supported - */ - @Nullable - public static LongRangeSet fromType(PsiType type) { - if (!(type instanceof PsiPrimitiveType) && !TypeConversionUtil.isPrimitiveWrapper(type)) { - return null; - } - type = PsiPrimitiveType.getOptionallyUnboxedType(type); - if (type != null) { - if (type.equals(PsiType.BYTE)) { - return Range.BYTE_RANGE; - } - if (type.equals(PsiType.CHAR)) { - return Range.CHAR_RANGE; - } - if (type.equals(PsiType.SHORT)) { - return Range.SHORT_RANGE; - } - if (type.equals(PsiType.INT)) { - return Range.INT_RANGE; - } - if (type.equals(PsiType.LONG)) { - return all(); - } + /** + * Returns a range which represents all the possible values after applying {@code x ^ y} operation for + * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain + * some more values. + * + * @param other other set to perform bitwise-xor with + * @return a new range + */ + public LongRangeSet bitwiseXor(LongRangeSet other, boolean isLong) { + if (this.isEmpty() || other.isEmpty()) { + return empty(); + } + LongRangeSet result = fromBits(getBitwiseMask().xor(other.getBitwiseMask())); + return isLong ? result : result.intersect(Range.INT_RANGE); } - return null; - } - @Nonnull - public static LongRangeSet fromPsiElement(PsiModifierListOwner owner) { - if (owner == null) { - return all(); - } - return StreamEx.of(AnnotationUtil.findAnnotation(owner, JETBRAINS_RANGE), owner.getAnnotation(JETBRAINS_RANGE)) - .nonNull() - .append(AnnotationUtil.findAnnotations(owner, ANNOTATIONS)) - .map(LongRangeSet::fromAnnotation).foldLeft(all(), LongRangeSet::intersect); - } - - private static LongRangeSet fromAnnotation(PsiAnnotation annotation) { - switch (Objects.requireNonNull(annotation.getQualifiedName())) { - case JETBRAINS_RANGE: - case CHECKER_RANGE: - Long from = AnnotationUtil.getLongAttributeValue(annotation, "from"); - Long to = AnnotationUtil.getLongAttributeValue(annotation, "to"); - if (from != null && to != null && to >= from) { - return range(from, to); - } - break; - case VALIDATION_MIN: - Long minValue = AnnotationUtil.getLongAttributeValue(annotation, "value"); - if (minValue != null && annotation.findDeclaredAttributeValue("groups") == null) { - return range(minValue, Long.MAX_VALUE); - } - break; - case VALIDATION_MAX: - Long maxValue = AnnotationUtil.getLongAttributeValue(annotation, "value"); - if (maxValue != null && annotation.findDeclaredAttributeValue("groups") == null) { - return range(Long.MIN_VALUE, maxValue); - } - break; - case CHECKER_GTE_NEGATIVE_ONE: - return range(-1, Long.MAX_VALUE); - case JSR305_NONNEGATIVE: - case CHECKER_NON_NEGATIVE: - return range(0, Long.MAX_VALUE); - case CHECKER_POSITIVE: - return range(1, Long.MAX_VALUE); - } - return all(); - } - - static LongRangeSet fromRanges(long[] ranges, int bound) { - if (bound == 0) { - return Empty.EMPTY; - } else if (bound == 2) { - return range(ranges[0], ranges[1]); - } else { - return new RangeSet(Arrays.copyOfRange(ranges, 0, bound)); - } - } - - /** - * Creates a LongRangeSet of values {@code x} for which {@code remainders.contains(x % mod)}. - * May include more values as well. - * - * @param mod a divisor - * @param remainders set of allowed remainders - * @return set of values which may produce supplied remainders when divided by mod. - */ - public static LongRangeSet fromRemainder(long mod, LongRangeSet remainders) { - if (remainders.isEmpty()) { - return empty(); - } - long min = remainders.min() > 0 ? 1 : Long.MIN_VALUE; - long max = remainders.max() < 0 ? -1 : Long.MAX_VALUE; - if (mod > 1 && mod <= Long.SIZE) { - long bits = remainders.contains(0) ? 1 : 0; - for (int rem = 1; rem < mod; rem++) { - if (remainders.contains(rem) || remainders.contains(rem - mod)) { - bits = setBit(bits, rem); - } - } - return modRange(min, max, mod, bits); + /** + * Returns a range which represents all the possible values after applying {@code x & y} operation for + * all {@code x} from this set and for all {@code y} from the other set. The resulting set may contain + * some more values. + * + * @param other other set to perform bitwise-and with + * @return a new range + */ + public LongRangeSet bitwiseAnd(LongRangeSet other) { + if (this.isEmpty() || other.isEmpty()) { + return empty(); + } + long[] left = splitAtZero(asRanges()); + long[] right = splitAtZero(other.asRanges()); + // More than three intervals --> convert to single interval to make result more compact (though probably less precise) + if (left.length > 6) { + left = splitAtZero(new long[]{ + left[0], + left[left.length - 1] + }); + } + if (right.length > 6) { + right = splitAtZero(new long[]{ + right[0], + right[right.length - 1] + }); + } + BitString globalMask = getBitwiseMask().and(other.getBitwiseMask()); + globalMask = new BitString(globalMask.myBits | ~globalMask.myMask, -1L); + LongRangeSet result = empty(); + for (int i = 0; i < left.length; i += 2) { + for (int j = 0; j < right.length; j += 2) { + result = result.unite(bitwiseAnd(left[i], left[i + 1], right[j], right[j + 1], globalMask)); + } + } + return result; } - return range(min, max); - } - static final class Empty extends LongRangeSet { - static final LongRangeSet EMPTY = new Empty(); + abstract public LongRangeSet mul(LongRangeSet multiplier, boolean isLong); - @Nonnull - @Override - public LongRangeSet subtract(@Nonnull LongRangeSet other) { - return this; + BitString getBitwiseMask() { + if (isEmpty()) { + return BitString.UNSURE; + } + return BitString.fromRange(min(), max()); + } + + /** + * Returns a range which represents all the possible values after applying {@code x / y} operation for + * all {@code x} from this set and for all {@code y} from the divisor set. The resulting set may contain + * some more values. Division by zero yields an empty set of possible results. + * + * @param divisor divisor set to divide by + * @param isLong whether the operation is performed on long type (if false, the int type is assumed). This only changes the special + * treatment of {@code MIN_VALUE/-1} division; other division results do not depend on the resulting type. + * @return a new range + */ + public LongRangeSet div(LongRangeSet divisor, boolean isLong) { + if (divisor.isEmpty() || divisor.equals(Point.ZERO)) { + return empty(); + } + LongRangeSet dividend = this; + if (!isLong) { + divisor = divisor.intersect(Range.INT_RANGE); + dividend = dividend.intersect(Range.INT_RANGE); + } + long[] left = splitAtZero(dividend.asRanges()); + long[] right = splitAtZero(new long[]{ + divisor.min(), + divisor.max() + }); + LongRangeSet result = empty(); + for (int i = 0; i < left.length; i += 2) { + for (int j = 0; j < right.length; j += 2) { + result = result.unite(divide(left[i], left[i + 1], right[j], right[j + 1], isLong)); + } + } + return result; } - @Nonnull - @Override - public LongRangeSet intersect(@Nonnull LongRangeSet other) { - return this; + /** + * Checks whether subtraction of this and other range may overflow + * + * @param other range to subtract from this range + * @param isLong whether subtraction should be performed on long values (otherwise int is assumed) + * @return true if result may overflow, false if it never overflows + */ + public boolean subtractionMayOverflow(LongRangeSet other, boolean isLong) { + long leftMin = min(); + long leftMax = max(); + long rightMin = other.min(); + long rightMax = other.max(); + return isLong + ? overflowsLong(leftMin, rightMax) || overflowsLong(leftMax, rightMin) + : overflowsInt(leftMin, rightMax) || overflowsInt(leftMax, rightMin); } - @Nonnull - @Override - public LongRangeSet unite(@Nonnull LongRangeSet other) { - return other; + private static boolean overflowsInt(long a, long b) { + long diff = a - b; + return diff < Integer.MIN_VALUE || diff > Integer.MAX_VALUE; } - @Override - public long min() { - throw new NoSuchElementException(); + private static boolean overflowsLong(long a, long b) { + long diff = a - b; + // Hacker's Delight 2nd Edition, 2-13 Overflow Detection + return ((a ^ b) & (a ^ diff)) < 0; } - @Override - public long max() { - throw new NoSuchElementException(); + private static LongRangeSet divide(long dividendMin, long dividendMax, long divisorMin, long divisorMax, boolean isLong) { + if (divisorMin == 0) { + if (divisorMax == 0) { + return empty(); + } + divisorMin = 1; + } + if (dividendMin >= 0) { + return divisorMin > 0 + ? range(dividendMin / divisorMax, dividendMax / divisorMin) + : range(dividendMax / divisorMax, dividendMin / divisorMin); + } + if (divisorMin > 0) { + return range(dividendMin / divisorMin, dividendMax / divisorMax); + } + long minValue = minValue(isLong); + if (dividendMin == minValue && divisorMax == -1) { + // MIN_VALUE/-1 = MIN_VALUE + return point(minValue) + .unite(divisorMin == -1 ? empty() : range(dividendMin / divisorMin, dividendMin / (divisorMax - 1))) + .unite(dividendMax == minValue ? empty() : range(dividendMax / divisorMin, (dividendMin + 1) / divisorMax)); + } + return range(dividendMax / divisorMin, dividendMin / divisorMax); + } + + /** + * Returns a range which represents all the possible values after applying {@code x << y} operation for + * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain + * some more values. + * + * @param shiftSize set of possible shift sizes (number of bits to shift to the left) + * @param isLong whether the operation is performed on long type (if false, the int type is assumed). + * @return a new range + */ + public LongRangeSet shiftLeft(LongRangeSet shiftSize, boolean isLong) { + if (isEmpty() || shiftSize.isEmpty()) { + return empty(); + } + if (shiftSize instanceof Point) { + long shift = ((Point)shiftSize).myValue & ((isLong ? Long.SIZE : Integer.SIZE) - 1); + return point(1L << shift).mul(this, isLong); + } + return isLong ? Range.LONG_RANGE : Range.INT_RANGE; } - @Override - public boolean intersects(LongRangeSet other) { - return false; + /** + * Returns a range which represents all the possible values after applying {@code x >> y} operation for + * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain + * some more values. + * + * @param shiftSize set of possible shift sizes (number of bits to shift to the right) + * @param isLong whether the operation is performed on long type (if false, the int type is assumed). + * @return a new range + */ + public LongRangeSet shiftRight(LongRangeSet shiftSize, boolean isLong) { + return doShiftRight(shiftSize, isLong, false); + } + + /** + * Returns a range which represents all the possible values after applying {@code x >>> y} operation for + * all {@code x} from this set and for all {@code y} from the shiftSize set. The resulting set may contain + * some more values. + * + * @param shiftSize set of possible shift sizes (number of bits to shift to the right) + * @param isLong whether the operation is performed on long type (if false, the int type is assumed). + * @return a new range + */ + public LongRangeSet unsignedShiftRight(LongRangeSet shiftSize, boolean isLong) { + return doShiftRight(shiftSize, isLong, true); + } + + private LongRangeSet doShiftRight(LongRangeSet shiftSize, boolean isLong, boolean unsigned) { + if (isEmpty() || shiftSize.isEmpty()) { + return empty(); + } + int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1; + if (shiftSize.min() < 0 || shiftSize.max() > maxShift) { + shiftSize = shiftSize.bitwiseAnd(point(maxShift)); + } + long min = shiftSize.min(); + long max = shiftSize.max(); + LongRangeSet negative = intersect(range(minValue(isLong), -1)); + LongRangeSet positive = intersect(range(0, maxValue(isLong))); + LongRangeSet positiveResult = positive.shrPositive(min, max, isLong); + LongRangeSet negativeResult; + LongRangeSet negativeComplement = point(-1).minus(negative, isLong); // -1-negative + if (unsigned) { + if (min == 0) { + positiveResult = positiveResult.unite(negative); + if (max == 0) { + return positiveResult; + } + min++; + } + // for x < 0, y > 0, x >>> y = (MAX_VALUE - ((-1-x) >> 1)) >> (y-1) + negativeResult = point(maxValue(isLong)).minus(negativeComplement.shrPositive(1, 1, isLong), isLong) + .shrPositive(min - 1, max - 1, isLong); + } + else { + negativeResult = point(-1).minus(negativeComplement.shrPositive(min, max, isLong), isLong); + } + return positiveResult.unite(negativeResult); } - @Override - public boolean contains(long value) { - return false; + private LongRangeSet shrPositive(long min, long max, boolean isLong) { + if (isEmpty()) { + return empty(); + } + int maxShift = (isLong ? Long.SIZE : Integer.SIZE) - 1; + if (max == maxShift) { + return min == max ? Point.ZERO : Point.ZERO.unite(div(range(1L << min, 1L << (max - 1)), isLong)); + } + return div(range(1L << min, 1L << max), isLong); } - @Override - public boolean contains(@Nonnull LongRangeSet other) { - return other.isEmpty(); - } + /** + * Returns a range which represents all the possible values after applying {@code x % y} operation for + * all {@code x} from this set and for all {@code y} from the divisor set. The resulting set may contain + * some more values. Division by zero yields an empty set of possible results. + * + * @param divisor divisor set to divide by + * @return a new range + */ + abstract public LongRangeSet mod(LongRangeSet divisor); - @Override - public String getPresentationText(PsiType type) { - return JavaAnalysisBundle.message("long.range.set.presentation.empty"); + private static long[] splitAtZero(long[] ranges) { + for (int i = 0; i < ranges.length; i += 2) { + if (ranges[i] < 0 && ranges[i + 1] >= 0) { + long[] result = new long[ranges.length + 2]; + System.arraycopy(ranges, 0, result, 0, i + 1); + result[i + 1] = -1; + System.arraycopy(ranges, i + 1, result, i + 3, ranges.length - i - 1); + return result; + } + } + return ranges; } - @Override - public boolean isCardinalityBigger(long cutoff) { - return cutoff < 0; + private static LongRangeSet bitwiseAnd(long leftFrom, long leftTo, long rightFrom, long rightTo, BitString globalMask) { + if (leftFrom == leftTo && rightFrom == rightTo) { + return point(leftFrom & rightFrom & (globalMask.myBits | ~globalMask.myMask)); + } + if (leftFrom == leftTo && Long.bitCount(leftFrom + 1) == 1) { + return bitwiseMask(rightFrom, rightTo, leftFrom); + } + if (rightFrom == rightTo && Long.bitCount(rightFrom + 1) == 1) { + return bitwiseMask(leftFrom, leftTo, rightFrom); + } + BitString leftBits = BitString.fromRange(leftFrom, leftTo); + BitString rightBits = BitString.fromRange(rightFrom, rightTo); + return fromBits(leftBits.and(rightBits).and(globalMask)); } - @Override - public LongRangeSet castTo(PsiPrimitiveType type) { - if (TypeConversionUtil.isIntegralNumberType(type)) { - return this; - } - throw new IllegalArgumentException(type.toString()); + /** + * Returns the range after applying the mask to the input range which looks like 0..01..1 in binary + * + * @param from input range start + * @param to input range end + * @param mask mask + * @return range set after applying the mask + */ + private static LongRangeSet bitwiseMask(long from, long to, long mask) { + if (to - from > mask) { + return range(0, mask); + } + long min = from & mask; + long max = to & mask; + assert min != max; + if (min < max) { + return range(min, max); + } + return new RangeSet(new long[]{ + 0, + max, + min, + mask + }); } - @Nonnull - @Override - public LongRangeSet abs(boolean isLong) { - return this; + /** + * Creates a set which contains all the numbers satisfying the supplied BitString. The resulting set may + * contain more values than necessary. + * + * @param bits a BitString + * @return a new LongRangeSet + */ + private static LongRangeSet fromBits(BitString bits) { + if (bits.myMask == -1) { + return point(bits.myBits); + } + long from = 0; + int i = Long.SIZE - 1; + while (i >= 0 && bits.get(i) != ThreeState.UNSURE) { + if (bits.get(i) == ThreeState.YES) { + from = setBit(from, i); + } + i--; + } + long to = ((i == Long.SIZE - 1 ? 0 : 1L << (i + 1)) - 1) | from; + int j = 0; + while (j < i && bits.get(j) != ThreeState.UNSURE) { + if (bits.get(j) == ThreeState.NO) { + to = clearBit(to, j); + } + j++; + } + if (i == j) { + return point(from).unite(point(to)); + } + long modBits = -1; + for (int rem = 0; rem < Long.SIZE; rem++) { + for (int pos = 0; pos < 6; pos++) { + ThreeState bit = bits.get(pos); + if (bit == ThreeState.fromBoolean(!isSet(rem, pos))) { + modBits = clearBit(modBits, rem); + break; + } + } + } + if (from >= 0 && to < 0) { + from = Long.MIN_VALUE; + to = Long.MAX_VALUE; + } + return from < to ? modRange(from, to, Long.SIZE, modBits) : modRange(to, from, Long.SIZE, modBits); } - @Nonnull - @Override - public LongRangeSet negate(boolean isLong) { - return this; + private static String formatNumber(long value) { + if (value == Long.MAX_VALUE) { + return "Long.MAX_VALUE"; + } + if (value == Long.MAX_VALUE - 1) { + return "Long.MAX_VALUE-1"; + } + if (value == Long.MIN_VALUE) { + return "Long.MIN_VALUE"; + } + if (value == Long.MIN_VALUE + 1) { + return "Long.MIN_VALUE+1"; + } + if (value == Integer.MAX_VALUE) { + return "Integer.MAX_VALUE"; + } + if (value == Integer.MAX_VALUE - 1) { + return "Integer.MAX_VALUE-1"; + } + if (value == Integer.MIN_VALUE) { + return "Integer.MIN_VALUE"; + } + if (value == Integer.MIN_VALUE + 1) { + return "Integer.MIN_VALUE+1"; + } + return String.valueOf(value); } - @Nonnull - @Override - public LongRangeSet plus(LongRangeSet other, boolean isLong) { - return this; - } + /** + * Returns a stream of all values from this range. Be careful: could be huge + * + * @return a new stream + */ + public abstract LongStream stream(); - @Override - public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { - return this; + /** + * @return an empty set + */ + public static LongRangeSet empty() { + return Empty.EMPTY; } - @Nonnull - @Override - public LongRangeSet mod(LongRangeSet divisor) { - return empty(); + /** + * @return a set containing all possible long values + */ + public static LongRangeSet all() { + return Range.LONG_RANGE; } - @Override - public LongStream stream() { - return LongStream.empty(); + /** + * Creates a set containing single given value + * + * @param value a value to be included into the set + * @return a new set + */ + public static LongRangeSet point(long value) { + return value == 0 ? Point.ZERO : value == 1 ? Point.ONE : new Point(value); } - @Override - long[] asRanges() { - return new long[0]; + /** + * Creates a set containing single value which is equivalent to supplied boxed constant (if its type is supported) + * + * @param val constant to create a set from + * @return new LongRangeSet or null if constant type is unsupported + */ + @Nullable + public static LongRangeSet fromConstant(Object val) { + if (val instanceof Byte || val instanceof Short || val instanceof Integer || val instanceof Long) { + return point(((Number)val).longValue()); + } + else if (val instanceof Character charValue) { + return point(charValue); + } + return null; } - @Override - public int hashCode() { - return 2154231; - } + /** + * Creates a new set which contains all the numbers between from (inclusive) and to (inclusive) + * + * @param from lower bound + * @param to upper bound (must be greater or equal to {@code from}) + * @return a new LongRangeSet + */ + public static LongRangeSet range(long from, long to) { + return from == to ? point(from) : new Range(from, to); + } + + /** + * Creates a new set which contains all the numbers between from (inclusive) and to (inclusive) + * which are equal to any of the set bits in supplied bit-mask modulo supplied mod + * + * @param from lower bound + * @param to upper bound (must be greater or equal to {@code from}) + * @param mod modulus; only modulus up to 64 is supported; bigger modulus is not tracked (instead full range will be returned) + * @param bits a bit-mask which represents allowed remainders: 0b1 represents that only (x % mod == 0) numbers are included; + * 0b10 represents that only (x % mod == 1) numbers are included and so on. + * @return a new LongRangeSet + */ + public static LongRangeSet modRange(long from, long to, long mod, long bits) { + if (mod <= 0) { + throw new IllegalArgumentException(); + } + if (bits == 0) { + return empty(); + } + if (mod == 1 || mod > Long.SIZE) { + return range(from, to); + } + int intMod = (int)mod; + // Adjust from/to: they should point to minimal/maximal value which is actually allowed by mod bits + long rotatedFrom = rotateRemainders(bits, intMod, remainder(from, intMod)); + from += Long.numberOfTrailingZeros(rotatedFrom); + int toBit = (remainder(to, intMod) + 1) % intMod; + long rotatedTo = rotateRemainders(bits, intMod, toBit); + to -= intMod - (Long.SIZE - Long.numberOfLeadingZeros(rotatedTo)); - @Override - public boolean equals(Object obj) { - return obj == this; - } + if (from > to) { + return empty(); + } + if (from == to) { + return new Point(from); + } + // Try to reduce mod if the range is too small + long length = to - from; + if (length > 0 && length <= intMod / 2) { + for (int newMod = (int)length; newMod <= intMod / 2; newMod++) { + if (intMod % newMod == 0) { + long newBits = 0; + // `to` could be Long.MAX_VALUE; so `i >= from` condition is important to react on possible overflow + for (long i = from; i >= from && i <= to; i++) { + if (isSet(bits, remainder(i, intMod))) { + newBits = setBit(newBits, remainder(i, newMod)); + } + } + intMod = newMod; + bits = newBits; + if (bits == 0) { + return empty(); + } + break; + } + } + } + // Try to reduce mod if bits are symmetrical + if (intMod % 2 == 0) { + int halfMod = intMod / 2; + long rightHalf = extractBits(bits, 0, halfMod); + long leftHalf = extractBits(bits, halfMod, halfMod); + if (rightHalf == leftHalf) { + return modRange(from, to, halfMod, leftHalf); + } + } + if (Long.bitCount(bits) == intMod) { + return range(from, to); + } - @Override - public String toString() { - return "{}"; + ModRange range = new ModRange(from, to, intMod, bits); + LongRangeSet fullRange = range(from, to); + return range.contains(fullRange) ? fullRange : range; } - } - static final class Point extends LongRangeSet { - static final Point ZERO = new Point(0); - static final Point ONE = new Point(1); + abstract long[] asRanges(); - final long myValue; - - Point(long value) { - myValue = value; + static String toString(long from, long to) { + return formatNumber(from) + (from == to ? "" : (to - from == 1 ? ", " : "..") + formatNumber(to)); } - @Override - public String getPresentationText(PsiType type) { - return formatNumber(myValue); + static long minValue(boolean isLong) { + return isLong ? Long.MIN_VALUE : Integer.MIN_VALUE; } - @Override - public boolean isCardinalityBigger(long cutoff) { - return cutoff < 1; + static long maxValue(boolean isLong) { + return isLong ? Long.MAX_VALUE : Integer.MAX_VALUE; } - @Nonnull - @Override - public LongRangeSet subtract(@Nonnull LongRangeSet other) { - return other.contains(myValue) ? Empty.EMPTY : this; + /** + * @return LongRangeSet describing possible array or string indices (from 0 to Integer.MAX_VALUE) + */ + public static LongRangeSet indexRange() { + return Range.INDEX_RANGE; } - @Nonnull - @Override - public LongRangeSet intersect(@Nonnull LongRangeSet other) { - return other.contains(myValue) ? this : Empty.EMPTY; + /** + * Creates a range for given type (for primitives and boxed: values range) + * + * @param type type to create a range for + * @return a range or null if type is not supported + */ + @Nullable + public static LongRangeSet fromType(PsiType type) { + if (!(type instanceof PsiPrimitiveType) && !TypeConversionUtil.isPrimitiveWrapper(type)) { + return null; + } + type = PsiPrimitiveType.getOptionallyUnboxedType(type); + if (type != null) { + if (type.equals(PsiType.BYTE)) { + return Range.BYTE_RANGE; + } + if (type.equals(PsiType.CHAR)) { + return Range.CHAR_RANGE; + } + if (type.equals(PsiType.SHORT)) { + return Range.SHORT_RANGE; + } + if (type.equals(PsiType.INT)) { + return Range.INT_RANGE; + } + if (type.equals(PsiType.LONG)) { + return all(); + } + } + return null; } - @Override - public long min() { - return myValue; + public static LongRangeSet fromPsiElement(PsiModifierListOwner owner) { + if (owner == null) { + return all(); + } + return StreamEx.of(AnnotationUtil.findAnnotation(owner, JETBRAINS_RANGE), owner.getAnnotation(JETBRAINS_RANGE)) + .nonNull() + .append(AnnotationUtil.findAnnotations(owner, ANNOTATIONS)) + .map(LongRangeSet::fromAnnotation).foldLeft(all(), LongRangeSet::intersect); } - @Override - public long max() { - return myValue; + private static LongRangeSet fromAnnotation(PsiAnnotation annotation) { + switch (Objects.requireNonNull(annotation.getQualifiedName())) { + case JETBRAINS_RANGE: + case CHECKER_RANGE: + Long from = AnnotationUtil.getLongAttributeValue(annotation, "from"); + Long to = AnnotationUtil.getLongAttributeValue(annotation, "to"); + if (from != null && to != null && to >= from) { + return range(from, to); + } + break; + case VALIDATION_MIN: + Long minValue = AnnotationUtil.getLongAttributeValue(annotation, "value"); + if (minValue != null && annotation.findDeclaredAttributeValue("groups") == null) { + return range(minValue, Long.MAX_VALUE); + } + break; + case VALIDATION_MAX: + Long maxValue = AnnotationUtil.getLongAttributeValue(annotation, "value"); + if (maxValue != null && annotation.findDeclaredAttributeValue("groups") == null) { + return range(Long.MIN_VALUE, maxValue); + } + break; + case CHECKER_GTE_NEGATIVE_ONE: + return range(-1, Long.MAX_VALUE); + case JSR305_NONNEGATIVE: + case CHECKER_NON_NEGATIVE: + return range(0, Long.MAX_VALUE); + case CHECKER_POSITIVE: + return range(1, Long.MAX_VALUE); + } + return all(); } - @Override - public Long getConstantValue() { - return myValue; + static LongRangeSet fromRanges(long[] ranges, int bound) { + if (bound == 0) { + return Empty.EMPTY; + } + else if (bound == 2) { + return range(ranges[0], ranges[1]); + } + else { + return new RangeSet(Arrays.copyOfRange(ranges, 0, bound)); + } } - @Nonnull - @Override - public LongRangeSet unite(@Nonnull LongRangeSet other) { - if (other.isEmpty() || other == this) { - return this; - } - if (other.contains(myValue)) { - return other; - } - if (other instanceof Point) { - long value1 = Math.min(myValue, ((Point) other).myValue); - long value2 = Math.max(myValue, ((Point) other).myValue); - return value1 + 1 == value2 - ? range(value1, value2) - : new RangeSet(new long[]{ - value1, - value1, - value2, - value2 - }); - } - if (other instanceof ModRange) { - return other.unite(this); - } - if (other instanceof Range) { - if (myValue < other.min()) { - return myValue + 1 == other.min() - ? range(myValue, other.max()) - : new RangeSet(new long[]{ - myValue, - myValue, - other.min(), - other.max() - }); - } else { - assert myValue > other.max(); - return myValue - 1 == other.max() - ? range(other.min(), myValue) - : new RangeSet(new long[]{ - other.min(), - other.max(), - myValue, - myValue - }); - } - } - long[] longs = other.asRanges(); - int pos = -Arrays.binarySearch(longs, myValue) - 1; - assert pos >= 0 && pos % 2 == 0; - boolean touchLeft = pos > 0 && longs[pos - 1] + 1 == myValue; - boolean touchRight = pos < longs.length - 1 && myValue + 1 == longs[pos]; - long[] result; - if (touchLeft) { - if (touchRight) { - result = new long[longs.length - 2]; - System.arraycopy(longs, 0, result, 0, pos - 1); - System.arraycopy(longs, pos + 1, result, pos - 1, longs.length - pos - 1); - } else { - result = longs.clone(); - result[pos - 1] = myValue; - } - } else { - if (touchRight) { - result = longs.clone(); - result[pos] = myValue; - } else { - result = new long[longs.length + 2]; - System.arraycopy(longs, 0, result, 0, pos); - result[pos] = result[pos + 1] = myValue; - System.arraycopy(longs, pos, result, pos + 2, longs.length - pos); - } - } - return fromRanges(result, result.length); + /** + * Creates a LongRangeSet of values {@code x} for which {@code remainders.contains(x % mod)}. + * May include more values as well. + * + * @param mod a divisor + * @param remainders set of allowed remainders + * @return set of values which may produce supplied remainders when divided by mod. + */ + public static LongRangeSet fromRemainder(long mod, LongRangeSet remainders) { + if (remainders.isEmpty()) { + return empty(); + } + long min = remainders.min() > 0 ? 1 : Long.MIN_VALUE; + long max = remainders.max() < 0 ? -1 : Long.MAX_VALUE; + if (mod > 1 && mod <= Long.SIZE) { + long bits = remainders.contains(0) ? 1 : 0; + for (int rem = 1; rem < mod; rem++) { + if (remainders.contains(rem) || remainders.contains(rem - mod)) { + bits = setBit(bits, rem); + } + } + return modRange(min, max, mod, bits); + } + return range(min, max); } - @Override - public boolean intersects(LongRangeSet other) { - return other.contains(myValue); - } + static final class Empty extends LongRangeSet { + static final LongRangeSet EMPTY = new Empty(); - @Override - public boolean contains(long value) { - return myValue == value; - } + @Override + public LongRangeSet subtract(LongRangeSet other) { + return this; + } - @Override - public boolean contains(@Nonnull LongRangeSet other) { - return other.isEmpty() || equals(other); - } + @Override + public LongRangeSet intersect(LongRangeSet other) { + return this; + } - @Override - public LongRangeSet castTo(PsiPrimitiveType type) { - if (PsiType.LONG.equals(type)) { - return this; - } - long newValue; - if (PsiType.CHAR.equals(type)) { - newValue = (char) myValue; - } else if (PsiType.INT.equals(type)) { - newValue = (int) myValue; - } else if (PsiType.SHORT.equals(type)) { - newValue = (short) myValue; - } else if (PsiType.BYTE.equals(type)) { - newValue = (byte) myValue; - } else { - throw new IllegalArgumentException(type.toString()); - } - return newValue == myValue ? this : point(newValue); - } + @Override + public LongRangeSet unite(LongRangeSet other) { + return other; + } - @Nonnull - @Override - public LongRangeSet abs(boolean isLong) { - return myValue >= 0 || myValue == minValue(isLong) ? this : point(-myValue); - } + @Override + public long min() { + throw new NoSuchElementException(); + } - @Nonnull - @Override - public LongRangeSet negate(boolean isLong) { - return myValue == minValue(isLong) ? this : point(-myValue); - } + @Override + public long max() { + throw new NoSuchElementException(); + } - @Nonnull - @Override - public LongRangeSet plus(LongRangeSet other, boolean isLong) { - if (other.isEmpty()) { - return other; - } - if (other instanceof Point) { - long res = myValue + ((Point) other).myValue; - return point(isLong ? res : (int) res); - } - return other.plus(this, isLong); - } + @Override + public boolean intersects(LongRangeSet other) { + return false; + } - @Override - public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { - if (multiplier.isEmpty()) { - return multiplier; - } - if (myValue == 0) { - return this; - } - if (myValue == 1) { - return multiplier; - } - if (myValue == -1) { - return multiplier.negate(isLong); - } - if (multiplier instanceof Point) { - long val = ((Point) multiplier).myValue; - long res = myValue * val; - return point(isLong ? res : (int) res); - } - boolean overflow = false; - long min = multiplier.min(); - long max = multiplier.max(); - if (isLong) { - try { - min = Math.multiplyExact(min, myValue); - max = Math.multiplyExact(max, myValue); - } catch (ArithmeticException e) { - overflow = true; - } - } else { - min *= myValue; - max *= myValue; - if (min != (int) min || max != (int) max) { - overflow = true; - } - } - LongRangeSet result; - if (overflow) { - result = isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } else { - result = min > max ? range(max, min) : range(min, max); - } - long abs = Math.abs(myValue); - if (overflow) { - abs = Long.lowestOneBit(abs); - } - if (abs < 0 || (abs > Long.SIZE && Long.bitCount(abs) == 1)) { - abs = Long.SIZE; - } - return modRange(result.min(), result.max(), abs, 1L); - } + @Override + public boolean contains(long value) { + return false; + } - @Nonnull - @Override - public LongRangeSet mod(LongRangeSet divisor) { - if (divisor.isEmpty() || divisor.equals(ZERO)) { - return empty(); - } - if (myValue == 0) { - return this; - } - if (divisor instanceof Point) { - return LongRangeSet.point(myValue % ((Point) divisor).myValue); - } - if (myValue != Long.MIN_VALUE) { - long abs = Math.abs(myValue); - if (!divisor.intersects(LongRangeSet.range(-abs, abs))) { - // like 10 % [15..20] == 10 regardless on exact divisor value - return this; - } - } - LongRangeSet addend = empty(); - if (divisor.contains(Long.MIN_VALUE)) { - divisor = divisor.subtract(point(Long.MIN_VALUE)); - addend = point(myValue); - } - long max = Math.max(0, Math.max(Math.abs(divisor.min()), Math.abs(divisor.max())) - 1); - if (myValue < 0) { - return LongRangeSet.range(Math.max(myValue, -max), 0).unite(addend); - } else { - // 10 % [-4..7] is [0..6], but 10 % [-30..30] is [0..10] - return LongRangeSet.range(0, Math.min(myValue, max)).unite(addend); - } - } + @Override + public boolean contains(LongRangeSet other) { + return other.isEmpty(); + } - @Override - public LongStream stream() { - return LongStream.of(myValue); - } + @Override + public String getPresentationText(PsiType type) { + return JavaAnalysisLocalize.longRangeSetPresentationEmpty().get(); + } - @Override - long[] asRanges() { - return new long[]{ - myValue, - myValue - }; - } + @Override + public boolean isCardinalityBigger(long cutoff) { + return cutoff < 0; + } - @Override - public int hashCode() { - return Long.hashCode(myValue); - } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (TypeConversionUtil.isIntegralNumberType(type)) { + return this; + } + throw new IllegalArgumentException(type.toString()); + } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - return o instanceof Point && myValue == ((Point) o).myValue; - } + @Override + public LongRangeSet abs(boolean isLong) { + return this; + } - @Override - public String toString() { - return "{" + formatNumber(myValue) + "}"; - } - } - - static class Range extends LongRangeSet { - static final Range BYTE_RANGE = new Range(Byte.MIN_VALUE, Byte.MAX_VALUE); - static final Range CHAR_RANGE = new Range(Character.MIN_VALUE, Character.MAX_VALUE); - static final Range SHORT_RANGE = new Range(Short.MIN_VALUE, Short.MAX_VALUE); - static final Range INT_RANGE = new Range(Integer.MIN_VALUE, Integer.MAX_VALUE); - static final Range LONG_RANGE = new Range(Long.MIN_VALUE, Long.MAX_VALUE); - static final Range INDEX_RANGE = new Range(0, Integer.MAX_VALUE); - - final long myFrom; // inclusive - final long myTo; // inclusive - - Range(long from, long to) { - if (to <= from) { // to == from => must be Point - throw new IllegalArgumentException(to + "<=" + from); - } - myFrom = from; - myTo = to; - } + @Override + public LongRangeSet negate(boolean isLong) { + return this; + } - @Override - public String getPresentationText(PsiType type) { - LongRangeSet set = fromType(type); - if (set != null) { - if (set.min() == myFrom) { - if (set.max() == myTo) { - return JavaAnalysisBundle.message("long.range.set.presentation.any"); - } - return "<= " + LongRangeSet.formatNumber(myTo); - } else if (set.max() == myTo) { - return ">= " + LongRangeSet.formatNumber(myFrom); - } - } - if (myTo - myFrom == 1) { - return JavaAnalysisBundle.message("long.range.set.presentation.two.values", myFrom, myTo); - } - return JavaAnalysisBundle.message("long.range.set.presentation.range", toString()); - } + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { + return this; + } - @Override - public boolean isCardinalityBigger(long cutoff) { - long diff = myTo - myFrom; - return diff < 0 || diff >= cutoff; - } + @Override + public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { + return this; + } - @Nonnull - @Override - public LongRangeSet subtract(@Nonnull LongRangeSet other) { - if (other.isEmpty()) { - return this; - } - if (other == this) { - return Empty.EMPTY; - } - if (other instanceof Point) { - long value = ((Point) other).myValue; - if (value < myFrom || value > myTo) { - return this; + @Override + public LongRangeSet mod(LongRangeSet divisor) { + return empty(); } - if (value == myFrom) { - return range(myFrom + 1, myTo); + + @Override + public LongStream stream() { + return LongStream.empty(); } - if (value == myTo) { - return range(myFrom, myTo - 1); + + @Override + long[] asRanges() { + return new long[0]; } - return new RangeSet(new long[]{ - myFrom, - value - 1, - value + 1, - myTo - }); - } - if (other instanceof Range) { - LongRangeSet toJoin = Empty.EMPTY; - long from = ((Range) other).myFrom; - long to = ((Range) other).myTo; - if (to < myFrom || from > myTo) { - return this; - } - if (other instanceof ModRange) { - ModRange modRange = (ModRange) other; - long newBits = ~modRange.myBits; - if (modRange.myMod < 64) { - newBits &= (1L << modRange.myMod) - 1; - } - toJoin = modRange(Math.max(from, myFrom), Math.min(to, myTo), modRange.myMod, newBits); - } - if (from <= myFrom && to >= myTo) { - return toJoin; - } - if (from > myFrom && to < myTo) { - return new RangeSet(new long[]{ - myFrom, - from - 1, - to + 1, - myTo - }).unite(toJoin); - } - if (from <= myFrom) { - return range(to + 1, myTo).unite(toJoin); - } - assert to >= myTo; - return range(myFrom, from - 1).unite(toJoin); - } - long[] ranges = ((RangeSet) other).myRanges; - LongRangeSet result = this; - for (int i = 0; i < ranges.length; i += 2) { - result = result.subtract(range(ranges[i], ranges[i + 1])); - if (result.isEmpty()) { - return result; - } - } - return result; - } - @Nonnull - @Override - public LongRangeSet intersect(@Nonnull LongRangeSet other) { - if (other == this) { - return this; - } - if (other.isEmpty()) { - return other; - } - if ((other instanceof ModRange && !(this instanceof ModRange)) || other instanceof Point) { - return other.intersect(this); - } - if (other instanceof Range) { - long from = ((Range) other).myFrom; - long to = ((Range) other).myTo; - if (from <= myFrom && to >= myTo) { - return this; - } - if (from >= myFrom && to <= myTo) { - return other; - } - if (from < myFrom) { - from = myFrom; - } - if (to > myTo) { - to = myTo; - } - return from <= to ? range(from, to) : Empty.EMPTY; - } - long[] ranges = ((RangeSet) other).myRanges; - long[] result = new long[ranges.length]; - int index = 0; - for (int i = 0; i < ranges.length; i += 2) { - long[] res = intersect(range(ranges[i], ranges[i + 1])).asRanges(); - System.arraycopy(res, 0, result, index, res.length); - index += res.length; - } - return fromRanges(result, index); - } + @Override + public int hashCode() { + return 2154231; + } - @Nonnull - @Override - public LongRangeSet unite(@Nonnull LongRangeSet other) { - if (other.isEmpty() || other == this) { - return this; - } - if (other instanceof Point) { - return other.unite(this); - } - if (other instanceof Range) { - if (other.min() <= max() && min() <= other.max() || - (other.max() < min() && other.max() + 1 == min()) || - (other.min() > max() && max() + 1 == other.min())) { - return range(Math.min(min(), other.min()), Math.max(max(), other.max())); - } - if (other.max() < min()) { - return new RangeSet(new long[]{ - other.min(), - other.max(), - min(), - max() - }); + @Override + public boolean equals(Object obj) { + return obj == this; } - return new RangeSet(new long[]{ - min(), - max(), - other.min(), - other.max() - }); - } - long[] longs = other.asRanges(); - int minIndex = Arrays.binarySearch(longs, min()); - if (minIndex < 0) { - minIndex = -minIndex - 1; - if (minIndex % 2 == 0 && minIndex > 0 && longs[minIndex - 1] + 1 == min()) { - minIndex--; - } - } else if (minIndex % 2 == 0) { - minIndex++; - } - int maxIndex = Arrays.binarySearch(longs, max()); - if (maxIndex < 0) { - maxIndex = -maxIndex - 1; - if (maxIndex % 2 == 0 && maxIndex < longs.length && max() + 1 == longs[maxIndex]) { - maxIndex++; - } - } else if (maxIndex % 2 == 0) { - maxIndex++; - } - long[] result = new long[longs.length + 2]; - System.arraycopy(longs, 0, result, 0, minIndex); - int pos = minIndex; - if (minIndex % 2 == 0) { - result[pos++] = min(); - } - if (maxIndex % 2 == 0) { - result[pos++] = max(); - } - System.arraycopy(longs, maxIndex, result, pos, longs.length - maxIndex); - return fromRanges(result, longs.length + pos - maxIndex); - } - @Override - public long min() { - return myFrom; + @Override + public String toString() { + return "{}"; + } } - @Override - public long max() { - return myTo; - } + static final class Point extends LongRangeSet { + static final Point ZERO = new Point(0); + static final Point ONE = new Point(1); - @Override - public boolean intersects(LongRangeSet other) { - if (other.isEmpty()) { - return false; - } - if (other instanceof RangeSet) { - long[] otherRanges = ((RangeSet) other).myRanges; - for (int i = 0; i < otherRanges.length && otherRanges[i] <= myTo; i += 2) { - if (myTo >= otherRanges[i] && myFrom <= otherRanges[i + 1]) { - return true; - } + final long myValue; + + Point(long value) { + myValue = value; } - return false; - } - return myTo >= other.min() && myFrom <= other.max(); - } - @Override - public boolean contains(long value) { - return myFrom <= value && myTo >= value; - } + @Override + public String getPresentationText(PsiType type) { + return formatNumber(myValue); + } - @Override - public boolean contains(@Nonnull LongRangeSet other) { - return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo; - } + @Override + public boolean isCardinalityBigger(long cutoff) { + return cutoff < 1; + } - @Override - public LongRangeSet castTo(PsiPrimitiveType type) { - if (PsiType.LONG.equals(type)) { - return this; - } - if (PsiType.BYTE.equals(type)) { - LongRangeSet result = mask(Byte.SIZE, type); - assert BYTE_RANGE.contains(result) : this; - return result; - } - if (PsiType.SHORT.equals(type)) { - LongRangeSet result = mask(Short.SIZE, type); - assert SHORT_RANGE.contains(result) : this; - return result; - } - if (PsiType.INT.equals(type)) { - LongRangeSet result = mask(Integer.SIZE, type); - assert INT_RANGE.contains(result) : this; - return result; - } - if (PsiType.CHAR.equals(type)) { - if (myFrom <= Character.MIN_VALUE && myTo >= Character.MAX_VALUE) { - return CHAR_RANGE; + @Override + public LongRangeSet subtract(LongRangeSet other) { + return other.contains(myValue) ? Empty.EMPTY : this; } - if (myFrom >= Character.MIN_VALUE && myTo <= Character.MAX_VALUE) { - return this; + + @Override + public LongRangeSet intersect(LongRangeSet other) { + return other.contains(myValue) ? this : Empty.EMPTY; } - return bitwiseAnd(point(0xFFFF)); - } - throw new IllegalArgumentException(type.toString()); - } - @Nonnull - private LongRangeSet mask(int size, PsiPrimitiveType type) { - long addend = 1L << (size - 1); - if (myFrom <= -addend && myTo >= addend - 1) { - return Objects.requireNonNull(fromType(type)); - } - if (myFrom >= -addend && myTo <= addend - 1) { - return this; - } - long mask = (1L << size) - 1; - return plus(myFrom, myTo, addend, addend, true).bitwiseAnd(point(mask)).plus(point(-addend), true); - } + @Override + public long min() { + return myValue; + } - @Nonnull - @Override - public LongRangeSet abs(boolean isLong) { - if (myFrom >= 0) { - return this; - } - long minValue = minValue(isLong); - long low = myFrom, hi = myTo; - if (low <= minValue) { - low = minValue + 1; - } - if (myTo <= 0) { - hi = -low; - low = -myTo; - } else { - hi = Math.max(-low, hi); - low = 0; - } - if (low > hi) { - return isLong ? LONG_RANGE : INT_RANGE; - } - if (myFrom <= minValue) { - return new RangeSet(new long[]{ - minValue, - minValue, - low, - hi - }); - } else { - return new Range(low, hi); - } - } + @Override + public long max() { + return myValue; + } - @Nonnull - @Override - public LongRangeSet negate(boolean isLong) { - long minValue = minValue(isLong); - if (myFrom <= minValue) { - if (myTo >= maxValue(isLong)) { - return isLong ? LONG_RANGE : INT_RANGE; + @Override + public Long getConstantValue() { + return myValue; } - return new RangeSet(new long[]{ - minValue, - minValue, - -myTo, - -(minValue + 1) - }); - } - return new Range(-myTo, -myFrom); - } - @Nonnull - @Override - public LongRangeSet plus(LongRangeSet other, boolean isLong) { - if (other.isEmpty()) { - return other; - } - if (isLong && equals(LONG_RANGE) || !isLong && equals(INT_RANGE)) { - return this; - } - if (other instanceof Point || other instanceof Range || (other instanceof RangeSet && ((RangeSet) other).myRanges.length > 6)) { - return plus(myFrom, myTo, other.min(), other.max(), isLong); - } - long[] ranges = other.asRanges(); - LongRangeSet result = empty(); - for (int i = 0; i < ranges.length; i += 2) { - result = result.unite(plus(myFrom, myTo, ranges[i], ranges[i + 1], isLong)); - } - return result; - } + @Override + public LongRangeSet unite(LongRangeSet other) { + if (other.isEmpty() || other == this) { + return this; + } + if (other.contains(myValue)) { + return other; + } + if (other instanceof Point) { + long value1 = Math.min(myValue, ((Point)other).myValue); + long value2 = Math.max(myValue, ((Point)other).myValue); + return value1 + 1 == value2 + ? range(value1, value2) + : new RangeSet(new long[]{value1, value1, value2, value2}); + } + if (other instanceof ModRange) { + return other.unite(this); + } + if (other instanceof Range) { + if (myValue < other.min()) { + return myValue + 1 == other.min() + ? range(myValue, other.max()) + : new RangeSet(new long[]{myValue, myValue, other.min(), other.max()}); + } + else { + assert myValue > other.max(); + return myValue - 1 == other.max() + ? range(other.min(), myValue) + : new RangeSet(new long[]{other.min(), other.max(), myValue, myValue}); + } + } + long[] longs = other.asRanges(); + int pos = -Arrays.binarySearch(longs, myValue) - 1; + assert pos >= 0 && pos % 2 == 0; + boolean touchLeft = pos > 0 && longs[pos - 1] + 1 == myValue; + boolean touchRight = pos < longs.length - 1 && myValue + 1 == longs[pos]; + long[] result; + if (touchLeft) { + if (touchRight) { + result = new long[longs.length - 2]; + System.arraycopy(longs, 0, result, 0, pos - 1); + System.arraycopy(longs, pos + 1, result, pos - 1, longs.length - pos - 1); + } + else { + result = longs.clone(); + result[pos - 1] = myValue; + } + } + else { + if (touchRight) { + result = longs.clone(); + result[pos] = myValue; + } + else { + result = new long[longs.length + 2]; + System.arraycopy(longs, 0, result, 0, pos); + result[pos] = result[pos + 1] = myValue; + System.arraycopy(longs, pos, result, pos + 2, longs.length - pos); + } + } + return fromRanges(result, result.length); + } - @Override - public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { - if (multiplier.isEmpty()) { - return multiplier; - } - if (multiplier instanceof Point) { - return multiplier.mul(this, isLong); - } - return isLong ? LONG_RANGE : INT_RANGE; - } + @Override + public boolean intersects(LongRangeSet other) { + return other.contains(myValue); + } - @Nonnull - private static LongRangeSet plus(long from1, long to1, long from2, long to2, boolean isLong) { - long len1 = to1 - from1; // may overflow - long len2 = to2 - from2; // may overflow - if ((len1 < 0 && len2 < 0) || ((len1 < 0 || len2 < 0) && len1 + len2 + 1 >= 0)) { // total length more than 2^64 - return isLong ? LONG_RANGE : INT_RANGE; - } - long from = from1 + from2; - long to = to1 + to2; - if (!isLong) { - if (to - from + 1 >= 0x1_0000_0000L) { - return INT_RANGE; - } - from = (int) from; - to = (int) to; - } - if (to < from) { - return new RangeSet(new long[]{ - minValue(isLong), - to, - from, - maxValue(isLong) - }); - } else { - return range(from, to); - } - } + @Override + public boolean contains(long value) { + return myValue == value; + } - @Nonnull - @Override - public LongRangeSet mod(LongRangeSet divisor) { - if (divisor.isEmpty() || divisor.equals(Point.ZERO)) { - return empty(); - } - if (divisor instanceof Point && ((Point) divisor).myValue == Long.MIN_VALUE) { - return this.contains(Long.MIN_VALUE) ? this.subtract(divisor).unite(Point.ZERO) : this; - } - if (divisor.contains(Long.MIN_VALUE)) { - return possibleMod(); - } - long min = divisor.min(); - long max = divisor.max(); - long maxDivisor = Math.max(Math.abs(min), Math.abs(max)); - long minDivisor = min > 0 ? min : max < 0 ? Math.abs(max) : 0; - if (!intersects(LongRangeSet.range(Long.MIN_VALUE, -minDivisor)) && - !intersects(LongRangeSet.range(minDivisor, Long.MAX_VALUE))) { - return this; - } - return possibleMod().intersect(range(-maxDivisor + 1, maxDivisor - 1)); - } + @Override + public boolean contains(LongRangeSet other) { + return other.isEmpty() || equals(other); + } - private LongRangeSet possibleMod() { - if (contains(0)) { - return this; - } - if (min() > 0) { - return range(0, max()); - } - return range(min(), 0); - } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (PsiType.LONG.equals(type)) { + return this; + } + long newValue; + if (PsiType.CHAR.equals(type)) { + newValue = (char)myValue; + } + else if (PsiType.INT.equals(type)) { + newValue = (int)myValue; + } + else if (PsiType.SHORT.equals(type)) { + newValue = (short)myValue; + } + else if (PsiType.BYTE.equals(type)) { + newValue = (byte)myValue; + } + else { + throw new IllegalArgumentException(type.toString()); + } + return newValue == myValue ? this : point(newValue); + } - @Override - public LongStream stream() { - return LongStream.rangeClosed(myFrom, myTo); - } + @Override + public LongRangeSet abs(boolean isLong) { + return myValue >= 0 || myValue == minValue(isLong) ? this : point(-myValue); + } - @Override - long[] asRanges() { - return new long[]{ - myFrom, - myTo - }; - } + @Override + public LongRangeSet negate(boolean isLong) { + return myValue == minValue(isLong) ? this : point(-myValue); + } - @Override - public int hashCode() { - return Long.hashCode(myFrom) * 1337 + Long.hashCode(myTo); - } + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { + if (other.isEmpty()) { + return other; + } + if (other instanceof Point) { + long res = myValue + ((Point)other).myValue; + return point(isLong ? res : (int)res); + } + return other.plus(this, isLong); + } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - return o != null && o.getClass() == getClass() && myFrom == ((Range) o).myFrom && myTo == ((Range) o).myTo; - } + @Override + public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { + if (multiplier.isEmpty()) { + return multiplier; + } + if (myValue == 0) { + return this; + } + if (myValue == 1) { + return multiplier; + } + if (myValue == -1) { + return multiplier.negate(isLong); + } + if (multiplier instanceof Point) { + long val = ((Point)multiplier).myValue; + long res = myValue * val; + return point(isLong ? res : (int)res); + } + boolean overflow = false; + long min = multiplier.min(); + long max = multiplier.max(); + if (isLong) { + try { + min = Math.multiplyExact(min, myValue); + max = Math.multiplyExact(max, myValue); + } + catch (ArithmeticException e) { + overflow = true; + } + } + else { + min *= myValue; + max *= myValue; + if (min != (int)min || max != (int)max) { + overflow = true; + } + } + LongRangeSet result; + if (overflow) { + result = isLong ? Range.LONG_RANGE : Range.INT_RANGE; + } + else { + result = min > max ? range(max, min) : range(min, max); + } + long abs = Math.abs(myValue); + if (overflow) { + abs = Long.lowestOneBit(abs); + } + if (abs < 0 || (abs > Long.SIZE && Long.bitCount(abs) == 1)) { + abs = Long.SIZE; + } + return modRange(result.min(), result.max(), abs, 1L); + } - @Override - public String toString() { - return "{" + toString(myFrom, myTo) + "}"; - } - } - - static final class ModRange extends Range { - private final int myMod; - private final long myBits; - - ModRange(long from, long to, int mod, long bits) { - super(from, to); - assert mod > 1 && mod <= Long.SIZE; - this.myMod = mod; - this.myBits = bits; - assert (bits & (~getMask())) == 0 : "bits outside of mask should be zero"; - assert bits != getMask() : "at least one bit in mask should be zero, otherwise simple Range could be used"; - } + @Override + public LongRangeSet mod(LongRangeSet divisor) { + if (divisor.isEmpty() || divisor.equals(ZERO)) { + return empty(); + } + if (myValue == 0) { + return this; + } + if (divisor instanceof Point) { + return LongRangeSet.point(myValue % ((Point)divisor).myValue); + } + if (myValue != Long.MIN_VALUE) { + long abs = Math.abs(myValue); + if (!divisor.intersects(LongRangeSet.range(-abs, abs))) { + // like 10 % [15..20] == 10 regardless on exact divisor value + return this; + } + } + LongRangeSet addend = empty(); + if (divisor.contains(Long.MIN_VALUE)) { + divisor = divisor.subtract(point(Long.MIN_VALUE)); + addend = point(myValue); + } + long max = Math.max(0, Math.max(Math.abs(divisor.min()), Math.abs(divisor.max())) - 1); + if (myValue < 0) { + return LongRangeSet.range(Math.max(myValue, -max), 0).unite(addend); + } + else { + // 10 % [-4..7] is [0..6], but 10 % [-30..30] is [0..10] + return LongRangeSet.range(0, Math.min(myValue, max)).unite(addend); + } + } - @Override - public String getPresentationText(PsiType type) { - LongRangeSet set = fromType(type); - if (set != null) { - set = modRange(set.min(), set.max(), myMod, myBits); - String prefix = null; - if (set.min() == myFrom) { - prefix = set.max() == myTo ? "" : "<= " + LongRangeSet.formatNumber(myTo); - } else if (set.max() == myTo) { - prefix = ">= " + LongRangeSet.formatNumber(myFrom); - } - if (prefix != null) { - if (prefix.isEmpty()) { - return getSuffix(); - } - return JavaAnalysisBundle.message("long.range.set.presentation.range.with.mod", prefix, getSuffix()); - } - } - String rangeMessage = JavaAnalysisBundle.message("long.range.set.presentation.range", super.toString()); - return JavaAnalysisBundle.message("long.range.set.presentation.range.with.mod", rangeMessage, getSuffix()); - } + @Override + public LongStream stream() { + return LongStream.of(myValue); + } - @Override - public boolean isCardinalityBigger(long cutoff) { - long bottom = myFrom - 1 - remainder(myFrom - 1, myMod) + myMod; - long top = myTo - remainder(myTo, myMod); - if (bottom >= myFrom && top > bottom && myTo >= top) { - int count = Long.bitCount(myBits); - long wholeCount = (top / myMod - bottom / myMod) * count; - if (wholeCount < 0 || wholeCount > cutoff) { - return true; - } - for (long i = myFrom; i < bottom; i++) { - if (isSet(myBits, remainder(i, myMod))) { - wholeCount++; - } - } - for (long i = top; i <= myTo; i++) { - if (isSet(myBits, remainder(i, myMod))) { - wholeCount++; - } - } - return wholeCount < 0 || wholeCount > cutoff; - } - return stream().limit(Math.max(0, cutoff + 1)).count() > cutoff; - } + @Override + long[] asRanges() { + return new long[]{ + myValue, + myValue + }; + } - @Override - public boolean contains(long value) { - return super.contains(value) && isSet(myBits, remainder(value, myMod)); - } + @Override + public int hashCode() { + return Long.hashCode(myValue); + } - @Override - public - @Nonnull - LongRangeSet subtract(@Nonnull LongRangeSet other) { - return super.subtract(other); - } + @Override + public boolean equals(Object o) { + return o == this + || o instanceof Point that + && myValue == that.myValue; + } - @Nonnull - @Override - public LongRangeSet intersect(@Nonnull LongRangeSet other) { - LongRangeSet intersection = super.intersect(other); - if (intersection instanceof Range || intersection instanceof Point) { - long bits = myBits; - int mod = myMod; - if (other instanceof ModRange) { - ModRange modRange = (ModRange) other; - int lcm = lcm(modRange.myMod); - if (lcm <= Long.SIZE) { - bits = widenBits(lcm) & modRange.widenBits(lcm); - mod = (byte) lcm; - } else if (modRange.myMod > myMod) { - bits = modRange.myBits; // new LCM doesn't fit the Long.SIZE: just select bigger mod - mod = modRange.myMod; - } - } - return modRange(intersection.min(), intersection.max(), mod, bits); - } - if (intersection instanceof RangeSet) { - long min = intersection.min(); - long max = intersection.max(); - long diff = max - min; - if (diff > 0 && diff < Long.SIZE) { - for (byte newMod = (byte) (diff + 1); newMod <= Long.SIZE; newMod++) { - if (newMod % myMod == 0) { - long bits = widenBits(newMod); - for (long pos = min; pos <= max; pos++) { - int bit = remainder(pos, newMod); - if (isSet(bits, bit) && !intersection.contains(pos)) { - bits = clearBit(bits, bit); - } - } - return modRange(min, max, newMod, bits); - } - } - } - } - return intersection; + @Override + public String toString() { + return "{" + formatNumber(myValue) + "}"; + } } - @Override - public boolean intersects(LongRangeSet other) { - if (other instanceof Point) { - return contains(((Point) other).myValue); - } - if (other instanceof ModRange) { - ModRange modRange = (ModRange) other; - int lcm = lcm(modRange.myMod); - if (lcm <= Long.SIZE && (modRange.widenBits(lcm) & widenBits(lcm)) == 0) { - return false; - } - } - long[] otherRanges = other.asRanges(); - for (int i = 0; i < otherRanges.length && otherRanges[i] <= myTo; i += 2) { - if (myTo >= otherRanges[i] && myFrom <= otherRanges[i + 1] && - !modRange(otherRanges[i], otherRanges[i + 1], myMod, myBits).isEmpty()) { - return true; - } - } - return false; - } + static class Range extends LongRangeSet { + static final Range BYTE_RANGE = new Range(Byte.MIN_VALUE, Byte.MAX_VALUE); + static final Range CHAR_RANGE = new Range(Character.MIN_VALUE, Character.MAX_VALUE); + static final Range SHORT_RANGE = new Range(Short.MIN_VALUE, Short.MAX_VALUE); + static final Range INT_RANGE = new Range(Integer.MIN_VALUE, Integer.MAX_VALUE); + static final Range LONG_RANGE = new Range(Long.MIN_VALUE, Long.MAX_VALUE); + static final Range INDEX_RANGE = new Range(0, Integer.MAX_VALUE); - @Nonnull - @Override - public LongRangeSet unite(@Nonnull LongRangeSet other) { - if (other instanceof ModRange) { - ModRange modRange = (ModRange) other; - int lcm = lcm(modRange.myMod); - if (lcm <= Long.SIZE) { - long bits = widenBits(lcm) | modRange.widenBits(lcm); - if (myTo >= modRange.myFrom && myFrom <= modRange.myTo || - myTo < modRange.myFrom && modRange(myTo + 1, modRange.myFrom - 1, lcm, bits).isEmpty() || - modRange.myTo < myFrom && modRange(modRange.myTo + 1, myFrom - 1, lcm, bits).isEmpty()) { - return modRange(Math.min(myFrom, modRange.myFrom), Math.max(myTo, modRange.myTo), lcm, bits); - } - } - } - if (other instanceof Point) { - long val = ((Point) other).myValue; - if (isSet(myBits, remainder(val, myMod))) { - if (val >= myFrom && val <= myTo) { - return this; - } - if (val < myFrom && modRange(val + 1, myFrom - 1, myMod, myBits).isEmpty() || - val > myTo && modRange(myTo + 1, val - 1, myMod, myBits).isEmpty()) { - return modRange(Math.min(myFrom, val), Math.max(myTo, val), myMod, myBits); - } - } - return other.unite(range(myFrom, myTo)); - } - return super.unite(other); - } + final long myFrom; // inclusive + final long myTo; // inclusive - @Override - public boolean contains(@Nonnull LongRangeSet other) { - if (other instanceof ModRange) { - ModRange modRange = (ModRange) other; - if (modRange.myFrom < myFrom || modRange.myTo > myTo) { - return false; - } - int lcm = lcm(modRange.myMod); - if (lcm <= Long.SIZE) { - return (~widenBits(lcm) & modRange.widenBits(lcm)) == 0; - } - } - long[] ranges = other.asRanges(); - for (int i = 0; i < ranges.length; i += 2) { - if (!contains(ranges[i], ranges[i + 1])) { - return false; - } - } - return true; - } + Range(long from, long to) { + if (to <= from) { // to == from => must be Point + throw new IllegalArgumentException(to + "<=" + from); + } + myFrom = from; + myTo = to; + } + + @Override + public String getPresentationText(PsiType type) { + LongRangeSet set = fromType(type); + if (set != null) { + if (set.min() == myFrom) { + if (set.max() == myTo) { + return JavaAnalysisLocalize.longRangeSetPresentationAny().get(); + } + return "<= " + LongRangeSet.formatNumber(myTo); + } + else if (set.max() == myTo) { + return ">= " + LongRangeSet.formatNumber(myFrom); + } + } + if (myTo - myFrom == 1) { + return JavaAnalysisLocalize.longRangeSetPresentationTwoValues(myFrom, myTo).get(); + } + return JavaAnalysisLocalize.longRangeSetPresentationRange(toString()).get(); + } - @Nonnull - @Override - public LongRangeSet negate(boolean isLong) { - LongRangeSet negated = super.negate(isLong); - if (negated instanceof Range) { - // Leave 0 at the place, reverse the rest - long negatedBits = (Long.reverse(myBits & -2) >>> (Long.SIZE - myMod - 1)) | (myBits & 1); - return modRange(negated.min(), negated.max(), myMod, negatedBits); - } - return negated; - } + @Override + public boolean isCardinalityBigger(long cutoff) { + long diff = myTo - myFrom; + return diff < 0 || diff >= cutoff; + } - @Nonnull - @Override - public LongRangeSet plus(LongRangeSet other, boolean isLong) { - LongRangeSet set = super.plus(other, isLong); - if (other instanceof Point || - other instanceof ModRange && ((ModRange) other).myMod == myMod && Long.bitCount(((ModRange) other).myBits) == 1) { - long[] ranges = set.asRanges(); - LongRangeSet result = empty(); - for (int i = 0; i < ranges.length; i += 2) { - result = result.unite(plus(ranges[i], ranges[i + 1], other, isLong)); + @Override + public LongRangeSet subtract(LongRangeSet other) { + if (other.isEmpty()) { + return this; + } + if (other == this) { + return Empty.EMPTY; + } + if (other instanceof Point) { + long value = ((Point)other).myValue; + if (value < myFrom || value > myTo) { + return this; + } + if (value == myFrom) { + return range(myFrom + 1, myTo); + } + if (value == myTo) { + return range(myFrom, myTo - 1); + } + return new RangeSet(new long[]{ + myFrom, + value - 1, + value + 1, + myTo + }); + } + if (other instanceof Range) { + LongRangeSet toJoin = Empty.EMPTY; + long from = ((Range)other).myFrom; + long to = ((Range)other).myTo; + if (to < myFrom || from > myTo) { + return this; + } + if (other instanceof ModRange) { + ModRange modRange = (ModRange)other; + long newBits = ~modRange.myBits; + if (modRange.myMod < 64) { + newBits &= (1L << modRange.myMod) - 1; + } + toJoin = modRange(Math.max(from, myFrom), Math.min(to, myTo), modRange.myMod, newBits); + } + if (from <= myFrom && to >= myTo) { + return toJoin; + } + if (from > myFrom && to < myTo) { + return new RangeSet(new long[]{ + myFrom, + from - 1, + to + 1, + myTo + }).unite(toJoin); + } + if (from <= myFrom) { + return range(to + 1, myTo).unite(toJoin); + } + assert to >= myTo; + return range(myFrom, from - 1).unite(toJoin); + } + long[] ranges = ((RangeSet)other).myRanges; + LongRangeSet result = this; + for (int i = 0; i < ranges.length; i += 2) { + result = result.subtract(range(ranges[i], ranges[i + 1])); + if (result.isEmpty()) { + return result; + } + } + return result; } - return result; - } - return set; - } - private LongRangeSet plus(long min, long max, LongRangeSet other, boolean isLong) { - if (Integer.bitCount(myMod) == 1 || !subtractionMayOverflow(other.negate(isLong), isLong)) { - int bit = other instanceof Point ? remainder(((Point) other).myValue, myMod) : Long.numberOfTrailingZeros(((ModRange) other).myBits); - long bits = rotateRemainders(myBits, myMod, myMod - bit); - return modRange(min, max, myMod, bits); - } - return range(min, max); - } + @Override + public LongRangeSet intersect(LongRangeSet other) { + if (other == this) { + return this; + } + if (other.isEmpty()) { + return other; + } + if ((other instanceof ModRange && !(this instanceof ModRange)) || other instanceof Point) { + return other.intersect(this); + } + if (other instanceof Range) { + long from = ((Range)other).myFrom; + long to = ((Range)other).myTo; + if (from <= myFrom && to >= myTo) { + return this; + } + if (from >= myFrom && to <= myTo) { + return other; + } + if (from < myFrom) { + from = myFrom; + } + if (to > myTo) { + to = myTo; + } + return from <= to ? range(from, to) : Empty.EMPTY; + } + long[] ranges = ((RangeSet)other).myRanges; + long[] result = new long[ranges.length]; + int index = 0; + for (int i = 0; i < ranges.length; i += 2) { + long[] res = intersect(range(ranges[i], ranges[i + 1])).asRanges(); + System.arraycopy(res, 0, result, index, res.length); + index += res.length; + } + return fromRanges(result, index); + } - @Nonnull - @Override - public LongRangeSet mod(LongRangeSet divisor) { - if (divisor instanceof Point) { - if (((Point) divisor).myValue > 1 && ((Point) divisor).myValue <= Long.SIZE) { - int divisorValue = (int) ((Point) divisor).myValue; - int lcm = lcm(divisorValue); - if (lcm <= Long.SIZE) { - long from = MathUtil.clamp(myFrom, -divisorValue + 1, 0); - long to = MathUtil.clamp(myTo, 0, divisorValue - 1); - long possibleMods = widenBits(lcm); - while (Long.SIZE - Long.numberOfLeadingZeros(possibleMods) > divisorValue) { - possibleMods = extractBits(possibleMods, divisorValue, Long.SIZE) | - extractBits(possibleMods, 0, divisorValue); - } - return modRange(from, to, divisorValue, possibleMods); - } - } - } - return range(myFrom, myTo).mod(divisor); - } + @Override + public LongRangeSet unite(LongRangeSet other) { + if (other.isEmpty() || other == this) { + return this; + } + if (other instanceof Point) { + return other.unite(this); + } + if (other instanceof Range) { + if (other.min() <= max() && min() <= other.max() || + (other.max() < min() && other.max() + 1 == min()) || + (other.min() > max() && max() + 1 == other.min())) { + return range(Math.min(min(), other.min()), Math.max(max(), other.max())); + } + if (other.max() < min()) { + return new RangeSet(new long[]{ + other.min(), + other.max(), + min(), + max() + }); + } + return new RangeSet(new long[]{ + min(), + max(), + other.min(), + other.max() + }); + } + long[] longs = other.asRanges(); + int minIndex = Arrays.binarySearch(longs, min()); + if (minIndex < 0) { + minIndex = -minIndex - 1; + if (minIndex % 2 == 0 && minIndex > 0 && longs[minIndex - 1] + 1 == min()) { + minIndex--; + } + } + else if (minIndex % 2 == 0) { + minIndex++; + } + int maxIndex = Arrays.binarySearch(longs, max()); + if (maxIndex < 0) { + maxIndex = -maxIndex - 1; + if (maxIndex % 2 == 0 && maxIndex < longs.length && max() + 1 == longs[maxIndex]) { + maxIndex++; + } + } + else if (maxIndex % 2 == 0) { + maxIndex++; + } + long[] result = new long[longs.length + 2]; + System.arraycopy(longs, 0, result, 0, minIndex); + int pos = minIndex; + if (minIndex % 2 == 0) { + result[pos++] = min(); + } + if (maxIndex % 2 == 0) { + result[pos++] = max(); + } + System.arraycopy(longs, maxIndex, result, pos, longs.length - maxIndex); + return fromRanges(result, longs.length + pos - maxIndex); + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass() || !super.equals(o)) { - return false; - } - return myMod == ((ModRange) o).myMod && myBits == ((ModRange) o).myBits; - } + @Override + public long min() { + return myFrom; + } - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), myMod, myBits); - } + @Override + public long max() { + return myTo; + } - @Override - public String toString() { - return super.toString() + ": " + getSuffix(); - } + @Override + public boolean intersects(LongRangeSet other) { + if (other.isEmpty()) { + return false; + } + if (other instanceof RangeSet) { + long[] otherRanges = ((RangeSet)other).myRanges; + for (int i = 0; i < otherRanges.length && otherRanges[i] <= myTo; i += 2) { + if (myTo >= otherRanges[i] && myFrom <= otherRanges[i + 1]) { + return true; + } + } + return false; + } + return myTo >= other.min() && myFrom <= other.max(); + } - private - @Nls - String getSuffix() { - String suffix; - if (myMod == 2) { - suffix = myBits == 1 ? - JavaAnalysisBundle.message("long.range.set.presentation.even") : - JavaAnalysisBundle.message("long.range.set.presentation.odd"); - } else if (myBits == 1) { - suffix = JavaAnalysisBundle.message("long.range.set.presentation.divisible.by", myMod); - } else { - //noinspection HardCodedStringLiteral - suffix = IntStreamEx.of(BitSet.valueOf(new long[]{myBits})).joining(", ", "<", "> mod " + myMod); - } - return suffix; - } + @Override + public boolean contains(long value) { + return myFrom <= value && myTo >= value; + } - @Override - public LongStream stream() { - return super.stream().filter(this::contains); - } + @Override + public boolean contains(LongRangeSet other) { + return other.isEmpty() || other.min() >= myFrom && other.max() <= myTo; + } - private boolean contains(long from, long to) { - if (from < myFrom || to > myTo) { - return false; - } - if (to == from) { - return contains(from); - } - if (to - from < 0 || to - from >= myMod) { - return false; - } - int fromBit = remainder(from, myMod); - int toBit = remainder(to, myMod); - if (fromBit < toBit) { - return Long.numberOfTrailingZeros(~(myBits >>> fromBit)) > toBit - fromBit; - } - return Long.numberOfTrailingZeros(~myBits) > toBit && - Long.SIZE - Long.numberOfLeadingZeros((~myBits) & getMask()) <= fromBit; - } + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + if (PsiType.LONG.equals(type)) { + return this; + } + if (PsiType.BYTE.equals(type)) { + LongRangeSet result = mask(Byte.SIZE, type); + assert BYTE_RANGE.contains(result) : this; + return result; + } + if (PsiType.SHORT.equals(type)) { + LongRangeSet result = mask(Short.SIZE, type); + assert SHORT_RANGE.contains(result) : this; + return result; + } + if (PsiType.INT.equals(type)) { + LongRangeSet result = mask(Integer.SIZE, type); + assert INT_RANGE.contains(result) : this; + return result; + } + if (PsiType.CHAR.equals(type)) { + if (myFrom <= Character.MIN_VALUE && myTo >= Character.MAX_VALUE) { + return CHAR_RANGE; + } + if (myFrom >= Character.MIN_VALUE && myTo <= Character.MAX_VALUE) { + return this; + } + return bitwiseAnd(point(0xFFFF)); + } + throw new IllegalArgumentException(type.toString()); + } - private long widenBits(int targetMod) { - assert targetMod <= Long.SIZE && targetMod % myMod == 0; - long result = myBits; - for (int shift = targetMod - myMod; shift > 0; shift -= myMod) { - result |= myBits << shift; - } - return result; - } + private LongRangeSet mask(int size, PsiPrimitiveType type) { + long addend = 1L << (size - 1); + if (myFrom <= -addend && myTo >= addend - 1) { + return Objects.requireNonNull(fromType(type)); + } + if (myFrom >= -addend && myTo <= addend - 1) { + return this; + } + long mask = (1L << size) - 1; + return plus(myFrom, myTo, addend, addend, true).bitwiseAnd(point(mask)).plus(point(-addend), true); + } - @Override - BitString getBitwiseMask() { - int knownBits = Long.numberOfTrailingZeros(myMod); - int powerOfTwo = 1 << knownBits; - long result = -1; - long mask = powerOfTwo - 1; - for (int rem = 0; rem < myMod; rem++) { - if (isSet(myBits, rem)) { - int setBits = rem % powerOfTwo; - if (result != -1) { - long diffBits = result ^ setBits; - mask &= ~diffBits; - } - result = setBits; - } - } - BitString intersection = new BitString(result, mask).intersect(super.getBitwiseMask()); - assert intersection != null; - return intersection; - } + @Override + public LongRangeSet abs(boolean isLong) { + if (myFrom >= 0) { + return this; + } + long minValue = minValue(isLong); + long low = myFrom, hi = myTo; + if (low <= minValue) { + low = minValue + 1; + } + if (myTo <= 0) { + hi = -low; + low = -myTo; + } + else { + hi = Math.max(-low, hi); + low = 0; + } + if (low > hi) { + return isLong ? LONG_RANGE : INT_RANGE; + } + if (myFrom <= minValue) { + return new RangeSet(new long[]{ + minValue, + minValue, + low, + hi + }); + } + else { + return new Range(low, hi); + } + } - private long getMask() { - return -1L >>> (Long.SIZE - myMod); - } + @Override + public LongRangeSet negate(boolean isLong) { + long minValue = minValue(isLong); + if (myFrom <= minValue) { + if (myTo >= maxValue(isLong)) { + return isLong ? LONG_RANGE : INT_RANGE; + } + return new RangeSet(new long[]{ + minValue, + minValue, + -myTo, + -(minValue + 1) + }); + } + return new Range(-myTo, -myFrom); + } - private int lcm(int otherMod) { - return myMod * otherMod / gcd(myMod, otherMod); - } - } + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { + if (other.isEmpty()) { + return other; + } + if (isLong && equals(LONG_RANGE) || !isLong && equals(INT_RANGE)) { + return this; + } + if (other instanceof Point || other instanceof Range || (other instanceof RangeSet && ((RangeSet)other).myRanges.length > 6)) { + return plus(myFrom, myTo, other.min(), other.max(), isLong); + } + long[] ranges = other.asRanges(); + LongRangeSet result = empty(); + for (int i = 0; i < ranges.length; i += 2) { + result = result.unite(plus(myFrom, myTo, ranges[i], ranges[i + 1], isLong)); + } + return result; + } - static final class RangeSet extends LongRangeSet { - final long[] myRanges; + @Override + public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { + if (multiplier.isEmpty()) { + return multiplier; + } + if (multiplier instanceof Point) { + return multiplier.mul(this, isLong); + } + return isLong ? LONG_RANGE : INT_RANGE; + } - RangeSet(long[] ranges) { - if (ranges.length < 4 || ranges.length % 2 != 0) { - // 0 ranges = Empty; 1 range = Range - throw new IllegalArgumentException("Bad length: " + ranges.length + " " + Arrays.toString(ranges)); - } - for (int i = 0; i < ranges.length; i += 2) { - if (ranges[i + 1] < ranges[i]) { - throw new IllegalArgumentException("Bad sub-range #" + (i / 2) + " " + Arrays.toString(ranges)); + private static LongRangeSet plus(long from1, long to1, long from2, long to2, boolean isLong) { + long len1 = to1 - from1; // may overflow + long len2 = to2 - from2; // may overflow + if ((len1 < 0 && len2 < 0) || ((len1 < 0 || len2 < 0) && len1 + len2 + 1 >= 0)) { // total length more than 2^64 + return isLong ? LONG_RANGE : INT_RANGE; + } + long from = from1 + from2; + long to = to1 + to2; + if (!isLong) { + if (to - from + 1 >= 0x1_0000_0000L) { + return INT_RANGE; + } + from = (int)from; + to = (int)to; + } + if (to < from) { + return new RangeSet(new long[]{ + minValue(isLong), + to, + from, + maxValue(isLong) + }); + } + else { + return range(from, to); + } } - if (i > 0 && (ranges[i - 1] == Long.MAX_VALUE || 1 + ranges[i - 1] > ranges[i])) { - throw new IllegalArgumentException("Bad sub-ranges #" + (i / 2 - 1) + " and #" + (i / 2) + " " + Arrays.toString(ranges)); + + @Override + public LongRangeSet mod(LongRangeSet divisor) { + if (divisor.isEmpty() || divisor.equals(Point.ZERO)) { + return empty(); + } + if (divisor instanceof Point && ((Point)divisor).myValue == Long.MIN_VALUE) { + return this.contains(Long.MIN_VALUE) ? this.subtract(divisor).unite(Point.ZERO) : this; + } + if (divisor.contains(Long.MIN_VALUE)) { + return possibleMod(); + } + long min = divisor.min(); + long max = divisor.max(); + long maxDivisor = Math.max(Math.abs(min), Math.abs(max)); + long minDivisor = min > 0 ? min : max < 0 ? Math.abs(max) : 0; + if (!intersects(LongRangeSet.range(Long.MIN_VALUE, -minDivisor)) && + !intersects(LongRangeSet.range(minDivisor, Long.MAX_VALUE))) { + return this; + } + return possibleMod().intersect(range(-maxDivisor + 1, maxDivisor - 1)); } - } - myRanges = ranges; - } - @Override - BitString getBitwiseMask() { - BitString result = null; - for (int i = 0; i < myRanges.length; i += 2) { - BitString newBits = BitString.fromRange(myRanges[i], myRanges[i + 1]); - result = result == null ? newBits : result.unite(newBits); - } - return result; - } + private LongRangeSet possibleMod() { + if (contains(0)) { + return this; + } + if (min() > 0) { + return range(0, max()); + } + return range(min(), 0); + } - @Nonnull - @Override - public LongRangeSet subtract(@Nonnull LongRangeSet other) { - if (other.isEmpty()) { - return this; - } - if (other == this) { - return Empty.EMPTY; - } - long[] result = new long[myRanges.length + other.asRanges().length]; - int index = 0; - for (int i = 0; i < myRanges.length; i += 2) { - LongRangeSet res = range(myRanges[i], myRanges[i + 1]).subtract(other); - long[] ranges = res.asRanges(); - System.arraycopy(ranges, 0, result, index, ranges.length); - index += ranges.length; - } - return fromRanges(result, index); - } + @Override + public LongStream stream() { + return LongStream.rangeClosed(myFrom, myTo); + } - @Nonnull - @Override - public LongRangeSet intersect(@Nonnull LongRangeSet other) { - if (other == this) { - return this; - } - if (other.isEmpty()) { - return other; - } - if (other instanceof Point || other instanceof Range) { - return other.intersect(this); - } - return subtract(all().subtract(other)); - } + @Override + long[] asRanges() { + return new long[]{myFrom, myTo}; + } - @Nonnull - @Override - public LongRangeSet unite(@Nonnull LongRangeSet other) { - if (!(other instanceof RangeSet)) { - return other.unite(this); - } - if (other == this) { - return this; - } - if (other.contains(this)) { - return other; - } - if (this.contains(other)) { - return this; - } - LongRangeSet result = other; - for (int i = 0; i < myRanges.length; i += 2) { - result = range(myRanges[i], myRanges[i + 1]).unite(result); - } - return result; - } + @Override + public int hashCode() { + return Long.hashCode(myFrom) * 1337 + Long.hashCode(myTo); + } - @Override - public long min() { - return myRanges[0]; - } + @Override + public boolean equals(Object o) { + return o == this + || o != null && o.getClass() == getClass() && myFrom == ((Range)o).myFrom && myTo == ((Range)o).myTo; + } - @Override - public long max() { - return myRanges[myRanges.length - 1]; + @Override + public String toString() { + return "{" + toString(myFrom, myTo) + "}"; + } } - @Override - public boolean intersects(LongRangeSet other) { - if (other.isEmpty()) { - return false; - } - if (other instanceof Point) { - return contains(((Point) other).myValue); - } - long[] otherRanges = other.asRanges(); - int a = 0, b = 0; - while (true) { - long aFrom = myRanges[a]; - long aTo = myRanges[a + 1]; - long bFrom = otherRanges[b]; - long bTo = otherRanges[b + 1]; - if (aFrom <= bTo && bFrom <= aTo) { - return true; - } - if (aFrom > bTo) { - b += 2; - if (b >= otherRanges.length) { - return false; - } - } else { - a += 2; - if (a >= myRanges.length) { + static final class ModRange extends Range { + private final int myMod; + private final long myBits; + + ModRange(long from, long to, int mod, long bits) { + super(from, to); + assert mod > 1 && mod <= Long.SIZE; + this.myMod = mod; + this.myBits = bits; + assert (bits & (~getMask())) == 0 : "bits outside of mask should be zero"; + assert bits != getMask() : "at least one bit in mask should be zero, otherwise simple Range could be used"; + } + + @Override + public String getPresentationText(PsiType type) { + LongRangeSet set = fromType(type); + if (set != null) { + set = modRange(set.min(), set.max(), myMod, myBits); + String prefix = null; + if (set.min() == myFrom) { + prefix = set.max() == myTo ? "" : "<= " + LongRangeSet.formatNumber(myTo); + } + else if (set.max() == myTo) { + prefix = ">= " + LongRangeSet.formatNumber(myFrom); + } + if (prefix != null) { + if (prefix.isEmpty()) { + return getSuffix(); + } + return JavaAnalysisLocalize.longRangeSetPresentationRangeWithMod(prefix, getSuffix()).get(); + } + } + LocalizeValue rangeMessage = JavaAnalysisLocalize.longRangeSetPresentationRange(super.toString()); + return JavaAnalysisLocalize.longRangeSetPresentationRangeWithMod(rangeMessage, getSuffix()).get(); + } + + @Override + public boolean isCardinalityBigger(long cutoff) { + long bottom = myFrom - 1 - remainder(myFrom - 1, myMod) + myMod; + long top = myTo - remainder(myTo, myMod); + if (bottom >= myFrom && top > bottom && myTo >= top) { + int count = Long.bitCount(myBits); + long wholeCount = (top / myMod - bottom / myMod) * count; + if (wholeCount < 0 || wholeCount > cutoff) { + return true; + } + for (long i = myFrom; i < bottom; i++) { + if (isSet(myBits, remainder(i, myMod))) { + wholeCount++; + } + } + for (long i = top; i <= myTo; i++) { + if (isSet(myBits, remainder(i, myMod))) { + wholeCount++; + } + } + return wholeCount < 0 || wholeCount > cutoff; + } + return stream().limit(Math.max(0, cutoff + 1)).count() > cutoff; + } + + @Override + public boolean contains(long value) { + return super.contains(value) && isSet(myBits, remainder(value, myMod)); + } + + @Override + public + LongRangeSet subtract(LongRangeSet other) { + return super.subtract(other); + } + + @Override + public LongRangeSet intersect(LongRangeSet other) { + LongRangeSet intersection = super.intersect(other); + if (intersection instanceof Range || intersection instanceof Point) { + long bits = myBits; + int mod = myMod; + if (other instanceof ModRange) { + ModRange modRange = (ModRange)other; + int lcm = lcm(modRange.myMod); + if (lcm <= Long.SIZE) { + bits = widenBits(lcm) & modRange.widenBits(lcm); + mod = (byte)lcm; + } + else if (modRange.myMod > myMod) { + bits = modRange.myBits; // new LCM doesn't fit the Long.SIZE: just select bigger mod + mod = modRange.myMod; + } + } + return modRange(intersection.min(), intersection.max(), mod, bits); + } + if (intersection instanceof RangeSet) { + long min = intersection.min(); + long max = intersection.max(); + long diff = max - min; + if (diff > 0 && diff < Long.SIZE) { + for (byte newMod = (byte)(diff + 1); newMod <= Long.SIZE; newMod++) { + if (newMod % myMod == 0) { + long bits = widenBits(newMod); + for (long pos = min; pos <= max; pos++) { + int bit = remainder(pos, newMod); + if (isSet(bits, bit) && !intersection.contains(pos)) { + bits = clearBit(bits, bit); + } + } + return modRange(min, max, newMod, bits); + } + } + } + } + return intersection; + } + + @Override + public boolean intersects(LongRangeSet other) { + if (other instanceof Point) { + return contains(((Point)other).myValue); + } + if (other instanceof ModRange) { + ModRange modRange = (ModRange)other; + int lcm = lcm(modRange.myMod); + if (lcm <= Long.SIZE && (modRange.widenBits(lcm) & widenBits(lcm)) == 0) { + return false; + } + } + long[] otherRanges = other.asRanges(); + for (int i = 0; i < otherRanges.length && otherRanges[i] <= myTo; i += 2) { + if (myTo >= otherRanges[i] && myFrom <= otherRanges[i + 1] && + !modRange(otherRanges[i], otherRanges[i + 1], myMod, myBits).isEmpty()) { + return true; + } + } return false; - } } - } - } - @Override - public boolean contains(long value) { - for (int i = 0; i < myRanges.length; i += 2) { - if (value >= myRanges[i] && value <= myRanges[i + 1]) { - return true; + @Override + public LongRangeSet unite(LongRangeSet other) { + if (other instanceof ModRange) { + ModRange modRange = (ModRange)other; + int lcm = lcm(modRange.myMod); + if (lcm <= Long.SIZE) { + long bits = widenBits(lcm) | modRange.widenBits(lcm); + if (myTo >= modRange.myFrom && myFrom <= modRange.myTo || + myTo < modRange.myFrom && modRange(myTo + 1, modRange.myFrom - 1, lcm, bits).isEmpty() || + modRange.myTo < myFrom && modRange(modRange.myTo + 1, myFrom - 1, lcm, bits).isEmpty()) { + return modRange(Math.min(myFrom, modRange.myFrom), Math.max(myTo, modRange.myTo), lcm, bits); + } + } + } + if (other instanceof Point) { + long val = ((Point)other).myValue; + if (isSet(myBits, remainder(val, myMod))) { + if (val >= myFrom && val <= myTo) { + return this; + } + if (val < myFrom && modRange(val + 1, myFrom - 1, myMod, myBits).isEmpty() || + val > myTo && modRange(myTo + 1, val - 1, myMod, myBits).isEmpty()) { + return modRange(Math.min(myFrom, val), Math.max(myTo, val), myMod, myBits); + } + } + return other.unite(range(myFrom, myTo)); + } + return super.unite(other); } - } - return false; - } - @Override - public boolean contains(@Nonnull LongRangeSet other) { - if (other.isEmpty() || other == this) { - return true; - } - if (other instanceof Point) { - return contains(((Point) other).myValue); - } - LongRangeSet result = other; - for (int i = 0; i < myRanges.length; i += 2) { - result = result.subtract(range(myRanges[i], myRanges[i + 1])); - if (result.isEmpty()) { - return true; - } - } - return false; - } + @Override + public boolean contains(LongRangeSet other) { + if (other instanceof ModRange) { + ModRange modRange = (ModRange)other; + if (modRange.myFrom < myFrom || modRange.myTo > myTo) { + return false; + } + int lcm = lcm(modRange.myMod); + if (lcm <= Long.SIZE) { + return (~widenBits(lcm) & modRange.widenBits(lcm)) == 0; + } + } + long[] ranges = other.asRanges(); + for (int i = 0; i < ranges.length; i += 2) { + if (!contains(ranges[i], ranges[i + 1])) { + return false; + } + } + return true; + } - @Override - public String getPresentationText(PsiType type) { - LongRangeSet set = fromType(type); - if (set != null) { - LongRangeSet diff = set.subtract(this); - if (diff instanceof Point) { - return "!= " + diff.min(); - } - if (diff instanceof Range && !diff.intersects(this)) { - String min = - diff.min() == set.min() ? "" : diff.min() == set.min() + 1 ? formatNumber(set.min()) : "<= " + formatNumber(diff.min() - 1); - String max = - diff.max() == set.max() ? "" : diff.max() == set.max() - 1 ? formatNumber(set.max()) : ">= " + formatNumber(diff.max() + 1); - if (min.isEmpty()) { - return max; - } - if (max.isEmpty()) { - return min; - } - return JavaAnalysisBundle.message("long.range.set.presentation.two.values", min, max); - } - } - if (myRanges.length == 4 && myRanges[0] == myRanges[1] && myRanges[2] == myRanges[3]) { - return JavaAnalysisBundle.message("long.range.set.presentation.two.values", myRanges[0], myRanges[2]); - } - return JavaAnalysisBundle.message("long.range.set.presentation.range", toString()); - } + @Override + public LongRangeSet negate(boolean isLong) { + LongRangeSet negated = super.negate(isLong); + if (negated instanceof Range) { + // Leave 0 at the place, reverse the rest + long negatedBits = (Long.reverse(myBits & -2) >>> (Long.SIZE - myMod - 1)) | (myBits & 1); + return modRange(negated.min(), negated.max(), myMod, negatedBits); + } + return negated; + } + + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { + LongRangeSet set = super.plus(other, isLong); + if (other instanceof Point || + other instanceof ModRange && ((ModRange)other).myMod == myMod && Long.bitCount(((ModRange)other).myBits) == 1) { + long[] ranges = set.asRanges(); + LongRangeSet result = empty(); + for (int i = 0; i < ranges.length; i += 2) { + result = result.unite(plus(ranges[i], ranges[i + 1], other, isLong)); + } + return result; + } + return set; + } - @Override - public boolean isCardinalityBigger(long cutoff) { - long totalDiff = 0; - for (int i = 0; i < myRanges.length; i += 2) { - long diff = myRanges[i + 1] - myRanges[i]; - if (diff < 0) { - return true; + private LongRangeSet plus(long min, long max, LongRangeSet other, boolean isLong) { + if (Integer.bitCount(myMod) == 1 || !subtractionMayOverflow(other.negate(isLong), isLong)) { + int bit = other instanceof Point + ? remainder(((Point)other).myValue, myMod) + : Long.numberOfTrailingZeros(((ModRange)other).myBits); + long bits = rotateRemainders(myBits, myMod, myMod - bit); + return modRange(min, max, myMod, bits); + } + return range(min, max); + } + + @Override + public LongRangeSet mod(LongRangeSet divisor) { + if (divisor instanceof Point) { + if (((Point)divisor).myValue > 1 && ((Point)divisor).myValue <= Long.SIZE) { + int divisorValue = (int)((Point)divisor).myValue; + int lcm = lcm(divisorValue); + if (lcm <= Long.SIZE) { + long from = MathUtil.clamp(myFrom, -divisorValue + 1, 0); + long to = MathUtil.clamp(myTo, 0, divisorValue - 1); + long possibleMods = widenBits(lcm); + while (Long.SIZE - Long.numberOfLeadingZeros(possibleMods) > divisorValue) { + possibleMods = extractBits(possibleMods, divisorValue, Long.SIZE) + | extractBits(possibleMods, 0, divisorValue); + } + return modRange(from, to, divisorValue, possibleMods); + } + } + } + return range(myFrom, myTo).mod(divisor); } - totalDiff += diff + 1; - if (totalDiff < 0 || totalDiff > cutoff) { - return true; + + @Override + public boolean equals(Object o) { + return this == o + || super.equals(o) + && o instanceof ModRange that + && myMod == that.myMod + && myBits == that.myBits; } - } - return false; - } - @Override - public LongRangeSet castTo(PsiPrimitiveType type) { - LongRangeSet result = empty(); - for (int i = 0; i < myRanges.length; i += 2) { - result = result.unite(range(myRanges[i], myRanges[i + 1]).castTo(type)); - } - return result; - } + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), myMod, myBits); + } - @Nonnull - @Override - public LongRangeSet abs(boolean isLong) { - LongRangeSet result = empty(); - for (int i = 0; i < myRanges.length; i += 2) { - result = result.unite(range(myRanges[i], myRanges[i + 1]).abs(isLong)); - } - return result; - } + @Override + public String toString() { + return super.toString() + ": " + getSuffix(); + } - @Nonnull - @Override - public LongRangeSet negate(boolean isLong) { - LongRangeSet result = empty(); - for (int i = 0; i < myRanges.length; i += 2) { - result = result.unite(range(myRanges[i], myRanges[i + 1]).negate(isLong)); - } - return result; - } + private + String getSuffix() { + LocalizeValue suffix; + if (myMod == 2) { + suffix = myBits == 1 + ? JavaAnalysisLocalize.longRangeSetPresentationEven() + : JavaAnalysisLocalize.longRangeSetPresentationOdd(); + } + else if (myBits == 1) { + suffix = JavaAnalysisLocalize.longRangeSetPresentationDivisibleBy(myMod); + } + else { + //noinspection HardCodedStringLiteral + suffix = LocalizeValue.localizeTODO( + IntStreamEx.of(BitSet.valueOf(new long[]{myBits})).joining(", ", "<", "> mod " + myMod) + ); + } + return suffix.get(); + } - @Nonnull - @Override - public LongRangeSet plus(LongRangeSet other, boolean isLong) { - if (myRanges.length > 6) { - return range(min(), max()).plus(other, isLong); - } - LongRangeSet result = empty(); - for (int i = 0; i < myRanges.length; i += 2) { - result = result.unite(range(myRanges[i], myRanges[i + 1]).plus(other, isLong)); - } - return result; - } + @Override + public LongStream stream() { + return super.stream().filter(this::contains); + } - @Override - public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { - if (multiplier.isEmpty()) { - return multiplier; - } - if (multiplier instanceof Point) { - return multiplier.mul(this, isLong); - } - return isLong ? Range.LONG_RANGE : Range.INT_RANGE; - } + private boolean contains(long from, long to) { + if (from < myFrom || to > myTo) { + return false; + } + if (to == from) { + return contains(from); + } + if (to - from < 0 || to - from >= myMod) { + return false; + } + int fromBit = remainder(from, myMod); + int toBit = remainder(to, myMod); + if (fromBit < toBit) { + return Long.numberOfTrailingZeros(~(myBits >>> fromBit)) > toBit - fromBit; + } + return Long.numberOfTrailingZeros(~myBits) > toBit && + Long.SIZE - Long.numberOfLeadingZeros((~myBits) & getMask()) <= fromBit; + } - @Nonnull - @Override - public LongRangeSet mod(LongRangeSet divisor) { - if (divisor.isEmpty()) { - return empty(); - } - LongRangeSet result = empty(); - for (int i = 0; i < myRanges.length; i += 2) { - result = result.unite(range(myRanges[i], myRanges[i + 1]).mod(divisor)); - } - return result; - } + private long widenBits(int targetMod) { + assert targetMod <= Long.SIZE && targetMod % myMod == 0; + long result = myBits; + for (int shift = targetMod - myMod; shift > 0; shift -= myMod) { + result |= myBits << shift; + } + return result; + } + + @Override + BitString getBitwiseMask() { + int knownBits = Long.numberOfTrailingZeros(myMod); + int powerOfTwo = 1 << knownBits; + long result = -1; + long mask = powerOfTwo - 1; + for (int rem = 0; rem < myMod; rem++) { + if (isSet(myBits, rem)) { + int setBits = rem % powerOfTwo; + if (result != -1) { + long diffBits = result ^ setBits; + mask &= ~diffBits; + } + result = setBits; + } + } + BitString intersection = new BitString(result, mask).intersect(super.getBitwiseMask()); + assert intersection != null; + return intersection; + } - @Override - public LongStream stream() { - return IntStream.range(0, myRanges.length / 2) - .mapToObj(idx -> LongStream.rangeClosed(myRanges[idx * 2], myRanges[idx * 2 + 1])) - .reduce(LongStream::concat).orElseGet(LongStream::empty); - } + private long getMask() { + return -1L >>> (Long.SIZE - myMod); + } - @Override - long[] asRanges() { - return myRanges; + private int lcm(int otherMod) { + return myMod * otherMod / gcd(myMod, otherMod); + } } - @Override - public int hashCode() { - return Arrays.hashCode(myRanges); - } + static final class RangeSet extends LongRangeSet { + final long[] myRanges; - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - return o instanceof RangeSet && Arrays.equals(myRanges, ((RangeSet) o).myRanges); - } + RangeSet(long[] ranges) { + if (ranges.length < 4 || ranges.length % 2 != 0) { + // 0 ranges = Empty; 1 range = Range + throw new IllegalArgumentException("Bad length: " + ranges.length + " " + Arrays.toString(ranges)); + } + for (int i = 0; i < ranges.length; i += 2) { + if (ranges[i + 1] < ranges[i]) { + throw new IllegalArgumentException("Bad sub-range #" + (i / 2) + " " + Arrays.toString(ranges)); + } + if (i > 0 && (ranges[i - 1] == Long.MAX_VALUE || 1 + ranges[i - 1] > ranges[i])) { + throw new IllegalArgumentException( + "Bad sub-ranges #" + (i / 2 - 1) + " and #" + (i / 2) + " " + Arrays.toString(ranges) + ); + } + } + myRanges = ranges; + } + + @Override + BitString getBitwiseMask() { + BitString result = null; + for (int i = 0; i < myRanges.length; i += 2) { + BitString newBits = BitString.fromRange(myRanges[i], myRanges[i + 1]); + result = result == null ? newBits : result.unite(newBits); + } + return result; + } + + @Override + public LongRangeSet subtract(LongRangeSet other) { + if (other.isEmpty()) { + return this; + } + if (other == this) { + return Empty.EMPTY; + } + long[] result = new long[myRanges.length + other.asRanges().length]; + int index = 0; + for (int i = 0; i < myRanges.length; i += 2) { + LongRangeSet res = range(myRanges[i], myRanges[i + 1]).subtract(other); + long[] ranges = res.asRanges(); + System.arraycopy(ranges, 0, result, index, ranges.length); + index += ranges.length; + } + return fromRanges(result, index); + } + + @Override + public LongRangeSet intersect(LongRangeSet other) { + if (other == this) { + return this; + } + if (other.isEmpty()) { + return other; + } + if (other instanceof Point || other instanceof Range) { + return other.intersect(this); + } + return subtract(all().subtract(other)); + } + + @Override + public LongRangeSet unite(LongRangeSet other) { + if (!(other instanceof RangeSet)) { + return other.unite(this); + } + if (other == this) { + return this; + } + if (other.contains(this)) { + return other; + } + if (this.contains(other)) { + return this; + } + LongRangeSet result = other; + for (int i = 0; i < myRanges.length; i += 2) { + result = range(myRanges[i], myRanges[i + 1]).unite(result); + } + return result; + } + + @Override + public long min() { + return myRanges[0]; + } + + @Override + public long max() { + return myRanges[myRanges.length - 1]; + } + + @Override + public boolean intersects(LongRangeSet other) { + if (other.isEmpty()) { + return false; + } + if (other instanceof Point) { + return contains(((Point)other).myValue); + } + long[] otherRanges = other.asRanges(); + int a = 0, b = 0; + while (true) { + long aFrom = myRanges[a]; + long aTo = myRanges[a + 1]; + long bFrom = otherRanges[b]; + long bTo = otherRanges[b + 1]; + if (aFrom <= bTo && bFrom <= aTo) { + return true; + } + if (aFrom > bTo) { + b += 2; + if (b >= otherRanges.length) { + return false; + } + } + else { + a += 2; + if (a >= myRanges.length) { + return false; + } + } + } + } + + @Override + public boolean contains(long value) { + for (int i = 0; i < myRanges.length; i += 2) { + if (value >= myRanges[i] && value <= myRanges[i + 1]) { + return true; + } + } + return false; + } + + @Override + public boolean contains(LongRangeSet other) { + if (other.isEmpty() || other == this) { + return true; + } + if (other instanceof Point) { + return contains(((Point)other).myValue); + } + LongRangeSet result = other; + for (int i = 0; i < myRanges.length; i += 2) { + result = result.subtract(range(myRanges[i], myRanges[i + 1])); + if (result.isEmpty()) { + return true; + } + } + return false; + } + + @Override + public String getPresentationText(PsiType type) { + LongRangeSet set = fromType(type); + if (set != null) { + LongRangeSet diff = set.subtract(this); + if (diff instanceof Point) { + return "!= " + diff.min(); + } + if (diff instanceof Range && !diff.intersects(this)) { + String min = diff.min() == set.min() + ? "" + : diff.min() == set.min() + 1 + ? formatNumber(set.min()) + : "<= " + formatNumber(diff.min() - 1); + String max = diff.max() == set.max() + ? "" + : diff.max() == set.max() - 1 + ? formatNumber(set.max()) + : ">= " + formatNumber(diff.max() + 1); + if (min.isEmpty()) { + return max; + } + if (max.isEmpty()) { + return min; + } + return JavaAnalysisLocalize.longRangeSetPresentationTwoValues(min, max).get(); + } + } + if (myRanges.length == 4 && myRanges[0] == myRanges[1] && myRanges[2] == myRanges[3]) { + return JavaAnalysisLocalize.longRangeSetPresentationTwoValues(myRanges[0], myRanges[2]).get(); + } + return JavaAnalysisLocalize.longRangeSetPresentationRange(toString()).get(); + } + + @Override + public boolean isCardinalityBigger(long cutoff) { + long totalDiff = 0; + for (int i = 0; i < myRanges.length; i += 2) { + long diff = myRanges[i + 1] - myRanges[i]; + if (diff < 0) { + return true; + } + totalDiff += diff + 1; + if (totalDiff < 0 || totalDiff > cutoff) { + return true; + } + } + return false; + } + + @Override + public LongRangeSet castTo(PsiPrimitiveType type) { + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.unite(range(myRanges[i], myRanges[i + 1]).castTo(type)); + } + return result; + } + + @Override + public LongRangeSet abs(boolean isLong) { + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.unite(range(myRanges[i], myRanges[i + 1]).abs(isLong)); + } + return result; + } + + @Override + public LongRangeSet negate(boolean isLong) { + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.unite(range(myRanges[i], myRanges[i + 1]).negate(isLong)); + } + return result; + } + + @Override + public LongRangeSet plus(LongRangeSet other, boolean isLong) { + if (myRanges.length > 6) { + return range(min(), max()).plus(other, isLong); + } + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.unite(range(myRanges[i], myRanges[i + 1]).plus(other, isLong)); + } + return result; + } + + @Override + public LongRangeSet mul(LongRangeSet multiplier, boolean isLong) { + if (multiplier.isEmpty()) { + return multiplier; + } + if (multiplier instanceof Point) { + return multiplier.mul(this, isLong); + } + return isLong ? Range.LONG_RANGE : Range.INT_RANGE; + } + + @Override + public LongRangeSet mod(LongRangeSet divisor) { + if (divisor.isEmpty()) { + return empty(); + } + LongRangeSet result = empty(); + for (int i = 0; i < myRanges.length; i += 2) { + result = result.unite(range(myRanges[i], myRanges[i + 1]).mod(divisor)); + } + return result; + } + + @Override + public LongStream stream() { + return IntStream.range(0, myRanges.length / 2) + .mapToObj(idx -> LongStream.rangeClosed(myRanges[idx * 2], myRanges[idx * 2 + 1])) + .reduce(LongStream::concat).orElseGet(LongStream::empty); + } + + @Override + long[] asRanges() { + return myRanges; + } - @Override - public String toString() { - StringJoiner sb = new StringJoiner(", ", "{", "}"); - for (int i = 0; i < myRanges.length; i += 2) { - sb.add(LongRangeSet.toString(myRanges[i], myRanges[i + 1])); - } - return sb.toString(); + @Override + public int hashCode() { + return Arrays.hashCode(myRanges); + } + + @Override + public boolean equals(Object o) { + return o == this + || o instanceof RangeSet that + && Arrays.equals(myRanges, that.myRanges); + } + + @Override + public String toString() { + StringJoiner sb = new StringJoiner(", ", "{", "}"); + for (int i = 0; i < myRanges.length; i += 2) { + sb.add(LongRangeSet.toString(myRanges[i], myRanges[i + 1])); + } + return sb.toString(); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeUtil.java index ed2565c252..25deac3d8f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/rangeSet/LongRangeUtil.java @@ -3,8 +3,7 @@ import consulo.util.lang.ThreeState; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Helper methods for LongRangeSet implementation @@ -127,7 +126,6 @@ static class BitString { * @param other a BitString to join * @return resulting BitString */ - @Nonnull BitString unite(BitString other) { long diff = myBits ^ other.myBits; return new BitString(myBits, myMask & other.myMask & ~diff); @@ -153,7 +151,6 @@ BitString intersect(BitString other) { * @param bit a bit number (0 = LSB) * @return YES for set bit, NO for clear bit, UNSURE for unknown bit */ - @Nonnull ThreeState get(int bit) { return isSet(myMask, bit) ? ThreeState.fromBoolean(isSet(myBits, bit)) : ThreeState.UNSURE; } @@ -164,7 +161,6 @@ ThreeState get(int bit) { * @param other other operand * @return result of bitwise-and */ - @Nonnull BitString and(BitString other) { long andBits = myBits & other.myBits; // 0 & ? = ? & 0 = 0; 1 & 1 = 1; ? & 1 = 1 & ? = ? & ? = ? @@ -178,7 +174,6 @@ BitString and(BitString other) { * @param other other operand * @return result of bitwise-or */ - @Nonnull BitString or(BitString other) { long orBits = myBits | other.myBits; // 1 | ? = ? | 1 = 1; 0 | 0 = 0; ? | 0 = 0 | ? = ? | ? = ? @@ -192,7 +187,6 @@ BitString or(BitString other) { * @param other other operand * @return result of bitwise-xor */ - @Nonnull BitString xor(BitString other) { long xorBits = myBits ^ other.myBits; // if bit is unknown in either operand, it's unknown in the result; otherwise we may xor normally @@ -221,7 +215,6 @@ public String toString() { * @return a BitString which covers at least all the values between from and to (may also cover some more values) */ static - @Nonnull BitString fromRange(long from, long to) { if (from == to) { return new BitString(from, -1L); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfAntiConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfAntiConstantType.java index 553971f467..6c5e18f321 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfAntiConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfAntiConstantType.java @@ -2,7 +2,6 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.Objects; @@ -12,17 +11,16 @@ * Represents a type that maintains a set of constants that excluded from this type */ public abstract class DfAntiConstantType implements DfType { - final @Nonnull + final Set myNotValues; - DfAntiConstantType(@Nonnull Set notValues) { + DfAntiConstantType(Set notValues) { myNotValues = notValues; } /** * @return set of excluded constants */ - @Nonnull public Set getNotValues() { return Collections.unmodifiableSet(myNotValues); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanConstantType.java index 3d58295063..94871c0500 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanConstantType.java @@ -2,28 +2,24 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; class DfBooleanConstantType extends DfConstantType implements DfBooleanType { DfBooleanConstantType(boolean value) { super(value); } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other.equals(this)) return this; if (other instanceof DfBooleanType) return DfTypes.BOOLEAN; return DfTypes.TOP; } - @Nonnull @Override public PsiPrimitiveType getPsiType() { return DfBooleanType.super.getPsiType(); } - @Nonnull @Override public DfType tryNegate() { return getValue() ? DfTypes.FALSE : DfTypes.TRUE; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanType.java index eb8d5470b6..b90228ea8f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfBooleanType.java @@ -3,10 +3,8 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; public interface DfBooleanType extends DfPrimitiveType { - @Nonnull @Override default PsiPrimitiveType getPsiType() { return PsiType.BOOLEAN; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfConstantType.java index a6d5c1bf8b..15617297f2 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfConstantType.java @@ -7,8 +7,7 @@ import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Objects; /** @@ -22,16 +21,14 @@ public abstract class DfConstantType implements DfType { } @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return other.equals(this) || other == DfTypes.BOTTOM; } - @Nonnull public abstract PsiType getPsiType(); - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { return other.isSuperType(this) ? this : DfTypes.BOTTOM; } @@ -62,7 +59,7 @@ public String toString() { * @param value constant value * @return true if given dfType represents a constant that is equal to given value */ - public static boolean isConst(@Nonnull DfType dfType, @Nullable Object value) { + public static boolean isConst(DfType dfType, @Nullable Object value) { return dfType instanceof DfConstantType && Objects.equals(((DfConstantType) dfType).getValue(), value); } @@ -73,7 +70,7 @@ public static boolean isConst(@Nonnull DfType dfType, @Nullable Object value) { * @return the constant of given type; null if the supplied dfType is not a constant or its type class differs from the supplied one. */ @Nullable - public static T getConstantOfType(@Nonnull DfType dfType, @Nonnull Class clazz) { + public static T getConstantOfType(DfType dfType, Class clazz) { return dfType instanceof DfConstantType ? ObjectUtil.tryCast(((DfConstantType) dfType).getValue(), clazz) : null; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleConstantType.java index 9facc57fc5..28b9780809 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleConstantType.java @@ -2,7 +2,6 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; import java.util.Collections; @@ -11,21 +10,18 @@ class DfDoubleConstantType extends DfConstantType implements DfDoubleTyp super(value); } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other.isSuperType(this)) return other; if (other instanceof DfDoubleType) return DfTypes.DOUBLE; return DfTypes.TOP; } - @Nonnull @Override public PsiPrimitiveType getPsiType() { return DfDoubleType.super.getPsiType(); } - @Nonnull @Override public DfType tryNegate() { return new DfDoubleNotValueType(Collections.singleton(getValue())); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleNotValueType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleNotValueType.java index 2d3d615fef..ed17195b42 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleNotValueType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleNotValueType.java @@ -1,55 +1,68 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.language.psi.PsiKeyword; -import javax.annotation.Nonnull; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import java.util.HashSet; import java.util.Set; class DfDoubleNotValueType extends DfAntiConstantType implements DfDoubleType { - DfDoubleNotValueType(Set values) { - super(values); - } + DfDoubleNotValueType(Set values) { + super(values); + } - @Override - public boolean isSuperType(@Nonnull DfType other) { - if (other == DfTypes.BOTTOM || other.equals(this)) return true; - if (other instanceof DfDoubleNotValueType) return ((DfDoubleNotValueType)other).myNotValues.containsAll(myNotValues); - if (other instanceof DfDoubleConstantType) return !myNotValues.contains(((DfDoubleConstantType)other).getValue()); - return false; - } + @Override + public boolean isSuperType(DfType other) { + if (other == DfTypes.BOTTOM || other.equals(this)) { + return true; + } + if (other instanceof DfDoubleNotValueType doubleNotValueType) { + return doubleNotValueType.myNotValues.containsAll(myNotValues); + } + if (other instanceof DfDoubleConstantType doubleConstantType) { + return !myNotValues.contains(doubleConstantType.getValue()); + } + return false; + } - @Nonnull - @Override - public DfType join(@Nonnull DfType other) { - if (isSuperType(other)) return this; - if (other.isSuperType(this)) return other; - if (other instanceof DfDoubleNotValueType) { - Set notValues = new HashSet<>(myNotValues); - notValues.retainAll(((DfDoubleNotValueType)other).myNotValues); - return notValues.isEmpty() ? DfTypes.DOUBLE : new DfDoubleNotValueType(notValues); + @Override + public DfType join(DfType other) { + if (isSuperType(other)) { + return this; + } + if (other.isSuperType(this)) { + return other; + } + if (other instanceof DfDoubleNotValueType doubleNotValueType) { + Set notValues = new HashSet<>(myNotValues); + notValues.retainAll(doubleNotValueType.myNotValues); + return notValues.isEmpty() ? DfTypes.DOUBLE : new DfDoubleNotValueType(notValues); + } + return DfTypes.TOP; } - return DfTypes.TOP; - } - @Nonnull - @Override - public DfType meet(@Nonnull DfType other) { - if (isSuperType(other)) return other; - if (other.isSuperType(this)) return this; - if (other instanceof DfDoubleConstantType && myNotValues.contains(((DfDoubleConstantType)other).getValue())) return DfTypes.BOTTOM; - if (other instanceof DfDoubleNotValueType) { - Set notValues = new HashSet<>(myNotValues); - notValues.addAll(((DfDoubleNotValueType)other).myNotValues); - return new DfDoubleNotValueType(notValues); + @Override + public DfType meet(DfType other) { + if (isSuperType(other)) { + return other; + } + if (other.isSuperType(this)) { + return this; + } + if (other instanceof DfDoubleConstantType doubleConstantType && myNotValues.contains(doubleConstantType.getValue())) { + return DfTypes.BOTTOM; + } + if (other instanceof DfDoubleNotValueType doubleNotValueType) { + Set notValues = new HashSet<>(myNotValues); + notValues.addAll(doubleNotValueType.myNotValues); + return new DfDoubleNotValueType(notValues); + } + return DfTypes.BOTTOM; } - return DfTypes.BOTTOM; - } - @Override - public String toString() { - return JavaAnalysisBundle.message("type.presentation.except.values", PsiKeyword.DOUBLE, myNotValues); - } + @Override + public String toString() { + return JavaAnalysisLocalize.typePresentationExceptValues(PsiKeyword.DOUBLE, myNotValues).get(); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleType.java index 7c3194420f..baf15647c4 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfDoubleType.java @@ -3,10 +3,8 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; public interface DfDoubleType extends DfFloatingPointType { - @Nonnull @Override default PsiPrimitiveType getPsiType() { return PsiType.DOUBLE; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfEphemeralReferenceType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfEphemeralReferenceType.java index 4275461da4..de56bde49a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfEphemeralReferenceType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfEphemeralReferenceType.java @@ -3,7 +3,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.*; -import javax.annotation.Nonnull; import java.util.Set; /** @@ -17,39 +16,39 @@ * @see DfaMemoryState#isEphemeral() */ public class DfEphemeralReferenceType implements DfReferenceType { - private final @Nonnull + private final TypeConstraint myTypeConstraint; - DfEphemeralReferenceType(@Nonnull TypeConstraint constraint) { + DfEphemeralReferenceType(TypeConstraint constraint) { myTypeConstraint = constraint; } @Override - public @Nonnull + public DfaNullability getNullability() { return DfaNullability.NOT_NULL; } @Override - public @Nonnull + public TypeConstraint getConstraint() { return myTypeConstraint; } @Override - public @Nonnull + public DfReferenceType dropNullability() { return this; } @Override - public @Nonnull + public DfReferenceType dropTypeConstraint() { return DfTypes.NOT_NULL_OBJECT; } @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { if (other == DfTypes.BOTTOM) return true; if (other instanceof DfEphemeralReferenceType) { return myTypeConstraint.isSuperConstraintOf(((DfEphemeralReferenceType)other).myTypeConstraint); @@ -58,8 +57,8 @@ public boolean isSuperType(@Nonnull DfType other) { } @Override - public @Nonnull - DfType join(@Nonnull DfType other) { + public + DfType join(DfType other) { if (other == DfTypes.BOTTOM) return this; if (other == DfTypes.TOP || !(other instanceof DfReferenceType)) return DfTypes.TOP; TypeConstraint otherConstraint = ((DfReferenceType)other).getConstraint(); @@ -76,8 +75,8 @@ DfType join(@Nonnull DfType other) { } @Override - public @Nonnull - DfType meet(@Nonnull DfType other) { + public + DfType meet(DfType other) { if (other == DfTypes.TOP) return this; if (other == DfTypes.BOTTOM) return other; if (other instanceof DfEphemeralReferenceType || diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatConstantType.java index d31f155433..3be7d7e086 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatConstantType.java @@ -2,7 +2,6 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; import java.util.Collections; @@ -11,21 +10,18 @@ class DfFloatConstantType extends DfConstantType implements DfFloatType { super(value); } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other.isSuperType(this)) return other; if (other instanceof DfFloatType) return DfTypes.FLOAT; return DfTypes.TOP; } - @Nonnull @Override public PsiPrimitiveType getPsiType() { return DfFloatType.super.getPsiType(); } - @Nonnull @Override public DfType tryNegate() { return new DfFloatNotValueType(Collections.singleton(getValue())); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatNotValueType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatNotValueType.java index 29751c08d9..85cc2b7c73 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatNotValueType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatNotValueType.java @@ -1,56 +1,69 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; -import com.intellij.java.analysis.JavaAnalysisBundle; -import consulo.util.lang.StringUtil; import com.intellij.java.language.psi.PsiKeyword; -import javax.annotation.Nonnull; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.util.lang.StringUtil; import java.util.HashSet; import java.util.Set; class DfFloatNotValueType extends DfAntiConstantType implements DfFloatType { - DfFloatNotValueType(Set values) { - super(values); - } + DfFloatNotValueType(Set values) { + super(values); + } - @Override - public boolean isSuperType(@Nonnull DfType other) { - if (other == DfTypes.BOTTOM || other.equals(this)) return true; - if (other instanceof DfFloatNotValueType) return ((DfFloatNotValueType)other).myNotValues.containsAll(myNotValues); - if (other instanceof DfFloatConstantType) return !myNotValues.contains(((DfFloatConstantType)other).getValue()); - return false; - } - - @Nonnull - @Override - public DfType join(@Nonnull DfType other) { - if (isSuperType(other)) return this; - if (other.isSuperType(this)) return other; - if (other instanceof DfFloatNotValueType) { - Set notValues = new HashSet<>(myNotValues); - notValues.retainAll(((DfFloatNotValueType)other).myNotValues); - return notValues.isEmpty() ? DfTypes.FLOAT : new DfFloatNotValueType(notValues); + @Override + public boolean isSuperType(DfType other) { + if (other == DfTypes.BOTTOM || other.equals(this)) { + return true; + } + if (other instanceof DfFloatNotValueType floatNotValueType) { + return floatNotValueType.myNotValues.containsAll(myNotValues); + } + if (other instanceof DfFloatConstantType floatConstantType) { + return !myNotValues.contains(floatConstantType.getValue()); + } + return false; } - return DfTypes.TOP; - } - @Nonnull - @Override - public DfType meet(@Nonnull DfType other) { - if (isSuperType(other)) return other; - if (other.isSuperType(this)) return this; - if (other instanceof DfFloatConstantType && myNotValues.contains(((DfFloatConstantType)other).getValue())) return DfTypes.BOTTOM; - if (other instanceof DfFloatNotValueType) { - Set notValues = new HashSet<>(myNotValues); - notValues.addAll(((DfFloatNotValueType)other).myNotValues); - return new DfFloatNotValueType(notValues); + @Override + public DfType join(DfType other) { + if (isSuperType(other)) { + return this; + } + if (other.isSuperType(this)) { + return other; + } + if (other instanceof DfFloatNotValueType floatNotValueType) { + Set notValues = new HashSet<>(myNotValues); + notValues.retainAll(floatNotValueType.myNotValues); + return notValues.isEmpty() ? DfTypes.FLOAT : new DfFloatNotValueType(notValues); + } + return DfTypes.TOP; } - return DfTypes.BOTTOM; - } - @Override - public String toString() { - return JavaAnalysisBundle.message("type.presentation.except.values", PsiKeyword.FLOAT, StringUtil.join(myNotValues, ", ")); - } + @Override + public DfType meet(DfType other) { + if (isSuperType(other)) { + return other; + } + if (other.isSuperType(this)) { + return this; + } + if (other instanceof DfFloatConstantType floatConstantType && myNotValues.contains(floatConstantType.getValue())) { + return DfTypes.BOTTOM; + } + if (other instanceof DfFloatNotValueType floatNotValueType) { + Set notValues = new HashSet<>(myNotValues); + notValues.addAll(floatNotValueType.myNotValues); + return new DfFloatNotValueType(notValues); + } + return DfTypes.BOTTOM; + } + + @Override + public String toString() { + return JavaAnalysisLocalize.typePresentationExceptValues(PsiKeyword.FLOAT, StringUtil.join(myNotValues, ", ")).get(); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatType.java index 215634ea0d..267cbbada7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfFloatType.java @@ -3,10 +3,8 @@ import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; public interface DfFloatType extends DfFloatingPointType { - @Nonnull @Override default PsiPrimitiveType getPsiType() { return PsiType.FLOAT; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfGenericObjectType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfGenericObjectType.java index 4648302496..fe980b8209 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfGenericObjectType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfGenericObjectType.java @@ -1,390 +1,394 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; -import com.intellij.java.analysis.JavaAnalysisBundle; import com.intellij.java.analysis.impl.codeInspection.dataFlow.*; import com.intellij.java.language.psi.JavaPsiFacade; import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiEnumConstant; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.BOTTOM; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.TOP; class DfGenericObjectType extends DfAntiConstantType implements DfReferenceType { - private final - @Nonnull - TypeConstraint myConstraint; - private final - @Nonnull - DfaNullability myNullability; - private final - @Nonnull - Mutability myMutability; - private final - @Nullable - SpecialField mySpecialField; - private final - @Nonnull - DfType mySpecialFieldType; - private final boolean myLocal; + private final + TypeConstraint myConstraint; + private final + DfaNullability myNullability; + private final + Mutability myMutability; + private final + @Nullable + SpecialField mySpecialField; + private final + DfType mySpecialFieldType; + private final boolean myLocal; - DfGenericObjectType(@Nonnull Set notValues, - @Nonnull TypeConstraint constraint, - @Nonnull DfaNullability nullability, - @Nonnull Mutability mutability, - @Nullable SpecialField field, - @Nonnull DfType type, - boolean local) { - super(notValues); - assert constraint != TypeConstraints.BOTTOM; - myConstraint = constraint; - myNullability = nullability; - myMutability = mutability; - mySpecialField = field; - mySpecialFieldType = type instanceof DfReferenceType ? ((DfReferenceType) type).dropSpecialField() : type; - myLocal = local; - } - - @Nonnull - @Override - public DfaNullability getNullability() { - return myNullability; - } - - @Nonnull - @Override - public TypeConstraint getConstraint() { - return myConstraint; - } - - @Nonnull - @Override - public Mutability getMutability() { - return myMutability; - } - - @Override - public boolean isLocal() { - return myLocal; - } - - @Nullable - @Override - public SpecialField getSpecialField() { - return mySpecialField; - } - - @Nonnull - @Override - public DfType getSpecialFieldType() { - return mySpecialFieldType; - } - - @Override - public DfType tryNegate() { - if (myMutability != Mutability.UNKNOWN || myLocal || mySpecialField != null) { - return null; - } - TypeConstraint negated = myConstraint.tryNegate(); - if (negated == null) { - return null; + DfGenericObjectType( + Set notValues, + TypeConstraint constraint, + DfaNullability nullability, + Mutability mutability, + @Nullable SpecialField field, + DfType type, + boolean local + ) { + super(notValues); + assert constraint != TypeConstraints.BOTTOM; + myConstraint = constraint; + myNullability = nullability; + myMutability = mutability; + mySpecialField = field; + mySpecialFieldType = type instanceof DfReferenceType ? ((DfReferenceType)type).dropSpecialField() : type; + myLocal = local; } - DfType result = negated.asDfType(); - return myNullability == DfaNullability.NOT_NULL ? result.join(DfTypes.NULL) : result.meet(DfTypes.NOT_NULL_OBJECT); - } - @Nonnull - @Override - public Set getNotValues() { - if (myNullability == DfaNullability.NOT_NULL) { - Set values = new HashSet<>(myNotValues); - values.add(null); - return Collections.unmodifiableSet(values); + @Override + public DfaNullability getNullability() { + return myNullability; } - return super.getNotValues(); - } - - @Nonnull - @Override - public DfReferenceType dropTypeConstraint() { - return myConstraint == TypeConstraints.TOP ? this : - new DfGenericObjectType(myNotValues, TypeConstraints.TOP, myNullability, myMutability, mySpecialField, mySpecialFieldType, - myLocal); - } - - @Nonnull - @Override - public DfReferenceType dropMutability() { - return myMutability == Mutability.UNKNOWN ? this : - new DfGenericObjectType(myNotValues, myConstraint, myNullability, Mutability.UNKNOWN, mySpecialField, mySpecialFieldType, - myLocal); - } - - @Nonnull - @Override - public DfReferenceType dropLocality() { - return myLocal ? new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType, - false) : this; - } - - @Nonnull - @Override - public DfReferenceType dropNullability() { - return myNullability == DfaNullability.UNKNOWN ? this : - new DfGenericObjectType(myNotValues, myConstraint, DfaNullability.UNKNOWN, myMutability, mySpecialField, mySpecialFieldType, - myLocal); - } - @Nonnull - @Override - public DfReferenceType dropSpecialField() { - return mySpecialField == null ? this : - new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, null, BOTTOM, myLocal); - } - - @Override - public boolean isSuperType(@Nonnull DfType other) { - if (other == BOTTOM) { - return true; - } - if (other instanceof DfNullConstantType) { - return getNullability() != DfaNullability.NOT_NULL; - } - if (!(other instanceof DfReferenceType)) { - return false; - } - if (!myNotValues.isEmpty()) { - if (other instanceof DfReferenceConstantType) { - if (myNotValues.contains(((DfReferenceConstantType) other).getValue())) { - return false; - } - } else if (other instanceof DfGenericObjectType) { - if (!((DfGenericObjectType) other).myNotValues.containsAll(myNotValues)) { - return false; - } - } else { - return false; - } - } - DfReferenceType type = (DfReferenceType) other; - if (isLocal() && !type.isLocal()) { - return false; - } - if (type.getNullability() != getNullability() && getNullability() != DfaNullability.UNKNOWN && - type.getNullability() != DfaNullability.NOT_NULL) { - return false; + @Override + public TypeConstraint getConstraint() { + return myConstraint; } - if (!getConstraint().isSuperConstraintOf(type.getConstraint())) { - return false; - } - if (getMutability().ordinal() > type.getMutability().ordinal()) { - return false; - } - SpecialField sf = getSpecialField(); - if (sf != null) { - if (sf != type.getSpecialField()) { - return false; - } - if (!getSpecialFieldType().isSuperType(type.getSpecialFieldType())) { - return false; - } - } - return true; - } - @Override - public boolean isMergeable(@Nonnull DfType other) { - if (!isSuperType(other)) { - return false; + @Override + public Mutability getMutability() { + return myMutability; } - if (getNullability() == DfaNullability.UNKNOWN) { - DfaNullability otherNullability = DfaNullability.fromDfType(other); - return otherNullability != DfaNullability.NULL && otherNullability != DfaNullability.NULLABLE; - } - return true; - } - @Nonnull - @Override - public DfType join(@Nonnull DfType other) { - if (isSuperType(other)) { - return this; - } - if (other.isSuperType(this)) { - return other; + @Override + public boolean isLocal() { + return myLocal; } - if (!(other instanceof DfReferenceType)) { - return TOP; - } - if (other instanceof DfNullConstantType || other instanceof DfEphemeralReferenceType) { - return other.join(this); - } - DfReferenceType type = (DfReferenceType) other; - TypeConstraint constraint = getConstraint().join(type.getConstraint()); - DfaNullability nullability = getNullability().unite(type.getNullability()); - Mutability mutability = getMutability().unite(type.getMutability()); - boolean locality = isLocal() && type.isLocal(); - SpecialField sf = Objects.equals(getSpecialField(), type.getSpecialField()) ? getSpecialField() : null; - DfType sfType = sf == null ? BOTTOM : getSpecialFieldType().join(type.getSpecialFieldType()); - Set notValues = myNotValues; - if (type instanceof DfGenericObjectType) { - notValues = new HashSet<>(myNotValues); - notValues.retainAll(((DfGenericObjectType) other).myNotValues); - } - if (type instanceof DfReferenceConstantType) { - notValues = new HashSet<>(myNotValues); - notValues.remove(((DfReferenceConstantType) type).getValue()); - } - return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality); - } - @Nonnull - @Override - public DfType meet(@Nonnull DfType other) { - if (other instanceof DfConstantType || other instanceof DfEphemeralReferenceType) { - return other.meet(this); - } - if (isSuperType(other)) { - return other; + @Nullable + @Override + public SpecialField getSpecialField() { + return mySpecialField; } - if (other.isSuperType(this)) { - return this; + + @Override + public DfType getSpecialFieldType() { + return mySpecialFieldType; } - if (!(other instanceof DfReferenceType)) { - return BOTTOM; + + @Override + public DfType tryNegate() { + if (myMutability != Mutability.UNKNOWN || myLocal || mySpecialField != null) { + return null; + } + TypeConstraint negated = myConstraint.tryNegate(); + if (negated == null) { + return null; + } + DfType result = negated.asDfType(); + return myNullability == DfaNullability.NOT_NULL ? result.join(DfTypes.NULL) : result.meet(DfTypes.NOT_NULL_OBJECT); } - DfReferenceType type = (DfReferenceType) other; - TypeConstraint constraint = getConstraint().meet(type.getConstraint()); - if (constraint == TypeConstraints.BOTTOM) { - return isSuperType(DfTypes.NULL) && other.isSuperType(DfTypes.NULL) ? DfTypes.NULL : BOTTOM; + + @Override + public Set getNotValues() { + if (myNullability == DfaNullability.NOT_NULL) { + Set values = new HashSet<>(myNotValues); + values.add(null); + return Collections.unmodifiableSet(values); + } + return super.getNotValues(); } - DfaNullability nullability = getNullability().intersect(type.getNullability()); - if (nullability == null) { - return BOTTOM; + + @Override + public DfReferenceType dropTypeConstraint() { + return myConstraint == TypeConstraints.TOP + ? this + : new DfGenericObjectType( + myNotValues, + TypeConstraints.TOP, + myNullability, + myMutability, + mySpecialField, + mySpecialFieldType, + myLocal + ); } - Mutability mutability = getMutability().intersect(type.getMutability()); - boolean locality = isLocal() || type.isLocal(); - SpecialField sf; - DfType sfType; - if (getSpecialField() == null) { - sf = type.getSpecialField(); - sfType = type.getSpecialFieldType(); - } else if (type.getSpecialField() == null) { - sf = getSpecialField(); - sfType = getSpecialFieldType(); - } else { - sf = getSpecialField(); - if (sf != type.getSpecialField()) { - return BOTTOM; - } - sfType = sf == null ? BOTTOM : getSpecialFieldType().meet(type.getSpecialFieldType()); + + @Override + public DfReferenceType dropMutability() { + return myMutability == Mutability.UNKNOWN + ? this + : new DfGenericObjectType( + myNotValues, + myConstraint, + myNullability, + Mutability.UNKNOWN, + mySpecialField, + mySpecialFieldType, + myLocal + ); } - if (sf != null && sfType == BOTTOM) { - return BOTTOM; + + @Override + public DfReferenceType dropLocality() { + return myLocal + ? new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType, false) + : this; } - Set notValues = myNotValues; - if (type instanceof DfGenericObjectType) { - Set otherNotValues = ((DfGenericObjectType) other).myNotValues; - if (otherNotValues.containsAll(myNotValues)) { - notValues = otherNotValues; - } else if (!myNotValues.containsAll(otherNotValues)) { - notValues = new HashSet<>(myNotValues); - notValues.addAll(otherNotValues); - if (nullability == DfaNullability.NOT_NULL) { - DfEphemeralReferenceType ephemeralValue = checkEphemeral(constraint, notValues); - if (ephemeralValue != null) { - return ephemeralValue; - } - } - } + + @Override + public DfReferenceType dropNullability() { + return myNullability == DfaNullability.UNKNOWN + ? this + : new DfGenericObjectType( + myNotValues, + myConstraint, + DfaNullability.UNKNOWN, + myMutability, + mySpecialField, + mySpecialFieldType, + myLocal + ); } - return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality); - } - private static DfEphemeralReferenceType checkEphemeral(TypeConstraint constraint, Set notValues) { - if (notValues.isEmpty()) { - return null; + @Override + public DfReferenceType dropSpecialField() { + return mySpecialField == null ? this : + new DfGenericObjectType(myNotValues, myConstraint, myNullability, myMutability, null, BOTTOM, myLocal); } - Object value = notValues.iterator().next(); - if (!(value instanceof PsiEnumConstant)) { - return null; + + @Override + public boolean isSuperType(DfType other) { + if (other == BOTTOM) { + return true; + } + if (other instanceof DfNullConstantType) { + return getNullability() != DfaNullability.NOT_NULL; + } + if (!(other instanceof DfReferenceType)) { + return false; + } + if (!myNotValues.isEmpty()) { + if (other instanceof DfReferenceConstantType referenceConstantType) { + if (myNotValues.contains(referenceConstantType.getValue())) { + return false; + } + } + else if (other instanceof DfGenericObjectType genericObjectType) { + if (!genericObjectType.myNotValues.containsAll(myNotValues)) { + return false; + } + } + else { + return false; + } + } + DfReferenceType type = (DfReferenceType)other; + if (isLocal() && !type.isLocal()) { + return false; + } + if (type.getNullability() != getNullability() && getNullability() != DfaNullability.UNKNOWN && + type.getNullability() != DfaNullability.NOT_NULL) { + return false; + } + if (!getConstraint().isSuperConstraintOf(type.getConstraint())) { + return false; + } + if (getMutability().ordinal() > type.getMutability().ordinal()) { + return false; + } + SpecialField sf = getSpecialField(); + if (sf != null) { + if (sf != type.getSpecialField()) { + return false; + } + if (!getSpecialFieldType().isSuperType(type.getSpecialFieldType())) { + return false; + } + } + return true; } - PsiClass enumClass = ((PsiEnumConstant) value).getContainingClass(); - if (enumClass == null) { - return null; + + @Override + public boolean isMergeable(DfType other) { + if (!isSuperType(other)) { + return false; + } + if (getNullability() == DfaNullability.UNKNOWN) { + DfaNullability otherNullability = DfaNullability.fromDfType(other); + return otherNullability != DfaNullability.NULL && otherNullability != DfaNullability.NULLABLE; + } + return true; } - TypeConstraint enumType = TypeConstraints.instanceOf( - JavaPsiFacade.getElementFactory(enumClass.getProject()).createType(enumClass)); - if (!enumType.equals(constraint)) { - return null; + + @Override + public DfType join(DfType other) { + if (isSuperType(other)) { + return this; + } + if (other.isSuperType(this)) { + return other; + } + if (!(other instanceof DfReferenceType)) { + return TOP; + } + if (other instanceof DfNullConstantType || other instanceof DfEphemeralReferenceType) { + return other.join(this); + } + DfReferenceType type = (DfReferenceType)other; + TypeConstraint constraint = getConstraint().join(type.getConstraint()); + DfaNullability nullability = getNullability().unite(type.getNullability()); + Mutability mutability = getMutability().unite(type.getMutability()); + boolean locality = isLocal() && type.isLocal(); + SpecialField sf = Objects.equals(getSpecialField(), type.getSpecialField()) ? getSpecialField() : null; + DfType sfType = sf == null ? BOTTOM : getSpecialFieldType().join(type.getSpecialFieldType()); + Set notValues = myNotValues; + if (type instanceof DfGenericObjectType genericObjectType) { + notValues = new HashSet<>(myNotValues); + notValues.retainAll(genericObjectType.myNotValues); + } + if (type instanceof DfReferenceConstantType referenceConstantType) { + notValues = new HashSet<>(myNotValues); + notValues.remove(referenceConstantType.getValue()); + } + return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality); } - Set allEnumConstants = StreamEx.of(enumClass.getFields()).select(PsiEnumConstant.class).toSet(); - if (notValues.size() != allEnumConstants.size()) { - return null; + + @Override + public DfType meet(DfType other) { + if (other instanceof DfConstantType || other instanceof DfEphemeralReferenceType) { + return other.meet(this); + } + if (isSuperType(other)) { + return other; + } + if (other.isSuperType(this)) { + return this; + } + if (!(other instanceof DfReferenceType)) { + return BOTTOM; + } + DfReferenceType type = (DfReferenceType)other; + TypeConstraint constraint = getConstraint().meet(type.getConstraint()); + if (constraint == TypeConstraints.BOTTOM) { + return isSuperType(DfTypes.NULL) && other.isSuperType(DfTypes.NULL) ? DfTypes.NULL : BOTTOM; + } + DfaNullability nullability = getNullability().intersect(type.getNullability()); + if (nullability == null) { + return BOTTOM; + } + Mutability mutability = getMutability().intersect(type.getMutability()); + boolean locality = isLocal() || type.isLocal(); + SpecialField sf; + DfType sfType; + if (getSpecialField() == null) { + sf = type.getSpecialField(); + sfType = type.getSpecialFieldType(); + } + else if (type.getSpecialField() == null) { + sf = getSpecialField(); + sfType = getSpecialFieldType(); + } + else { + sf = getSpecialField(); + if (sf != type.getSpecialField()) { + return BOTTOM; + } + sfType = sf == null ? BOTTOM : getSpecialFieldType().meet(type.getSpecialFieldType()); + } + if (sf != null && sfType == BOTTOM) { + return BOTTOM; + } + Set notValues = myNotValues; + if (type instanceof DfGenericObjectType genericObjectType) { + Set otherNotValues = genericObjectType.myNotValues; + if (otherNotValues.containsAll(myNotValues)) { + notValues = otherNotValues; + } + else if (!myNotValues.containsAll(otherNotValues)) { + notValues = new HashSet<>(myNotValues); + notValues.addAll(otherNotValues); + if (nullability == DfaNullability.NOT_NULL) { + DfEphemeralReferenceType ephemeralValue = checkEphemeral(constraint, notValues); + if (ephemeralValue != null) { + return ephemeralValue; + } + } + } + } + return new DfGenericObjectType(notValues, constraint, nullability, mutability, sf, sfType, locality); } - for (Object notValue : notValues) { - if (!(notValue instanceof PsiEnumConstant)) { - return null; - } - if (!allEnumConstants.remove(notValue)) { + + private static DfEphemeralReferenceType checkEphemeral(TypeConstraint constraint, Set notValues) { + if (notValues.isEmpty()) { + return null; + } + PsiClass enumClass = notValues.iterator().next() instanceof PsiEnumConstant enumConstant ? enumConstant.getContainingClass() : null; + if (enumClass == null) { + return null; + } + TypeConstraint enumType = TypeConstraints.instanceOf( + JavaPsiFacade.getElementFactory(enumClass.getProject()).createType(enumClass) + ); + if (!enumType.equals(constraint)) { + return null; + } + Set allEnumConstants = StreamEx.of(enumClass.getFields()).select(PsiEnumConstant.class).toSet(); + if (notValues.size() != allEnumConstants.size()) { + return null; + } + for (Object notValue : notValues) { + if (!(notValue instanceof PsiEnumConstant) || !allEnumConstants.remove(notValue)) { + return null; + } + } + if (allEnumConstants.isEmpty()) { + return new DfEphemeralReferenceType(constraint); + } return null; - } } - if (allEnumConstants.isEmpty()) { - return new DfEphemeralReferenceType(constraint); - } - return null; - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; + @Override + public boolean equals(Object o) { + return this == o + || o instanceof DfGenericObjectType that + && myLocal == that.myLocal + && myNullability == that.myNullability + && myMutability == that.myMutability + && mySpecialField == that.mySpecialField + && myConstraint.equals(that.myConstraint) + && mySpecialFieldType.equals(that.mySpecialFieldType) + && myNotValues.equals(that.myNotValues); } - DfGenericObjectType type = (DfGenericObjectType) o; - return myLocal == type.myLocal && - myNullability == type.myNullability && - myMutability == type.myMutability && - mySpecialField == type.mySpecialField && - myConstraint.equals(type.myConstraint) && - mySpecialFieldType.equals(type.mySpecialFieldType) && - myNotValues.equals(type.myNotValues); - } - - @Override - public int hashCode() { - return Objects.hash(myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType, myLocal, myNotValues); - } - @Override - public String toString() { - List components = new ArrayList<>(); - if (myConstraint != TypeConstraints.TOP) { - components.add(myConstraint.toString()); + @Override + public int hashCode() { + return Objects.hash(myConstraint, myNullability, myMutability, mySpecialField, mySpecialFieldType, myLocal, myNotValues); } - if (myNullability != DfaNullability.UNKNOWN) { - components.add(myNullability.toString()); - } - if (myMutability != Mutability.UNKNOWN) { - components.add(myMutability.name()); - } - if (myLocal) { - components.add(JavaAnalysisBundle.message("type.information.local.object")); - } - if (mySpecialField != null) { - components.add(mySpecialField + "=" + mySpecialFieldType); - } - if (!myNotValues.isEmpty()) { - components.add("!= " + StreamEx.of(myNotValues).map(DfConstantType::renderValue).joining(", ")); + + @Override + public String toString() { + List components = new ArrayList<>(); + if (myConstraint != TypeConstraints.TOP) { + components.add(myConstraint.toString()); + } + if (myNullability != DfaNullability.UNKNOWN) { + components.add(myNullability.toString()); + } + if (myMutability != Mutability.UNKNOWN) { + components.add(myMutability.name()); + } + if (myLocal) { + components.add(JavaAnalysisLocalize.typeInformationLocalObject().get()); + } + if (mySpecialField != null) { + components.add(mySpecialField + "=" + mySpecialFieldType); + } + if (!myNotValues.isEmpty()) { + components.add("!= " + StreamEx.of(myNotValues).map(DfConstantType::renderValue).joining(", ")); + } + return String.join(" ", components); } - return String.join(" ", components); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntConstantType.java index 418f14580a..a22110a800 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntConstantType.java @@ -3,20 +3,17 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; class DfIntConstantType extends DfConstantType implements DfIntType { DfIntConstantType(int value) { super(value); } - @Nonnull @Override public PsiPrimitiveType getPsiType() { return DfIntType.super.getPsiType(); } - @Nonnull @Override public LongRangeSet getRange() { return LongRangeSet.point(getValue()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntRangeType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntRangeType.java index 969fbd1f81..21c350606a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntRangeType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntRangeType.java @@ -4,12 +4,11 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiKeyword; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; import java.util.Objects; class DfIntRangeType implements DfIntType { - static final @Nonnull + static final LongRangeSet FULL_RANGE = Objects.requireNonNull(LongRangeSet.fromType(PsiType.INT)); private final LongRangeSet myRange; @@ -20,14 +19,13 @@ class DfIntRangeType implements DfIntType { myRange = FULL_RANGE.equals(range) ? FULL_RANGE : range; } - @Nonnull @Override public LongRangeSet getRange() { return myRange; } @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { if (other == DfTypes.BOTTOM) return true; if (!(other instanceof DfIntType)) return false; return myRange.contains(((DfIntType)other).getRange()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntType.java index afb5985e4b..f48942c3ce 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntType.java @@ -4,36 +4,30 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public interface DfIntType extends DfIntegralType { @Override - @Nonnull LongRangeSet getRange(); - @Nonnull @Override - default DfType join(@Nonnull DfType other) { + default DfType join(DfType other) { if (!(other instanceof DfIntType)) return DfTypes.TOP; return DfTypes.intRange(((DfIntType)other).getRange().unite(getRange())); } - @Nonnull @Override - default DfType meet(@Nonnull DfType other) { + default DfType meet(DfType other) { if (other == DfTypes.TOP) return this; if (!(other instanceof DfIntType)) return DfTypes.BOTTOM; return DfTypes.intRange(((DfIntType)other).getRange().intersect(getRange())); } - @Nonnull @Override - default DfType meetRange(@Nonnull LongRangeSet range) { + default DfType meetRange(LongRangeSet range) { return meet(DfTypes.intRangeClamped(range)); } - @Nonnull @Override default PsiPrimitiveType getPsiType() { return PsiType.INT; @@ -47,8 +41,7 @@ default DfType tryNegate() { return res.intersects(range) ? null : DfTypes.intRange(res); } - @Nonnull - static LongRangeSet extractRange(@Nonnull DfType type) { + static LongRangeSet extractRange(DfType type) { return type instanceof DfIntegralType ? ((DfIntegralType)type).getRange().intersect(DfIntRangeType.FULL_RANGE) : DfIntRangeType.FULL_RANGE; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntegralType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntegralType.java index 0bf20bbade..6996a01ae9 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntegralType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfIntegralType.java @@ -3,17 +3,14 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.analysis.impl.codeInspection.dataFlow.value.RelationType; -import javax.annotation.Nonnull; /** * Represents an integral primitive (int or long) */ public interface DfIntegralType extends DfPrimitiveType { - @Nonnull LongRangeSet getRange(); - @Nonnull - default DfType meetRelation(@Nonnull RelationType relation, @Nonnull DfType other) { + default DfType meetRelation(RelationType relation, DfType other) { if (other == DfTypes.TOP) return this; if (other instanceof DfIntegralType) { return meetRange(((DfIntegralType)other).getRange().fromRelation(relation)); @@ -21,6 +18,5 @@ default DfType meetRelation(@Nonnull RelationType relation, @Nonnull DfType othe return DfTypes.BOTTOM; } - @Nonnull - DfType meetRange(@Nonnull LongRangeSet range); + DfType meetRange(LongRangeSet range); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongConstantType.java index 6f396d5694..43aa95657d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongConstantType.java @@ -3,20 +3,17 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; class DfLongConstantType extends DfConstantType implements DfLongType { DfLongConstantType(long value) { super(value); } - @Nonnull @Override public PsiPrimitiveType getPsiType() { return DfLongType.super.getPsiType(); } - @Nonnull @Override public LongRangeSet getRange() { return LongRangeSet.point(getValue()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongRangeType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongRangeType.java index 1a77d64255..d29e685338 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongRangeType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongRangeType.java @@ -4,7 +4,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiKeyword; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; class DfLongRangeType implements DfLongType { private final LongRangeSet myRange; @@ -13,14 +12,13 @@ class DfLongRangeType implements DfLongType { myRange = range; } - @Nonnull @Override public LongRangeSet getRange() { return myRange; } @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { if (other == DfTypes.BOTTOM) return true; if (!(other instanceof DfLongType)) return false; return myRange.contains(((DfLongType)other).getRange()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongType.java index 05a0d79980..fdbb72999a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfLongType.java @@ -4,36 +4,30 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.java.language.psi.PsiPrimitiveType; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public interface DfLongType extends DfIntegralType { @Override - @Nonnull LongRangeSet getRange(); - @Nonnull @Override - default DfType join(@Nonnull DfType other) { + default DfType join(DfType other) { if (!(other instanceof DfLongType)) return DfTypes.TOP; return DfTypes.longRange(((DfLongType)other).getRange().unite(getRange())); } - @Nonnull @Override - default DfType meet(@Nonnull DfType other) { + default DfType meet(DfType other) { if (other == DfTypes.TOP) return this; if (!(other instanceof DfLongType)) return DfTypes.BOTTOM; return DfTypes.longRange(((DfLongType)other).getRange().intersect(getRange())); } - @Nonnull @Override - default DfType meetRange(@Nonnull LongRangeSet range) { + default DfType meetRange(LongRangeSet range) { return meet(DfTypes.longRange(range)); } - @Nonnull @Override default PsiPrimitiveType getPsiType() { return PsiType.LONG; @@ -47,8 +41,7 @@ default DfType tryNegate() { return res.intersects(range) ? null : DfTypes.longRange(res); } - @Nonnull - static LongRangeSet extractRange(@Nonnull DfType type) { + static LongRangeSet extractRange(DfType type) { return type instanceof DfIntegralType ? ((DfIntegralType)type).getRange() : LongRangeSet.all(); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfNullConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfNullConstantType.java index fb9f3430a2..07aa493c0d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfNullConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfNullConstantType.java @@ -5,7 +5,6 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.TypeConstraint; import com.intellij.java.analysis.impl.codeInspection.dataFlow.TypeConstraints; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; import java.util.Set; @@ -17,13 +16,11 @@ public class DfNullConstantType extends DfConstantType implements DfRefe super(null); } - @Nonnull @Override public DfaNullability getNullability() { return DfaNullability.NULL; } - @Nonnull @Override public TypeConstraint getConstraint() { return TypeConstraints.TOP; @@ -34,21 +31,18 @@ public DfType tryNegate() { return DfTypes.NOT_NULL_OBJECT; } - @Nonnull @Override public PsiType getPsiType() { return PsiType.NULL; } - @Nonnull @Override public DfReferenceType dropNullability() { return DfTypes.OBJECT_OR_NULL; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (isSuperType(other)) return this; if (other.isSuperType(this)) return other; if (!(other instanceof DfReferenceType)) return TOP; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfPrimitiveType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfPrimitiveType.java index 4b5ea947a2..fbb8d971f3 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfPrimitiveType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfPrimitiveType.java @@ -2,12 +2,10 @@ package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; import com.intellij.java.language.psi.PsiPrimitiveType; -import javax.annotation.Nonnull; /** * A type that represents concrete JVM primitive type or subset of values of given type */ public interface DfPrimitiveType extends DfType { - @Nonnull PsiPrimitiveType getPsiType(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceConstantType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceConstantType.java index 34c8af83e8..e4c709d6e0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceConstantType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceConstantType.java @@ -5,8 +5,8 @@ import com.intellij.java.language.psi.PsiModifierListOwner; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.Objects; import java.util.Set; @@ -14,18 +14,18 @@ import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.TOP; public class DfReferenceConstantType extends DfConstantType implements DfReferenceType { - private final @Nonnull + private final PsiType myPsiType; - private final @Nonnull + private final TypeConstraint myConstraint; - private final @Nonnull + private final Mutability myMutability; private final @Nullable SpecialField mySpecialField; - private final @Nonnull + private final DfType mySpecialFieldType; - DfReferenceConstantType(@Nonnull Object constant, @Nonnull PsiType psiType, @Nonnull TypeConstraint type) { + DfReferenceConstantType(Object constant, PsiType psiType, TypeConstraint type) { super(constant); myPsiType = psiType; myConstraint = type; @@ -34,9 +34,8 @@ public class DfReferenceConstantType extends DfConstantType implements D mySpecialFieldType = mySpecialField == null ? BOTTOM : mySpecialField.fromConstant(constant); } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { if (other.isSuperType(this)) return this; if (other instanceof DfEphemeralReferenceType) return BOTTOM; if (other instanceof DfGenericObjectType) { @@ -51,25 +50,21 @@ public DfType meet(@Nonnull DfType other) { return BOTTOM; } - @Nonnull @Override public PsiType getPsiType() { return myPsiType; } - @Nonnull @Override public DfaNullability getNullability() { return DfaNullability.NOT_NULL; } - @Nonnull @Override public TypeConstraint getConstraint() { return myConstraint; } - @Nonnull @Override public Mutability getMutability() { return myMutability; @@ -81,7 +76,6 @@ public SpecialField getSpecialField() { return mySpecialField; } - @Nonnull @Override public DfType getSpecialFieldType() { return mySpecialFieldType; @@ -93,15 +87,13 @@ public DfType tryNegate() { null, BOTTOM, false); } - @Nonnull @Override public DfReferenceType dropNullability() { return this; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other instanceof DfGenericObjectType || other instanceof DfEphemeralReferenceType) { return other.join(this); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceType.java index 1019134861..b04e51d4cd 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfReferenceType.java @@ -3,8 +3,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.*; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes.BOTTOM; @@ -15,19 +14,16 @@ public interface DfReferenceType extends DfType { /** * @return nullability of this type */ - @Nonnull DfaNullability getNullability(); /** * @return type constraint of this type */ - @Nonnull TypeConstraint getConstraint(); /** * @return mutability of all the objects referred by this type */ - @Nonnull default Mutability getMutability() { return Mutability.UNKNOWN; } @@ -50,7 +46,6 @@ default SpecialField getSpecialField() { /** * @return type of special field; {@link DfTypes#BOTTOM} if {@link #getSpecialField()} returns null */ - @Nonnull default DfType getSpecialFieldType() { return BOTTOM; } @@ -58,7 +53,6 @@ default DfType getSpecialFieldType() { /** * @return this type without type constraint, or simply this type if it's a constant */ - @Nonnull default DfReferenceType dropTypeConstraint() { return this; } @@ -66,7 +60,6 @@ default DfReferenceType dropTypeConstraint() { /** * @return this type without locality flag, or simply this type if it's a constant */ - @Nonnull default DfReferenceType dropLocality() { return this; } @@ -74,13 +67,11 @@ default DfReferenceType dropLocality() { /** * @return this type without nullability knowledge, or simply this type if it's a constant */ - @Nonnull DfReferenceType dropNullability(); /** * @return this type without mutability knowledge, or simply this type if it's a constant */ - @Nonnull default DfReferenceType dropMutability() { return this; } @@ -105,8 +96,7 @@ static boolean isLocal(DfType type) { * @param type type to drop * @return this type dropping any relation to the supplied type */ - @Nonnull - default DfType withoutType(@Nonnull TypeConstraint type) { + default DfType withoutType(TypeConstraint type) { TypeConstraint constraint = getConstraint(); if (constraint.equals(type)) { return dropTypeConstraint(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfType.java index 11168bfc88..e4e04273ac 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfType.java @@ -1,8 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.java.analysis.impl.codeInspection.dataFlow.types; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents a domain of possible values within data flow analysis @@ -17,9 +16,9 @@ public interface DfType * @param other other type * @return true if this type is the supertype of other. */ - boolean isSuperType(@Nonnull DfType other); + boolean isSuperType(DfType other); - default boolean isMergeable(@Nonnull DfType other) + default boolean isMergeable(DfType other) { return isSuperType(other); } @@ -30,8 +29,7 @@ default boolean isMergeable(@Nonnull DfType other) * @param other type to join * @return the result of the join operation */ - @Nonnull - DfType join(@Nonnull DfType other); + DfType join(DfType other); /** * Returns the least specific type that contains all values that belong both to this type and to other type. @@ -39,8 +37,7 @@ default boolean isMergeable(@Nonnull DfType other) * @param other type to meet * @return the result of the meet operation. */ - @Nonnull - DfType meet(@Nonnull DfType other); + DfType meet(DfType other); /** * @return a type that contains all the values of the corresponding JVM type except the values of given type; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfTypes.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfTypes.java index ce368cda8f..9337e999eb 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfTypes.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/types/DfTypes.java @@ -9,8 +9,8 @@ import com.intellij.java.language.psi.PsiType; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.Objects; import java.util.Set; @@ -26,23 +26,20 @@ private DfTypes() { */ public static final DfType TOP = new DfType() { @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return true; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { return this; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { return other; } - @Nonnull @Override public DfType tryNegate() { return BOTTOM; @@ -64,23 +61,20 @@ public String toString() { */ public static final DfType BOTTOM = new DfType() { @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return other == this; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { return other; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { return this; } - @Nonnull @Override public DfType tryNegate() { return TOP; @@ -102,21 +96,18 @@ public String toString() { * with exception handling). This value is like a constant but it's type doesn't correspond to any JVM type. */ public static final DfType FAIL = new DfConstantType<>(ObjectUtil.sentinel("FAIL")) { - @Nonnull @Override public PsiType getPsiType() { return PsiType.VOID; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { return other == this ? this : TOP; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { return other == this ? this : BOTTOM; } @@ -131,26 +122,23 @@ public int hashCode() { */ public static final DfBooleanType BOOLEAN = new DfBooleanType() { @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return other == BOTTOM || other instanceof DfBooleanType; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other instanceof DfBooleanType) return this; return TOP; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { if (other == TOP) return this; if (other instanceof DfBooleanType) return other; return BOTTOM; } - @Nonnull @Override public DfType tryNegate() { return BOTTOM; @@ -196,7 +184,6 @@ public static DfBooleanConstantType booleanValue(boolean value) { * @param range range of values. Values that cannot be represented in JVM int type are removed from this range upon creation. * @return resulting type. Might be {@link #BOTTOM} if range is empty or all its values are out of the int domain. */ - @Nonnull public static DfType intRangeClamped(LongRangeSet range) { return intRange(range.intersect(DfIntRangeType.FULL_RANGE)); } @@ -208,7 +195,6 @@ public static DfType intRangeClamped(LongRangeSet range) { * @return resulting type. Might be {@link #BOTTOM} if range is empty. * @throws IllegalArgumentException if range contains values not representable in the JVM int type. */ - @Nonnull public static DfType intRange(LongRangeSet range) { if (range.equals(DfIntRangeType.FULL_RANGE)) return INT; if (range.isEmpty()) return BOTTOM; @@ -223,7 +209,6 @@ public static DfType intRange(LongRangeSet range) { * @param value int value * @return a int constant type that contains a given value */ - @Nonnull public static DfIntConstantType intValue(int value) { return new DfIntConstantType(value); } @@ -239,7 +224,6 @@ public static DfIntConstantType intValue(int value) { * @param range range of values. * @return resulting type. Might be {@link #BOTTOM} if range is empty. */ - @Nonnull public static DfType longRange(LongRangeSet range) { if (range.equals(LongRangeSet.all())) return LONG; if (range.isEmpty()) return BOTTOM; @@ -254,7 +238,6 @@ public static DfType longRange(LongRangeSet range) { * @param value long value * @return a long constant type that contains a given value */ - @Nonnull public static DfLongConstantType longValue(long value) { return new DfLongConstantType(value); } @@ -266,7 +249,6 @@ public static DfLongConstantType longValue(long value) { * @param isLong whether int or long type should be created * @return resulting type. */ - @Nonnull public static DfType rangeClamped(LongRangeSet range, boolean isLong) { return isLong ? longRange(range) : intRangeClamped(range); } @@ -276,26 +258,23 @@ public static DfType rangeClamped(LongRangeSet range, boolean isLong) { */ public static final DfFloatType FLOAT = new DfFloatType() { @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return other == BOTTOM || other instanceof DfFloatType; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other instanceof DfFloatType) return this; return TOP; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { if (other == TOP) return this; if (other instanceof DfFloatType) return other; return BOTTOM; } - @Nonnull @Override public DfType tryNegate() { return BOTTOM; @@ -325,26 +304,23 @@ public static DfFloatConstantType floatValue(float value) { */ public static final DfDoubleType DOUBLE = new DfDoubleType() { @Override - public boolean isSuperType(@Nonnull DfType other) { + public boolean isSuperType(DfType other) { return other == BOTTOM || other instanceof DfDoubleType; } - @Nonnull @Override - public DfType join(@Nonnull DfType other) { + public DfType join(DfType other) { if (other instanceof DfDoubleType) return this; return TOP; } - @Nonnull @Override - public DfType meet(@Nonnull DfType other) { + public DfType meet(DfType other) { if (other == TOP) return this; if (other instanceof DfDoubleType) return other; return BOTTOM; } - @Nonnull @Override public DfType tryNegate() { return BOTTOM; @@ -400,8 +376,7 @@ public static DfDoubleConstantType doubleValue(double value) { * @param type value type * @return a constant type that contains only given constant */ - @Nonnull - public static DfConstantType constant(@Nullable Object constant, @Nonnull PsiType type) { + public static DfConstantType constant(@Nullable Object constant, PsiType type) { if (constant == null) { return NULL; } @@ -430,7 +405,7 @@ public static DfConstantType constant(@Nullable Object constant, @Nonnull Psi * @param type PsiType to get default value of * @return a constant that represents a JVM default value of given type (0 for int, false for boolean, etc) */ - public static DfConstantType defaultValue(@Nonnull PsiType type) { + public static DfConstantType defaultValue(PsiType type) { if (type instanceof PsiPrimitiveType) { switch (type.getCanonicalText()) { case "boolean": @@ -456,8 +431,7 @@ public static DfConstantType defaultValue(@Nonnull PsiType type) { * @param nullability nullability * @return a type that references given objects of given type (or it subtypes) and has given nullability */ - @Nonnull - public static DfType typedObject(@Nullable PsiType type, @Nonnull Nullability nullability) { + public static DfType typedObject(@Nullable PsiType type, Nullability nullability) { if (type == null) return TOP; if (type instanceof PsiPrimitiveType) { if (type.equals(PsiType.VOID)) return TOP; @@ -496,11 +470,11 @@ public static DfType typedObject(@Nullable PsiType type, @Nonnull Nullability nu * @param sfType type of special field * @return a reference type object */ - public static DfReferenceType customObject(@Nonnull TypeConstraint constraint, - @Nonnull DfaNullability nullability, - @Nonnull Mutability mutability, + public static DfReferenceType customObject(TypeConstraint constraint, + DfaNullability nullability, + Mutability mutability, @Nullable SpecialField specialField, - @Nonnull DfType sfType) { + DfType sfType) { if (nullability == DfaNullability.NULL) { throw new IllegalArgumentException(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBinOpValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBinOpValue.java index 0bdfea5ba0..7b511b4870 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBinOpValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBinOpValue.java @@ -12,8 +12,7 @@ import consulo.language.ast.IElementType; import com.intellij.java.language.psi.util.TypeConversionUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.Map; @@ -24,17 +23,14 @@ public final class DfaBinOpValue extends DfaValue { private final - @Nonnull DfaVariableValue myLeft; private final - @Nonnull DfaValue myRight; private final boolean myLong; private final - @Nonnull LongRangeBinOp myOp; - private DfaBinOpValue(@Nonnull DfaVariableValue left, @Nonnull DfaValue right, boolean isLong, @Nonnull LongRangeBinOp op) + private DfaBinOpValue(DfaVariableValue left, DfaValue right, boolean isLong, LongRangeBinOp op) { super(left.getFactory()); switch(op) @@ -66,13 +62,11 @@ private DfaBinOpValue(@Nonnull DfaVariableValue left, @Nonnull DfaValue right, b myOp = op; } - @Nonnull public DfaVariableValue getLeft() { return myLeft; } - @Nonnull public DfaValue getRight() { return myRight; @@ -80,13 +74,11 @@ public DfaValue getRight() @Override public - @Nonnull PsiType getType() { return myLong ? PsiType.LONG : PsiType.INT; } - @Nonnull @Override public DfIntegralType getDfType() { @@ -100,7 +92,6 @@ public boolean dependsOn(DfaVariableValue other) } public - @Nonnull LongRangeBinOp getOperation() { return myOp; @@ -121,7 +112,6 @@ public String toString() return myLeft + delimiter + myRight; } - @Nonnull public DfaValue tryReduceOnCast(DfaMemoryState state, PsiPrimitiveType type) { if(!TypeConversionUtil.isIntegralNumberType(type)) @@ -300,7 +290,6 @@ else if(state.areEqual(right, sumValue.getRight())) return null; } - @Nonnull private DfaBinOpValue doCreate(DfaVariableValue left, DfaValue right, boolean isLong, LongRangeBinOp op) { long hash = ((isLong ? 1L : 0L) << 63) | ((long) left.getID() << 32) | right.getID(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBoxedValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBoxedValue.java index 7fa0287018..274ec1e2fc 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBoxedValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaBoxedValue.java @@ -8,33 +8,28 @@ import com.intellij.java.language.psi.PsiType; import consulo.util.collection.primitive.ints.IntMaps; import consulo.util.collection.primitive.ints.IntObjectMap; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public final class DfaBoxedValue extends DfaValue { private final - @Nonnull DfaVariableValue myWrappedValue; private final @Nullable PsiType myType; - private DfaBoxedValue(@Nonnull DfaVariableValue valueToWrap, @Nonnull DfaValueFactory factory, @Nullable PsiType type) + private DfaBoxedValue(DfaVariableValue valueToWrap, DfaValueFactory factory, @Nullable PsiType type) { super(factory); myWrappedValue = valueToWrap; myType = type; } - @NonNls public String toString() { return "Boxed " + myWrappedValue.toString(); } - @Nonnull public DfaVariableValue getWrappedValue() { return myWrappedValue; @@ -47,7 +42,6 @@ public PsiType getType() return myType; } - @Nonnull @Override public DfType getDfType() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaCondition.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaCondition.java index 9f9db8b69a..84596e9727 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaCondition.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaCondition.java @@ -3,10 +3,9 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaUtil; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.*; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; /** * Represents a condition to be applied to DFA memory state. @@ -23,7 +22,6 @@ public abstract class DfaCondition /** * @return a condition which is the opposite to this condition */ - @Nonnull @Contract(pure = true) public abstract DfaCondition negate(); @@ -57,8 +55,7 @@ public static DfaCondition getUnknown() /** * @see DfaValue#cond(RelationType, DfaValue) */ - @Nonnull - static DfaCondition createCondition(@Nonnull DfaValue left, @Nonnull RelationType relationType, @Nonnull DfaValue right) + static DfaCondition createCondition(DfaValue left, RelationType relationType, DfaValue right) { Exact value = Exact.tryEvaluate(left, relationType, right); if(value != null) @@ -88,7 +85,6 @@ public String toString() static final Exact FALSE = new Exact("FALSE"); static final Exact UNKNOWN = new Exact("UNKNOWN"); - @Nonnull @Override public DfaCondition negate() { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java index 68245c6e17..fa799b463d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -18,14 +18,14 @@ import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.LongStreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,542 +38,545 @@ */ public class DfaExpressionFactory { - private final DfaValueFactory myFactory; - private final Map myArrayIndices = new HashMap<>(); + private final DfaValueFactory myFactory; + private final Map myArrayIndices = new HashMap<>(); - DfaExpressionFactory(DfaValueFactory factory) { - myFactory = factory; - } - - @Nullable - @Contract("null -> null") - DfaValue getExpressionDfaValue(@Nullable PsiExpression expression) { - if (expression == null) { - return null; + DfaExpressionFactory(DfaValueFactory factory) { + myFactory = factory; } - if (expression instanceof PsiParenthesizedExpression) { - return getExpressionDfaValue(((PsiParenthesizedExpression) expression).getExpression()); - } + @Nullable + @Contract("null -> null") + @RequiredReadAction + DfaValue getExpressionDfaValue(@Nullable PsiExpression expression) { + if (expression == null) { + return null; + } - if (expression instanceof PsiArrayAccessExpression) { - PsiExpression arrayExpression = ((PsiArrayAccessExpression) expression).getArrayExpression(); - DfaValue qualifier = getQualifierValue(arrayExpression); - if (qualifier != null) { - Object index = ExpressionUtils.computeConstantExpression(((PsiArrayAccessExpression) expression).getIndexExpression()); - if (index instanceof Integer) { - DfaValue arrayElementValue = getArrayElementValue(qualifier, (Integer) index); - if (arrayElementValue != null) { - return arrayElementValue; - } - } - } - PsiType type = expression.getType(); - if (type != null) { - return myFactory.getObjectType(type, DfaPsiUtil.getElementNullability(type, null)); - } - } + if (expression instanceof PsiParenthesizedExpression parenthesized) { + return getExpressionDfaValue(parenthesized.getExpression()); + } - if (expression instanceof PsiMethodCallExpression) { - return createReferenceValue(((PsiMethodCallExpression) expression).getMethodExpression()); - } + if (expression instanceof PsiArrayAccessExpression arrayAccess) { + PsiExpression arrayExpression = arrayAccess.getArrayExpression(); + DfaValue qualifier = getQualifierValue(arrayExpression); + if (qualifier != null + && ExpressionUtils.computeConstantExpression(arrayAccess.getIndexExpression()) instanceof Integer index1) { + DfaValue arrayElementValue = getArrayElementValue(qualifier, index1); + if (arrayElementValue != null) { + return arrayElementValue; + } + } + PsiType type = expression.getType(); + if (type != null) { + return myFactory.getObjectType(type, DfaPsiUtil.getElementNullability(type, null)); + } + } - if (expression instanceof PsiReferenceExpression) { - return createReferenceValue((PsiReferenceExpression) expression); - } + if (expression instanceof PsiMethodCallExpression methodCall) { + return createReferenceValue(methodCall.getMethodExpression()); + } - if (expression instanceof PsiLiteralExpression) { - return myFactory.fromDfType(DfaPsiUtil.fromLiteral((PsiLiteralExpression) expression)); - } + if (expression instanceof PsiReferenceExpression refExpr) { + return createReferenceValue(refExpr); + } - if (expression instanceof PsiNewExpression || expression instanceof PsiLambdaExpression) { - return myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL); - } + if (expression instanceof PsiLiteralExpression literal) { + return myFactory.fromDfType(DfaPsiUtil.fromLiteral(literal)); + } - final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); - if (value != null) { - PsiType type = expression.getType(); - if (type != null) { - return myFactory.getConstant(value, type); - } - } + if (expression instanceof PsiNewExpression || expression instanceof PsiLambdaExpression) { + return myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL); + } - if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) { - PsiJavaCodeReferenceElement qualifier = ((PsiQualifiedExpression) expression).getQualifier(); - PsiClass target; - if (qualifier != null) { - target = ObjectUtil.tryCast(qualifier.resolve(), PsiClass.class); - } else { - target = ClassUtils.getContainingClass(expression); - } - return target == null - ? myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL) - : myFactory.getVarFactory().createThisValue(target); - } - return null; - } - - private DfaValue createReferenceValue(@Nonnull PsiReferenceExpression refExpr) { - PsiElement target = refExpr.resolve(); - if (target instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) target; - if (!PsiUtil.isAccessedForWriting(refExpr)) { - DfaValue constValue = myFactory.getConstantFromVariable(variable); - if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) { - return constValue; - } - } - } - VariableDescriptor var = getAccessedVariableOrGetter(target); - if (var == null) { - return null; - } + Object value = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); + if (value != null) { + PsiType type = expression.getType(); + if (type != null) { + return myFactory.getConstant(value, type); + } + } - DfaValue qualifier = getQualifierOrThisValue(refExpr); - DfaValue result = var.createValue(myFactory, qualifier, true); - if (var instanceof SpecialField) { - PsiType wantedType = refExpr.getType(); - result = DfaUtil.boxUnbox(result, wantedType); - } - return result; - } - - /** - * Returns a DFA variable which represents the qualifier for given reference if possible. For unqualified reference - * to a non-static member, a variable which represents the corresponding {@code this} may be returned - * - * @param refExpr reference to create a qualifier variable for - * @return a qualifier variable or null if qualifier is unnecessary or cannot be represented as a variable - */ - @Nullable - public DfaValue getQualifierOrThisValue(PsiReferenceExpression refExpr) { - PsiExpression qualifierExpression = refExpr.getQualifierExpression(); - if (qualifierExpression == null) { - PsiElement element = refExpr.resolve(); - if (element instanceof PsiMember && !((PsiMember) element).hasModifierProperty(PsiModifier.STATIC)) { - PsiClass currentClass; - currentClass = ClassUtils.getContainingClass(refExpr); - PsiClass memberClass = ((PsiMember) element).getContainingClass(); - if (memberClass != null && currentClass != null) { - PsiClass target; - if (currentClass == memberClass || InheritanceUtil.isInheritorOrSelf(currentClass, memberClass, true)) { - target = currentClass; - } else { - target = memberClass; - } - return myFactory.getVarFactory().createThisValue(target); - } - } - } - return getQualifierValue(qualifierExpression); - } - - @Nullable - private DfaValue getQualifierValue(PsiExpression qualifierExpression) { - DfaValue qualifierValue = getExpressionDfaValue(qualifierExpression); - if (qualifierValue == null) { - return null; - } - PsiVariable constVar = DfConstantType.getConstantOfType(qualifierValue.getDfType(), PsiVariable.class); - if (constVar != null) { - return myFactory.getVarFactory().createVariableValue(constVar); - } - return qualifierValue; - } - - private static boolean maybeUninitializedConstant(DfaValue constValue, - @Nonnull PsiReferenceExpression refExpr, - PsiModifierListOwner var) { - // If static final field is referred from the same or inner/nested class, - // we consider that it might be uninitialized yet as some class initializers may call its methods or - // even instantiate objects of this class and call their methods - if (!DfConstantType.isConst(constValue.getDfType(), var)) { - return false; - } - if (!(var instanceof PsiField) || var instanceof PsiEnumConstant) { - return false; - } - return PsiTreeUtil.getTopmostParentOfType(refExpr, PsiClass.class) == PsiTreeUtil.getTopmostParentOfType(var, PsiClass.class); - } - - @Contract("null -> null") - @Nullable - public static VariableDescriptor getAccessedVariableOrGetter(final PsiElement target) { - SpecialField sf = SpecialField.findSpecialField(target); - if (sf != null) { - return sf; - } - if (target instanceof PsiVariable) { - return new PlainDescriptor((PsiVariable) target); - } - if (target instanceof PsiMethod) { - PsiMethod method = (PsiMethod) target; - if (method.getParameterList().isEmpty() && - (PropertyUtilBase.isSimplePropertyGetter(method) || JavaMethodContractUtil.isPure(method) || isClassAnnotatedImmutable(method)) && - isContractAllowedForGetter(method)) { - return new GetterDescriptor(method); - } - } - return null; - } - - private static boolean isClassAnnotatedImmutable(PsiMethod method) { - List annotations = ConcurrencyAnnotationsManager.getInstance(method.getProject()).getImmutableAnnotations(); - return AnnotationUtil.findAnnotation(method.getContainingClass(), annotations) != null; - } - - private static boolean isContractAllowedForGetter(PsiMethod method) { - List contracts = JavaMethodContractUtil.getMethodCallContracts(method, null); - if (contracts.size() == 1) { - MethodContract contract = contracts.get(0); - return contract.isTrivial() && contract.getReturnValue().equals(ContractReturnValue.returnNew()); + if (expression instanceof PsiThisExpression || expression instanceof PsiSuperExpression) { + PsiJavaCodeReferenceElement qualifier = ((PsiQualifiedExpression)expression).getQualifier(); + PsiClass target; + if (qualifier != null) { + target = ObjectUtil.tryCast(qualifier.resolve(), PsiClass.class); + } + else { + target = ClassUtils.getContainingClass(expression); + } + return target == null + ? myFactory.getObjectType(expression.getType(), Nullability.NOT_NULL) + : myFactory.getVarFactory().createThisValue(target); + } + return null; } - return contracts.isEmpty(); - } - @Nonnull - private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression, @Nullable PsiType targetType) { - if (expression == null) { - return myFactory.getUnknown(); - } - DfaValue value = getExpressionDfaValue(expression); - if (value != null) { - return DfaUtil.boxUnbox(value, targetType); - } - if (expression instanceof PsiConditionalExpression) { - return getAdvancedExpressionDfaValue(((PsiConditionalExpression) expression).getThenExpression(), targetType).unite( - getAdvancedExpressionDfaValue(((PsiConditionalExpression) expression).getElseExpression(), targetType)); - } - PsiType type = expression.getType(); - if (expression instanceof PsiArrayInitializerExpression) { - int length = ((PsiArrayInitializerExpression) expression).getInitializers().length; - return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length), type)); - } - DfType dfType = DfTypes.typedObject(type, NullabilityUtil.getExpressionNullability(expression)); - if (type instanceof PsiPrimitiveType && targetType instanceof PsiPrimitiveType && !type.equals(targetType)) { - if (TypeConversionUtil.isIntegralNumberType(targetType)) { - LongRangeSet range = DfLongType.extractRange(dfType); - return myFactory.fromDfType(rangeClamped(range.castTo((PsiPrimitiveType) targetType), PsiType.LONG.equals(targetType))); - } - return myFactory.fromDfType(DfTypes.typedObject(targetType, Nullability.UNKNOWN)); - } - return DfaUtil.boxUnbox(myFactory.fromDfType(dfType), targetType); - } + @RequiredReadAction + private DfaValue createReferenceValue(PsiReferenceExpression refExpr) { + PsiElement target = refExpr.resolve(); + if (target instanceof PsiVariable variable && !PsiUtil.isAccessedForWriting(refExpr)) { + DfaValue constValue = myFactory.getConstantFromVariable(variable); + if (constValue != null && !maybeUninitializedConstant(constValue, refExpr, variable)) { + return constValue; + } + } + VariableDescriptor var = getAccessedVariableOrGetter(target); + if (var == null) { + return null; + } - @Nonnull - public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) { - if (!(array instanceof DfaVariableValue)) { - return myFactory.getUnknown(); + DfaValue qualifier = getQualifierOrThisValue(refExpr); + DfaValue result = var.createValue(myFactory, qualifier, true); + if (var instanceof SpecialField) { + PsiType wantedType = refExpr.getType(); + result = DfaUtil.boxUnbox(result, wantedType); + } + return result; } - if (indexSet.isEmpty()) { - return myFactory.getUnknown(); + + /** + * Returns a DFA variable which represents the qualifier for given reference if possible. For unqualified reference + * to a non-static member, a variable which represents the corresponding {@code this} may be returned + * + * @param refExpr reference to create a qualifier variable for + * @return a qualifier variable or null if qualifier is unnecessary or cannot be represented as a variable + */ + @Nullable + @RequiredReadAction + public DfaValue getQualifierOrThisValue(PsiReferenceExpression refExpr) { + PsiExpression qualifierExpression = refExpr.getQualifierExpression(); + if (qualifierExpression == null) { + PsiElement element = refExpr.resolve(); + if (element instanceof PsiMember member && !member.isStatic()) { + PsiClass currentClass; + currentClass = ClassUtils.getContainingClass(refExpr); + PsiClass memberClass = member.getContainingClass(); + if (memberClass != null && currentClass != null) { + PsiClass target; + if (currentClass == memberClass || InheritanceUtil.isInheritorOrSelf(currentClass, memberClass, true)) { + target = currentClass; + } + else { + target = memberClass; + } + return myFactory.getVarFactory().createThisValue(target); + } + } + } + return getQualifierValue(qualifierExpression); } - long min = indexSet.min(); - long max = indexSet.max(); - if (min == max && min >= 0 && min < Integer.MAX_VALUE) { - DfaValue value = getArrayElementValue(array, (int) min); - return value == null ? myFactory.getUnknown() : value; + + @Nullable + @RequiredReadAction + private DfaValue getQualifierValue(PsiExpression qualifierExpression) { + DfaValue qualifierValue = getExpressionDfaValue(qualifierExpression); + if (qualifierValue == null) { + return null; + } + PsiVariable constVar = DfConstantType.getConstantOfType(qualifierValue.getDfType(), PsiVariable.class); + if (constVar != null) { + return myFactory.getVarFactory().createVariableValue(constVar); + } + return qualifierValue; + } + + private static boolean maybeUninitializedConstant( + DfaValue constValue, + PsiReferenceExpression refExpr, + PsiModifierListOwner var + ) { + // If static final field is referred from the same or inner/nested class, + // we consider that it might be uninitialized yet as some class initializers may call its methods or + // even instantiate objects of this class and call their methods + if (!DfConstantType.isConst(constValue.getDfType(), var)) { + return false; + } + //noinspection SimplifiableIfStatement + if (!(var instanceof PsiField) || var instanceof PsiEnumConstant) { + return false; + } + return PsiTreeUtil.getTopmostParentOfType(refExpr, PsiClass.class) == PsiTreeUtil.getTopmostParentOfType(var, PsiClass.class); } - DfaVariableValue arrayDfaVar = (DfaVariableValue) array; - PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); - if (!(arrayPsiVar instanceof PsiVariable)) { - return myFactory.getUnknown(); + + @Contract("null -> null") + @Nullable + public static VariableDescriptor getAccessedVariableOrGetter(PsiElement target) { + SpecialField sf = SpecialField.findSpecialField(target); + if (sf != null) { + return sf; + } + if (target instanceof PsiVariable variable) { + return new PlainDescriptor(variable); + } + if (target instanceof PsiMethod method) { + if (method.getParameterList().isEmpty() + && ( + PropertyUtilBase.isSimplePropertyGetter(method) + || JavaMethodContractUtil.isPure(method) + || isClassAnnotatedImmutable(method) + ) + && isContractAllowedForGetter(method)) { + return new GetterDescriptor(method); + } + } + return null; } - PsiType arrayType = ((PsiVariable) arrayPsiVar).getType(); - PsiType targetType = arrayType instanceof PsiArrayType ? ((PsiArrayType) arrayType).getComponentType() : null; - PsiExpression[] elements = ExpressionUtils.getConstantArrayElements((PsiVariable) arrayPsiVar); - if (elements == null || elements.length == 0) { - return myFactory.getUnknown(); + + private static boolean isClassAnnotatedImmutable(PsiMethod method) { + List annotations = ConcurrencyAnnotationsManager.getInstance(method.getProject()).getImmutableAnnotations(); + return AnnotationUtil.findAnnotation(method.getContainingClass(), annotations) != null; } - indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1)); - if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) { - return myFactory.getUnknown(); + + private static boolean isContractAllowedForGetter(PsiMethod method) { + List contracts = JavaMethodContractUtil.getMethodCallContracts(method, null); + if (contracts.size() == 1) { + MethodContract contract = contracts.get(0); + return contract.isTrivial() && contract.getReturnValue().equals(ContractReturnValue.returnNew()); + } + return contracts.isEmpty(); } - return LongStreamEx.of(indexSet.stream()) - .mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int) idx], targetType)) - .prefix(DfaValue::unite) - .takeWhileInclusive(value -> !DfaTypeValue.isUnknown(value)) - .reduce((a, b) -> b) - .orElseGet(myFactory::getUnknown); - } - - @Contract("null, _ -> null") - @Nullable - public DfaValue getArrayElementValue(DfaValue array, int index) { - if (!(array instanceof DfaVariableValue)) { - return null; + + @RequiredReadAction + private DfaValue getAdvancedExpressionDfaValue(@Nullable PsiExpression expression, @Nullable PsiType targetType) { + if (expression == null) { + return myFactory.getUnknown(); + } + DfaValue value = getExpressionDfaValue(expression); + if (value != null) { + return DfaUtil.boxUnbox(value, targetType); + } + if (expression instanceof PsiConditionalExpression conditionalExpr) { + return getAdvancedExpressionDfaValue(conditionalExpr.getThenExpression(), targetType) + .unite(getAdvancedExpressionDfaValue(conditionalExpr.getElseExpression(), targetType)); + } + PsiType type = expression.getType(); + if (expression instanceof PsiArrayInitializerExpression arrayInitializer) { + int length = arrayInitializer.getInitializers().length; + return myFactory.fromDfType(SpecialField.ARRAY_LENGTH.asDfType(DfTypes.intValue(length), type)); + } + DfType dfType = DfTypes.typedObject(type, NullabilityUtil.getExpressionNullability(expression)); + if (type instanceof PsiPrimitiveType primitiveType + && targetType instanceof PsiPrimitiveType targetPrimitiveType + && !primitiveType.equals(targetPrimitiveType)) { + if (TypeConversionUtil.isIntegralNumberType(targetPrimitiveType)) { + LongRangeSet range = DfLongType.extractRange(dfType); + return myFactory.fromDfType(rangeClamped(range.castTo(targetPrimitiveType), PsiType.LONG.equals(targetPrimitiveType))); + } + return myFactory.fromDfType(DfTypes.typedObject(targetPrimitiveType, Nullability.UNKNOWN)); + } + return DfaUtil.boxUnbox(myFactory.fromDfType(dfType), targetType); } - DfaVariableValue arrayDfaVar = (DfaVariableValue) array; - PsiType type = arrayDfaVar.getType(); - if (!(type instanceof PsiArrayType)) { - return null; + + public DfaValue getArrayElementValue(DfaValue array, LongRangeSet indexSet) { + if (!(array instanceof DfaVariableValue arrayDfaVar)) { + return myFactory.getUnknown(); + } + if (indexSet.isEmpty()) { + return myFactory.getUnknown(); + } + long min = indexSet.min(); + long max = indexSet.max(); + if (min == max && min >= 0 && min < Integer.MAX_VALUE) { + DfaValue value = getArrayElementValue(array, (int)min); + return value == null ? myFactory.getUnknown() : value; + } + if (!(arrayDfaVar.getPsiVariable() instanceof PsiVariable arrayVar)) { + return myFactory.getUnknown(); + } + PsiType targetType = arrayVar.getType() instanceof PsiArrayType arrayType ? arrayType.getComponentType() : null; + PsiExpression[] elements = ExpressionUtils.getConstantArrayElements(arrayVar); + if (elements == null || elements.length == 0) { + return myFactory.getUnknown(); + } + indexSet = indexSet.intersect(LongRangeSet.range(0, elements.length - 1)); + if (indexSet.isEmpty() || indexSet.isCardinalityBigger(100)) { + return myFactory.getUnknown(); + } + return LongStreamEx.of(indexSet.stream()) + .mapToObj(idx -> getAdvancedExpressionDfaValue(elements[(int)idx], targetType)) + .prefix(DfaValue::unite) + .takeWhileInclusive(value -> !DfaTypeValue.isUnknown(value)) + .reduce((a, b) -> b) + .orElseGet(myFactory::getUnknown); } - PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); - if (arrayPsiVar instanceof PsiVariable) { - PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement((PsiVariable) arrayPsiVar, index); - if (constantArrayElement != null) { - return getAdvancedExpressionDfaValue(constantArrayElement, ((PsiArrayType) type).getComponentType()); - } + + @Contract("null, _ -> null") + @Nullable + @RequiredReadAction + public DfaValue getArrayElementValue(DfaValue array, int index) { + if (!(array instanceof DfaVariableValue arrayDfaVar) || !(arrayDfaVar.getType() instanceof PsiArrayType arrayType)) { + return null; + } + PsiModifierListOwner arrayPsiVar = arrayDfaVar.getPsiVariable(); + if (arrayPsiVar instanceof PsiVariable arrayVar) { + PsiExpression constantArrayElement = ExpressionUtils.getConstantArrayElement(arrayVar, index); + if (constantArrayElement != null) { + return getAdvancedExpressionDfaValue(constantArrayElement, arrayType.getComponentType()); + } + } + ArrayElementDescriptor indexVariable = getArrayIndexVariable(index); + if (indexVariable == null) { + return null; + } + return indexVariable.createValue(myFactory, arrayDfaVar); } - ArrayElementDescriptor indexVariable = getArrayIndexVariable(index); - if (indexVariable == null) { - return null; + + @Nullable + private ArrayElementDescriptor getArrayIndexVariable(int index) { + if (index >= 0) { + return myArrayIndices.computeIfAbsent(index, ArrayElementDescriptor::new); + } + return null; } - return indexVariable.createValue(myFactory, arrayDfaVar); - } - @Nullable - private ArrayElementDescriptor getArrayIndexVariable(int index) { - if (index >= 0) { - return myArrayIndices.computeIfAbsent(index, ArrayElementDescriptor::new); + private static PsiSubstitutor getSubstitutor(PsiElement member, @Nullable DfaVariableValue qualifier) { + if (member instanceof PsiMember psiMember && qualifier != null) { + PsiClass fieldClass = psiMember.getContainingClass(); + PsiClassType classType = ObjectUtil.tryCast(qualifier.getType(), PsiClassType.class); + if (classType != null && InheritanceUtil.isInheritorOrSelf(classType.resolve(), fieldClass, true)) { + return TypeConversionUtil.getSuperClassSubstitutor(fieldClass, classType); + } + } + return PsiSubstitutor.EMPTY; } - return null; - } - - @Nonnull - private static PsiSubstitutor getSubstitutor(PsiElement member, @Nullable DfaVariableValue qualifier) { - if (member instanceof PsiMember && qualifier != null) { - PsiClass fieldClass = ((PsiMember) member).getContainingClass(); - PsiClassType classType = ObjectUtil.tryCast(qualifier.getType(), PsiClassType.class); - if (classType != null && InheritanceUtil.isInheritorOrSelf(classType.resolve(), fieldClass, true)) { - return TypeConversionUtil.getSuperClassSubstitutor(fieldClass, classType); - } + + public DfaVariableValue getAssertionsDisabledVariable() { + return myFactory.getVarFactory().createVariableValue(AssertionDisabledDescriptor.INSTANCE); } - return PsiSubstitutor.EMPTY; - } - public DfaVariableValue getAssertionsDisabledVariable() { - return myFactory.getVarFactory().createVariableValue(AssertionDisabledDescriptor.INSTANCE); - } + public static final class AssertionDisabledDescriptor implements VariableDescriptor { + static final AssertionDisabledDescriptor INSTANCE = new AssertionDisabledDescriptor(); - public static final class AssertionDisabledDescriptor implements VariableDescriptor { - static final AssertionDisabledDescriptor INSTANCE = new AssertionDisabledDescriptor(); + private AssertionDisabledDescriptor() { + } - private AssertionDisabledDescriptor() { - } + @Override + public boolean isStable() { + return true; + } - @Override - public boolean isStable() { - return true; - } + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return PsiType.BOOLEAN; + } - @Nonnull - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return PsiType.BOOLEAN; + @Override + public String toString() { + return "$assertionsDisabled"; + } } - @Override - public String toString() { - return "$assertionsDisabled"; - } - } + static final class PlainDescriptor implements VariableDescriptor { + private final + PsiVariable myVariable; - static final class PlainDescriptor implements VariableDescriptor { - private final - @Nonnull - PsiVariable myVariable; + PlainDescriptor(PsiVariable variable) { + myVariable = variable; + } - PlainDescriptor(@Nonnull PsiVariable variable) { - myVariable = variable; - } + @Override + @RequiredReadAction + public String toString() { + return String.valueOf(myVariable.getName()); + } - @Nonnull - @Override - public String toString() { - return String.valueOf(myVariable.getName()); - } + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + PsiType type = myVariable.getType(); + if (type instanceof PsiEllipsisType ellipsisType) { + type = ellipsisType.toArrayType(); + } + return getSubstitutor(myVariable, qualifier).substitute(type); + } - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - PsiType type = myVariable.getType(); - if (type instanceof PsiEllipsisType) { - type = ((PsiEllipsisType) type).toArrayType(); - } - return getSubstitutor(myVariable, qualifier).substitute(type); - } + @Override + public PsiVariable getPsiElement() { + return myVariable; + } - @Override - public PsiVariable getPsiElement() { - return myVariable; - } + @Override + public boolean isStable() { + return PsiUtil.isJvmLocalVariable(myVariable) || myVariable.hasModifierProperty(PsiModifier.FINAL); + } - @Override - public boolean isStable() { - return PsiUtil.isJvmLocalVariable(myVariable) || myVariable.hasModifierProperty(PsiModifier.FINAL); - } + @Override + public DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (myVariable.hasModifierProperty(PsiModifier.VOLATILE)) { + PsiType type = getType(ObjectUtil.tryCast(qualifier, DfaVariableValue.class)); + return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable)); + } + if (PsiUtil.isJvmLocalVariable(myVariable) + || (myVariable instanceof PsiField field && field.isStatic() + && (!field.isFinal() || !DfaUtil.hasInitializationHacks(field)))) { + return factory.getVarFactory().createVariableValue(this); + } + return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); + } - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (myVariable.hasModifierProperty(PsiModifier.VOLATILE)) { - PsiType type = getType(ObjectUtil.tryCast(qualifier, DfaVariableValue.class)); - return factory.getObjectType(type, DfaPsiUtil.getElementNullability(type, myVariable)); - } - if (PsiUtil.isJvmLocalVariable(myVariable) || - (myVariable instanceof PsiField && myVariable.hasModifierProperty(PsiModifier.STATIC) && - (!myVariable.hasModifierProperty(PsiModifier.FINAL) || !DfaUtil.hasInitializationHacks((PsiField) myVariable)))) { - return factory.getVarFactory().createVariableValue(this); - } - return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); - } + @Override + @RequiredReadAction + public int hashCode() { + return Objects.hashCode(myVariable.getName()); + } - @Override - public int hashCode() { - return Objects.hashCode(myVariable.getName()); + @Override + public boolean equals(Object obj) { + return obj == this || obj instanceof PlainDescriptor plainDescriptor && plainDescriptor.myVariable == myVariable; + } } - @Override - public boolean equals(Object obj) { - return obj == this || obj instanceof PlainDescriptor && ((PlainDescriptor) obj).myVariable == myVariable; - } - } - - public static final class GetterDescriptor implements VariableDescriptor { - private static final CallMatcher STABLE_METHODS = CallMatcher.anyOf( - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0), - CallMatcher.instanceCall("java.lang.reflect.Member", "getName", "getModifiers", "getDeclaringClass", "isSynthetic"), - CallMatcher.instanceCall("java.lang.reflect.Executable", "getParameterCount", "isVarArgs"), - CallMatcher.instanceCall("java.lang.reflect.Field", "getType"), - CallMatcher.instanceCall("java.lang.reflect.Method", "getReturnType"), - CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_CLASS, "getName", "isInterface", "isArray", "isPrimitive", "isSynthetic", - "isAnonymousClass", "isLocalClass", "isMemberClass", "getDeclaringClass", "getEnclosingClass", - "getSimpleName", "getCanonicalName") - ); - private final - @Nonnull - PsiMethod myGetter; - private final boolean myStable; - - public GetterDescriptor(@Nonnull PsiMethod getter) { - myGetter = getter; - if (STABLE_METHODS.methodMatches(getter) || getter instanceof LightRecordMethod) { - myStable = true; - } else { - PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter); - myStable = field != null && field.hasModifierProperty(PsiModifier.FINAL); - } - } + public static final class GetterDescriptor implements VariableDescriptor { + private static final CallMatcher STABLE_METHODS = CallMatcher.anyOf( + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0), + CallMatcher.instanceCall("java.lang.reflect.Member", "getName", "getModifiers", "getDeclaringClass", "isSynthetic"), + CallMatcher.instanceCall("java.lang.reflect.Executable", "getParameterCount", "isVarArgs"), + CallMatcher.instanceCall("java.lang.reflect.Field", "getType"), + CallMatcher.instanceCall("java.lang.reflect.Method", "getReturnType"), + CallMatcher.instanceCall( + CommonClassNames.JAVA_LANG_CLASS, + "getName", + "isInterface", + "isArray", + "isPrimitive", + "isSynthetic", + "isAnonymousClass", + "isLocalClass", + "isMemberClass", + "getDeclaringClass", + "getEnclosingClass", + "getSimpleName", + "getCanonicalName" + ) + ); + private final + PsiMethod myGetter; + private final boolean myStable; + + public GetterDescriptor(PsiMethod getter) { + myGetter = getter; + if (STABLE_METHODS.methodMatches(getter) || getter instanceof LightRecordMethod) { + myStable = true; + } + else { + PsiField field = PsiUtil.canBeOverridden(getter) ? null : PropertyUtil.getFieldOfGetter(getter); + myStable = field != null && field.isFinal(); + } + } - @Nonnull - @Override - public String toString() { - return myGetter.getName(); - } + @Override + public String toString() { + return myGetter.getName(); + } - @Nullable - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return getSubstitutor(myGetter, qualifier).substitute(myGetter.getReturnType()); - } + @Nullable + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return getSubstitutor(myGetter, qualifier).substitute(myGetter.getReturnType()); + } - @Nonnull - @Override - public PsiMethod getPsiElement() { - return myGetter; - } + @Override + public PsiMethod getPsiElement() { + return myGetter; + } - @Override - public boolean isStable() { - return myStable; - } + @Override + public boolean isStable() { + return myStable; + } - @Override - public boolean isCall() { - return true; - } + @Override + public boolean isCall() { + return true; + } - @Nonnull - @Override - public DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { - if (myGetter.hasModifierProperty(PsiModifier.STATIC)) { - return factory.getVarFactory().createVariableValue(this); - } - return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); - } + @Override + public DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { + if (myGetter.isStatic()) { + return factory.getVarFactory().createVariableValue(this); + } + return VariableDescriptor.super.createValue(factory, qualifier, forAccessor); + } - @Override - public int hashCode() { - return Objects.hashCode(myGetter.getName()); - } + @Override + public int hashCode() { + return Objects.hashCode(myGetter.getName()); + } - @Override - public boolean equals(Object obj) { - return obj == this || (obj instanceof GetterDescriptor && ((GetterDescriptor) obj).myGetter == myGetter); + @Override + public boolean equals(Object obj) { + return obj == this || (obj instanceof GetterDescriptor getterDescr && getterDescr.myGetter == myGetter); + } } - } - public static final class ArrayElementDescriptor implements VariableDescriptor { - private final int myIndex; + public static final class ArrayElementDescriptor implements VariableDescriptor { + private final int myIndex; - ArrayElementDescriptor(int index) { - myIndex = index; - } + ArrayElementDescriptor(int index) { + myIndex = index; + } - public int getIndex() { - return myIndex; - } + public int getIndex() { + return myIndex; + } - @Nullable - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - if (qualifier == null) { - return null; - } - PsiType qualifierType = qualifier.getType(); - return qualifierType instanceof PsiArrayType ? ((PsiArrayType) qualifierType).getComponentType() : null; - } + @Nullable + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + if (qualifier == null) { + return null; + } + return qualifier.getType() instanceof PsiArrayType arrayType ? arrayType.getComponentType() : null; + } - @Nonnull - @Override - public String toString() { - return "[" + myIndex + "]"; - } + @Override + public String toString() { + return "[" + myIndex + "]"; + } - @Override - public boolean isStable() { - return false; + @Override + public boolean isStable() { + return false; + } } - } - public static final class ThisDescriptor implements VariableDescriptor { - @Nonnull - private final PsiClass myQualifier; + public static final class ThisDescriptor implements VariableDescriptor { + private final PsiClass myQualifier; - ThisDescriptor(@Nonnull PsiClass qualifier) { - myQualifier = qualifier; - } + ThisDescriptor(PsiClass qualifier) { + myQualifier = qualifier; + } - @Nonnull - @Override - public String toString() { - return myQualifier.getName() + ".this"; - } + @Override + @RequiredReadAction + public String toString() { + return myQualifier.getName() + ".this"; + } - @Nonnull - @Override - public PsiType getType(@Nullable DfaVariableValue qualifier) { - return new PsiImmediateClassType(myQualifier, PsiSubstitutor.EMPTY); - } + @Override + public PsiType getType(@Nullable DfaVariableValue qualifier) { + return new PsiImmediateClassType(myQualifier, PsiSubstitutor.EMPTY); + } - @Override - public PsiClass getPsiElement() { - return myQualifier; - } + @Override + public PsiClass getPsiElement() { + return myQualifier; + } - @Override - public boolean isStable() { - return true; - } + @Override + public boolean isStable() { + return true; + } - @Override - public int hashCode() { - return Objects.hashCode(myQualifier.getQualifiedName()); - } + @Override + public int hashCode() { + return Objects.hashCode(myQualifier.getQualifiedName()); + } - @Override - public boolean equals(Object obj) { - return this == obj || obj instanceof ThisDescriptor && ((ThisDescriptor) obj).myQualifier == myQualifier; + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof ThisDescriptor thisDescr && thisDescr.myQualifier == myQualifier; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaRelation.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaRelation.java index 002b127626..33ce75bb25 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaRelation.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaRelation.java @@ -19,15 +19,12 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaNullability; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfConstantType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; /** * A condition that represents a relation between two DfaValues */ public final class DfaRelation extends DfaCondition { - @Nonnull @Override public DfaRelation negate() { @@ -35,35 +32,30 @@ public DfaRelation negate() } private - @Nonnull final DfaValue myLeftOperand; private - @Nonnull final DfaValue myRightOperand; private - @Nonnull final RelationType myRelation; - private DfaRelation(@Nonnull DfaValue leftOperand, @Nonnull DfaValue rightOperand, @Nonnull RelationType relationType) + private DfaRelation(DfaValue leftOperand, DfaValue rightOperand, RelationType relationType) { myLeftOperand = leftOperand; myRightOperand = rightOperand; myRelation = relationType; } - @Nonnull public DfaValue getLeftOperand() { return myLeftOperand; } - @Nonnull public DfaValue getRightOperand() { return myRightOperand; } - public static DfaRelation createRelation(@Nonnull DfaValue dfaLeft, @Nonnull RelationType relationType, @Nonnull DfaValue dfaRight) + public static DfaRelation createRelation(DfaValue dfaLeft, RelationType relationType, DfaValue dfaRight) { if((relationType == RelationType.IS || relationType == RelationType.IS_NOT) && dfaRight instanceof DfaTypeValue && !(dfaLeft instanceof DfaTypeValue)) @@ -92,7 +84,6 @@ else if(dfaRight instanceof DfaTypeValue && dfaLeft.getDfType() instanceof DfCon return null; } - @Nonnull private static DfaRelation createConstBasedRelation(DfaTypeValue dfaLeft, RelationType relationType, DfaValue dfaRight) { if(dfaRight.getDfType() == DfTypes.NULL && DfaNullability.fromDfType(dfaLeft.getDfType()) == DfaNullability.NULLABLE) @@ -112,7 +103,6 @@ public boolean isNonEquality() return myRelation == RelationType.NE || myRelation == RelationType.GT || myRelation == RelationType.LT; } - @Nonnull public RelationType getRelation() { return myRelation; @@ -144,7 +134,6 @@ public int hashCode() return result; } - @NonNls public String toString() { return myLeftOperand + " " + myRelation + " " + myRightOperand; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaTypeValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaTypeValue.java index 09fdbc10df..1737c1a046 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaTypeValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaTypeValue.java @@ -8,8 +8,7 @@ import consulo.project.Project; import com.intellij.java.language.psi.PsiType; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.Map; @@ -17,16 +16,14 @@ public class DfaTypeValue extends DfaValue { private final - @Nonnull DfType myType; - DfaTypeValue(@Nonnull DfaValueFactory factory, @Nonnull DfType type) + DfaTypeValue(DfaValueFactory factory, DfType type) { super(factory); myType = type; } - @Nonnull @Override public DfType getDfType() { @@ -73,8 +70,7 @@ static class Factory myFactory = factory; } - @Nonnull - DfaTypeValue create(@Nonnull DfType type) + DfaTypeValue create(DfType type) { return myValues.computeIfAbsent(type, t -> new DfaTypeValue(myFactory, t)); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValue.java index 39eaf43eea..37db4bd7a8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValue.java @@ -19,22 +19,19 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfType; import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public abstract class DfaValue { private final int myID; - @Nonnull protected final DfaValueFactory myFactory; - protected DfaValue(@Nonnull final DfaValueFactory factory) + protected DfaValue(final DfaValueFactory factory) { myFactory = factory; myID = factory.registerValue(this); } - @Nonnull public DfaValueFactory getFactory() { return myFactory; @@ -57,7 +54,6 @@ public PsiType getType() /** * @return a DfType this value belongs under any possible memory state */ - @Nonnull public DfType getDfType() { return DfTypes.TOP; @@ -98,8 +94,7 @@ public final DfaCondition eq(DfaValue other) * @param other other condition operand * @return resulting condition between this value and other operand */ - @Nonnull - public final DfaCondition cond(@Nonnull RelationType relationType, @Nonnull DfaValue other) + public final DfaCondition cond(RelationType relationType, DfaValue other) { return DfaCondition.createCondition(this, relationType, other); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java index 5b800d9baa..19ac834eab 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaValueFactory.java @@ -14,6 +14,7 @@ import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpressionUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.CachedValueProvider; import consulo.language.pattern.ElementPattern; import consulo.language.psi.PsiCompiledElement; @@ -26,11 +27,9 @@ import consulo.util.collection.FList; import consulo.util.collection.FactoryMap; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import static com.intellij.java.language.patterns.PsiJavaPatterns.psiMember; @@ -38,369 +37,359 @@ import static consulo.language.pattern.StandardPatterns.or; public class DfaValueFactory { - private final - @Nonnull - List myValues = new ArrayList<>(); - private final boolean myUnknownMembersAreNullable; - private final - @Nonnull - FieldChecker myFieldChecker; - private final - @Nonnull - Project myProject; - private - @Nullable - DfaVariableValue myAssertionDisabled; - - /** - * @param project a project in which context the analysis is performed - * @param context an item to analyze (code-block, expression, class) - * @param unknownMembersAreNullable if true, unknown (non-annotated members) are assumed to be nullable - */ - public DfaValueFactory(@Nonnull Project project, @Nullable PsiElement context, boolean unknownMembersAreNullable) { - myProject = project; - myFieldChecker = new FieldChecker(context); - myUnknownMembersAreNullable = unknownMembersAreNullable; - myValues.add(null); - myVarFactory = new DfaVariableValue.Factory(this); - myBoxedFactory = new DfaBoxedValue.Factory(this); - myExpressionFactory = new DfaExpressionFactory(this); - myBinOpFactory = new DfaBinOpValue.Factory(this); - myTypeValueFactory = new DfaTypeValue.Factory(this); - } - - public boolean canTrustFieldInitializer(PsiField field) { - return myFieldChecker.canTrustFieldInitializer(field); - } - - private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = - or(psiMember(), psiParameter().withSuperParent(2, psiMember())); - - - @Nonnull - public Nullability suggestNullabilityForNonAnnotatedMember(@Nonnull PsiModifierListOwner member) { - if (myUnknownMembersAreNullable && - MEMBER_OR_METHOD_PARAMETER.accepts(member) && - AnnotationUtil.getSuperAnnotationOwners(member).isEmpty()) { - return Nullability.NULLABLE; + private final List myValues = new ArrayList<>(); + private final boolean myUnknownMembersAreNullable; + private final FieldChecker myFieldChecker; + private final Project myProject; + @Nullable + private DfaVariableValue myAssertionDisabled; + + /** + * @param project a project in which context the analysis is performed + * @param context an item to analyze (code-block, expression, class) + * @param unknownMembersAreNullable if true, unknown (non-annotated members) are assumed to be nullable + */ + public DfaValueFactory(Project project, @Nullable PsiElement context, boolean unknownMembersAreNullable) { + myProject = project; + myFieldChecker = new FieldChecker(context); + myUnknownMembersAreNullable = unknownMembersAreNullable; + myValues.add(null); + myVarFactory = new DfaVariableValue.Factory(this); + myBoxedFactory = new DfaBoxedValue.Factory(this); + myExpressionFactory = new DfaExpressionFactory(this); + myBinOpFactory = new DfaBinOpValue.Factory(this); + myTypeValueFactory = new DfaTypeValue.Factory(this); } - return Nullability.UNKNOWN; - } - - @Nonnull - public DfaTypeValue getObjectType(@Nullable PsiType type, @Nonnull Nullability nullability) { - return fromDfType(DfTypes.typedObject(type, nullability)); - } - - public - @Nullable - DfaVariableValue getAssertionDisabled() { - return myAssertionDisabled; - } - - void setAssertionDisabled(@Nonnull DfaVariableValue value) { - assert myAssertionDisabled == null; - myAssertionDisabled = value; - } - - int registerValue(DfaValue value) { - myValues.add(value); - return myValues.size() - 1; - } - - public DfaValue getValue(int id) { - return myValues.get(id); - } - - @Nullable - @Contract("null -> null") - public DfaValue createValue(PsiExpression psiExpression) { - return myExpressionFactory.getExpressionDfaValue(psiExpression); - } - - @Nonnull - public DfaTypeValue getInt(int value) { - return fromDfType(DfTypes.intValue(value)); - } - - @Nonnull - public DfaTypeValue getUnknown() { - return fromDfType(DfTypes.TOP); - } - - /** - * @return a special sentinel value that never equals to anything else (even unknown value) and - * sometimes pushed on the stack as control flow implementation detail. - * It's never assigned to the variable or merged with any other value. - */ - @Nonnull - public DfaValue getSentinel() { - return mySentinelValue; - } - - @Nonnull - public DfaTypeValue getBoolean(boolean value) { - return fromDfType(DfTypes.booleanValue(value)); - } - - /** - * @return a null value - */ - @Nonnull - public DfaTypeValue getNull() { - return fromDfType(DfTypes.NULL); - } - - /** - * Creates a constant of given type and given value. Constants are always unique - * (two different constants are not equal to each other). - *

- * The following types of the objects are supported: - *

    - *
  • Integer/Long/Double/Float/Boolean (will be unboxed)
  • - *
  • Character/Byte/Short (will be unboxed and widened to int)
  • - *
  • String
  • - *
  • {@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)
  • - *
  • {@link PsiField} (a final field that contains a unique value, type must be a type of that field)
  • - *
  • {@link PsiType} (java.lang.Class object value, type must be java.lang.Class)
  • - *
- * - * @param type type of the constant - * @return a DfaTypeValue whose type is DfConstantType that corresponds to given constant. - */ - public DfaTypeValue getConstant(Object value, @Nonnull PsiType type) { - return fromDfType(DfTypes.constant(value, type)); - } - - /** - * @param variable variable to create a constant based on its value - * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant - */ - @Nullable - public DfaValue getConstantFromVariable(PsiVariable variable) { - if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) { - return null; + public boolean canTrustFieldInitializer(PsiField field) { + return myFieldChecker.canTrustFieldInitializer(field); } - Object value = variable.computeConstantValue(); - PsiType type = variable.getType(); - if (value == null) { - Boolean boo = computeJavaLangBooleanFieldReference(variable); - if (boo != null) { - DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN); - return getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable)); - } - if (DfaUtil.isEmptyCollectionConstantField(variable)) { - return getConstant(variable, type); - } - PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); - initializer = PsiUtil.skipParenthesizedExprDown(initializer); - if (initializer instanceof PsiLiteralExpression && initializer.textMatches(PsiKeyword.NULL)) { - return getNull(); - } - if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC) && ExpressionUtils.isNewObject(initializer)) { - return getConstant(variable, type); - } - return null; + + private static final ElementPattern MEMBER_OR_METHOD_PARAMETER = + or(psiMember(), psiParameter().withSuperParent(2, psiMember())); + + + public Nullability suggestNullabilityForNonAnnotatedMember(PsiModifierListOwner member) { + if (myUnknownMembersAreNullable && + MEMBER_OR_METHOD_PARAMETER.accepts(member) && + AnnotationUtil.getSuperAnnotationOwners(member).isEmpty()) { + return Nullability.NULLABLE; + } + + return Nullability.UNKNOWN; } - return getConstant(value, type); - } - - @Nonnull - public Project getProject() { - return myProject; - } - - @Nullable - private static Boolean computeJavaLangBooleanFieldReference(final PsiVariable variable) { - if (!(variable instanceof PsiField)) { - return null; + + public DfaTypeValue getObjectType(@Nullable PsiType type, Nullability nullability) { + return fromDfType(DfTypes.typedObject(type, nullability)); } - PsiClass psiClass = ((PsiField) variable).getContainingClass(); - if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) { - return null; + + public + @Nullable + DfaVariableValue getAssertionDisabled() { + return myAssertionDisabled; } - @NonNls String name = variable.getName(); - return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; - } - - @Nonnull - public DfaTypeValue fromDfType(@Nonnull DfType dfType) { - return myTypeValueFactory.create(dfType); - } - - public Collection getValues() { - return Collections.unmodifiableCollection(myValues); - } - - @Nonnull - public DfaControlTransferValue controlTransfer(TransferTarget kind, FList traps) { - return myControlTransfers.get(Pair.create(kind, traps)); - } - - private final Map>, DfaControlTransferValue> myControlTransfers = - FactoryMap.create(p -> new DfaControlTransferValue(this, p.first, p.second)); - - private final DfaVariableValue.Factory myVarFactory; - private final DfaBoxedValue.Factory myBoxedFactory; - private final DfaBinOpValue.Factory myBinOpFactory; - private final DfaExpressionFactory myExpressionFactory; - private final DfaTypeValue.Factory myTypeValueFactory; - private final DfaValue mySentinelValue = new DfaValue(this) { - @Override - public String toString() { - return "SENTINEL"; + + void setAssertionDisabled(DfaVariableValue value) { + assert myAssertionDisabled == null; + myAssertionDisabled = value; + } + + int registerValue(DfaValue value) { + myValues.add(value); + return myValues.size() - 1; } - }; - - @Nonnull - public DfaVariableValue.Factory getVarFactory() { - return myVarFactory; - } - - @Nonnull - public DfaBoxedValue.Factory getBoxedFactory() { - return myBoxedFactory; - } - - @Nonnull - public DfaExpressionFactory getExpressionFactory() { - return myExpressionFactory; - } - - @Nonnull - public DfaBinOpValue.Factory getBinOpFactory() { - return myBinOpFactory; - } - - @Nonnull - public DfaValue createCommonValue(@Nonnull PsiExpression[] expressions, PsiType targetType) { - DfaValue loopElement = null; - for (PsiExpression expression : expressions) { - DfaValue expressionValue = createValue(expression); - if (expressionValue == null) { - expressionValue = getObjectType(expression.getType(), NullabilityUtil.getExpressionNullability(expression)); - } - loopElement = loopElement == null ? expressionValue : loopElement.unite(expressionValue); - if (DfaTypeValue.isUnknown(loopElement)) { - break; - } + + public DfaValue getValue(int id) { + return myValues.get(id); + } + + @Nullable + @Contract("null -> null") + @RequiredReadAction + public DfaValue createValue(PsiExpression psiExpression) { + return myExpressionFactory.getExpressionDfaValue(psiExpression); + } + + public DfaTypeValue getInt(int value) { + return fromDfType(DfTypes.intValue(value)); + } + + public DfaTypeValue getUnknown() { + return fromDfType(DfTypes.TOP); + } + + /** + * @return a special sentinel value that never equals to anything else (even unknown value) and + * sometimes pushed on the stack as control flow implementation detail. + * It's never assigned to the variable or merged with any other value. + */ + public DfaValue getSentinel() { + return mySentinelValue; + } + + public DfaTypeValue getBoolean(boolean value) { + return fromDfType(DfTypes.booleanValue(value)); + } + + /** + * @return a null value + */ + public DfaTypeValue getNull() { + return fromDfType(DfTypes.NULL); + } + + /** + * Creates a constant of given type and given value. Constants are always unique + * (two different constants are not equal to each other). + *

+ * The following types of the objects are supported: + *

    + *
  • Integer/Long/Double/Float/Boolean (will be unboxed)
  • + *
  • Character/Byte/Short (will be unboxed and widened to int)
  • + *
  • String
  • + *
  • {@link PsiEnumConstant} (enum constant value, type must be the corresponding enum type)
  • + *
  • {@link PsiField} (a final field that contains a unique value, type must be a type of that field)
  • + *
  • {@link PsiType} (java.lang.Class object value, type must be java.lang.Class)
  • + *
+ * + * @param type type of the constant + * @return a DfaTypeValue whose type is DfConstantType that corresponds to given constant. + */ + public DfaTypeValue getConstant(Object value, PsiType type) { + return fromDfType(DfTypes.constant(value, type)); } - return loopElement == null ? getUnknown() : DfaUtil.boxUnbox(loopElement, targetType); - } - - private static class ClassInitializationInfo { - private static final CallMatcher SAFE_CALLS = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); - - final boolean myCanInstantiateItself; - final boolean myCtorsCallMethods; - final boolean mySuperCtorsCallMethods; - - ClassInitializationInfo(@Nonnull PsiClass psiClass) { - // Indirect instantiation via other class is still possible, but hopefully unlikely - boolean canInstantiateItself = false; - for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiMember && ((PsiMember) child).hasModifierProperty(PsiModifier.STATIC) && - SyntaxTraverser.psiTraverser(child).filter(PsiNewExpression.class) - .filterMap(PsiNewExpression::getClassReference) - .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { - canInstantiateItself = true; - break; + + /** + * @param variable variable to create a constant based on its value + * @return a value that represents a constant created from variable; null if variable cannot be represented as a constant + */ + @Nullable + @RequiredReadAction + public DfaValue getConstantFromVariable(PsiVariable variable) { + if (!variable.hasModifierProperty(PsiModifier.FINAL) || DfaUtil.ignoreInitializer(variable)) { + return null; + } + Object value = variable.computeConstantValue(); + PsiType type = variable.getType(); + if (value == null) { + Boolean boo = computeJavaLangBooleanFieldReference(variable); + if (boo != null) { + DfaValue unboxed = getConstant(boo, PsiType.BOOLEAN); + return getBoxedFactory().createBoxed(unboxed, PsiType.BOOLEAN.getBoxedType(variable)); + } + if (DfaUtil.isEmptyCollectionConstantField(variable)) { + return getConstant(variable, type); + } + PsiExpression initializer = PsiFieldImpl.getDetachedInitializer(variable); + initializer = PsiUtil.skipParenthesizedExprDown(initializer); + if (initializer instanceof PsiLiteralExpression literal && literal.textMatches(PsiKeyword.NULL)) { + return getNull(); + } + if (variable instanceof PsiField field && field.isStatic() && ExpressionUtils.isNewObject(initializer)) { + return getConstant(variable, type); + } + return null; } - } - myCanInstantiateItself = canInstantiateItself; - mySuperCtorsCallMethods = - !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); - myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); + return getConstant(value, type); + } + + public Project getProject() { + return myProject; } - private static boolean canCallMethodsInConstructors(@Nonnull PsiClass aClass, boolean virtual) { - boolean inByteCode = false; - if (aClass instanceof PsiCompiledElement) { - inByteCode = true; - PsiElement navigationElement = aClass.getNavigationElement(); - if (navigationElement instanceof PsiClass) { - aClass = (PsiClass) navigationElement; + @Nullable + @RequiredReadAction + private static Boolean computeJavaLangBooleanFieldReference(PsiVariable variable) { + if (!(variable instanceof PsiField field)) { + return null; } - } - for (PsiMethod constructor : aClass.getConstructors()) { - if (inByteCode && JavaMethodContractUtil.isPure(constructor) && - !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { - // While pure constructor may call pure overridable method, our current implementation - // of bytecode inference will not infer the constructor purity in this case. - // So if we inferred a constructor purity from bytecode we can currently rely that - // no overridable methods are called there. - continue; + PsiClass psiClass = field.getContainingClass(); + if (psiClass == null || !CommonClassNames.JAVA_LANG_BOOLEAN.equals(psiClass.getQualifiedName())) { + return null; } - if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { - return true; + String name = variable.getName(); + return "TRUE".equals(name) ? Boolean.TRUE : "FALSE".equals(name) ? Boolean.FALSE : null; + } + + public DfaTypeValue fromDfType(DfType dfType) { + return myTypeValueFactory.create(dfType); + } + + public Collection getValues() { + return Collections.unmodifiableCollection(myValues); + } + + public DfaControlTransferValue controlTransfer(TransferTarget kind, FList traps) { + return myControlTransfers.get(Pair.create(kind, traps)); + } + + private final Map>, DfaControlTransferValue> myControlTransfers = + FactoryMap.create(p -> new DfaControlTransferValue(this, p.first, p.second)); + + private final DfaVariableValue.Factory myVarFactory; + private final DfaBoxedValue.Factory myBoxedFactory; + private final DfaBinOpValue.Factory myBinOpFactory; + private final DfaExpressionFactory myExpressionFactory; + private final DfaTypeValue.Factory myTypeValueFactory; + private final DfaValue mySentinelValue = new DfaValue(this) { + @Override + public String toString() { + return "SENTINEL"; } + }; - PsiCodeBlock body = constructor.getBody(); - if (body == null) { - continue; + public DfaVariableValue.Factory getVarFactory() { + return myVarFactory; + } + + public DfaBoxedValue.Factory getBoxedFactory() { + return myBoxedFactory; + } + + public DfaExpressionFactory getExpressionFactory() { + return myExpressionFactory; + } + + public DfaBinOpValue.Factory getBinOpFactory() { + return myBinOpFactory; + } + + @RequiredReadAction + public DfaValue createCommonValue(PsiExpression[] expressions, PsiType targetType) { + DfaValue loopElement = null; + for (PsiExpression expression : expressions) { + DfaValue expressionValue = createValue(expression); + if (expressionValue == null) { + expressionValue = getObjectType(expression.getType(), NullabilityUtil.getExpressionNullability(expression)); + } + loopElement = loopElement == null ? expressionValue : loopElement.unite(expressionValue); + if (DfaTypeValue.isUnknown(loopElement)) { + break; + } } + return loopElement == null ? getUnknown() : DfaUtil.boxUnbox(loopElement, targetType); + } - for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { - PsiReferenceExpression methodExpression = call.getMethodExpression(); - if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) { - continue; - } - if (SAFE_CALLS.test(call)) { - continue; - } - if (!virtual) { - return true; - } - - PsiMethod target = call.resolveMethod(); - if (target != null && PsiUtil.canBeOverridden(target)) { - return true; - } + private static class ClassInitializationInfo { + private static final CallMatcher SAFE_CALLS = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OBJECTS, "requireNonNull"); + + final boolean myCanInstantiateItself; + final boolean myCtorsCallMethods; + final boolean mySuperCtorsCallMethods; + + @RequiredReadAction + ClassInitializationInfo(PsiClass psiClass) { + // Indirect instantiation via other class is still possible, but hopefully unlikely + boolean canInstantiateItself = false; + for (PsiElement child = psiClass.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiMember member && member.isStatic() + && SyntaxTraverser.psiTraverser(child) + .filter(PsiNewExpression.class) + .filterMap(PsiNewExpression::getClassReference) + .find(classRef -> classRef.isReferenceTo(psiClass)) != null) { + canInstantiateItself = true; + break; + } + } + myCanInstantiateItself = canInstantiateItself; + mySuperCtorsCallMethods = + !InheritanceUtil.processSupers(psiClass, false, superClass -> !canCallMethodsInConstructors(superClass, true)); + myCtorsCallMethods = canCallMethodsInConstructors(psiClass, false); } - } - return false; - } - } - - private static class FieldChecker { - private final boolean myTrustDirectFieldInitializers; - private final boolean myTrustFieldInitializersInConstructors; - private final boolean myCanInstantiateItself; - private final PsiClass myClass; - - FieldChecker(PsiElement context) { - PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); - PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass ? (PsiClass) context : null; - myClass = contextClass; - if (method == null || myClass == null) { - myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; - return; - } - // Indirect instantiation via other class is still possible, but hopefully unlikely - ClassInitializationInfo info = LanguageCachedValueUtil.getCachedValue(contextClass, () -> CachedValueProvider.Result - .create(new ClassInitializationInfo(contextClass), PsiModificationTracker.MODIFICATION_COUNT)); - myCanInstantiateItself = info.myCanInstantiateItself; - if (method.hasModifierProperty(PsiModifier.STATIC) || method.isConstructor()) { - myTrustDirectFieldInitializers = true; - myTrustFieldInitializersInConstructors = false; - return; - } - myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; - myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; + @RequiredReadAction + private static boolean canCallMethodsInConstructors(PsiClass aClass, boolean virtual) { + boolean inByteCode = false; + if (aClass instanceof PsiCompiledElement) { + inByteCode = true; + if (aClass.getNavigationElement() instanceof PsiClass navigationClass) { + aClass = navigationClass; + } + } + for (PsiMethod constructor : aClass.getConstructors()) { + if (inByteCode && JavaMethodContractUtil.isPure(constructor) + && !JavaMethodContractUtil.hasExplicitContractAnnotation(constructor)) { + // While pure constructor may call pure overridable method, our current implementation + // of bytecode inference will not infer the constructor purity in this case. + // So if we inferred a constructor purity from bytecode we can currently rely that + // no overridable methods are called there. + continue; + } + if (!constructor.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { + return true; + } + + PsiCodeBlock body = constructor.getBody(); + if (body == null) { + continue; + } + + for (PsiMethodCallExpression call : SyntaxTraverser.psiTraverser().withRoot(body).filter(PsiMethodCallExpression.class)) { + PsiReferenceExpression methodExpression = call.getMethodExpression(); + if (methodExpression.textMatches(PsiKeyword.THIS) || methodExpression.textMatches(PsiKeyword.SUPER)) { + continue; + } + if (SAFE_CALLS.test(call)) { + continue; + } + if (!virtual) { + return true; + } + + PsiMethod target = call.resolveMethod(); + if (target != null && PsiUtil.canBeOverridden(target)) { + return true; + } + } + } + + return false; + } } - boolean canTrustFieldInitializer(PsiField field) { - if (field.hasInitializer()) { - boolean staticField = field.hasModifierProperty(PsiModifier.STATIC); - if (staticField && myClass != null && field.getContainingClass() != myClass) { - return true; + private static class FieldChecker { + private final boolean myTrustDirectFieldInitializers; + private final boolean myTrustFieldInitializersInConstructors; + private final boolean myCanInstantiateItself; + private final PsiClass myClass; + + FieldChecker(PsiElement context) { + PsiMethod method = context instanceof PsiClass ? null : PsiTreeUtil.getParentOfType(context, PsiMethod.class); + PsiClass contextClass = method != null ? method.getContainingClass() : context instanceof PsiClass psiClass ? psiClass : null; + myClass = contextClass; + if (method == null || myClass == null) { + myTrustDirectFieldInitializers = myTrustFieldInitializersInConstructors = myCanInstantiateItself = false; + return; + } + // Indirect instantiation via other class is still possible, but hopefully unlikely + ClassInitializationInfo info = LanguageCachedValueUtil.getCachedValue( + contextClass, + () -> CachedValueProvider.Result.create( + new ClassInitializationInfo(contextClass), + PsiModificationTracker.MODIFICATION_COUNT + ) + ); + myCanInstantiateItself = info.myCanInstantiateItself; + if (method.isStatic() || method.isConstructor()) { + myTrustDirectFieldInitializers = true; + myTrustFieldInitializersInConstructors = false; + return; + } + myTrustFieldInitializersInConstructors = !info.mySuperCtorsCallMethods && !info.myCtorsCallMethods; + myTrustDirectFieldInitializers = !info.mySuperCtorsCallMethods; + } + + boolean canTrustFieldInitializer(PsiField field) { + if (field.hasInitializer()) { + boolean staticField = field.isStatic(); + //noinspection SimplifiableIfStatement + if (staticField && myClass != null && field.getContainingClass() != myClass) { + return true; + } + return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); + } + return myTrustFieldInitializersInConstructors; } - return myTrustDirectFieldInitializers && (!myCanInstantiateItself || !staticField); - } - return myTrustFieldInitializersInConstructors; } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaVariableValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaVariableValue.java index 493d1dbf45..def0099ece 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/DfaVariableValue.java @@ -30,8 +30,7 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.util.collection.SmartList; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.HashMap; import java.util.List; @@ -50,7 +49,6 @@ public static class Factory myFactory = factory; } - @Nonnull public DfaVariableValue createVariableValue(PsiVariable variable) { DfaVariableValue qualifier = null; @@ -77,14 +75,12 @@ public DfaVariableValue createThisValue(@Nullable PsiClass aClass) return createVariableValue(new DfaExpressionFactory.ThisDescriptor(aClass)); } - @Nonnull - public DfaVariableValue createVariableValue(@Nonnull VariableDescriptor descriptor) + public DfaVariableValue createVariableValue(VariableDescriptor descriptor) { return createVariableValue(descriptor, null); } - @Nonnull - DfaVariableValue createVariableValue(@Nonnull VariableDescriptor descriptor, @Nullable DfaVariableValue qualifier) + DfaVariableValue createVariableValue(VariableDescriptor descriptor, @Nullable DfaVariableValue qualifier) { Pair key = Pair.create(descriptor, qualifier); DfaVariableValue var = myExistingVars.get(key); @@ -102,7 +98,6 @@ DfaVariableValue createVariableValue(@Nonnull VariableDescriptor descriptor, @Nu } } - @Nonnull private final VariableDescriptor myDescriptor; private final PsiType myVarType; @Nullable @@ -110,7 +105,7 @@ DfaVariableValue createVariableValue(@Nonnull VariableDescriptor descriptor, @Nu private DfType myInherentType; private final List myDependents = new SmartList<>(); - private DfaVariableValue(@Nonnull VariableDescriptor descriptor, @Nonnull DfaValueFactory factory, @Nullable DfaVariableValue qualifier) + private DfaVariableValue(VariableDescriptor descriptor, DfaValueFactory factory, @Nullable DfaVariableValue qualifier) { super(factory); myDescriptor = descriptor; @@ -128,7 +123,6 @@ public PsiModifierListOwner getPsiVariable() return myDescriptor.getPsiElement(); } - @Nonnull public VariableDescriptor getDescriptor() { return myDescriptor; @@ -150,7 +144,6 @@ public boolean dependsOn(DfaVariableValue other) /** * @return list of all variables created within the same factory which are directly or indirectly qualified by this variable. */ - @Nonnull public List getDependentVariables() { return myDependents; @@ -168,7 +161,6 @@ public int getDepth() return depth; } - @Nonnull @Contract(pure = true) public DfaVariableValue withQualifier(DfaVariableValue newQualifier) { @@ -227,7 +219,6 @@ private DfType calcInherentType() return dfType; } - @Nonnull public Nullability getInherentNullability() { return DfaNullability.toNullability(DfaNullability.fromDfType(getInherentType())); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/RelationType.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/RelationType.java index ea4cda973b..d8f65ce13c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/RelationType.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/RelationType.java @@ -3,8 +3,7 @@ import com.intellij.java.language.psi.JavaTokenType; import consulo.language.ast.IElementType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public enum RelationType { @@ -39,7 +38,7 @@ public enum RelationType * null if result is bottom */ @Nullable - public RelationType meet(@Nonnull RelationType other) + public RelationType meet(RelationType other) { if(isSubRelation(other)) { @@ -83,7 +82,6 @@ public boolean isSubRelation(RelationType other) } } - @Nonnull public RelationType getNegated() { switch(this) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/VariableDescriptor.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/VariableDescriptor.java index 6636202f40..20742b8ef6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/VariableDescriptor.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/dataFlow/value/VariableDescriptor.java @@ -8,8 +8,7 @@ import com.intellij.java.analysis.impl.codeInspection.dataFlow.types.DfTypes; import com.intellij.java.language.psi.PsiModifierListOwner; import com.intellij.java.language.psi.PsiType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents a descriptor of {@link DfaVariableValue}. Two variables are the same if they have the same descriptor and qualifier. @@ -49,8 +48,7 @@ default boolean isCall() * @param qualifier qualifier to use * @return a field value */ - @Nonnull - default DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier) + default DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier) { return createValue(factory, qualifier, false); } @@ -63,8 +61,7 @@ default DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValu * @param forAccessor whether the value is created for accessor result * @return a field value */ - @Nonnull - default DfaValue createValue(@Nonnull DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) + default DfaValue createValue(DfaValueFactory factory, @Nullable DfaValue qualifier, boolean forAccessor) { if(qualifier instanceof DfaVariableValue) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/RefUnreachableFilter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/RefUnreachableFilter.java index 3974278796..ad832eaa44 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/RefUnreachableFilter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/RefUnreachableFilter.java @@ -30,21 +30,18 @@ import consulo.language.editor.inspection.GlobalInspectionContext; import consulo.language.editor.inspection.GlobalInspectionTool; -import javax.annotation.Nonnull; public class RefUnreachableFilter extends RefFilter { - @Nonnull protected GlobalInspectionTool myTool; - @Nonnull protected final GlobalInspectionContext myContext; - public RefUnreachableFilter(@Nonnull GlobalInspectionTool tool, @Nonnull GlobalInspectionContext context) { + public RefUnreachableFilter(GlobalInspectionTool tool, GlobalInspectionContext context) { myTool = tool; myContext = context; } @Override - public int getElementProblemCount(@Nonnull RefJavaElement refElement) { + public int getElementProblemCount(RefJavaElement refElement) { if (refElement instanceof RefParameter) return 0; if (refElement.isSyntheticJSP()) return 0; if (!(refElement instanceof RefMethod || refElement instanceof RefClass || refElement instanceof RefField)) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnreferencedFilter.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnreferencedFilter.java index 89bea508f7..b6479ecc7b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnreferencedFilter.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnreferencedFilter.java @@ -29,15 +29,13 @@ import consulo.language.editor.inspection.GlobalInspectionContext; import consulo.language.editor.inspection.GlobalInspectionTool; -import javax.annotation.Nonnull; - public class UnreferencedFilter extends RefUnreachableFilter { - public UnreferencedFilter(@Nonnull GlobalInspectionTool tool, @Nonnull GlobalInspectionContext context) { + public UnreferencedFilter(GlobalInspectionTool tool, GlobalInspectionContext context) { super(tool, context); } @Override - public int getElementProblemCount(@Nonnull RefJavaElement refElement) { + public int getElementProblemCount(RefJavaElement refElement) { if (refElement instanceof RefParameter) return 0; if (refElement.isEntry() || !((RefElementImpl) refElement).isSuspicious() || refElement.isSyntheticJSP()) return 0; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionBase.java index 5c4107abb9..b953a77538 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionBase.java @@ -13,21 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: Oct 12, 2001 - * Time: 9:40:45 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ - package com.intellij.java.analysis.impl.codeInspection.deadCode; import com.intellij.java.analysis.codeInspection.GlobalJavaInspectionContext; -import com.intellij.java.analysis.codeInspection.GroupNames; -import com.intellij.java.analysis.codeInspection.ex.EntryPoint; +import com.intellij.java.analysis.codeInspection.ex.EntryPointProvider; +import com.intellij.java.analysis.codeInspection.ex.EntryPointState; import com.intellij.java.analysis.codeInspection.ex.EntryPointsManager; import com.intellij.java.analysis.codeInspection.reference.*; import com.intellij.java.analysis.impl.codeInspection.reference.RefClassImpl; @@ -40,18 +30,17 @@ import com.intellij.java.language.impl.psi.impl.PsiClassImplUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiMethodUtil; -import consulo.application.ApplicationManager; +import consulo.application.Application; import consulo.application.progress.ProgressManager; -import consulo.component.extension.Extensions; -import consulo.java.analysis.codeInspection.JavaExtensionPoints; -import consulo.java.language.module.util.JavaClassNames; +import consulo.java.deadCodeNotWorking.OldStyleInspection; import consulo.language.Language; import consulo.language.editor.ImplicitUsageProvider; import consulo.language.editor.impl.inspection.reference.RefElementImpl; import consulo.language.editor.inspection.GlobalInspectionContext; import consulo.language.editor.inspection.GlobalInspectionTool; -import consulo.language.editor.inspection.InspectionsBundle; +import consulo.language.editor.inspection.InspectionToolState; import consulo.language.editor.inspection.ProblemDescriptionsProcessor; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefEntity; import consulo.language.editor.inspection.reference.RefManager; @@ -68,61 +57,42 @@ import consulo.language.psi.search.PsiNonJavaFileReferenceProcessor; import consulo.language.psi.search.PsiSearchHelper; import consulo.language.psi.search.ReferencesSearch; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import consulo.util.collection.ContainerUtil; -import consulo.util.collection.Lists; -import consulo.util.xml.serializer.InvalidDataException; -import consulo.util.xml.serializer.WriteExternalException; import consulo.virtualFileSystem.VirtualFile; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.TestOnly; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; -public abstract class UnusedDeclarationInspectionBase extends GlobalInspectionTool { +/** + * @author max + * @since 2001-10-12 + */ +public abstract class UnusedDeclarationInspectionBase extends GlobalInspectionTool implements OldStyleInspection { + @Deprecated public boolean ADD_MAINS_TO_ENTRIES = true; - + @Deprecated public boolean ADD_APPLET_TO_ENTRIES = true; + @Deprecated public boolean ADD_SERVLET_TO_ENTRIES = true; + @Deprecated public boolean ADD_NONJAVA_TO_ENTRIES = true; private Set myProcessedSuspicious = null; private int myPhase; - public static final String DISPLAY_NAME = InspectionsBundle.message("inspection.dead.code.display.name"); - @NonNls public static final String SHORT_NAME = "unused"; - @NonNls public static final String ALTERNATIVE_ID = "UnusedDeclaration"; - protected final List myExtensions = Lists.newLockFreeCopyOnWriteList(); private static final Logger LOG = Logger.getInstance(UnusedDeclarationInspectionBase.class); private GlobalInspectionContext myContext; protected final UnusedSymbolLocalInspectionBase myLocalInspectionBase = createUnusedSymbolLocalInspection(); - private final boolean myEnabledInEditor; - - @TestOnly - public UnusedDeclarationInspectionBase(boolean enabledInEditor) { - List deadCodeAddins = new ArrayList<>(JavaExtensionPoints.DEAD_CODE_EP_NAME.getExtensionList()); - Collections.sort(deadCodeAddins, new Comparator() { - @Override - public int compare(final EntryPoint o1, final EntryPoint o2) { - return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName()); - } - }); - myExtensions.addAll(deadCodeAddins); - myEnabledInEditor = enabledInEditor; - } - - public UnusedDeclarationInspectionBase() { - this(!ApplicationManager.getApplication().isUnitTestMode()); + @Override + public InspectionToolState createStateProvider() { + return new UnusedDeclarationInspectionState(); } - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; @@ -142,36 +112,17 @@ protected GlobalInspectionContext getContext() { return myContext; } - private boolean isAddMainsEnabled() { - return ADD_MAINS_TO_ENTRIES; - } - - private boolean isAddAppletEnabled() { - return ADD_APPLET_TO_ENTRIES; - } - - private boolean isAddServletEnabled() { - return ADD_SERVLET_TO_ENTRIES; - } - - private boolean isAddNonJavaUsedEnabled() { - return ADD_NONJAVA_TO_ENTRIES; - } - @Override - @Nonnull - public String getDisplayName() { - return DISPLAY_NAME; + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionDeadCodeDisplayName(); } @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.DECLARATION_REDUNDANCY; + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesDeclarationRedundancy(); } @Override - @Nonnull public String getShortName() { return SHORT_NAME; } @@ -182,29 +133,7 @@ public Language getLanguage() { return JavaLanguage.INSTANCE; } - @Override - public void readSettings(@Nonnull Element node) throws InvalidDataException { - super.readSettings(node); - myLocalInspectionBase.readSettings(node); - for (EntryPoint extension : myExtensions) { - extension.readExternal(node); - } - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - myLocalInspectionBase.writeSettings(node); - writeUnusedDeclarationSettings(node); - } - - protected void writeUnusedDeclarationSettings(Element node) throws WriteExternalException { - super.writeSettings(node); - for (EntryPoint extension : myExtensions) { - extension.writeExternal(node); - } - } - - private static boolean isExternalizableNoParameterConstructor(@Nonnull PsiMethod method, RefClass refClass) { + private static boolean isExternalizableNoParameterConstructor(PsiMethod method, RefClass refClass) { if (!method.isConstructor()) { return false; } @@ -219,8 +148,8 @@ private static boolean isExternalizableNoParameterConstructor(@Nonnull PsiMethod return aClass == null || isExternalizable(aClass, refClass); } - private static boolean isSerializationImplicitlyUsedField(@Nonnull PsiField field) { - @NonNls final String name = field.getName(); + private static boolean isSerializationImplicitlyUsedField(PsiField field) { + final String name = field.getName(); if (!HighlightUtilBase.SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !"serialPersistentFields".equals(name)) { return false; } @@ -231,8 +160,8 @@ private static boolean isSerializationImplicitlyUsedField(@Nonnull PsiField fiel return aClass == null || isSerializable(aClass, null); } - private static boolean isWriteObjectMethod(@Nonnull PsiMethod method, RefClass refClass) { - @NonNls final String name = method.getName(); + private static boolean isWriteObjectMethod(PsiMethod method, RefClass refClass) { + final String name = method.getName(); if (!"writeObject".equals(name)) { return false; } @@ -250,8 +179,8 @@ private static boolean isWriteObjectMethod(@Nonnull PsiMethod method, RefClass r return !(aClass != null && !isSerializable(aClass, refClass)); } - private static boolean isReadObjectMethod(@Nonnull PsiMethod method, RefClass refClass) { - @NonNls final String name = method.getName(); + private static boolean isReadObjectMethod(PsiMethod method, RefClass refClass) { + final String name = method.getName(); if (!"readObject".equals(name)) { return false; } @@ -269,8 +198,8 @@ private static boolean isReadObjectMethod(@Nonnull PsiMethod method, RefClass re return !(aClass != null && !isSerializable(aClass, refClass)); } - private static boolean isWriteReplaceMethod(@Nonnull PsiMethod method, RefClass refClass) { - @NonNls final String name = method.getName(); + private static boolean isWriteReplaceMethod(PsiMethod method, RefClass refClass) { + final String name = method.getName(); if (!"writeReplace".equals(name)) { return false; } @@ -278,7 +207,7 @@ private static boolean isWriteReplaceMethod(@Nonnull PsiMethod method, RefClass if (parameters.length != 0) { return false; } - if (!method.getReturnType().equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) { + if (!method.getReturnType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { return false; } if (method.hasModifierProperty(PsiModifier.STATIC)) { @@ -288,8 +217,8 @@ private static boolean isWriteReplaceMethod(@Nonnull PsiMethod method, RefClass return !(aClass != null && !isSerializable(aClass, refClass)); } - private static boolean isReadResolveMethod(@Nonnull PsiMethod method, RefClass refClass) { - @NonNls final String name = method.getName(); + private static boolean isReadResolveMethod(PsiMethod method, RefClass refClass) { + final String name = method.getName(); if (!"readResolve".equals(name)) { return false; } @@ -297,7 +226,7 @@ private static boolean isReadResolveMethod(@Nonnull PsiMethod method, RefClass r if (parameters.length != 0) { return false; } - if (!method.getReturnType().equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) { + if (!method.getReturnType().equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { return false; } if (method.hasModifierProperty(PsiModifier.STATIC)) { @@ -309,14 +238,15 @@ private static boolean isReadResolveMethod(@Nonnull PsiMethod method, RefClass r private static boolean isSerializable(PsiClass aClass, @Nullable RefClass refClass) { final PsiClass serializableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.io" + - ".Serializable", aClass.getResolveScope()); + ".Serializable", + aClass.getResolveScope()); return serializableClass != null && isSerializable(aClass, refClass, serializableClass); } - private static boolean isExternalizable(@Nonnull PsiClass aClass, RefClass refClass) { + private static boolean isExternalizable(PsiClass aClass, RefClass refClass) { final GlobalSearchScope scope = aClass.getResolveScope(); final PsiClass externalizableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.io" + - ".Externalizable", scope); + ".Externalizable", scope); return externalizableClass != null && isSerializable(aClass, refClass, externalizableClass); } @@ -339,15 +269,20 @@ private static boolean isSerializable(PsiClass aClass, RefClass refClass, PsiCla } @Override - public void runInspection(@Nonnull final AnalysisScope scope, - @Nonnull InspectionManager manager, - @Nonnull final GlobalInspectionContext globalContext, - @Nonnull ProblemDescriptionsProcessor problemDescriptionsProcessor) { + public void runInspection( + final AnalysisScope scope, + InspectionManager manager, + final GlobalInspectionContext globalContext, + ProblemDescriptionsProcessor problemDescriptionsProcessor, + Object state + ) { + UnusedDeclarationInspectionState inspectionState = (UnusedDeclarationInspectionState)state; + globalContext.getRefManager().iterate(new RefJavaVisitor() { @Override - public void visitElement(@Nonnull final RefEntity refEntity) { + public void visitElement(final RefEntity refEntity) { if (refEntity instanceof RefJavaElement) { - final RefElementImpl refElement = (RefElementImpl) refEntity; + final RefElementImpl refElement = (RefElementImpl)refEntity; if (!refElement.isSuspicious()) { return; } @@ -367,16 +302,15 @@ public void visitElement(@Nonnull final RefEntity refEntity) { refElement.accept(new RefJavaVisitor() { @Override - public void visitMethod(@Nonnull RefMethod method) { - if (isAddMainsEnabled() && method.isAppMain()) { + public void visitMethod(RefMethod method) { + if (inspectionState.isAddMainsEnabled() && method.isAppMain()) { getEntryPointsManager().addEntryPoint(method, false); } } @Override - public void visitClass(@Nonnull RefClass aClass) { - if (isAddAppletEnabled() && aClass.isApplet() || isAddServletEnabled() && aClass - .isServlet()) { + public void visitClass(RefClass aClass) { + if (inspectionState.isAddAppletEnabled() && aClass.isApplet() || inspectionState.isAddServletEnabled() && aClass.isServlet()) { getEntryPointsManager().addEntryPoint(aClass, false); } } @@ -385,22 +319,23 @@ public void visitClass(@Nonnull RefClass aClass) { } }); - if (isAddNonJavaUsedEnabled()) { + if (inspectionState.isAddNonJavaUsedEnabled()) { checkForReachables(globalContext); final StrictUnreferencedFilter strictUnreferencedFilter = new StrictUnreferencedFilter(this, - globalContext); + globalContext); ProgressManager.getInstance().runProcess(new Runnable() { @Override public void run() { final RefManager refManager = globalContext.getRefManager(); - final PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(refManager.getProject()); + final PsiSearchHelper helper = PsiSearchHelper.getInstance(refManager.getProject()); refManager.iterate(new RefJavaVisitor() { @Override - public void visitElement(@Nonnull final RefEntity refEntity) { - if (refEntity instanceof RefClass && strictUnreferencedFilter.accepts((RefClass) refEntity)) { - findExternalClassReferences((RefClass) refEntity); - } else if (refEntity instanceof RefMethod) { - RefMethod refMethod = (RefMethod) refEntity; + public void visitElement(final RefEntity refEntity) { + if (refEntity instanceof RefClass && strictUnreferencedFilter.accepts((RefClass)refEntity)) { + findExternalClassReferences((RefClass)refEntity); + } + else if (refEntity instanceof RefMethod) { + RefMethod refMethod = (RefMethod)refEntity; if (refMethod.isConstructor() && strictUnreferencedFilter.accepts(refMethod)) { findExternalClassReferences(refMethod.getOwnerClass()); } @@ -412,26 +347,22 @@ private void findExternalClassReferences(final RefClass refElement) { String qualifiedName = psiClass != null ? psiClass.getQualifiedName() : null; if (qualifiedName != null) { final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(globalContext - .getProject()); - final PsiNonJavaFileReferenceProcessor processor = new - PsiNonJavaFileReferenceProcessor() { - @Override - public boolean process(PsiFile file, int startOffset, int endOffset) { - getEntryPointsManager().addEntryPoint(refElement, false); - return false; - } - }; + .getProject()); + final PsiNonJavaFileReferenceProcessor processor = (file, startOffset, endOffset) -> { + getEntryPointsManager().addEntryPoint(refElement, false); + return false; + }; final DelegatingGlobalSearchScope globalSearchScope = new DelegatingGlobalSearchScope - (projectScope) { + (projectScope) { @Override - public boolean contains(@Nonnull VirtualFile file) { + public boolean contains(VirtualFile file) { return file.getFileType() != JavaFileType.INSTANCE && super.contains(file); } }; if (helper.processUsagesInNonJavaFiles(qualifiedName, processor, globalSearchScope)) { final PsiReference reference = ReferencesSearch.search(psiClass, - globalSearchScope).findFirst(); + globalSearchScope).findFirst(); if (reference != null) { getEntryPointsManager().addEntryPoint(refElement, false); for (PsiMethod method : psiClass.getMethods()) { @@ -449,12 +380,13 @@ public boolean contains(@Nonnull VirtualFile file) { }, null); } - myProcessedSuspicious = new HashSet(); + myProcessedSuspicious = new HashSet<>(); myPhase = 1; } - public boolean isEntryPoint(@Nonnull RefElement owner) { - final PsiElement element = owner.getElement(); + @SuppressWarnings("unchecked") + public boolean isEntryPoint(RefElement owner, UnusedDeclarationInspectionState state) { + final PsiElement element = owner.getPsiElement(); if (RefUtil.isImplicitUsage(element)) { return true; } @@ -465,8 +397,10 @@ public boolean isEntryPoint(@Nonnull RefElement owner) { } } if (element != null) { - for (EntryPoint extension : myExtensions) { - if (extension.isEntryPoint(owner, element)) { + for (EntryPointProvider extension : Application.get().getExtensionList(EntryPointProvider.class)) { + EntryPointState pointState = state.getEntryPointState(extension); + + if (extension.isEntryPoint(owner, element, pointState)) { return true; } } @@ -474,25 +408,26 @@ public boolean isEntryPoint(@Nonnull RefElement owner) { return false; } - public boolean isEntryPoint(@Nonnull PsiElement element) { + @SuppressWarnings("unchecked") + public boolean isEntryPoint(PsiElement element, UnusedDeclarationInspectionState state) { final Project project = element.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - if (element instanceof PsiMethod && isAddMainsEnabled() && PsiClassImplUtil.isMainOrPremainMethod((PsiMethod) - element)) { + if (element instanceof PsiMethod && state.isAddMainsEnabled() && PsiClassImplUtil.isMainOrPremainMethod((PsiMethod) + element)) { return true; } if (element instanceof PsiClass) { - PsiClass aClass = (PsiClass) element; + PsiClass aClass = (PsiClass)element; final PsiClass applet = psiFacade.findClass("java.applet.Applet", GlobalSearchScope.allScope(project)); - if (isAddAppletEnabled() && applet != null && aClass.isInheritor(applet, true)) { + if (state.isAddAppletEnabled() && applet != null && aClass.isInheritor(applet, true)) { return true; } final PsiClass servlet = psiFacade.findClass("javax.servlet.Servlet", GlobalSearchScope.allScope(project)); - if (isAddServletEnabled() && servlet != null && aClass.isInheritor(servlet, true)) { + if (state.isAddServletEnabled() && servlet != null && aClass.isInheritor(servlet, true)) { return true; } - if (isAddMainsEnabled() && PsiMethodUtil.hasMainMethod(aClass)) { + if (state.isAddMainsEnabled() && PsiMethodUtil.hasMainMethod(aClass)) { return true; } } @@ -502,32 +437,30 @@ public boolean isEntryPoint(@Nonnull PsiElement element) { return true; } } - for (EntryPoint extension : myExtensions) { - if (extension.isEntryPoint(element)) { - return true; - } - } - final ImplicitUsageProvider[] implicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME); - for (ImplicitUsageProvider provider : implicitUsageProviders) { - if (provider.isImplicitUsage(element)) { + + for (EntryPointProvider extension : Application.get().getExtensionList(EntryPointProvider.class)) { + EntryPointState pointState = state.getEntryPointState(extension); + + if (extension.isEntryPoint(element, pointState)) { return true; } } - return false; + + return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(it -> it.isImplicitUsage(element)) != null; } public boolean isGlobalEnabledInEditor() { - return myEnabledInEditor; + return true; } private static class StrictUnreferencedFilter extends UnreferencedFilter { - private StrictUnreferencedFilter(@Nonnull UnusedDeclarationInspectionBase tool, - @Nonnull GlobalInspectionContext context) { + private StrictUnreferencedFilter(UnusedDeclarationInspectionBase tool, + GlobalInspectionContext context) { super(tool, context); } @Override - public int getElementProblemCount(@Nonnull RefJavaElement refElement) { + public int getElementProblemCount(RefJavaElement refElement) { final int problemCount = super.getElementProblemCount(refElement); if (problemCount > -1) { return problemCount; @@ -537,57 +470,57 @@ public int getElementProblemCount(@Nonnull RefJavaElement refElement) { } @Override - public boolean queryExternalUsagesRequests(@Nonnull InspectionManager manager, - @Nonnull GlobalInspectionContext globalContext, - @Nonnull ProblemDescriptionsProcessor problemDescriptionsProcessor) { + public boolean queryExternalUsagesRequests(InspectionManager manager, + GlobalInspectionContext globalContext, + ProblemDescriptionsProcessor problemDescriptionsProcessor, + Object state) { checkForReachables(globalContext); final RefFilter filter = myPhase == 1 ? new StrictUnreferencedFilter(this, - globalContext) : new RefUnreachableFilter(this, globalContext); + globalContext) : new RefUnreachableFilter(this, globalContext); final boolean[] requestAdded = {false}; globalContext.getRefManager().iterate(new RefJavaVisitor() { @Override - public void visitElement(@Nonnull RefEntity refEntity) { + public void visitElement(RefEntity refEntity) { if (!(refEntity instanceof RefJavaElement)) { return; } - if (refEntity instanceof RefClass && ((RefClass) refEntity).isAnonymous()) { + if (refEntity instanceof RefClass && ((RefClass)refEntity).isAnonymous()) { return; } - RefJavaElement refElement = (RefJavaElement) refEntity; + RefJavaElement refElement = (RefJavaElement)refEntity; if (filter.accepts(refElement) && !myProcessedSuspicious.contains(refElement)) { refEntity.accept(new RefJavaVisitor() { @Override - public void visitField(@Nonnull final RefField refField) { + public void visitField(final RefField refField) { myProcessedSuspicious.add(refField); PsiField psiField = refField.getElement(); if (psiField != null && isSerializationImplicitlyUsedField(psiField)) { getEntryPointsManager().addEntryPoint(refField, false); - } else { - getJavaContext().enqueueFieldUsagesProcessor(refField, new GlobalJavaInspectionContext - .UsagesProcessor() { - @Override - public boolean process(PsiReference psiReference) { - getEntryPointsManager().addEntryPoint(refField, false); - return false; - } + } + else { + getJavaContext().enqueueFieldUsagesProcessor(refField, psiReference -> { + getEntryPointsManager().addEntryPoint(refField, false); + return false; }); requestAdded[0] = true; } } @Override - public void visitMethod(@Nonnull final RefMethod refMethod) { + public void visitMethod(final RefMethod refMethod) { myProcessedSuspicious.add(refMethod); if (refMethod instanceof RefImplicitConstructor) { visitClass(refMethod.getOwnerClass()); - } else { - PsiMethod psiMethod = (PsiMethod) refMethod.getElement(); + } + else { + PsiMethod psiMethod = (PsiMethod)refMethod.getElement(); if (psiMethod != null && isSerializablePatternMethod(psiMethod, - refMethod.getOwnerClass())) { + refMethod.getOwnerClass())) { getEntryPointsManager().addEntryPoint(refMethod, false); - } else if (!refMethod.isExternalOverride() && !PsiModifier.PRIVATE.equals(refMethod - .getAccessModifier())) { + } + else if (!refMethod.isExternalOverride() && !PsiModifier.PRIVATE.equals(refMethod + .getAccessModifier())) { for (final RefMethod derivedMethod : refMethod.getDerivedMethods()) { myProcessedSuspicious.add(derivedMethod); } @@ -599,26 +532,24 @@ public void visitMethod(@Nonnull final RefMethod refMethod) { } @Override - public void visitClass(@Nonnull final RefClass refClass) { + public void visitClass(final RefClass refClass) { myProcessedSuspicious.add(refClass); if (!refClass.isAnonymous()) { - getJavaContext().enqueueDerivedClassesProcessor(refClass, - new GlobalJavaInspectionContext.DerivedClassesProcessor() { - @Override - public boolean process(PsiClass inheritor) { - getEntryPointsManager().addEntryPoint(refClass, false); - return false; - } - }); + getJavaContext().enqueueDerivedClassesProcessor( + refClass, + inheritor -> { + getEntryPointsManager().addEntryPoint(refClass, false); + return false; + } + ); - getJavaContext().enqueueClassUsagesProcessor(refClass, new GlobalJavaInspectionContext - .UsagesProcessor() { - @Override - public boolean process(PsiReference psiReference) { + getJavaContext().enqueueClassUsagesProcessor( + refClass, + psiReference -> { getEntryPointsManager().addEntryPoint(refClass, false); return false; } - }); + ); requestAdded[0] = true; } } @@ -631,7 +562,8 @@ public boolean process(PsiReference psiReference) { if (myPhase == 2) { myProcessedSuspicious = null; return false; - } else { + } + else { myPhase = 2; } } @@ -639,23 +571,21 @@ public boolean process(PsiReference psiReference) { return true; } - private static boolean isSerializablePatternMethod(@Nonnull PsiMethod psiMethod, RefClass refClass) { + private static boolean isSerializablePatternMethod(PsiMethod psiMethod, RefClass refClass) { return isReadObjectMethod(psiMethod, refClass) || isWriteObjectMethod(psiMethod, - refClass) || isReadResolveMethod(psiMethod, refClass) || - isWriteReplaceMethod(psiMethod, refClass) || isExternalizableNoParameterConstructor(psiMethod, - refClass); + refClass) || isReadResolveMethod(psiMethod, refClass) || + isWriteReplaceMethod(psiMethod, refClass) || isExternalizableNoParameterConstructor(psiMethod, + refClass); } private void enqueueMethodUsages(final RefMethod refMethod) { if (refMethod.getSuperMethods().isEmpty()) { - getJavaContext().enqueueMethodUsagesProcessor(refMethod, new GlobalJavaInspectionContext.UsagesProcessor() { - @Override - public boolean process(PsiReference psiReference) { - getEntryPointsManager().addEntryPoint(refMethod, false); - return false; - } + getJavaContext().enqueueMethodUsagesProcessor(refMethod, psiReference -> { + getEntryPointsManager().addEntryPoint(refMethod, false); + return false; }); - } else { + } + else { for (RefMethod refSuper : refMethod.getSuperMethods()) { enqueueMethodUsages(refSuper); } @@ -670,20 +600,20 @@ private GlobalJavaInspectionContext getJavaContext() { @Override public JobDescriptor[] getAdditionalJobs() { return new JobDescriptor[]{ - getContext().getStdJobDescriptors().BUILD_GRAPH, - getContext().getStdJobDescriptors().FIND_EXTERNAL_USAGES + getContext().getStdJobDescriptors().BUILD_GRAPH, + getContext().getStdJobDescriptors().FIND_EXTERNAL_USAGES }; } - public void checkForReachables(@Nonnull final GlobalInspectionContext context) { + public void checkForReachables(final GlobalInspectionContext context) { CodeScanner codeScanner = new CodeScanner(); // Cleanup previous reachability information. context.getRefManager().iterate(new RefJavaVisitor() { @Override - public void visitElement(@Nonnull RefEntity refEntity) { + public void visitElement(RefEntity refEntity) { if (refEntity instanceof RefJavaElement) { - final RefJavaElementImpl refElement = (RefJavaElementImpl) refEntity; + final RefJavaElementImpl refElement = (RefJavaElementImpl)refEntity; if (!context.isToCheckMember(refElement, UnusedDeclarationInspectionBase.this)) { return; } @@ -708,29 +638,32 @@ private EntryPointsManager getEntryPointsManager() { } private static class CodeScanner extends RefJavaVisitor { - private final Map> myClassIDtoMethods = new HashMap>(); - private final Set myInstantiatedClasses = new HashSet(); + private final Map> myClassIDtoMethods = new HashMap<>(); + private final Set myInstantiatedClasses = new HashSet<>(); private int myInstantiatedClassesCount; - private final Set myProcessedMethods = new HashSet(); + private final Set myProcessedMethods = new HashSet<>(); @Override - public void visitMethod(@Nonnull RefMethod method) { + public void visitMethod(RefMethod method) { if (!myProcessedMethods.contains(method)) { - // Process class's static intitializers + // Process class's static initializers if (method.isStatic() || method.isConstructor()) { if (method.isConstructor()) { addInstantiatedClass(method.getOwnerClass()); - } else { - ((RefClassImpl) method.getOwnerClass()).setReachable(true); + } + else { + ((RefClassImpl)method.getOwnerClass()).setReachable(true); } myProcessedMethods.add(method); - makeContentReachable((RefJavaElementImpl) method); + makeContentReachable((RefJavaElementImpl)method); makeClassInitializersReachable(method.getOwnerClass()); - } else { + } + else { if (isClassInstantiated(method.getOwnerClass())) { myProcessedMethods.add(method); - makeContentReachable((RefJavaElementImpl) method); - } else { + makeContentReachable((RefJavaElementImpl)method); + } + else { addDelayedMethod(method); } @@ -742,12 +675,12 @@ public void visitMethod(@Nonnull RefMethod method) { } @Override - public void visitClass(@Nonnull RefClass refClass) { + public void visitClass(RefClass refClass) { boolean alreadyActive = refClass.isReachable(); - ((RefClassImpl) refClass).setReachable(true); + ((RefClassImpl)refClass).setReachable(true); if (!alreadyActive) { - // Process class's static intitializers. + // Process class's static initializers. makeClassInitializersReachable(refClass); } @@ -755,17 +688,17 @@ public void visitClass(@Nonnull RefClass refClass) { } @Override - public void visitField(@Nonnull RefField field) { - // Process class's static intitializers. + public void visitField(RefField field) { + // Process class's static initializers. if (!field.isReachable()) { - makeContentReachable((RefJavaElementImpl) field); + makeContentReachable((RefJavaElementImpl)field); makeClassInitializersReachable(field.getOwnerClass()); } } private void addInstantiatedClass(RefClass refClass) { if (myInstantiatedClasses.add(refClass)) { - ((RefClassImpl) refClass).setReachable(true); + ((RefClassImpl)refClass).setReachable(true); myInstantiatedClassesCount++; final List refMethods = refClass.getLibraryMethods(); @@ -794,7 +727,7 @@ private void makeClassInitializersReachable(RefClass refClass) { private void addDelayedMethod(RefMethod refMethod) { Set methods = myClassIDtoMethods.get(refMethod.getOwnerClass()); if (methods == null) { - methods = new HashSet(); + methods = new HashSet<>(); myClassIDtoMethods.put(refMethod.getOwnerClass(), methods); } methods.add(refMethod); @@ -829,18 +762,13 @@ private void processDelayedMethods() { } @Override - public void initialize(@Nonnull GlobalInspectionContext context) { - super.initialize(context); + @SuppressWarnings("unchecked") + public void initialize(GlobalInspectionContext context, Object state) { + super.initialize(context, state); myContext = context; - for (EntryPoint extension : JavaExtensionPoints.DEAD_CODE_EP_NAME.getExtensionList()) { - boolean alreadyAdded = ContainerUtil.find(myExtensions, point -> point.getClass().equals(extension.getClass())) != null; - if (!alreadyAdded) { - try { - myExtensions.add(extension.clone()); - } catch (CloneNotSupportedException e) { - LOG.error(e); - } - } + UnusedDeclarationInspectionState inspectionState = (UnusedDeclarationInspectionState)state; + for (EntryPointProvider extension : Application.get().getExtensionList(EntryPointProvider.class)) { + inspectionState.ENTRY_POINTS.put(extension.getId(), extension.createState()); } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionState.java new file mode 100644 index 0000000000..121f7005ae --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deadCode/UnusedDeclarationInspectionState.java @@ -0,0 +1,69 @@ +package com.intellij.java.analysis.impl.codeInspection.deadCode; + +import com.intellij.java.analysis.codeInspection.ex.EntryPointProvider; +import com.intellij.java.analysis.codeInspection.ex.EntryPointState; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author VISTALL + * @since 20/03/2023 + */ +public class UnusedDeclarationInspectionState implements InspectionToolState +{ + public boolean ADD_MAINS_TO_ENTRIES = true; + public boolean ADD_APPLET_TO_ENTRIES = true; + public boolean ADD_SERVLET_TO_ENTRIES = true; + public boolean ADD_NONJAVA_TO_ENTRIES = true; + + public Map ENTRY_POINTS = new LinkedHashMap<>(); + + public boolean isAddMainsEnabled() + { + return ADD_MAINS_TO_ENTRIES; + } + + public boolean isAddAppletEnabled() + { + return ADD_APPLET_TO_ENTRIES; + } + + public boolean isAddServletEnabled() + { + return ADD_SERVLET_TO_ENTRIES; + } + + public boolean isAddNonJavaUsedEnabled() + { + return ADD_NONJAVA_TO_ENTRIES; + } + + @SuppressWarnings("unchecked") + public State getEntryPointState(EntryPointProvider provider) + { + EntryPointState state = ENTRY_POINTS.get(provider.getId()); + if(state != null) + { + return (State) state; + } + + return provider.createState(); + } + + @Nullable + @Override + public UnusedDeclarationInspectionState getState() + { + return this; + } + + @Override + public void loadState(UnusedDeclarationInspectionState state) + { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java index 021919e33d..7d50f349ce 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspection.java @@ -24,310 +24,359 @@ import com.intellij.java.language.psi.infos.MethodCandidateInfo; import com.intellij.java.language.psi.util.MethodSignatureBackedByPsiMethod; import consulo.annotation.DeprecationInfo; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.document.util.TextRange; -import consulo.language.editor.DeprecationUtil; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.editor.inspection.ui.MultipleCheckboxOptionsPanel; +import consulo.java.analysis.codeInspection.DeprecationUtil; +import consulo.language.editor.inspection.*; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.NonNls; +import consulo.localize.LocalizeValue; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; import java.util.List; /** * @author max */ @ExtensionImpl -public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool { - @NonNls - public static final String SHORT_NAME = DeprecationUtil.DEPRECATION_SHORT_NAME; - @NonNls - public static final String ID = DeprecationUtil.DEPRECATION_ID; - public static final String DISPLAY_NAME = DeprecationUtil.DEPRECATION_DISPLAY_NAME; - public static final String IGNORE_METHODS_OF_DEPRECATED_NAME = "IGNORE_METHODS_OF_DEPRECATED"; +public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool implements ProblemHighlightTypeInspectionRuler { + public static final String SHORT_NAME = DeprecationUtil.DEPRECATION_SHORT_NAME; + public static final String ID = DeprecationUtil.DEPRECATION_ID; + public static final LocalizeValue DISPLAY_NAME = DeprecationUtil.DEPRECATION_DISPLAY_NAME; - public boolean IGNORE_INSIDE_DEPRECATED = false; - public boolean IGNORE_ABSTRACT_DEPRECATED_OVERRIDES = true; - public boolean IGNORE_IMPORT_STATEMENTS = true; - public boolean IGNORE_METHODS_OF_DEPRECATED = true; - - @Override - @Nonnull - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - return new DeprecationElementVisitor(holder, IGNORE_INSIDE_DEPRECATED, IGNORE_ABSTRACT_DEPRECATED_OVERRIDES, IGNORE_IMPORT_STATEMENTS, - IGNORE_METHODS_OF_DEPRECATED); - } - - @Override - @Nonnull - public String getDisplayName() { - return DISPLAY_NAME; - } - - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } - - @Override - @Nonnull - @NonNls - public String getID() { - return ID; - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - public JComponent createOptionsPanel() { - final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); - panel.addCheckbox("Ignore inside deprecated members", "IGNORE_INSIDE_DEPRECATED"); - panel.addCheckbox("Ignore inside non-static imports", "IGNORE_IMPORT_STATEMENTS"); - panel.addCheckbox("Ignore overrides of deprecated abstract methods from non-deprecated supers", - "IGNORE_ABSTRACT_DEPRECATED_OVERRIDES"); - panel.addCheckbox("Ignore members of deprecated classes", IGNORE_METHODS_OF_DEPRECATED_NAME); - return panel; - - } + @Override + public PsiElementVisitor buildVisitorImpl( + ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + DeprecationInspectionState state + ) { + return new DeprecationElementVisitor( + holder, + state.IGNORE_INSIDE_DEPRECATED, + state.IGNORE_ABSTRACT_DEPRECATED_OVERRIDES, + state.IGNORE_IMPORT_STATEMENTS, + state.IGNORE_METHODS_OF_DEPRECATED + ); + } - private static class DeprecationElementVisitor extends JavaElementVisitor { - private final ProblemsHolder myHolder; - private final boolean myIgnoreInsideDeprecated; - private final boolean myIgnoreAbstractDeprecatedOverrides; - private final boolean myIgnoreImportStatements; - private final boolean myIgnoreMethodsOfDeprecated; + @Override + public InspectionToolState createStateProvider() { + return new DeprecationInspectionState(); + } - public DeprecationElementVisitor(final ProblemsHolder holder, - boolean ignoreInsideDeprecated, - boolean ignoreAbstractDeprecatedOverrides, - boolean ignoreImportStatements, - boolean ignoreMethodsOfDeprecated) { - myHolder = holder; - myIgnoreInsideDeprecated = ignoreInsideDeprecated; - myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides; - myIgnoreImportStatements = ignoreImportStatements; - myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated; + @Override + public LocalizeValue getDisplayName() { + return DISPLAY_NAME; } @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - JavaResolveResult result = reference.advancedResolve(true); - PsiElement resolved = result.getElement(); - checkDeprecated(resolved, reference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, myIgnoreImportStatements, - myIgnoreMethodsOfDeprecated, myHolder); + public String getShortName() { + return SHORT_NAME; } @Override - public void visitImportStaticStatement(PsiImportStaticStatement statement) { - final PsiJavaCodeReferenceElement importReference = statement.getImportReference(); - if (importReference != null) { - checkDeprecated(importReference.resolve(), importReference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, false, true, - myHolder); - } + public String getID() { + return ID; } @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); + public boolean isEnabledByDefault() { + return true; } @Override - public void visitNewExpression(PsiNewExpression expression) { - PsiType type = expression.getType(); - PsiExpressionList list = expression.getArgumentList(); - if (!(type instanceof PsiClassType)) { - return; - } - PsiClassType.ClassResolveResult typeResult = ((PsiClassType) type).resolveGenerics(); - PsiClass aClass = typeResult.getElement(); - if (aClass == null) { - return; - } - if (aClass instanceof PsiAnonymousClass) { - type = ((PsiAnonymousClass) aClass).getBaseClassType(); - typeResult = ((PsiClassType) type).resolveGenerics(); - aClass = typeResult.getElement(); - if (aClass == null) { - return; + public ProblemHighlightType getControllableHighlightType() { + return ProblemHighlightType.LIKE_DEPRECATED; + } + + private static class DeprecationElementVisitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + private final boolean myIgnoreInsideDeprecated; + private final boolean myIgnoreAbstractDeprecatedOverrides; + private final boolean myIgnoreImportStatements; + private final boolean myIgnoreMethodsOfDeprecated; + + public DeprecationElementVisitor( + ProblemsHolder holder, + boolean ignoreInsideDeprecated, + boolean ignoreAbstractDeprecatedOverrides, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated + ) { + myHolder = holder; + myIgnoreInsideDeprecated = ignoreInsideDeprecated; + myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides; + myIgnoreImportStatements = ignoreImportStatements; + myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated; } - } - final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); - final PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length > 0 && list != null) { - JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType) type, list, list); - MethodCandidateInfo result = null; - if (results.length == 1) { - result = (MethodCandidateInfo) results[0]; + + @Override + @RequiredReadAction + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + JavaResolveResult result = reference.advancedResolve(true); + PsiElement resolved = result.getElement(); + checkDeprecated( + resolved, + reference.getReferenceNameElement(), + null, + myIgnoreInsideDeprecated, + myIgnoreImportStatements, + myIgnoreMethodsOfDeprecated, + myHolder + ); } - PsiMethod constructor = result == null ? null : result.getElement(); - if (constructor != null && expression.getClassOrAnonymousClassReference() != null) { - if (expression.getClassReference() == null && constructor.getParameterList().getParametersCount() == 0) { - return; - } - checkDeprecated(constructor, expression.getClassOrAnonymousClassReference(), null, myIgnoreInsideDeprecated, - myIgnoreImportStatements, true, myHolder); + @Override + @RequiredReadAction + public void visitImportStaticStatement(PsiImportStaticStatement statement) { + PsiJavaCodeReferenceElement importReference = statement.getImportReference(); + if (importReference != null) { + checkDeprecated( + importReference.resolve(), + importReference.getReferenceNameElement(), + null, + myIgnoreInsideDeprecated, + false, + true, + myHolder + ); + } } - } - } - @Override - public void visitMethod(PsiMethod method) { - MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); - if (!method.isConstructor()) { - List superMethodSignatures = method.findSuperMethodSignaturesIncludingStatic(true); - checkMethodOverridesDeprecated(methodSignature, superMethodSignatures, myIgnoreAbstractDeprecatedOverrides, myHolder); - } else { - checkImplicitCallToSuper(method); - } - } + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } - private void checkImplicitCallToSuper(PsiMethod method) { - final PsiClass containingClass = method.getContainingClass(); - assert containingClass != null; - final PsiClass superClass = containingClass.getSuperClass(); - if (hasDefaultDeprecatedConstructor(superClass)) { - if (superClass instanceof PsiAnonymousClass) { - final PsiExpressionList argumentList = ((PsiAnonymousClass) superClass).getArgumentList(); - if (argumentList != null && argumentList.getExpressions().length > 0) { - return; - } + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + PsiType type = expression.getType(); + PsiExpressionList list = expression.getArgumentList(); + if (!(type instanceof PsiClassType classType)) { + return; + } + PsiClassType.ClassResolveResult typeResult = classType.resolveGenerics(); + PsiClass aClass = typeResult.getElement(); + if (aClass == null) { + return; + } + if (aClass instanceof PsiAnonymousClass anonymousClass) { + type = anonymousClass.getBaseClassType(); + typeResult = classType.resolveGenerics(); + aClass = typeResult.getElement(); + if (aClass == null) { + return; + } + } + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length > 0 && list != null) { + JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType) type, list, list); + MethodCandidateInfo result = null; + if (results.length == 1) { + result = (MethodCandidateInfo) results[0]; + } + + PsiMethod constructor = result == null ? null : result.getElement(); + if (constructor != null && expression.getClassOrAnonymousClassReference() != null) { + if (expression.getClassReference() == null && constructor.getParameterList().getParametersCount() == 0) { + return; + } + checkDeprecated(constructor, expression.getClassOrAnonymousClassReference(), null, myIgnoreInsideDeprecated, + myIgnoreImportStatements, true, myHolder + ); + } + } + } + + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); + if (!method.isConstructor()) { + List superMethodSignatures = method.findSuperMethodSignaturesIncludingStatic(true); + checkMethodOverridesDeprecated(methodSignature, superMethodSignatures, myIgnoreAbstractDeprecatedOverrides, myHolder); + } + else { + checkImplicitCallToSuper(method); + } } - final PsiCodeBlock body = method.getBody(); - if (body != null) { - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true)) { - registerDefaultConstructorProblem(superClass, method.getNameIdentifier(), false); - } + + @RequiredReadAction + private void checkImplicitCallToSuper(PsiMethod method) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + return; + } + + PsiClass superClass = containingClass.getSuperClass(); + if (hasDefaultDeprecatedConstructor(superClass)) { + if (superClass instanceof PsiAnonymousClass anonymousClass) { + PsiExpressionList argumentList = anonymousClass.getArgumentList(); + if (argumentList != null && argumentList.getExpressions().length > 0) { + return; + } + } + PsiCodeBlock body = method.getBody(); + if (body != null) { + PsiStatement[] statements = body.getStatements(); + if (statements.length == 0 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true)) { + registerDefaultConstructorProblem(superClass, method.getNameIdentifier(), false); + } + } + } } - } - } - private void registerDefaultConstructorProblem(PsiClass superClass, PsiElement nameIdentifier, boolean asDeprecated) { - myHolder.registerProblem(nameIdentifier, "Default constructor in " + superClass.getQualifiedName() + " is deprecated", - asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } + @RequiredReadAction + private void registerDefaultConstructorProblem(PsiClass superClass, PsiElement nameIdentifier, boolean asDeprecated) { + myHolder.newProblem(LocalizeValue.of("Default constructor in " + superClass.getQualifiedName() + " is deprecated")) + .range(nameIdentifier) + .highlightType(asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + .create(); + } - @Override - public void visitClass(PsiClass aClass) { - if (aClass instanceof PsiTypeParameter) { - return; - } - final PsiMethod[] currentConstructors = aClass.getConstructors(); - if (currentConstructors.length == 0) { - final PsiClass superClass = aClass.getSuperClass(); - if (hasDefaultDeprecatedConstructor(superClass)) { - final boolean isAnonymous = aClass instanceof PsiAnonymousClass; - if (isAnonymous) { - final PsiExpressionList argumentList = ((PsiAnonymousClass) aClass).getArgumentList(); - if (argumentList != null && argumentList.getExpressions().length > 0) { - return; + @Override + @RequiredReadAction + public void visitClass(PsiClass aClass) { + if (aClass instanceof PsiTypeParameter) { + return; + } + PsiMethod[] currentConstructors = aClass.getConstructors(); + if (currentConstructors.length == 0) { + PsiClass superClass = aClass.getSuperClass(); + if (hasDefaultDeprecatedConstructor(superClass)) { + boolean isAnonymous = aClass instanceof PsiAnonymousClass; + if (isAnonymous) { + PsiExpressionList argumentList = ((PsiAnonymousClass) aClass).getArgumentList(); + if (argumentList != null && argumentList.getExpressions().length > 0) { + return; + } + } + registerDefaultConstructorProblem( + superClass, + isAnonymous ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass.getNameIdentifier(), + isAnonymous + ); + } } - } - registerDefaultConstructorProblem(superClass, isAnonymous ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass - .getNameIdentifier(), isAnonymous); } - } } - } - private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass) { - if (superClass != null) { - final PsiMethod[] constructors = superClass.getConstructors(); - for (PsiMethod constructor : constructors) { - if (constructor.getParameterList().getParametersCount() == 0 && constructor.isDeprecated()) { - return true; + private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass) { + if (superClass != null) { + PsiMethod[] constructors = superClass.getConstructors(); + for (PsiMethod constructor : constructors) { + if (constructor.getParameterList().getParametersCount() == 0 && constructor.isDeprecated()) { + return true; + } + } } - } + return false; } - return false; - } - //@top - static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod methodSignature, - List superMethodSignatures, - boolean ignoreAbstractDeprecatedOverrides, - ProblemsHolder holder) { - PsiMethod method = methodSignature.getMethod(); - PsiElement methodName = method.getNameIdentifier(); - for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { - PsiMethod superMethod = superMethodSignature.getMethod(); - PsiClass aClass = superMethod.getContainingClass(); - if (aClass == null) { - continue; - } - // do not show deprecated warning for class implementing deprecated methods - if (ignoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { - continue; - } - if (superMethod.isDeprecated()) { - String description = JavaErrorBundle.message("overrides.deprecated.method", HighlightMessageUtil.getSymbolName(aClass, - PsiSubstitutor.EMPTY)); - holder.registerProblem(methodName, description, ProblemHighlightType.LIKE_DEPRECATED); - } + //@top + static void checkMethodOverridesDeprecated( + MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean ignoreAbstractDeprecatedOverrides, + ProblemsHolder holder + ) { + PsiMethod method = methodSignature.getMethod(); + PsiElement methodName = method.getNameIdentifier(); + for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) { + PsiMethod superMethod = superMethodSignature.getMethod(); + PsiClass aClass = superMethod.getContainingClass(); + if (aClass == null) { + continue; + } + // do not show deprecated warning for class implementing deprecated methods + if (ignoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && superMethod.isAbstract()) { + continue; + } + if (superMethod.isDeprecated()) { + String description = JavaErrorBundle.message("overrides.deprecated.method", HighlightMessageUtil.getSymbolName( + aClass, + PsiSubstitutor.EMPTY + )); + holder.newProblem(LocalizeValue.localizeTODO(description)) + .range(methodName) + .highlightType(ProblemHighlightType.LIKE_DEPRECATED) + .create(); + } + } } - } - - public static void checkDeprecated(PsiElement refElement, - PsiElement elementToHighlight, - @Nullable TextRange rangeInElement, - ProblemsHolder holder) { - checkDeprecated(refElement, elementToHighlight, rangeInElement, false, false, true, holder); - } - public static void checkDeprecated(PsiElement refElement, - PsiElement elementToHighlight, - @Nullable TextRange rangeInElement, - boolean ignoreInsideDeprecated, - boolean ignoreImportStatements, - boolean ignoreMethodsOfDeprecated, - ProblemsHolder holder) { - if (!(refElement instanceof PsiDocCommentOwner)) { - return; - } - if (!((PsiDocCommentOwner) refElement).isDeprecated()) { - if (!ignoreMethodsOfDeprecated) { - checkDeprecated(((PsiDocCommentOwner) refElement).getContainingClass(), elementToHighlight, rangeInElement, ignoreInsideDeprecated, - ignoreImportStatements, false, holder); - } - return; + @RequiredReadAction + public static void checkDeprecated( + PsiElement refElement, + PsiElement elementToHighlight, + @Nullable TextRange rangeInElement, + ProblemsHolder holder + ) { + checkDeprecated(refElement, elementToHighlight, rangeInElement, false, false, true, holder); } - if (ignoreInsideDeprecated) { - PsiElement parent = elementToHighlight; - while ((parent = PsiTreeUtil.getParentOfType(parent, PsiDocCommentOwner.class, true)) != null) { - if (((PsiDocCommentOwner) parent).isDeprecated()) { - return; + @RequiredReadAction + public static void checkDeprecated( + PsiElement refElement, + PsiElement elementToHighlight, + @Nullable TextRange rangeInElement, + boolean ignoreInsideDeprecated, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated, + ProblemsHolder holder + ) { + if (!(refElement instanceof PsiDocCommentOwner docCommentOwner)) { + return; + } + if (!docCommentOwner.isDeprecated()) { + if (!ignoreMethodsOfDeprecated) { + checkDeprecated( + docCommentOwner.getContainingClass(), + elementToHighlight, + rangeInElement, + ignoreInsideDeprecated, + ignoreImportStatements, + false, + holder + ); + } + return; } - } - } - if (ignoreImportStatements && PsiTreeUtil.getParentOfType(elementToHighlight, PsiImportStatementBase.class) != null) { - return; - } + if (ignoreInsideDeprecated) { + PsiElement parent = elementToHighlight; + while ((parent = PsiTreeUtil.getParentOfType(parent, PsiDocCommentOwner.class, true)) != null) { + if (((PsiDocCommentOwner) parent).isDeprecated()) { + return; + } + } + } - String description = null; + if (ignoreImportStatements && PsiTreeUtil.getParentOfType(elementToHighlight, PsiImportStatementBase.class) != null) { + return; + } - String symbolName = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); - PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiModifierListOwner) refElement, DeprecationInfo.class.getName()); - if (annotation != null) { - String value = AnnotationUtil.getStringAttributeValue(annotation, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); - description = JavaErrorBundle.message("deprecated.symbol.0", symbolName, value); - } + String description = null; - if (description == null) { - description = JavaErrorBundle.message("deprecated.symbol", symbolName); + LocalizeValue symbolName = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); + PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiModifierListOwner) refElement, DeprecationInfo.class.getName()); + if (annotation != null) { + String value = AnnotationUtil.getStringAttributeValue(annotation, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); + description = JavaErrorBundle.message("deprecated.symbol.0", symbolName, value); + } + + if (description == null) { + description = JavaErrorBundle.message("deprecated.symbol", symbolName); + } + holder.newProblem(LocalizeValue.of(description)) + .range(elementToHighlight, rangeInElement) + .highlightType(ProblemHighlightType.LIKE_DEPRECATED) + .create(); } - holder.registerProblem(elementToHighlight, description, ProblemHighlightType.LIKE_DEPRECATED, rangeInElement); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspectionState.java new file mode 100644 index 0000000000..b6e622abcc --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/deprecation/DeprecationInspectionState.java @@ -0,0 +1,51 @@ +package com.intellij.java.analysis.impl.codeInspection.deprecation; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 20/03/2023 + */ +public class DeprecationInspectionState implements InspectionToolState { + public boolean IGNORE_INSIDE_DEPRECATED = false; + public boolean IGNORE_IMPORT_STATEMENTS = true; + public boolean IGNORE_ABSTRACT_DEPRECATED_OVERRIDES = true; + public boolean IGNORE_METHODS_OF_DEPRECATED = true; + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder builder = ConfigurableBuilder.newBuilder(); + builder.checkBox(LocalizeValue.localizeTODO("Ignore inside deprecated members"), + () -> IGNORE_INSIDE_DEPRECATED, + b -> IGNORE_INSIDE_DEPRECATED = b); + builder.checkBox(LocalizeValue.localizeTODO("Ignore inside non-static imports"), + () -> IGNORE_IMPORT_STATEMENTS, + b -> IGNORE_IMPORT_STATEMENTS = b); + builder.checkBox(LocalizeValue.localizeTODO("Ignore overrides of deprecated abstract methods from non-deprecated supers"), + () -> IGNORE_ABSTRACT_DEPRECATED_OVERRIDES, + b -> IGNORE_ABSTRACT_DEPRECATED_OVERRIDES = b); + builder.checkBox(LocalizeValue.localizeTODO("Ignore members of deprecated classes"), + () -> IGNORE_METHODS_OF_DEPRECATED, + b -> IGNORE_METHODS_OF_DEPRECATED = b); + return builder.buildUnnamed(); + } + + @Nullable + @Override + public DeprecationInspectionState getState() { + return this; + } + + @Override + public void loadState(DeprecationInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java index 00042f25ec..3291b7171b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/equalsAndHashcode/EqualsAndHashcode.java @@ -18,121 +18,134 @@ import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.MethodSignatureUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.application.ApplicationManager; import consulo.application.util.CachedValueProvider; import consulo.application.util.CachedValuesManager; -import consulo.application.util.function.Computable; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.inspection.InspectionsBundle; -import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.LocalInspectionToolSession; import consulo.language.editor.inspection.ProblemsHolder; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.scope.GlobalSearchScope; +import consulo.localize.LocalizeValue; import consulo.module.content.ProjectRootManager; import consulo.project.Project; -import consulo.util.lang.Pair; -import org.jetbrains.annotations.NonNls; +import consulo.util.lang.Couple; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import java.util.function.Supplier; /** * @author max */ @ExtensionImpl public class EqualsAndHashcode extends BaseJavaBatchLocalInspectionTool { - @Override - @Nonnull - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - final Project project = holder.getProject(); - Pair pair = CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider>() { - @Override - public Result> compute() { - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiClass psiObjectClass = ApplicationManager.getApplication().runReadAction( - new Computable() { - @Override - @Nullable - public PsiClass compute() { - return psiFacade.findClass(JavaClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project)); - } - } - ); - if (psiObjectClass == null) { - return Result.create(null, ProjectRootManager.getInstance(project)); + private static record CheckResult(boolean hasEquals, boolean hasHashCode) { + private static final String CODE_EQUALS = "equals()"; + private static final String CODE_HASH_CODE = "hashCode()"; + + public boolean hasInconsistency() { + return hasEquals != hasHashCode; } - PsiMethod[] methods = psiObjectClass.getMethods(); - PsiMethod myEquals = null; - PsiMethod myHashCode = null; - for (PsiMethod method : methods) { - @NonNls final String name = method.getName(); - if ("equals".equals(name)) { - myEquals = method; - } - else if ("hashCode".equals(name)) { - myHashCode = method; - } + + public LocalizeValue getErrorMessage() { + if (!hasInconsistency()) { + return LocalizeValue.empty(); + } + return hasEquals + ? InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor(CODE_EQUALS, CODE_HASH_CODE) + : InspectionLocalize.inspectionEqualsHashcodeOnlyOneDefinedProblemDescriptor(CODE_HASH_CODE, CODE_EQUALS); } - return Result.create(Pair.create(myEquals, myHashCode), psiObjectClass); - } - }); + } - if (pair == null) return new PsiElementVisitor() {}; + @Override + @RequiredReadAction + public PsiElementVisitor buildVisitorImpl( + final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + Project project = holder.getProject(); + Couple pair = CachedValuesManager.getManager(project).getCachedValue( + project, + () -> { + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiClass psiObjectClass = project.getApplication().runReadAction( + (Supplier) () -> psiFacade.findClass(CommonClassNames.JAVA_LANG_OBJECT, GlobalSearchScope.allScope(project)) + ); + if (psiObjectClass == null) { + return CachedValueProvider.Result.create(null, ProjectRootManager.getInstance(project)); + } + PsiMethod[] methods = psiObjectClass.getMethods(); + PsiMethod myEquals = null; + PsiMethod myHashCode = null; + for (PsiMethod method : methods) { + String name = method.getName(); + if ("equals".equals(name)) { + myEquals = method; + } + else if ("hashCode".equals(name)) { + myHashCode = method; + } + } + return CachedValueProvider.Result.create(Couple.of(myEquals, myHashCode), psiObjectClass); + } + ); - //jdk wasn't configured for the project - final PsiMethod myEquals = pair.first; - final PsiMethod myHashCode = pair.second; - if (myEquals == null || myHashCode == null || !myEquals.isValid() || !myHashCode.isValid()) return new PsiElementVisitor() {}; + if (pair == null) { + return new PsiElementVisitor() { + }; + } - return new JavaElementVisitor() { - @Override public void visitClass(PsiClass aClass) { - super.visitClass(aClass); - boolean [] hasEquals = {false}; - boolean [] hasHashCode = {false}; - processClass(aClass, hasEquals, hasHashCode, myEquals, myHashCode); - if (hasEquals[0] != hasHashCode[0]) { - PsiIdentifier identifier = aClass.getNameIdentifier(); - holder.registerProblem(identifier != null ? identifier : aClass, - hasEquals[0] - ? InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor", "equals()", "hashCode()") - : InspectionsBundle.message("inspection.equals.hashcode.only.one.defined.problem.descriptor","hashCode()", "equals()"), - (LocalQuickFix[])null); + //jdk wasn't configured for the project + final PsiMethod myEquals = pair.first; + final PsiMethod myHashCode = pair.second; + if (myEquals == null || myHashCode == null || !myEquals.isValid() || !myHashCode.isValid()) { + return new PsiElementVisitor() { + }; } - } - }; - } - private static void processClass(final PsiClass aClass, - final boolean[] hasEquals, - final boolean[] hasHashCode, - PsiMethod equals, PsiMethod hashcode) { - final PsiMethod[] methods = aClass.getMethods(); - for (PsiMethod method : methods) { - if (MethodSignatureUtil.areSignaturesEqual(method, equals)) { - hasEquals[0] = true; - } - else if (MethodSignatureUtil.areSignaturesEqual(method, hashcode)) { - hasHashCode[0] = true; - } + return new JavaElementVisitor() { + @Override + public void visitClass(PsiClass aClass) { + super.visitClass(aClass); + CheckResult checkResult = processClass(aClass, myEquals, myHashCode); + if (checkResult.hasInconsistency()) { + PsiIdentifier identifier = aClass.getNameIdentifier(); + holder.newProblem(checkResult.getErrorMessage()) + .range(identifier != null ? identifier : aClass) + .create(); + } + } + }; + } + + private static CheckResult processClass(PsiClass aClass, PsiMethod equals, PsiMethod hashCode) { + boolean hasEquals = false, hasHashCode = false; + PsiMethod[] methods = aClass.getMethods(); + for (PsiMethod method : methods) { + if (MethodSignatureUtil.areSignaturesEqual(method, equals)) { + hasEquals = true; + } + else if (MethodSignatureUtil.areSignaturesEqual(method, hashCode)) { + hasHashCode = true; + } + } + return new CheckResult(hasEquals, hasHashCode); } - } - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("inspection.equals.hashcode.display.name"); - } + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionEqualsHashcodeDisplayName(); + } - @Override - @Nonnull - public String getGroupDisplayName() { - return ""; - } + @Override + public LocalizeValue getGroupDisplayName() { + return LocalizeValue.empty(); + } - @Override - @Nonnull - public String getShortName() { - return "EqualsAndHashcode"; - } + @Override + public String getShortName() { + return "EqualsAndHashcode"; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/BaseLocalInspectionTool.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/BaseLocalInspectionTool.java index ea2c4796f8..858d3f65f7 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/BaseLocalInspectionTool.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/BaseLocalInspectionTool.java @@ -18,21 +18,18 @@ import com.intellij.java.analysis.impl.codeInspection.BaseJavaLocalInspectionTool; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; -import javax.annotation.Nonnull; - /** - * User: anna - * Date: Dec 27, 2004 + * @author anna + * @since 2004-12-27 */ public abstract class BaseLocalInspectionTool extends BaseJavaLocalInspectionTool { - @Override - public boolean isEnabledByDefault() { - return true; - } + @Override + public boolean isEnabledByDefault() { + return true; + } - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/EntryPointsManagerBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/EntryPointsManagerBase.java index 1b3c2e44e6..1a1e374339 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/EntryPointsManagerBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/ex/EntryPointsManagerBase.java @@ -15,7 +15,7 @@ */ package com.intellij.java.analysis.impl.codeInspection.ex; -import com.intellij.java.analysis.codeInspection.ex.EntryPoint; +import com.intellij.java.analysis.codeInspection.ex.EntryPointProvider; import com.intellij.java.analysis.codeInspection.ex.EntryPointsManager; import com.intellij.java.analysis.codeInspection.reference.RefClass; import com.intellij.java.analysis.codeInspection.reference.RefJavaManager; @@ -23,38 +23,34 @@ import com.intellij.java.language.codeInsight.AnnotationUtil; import com.intellij.java.language.psi.PsiDocCommentOwner; import com.intellij.java.language.psi.PsiModifierListOwner; +import consulo.application.Application; import consulo.application.ApplicationManager; import consulo.codeEditor.Editor; import consulo.component.persist.PersistentStateComponent; import consulo.disposer.Disposer; -import consulo.ide.ServiceManager; -import consulo.java.analysis.codeInspection.JavaExtensionPoints; -import consulo.java.analysis.impl.JavaQuickFixBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; import consulo.language.editor.impl.inspection.reference.RefElementImpl; import consulo.language.editor.impl.inspection.reference.SmartRefElementPointerImpl; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefEntity; import consulo.language.editor.inspection.reference.RefManager; import consulo.language.editor.inspection.reference.SmartRefElementPointer; -import consulo.language.editor.intention.IntentionAction; import consulo.language.editor.intention.SyntheticIntentionAction; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiManager; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Comparing; import consulo.util.xml.serializer.InvalidDataException; import consulo.util.xml.serializer.JDOMExternalizableStringList; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; import java.util.*; public abstract class EntryPointsManagerBase extends EntryPointsManager implements PersistentStateComponent { - @NonNls private static final String[] STANDARD_ANNOS = { "javax.ws.rs.*", }; @@ -65,9 +61,9 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen public Collection getAdditionalAnnotations() { List annos = ADDITIONAL_ANNOS; if (annos == null) { - annos = new ArrayList(); + annos = new ArrayList<>(); Collections.addAll(annos, STANDARD_ANNOS); - for (EntryPoint extension : JavaExtensionPoints.DEAD_CODE_EP_NAME.getExtensions()) { + for (EntryPointProvider extension : Application.get().getExtensionList(EntryPointProvider.class)) { final String[] ignoredAnnotations = extension.getIgnoreAnnotations(); if (ignoredAnnotations != null) { ContainerUtil.addAll(annos, ignoredAnnotations); @@ -82,9 +78,7 @@ public Collection getAdditionalAnnotations() { private final Map myPersistentEntryPoints; private final Set myTemporaryEntryPoints; private static final String VERSION = "2.0"; - @NonNls private static final String VERSION_ATTR = "version"; - @NonNls private static final String ENTRY_POINT_ATTR = "entry_point"; private boolean myAddNonJavaEntries = true; private boolean myResolved = false; @@ -99,7 +93,7 @@ public EntryPointsManagerBase(Project project) { } public static EntryPointsManagerBase getInstance(Project project) { - return (EntryPointsManagerBase) ServiceManager.getService(project, EntryPointsManager.class); + return (EntryPointsManagerBase) project.getInstance(EntryPointsManager.class); } @Override @@ -152,7 +146,7 @@ public static void writeExternal(final Element element, final Map>> ourForbiddenAPI = new EnumMap<>(LanguageLevel.class); - private static final Supplier> ourIgnored16ClassesAPI = LazyValue.notNull(() -> loadForbiddenApi("ignore16List.txt")); - private static final Map ourPresentableShortMessage = new EnumMap<>(LanguageLevel.class); - - private static final LanguageLevel ourHighestKnownLanguage = LanguageLevel.JDK_19; - - static { - ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_8, "9"); - ourPresentableShortMessage.put(LanguageLevel.JDK_1_9, "10"); - ourPresentableShortMessage.put(LanguageLevel.JDK_10, "11"); - ourPresentableShortMessage.put(LanguageLevel.JDK_11, "12"); - ourPresentableShortMessage.put(LanguageLevel.JDK_12, "13"); - ourPresentableShortMessage.put(LanguageLevel.JDK_13, "14"); - ourPresentableShortMessage.put(LanguageLevel.JDK_14, "15"); - ourPresentableShortMessage.put(LanguageLevel.JDK_15, "16"); - ourPresentableShortMessage.put(LanguageLevel.JDK_16, "17"); - ourPresentableShortMessage.put(LanguageLevel.JDK_17, "18"); - ourPresentableShortMessage.put(LanguageLevel.JDK_18, "19"); - - } - - private static final Set ourGenerifiedClasses = new HashSet<>(); - - static { - ourGenerifiedClasses.add("javax.swing.JComboBox"); - ourGenerifiedClasses.add("javax.swing.ListModel"); - ourGenerifiedClasses.add("javax.swing.JList"); - } - - private static final Set ourDefaultMethods = new HashSet<>(); - - static { - ourDefaultMethods.add("java.util.Iterator#remove()"); - } - - LanguageLevel myEffectiveLanguageLevel; - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - public JComponent createOptionsPanel() { - JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 5, true, false)); - panel.add(new JLabel("Forbid API usages:")); - - JRadioButton projectRb = new JRadioButton("Respecting to project language level settings"); - panel.add(projectRb); - JRadioButton customRb = new JRadioButton("Higher than:"); - panel.add(customRb); - ButtonGroup gr = new ButtonGroup(); - gr.add(projectRb); - gr.add(customRb); - - JComboBox llCombo = new ComboBox(LanguageLevel.values()) { - @Override - public void setEnabled(boolean b) { - if (b == customRb.isSelected()) { - super.setEnabled(b); - } - } - }; - llCombo.setSelectedItem(myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : LanguageLevel.JDK_1_3); - llCombo.setRenderer(SimpleListCellRenderer.create("", LanguageLevel::getFullText)); - llCombo.addActionListener(e -> myEffectiveLanguageLevel = (LanguageLevel) llCombo.getSelectedItem()); - - JPanel comboPanel = new JPanel(new BorderLayout()); - comboPanel.setBorder(JBUI.Borders.emptyLeft(20)); - comboPanel.add(llCombo, BorderLayout.WEST); - panel.add(comboPanel); - - ActionListener actionListener = e -> { - if (projectRb.isSelected()) { - myEffectiveLanguageLevel = null; - } else { - myEffectiveLanguageLevel = (LanguageLevel) llCombo.getSelectedItem(); - } - UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); - }; - projectRb.addActionListener(actionListener); - customRb.addActionListener(actionListener); - projectRb.setSelected(myEffectiveLanguageLevel == null); - customRb.setSelected(myEffectiveLanguageLevel != null); - UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); - return panel; - } - - @Nullable - private static Set getForbiddenApi(@Nonnull LanguageLevel languageLevel) { - if (!ourPresentableShortMessage.containsKey(languageLevel)) { - return null; - } - Reference> ref = ourForbiddenAPI.get(languageLevel); - Set result = SoftReference.dereference(ref); - if (result == null) { - result = loadForbiddenApi("api" + getShortName(languageLevel) + ".txt"); - ourForbiddenAPI.put(languageLevel, new SoftReference<>(result)); + public static final String SHORT_NAME = "Since15"; + + private static final String EFFECTIVE_LL = "effectiveLL"; + + private static final Map>> ourForbiddenAPI = new EnumMap<>(LanguageLevel.class); + private static final Supplier> ourIgnored16ClassesAPI = LazyValue.notNull(() -> loadForbiddenApi("ignore16List.txt")); + private static final Map ourPresentableShortMessage = new EnumMap<>(LanguageLevel.class); + + private static final LanguageLevel ourHighestKnownLanguage = LanguageLevel.JDK_19; + + static { + ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_8, "9"); + ourPresentableShortMessage.put(LanguageLevel.JDK_1_9, "10"); + ourPresentableShortMessage.put(LanguageLevel.JDK_10, "11"); + ourPresentableShortMessage.put(LanguageLevel.JDK_11, "12"); + ourPresentableShortMessage.put(LanguageLevel.JDK_12, "13"); + ourPresentableShortMessage.put(LanguageLevel.JDK_13, "14"); + ourPresentableShortMessage.put(LanguageLevel.JDK_14, "15"); + ourPresentableShortMessage.put(LanguageLevel.JDK_15, "16"); + ourPresentableShortMessage.put(LanguageLevel.JDK_16, "17"); + ourPresentableShortMessage.put(LanguageLevel.JDK_17, "18"); + ourPresentableShortMessage.put(LanguageLevel.JDK_18, "19"); + } - return result; - } - - private static Set loadForbiddenApi(String fileName) { - URL resource = Java15APIUsageInspection.class.getResource(fileName); - if (resource == null) { - Logger.getInstance(Java15APIUsageInspection.class).warn("not found: " + fileName); - return Collections.emptySet(); + + private static final Set ourGenerifiedClasses = new HashSet<>(); + + static { + ourGenerifiedClasses.add("javax.swing.JComboBox"); + ourGenerifiedClasses.add("javax.swing.ListModel"); + ourGenerifiedClasses.add("javax.swing.JList"); } - try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) { - return new HashSet<>(FileUtil.loadLines(reader)); - } catch (IOException ex) { - Logger.getInstance(Java15APIUsageInspection.class).warn("cannot load: " + fileName, ex); - return Collections.emptySet(); + private static final Set ourDefaultMethods = new HashSet<>(); + + static { + ourDefaultMethods.add("java.util.Iterator#remove()"); } - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("inspection.1.5.display.name"); - } - - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } - - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.ERROR; - } - - @Override - public void readSettings(@Nonnull Element node) throws InvalidDataException { - final Element element = node.getChild(EFFECTIVE_LL); - if (element != null) { - myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value")); + + LanguageLevel myEffectiveLanguageLevel; + + @Override + public boolean isEnabledByDefault() { + return true; } - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - if (myEffectiveLanguageLevel != null) { - final Element llElement = new Element(EFFECTIVE_LL); - llElement.setAttribute("value", myEffectiveLanguageLevel.toString()); - node.addContent(llElement); + + @Override + public JComponent createOptionsPanel() { + JPanel panel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 5, true, false)); + panel.add(new JLabel("Forbid API usages:")); + + JRadioButton projectRb = new JRadioButton("Respecting to project language level settings"); + panel.add(projectRb); + JRadioButton customRb = new JRadioButton("Higher than:"); + panel.add(customRb); + ButtonGroup gr = new ButtonGroup(); + gr.add(projectRb); + gr.add(customRb); + + JComboBox llCombo = new ComboBox(LanguageLevel.values()) { + @Override + public void setEnabled(boolean b) { + if (b == customRb.isSelected()) { + super.setEnabled(b); + } + } + }; + llCombo.setSelectedItem(myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : LanguageLevel.JDK_1_3); + llCombo.setRenderer(SimpleListCellRenderer.create("", languageLevel -> languageLevel.getDescription().get())); + llCombo.addActionListener(e -> myEffectiveLanguageLevel = (LanguageLevel)llCombo.getSelectedItem()); + + JPanel comboPanel = new JPanel(new BorderLayout()); + comboPanel.setBorder(JBUI.Borders.emptyLeft(20)); + comboPanel.add(llCombo, BorderLayout.WEST); + panel.add(comboPanel); + + ActionListener actionListener = e -> { + if (projectRb.isSelected()) { + myEffectiveLanguageLevel = null; + } + else { + myEffectiveLanguageLevel = (LanguageLevel)llCombo.getSelectedItem(); + } + UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); + }; + projectRb.addActionListener(actionListener); + customRb.addActionListener(actionListener); + projectRb.setSelected(myEffectiveLanguageLevel == null); + customRb.setSelected(myEffectiveLanguageLevel != null); + UIUtil.setEnabled(comboPanel, !projectRb.isSelected(), true); + return panel; } - } - @Override - @Nonnull - public PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, boolean isOnTheFly) { - return new MyVisitor(holder, isOnTheFly); - } + @Nullable + private static Set getForbiddenApi(LanguageLevel languageLevel) { + if (!ourPresentableShortMessage.containsKey(languageLevel)) { + return null; + } + Reference> ref = ourForbiddenAPI.get(languageLevel); + Set result = SoftReference.dereference(ref); + if (result == null) { + result = loadForbiddenApi("api" + getShortName(languageLevel) + ".txt"); + ourForbiddenAPI.put(languageLevel, new SoftReference<>(result)); + } + return result; + } - private static boolean isInProject(final PsiElement elt) { - return elt.getManager().isInProject(elt); - } + private static Set loadForbiddenApi(String fileName) { + URL resource = Java15APIUsageInspection.class.getResource(fileName); + if (resource == null) { + Logger.getInstance(Java15APIUsageInspection.class).warn("not found: " + fileName); + return Collections.emptySet(); + } - public static String getShortName(LanguageLevel languageLevel) { - return ourPresentableShortMessage.get(languageLevel); - } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8))) { + return new HashSet<>(FileUtil.loadLines(reader)); + } + catch (IOException ex) { + Logger.getInstance(Java15APIUsageInspection.class).warn("cannot load: " + fileName, ex); + return Collections.emptySet(); + } + } - private class MyVisitor extends JavaElementVisitor { - private final ProblemsHolder myHolder; - private final boolean myOnTheFly; + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids(); + } - MyVisitor(final ProblemsHolder holder, boolean onTheFly) { - myHolder = holder; - myOnTheFly = onTheFly; + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspection15DisplayName(); } @Override - public void visitDocComment(PsiDocComment comment) { - // No references inside doc comment are of interest. + public String getShortName() { + return SHORT_NAME; } + @Override - public void visitClass(PsiClass aClass) { - // Don't go into classes (anonymous, locals). - if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter)) { - final Module module = ModuleUtilCore.findModuleForPsiElement(aClass); - final LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null; - if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - final JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass); - if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) { - final List methods = new ArrayList<>(); - for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) { - final PsiMethod method = methodSignature.getMethod(); - if (ourDefaultMethods.contains(getSignature(method))) { - methods.add(method); - } - } + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.ERROR; + } - if (!methods.isEmpty()) { - PsiElement element2Highlight = aClass.getNameIdentifier(); - if (element2Highlight == null) { - element2Highlight = aClass instanceof PsiAnonymousClass ? ((PsiAnonymousClass) aClass).getBaseClassReference() : aClass; - } - myHolder.registerProblem(element2Highlight, - methods.size() == 1 ? InspectionsBundle.message("inspection.1.8.problem.single.descriptor", methods.get(0).getName(), getJdkName(effectiveLanguageLevel)) - : InspectionsBundle.message("inspection.1.8.problem.descriptor", methods.size(), getJdkName(effectiveLanguageLevel)), - QuickFixFactory.getInstance().createImplementMethodsFix(aClass)); - } - } + @Override + public void readSettings(Element node) throws InvalidDataException { + Element element = node.getChild(EFFECTIVE_LL); + if (element != null) { + myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value")); } - } } @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); + public void writeSettings(Element node) throws WriteExternalException { + if (myEffectiveLanguageLevel != null) { + Element llElement = new Element(EFFECTIVE_LL); + llElement.setAttribute("value", myEffectiveLanguageLevel.toString()); + node.addContent(llElement); + } } @Override - public void visitNameValuePair(PsiNameValuePair pair) { - super.visitNameValuePair(pair); - PsiReference reference = pair.getReference(); - if (reference != null) { - PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiCompiledElement && resolve instanceof PsiAnnotationMethod) { - final Module module = ModuleUtilCore.findModuleForPsiElement(pair); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember) resolve, languageLevel); - if (sinceLanguageLevel != null) { - registerError(ObjectUtil.notNull(pair.getNameIdentifier(), pair), sinceLanguageLevel); + public PsiElementVisitor buildVisitorImpl( + ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + return new MyVisitor(holder, isOnTheFly); + } + + private static boolean isInProject(PsiElement elt) { + return elt.getManager().isInProject(elt); + } + + public static String getShortName(LanguageLevel languageLevel) { + return ourPresentableShortMessage.get(languageLevel); + } + + private class MyVisitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + private final boolean myOnTheFly; + + MyVisitor(ProblemsHolder holder, boolean onTheFly) { + myHolder = holder; + myOnTheFly = onTheFly; + } + + @Override + public void visitDocComment(PsiDocComment comment) { + // No references inside doc comment are of interest. + } + + @Override + @RequiredReadAction + public void visitClass(PsiClass aClass) { + // Don't go into classes (anonymous, locals). + if (!aClass.isAbstract() && !(aClass instanceof PsiTypeParameter)) { + Module module = aClass.getModule(); + LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null; + if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass); + if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) { + List methods = new ArrayList<>(); + for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) { + PsiMethod method = methodSignature.getMethod(); + if (ourDefaultMethods.contains(getSignature(method))) { + methods.add(method); + } + } + + if (!methods.isEmpty()) { + PsiElement element2Highlight = aClass.getNameIdentifier(); + if (element2Highlight == null) { + element2Highlight = + aClass instanceof PsiAnonymousClass anonymousClass ? anonymousClass.getBaseClassReference() : aClass; + } + String descriptionTemplate = methods.size() == 1 + ? InspectionsBundle.message( + "inspection.1.8.problem.single.descriptor", + methods.get(0).getName(), + getJdkName(effectiveLanguageLevel) + ) + : InspectionsBundle.message( + "inspection.1.8.problem.descriptor", + methods.size(), + getJdkName(effectiveLanguageLevel) + ); + myHolder.newProblem(LocalizeValue.of(descriptionTemplate)) + .range(element2Highlight) + .withFixes(QuickFixFactory.getInstance().createImplementMethodsFix(aClass)) + .create(); + } + } + } } - } } - } - } - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - final PsiElement resolved = reference.resolve(); - - if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember) { - final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement()); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel((PsiMember) resolved, languageLevel); - if (sinceLanguageLevel != null) { - PsiClass psiClass = null; - final PsiElement qualifier = reference.getQualifier(); - if (qualifier != null) { - if (qualifier instanceof PsiExpression) { - psiClass = PsiUtil.resolveClassInType(((PsiExpression) qualifier).getType()); - } - } else { - psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class); + @Override + @RequiredReadAction + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } + + @Override + @RequiredReadAction + public void visitNameValuePair(PsiNameValuePair pair) { + super.visitNameValuePair(pair); + PsiReference reference = pair.getReference(); + if (reference != null) { + PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiCompiledElement && resolve instanceof PsiAnnotationMethod annotationMethod) { + Module module = pair.getModule(); + if (module != null) { + LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(annotationMethod, languageLevel); + if (sinceLanguageLevel != null) { + registerError(ObjectUtil.notNull(pair.getNameIdentifier(), pair), sinceLanguageLevel); + } + } + } } - if (psiClass != null) { - if (isIgnored(psiClass)) { - return; - } - for (PsiClass superClass : psiClass.getSupers()) { - if (isIgnored(superClass)) { - return; + } + + @Override + @RequiredReadAction + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + PsiElement resolved = reference.resolve(); + + if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember member) { + Module module = reference.getElement().getModule(); + if (module != null) { + LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(member, languageLevel); + if (sinceLanguageLevel != null) { + PsiClass psiClass = null; + PsiElement qualifier = reference.getQualifier(); + if (qualifier != null) { + if (qualifier instanceof PsiExpression) { + psiClass = PsiUtil.resolveClassInType(((PsiExpression)qualifier).getType()); + } + } + else { + psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class); + } + if (psiClass != null) { + if (isIgnored(psiClass)) { + return; + } + for (PsiClass superClass : psiClass.getSupers()) { + if (isIgnored(superClass)) { + return; + } + } + } + registerError(reference, sinceLanguageLevel); + } + else if (resolved instanceof PsiClass psiClass && isInProject(reference) && !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) { + PsiReferenceParameterList parameterList = reference.getParameterList(); + if (parameterList != null && parameterList.getTypeParameterElements().length > 0) { + for (String generifiedClass : ourGenerifiedClasses) { + if (InheritanceUtil.isInheritor(psiClass, generifiedClass) + && !isRawInheritance(generifiedClass, psiClass, new HashSet<>())) { + String message = + InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel)); + myHolder.registerProblem(reference, message); + break; + } + } + } + } } - } } - 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) { - for (String generifiedClass : ourGenerifiedClasses) { - if (InheritanceUtil.isInheritor((PsiClass) resolved, generifiedClass) && - !isRawInheritance(generifiedClass, (PsiClass) resolved, new HashSet<>())) { - String message = InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel)); - myHolder.registerProblem(reference, message); - break; + } + + private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set visited) { + for (PsiClassType classType : currentClass.getSuperTypes()) { + if (classType.isRaw()) { + return true; + } + PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + PsiClass superClass = resolveResult.getElement(); + if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) { + if (isRawInheritance(generifiedClassQName, superClass, visited)) { + return true; + } } - } } - } + return false; } - } - } - private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set visited) { - for (PsiClassType classType : currentClass.getSuperTypes()) { - if (classType.isRaw()) { - return true; + private boolean isIgnored(PsiClass psiClass) { + String qualifiedName = psiClass.getQualifiedName(); + return qualifiedName != null && ourIgnored16ClassesAPI.get().contains(qualifiedName); } - final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); - final PsiClass superClass = resolveResult.getElement(); - if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) { - if (isRawInheritance(generifiedClassQName, superClass, visited)) { - return true; - } + + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + super.visitNewExpression(expression); + PsiMethod constructor = expression.resolveConstructor(); + Module module = expression.getModule(); + if (module != null) { + LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + if (constructor instanceof PsiCompiledElement) { + LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); + if (sinceLanguageLevel != null) { + registerError(expression.getClassReference(), sinceLanguageLevel); + } + } + } } - } - return false; - } - private boolean isIgnored(PsiClass psiClass) { - final String qualifiedName = psiClass.getQualifiedName(); - return qualifiedName != null && ourIgnored16ClassesAPI.get().contains(qualifiedName); - } + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + PsiAnnotation annotation = !method.isConstructor() + ? AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) : null; + if (annotation != null) { + Module module = annotation.getModule(); + LanguageLevel sinceLanguageLevel = null; + if (module != null) { + LanguageLevel languageLevel = getEffectiveLanguageLevel(module); + PsiMethod[] methods = method.findSuperMethods(); + for (PsiMethod superMethod : methods) { + if (superMethod instanceof PsiCompiledElement) { + sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); + if (sinceLanguageLevel == null) { + return; + } + } + else { + return; + } + } + if (methods.length > 0) { + registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); + } + } + } + } - @Override - public void visitNewExpression(final PsiNewExpression expression) { - super.visitNewExpression(expression); - final PsiMethod constructor = expression.resolveConstructor(); - final Module module = ModuleUtilCore.findModuleForPsiElement(expression); - if (module != null) { - final LanguageLevel languageLevel = getEffectiveLanguageLevel(module); - if (constructor instanceof PsiCompiledElement) { - LanguageLevel sinceLanguageLevel = getLastIncompatibleLanguageLevel(constructor, languageLevel); - if (sinceLanguageLevel != null) { - registerError(expression.getClassReference(), sinceLanguageLevel); - } + @RequiredReadAction + private LanguageLevel getEffectiveLanguageLevel(Module module) { + return myEffectiveLanguageLevel != null ? myEffectiveLanguageLevel : EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); } - } - } - @Override - public void visitMethod(PsiMethod method) { - super.visitMethod(method); - 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) { - sinceLanguageLevel = getLastIncompatibleLanguageLevel(superMethod, languageLevel); - if (sinceLanguageLevel == null) { - return; - } - } else { - return; + @RequiredReadAction + private void registerError(PsiElement reference, LanguageLevel api) { + if (reference != null && isInProject(reference)) { + ProblemBuilder builder = myHolder.newProblem(InspectionLocalize.inspection15ProblemDescriptor(getShortName(api))) + .range(reference); + if (myOnTheFly) { + QuickFixFactory factory = QuickFixFactory.getInstance(); + builder.withFix((LocalQuickFix)factory.createIncreaseLanguageLevelFix(LanguageLevel.values()[api.ordinal() + 1])); + } + builder.create(); } - } - if (methods.length > 0) { - registerError(annotation.getNameReferenceElement(), sinceLanguageLevel); - } } - } - } - - private LanguageLevel getEffectiveLanguageLevel(Module module) { - if (myEffectiveLanguageLevel != null) { - return myEffectiveLanguageLevel; - } - return EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); } - private void registerError(PsiElement reference, LanguageLevel api) { - if (reference != null && isInProject(reference)) { - 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); - } + private static String getJdkName(LanguageLevel languageLevel) { + return languageLevel.getDescription().get(); } - } - private static String getJdkName(LanguageLevel languageLevel) { - final String presentableText = languageLevel.getFullText(); - return presentableText.substring(0, presentableText.indexOf(' ')); - } + public static LanguageLevel getLastIncompatibleLanguageLevel(PsiMember member, LanguageLevel languageLevel) { + if (member instanceof PsiAnonymousClass) { + return null; + } + PsiClass containingClass = member.getContainingClass(); + if (containingClass instanceof PsiAnonymousClass) { + return null; + } + if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) { + return null; + } - public static LanguageLevel getLastIncompatibleLanguageLevel(@Nonnull PsiMember member, @Nonnull LanguageLevel languageLevel) { - if (member instanceof PsiAnonymousClass) { - return null; - } - PsiClass containingClass = member.getContainingClass(); - if (containingClass instanceof PsiAnonymousClass) { - return null; - } - if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) { - return null; + Set forbiddenApi = getForbiddenApi(languageLevel); + String signature = getSignature(member); + if (forbiddenApi != null && signature != null) { + LanguageLevel lastIncompatibleLanguageLevel = + getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); + if (lastIncompatibleLanguageLevel != null) { + return lastIncompatibleLanguageLevel; + } + } + return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; } - Set forbiddenApi = getForbiddenApi(languageLevel); - String signature = getSignature(member); - if (forbiddenApi != null && signature != null) { - LanguageLevel lastIncompatibleLanguageLevel = getLastIncompatibleLanguageLevelForSignature(signature, languageLevel, forbiddenApi); - if (lastIncompatibleLanguageLevel != null) { - return lastIncompatibleLanguageLevel; - } + private static LanguageLevel getLastIncompatibleLanguageLevelForSignature( + String signature, + LanguageLevel languageLevel, + Set forbiddenApi + ) { + if (forbiddenApi.contains(signature)) { + return languageLevel; + } + if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { + return null; + } + LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; + Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); + return nextForbiddenApi != null + ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) + : null; } - return containingClass != null ? getLastIncompatibleLanguageLevel(containingClass, languageLevel) : null; - } + /** + * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages + */ + @Nullable + public static String getSignature(@Nullable PsiMember member) { + if (member instanceof PsiClass psiClass) { + return psiClass.getQualifiedName(); + } + if (member instanceof PsiField) { + String containingClass = getSignature(member.getContainingClass()); + return containingClass == null ? null : containingClass + "#" + member.getName(); + } + if (member instanceof PsiMethod method) { + String containingClass = getSignature(member.getContainingClass()); + if (containingClass == null) { + return null; + } - private static LanguageLevel getLastIncompatibleLanguageLevelForSignature(@Nonnull String signature, @Nonnull LanguageLevel languageLevel, @Nonnull Set forbiddenApi) { - if (forbiddenApi.contains(signature)) { - return languageLevel; - } - if (languageLevel.compareTo(ourHighestKnownLanguage) == 0) { - return null; - } - LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1]; - Set nextForbiddenApi = getForbiddenApi(nextLanguageLevel); - return nextForbiddenApi != null ? getLastIncompatibleLanguageLevelForSignature(signature, nextLanguageLevel, nextForbiddenApi) : null; - } - - /** - * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages - */ - @Nullable - public static String getSignature(@Nullable PsiMember member) { - if (member instanceof PsiClass) { - return ((PsiClass) member).getQualifiedName(); - } - if (member instanceof PsiField) { - String containingClass = getSignature(member.getContainingClass()); - return containingClass == null ? null : containingClass + "#" + member.getName(); - } - if (member instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) member; - String containingClass = getSignature(member.getContainingClass()); - if (containingClass == null) { + StringBuilder buf = new StringBuilder(); + buf.append(containingClass).append('#').append(method.getName()).append('('); + for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) { + buf.append(type.getCanonicalText()).append(";"); + } + buf.append(')'); + return buf.toString(); + } return null; - } - - StringBuilder buf = new StringBuilder(); - buf.append(containingClass); - buf.append('#'); - buf.append(method.getName()); - buf.append('('); - for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) { - buf.append(type.getCanonicalText()); - buf.append(";"); - } - buf.append(')'); - return buf.toString(); } - return null; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/localCanBeFinal/LocalCanBeFinal.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/localCanBeFinal/LocalCanBeFinal.java index abf723b893..74fef90c52 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/localCanBeFinal/LocalCanBeFinal.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/localCanBeFinal/LocalCanBeFinal.java @@ -16,30 +16,27 @@ package com.intellij.java.analysis.impl.codeInspection.localCanBeFinal; import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.impl.psi.controlFlow.*; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; import consulo.language.editor.FileModificationService; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.language.editor.inspection.ProblemHighlightType; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionManager; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import java.awt.*; import java.util.ArrayList; import java.util.HashSet; @@ -51,310 +48,327 @@ */ @ExtensionImpl public class LocalCanBeFinal extends BaseJavaBatchLocalInspectionTool { - private static final Logger LOG = Logger.getInstance(LocalCanBeFinal.class); - - public boolean REPORT_VARIABLES = true; - public boolean REPORT_PARAMETERS = true; - - private final LocalQuickFix myQuickFix; - @NonNls - public static final String SHORT_NAME = "LocalCanBeFinal"; - - public LocalCanBeFinal() { - myQuickFix = new AcceptSuggested(); - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Override - public ProblemDescriptor[] checkMethod(@Nonnull PsiMethod method, @Nonnull InspectionManager manager, boolean isOnTheFly) { - List list = checkCodeBlock(method.getBody(), manager, isOnTheFly); - return list == null ? null : list.toArray(new ProblemDescriptor[list.size()]); - } - - @Override - public ProblemDescriptor[] checkClass(@Nonnull PsiClass aClass, @Nonnull InspectionManager manager, boolean isOnTheFly) { - List allProblems = null; - final PsiClassInitializer[] initializers = aClass.getInitializers(); - for (PsiClassInitializer initializer : initializers) { - final List problems = checkCodeBlock(initializer.getBody(), manager, isOnTheFly); - if (problems != null) { - if (allProblems == null) { - allProblems = new ArrayList(1); - } - allProblems.addAll(problems); - } - } - return allProblems == null ? null : allProblems.toArray(new ProblemDescriptor[allProblems.size()]); - } - - @Nullable - private List checkCodeBlock(final PsiCodeBlock body, InspectionManager manager, boolean onTheFly) { - if (body == null) return null; - final ControlFlow flow; - try { - ControlFlowPolicy policy = new ControlFlowPolicy() { - @Override - public PsiVariable getUsedVariable(PsiReferenceExpression refExpr) { - if (refExpr.isQualified()) return null; + private static final Logger LOG = Logger.getInstance(LocalCanBeFinal.class); - PsiElement refElement = refExpr.resolve(); - if (refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) { - if (!isVariableDeclaredInMethod((PsiVariable) refElement)) return null; - return (PsiVariable) refElement; - } + public boolean REPORT_VARIABLES = true; + public boolean REPORT_PARAMETERS = true; - return null; - } + private final LocalQuickFix myQuickFix; + public static final String SHORT_NAME = "LocalCanBeFinal"; - @Override - public boolean isParameterAccepted(PsiParameter psiParameter) { - return isVariableDeclaredInMethod(psiParameter); - } - - @Override - public boolean isLocalVariableAccepted(PsiLocalVariable psiVariable) { - return isVariableDeclaredInMethod(psiVariable); - } + public LocalCanBeFinal() { + myQuickFix = new AcceptSuggested(); + } - private boolean isVariableDeclaredInMethod(PsiVariable psiVariable) { - return PsiTreeUtil.getParentOfType(psiVariable, PsiClass.class) == PsiTreeUtil.getParentOfType(body, PsiClass.class); - } - }; - flow = ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, policy, false); - } catch (AnalysisCanceledException e) { - return null; + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; } - int start = flow.getStartOffset(body); - int end = flow.getEndOffset(body); + @Override + @RequiredReadAction + public ProblemDescriptor[] checkMethod( + PsiMethod method, + InspectionManager manager, + boolean isOnTheFly, + Object state + ) { + List list = checkCodeBlock(method.getBody(), manager, isOnTheFly); + return list == null ? null : list.toArray(new ProblemDescriptor[list.size()]); + } - final List writtenVariables = new ArrayList(ControlFlowUtil.getWrittenVariables(flow, start, end, false)); + @Override + @RequiredReadAction + public ProblemDescriptor[] checkClass(PsiClass aClass, InspectionManager manager, boolean isOnTheFly, Object state) { + List allProblems = null; + PsiClassInitializer[] initializers = aClass.getInitializers(); + for (PsiClassInitializer initializer : initializers) { + List problems = checkCodeBlock(initializer.getBody(), manager, isOnTheFly); + if (problems != null) { + if (allProblems == null) { + allProblems = new ArrayList<>(1); + } + allProblems.addAll(problems); + } + } + return allProblems == null ? null : allProblems.toArray(new ProblemDescriptor[allProblems.size()]); + } - final HashSet ssaVarsSet = new HashSet(); - body.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitCodeBlock(PsiCodeBlock block) { - super.visitCodeBlock(block); - PsiElement anchor = block; - if (block.getParent() instanceof PsiSwitchStatement) { - anchor = block.getParent(); + @Nullable + @RequiredReadAction + private List checkCodeBlock(final PsiCodeBlock body, InspectionManager manager, boolean onTheFly) { + if (body == null) { + return null; } - int from = flow.getStartOffset(anchor); - int end = flow.getEndOffset(anchor); - List ssa = ControlFlowUtil.getSSAVariables(flow, from, end, true); - HashSet declared = getDeclaredVariables(block); - for (PsiVariable psiVariable : ssa) { - if (declared.contains(psiVariable)) { - ssaVarsSet.add(psiVariable); - } + final ControlFlow flow; + try { + ControlFlowPolicy policy = new ControlFlowPolicy() { + @Override + @RequiredReadAction + public PsiVariable getUsedVariable(PsiReferenceExpression refExpr) { + if (refExpr.isQualified()) { + return null; + } + + PsiElement refElement = refExpr.resolve(); + if (refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) { + if (!isVariableDeclaredInMethod((PsiVariable) refElement)) { + return null; + } + return (PsiVariable) refElement; + } + + return null; + } + + @Override + public boolean isParameterAccepted(PsiParameter psiParameter) { + return isVariableDeclaredInMethod(psiParameter); + } + + @Override + public boolean isLocalVariableAccepted(PsiLocalVariable psiVariable) { + return isVariableDeclaredInMethod(psiVariable); + } + + private boolean isVariableDeclaredInMethod(PsiVariable psiVariable) { + return PsiTreeUtil.getParentOfType(psiVariable, PsiClass.class) == PsiTreeUtil.getParentOfType(body, PsiClass.class); + } + }; + flow = ControlFlowFactory.getInstance(body.getProject()).getControlFlow(body, policy, false); } - } - - @Override - public void visitForeachStatement(PsiForeachStatement statement) { - super.visitForeachStatement(statement); - final PsiParameter param = statement.getIterationParameter(); - final PsiStatement body = statement.getBody(); - if (body == null) return; - int from = flow.getStartOffset(body); - int end = flow.getEndOffset(body); - if (!ControlFlowUtil.getWrittenVariables(flow, from, end, false).contains(param)) { - writtenVariables.remove(param); - ssaVarsSet.add(param); + catch (AnalysisCanceledException e) { + return null; } - } - private HashSet getDeclaredVariables(PsiCodeBlock block) { - final HashSet result = new HashSet(); - PsiElement[] children = block.getChildren(); - for (PsiElement child : children) { - child.accept(new JavaElementVisitor() { + int start = flow.getStartOffset(body); + int end = flow.getEndOffset(body); + + final List writtenVariables = + new ArrayList<>(ControlFlowUtil.getWrittenVariables(flow, start, end, false)); + + final HashSet ssaVarsSet = new HashSet<>(); + body.accept(new JavaRecursiveElementWalkingVisitor() { @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); + @RequiredReadAction + public void visitCodeBlock(PsiCodeBlock block) { + super.visitCodeBlock(block); + PsiElement anchor = block; + if (block.getParent() instanceof PsiSwitchStatement) { + anchor = block.getParent(); + } + int from = flow.getStartOffset(anchor); + int end = flow.getEndOffset(anchor); + List ssa = ControlFlowUtil.getSSAVariables(flow, from, end, true); + HashSet declared = getDeclaredVariables(block); + for (PsiVariable psiVariable : ssa) { + if (declared.contains(psiVariable)) { + ssaVarsSet.add(psiVariable); + } + } } @Override - public void visitDeclarationStatement(PsiDeclarationStatement statement) { - PsiElement[] declaredElements = statement.getDeclaredElements(); - for (PsiElement declaredElement : declaredElements) { - if (declaredElement instanceof PsiVariable) result.add(declaredElement); - } + public void visitForeachStatement(PsiForeachStatement statement) { + super.visitForeachStatement(statement); + PsiParameter param = statement.getIterationParameter(); + PsiStatement body = statement.getBody(); + if (body == null) { + return; + } + int from = flow.getStartOffset(body); + int end = flow.getEndOffset(body); + if (!ControlFlowUtil.getWrittenVariables(flow, from, end, false).contains(param)) { + writtenVariables.remove(param); + ssaVarsSet.add(param); + } } - }); - } - - return result; - } - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - } - }); + @RequiredReadAction + private HashSet getDeclaredVariables(PsiCodeBlock block) { + final HashSet result = new HashSet<>(); + PsiElement[] children = block.getChildren(); + for (PsiElement child : children) { + child.accept(new JavaElementVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } + + @Override + public void visitDeclarationStatement(PsiDeclarationStatement statement) { + PsiElement[] declaredElements = statement.getDeclaredElements(); + for (PsiElement declaredElement : declaredElements) { + if (declaredElement instanceof PsiVariable) { + result.add(declaredElement); + } + } + } + }); + } + + return result; + } - ArrayList result = new ArrayList(ssaVarsSet); + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + }); - if (body.getParent() instanceof PsiMethod) { - PsiMethod method = (PsiMethod) body.getParent(); - PsiParameter[] parameters = method.getParameterList().getParameters(); - for (PsiParameter parameter : parameters) { - if (!result.contains(parameter)) result.add(parameter); - } - } + List result = new ArrayList<>(ssaVarsSet); - PsiVariable[] psiVariables = result.toArray(new PsiVariable[result.size()]); - for (PsiVariable psiVariable : psiVariables) { - if (!isReportParameters() && psiVariable instanceof PsiParameter || !isReportVariables() && psiVariable instanceof PsiLocalVariable || - psiVariable.hasModifierProperty(PsiModifier.FINAL)) { - result.remove(psiVariable); - } - - if (psiVariable instanceof PsiLocalVariable) { - PsiDeclarationStatement decl = (PsiDeclarationStatement) psiVariable.getParent(); - if (decl != null && decl.getParent() instanceof PsiForStatement) { - result.remove(psiVariable); + if (body.getParent() instanceof PsiMethod method) { + for (PsiParameter parameter : method.getParameterList().getParameters()) { + if (!result.contains(parameter)) { + result.add(parameter); + } + } } - } - } - for (PsiVariable writtenVariable : writtenVariables) { - if (writtenVariable instanceof PsiParameter) { - result.remove(writtenVariable); - } - } + PsiVariable[] psiVariables = result.toArray(new PsiVariable[result.size()]); + for (PsiVariable psiVariable : psiVariables) { + if (!isReportParameters() && psiVariable instanceof PsiParameter + || !isReportVariables() && psiVariable instanceof PsiLocalVariable + || psiVariable.hasModifierProperty(PsiModifier.FINAL)) { + result.remove(psiVariable); + } - if (result.isEmpty()) return null; - for (Iterator iterator = result.iterator(); iterator.hasNext(); ) { - final PsiVariable variable = iterator.next(); - if (!variable.isPhysical()) { - iterator.remove(); - } - } - List problems = new ArrayList(result.size()); - for (PsiVariable variable : result) { - final PsiIdentifier nameIdenitier = variable.getNameIdentifier(); - PsiElement problemElement = nameIdenitier != null ? nameIdenitier : variable; - if (variable instanceof PsiParameter && !(((PsiParameter) variable).getDeclarationScope() instanceof PsiForeachStatement)) { - problems.add(manager.createProblemDescriptor(problemElement, - InspectionsBundle.message("inspection.can.be.local.parameter.problem.descriptor"), - myQuickFix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly)); - } else { - problems.add(manager.createProblemDescriptor(problemElement, - InspectionsBundle.message("inspection.can.be.local.variable.problem.descriptor"), - myQuickFix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, onTheFly)); - } - } + if (psiVariable instanceof PsiLocalVariable) { + PsiDeclarationStatement decl = (PsiDeclarationStatement) psiVariable.getParent(); + if (decl != null && decl.getParent() instanceof PsiForStatement) { + result.remove(psiVariable); + } + } + } - return problems; - } + for (PsiVariable writtenVariable : writtenVariables) { + if (writtenVariable instanceof PsiParameter) { + result.remove(writtenVariable); + } + } - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("inspection.local.can.be.final.display.name"); - } + if (result.isEmpty()) { + return null; + } + for (Iterator iterator = result.iterator(); iterator.hasNext(); ) { + PsiVariable variable = iterator.next(); + if (!variable.isPhysical()) { + iterator.remove(); + } + } + List problems = new ArrayList<>(result.size()); + for (PsiVariable variable : result) { + PsiIdentifier nameIdentifier = variable.getNameIdentifier(); + PsiElement problemElement = nameIdentifier != null ? nameIdentifier : variable; + if (variable instanceof PsiParameter param && !(param.getDeclarationScope() instanceof PsiForeachStatement)) { + problems.add(manager.newProblemDescriptor(InspectionLocalize.inspectionCanBeLocalParameterProblemDescriptor()) + .range(problemElement) + .onTheFly(onTheFly) + .withOptionalFix(myQuickFix) + .create()); + } + else { + problems.add(manager.newProblemDescriptor(InspectionLocalize.inspectionCanBeLocalVariableProblemDescriptor()) + .range(problemElement) + .onTheFly(onTheFly) + .withOptionalFix(myQuickFix) + .create()); + } + } - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.STYLE_GROUP_NAME; - } + return problems; + } - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionLocalCanBeFinalDisplayName(); + } - private static class AcceptSuggested implements LocalQuickFix { @Override - @Nonnull - public String getName() { - return InspectionsBundle.message("inspection.can.be.final.accept.quickfix"); + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesCodeStyleIssues(); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor problem) { - if (!FileModificationService.getInstance().preparePsiElementForWrite(problem.getPsiElement())) return; - PsiElement nameIdentifier = problem.getPsiElement(); - if (nameIdentifier == null) return; - PsiVariable psiVariable = PsiTreeUtil.getParentOfType(nameIdentifier, PsiVariable.class, false); - if (psiVariable == null) return; - try { - psiVariable.normalizeDeclaration(); - PsiUtil.setModifierProperty(psiVariable, PsiModifier.FINAL, true); - } catch (IncorrectOperationException e) { - LOG.error(e); - } + public String getShortName() { + return SHORT_NAME; + } + + private static class AcceptSuggested implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return InspectionLocalize.inspectionCanBeFinalAcceptQuickfix(); + } + + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor problem) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(problem.getPsiElement())) { + return; + } + PsiElement nameIdentifier = problem.getPsiElement(); + if (nameIdentifier == null) { + return; + } + PsiVariable psiVariable = PsiTreeUtil.getParentOfType(nameIdentifier, PsiVariable.class, false); + if (psiVariable == null) { + return; + } + try { + psiVariable.normalizeDeclaration(); + PsiUtil.setModifierProperty(psiVariable, PsiModifier.FINAL, true); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } } @Override - @Nonnull - public String getFamilyName() { - return getName(); + public JComponent createOptionsPanel() { + return new OptionsPanel(); } - } - @Override - public JComponent createOptionsPanel() { - return new OptionsPanel(); - } + private boolean isReportVariables() { + return REPORT_VARIABLES; + } - private boolean isReportVariables() { - return REPORT_VARIABLES; - } + private boolean isReportParameters() { + return REPORT_PARAMETERS; + } - private boolean isReportParameters() { - return REPORT_PARAMETERS; - } + private class OptionsPanel extends JPanel { + private final JCheckBox myReportVariablesCheckbox; + private final JCheckBox myReportParametersCheckbox; - private class OptionsPanel extends JPanel { - private final JCheckBox myReportVariablesCheckbox; - private final JCheckBox myReportParametersCheckbox; + private OptionsPanel() { + super(new GridBagLayout()); - private OptionsPanel() { - super(new GridBagLayout()); + GridBagConstraints gc = new GridBagConstraints(); + gc.weighty = 0; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + gc.anchor = GridBagConstraints.NORTHWEST; - GridBagConstraints gc = new GridBagConstraints(); - gc.weighty = 0; - gc.weightx = 1; - gc.fill = GridBagConstraints.HORIZONTAL; - gc.anchor = GridBagConstraints.NORTHWEST; + myReportVariablesCheckbox = new JCheckBox(InspectionLocalize.inspectionLocalCanBeFinalOption().get()); + myReportVariablesCheckbox.setSelected(REPORT_VARIABLES); + myReportVariablesCheckbox.getModel().addChangeListener(e -> REPORT_VARIABLES = myReportVariablesCheckbox.isSelected()); + gc.gridy = 0; + add(myReportVariablesCheckbox, gc); - myReportVariablesCheckbox = new JCheckBox(InspectionsBundle.message("inspection.local.can.be.final.option")); - myReportVariablesCheckbox.setSelected(REPORT_VARIABLES); - myReportVariablesCheckbox.getModel().addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - REPORT_VARIABLES = myReportVariablesCheckbox.isSelected(); - } - }); - gc.gridy = 0; - add(myReportVariablesCheckbox, gc); + myReportParametersCheckbox = new JCheckBox(InspectionLocalize.inspectionLocalCanBeFinalOption1().get()); + myReportParametersCheckbox.setSelected(REPORT_PARAMETERS); + myReportParametersCheckbox.getModel().addChangeListener(e -> REPORT_PARAMETERS = myReportParametersCheckbox.isSelected()); - myReportParametersCheckbox = new JCheckBox(InspectionsBundle.message("inspection.local.can.be.final.option1")); - myReportParametersCheckbox.setSelected(REPORT_PARAMETERS); - myReportParametersCheckbox.getModel().addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - REPORT_PARAMETERS = myReportParametersCheckbox.isSelected(); + gc.weighty = 1; + gc.gridy++; + add(myReportParametersCheckbox, gc); } - }); - - gc.weighty = 1; - gc.gridy++; - add(myReportParametersCheckbox, gc); } - } - @Override - public boolean isEnabledByDefault() { - return false; - } + @Override + public boolean isEnabledByDefault() { + return false; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/GenericsInspectionToolBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/GenericsInspectionToolBase.java index a3de3aa37f..8d2a817459 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/GenericsInspectionToolBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/GenericsInspectionToolBase.java @@ -22,27 +22,27 @@ import consulo.language.psi.PsiElement; import consulo.util.collection.ContainerUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.ArrayList; import java.util.List; /** * @author ven */ -public abstract class GenericsInspectionToolBase extends BaseJavaBatchLocalInspectionTool { +public abstract class GenericsInspectionToolBase extends BaseJavaBatchLocalInspectionTool { @Override public boolean isEnabledByDefault() { return true; } @Override - public ProblemDescriptor[] checkClass(@Nonnull PsiClass aClass, @Nonnull InspectionManager manager, boolean isOnTheFly) { + public ProblemDescriptor[] checkClass(PsiClass aClass, InspectionManager manager, boolean isOnTheFly, State state) { final PsiClassInitializer[] initializers = aClass.getInitializers(); if (initializers.length == 0) return null; List descriptors = new ArrayList(); for (PsiClassInitializer initializer : initializers) { - final ProblemDescriptor[] localDescriptions = getDescriptions(initializer, manager, isOnTheFly); + final ProblemDescriptor[] localDescriptions = getDescriptions(initializer, manager, isOnTheFly, state); if (localDescriptions != null) { ContainerUtil.addAll(descriptors, localDescriptions); } @@ -52,26 +52,26 @@ public ProblemDescriptor[] checkClass(@Nonnull PsiClass aClass, @Nonnull Inspect } @Override - public ProblemDescriptor[] checkField(@Nonnull PsiField field, @Nonnull InspectionManager manager, boolean isOnTheFly) { + public ProblemDescriptor[] checkField(PsiField field, InspectionManager manager, boolean isOnTheFly, State state) { final PsiExpression initializer = field.getInitializer(); if (initializer != null) { - return getDescriptions(initializer, manager, isOnTheFly); + return getDescriptions(initializer, manager, isOnTheFly, state); } if (field instanceof PsiEnumConstant) { - return getDescriptions(field, manager, isOnTheFly); + return getDescriptions(field, manager, isOnTheFly, state); } return null; } @Override - public ProblemDescriptor[] checkMethod(@Nonnull PsiMethod psiMethod, @Nonnull InspectionManager manager, boolean isOnTheFly) { + public ProblemDescriptor[] checkMethod(PsiMethod psiMethod, InspectionManager manager, boolean isOnTheFly, State state) { final PsiCodeBlock body = psiMethod.getBody(); if (body != null) { - return getDescriptions(body, manager, isOnTheFly); + return getDescriptions(body, manager, isOnTheFly, state); } return null; } @Nullable - public abstract ProblemDescriptor[] getDescriptions(PsiElement place, InspectionManager manager, boolean isOnTheFly); + public abstract ProblemDescriptor[] getDescriptions(PsiElement place, InspectionManager manager, boolean isOnTheFly, State state); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/RedundantTypeArgsInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/RedundantTypeArgsInspection.java index ba04893ccf..f6c18ab03f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/RedundantTypeArgsInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/RedundantTypeArgsInspection.java @@ -15,22 +15,23 @@ */ package com.intellij.java.analysis.impl.codeInspection.miscGenerics; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.inspection.ProblemHighlightType; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionManager; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; @@ -39,143 +40,166 @@ */ @ExtensionImpl public class RedundantTypeArgsInspection extends GenericsInspectionToolBase { - private static final Logger LOG = Logger.getInstance(RedundantTypeArgsInspection.class); - - public RedundantTypeArgsInspection() { - myQuickFixAction = new MyQuickFixAction(); - } - - private final LocalQuickFix myQuickFixAction; - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.VERBOSE_GROUP_NAME; - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("inspection.redundant.type.display.name"); - } - - @Override - @Nonnull - public String getShortName() { - return "RedundantTypeArguments"; - } - - - @Override - public ProblemDescriptor[] checkMethod(@Nonnull PsiMethod psiMethod, @Nonnull InspectionManager manager, boolean isOnTheFly) { - final PsiCodeBlock body = psiMethod.getBody(); - if (body != null) { - return getDescriptions(body, manager, isOnTheFly); + private static final Logger LOG = Logger.getInstance(RedundantTypeArgsInspection.class); + + public RedundantTypeArgsInspection() { + myQuickFixAction = new MyQuickFixAction(); } - return null; - } - - @Override - public ProblemDescriptor[] getDescriptions(PsiElement place, final InspectionManager inspectionManager, boolean isOnTheFly) { - final List problems = new ArrayList(); - place.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - final PsiType[] typeArguments = expression.getTypeArguments(); - if (typeArguments.length > 0) { - checkCallExpression(expression.getMethodExpression(), typeArguments, expression, inspectionManager, problems); - } - } - - @Override - public void visitNewExpression(PsiNewExpression expression) { - final PsiType[] typeArguments = expression.getTypeArguments(); - if (typeArguments.length > 0) { - final PsiJavaCodeReferenceElement classReference = expression.getClassReference(); - if (classReference != null) { - checkCallExpression(classReference, typeArguments, expression, inspectionManager, problems); - } - } - } - - private void checkCallExpression(final PsiJavaCodeReferenceElement reference, - final PsiType[] typeArguments, - PsiCallExpression expression, - final InspectionManager inspectionManager, final List problems) { - - PsiExpressionList argumentList = expression.getArgumentList(); - if (argumentList == null) return; - final JavaResolveResult resolveResult = reference.advancedResolve(false); - - final PsiElement element = resolveResult.getElement(); - if (element instanceof PsiMethod && resolveResult.isValidResult()) { - PsiMethod method = (PsiMethod) element; - final PsiTypeParameter[] typeParameters = method.getTypeParameters(); - if (typeParameters.length == typeArguments.length) { - final PsiParameter[] parameters = method.getParameterList().getParameters(); - PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); - final PsiSubstitutor psiSubstitutor = resolveHelper - .inferTypeArguments(typeParameters, parameters, argumentList.getExpressions(), PsiSubstitutor.EMPTY, expression, DefaultParameterTypeInferencePolicy.INSTANCE); - for (int i = 0, length = typeParameters.length; i < length; i++) { - PsiTypeParameter typeParameter = typeParameters[i]; - final PsiType inferredType = psiSubstitutor.getSubstitutionMap().get(typeParameter); - if (!typeArguments[i].equals(inferredType)) return; - if (PsiUtil.resolveClassInType(method.getReturnType()) == typeParameter && PsiPrimitiveType.getUnboxedType(inferredType) != null) - return; - } - final PsiCallExpression copy = (PsiCallExpression) expression.copy(); //see IDEADEV-8174 - try { - copy.getTypeArgumentList().delete(); - if (copy.resolveMethod() != element) return; - } catch (IncorrectOperationException e) { - LOG.error(e); - return; - } + private final LocalQuickFix myQuickFixAction; - final ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(expression.getTypeArgumentList(), - InspectionsBundle.message("inspection.redundant.type.problem.descriptor"), - myQuickFixAction, - ProblemHighlightType.LIKE_UNUSED_SYMBOL, false); - problems.add(descriptor); - } - } - } + @Override + public boolean isEnabledByDefault() { + return true; + } - }); + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesVerboseOrRedundantCodeConstructs(); + } - if (problems.isEmpty()) return null; - return problems.toArray(new ProblemDescriptor[problems.size()]); - } + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionRedundantTypeDisplayName(); + } - private static class MyQuickFixAction implements LocalQuickFix { @Override - @Nonnull - public String getName() { - return InspectionsBundle.message("inspection.redundant.type.remove.quickfix"); + public String getShortName() { + return "RedundantTypeArguments"; } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiReferenceParameterList typeArgumentList = (PsiReferenceParameterList) descriptor.getPsiElement(); - try { - final PsiMethodCallExpression expr = - (PsiMethodCallExpression) JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText("foo()", null); - typeArgumentList.replace(expr.getTypeArgumentList()); - } catch (IncorrectOperationException e) { - LOG.error(e); - } + public ProblemDescriptor[] checkMethod( + PsiMethod psiMethod, + InspectionManager manager, + boolean isOnTheFly, + Object state + ) { + PsiCodeBlock body = psiMethod.getBody(); + if (body != null) { + return getDescriptions(body, manager, isOnTheFly, state); + } + return null; } @Override - @Nonnull - public String getFamilyName() { - return getName(); + public ProblemDescriptor[] getDescriptions( + PsiElement place, + final InspectionManager inspectionManager, + boolean isOnTheFly, + Object state + ) { + final List problems = new ArrayList<>(); + place.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + @RequiredReadAction + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + PsiType[] typeArguments = expression.getTypeArguments(); + if (typeArguments.length > 0) { + checkCallExpression(expression.getMethodExpression(), typeArguments, expression, inspectionManager, problems); + } + } + + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + PsiType[] typeArguments = expression.getTypeArguments(); + if (typeArguments.length > 0) { + PsiJavaCodeReferenceElement classReference = expression.getClassReference(); + if (classReference != null) { + checkCallExpression(classReference, typeArguments, expression, inspectionManager, problems); + } + } + } + + @RequiredReadAction + private void checkCallExpression( + PsiJavaCodeReferenceElement reference, + PsiType[] typeArguments, + PsiCallExpression expression, + InspectionManager inspectionManager, + List problems + ) { + PsiExpressionList argumentList = expression.getArgumentList(); + if (argumentList == null) { + return; + } + JavaResolveResult resolveResult = reference.advancedResolve(false); + + if (resolveResult.getElement() instanceof PsiMethod method && resolveResult.isValidResult()) { + PsiTypeParameter[] typeParameters = method.getTypeParameters(); + if (typeParameters.length == typeArguments.length) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); + PsiSubstitutor psiSubstitutor = resolveHelper.inferTypeArguments( + typeParameters, + parameters, + argumentList.getExpressions(), + PsiSubstitutor.EMPTY, + expression, + DefaultParameterTypeInferencePolicy.INSTANCE + ); + for (int i = 0, length = typeParameters.length; i < length; i++) { + PsiTypeParameter typeParameter = typeParameters[i]; + PsiType inferredType = psiSubstitutor.getSubstitutionMap().get(typeParameter); + if (!typeArguments[i].equals(inferredType)) { + return; + } + if (PsiUtil.resolveClassInType(method.getReturnType()) == typeParameter + && PsiPrimitiveType.getUnboxedType(inferredType) != null) { + return; + } + } + + PsiCallExpression copy = (PsiCallExpression) expression.copy(); //see IDEADEV-8174 + try { + copy.getTypeArgumentList().delete(); + if (copy.resolveMethod() != method) { + return; + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + return; + } + + ProblemDescriptor descriptor = inspectionManager.newProblemDescriptor( + InspectionLocalize.inspectionRedundantTypeProblemDescriptor() + ) + .range(expression.getTypeArgumentList()) + .highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL) + .withOptionalFix(myQuickFixAction) + .create(); + problems.add(descriptor); + } + } + } + }); + + if (problems.isEmpty()) { + return null; + } + return problems.toArray(new ProblemDescriptor[problems.size()]); + } + + private static class MyQuickFixAction implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return InspectionLocalize.inspectionRedundantTypeRemoveQuickfix(); + } + + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiReferenceParameterList typeArgumentList = (PsiReferenceParameterList) descriptor.getPsiElement(); + try { + PsiMethodCallExpression expr = (PsiMethodCallExpression) JavaPsiFacade.getInstance(project).getElementFactory() + .createExpressionFromText("foo()", null); + typeArgumentList.replace(expr.getTypeArgumentList()); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java index 6291bb1642..e6e1ea5797 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/miscGenerics/SuspiciousMethodCallUtil.java @@ -8,390 +8,446 @@ import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.TypeUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.NullableLazyValue; import consulo.language.editor.inspection.InspectionsBundle; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiManager; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.collection.ArrayUtil; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; public class SuspiciousMethodCallUtil { - // List.of/Set.of are unnecessary here as they don't accept nulls - private static final CallMatcher.Simple SINGLETON_COLLECTION = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1); - - private static void setupPatternMethods(PsiManager manager, - GlobalSearchScope searchScope, - List patternMethods) { - final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(manager.getProject()); - final PsiClass collectionClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, searchScope); - PsiClassType object = PsiType.getJavaLangObject(manager, searchScope); - PsiType[] javaLangObject = {object}; - PsiType[] twoObjects = { - object, - object - }; - MethodSignature removeSignature = - MethodSignatureUtil.createMethodSignature("remove", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - if (collectionClass != null) { - PsiMethod remove = MethodSignatureUtil.findMethodBySignature(collectionClass, removeSignature, false); - addMethod(remove, 0, patternMethods, 0); - - addSingleParameterMethod(patternMethods, collectionClass, "contains", object); - - if (PsiUtil.isLanguageLevel5OrHigher(collectionClass)) { - PsiClassType wildcardCollection = javaPsiFacade.getElementFactory().createType(collectionClass, PsiWildcardType.createUnbounded(manager)); - addSingleParameterMethod(patternMethods, collectionClass, "removeAll", wildcardCollection); - addSingleParameterMethod(patternMethods, collectionClass, "retainAll", wildcardCollection); - } - } + // List.of/Set.of are unnecessary here as they don't accept nulls + private static final CallMatcher.Simple SINGLETON_COLLECTION = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1); + + @RequiredReadAction + private static void setupPatternMethods(PsiManager manager, GlobalSearchScope searchScope, List patternMethods) { + JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(manager.getProject()); + PsiClass collectionClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTION, searchScope); + PsiClassType object = PsiType.getJavaLangObject(manager, searchScope); + PsiType[] javaLangObject = {object}; + PsiType[] twoObjects = { + object, + object + }; + MethodSignature removeSignature = + MethodSignatureUtil.createMethodSignature("remove", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + if (collectionClass != null) { + PsiMethod remove = MethodSignatureUtil.findMethodBySignature(collectionClass, removeSignature, false); + addMethod(remove, 0, patternMethods, 0); + + addSingleParameterMethod(patternMethods, collectionClass, "contains", object); + + if (PsiUtil.isLanguageLevel5OrHigher(collectionClass)) { + PsiClassType wildcardCollection = + javaPsiFacade.getElementFactory().createType(collectionClass, PsiWildcardType.createUnbounded(manager)); + addSingleParameterMethod(patternMethods, collectionClass, "removeAll", wildcardCollection); + addSingleParameterMethod(patternMethods, collectionClass, "retainAll", wildcardCollection); + } + } - final PsiClass listClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_LIST, searchScope); - if (listClass != null) { - addSingleParameterMethod(patternMethods, listClass, "indexOf", object); - addSingleParameterMethod(patternMethods, listClass, "lastIndexOf", object); - } + PsiClass listClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_LIST, searchScope); + if (listClass != null) { + addSingleParameterMethod(patternMethods, listClass, "indexOf", object); + addSingleParameterMethod(patternMethods, listClass, "lastIndexOf", object); + } - final PsiClass mapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_MAP, searchScope); - if (mapClass != null) { - PsiMethod remove = MethodSignatureUtil.findMethodBySignature(mapClass, removeSignature, false); - addMethod(remove, 0, patternMethods, 0); - - addSingleParameterMethod(patternMethods, mapClass, "get", object); - - PsiTypeParameter[] typeParameters = mapClass.getTypeParameters(); - if (typeParameters.length > 0) { - MethodSignature getOrDefaultSignature = MethodSignatureUtil.createMethodSignature("getOrDefault", - new PsiType[]{ - object, - PsiSubstitutor.EMPTY.substitute(typeParameters[1]) - }, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod getOrDefault = MethodSignatureUtil.findMethodBySignature(mapClass, getOrDefaultSignature, false); - addMethod(getOrDefault, 0, patternMethods, 0); - } - - MethodSignature removeWithDefaultSignature = MethodSignatureUtil.createMethodSignature("remove", - twoObjects, - PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeWithDefault = MethodSignatureUtil.findMethodBySignature(mapClass, removeWithDefaultSignature, false); - addMethod(removeWithDefault, 0, patternMethods, 0); - addMethod(removeWithDefault, 1, patternMethods, 1); - - addSingleParameterMethod(patternMethods, mapClass, "containsKey", object); - - MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(mapClass, containsValueSignature, false); - addMethod(containsValue, 1, patternMethods, 0); - } + PsiClass mapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_MAP, searchScope); + if (mapClass != null) { + PsiMethod remove = MethodSignatureUtil.findMethodBySignature(mapClass, removeSignature, false); + addMethod(remove, 0, patternMethods, 0); + + addSingleParameterMethod(patternMethods, mapClass, "get", object); + + PsiTypeParameter[] typeParameters = mapClass.getTypeParameters(); + if (typeParameters.length > 0) { + MethodSignature getOrDefaultSignature = MethodSignatureUtil.createMethodSignature("getOrDefault", + new PsiType[]{object, PsiSubstitutor.EMPTY.substitute(typeParameters[1])}, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod getOrDefault = MethodSignatureUtil.findMethodBySignature(mapClass, getOrDefaultSignature, false); + addMethod(getOrDefault, 0, patternMethods, 0); + } - final PsiClass concurrentMapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP, searchScope); - if (concurrentMapClass != null) { - MethodSignature containsSignature = MethodSignatureUtil.createMethodSignature("contains", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod contains = MethodSignatureUtil.findMethodBySignature(concurrentMapClass, containsSignature, false); - addMethod(contains, 1, patternMethods, 0); - } - PsiClass guavaTable = javaPsiFacade.findClass("com.google.common.collect.Table", searchScope); - if (guavaTable != null) { - MethodSignature getSignature = - MethodSignatureUtil.createMethodSignature("get", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod get = MethodSignatureUtil.findMethodBySignature(guavaTable, getSignature, false); - addMethod(get, 0, patternMethods, 0); - addMethod(get, 1, patternMethods, 1); - - MethodSignature containsSignature = - MethodSignatureUtil.createMethodSignature("contains", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod contains = MethodSignatureUtil.findMethodBySignature(guavaTable, containsSignature, false); - addMethod(contains, 0, patternMethods, 0); - addMethod(contains, 1, patternMethods, 1); - - MethodSignature containsRowSignature = - MethodSignatureUtil.createMethodSignature("containsRow", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsRow = MethodSignatureUtil.findMethodBySignature(guavaTable, containsRowSignature, false); - addMethod(containsRow, 0, patternMethods, 0); - - MethodSignature containsColumnSignature = - MethodSignatureUtil.createMethodSignature("containsColumn", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, containsColumnSignature, false); - addMethod(containsColumn, 1, patternMethods, 0); - - MethodSignature containsValueSignature = - MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaTable, containsValueSignature, false); - addMethod(containsValue, 2, patternMethods, 0); - - MethodSignature removeByRowAndColumnSignature = - MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeByRowAndColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, removeByRowAndColumnSignature, false); - addMethod(removeByRowAndColumn, 0, patternMethods, 0); - addMethod(removeByRowAndColumn, 1, patternMethods, 1); - } + MethodSignature removeWithDefaultSignature = MethodSignatureUtil.createMethodSignature( + "remove", + twoObjects, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod removeWithDefault = MethodSignatureUtil.findMethodBySignature(mapClass, removeWithDefaultSignature, false); + addMethod(removeWithDefault, 0, patternMethods, 0); + addMethod(removeWithDefault, 1, patternMethods, 1); + + addSingleParameterMethod(patternMethods, mapClass, "containsKey", object); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(mapClass, containsValueSignature, false); + addMethod(containsValue, 1, patternMethods, 0); + } - PsiClass guavaMultimap = javaPsiFacade.findClass("com.google.common.collect.Multimap", searchScope); - if (guavaMultimap != null) { - MethodSignature containsKeySignature = - MethodSignatureUtil.createMethodSignature("containsKey", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsKey = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsKeySignature, false); - addMethod(containsKey, 0, patternMethods, 0); - - MethodSignature containsValueSignature = - MethodSignatureUtil.createMethodSignature("containsValue", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsValueSignature, false); - addMethod(containsValue, 1, patternMethods, 0); - - MethodSignature containsEntrySignature = - MethodSignatureUtil.createMethodSignature("containsEntry", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod containsEntry = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsEntrySignature, false); - addMethod(containsEntry, 0, patternMethods, 0); - addMethod(containsEntry, 1, patternMethods, 1); - - MethodSignature removeByKeyAndValueSignature = - MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeByKeyAndValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeByKeyAndValueSignature, false); - addMethod(removeByKeyAndValue, 0, patternMethods, 0); - addMethod(removeByKeyAndValue, 1, patternMethods, 1); - - MethodSignature removeAllSignature = - MethodSignatureUtil.createMethodSignature("removeAll", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod removeAll = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeAllSignature, false); - addMethod(removeAll, 0, patternMethods, 0); - } + PsiClass concurrentMapClass = javaPsiFacade.findClass(CommonClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP, searchScope); + if (concurrentMapClass != null) { + MethodSignature containsSignature = + MethodSignatureUtil.createMethodSignature("contains", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod contains = MethodSignatureUtil.findMethodBySignature(concurrentMapClass, containsSignature, false); + addMethod(contains, 1, patternMethods, 0); + } + PsiClass guavaTable = javaPsiFacade.findClass("com.google.common.collect.Table", searchScope); + if (guavaTable != null) { + MethodSignature getSignature = + MethodSignatureUtil.createMethodSignature("get", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod get = MethodSignatureUtil.findMethodBySignature(guavaTable, getSignature, false); + addMethod(get, 0, patternMethods, 0); + addMethod(get, 1, patternMethods, 1); + + MethodSignature containsSignature = + MethodSignatureUtil.createMethodSignature("contains", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod contains = MethodSignatureUtil.findMethodBySignature(guavaTable, containsSignature, false); + addMethod(contains, 0, patternMethods, 0); + addMethod(contains, 1, patternMethods, 1); + + MethodSignature containsRowSignature = MethodSignatureUtil.createMethodSignature( + "containsRow", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsRow = MethodSignatureUtil.findMethodBySignature(guavaTable, containsRowSignature, false); + addMethod(containsRow, 0, patternMethods, 0); + + MethodSignature containsColumnSignature = MethodSignatureUtil.createMethodSignature( + "containsColumn", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, containsColumnSignature, false); + addMethod(containsColumn, 1, patternMethods, 0); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaTable, containsValueSignature, false); + addMethod(containsValue, 2, patternMethods, 0); + + MethodSignature removeByRowAndColumnSignature = + MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeByRowAndColumn = MethodSignatureUtil.findMethodBySignature(guavaTable, removeByRowAndColumnSignature, false); + addMethod(removeByRowAndColumn, 0, patternMethods, 0); + addMethod(removeByRowAndColumn, 1, patternMethods, 1); + } - PsiClass guavaMultiset = javaPsiFacade.findClass("com.google.common.collect.Multiset", searchScope); - if (guavaMultiset != null) { - MethodSignature countSignature = - MethodSignatureUtil.createMethodSignature("count", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod count = MethodSignatureUtil.findMethodBySignature(guavaMultiset, countSignature, false); - addMethod(count, 0, patternMethods, 0); - } + PsiClass guavaMultimap = javaPsiFacade.findClass("com.google.common.collect.Multimap", searchScope); + if (guavaMultimap != null) { + MethodSignature containsKeySignature = MethodSignatureUtil.createMethodSignature( + "containsKey", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsKey = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsKeySignature, false); + addMethod(containsKey, 0, patternMethods, 0); + + MethodSignature containsValueSignature = MethodSignatureUtil.createMethodSignature( + "containsValue", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod containsValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsValueSignature, false); + addMethod(containsValue, 1, patternMethods, 0); + + MethodSignature containsEntrySignature = + MethodSignatureUtil.createMethodSignature("containsEntry", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod containsEntry = MethodSignatureUtil.findMethodBySignature(guavaMultimap, containsEntrySignature, false); + addMethod(containsEntry, 0, patternMethods, 0); + addMethod(containsEntry, 1, patternMethods, 1); + + MethodSignature removeByKeyAndValueSignature = + MethodSignatureUtil.createMethodSignature("remove", twoObjects, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeByKeyAndValue = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeByKeyAndValueSignature, false); + addMethod(removeByKeyAndValue, 0, patternMethods, 0); + addMethod(removeByKeyAndValue, 1, patternMethods, 1); + + MethodSignature removeAllSignature = + MethodSignatureUtil.createMethodSignature("removeAll", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod removeAll = MethodSignatureUtil.findMethodBySignature(guavaMultimap, removeAllSignature, false); + addMethod(removeAll, 0, patternMethods, 0); + } - PsiClass guavaCache = javaPsiFacade.findClass("com.google.common.cache.Cache", searchScope); - if (guavaCache != null) { - MethodSignature getIfPresentSignature = - MethodSignatureUtil.createMethodSignature("getIfPresent", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod getIfPresent = MethodSignatureUtil.findMethodBySignature(guavaCache, getIfPresentSignature, false); - addMethod(getIfPresent, 0, patternMethods, 0); - MethodSignature invalidateSignature = - MethodSignatureUtil.createMethodSignature("invalidate", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod invalidate = MethodSignatureUtil.findMethodBySignature(guavaCache, invalidateSignature, false); - addMethod(invalidate, 0, patternMethods, 0); - } - } - - @Contract(value = "null -> false", pure = true) - public static boolean isCollectionAcceptingMethod(@Nullable String name) { - return "removeAll".equals(name) || "retainAll".equals(name) || "containsAll".equals(name); - } - - - private static void addSingleParameterMethod(List patternMethods, - PsiClass methodClass, String methodName, PsiClassType parameterType) { - MethodSignature signature = MethodSignatureUtil - .createMethodSignature(methodName, new PsiType[]{parameterType}, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); - PsiMethod method = MethodSignatureUtil.findMethodBySignature(methodClass, signature, false); - addMethod(method, 0, patternMethods, 0); - } - - private static void addMethod(final PsiMethod patternMethod, - int typeParamIndex, - List patternMethods, - int argIdx) { - if (patternMethod != null) { - patternMethods.add(new PatternMethod(patternMethod, typeParamIndex, argIdx)); - } - } + PsiClass guavaMultiset = javaPsiFacade.findClass("com.google.common.collect.Multiset", searchScope); + if (guavaMultiset != null) { + MethodSignature countSignature = + MethodSignatureUtil.createMethodSignature("count", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod count = MethodSignatureUtil.findMethodBySignature(guavaMultiset, countSignature, false); + addMethod(count, 0, patternMethods, 0); + } - private static boolean isInheritorOrSelf(PsiMethod inheritorCandidate, PsiMethod base) { - PsiClass aClass = inheritorCandidate.getContainingClass(); - PsiClass bClass = base.getContainingClass(); - if (aClass == null || bClass == null) { - return false; - } - PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(bClass, aClass, PsiSubstitutor.EMPTY); - return substitutor != null && - MethodSignatureUtil.findMethodBySignature(bClass, inheritorCandidate.getSignature(substitutor), false) == base; - } - - @Nullable - public static String getSuspiciousMethodCallMessage(@Nonnull PsiMethodCallExpression methodCall, - PsiExpression arg, - PsiType argType, - boolean reportConvertibleMethodCalls, - @Nonnull List patternMethods, - int idx) { - final PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); - - if (arg instanceof PsiConditionalExpression && - argType != null && - argType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) && - PsiPolyExpressionUtil.isPolyExpression(arg)) { - return null; + PsiClass guavaCache = javaPsiFacade.findClass("com.google.common.cache.Cache", searchScope); + if (guavaCache != null) { + MethodSignature getIfPresentSignature = MethodSignatureUtil.createMethodSignature( + "getIfPresent", + javaLangObject, + PsiTypeParameter.EMPTY_ARRAY, + PsiSubstitutor.EMPTY + ); + PsiMethod getIfPresent = MethodSignatureUtil.findMethodBySignature(guavaCache, getIfPresentSignature, false); + addMethod(getIfPresent, 0, patternMethods, 0); + MethodSignature invalidateSignature = + MethodSignatureUtil.createMethodSignature("invalidate", javaLangObject, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod invalidate = MethodSignatureUtil.findMethodBySignature(guavaCache, invalidateSignature, false); + addMethod(invalidate, 0, patternMethods, 0); + } } - return getSuspiciousMethodCallMessage(methodExpression, argType, reportConvertibleMethodCalls, patternMethods, idx); - } - - @Nullable - public static String getSuspiciousMethodCallMessage(PsiReferenceExpression methodExpression, - PsiType argType, - boolean reportConvertibleMethodCalls, - @Nonnull List patternMethods, - int argIdx) { - final PsiExpression qualifier = methodExpression.getQualifierExpression(); - if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { - return null; + + @Contract(value = "null -> false", pure = true) + public static boolean isCollectionAcceptingMethod(@Nullable String name) { + return "removeAll".equals(name) || "retainAll".equals(name) || "containsAll".equals(name); } - if (argType instanceof PsiPrimitiveType) { - argType = ((PsiPrimitiveType) argType).getBoxedType(methodExpression); + + + private static void addSingleParameterMethod( + List patternMethods, + PsiClass methodClass, + String methodName, + PsiClassType parameterType + ) { + MethodSignature signature = MethodSignatureUtil + .createMethodSignature(methodName, new PsiType[]{parameterType}, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY); + PsiMethod method = MethodSignatureUtil.findMethodBySignature(methodClass, signature, false); + addMethod(method, 0, patternMethods, 0); } - if (argType == null) { - return null; + private static void addMethod( + PsiMethod patternMethod, + int typeParamIndex, + List patternMethods, + int argIdx + ) { + if (patternMethod != null) { + patternMethods.add(new PatternMethod(patternMethod, typeParamIndex, argIdx)); + } } - final JavaResolveResult resolveResult = methodExpression.advancedResolve(false); - PsiElement element = resolveResult.getElement(); - if (!(element instanceof PsiMethod)) { - return null; + private static boolean isInheritorOrSelf(PsiMethod inheritorCandidate, PsiMethod base) { + PsiClass aClass = inheritorCandidate.getContainingClass(); + PsiClass bClass = base.getContainingClass(); + if (aClass == null || bClass == null) { + return false; + } + PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(bClass, aClass, PsiSubstitutor.EMPTY); + return substitutor != null && + MethodSignatureUtil.findMethodBySignature(bClass, inheritorCandidate.getSignature(substitutor), false) == base; } - PsiMethod calleeMethod = (PsiMethod) element; - NullableLazyValue lazyContextMethod = NullableLazyValue.createValue(() -> PsiTreeUtil.getParentOfType(methodExpression, PsiMethod.class)); - - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (patternMethods) { - if (patternMethods.isEmpty()) { - setupPatternMethods(methodExpression.getManager(), methodExpression.getResolveScope(), patternMethods); - } + + @Nullable + @RequiredReadAction + public static String getSuspiciousMethodCallMessage( + PsiMethodCallExpression methodCall, + PsiExpression arg, + PsiType argType, + boolean reportConvertibleMethodCalls, + List patternMethods, + int idx + ) { + PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + + if (arg instanceof PsiConditionalExpression + && argType != null + && argType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) + && PsiPolyExpressionUtil.isPolyExpression(arg)) { + return null; + } + return getSuspiciousMethodCallMessage(methodExpression, argType, reportConvertibleMethodCalls, patternMethods, idx); } - for (PatternMethod patternMethod : patternMethods) { - PsiMethod method = patternMethod.patternMethod; - if (!method.getName().equals(methodExpression.getReferenceName())) { - continue; - } - if (patternMethod.argIdx != argIdx) { - continue; - } - - //we are in collections method implementation - PsiMethod contextMethod = lazyContextMethod.getValue(); - if (contextMethod != null && isInheritorOrSelf(contextMethod, method)) { - return null; - } - - final PsiClass calleeClass = calleeMethod.getContainingClass(); - PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - final PsiClass patternClass = method.getContainingClass(); - assert patternClass != null; - assert calleeClass != null; - substitutor = TypeConversionUtil.getClassSubstitutor(patternClass, calleeClass, substitutor); - if (substitutor == null) { - continue; - } - - if (!method.getSignature(substitutor).equals(calleeMethod.getSignature(resolveResult.getSubstitutor()))) { - continue; - } - - PsiTypeParameter[] typeParameters = patternClass.getTypeParameters(); - if (typeParameters.length <= patternMethod.typeParameterIdx) { - return null; - } - final PsiTypeParameter typeParameter = typeParameters[patternMethod.typeParameterIdx]; - PsiType typeParamMapping = substitutor.substitute(typeParameter); - if (typeParamMapping == null) { - return null; - } - - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length == 1 && ("removeAll".equals(method.getName()) || "retainAll".equals(method.getName()))) { - PsiType paramType = parameters[0].getType(); - if (InheritanceUtil.isInheritor(paramType, CommonClassNames.JAVA_UTIL_COLLECTION)) { - PsiType qualifierType = qualifier.getType(); - if (qualifierType != null) { - final PsiType itemType = JavaGenericsUtil.getCollectionItemType(argType, calleeMethod.getResolveScope()); - final PsiType qualifierItemType = JavaGenericsUtil.getCollectionItemType(qualifierType, calleeMethod.getResolveScope()); - if (qualifierItemType != null && itemType != null && !qualifierItemType.isAssignableFrom(itemType)) { - if (TypeUtils.isJavaLangObject(itemType) && hasNullCollectionArg(methodExpression)) { - // removeAll(Collections.singleton(null)) is a valid way to remove all nulls from collection + @Nullable + @RequiredReadAction + public static String getSuspiciousMethodCallMessage( + PsiReferenceExpression methodExpression, + PsiType argType, + boolean reportConvertibleMethodCalls, + List patternMethods, + int argIdx + ) { + PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier == null || qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { + return null; + } + if (argType instanceof PsiPrimitiveType primitiveType) { + argType = primitiveType.getBoxedType(methodExpression); + } + + if (argType == null) { + return null; + } + + JavaResolveResult resolveResult = methodExpression.advancedResolve(false); + PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiMethod calleeMethod)) { + return null; + } + NullableLazyValue lazyContextMethod = + NullableLazyValue.createValue(() -> PsiTreeUtil.getParentOfType(methodExpression, PsiMethod.class)); + + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (patternMethods) { + if (patternMethods.isEmpty()) { + setupPatternMethods(methodExpression.getManager(), methodExpression.getResolveScope(), patternMethods); + } + } + + for (PatternMethod patternMethod : patternMethods) { + PsiMethod method = patternMethod.patternMethod; + if (!method.getName().equals(methodExpression.getReferenceName())) { + continue; + } + if (patternMethod.argIdx != argIdx) { + continue; + } + + //we are in collections method implementation + PsiMethod contextMethod = lazyContextMethod.getValue(); + if (contextMethod != null && isInheritorOrSelf(contextMethod, method)) { + return null; + } + + PsiClass calleeClass = calleeMethod.getContainingClass(); + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + PsiClass patternClass = method.getContainingClass(); + assert patternClass != null; + assert calleeClass != null; + substitutor = TypeConversionUtil.getClassSubstitutor(patternClass, calleeClass, substitutor); + if (substitutor == null) { + continue; + } + + if (!method.getSignature(substitutor).equals(calleeMethod.getSignature(resolveResult.getSubstitutor()))) { + continue; + } + + PsiTypeParameter[] typeParameters = patternClass.getTypeParameters(); + if (typeParameters.length <= patternMethod.typeParameterIdx) { return null; - } - if (qualifierItemType.isConvertibleFrom(itemType) && !reportConvertibleMethodCalls) { + } + PsiTypeParameter typeParameter = typeParameters[patternMethod.typeParameterIdx]; + PsiType typeParamMapping = substitutor.substitute(typeParameter); + if (typeParamMapping == null) { return null; - } - return InspectionsBundle.message("inspection.suspicious.collections.method.calls.problem.descriptor", - PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), - PsiFormatUtil.formatType(itemType, 0, PsiSubstitutor.EMPTY), - "objects"); } - } - return null; - } - } - - String message = null; - if (typeParamMapping instanceof PsiCapturedWildcardType) { - typeParamMapping = ((PsiCapturedWildcardType) typeParamMapping).getWildcard(); - } - if (!typeParamMapping.isAssignableFrom(argType)) { - if (typeParamMapping.isConvertibleFrom(argType)) { - if (reportConvertibleMethodCalls) { - message = InspectionsBundle.message("inspection.suspicious.collections.method.calls.problem.descriptor1", - PsiFormatUtil.formatMethod(calleeMethod, substitutor, - PsiFormatUtilBase.SHOW_NAME | - PsiFormatUtilBase.SHOW_CONTAINING_CLASS, - PsiFormatUtilBase.SHOW_TYPE)); - } - } else { - PsiType qualifierType = qualifier.getType(); - if (qualifierType != null) { - message = InspectionsBundle.message("inspection.suspicious.collections.method.calls.problem.descriptor", - PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), - PsiFormatUtil.formatType(argType, 0, PsiSubstitutor.EMPTY), - getPreciseObjectTitle(patternClass, patternMethod.typeParameterIdx)); - } + + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length == 1 && ("removeAll".equals(method.getName()) || "retainAll".equals(method.getName()))) { + PsiType paramType = parameters[0].getType(); + if (InheritanceUtil.isInheritor(paramType, CommonClassNames.JAVA_UTIL_COLLECTION)) { + PsiType qualifierType = qualifier.getType(); + if (qualifierType != null) { + PsiType itemType = JavaGenericsUtil.getCollectionItemType(argType, calleeMethod.getResolveScope()); + PsiType qualifierItemType = + JavaGenericsUtil.getCollectionItemType(qualifierType, calleeMethod.getResolveScope()); + if (qualifierItemType != null && itemType != null && !qualifierItemType.isAssignableFrom(itemType)) { + if (TypeUtils.isJavaLangObject(itemType) && hasNullCollectionArg(methodExpression)) { + // removeAll(Collections.singleton(null)) is a valid way to remove all nulls from collection + return null; + } + if (qualifierItemType.isConvertibleFrom(itemType) && !reportConvertibleMethodCalls) { + return null; + } + return InspectionsBundle.message( + "inspection.suspicious.collections.method.calls.problem.descriptor", + PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), + PsiFormatUtil.formatType(itemType, 0, PsiSubstitutor.EMPTY), + "objects" + ); + } + } + return null; + } + } + + String message = null; + if (typeParamMapping instanceof PsiCapturedWildcardType capturedWildcardType) { + typeParamMapping = capturedWildcardType.getWildcard(); + } + if (!typeParamMapping.isAssignableFrom(argType)) { + if (typeParamMapping.isConvertibleFrom(argType)) { + if (reportConvertibleMethodCalls) { + message = InspectionLocalize.inspectionSuspiciousCollectionsMethodCallsProblemDescriptor1( + PsiFormatUtil.formatMethod( + calleeMethod, + substitutor, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_CONTAINING_CLASS, + PsiFormatUtilBase.SHOW_TYPE + ) + ).get(); + } + } + else { + PsiType qualifierType = qualifier.getType(); + if (qualifierType != null) { + message = InspectionsBundle.message( + "inspection.suspicious.collections.method.calls.problem.descriptor", + PsiFormatUtil.formatType(qualifierType, 0, PsiSubstitutor.EMPTY), + PsiFormatUtil.formatType(argType, 0, PsiSubstitutor.EMPTY), + getPreciseObjectTitle(patternClass, patternMethod.typeParameterIdx) + ); + } + } + } + return message; } - } - return message; + return null; } - return null; - } - private static String getPreciseObjectTitle(PsiClass patternClass, int index) { - if (InheritanceUtil.isInheritor(patternClass, CommonClassNames.JAVA_UTIL_MAP)) { - return index == 0 ? "keys" : "values"; + private static String getPreciseObjectTitle(PsiClass patternClass, int index) { + if (InheritanceUtil.isInheritor(patternClass, CommonClassNames.JAVA_UTIL_MAP)) { + return index == 0 ? "keys" : "values"; + } + + return "objects"; } - return "objects"; - } - - private static boolean hasNullCollectionArg(PsiReferenceExpression methodExpression) { - PsiMethodCallExpression call = ObjectUtil.tryCast(methodExpression.getParent(), PsiMethodCallExpression.class); - if (call != null) { - PsiExpression arg = - ExpressionUtils.resolveExpression(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); - PsiMethodCallExpression argCall = - ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class); - return SINGLETON_COLLECTION.test(argCall) && ExpressionUtils.isNullLiteral(argCall.getArgumentList().getExpressions()[0]); + @RequiredReadAction + private static boolean hasNullCollectionArg(PsiReferenceExpression methodExpression) { + PsiMethodCallExpression call = ObjectUtil.tryCast(methodExpression.getParent(), PsiMethodCallExpression.class); + if (call != null) { + PsiExpression arg = + ExpressionUtils.resolveExpression(ArrayUtil.getFirstElement(call.getArgumentList().getExpressions())); + PsiMethodCallExpression argCall = + ObjectUtil.tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class); + return SINGLETON_COLLECTION.test(argCall) && ExpressionUtils.isNullLiteral(argCall.getArgumentList().getExpressions()[0]); + } + return false; } - return false; - } - - public static class PatternMethod { - PsiMethod patternMethod; - int typeParameterIdx; - int argIdx; - - PatternMethod(PsiMethod patternMethod, int typeParameterIdx, int argIdx) { - this.patternMethod = patternMethod; - this.typeParameterIdx = typeParameterIdx; - this.argIdx = argIdx; + + public static class PatternMethod { + PsiMethod patternMethod; + int typeParameterIdx; + int argIdx; + + PatternMethod(PsiMethod patternMethod, int typeParameterIdx, int argIdx) { + this.patternMethod = patternMethod; + this.typeParameterIdx = typeParameterIdx; + this.argIdx = argIdx; + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/AnnotateOverriddenMethodParameterFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/AnnotateOverriddenMethodParameterFix.java index d221ab1a7d..9472b5157c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/AnnotateOverriddenMethodParameterFix.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/AnnotateOverriddenMethodParameterFix.java @@ -1,5 +1,6 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2013-2026 consulo.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,88 +16,97 @@ */ package com.intellij.java.analysis.impl.codeInspection.nullable; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.Nonnull; - -import com.intellij.java.language.codeInsight.AnnotationUtil; +import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationPsiFix; +import com.intellij.java.indexing.search.searches.OverridingMethodsSearch; +import com.intellij.java.language.codeInsight.Nullability; +import com.intellij.java.language.codeInsight.NullabilityAnnotationInfo; +import com.intellij.java.language.codeInsight.NullableNotNullManager; +import com.intellij.java.language.psi.PsiMethod; +import com.intellij.java.language.psi.PsiParameter; +import consulo.application.ReadAction; +import consulo.application.progress.ProgressManager; +import consulo.java.analysis.localize.JavaAnalysisLocalize; import consulo.language.editor.FileModificationService; -import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationFix; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.logging.Logger; -import consulo.project.Project; -import consulo.language.psi.PsiElement; -import com.intellij.java.language.psi.PsiMethod; -import com.intellij.java.language.psi.PsiParameter; -import consulo.language.psi.scope.GlobalSearchScope; -import com.intellij.java.indexing.search.searches.OverridingMethodsSearch; -import com.intellij.java.language.psi.util.ClassUtil; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; import consulo.util.collection.ArrayUtil; -import consulo.language.util.IncorrectOperationException; -/** - * @author cdr - */ +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + public class AnnotateOverriddenMethodParameterFix implements LocalQuickFix { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.AnnotateMethodFix"); - private final String myAnnotation; - private final String[] myAnnosToRemove; + private final Nullability myTargetNullability; - public AnnotateOverriddenMethodParameterFix(final String fqn, String... annosToRemove) { - myAnnotation = fqn; - myAnnosToRemove = annosToRemove; - } + AnnotateOverriddenMethodParameterFix(Nullability targetNullability) { + myTargetNullability = targetNullability; + } - @Override - @Nonnull - public String getName() { - return InspectionsBundle.message("annotate.overridden.methods.parameters", ClassUtil.extractClassName(myAnnotation)); - } + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.annotateOverriddenMethodsParameters( + myTargetNullability == Nullability.NOT_NULL ? "NotNull" : "Nullable" + ); + } - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - final PsiElement psiElement = descriptor.getPsiElement(); + @Override + public boolean startInWriteAction() { + return false; + } - PsiParameter parameter = PsiTreeUtil.getParentOfType(psiElement, PsiParameter.class, false); - if (parameter == null) return; - PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class); - if (method == null) return; - PsiParameter[] parameters = method.getParameterList().getParameters(); - int index = ArrayUtil.find(parameters, parameter); + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + List toAnnotate = new ArrayList<>(); - List toAnnotate = new ArrayList(); + PsiParameter parameter = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiParameter.class, false); + if (parameter == null || !processParameterInheritorsUnderProgress(parameter, toAnnotate::add)) { + return; + } - PsiMethod[] methods = OverridingMethodsSearch.search(method, GlobalSearchScope.allScope(project), true).toArray(PsiMethod.EMPTY_ARRAY); - for (PsiMethod psiMethod : methods) { - PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters(); - if (index >= psiParameters.length) continue; - PsiParameter psiParameter = psiParameters[index]; - if (!AnnotationUtil.isAnnotated(psiParameter, myAnnotation, false, false) && psiMethod.getManager().isInProject(psiMethod)) { - toAnnotate.add(psiParameter); - } + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate); + for (PsiParameter psiParam : toAnnotate) { + assert psiParam != null : toAnnotate; + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(psiParam); + if (info != null && info.getNullability() == myTargetNullability && + info.getInheritedFrom() == null && !info.isInferred()) { + continue; + } + AddAnnotationPsiFix fix = myTargetNullability == Nullability.NOT_NULL + ? AddAnnotationPsiFix.createAddNotNullFix(psiParam) + : AddAnnotationPsiFix.createAddNullableFix(psiParam); + if (fix != null) { + fix.invoke(project, psiParam.getContainingFile(), psiParam, psiParam); + } + } } - FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate); - for (PsiParameter psiParam : toAnnotate) { - try { - assert psiParam != null : toAnnotate; - if (AnnotationUtil.isAnnotatingApplicable(psiParam, myAnnotation)) { - new AddAnnotationFix(myAnnotation, psiParam, myAnnosToRemove).invoke(project, null, psiParam.getContainingFile()); - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } + public static boolean processParameterInheritorsUnderProgress(PsiParameter parameter, Consumer consumer) { + PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class); + if (method == null) return false; + PsiParameter[] parameters = method.getParameterList().getParameters(); + int index = ArrayUtil.find(parameters, parameter); + + return processModifiableInheritorsUnderProgress(method, psiMethod -> { + PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters(); + if (index < psiParameters.length) { + consumer.accept(psiParameters[index]); + } + }); } - } - @Override - @Nonnull - public String getFamilyName() { - return getName(); - } + public static boolean processModifiableInheritorsUnderProgress(PsiMethod method, Consumer consumer) { + return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + for (PsiMethod psiMethod : OverridingMethodsSearch.search(method).findAll()) { + ReadAction.run(() -> { + if (psiMethod.isPhysical() && !NullableStuffInspectionBase.shouldSkipOverriderAsGenerated(psiMethod)) { + consumer.accept(psiMethod); + } + }); + } + }, JavaAnalysisLocalize.searchingForOverridingMethods(), true, method.getProject()); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/ChangeNullableDefaultsFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/ChangeNullableDefaultsFix.java deleted file mode 100644 index e3fc93161b..0000000000 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/ChangeNullableDefaultsFix.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2000-2013 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.java.analysis.impl.codeInspection.nullable; - -import javax.annotation.Nonnull; - -import com.intellij.java.language.codeInsight.NullableNotNullManager; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import com.intellij.java.language.psi.PsiAnnotation; - -/** -* User: anna -* Date: 2/22/13 -*/ -class ChangeNullableDefaultsFix implements LocalQuickFix { - private final NullableNotNullManager myManager; - private final String myNotNullName; - private final String myNullableName; - - public ChangeNullableDefaultsFix(PsiAnnotation notNull, PsiAnnotation nullable, NullableNotNullManager manager) { - myNotNullName = notNull != null ? notNull.getQualifiedName() : null; - myNullableName = nullable != null ? nullable.getQualifiedName() : null; - myManager = manager; - } - - ChangeNullableDefaultsFix(String notNull, String nullable, NullableNotNullManager manager) { - myManager = manager; - myNotNullName = notNull; - myNullableName = nullable; - } - - @Nonnull - @Override - public String getName() { - return "Make \"" + (myNotNullName != null ? myNotNullName : myNullableName) + "\" default annotation"; - } - - @Nonnull - @Override - public String getFamilyName() { - return getName(); - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - if (myNotNullName != null) { - myManager.setDefaultNotNull(myNotNullName); - } else { - myManager.setDefaultNullable(myNullableName); - } - } -} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToArrayFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToArrayFix.java new file mode 100644 index 0000000000..6c3815c761 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToArrayFix.java @@ -0,0 +1,50 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/* + * Copyright 2013-2026 consulo.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import com.intellij.java.language.psi.*; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.util.lang.ObjectUtil; + +public class MoveAnnotationToArrayFix implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.intentionFamilyNameMoveAnnotationToArray(); + } + + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiAnnotation annotation = ObjectUtil.tryCast(descriptor.getPsiElement(), PsiAnnotation.class); + if (annotation == null) return; + String qualifiedName = annotation.getQualifiedName(); + if (qualifiedName == null) return; + PsiModifierList owner = ObjectUtil.tryCast(annotation.getOwner(), PsiModifierList.class); + if (owner == null) return; + PsiModifierListOwner member = ObjectUtil.tryCast(owner.getParent(), PsiModifierListOwner.class); + PsiTypeElement typeElement = member instanceof PsiMethod method ? method.getReturnTypeElement() + : member instanceof PsiVariable variable ? variable.getTypeElement() : null; + if (typeElement == null || !(typeElement.getType() instanceof PsiArrayType)) return; + PsiAnnotation addedAnnotation = typeElement.addAnnotation(qualifiedName); + addedAnnotation.replace(annotation); + annotation.delete(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToBoundFix.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToBoundFix.java new file mode 100644 index 0000000000..14fc7ed48d --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/MoveAnnotationToBoundFix.java @@ -0,0 +1,72 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +/* + * Copyright 2013-2026 consulo.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.language.editor.inspection.LocalQuickFix; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; + +public class MoveAnnotationToBoundFix implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.intentionFamilyNameMoveAnnotationToUpperBound(); + } + + @Override + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiAnnotation annotation = ObjectUtil.tryCast(descriptor.getPsiElement(), PsiAnnotation.class); + if (annotation == null) return; + String qualifiedName = annotation.getQualifiedName(); + if (qualifiedName == null) return; + PsiWildcardType owner = ObjectUtil.tryCast(annotation.getOwner(), PsiWildcardType.class); + if (owner == null) return; + PsiTypeElement typeElement = ObjectUtil.tryCast(annotation.getParent(), PsiTypeElement.class); + if (typeElement == null) return; + StringBuilder newText = new StringBuilder(); + boolean added = false; + for (PsiElement child : typeElement.getChildren()) { + if (child == annotation) continue; + newText.append(child.getText()); + if (PsiUtil.isJavaToken(child, JavaTokenType.EXTENDS_KEYWORD) || PsiUtil.isJavaToken(child, JavaTokenType.SUPER_KEYWORD)) { + newText.append(" ").append(annotation.getText()); + added = true; + } + } + if (!added) { + newText.append(" extends ").append(annotation.getText()).append(" ").append(CommonClassNames.JAVA_LANG_OBJECT); + } + PsiTypeElement newTypeElement = JavaPsiFacade.getElementFactory(project).createTypeElementFromText(newText.toString().trim(), typeElement); + typeElement.replace(newTypeElement); + } + + static @Nullable MoveAnnotationToBoundFix create(PsiAnnotation annotation) { + PsiWildcardType owner = ObjectUtil.tryCast(annotation.getOwner(), PsiWildcardType.class); + if (owner == null) return null; + PsiType bound = owner.getBound(); + String qualifiedName = annotation.getQualifiedName(); + if (qualifiedName == null) return null; + if (bound != null && bound.hasAnnotation(qualifiedName)) return null; + return new MoveAnnotationToBoundFix(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullabilityAnnotationWrapper.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullabilityAnnotationWrapper.java new file mode 100644 index 0000000000..32ea42b9dc --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullabilityAnnotationWrapper.java @@ -0,0 +1,103 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import com.intellij.java.language.codeInsight.*; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import com.intellij.java.language.util.JavaTypeNullabilityUtil; +import consulo.language.psi.PsiElement; +import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/// +/// Encapsulates information related to a nullability annotation. +/// +@NullMarked +public final class NullabilityAnnotationWrapper { + private final NullabilityAnnotationInfo info; + + private NullabilityAnnotationWrapper(NullabilityAnnotationInfo info) { + this.info = info; + } + + /// Constructs a NullabilityAnnotationWrapper for a given PsiAnnotation + /// + /// @return a NullabilityAnnotationWrapper instance containing nullability and annotation details, + /// or null if the annotation's nullability cannot be determined + public static @Nullable NullabilityAnnotationWrapper from(PsiAnnotation annotation) { + TypeNullability typeNullability = JavaTypeNullabilityUtil.getNullabilityFromAnnotations(new PsiAnnotation[]{annotation}); + NullabilityAnnotationInfo info = typeNullability.toNullabilityAnnotationInfo(); + if (info == null) return null; + return new NullabilityAnnotationWrapper(info); + } + + /// Evaluates whether the wrapped annotation is redundant within the scope of a container annotation + /// and returns that container nullability annotation info. Otherwise, returns null. + public @Nullable NullabilityAnnotationInfo findContainerInfoForRedundantAnnotation() { + PsiModifierListOwner listOwner = listOwner(); + if (listOwner != null && targetType() != null) { + NullabilityAnnotationInfo info = manager().findContainerAnnotation(listOwner); + return isRedundantInContainerScope(info) ? info : null; + } + else { + PsiType type = type(); + if (type != null) { + PsiElement context = type instanceof PsiClassType classType ? classType.getPsiContext() : info.getAnnotation(); + if (context != null) { + NullabilityAnnotationInfo info = manager().findDefaultTypeUseNullability(context); + return isRedundantInContainerScope(info) ? info : null; + } + } + } + return null; + } + + private boolean isRedundantInContainerScope(@Nullable NullabilityAnnotationInfo containerInfo) { + return containerInfo != null && + !containerInfo.getAnnotation().equals(info.getAnnotation()) && + containerInfo.getNullability() == info.getNullability(); + } + + /// @return modifier list owner + public @Nullable PsiModifierListOwner listOwner() { + return annotation().getOwner() instanceof PsiModifierList modifierList + ? ObjectUtil.tryCast(modifierList.getParent(), PsiModifierListOwner.class) + : null; + } + + /// @return related type of wrapped annotation + public @Nullable PsiType type() { + return AnnotationUtil.getRelatedType(annotation()); + } + + /// @return target type of wrapped annotation + public @Nullable PsiType targetType() { + PsiModifierListOwner listOwner = listOwner(); + return listOwner == null ? null : PsiUtil.getTypeByPsiElement(listOwner); + } + + /// @return qualified name of wrapped annotation + public @Nullable String qualifiedName() { + return annotation().getQualifiedName(); + } + + /// @return wrapped annotation owner + public @Nullable PsiAnnotationOwner owner() { + return annotation().getOwner(); + } + + /// @return nullability represented by wrapped annotation + public Nullability nullability() { + return info.getNullability(); + } + + /// @return wrapped annotation + public PsiAnnotation annotation() { + return info.getAnnotation(); + } + + private NullableNotNullManager manager() { + return NullableNotNullManager.getInstance(info.getAnnotation().getProject()); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableNotNullDialogProxy.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableNotNullDialogProxy.java new file mode 100644 index 0000000000..d8d55a1e87 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableNotNullDialogProxy.java @@ -0,0 +1,15 @@ +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import consulo.annotation.component.ComponentScope; +import consulo.annotation.component.ServiceAPI; +import consulo.ui.Button; + + +/** + * @author VISTALL + * @since 07/09/2023 + */ +@ServiceAPI(ComponentScope.APPLICATION) +public interface NullableNotNullDialogProxy { + Button createConfigureAnnotationsButton(); +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java index 502e626905..d3fc95fce6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionBase.java @@ -2,1059 +2,1777 @@ package com.intellij.java.analysis.impl.codeInspection.nullable; import com.intellij.java.analysis.codeInspection.AbstractBaseJavaLocalInspectionTool; -import com.intellij.java.analysis.codeInspection.GroupNames; +import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationFix; +import com.intellij.java.analysis.impl.codeInsight.daemon.impl.quickfix.MoveAnnotationOnStaticMemberQualifyingTypeFix; import com.intellij.java.analysis.impl.codeInsight.intention.AddAnnotationPsiFix; -import com.intellij.java.analysis.impl.codeInspection.AnnotateMethodFix; +import com.intellij.java.analysis.impl.codeInsight.intention.AddTypeAnnotationFix; import com.intellij.java.analysis.impl.codeInspection.RemoveAnnotationQuickFix; import com.intellij.java.analysis.impl.codeInspection.dataFlow.DfaPsiUtil; import com.intellij.java.analysis.impl.codeInspection.dataFlow.instructions.MethodCallInstruction; import com.intellij.java.analysis.impl.psi.impl.search.JavaNullMethodArgumentUtil; import com.intellij.java.indexing.impl.search.JavaOverridingMethodsSearcher; import com.intellij.java.indexing.search.searches.OverridingMethodsSearch; +import com.intellij.java.language.JavaFeature; import com.intellij.java.language.codeInsight.*; -import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; +import com.intellij.java.language.impl.psi.impl.PsiImplUtil; +import com.intellij.java.language.impl.psi.util.JavaPsiRecordUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import com.intellij.java.language.psi.codeStyle.VariableKind; import com.intellij.java.language.psi.util.*; +import com.intellij.java.language.util.JavaTypeNullabilityUtil; +import com.siyeh.ig.psiutils.TypeUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.registry.Registry; -import consulo.language.editor.inspection.InspectionsBundle; -import consulo.language.editor.inspection.LocalQuickFix; -import consulo.language.editor.inspection.ProblemHighlightType; -import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiElementVisitor; -import consulo.language.psi.PsiFile; -import consulo.language.psi.SyntaxTraverser; +import consulo.java.analysis.localize.JavaAnalysisLocalize; +import consulo.language.editor.FileModificationService; +import consulo.language.editor.inspection.*; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.language.editor.util.LanguageUndoUtil; +import consulo.language.psi.*; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; import consulo.project.content.GeneratedSourcesFilter; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ArrayUtil; import consulo.util.collection.ContainerUtil; -import consulo.util.lang.Couple; -import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import consulo.util.xml.serializer.WriteExternalException; import consulo.virtualFileSystem.VirtualFile; -import org.jdom.Element; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; -import static com.intellij.java.language.codeInsight.AnnotationUtil.CHECK_HIERARCHY; -import static com.intellij.java.language.codeInsight.AnnotationUtil.CHECK_TYPE; +import static com.intellij.java.language.codeInsight.AnnotationUtil.*; import static com.intellij.java.language.patterns.PsiJavaPatterns.psiElement; import static com.intellij.java.language.patterns.PsiJavaPatterns.psiMethod; +import static consulo.util.lang.ObjectUtil.tryCast; -public abstract class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspectionTool { - // deprecated fields remain to minimize changes to users inspection profiles (which are often located in version control). - @Deprecated - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL = true; - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = true; - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = true; - @Deprecated - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true; - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOT_ANNOTATED_GETTER = true; - @SuppressWarnings({"WeakerAccess"}) - public boolean IGNORE_EXTERNAL_SUPER_NOTNULL; - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED; - @Deprecated - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true; - @Deprecated - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test - @SuppressWarnings({"WeakerAccess"}) - public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true; - public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; - - private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class); - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - super.writeSettings(node); - for (Element child : new ArrayList<>(node.getChildren())) { - String name = child.getAttributeValue("name"); - String value = child.getAttributeValue("value"); - if ("IGNORE_EXTERNAL_SUPER_NOTNULL".equals(name) && "false".equals(value) || - "REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED".equals(name) && "false".equals(value) || - "REQUIRE_NOTNULL_FIELDS_INITIALIZED".equals(name) && "true".equals(value) || - "REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER".equals(name) && "true".equals(value)) { - node.removeContent(child); - } - } - } - - @Override - @Nonnull - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - final PsiFile file = holder.getFile(); - if (!PsiUtil.isLanguageLevel5OrHigher(file) || nullabilityAnnotationsNotAvailable(file)) { - return PsiElementVisitor.EMPTY_VISITOR; - } - return new JavaElementVisitor() { - @Override - public void visitMethod(PsiMethod method) { - checkNullableStuffForMethod(method, holder, isOnTheFly); - } - - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - checkMethodReference(expression, holder); - - JavaResolveResult result = expression.advancedResolve(false); - PsiElement target = result.getElement(); - if (target instanceof PsiMethod) { - checkCollectionNullityOnAssignment(expression, - LambdaUtil.getFunctionalInterfaceReturnType(expression), - result.getSubstitutor().substitute(((PsiMethod) target).getReturnType())); - } - } - - @Override - public void visitField(PsiField field) { - final PsiType type = field.getType(); - final Annotated annotated = check(field, holder, type); - if (TypeConversionUtil.isPrimitiveAndNotNull(type)) { - return; +public abstract class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspectionTool { + private static Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class); + + @Override + @RequiredReadAction + public PsiElementVisitor buildVisitorImpl( + final ProblemsHolder holder, + final boolean isOnTheFly, + LocalInspectionToolSession session, + NullableStuffInspectionState state + ) { + PsiFile file = holder.getFile(); + if (!PsiUtil.isAvailable(JavaFeature.ANNOTATIONS, file) || nullabilityAnnotationsNotAvailable(file)) { + return PsiElementVisitor.EMPTY_VISITOR; } - Project project = holder.getProject(); - final NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { - final String anno = annotated.isDeclaredNotNull ? manager.getDefaultNotNull() : manager.getDefaultNullable(); - final List annoToRemove = annotated.isDeclaredNotNull ? manager.getNullables() : manager.getNotNulls(); + return new JavaElementVisitor() { + private NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject()); + private List nullables = manager.getNullables(); + private List notNulls = manager.getNotNulls(); + + @Override + @RequiredReadAction + public void visitMethod(PsiMethod method) { + checkNullableStuffForMethod(method, holder, state); + } - if (!checkNonStandardAnnotations(field, annotated, manager, anno, holder)) { - return; - } + @Override + public void visitClass(PsiClass aClass) { + if (aClass.isRecord()) { + PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(aClass); + if (constructor instanceof SyntheticElement) { + checkParameters(constructor, holder, List.of(), manager, state); + } + } + checkConflictingContainerAnnotations(holder, aClass.getModifierList()); + } - checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder); + @Override + public void visitPackageStatement(PsiPackageStatement statement) { + checkConflictingContainerAnnotations(holder, statement.getAnnotationList()); + } - checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder); - } - } - - @Override - public void visitParameter(PsiParameter parameter) { - check(parameter, holder, parameter.getType()); - } - - @Override - public void visitTypeElement(PsiTypeElement type) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(type.getProject()); - List annotations = getExclusiveAnnotations(type); - - checkType(null, holder, type.getType(), - ContainerUtil.find(annotations, a -> manager.getNotNulls().contains(a.getQualifiedName())), - ContainerUtil.find(annotations, a -> manager.getNullables().contains(a.getQualifiedName()))); - } - - private List getExclusiveAnnotations(PsiTypeElement type) { - List annotations = ContainerUtil.newArrayList(type.getAnnotations()); - PsiTypeElement topMost = Objects.requireNonNull(SyntaxTraverser.psiApi().parents(type).filter(PsiTypeElement.class).last()); - PsiElement parent = topMost.getParent(); - if (parent instanceof PsiModifierListOwner && type.getType().equals(topMost.getType().getDeepComponentType())) { - PsiModifierList modifierList = ((PsiModifierListOwner) parent).getModifierList(); - if (modifierList != null) { - PsiAnnotation.TargetType[] targets = ArrayUtil.remove(AnnotationTargetUtil.getTargetsForLocation(modifierList), PsiAnnotation.TargetType.TYPE_USE); - annotations.addAll(ContainerUtil.filter(modifierList.getAnnotations(), - a -> AnnotationTargetUtil.isTypeAnnotation(a) && AnnotationTargetUtil.findAnnotationTarget(a, targets) == null)); - } - } - return annotations; - } + @Override + public void visitModule(PsiJavaModule module) { + checkConflictingContainerAnnotations(holder, module.getModifierList()); + } - @Override - public void visitAnnotation(PsiAnnotation annotation) { - if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) { - return; - } + @Override + @RequiredReadAction + public void visitNewExpression(PsiNewExpression expression) { + if (expression.isArrayCreation()) { + return; + } + super.visitNewExpression(expression); + PsiJavaCodeReferenceElement ref = expression.getClassOrAnonymousClassReference(); + if (ref == null) { + return; + } + if (!(ref.resolve() instanceof PsiClass cls)) { + return; + } + String qualifiedName = cls.getQualifiedName(); + if (qualifiedName == null) { + return; + } + PsiExpressionList list = expression.getArgumentList(); + if (list == null) { + return; + } + if (!(expression.getType() instanceof PsiClassType type) || type.getParameterCount() != 1) { + return; + } + PsiType typeParameter = type.getParameters()[0]; + if (!(typeParameter instanceof PsiClassType classType) || + DfaPsiUtil.getTypeNullability(typeParameter) != Nullability.NOT_NULL) { + return; + } + boolean matched = switch (qualifiedName) { + case "java.util.concurrent.atomic.AtomicReference" -> list.getExpressionCount() == 0; + case "java.util.concurrent.atomic.AtomicReferenceArray" -> + list.getExpressionCount() == 1 && PsiTypes.intType().equals(list.getExpressionTypes()[0]); + case "java.lang.ThreadLocal" -> list.getExpressionCount() == 0 && expression.getAnonymousClass() == null; + default -> false; + }; + if (!matched) { + return; + } + + AddTypeAnnotationFix fix = null; + if (classType.getPsiContext() instanceof PsiJavaCodeReferenceElement typeRef + && typeRef.getParent() instanceof PsiTypeElement typeElement + && typeElement.getType().equals(classType) + && typeElement.acceptsAnnotations()) { + fix = new AddTypeAnnotationFix(typeElement, manager.getDefaultAnnotation(Nullability.NULLABLE, expression), notNulls); + } + holder.newProblem(JavaAnalysisLocalize.inspectionNullableProblemsConstructorNotCompatibleNonNullTypeArgument()) + .range(expression) + .withOptionalFix(fix) + .create(); + } + + @Override + @RequiredReadAction + public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + checkMethodReference(expression, holder, state); + + JavaResolveResult result = expression.advancedResolve(false); + if (result.getElement() instanceof PsiMethod method) { + checkNestedGenericClasses( + holder, + expression, + LambdaUtil.getFunctionalInterfaceReturnType(expression), + result.getSubstitutor().substitute(method.getReturnType()), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM, + state + ); + } + } + + @Override + @RequiredReadAction + public void visitField(PsiField field) { + PsiType type = field.getType(); + Annotated annotated = check(field, holder, type); + if (TypeConversionUtil.isPrimitiveAndNotNull(type)) { + return; + } + Project project = holder.getProject(); + if (annotated.isDeclaredNotNull ^ annotated.isDeclaredNullable) { + String anno = + manager.getDefaultAnnotation(annotated.isDeclaredNotNull ? Nullability.NOT_NULL : Nullability.NULLABLE, field); + List annoToRemove = annotated.isDeclaredNotNull ? nullables : notNulls; + + checkAccessors(field, annotated, project, manager, anno, annoToRemove, holder, state); + + checkConstructorParameters(field, annotated, anno, annoToRemove, holder, state); + } + PsiExpression initializer = field.getInitializer(); + PsiElement identifyingElement = field.getIdentifyingElement(); + if (initializer != null && identifyingElement != null) { + checkNestedGenericClasses(holder, identifyingElement, field.getType(), initializer.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM, state + ); + } + } - PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception"); - if (value instanceof PsiClassObjectAccessExpression) { - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(((PsiClassObjectAccessExpression) value).getOperand().getType()); - if (psiClass != null && !hasStringConstructor(psiClass)) { - holder.registerProblem(value, - "Custom exception class should have a constructor with a single message parameter of String type", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + @Override + @RequiredReadAction + public void visitMethodCallExpression(PsiMethodCallExpression call) { + super.visitMethodCallExpression(call); + PsiReferenceParameterList parameterList = call.getMethodExpression().getParameterList(); + if (parameterList == null) { + return; + } + PsiType[] parameterization = parameterList.getTypeArguments(); + if (parameterization.length == 0) { + return; + } + PsiMethod method = call.resolveMethod(); + if (method == null) { + return; + } + PsiTypeParameter[] typeParameters = method.getTypeParameters(); + if (typeParameters.length != parameterization.length) { + return; + } + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeParameter typeParameter = typeParameters[i]; + PsiType instance = parameterization[i]; + TypeNullability nullability = TypeNullability.ofTypeParameter(typeParameter); + if (nullability.nullability() != Nullability.NOT_NULL) { + continue; + } + TypeNullability instanceNullability = instance.getNullability(); + if (instanceNullability.nullability() == Nullability.NOT_NULL) { + continue; + } + NullabilitySource source = instanceNullability.source(); + if (source instanceof NullabilitySource.ExplicitAnnotation explicit) { + PsiAnnotation anchor = explicit.annotation(); + PsiJavaCodeReferenceElement ref = anchor.getNameReferenceElement(); + if (ref != null) { + reportProblem( + holder, + anchor, + JavaAnalysisLocalize.inspectionNullableProblemsNullableInstantiationOfNotnull( + typeParameter.getName(), + ref.getReferenceName() + ) + ); + } + } + else if (source instanceof NullabilitySource.ContainerAnnotation container) { + PsiElement anchor = parameterList.getTypeParameterElements()[i]; + PsiJavaCodeReferenceElement ref = container.annotation().getNameReferenceElement(); + if (ref != null) { + reportProblem( + holder, + anchor, + JavaAnalysisLocalize.inspectionNullableProblemsNullableInstantiationOfNotnullContainer( + typeParameter.getName(), + ref.getReferenceName() + ) + ); + } + } + } + } + + @Override + @RequiredReadAction + public void visitParameter(PsiParameter parameter) { + check(parameter, holder, parameter.getType()); + } - } + @Override + @RequiredReadAction + public void visitAnnotation(PsiAnnotation annotation) { + NullabilityAnnotationWrapper wrapper = NullabilityAnnotationWrapper.from(annotation); + if (wrapper == null) { + return; + } + PsiType targetType = wrapper.targetType(); + PsiType type = wrapper.type(); + PsiModifierListOwner listOwner = wrapper.listOwner(); + checkRedundantInContainerScope(wrapper, state); + if (type != null + && wrapper.nullability() == Nullability.NOT_NULL + && PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter) { + PsiType notAnnotated = type.annotate(TypeAnnotationProvider.EMPTY); + TypeNullability notAnnotatedNullability = notAnnotated.getNullability(); + if (notAnnotatedNullability.nullability() == Nullability.NOT_NULL + && notAnnotatedNullability.source() instanceof NullabilitySource.ExtendsBound) { + reportProblem( + holder, + annotation, + new RemoveAnnotationQuickFix(annotation, null), + JavaAnalysisLocalize.inspectionNullableProblemsRedundantAnnotationInheritedNotnull() + ); + } + } + if (type instanceof PsiPrimitiveType) { + LocalQuickFix additionalFix = null; + if (targetType instanceof PsiArrayType && !targetType.hasAnnotations()) { + additionalFix = new MoveAnnotationToArrayFix(); + } + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsPrimitiveTypeAnnotation(), + additionalFix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[]{additionalFix} + ); + } + if (type instanceof PsiClassType classType) { + PsiElement context = classType.getPsiContext(); + // outer type/package + if (context instanceof PsiJavaCodeReferenceElement outerCtx) { + PsiElement parent = context.getParent(); + if (parent instanceof PsiJavaCodeReferenceElement) { + if (outerCtx.resolve() instanceof PsiPackage) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAppliedToPackage(), + new MoveAnnotationOnStaticMemberQualifyingTypeFix() + ); + } + else { + // If outer is qualifier of static member then don't report problem as it is already reported + // as ANNOTATION_NOT_ALLOWED_STATIC which contains exactly the same fix "Move annotation". + if (!PsiImplUtil.isTypeQualifierOfStaticMember(outerCtx)) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsOuterType(), + new MoveAnnotationOnStaticMemberQualifyingTypeFix() + ); + } + } + } + if (parent instanceof PsiReferenceList) { + PsiElement firstChild = parent.getFirstChild(); + if ((PsiUtil.isJavaToken(firstChild, JavaTokenType.EXTENDS_KEYWORD) || + PsiUtil.isJavaToken(firstChild, JavaTokenType.IMPLEMENTS_KEYWORD)) + && !(parent.getParent() instanceof PsiTypeParameter)) { + reportIncorrectLocation(holder, annotation, listOwner, JavaAnalysisLocalize.inspectionNullableProblemsAtReferenceList()); + } + if (PsiUtil.isJavaToken(firstChild, JavaTokenType.THROWS_KEYWORD)) { + reportIncorrectLocation(holder, annotation, listOwner, JavaAnalysisLocalize.inspectionNullableProblemsAtThrows()); + } + } + } + } + if (type instanceof PsiArrayType + && annotation.getParent() instanceof PsiTypeElement parent + && parent.getType().equals(type) + && !manager.canAnnotateLocals(wrapper.qualifiedName())) { + checkIllegalLocalAnnotation(annotation, parent.getParent(), state); + } + if (listOwner instanceof PsiMethod method && method.isConstructor()) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAtConstructor() + ); + } + if (listOwner instanceof PsiClass + && AnnotationTargetUtil.findAnnotationTarget(annotation, PsiAnnotation.TargetType.TYPE) == null) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAtClass() + ); + } + if (listOwner instanceof PsiEnumConstant) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAtEnumConstant() + ); + } + if (!manager.canAnnotateLocals(wrapper.qualifiedName()) && !(targetType instanceof PsiArrayType)) { + checkIllegalLocalAnnotation(annotation, listOwner, state); + } + if (type instanceof PsiWildcardType && manager.isTypeUseAnnotationLocationRestricted(wrapper.qualifiedName())) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAtWildcard() + ); + } + if (wrapper.owner() instanceof PsiTypeParameter && manager.isTypeUseAnnotationLocationRestricted(wrapper.qualifiedName())) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsAtTypeParameter() + ); + } + if (listOwner instanceof PsiReceiverParameter && wrapper.nullability() != Nullability.NOT_NULL) { + reportIncorrectLocation( + holder, + annotation, + listOwner, + JavaAnalysisLocalize.inspectionNullableProblemsReceiverAnnotation() + ); + } + checkOppositeAnnotationConflict(annotation, wrapper.nullability()); + if (NOT_NULL.equals(wrapper.qualifiedName())) { + PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception"); + if (value instanceof PsiClassObjectAccessExpression classObjectAccessExpression) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(classObjectAccessExpression.getOperand().getType()); + if (psiClass != null && !hasStringConstructor(psiClass)) { + reportProblem(holder, value, JavaAnalysisLocalize.customExceptionClassShouldHaveAConstructor()); + } + } + } + } + + private void checkRedundantInContainerScope(NullabilityAnnotationWrapper wrapper, NullableStuffInspectionState state) { + if (state.REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER) { + NullabilityAnnotationInfo containerInfo = wrapper.findContainerInfoForRedundantAnnotation(); + if (containerInfo != null) { + if (containerInfo.getNullability() == Nullability.NOT_NULL && manager.isNonNullUsedForInstrumentation(wrapper.annotation())) { + return; + } + reportRedundantInContainerScope(wrapper.annotation(), containerInfo); + } + } + } + + private void reportRedundantInContainerScope(PsiAnnotation annotation, NullabilityAnnotationInfo containerInfo) { + PsiJavaCodeReferenceElement containerName = containerInfo.getAnnotation().getNameReferenceElement(); + if (containerName != null) { + LocalQuickFix updateOptionFix = new UpdateInspectionOptionFix<>( + NullableStuffInspectionBase.this, + JavaAnalysisLocalize.inspectionNullableProblemsTurnOffRedundantAnnotationUnderContainer(), + (NullableStuffInspectionState state) -> + state.REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER = false + ); + reportProblem(holder, + annotation, + new LocalQuickFix[]{new RemoveAnnotationQuickFix(annotation, null), updateOptionFix}, + JavaAnalysisLocalize.inspectionNullableProblemsRedundantAnnotationUnderContainer(containerName.getReferenceName()) + ); + } + } + + private void checkIllegalLocalAnnotation( + PsiAnnotation annotation, + @Nullable PsiElement owner, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NULLABILITY_ANNOTATION_ON_LOCALS) { + return; + } + if (owner instanceof PsiLocalVariable || + owner instanceof PsiParameter parameter + && parameter.getDeclarationScope() instanceof PsiCatchSection) { + reportIncorrectLocation( + holder, + annotation, + (PsiVariable) owner, + JavaAnalysisLocalize.inspectionNullableProblemsAtLocalVariable() + ); + } + } + + private void checkOppositeAnnotationConflict(PsiAnnotation annotation, Nullability nullability) { + PsiAnnotationOwner owner = annotation.getOwner(); + if (owner == null) { + return; + } + PsiModifierListOwner listOwner = owner instanceof PsiModifierList modifierList + ? tryCast(modifierList.getParent(), PsiModifierListOwner.class) + : null; + Predicate filter = anno -> + anno != annotation && manager.getAnnotationNullability(anno.getQualifiedName()) + .filter(n -> n != nullability) + .isPresent(); + PsiAnnotation oppositeAnno = ContainerUtil.find(owner.getAnnotations(), filter); + if (oppositeAnno == null && listOwner != null) { + NullabilityAnnotationInfo result = manager.findNullabilityAnnotationInfo( + listOwner, ContainerUtil.filter(Nullability.values(), n -> n != nullability)); + oppositeAnno = result == null || result.isContainer() ? null : result.getAnnotation(); + } + if (oppositeAnno != null && + Objects.equals(getRelatedType(annotation), getRelatedType(oppositeAnno))) { + reportProblem( + holder, + annotation, + new RemoveAnnotationQuickFix(annotation, listOwner), + JavaAnalysisLocalize.inspectionNullableProblemsNullableNotnullConflict( + getPresentableAnnoName(annotation), + getPresentableAnnoName(oppositeAnno) + ) + ); + } + } + + private static boolean hasStringConstructor(PsiClass aClass) { + for (PsiMethod method : aClass.getConstructors()) { + PsiParameterList list = method.getParameterList(); + if (list.getParametersCount() == 1 && + Objects.requireNonNull(list.getParameter(0)).getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return true; + } + } + return false; + } + + @Override + @RequiredReadAction + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + + checkNullableNotNullInstantiationConflict(reference); + + PsiElement list = reference.getParent(); + PsiElement parent = list instanceof PsiReferenceList ? list.getParent() : null; + if (parent instanceof PsiClass psiClass + && list == psiClass.getImplementsList() + && reference.resolve() instanceof PsiClass intf + && intf.isInterface()) { + LocalizeValue error = checkIndirectInheritance(psiClass, intf, state); + if (error != null && error.isNotEmpty()) { + holder.newProblem(error).range(reference).create(); + } + } + } + + @RequiredReadAction + private void checkNullableNotNullInstantiationConflict(PsiJavaCodeReferenceElement reference) { + if (reference.resolve() instanceof PsiClass psiClass) { + PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); + PsiTypeElement[] typeArguments = getReferenceTypeArguments(reference); + if (typeParameters.length > 0 && typeParameters.length == typeArguments.length && !(typeArguments[0].getType() instanceof PsiDiamondType)) { + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeElement typeArgument = typeArguments[i]; + Project project = psiClass.getProject(); + PsiType type = typeArgument.getType(); + if (TypeNullability.ofTypeParameter(typeParameters[i]).nullability() != Nullability.NOT_NULL) { + continue; + } + TypeNullability nullability = type.getNullability(); + Nullability typeNullability = nullability.nullability(); + if (typeNullability != Nullability.NOT_NULL && + !(typeNullability == Nullability.UNKNOWN && type instanceof PsiWildcardType wildcardType && !wildcardType.isExtends())) { + String annotationToAdd = manager.getDefaultAnnotation(Nullability.NOT_NULL, reference); + PsiClass annotationClass = + JavaPsiFacade.getInstance(project).findClass(annotationToAdd, psiClass.getResolveScope()); + List fixes = new ArrayList<>(); + if (annotationClass != null && + AnnotationTargetUtil.findAnnotationTarget(annotationClass, PsiAnnotation.TargetType.TYPE_USE) != null) { + fixes.add(new AddTypeAnnotationFix( + typeArgument, + annotationToAdd, + manager.getNullables() + )); + } + ProblemHighlightType level = + nullability == TypeNullability.UNKNOWN && !state.REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE + ? ProblemHighlightType.INFORMATION + : ProblemHighlightType.GENERIC_ERROR_OR_WARNING; + if (!isOnTheFly && level == ProblemHighlightType.INFORMATION) { + continue; + } + holder.newProblem(JavaAnalysisLocalize.nonNullTypeArgumentIsExpected()) + .range(typeArgument) + .withFixes(fixes) + .highlightType(level) + .create(); + } + } + } + } + } + + private static PsiTypeElement[] getReferenceTypeArguments(PsiJavaCodeReferenceElement reference) { + PsiReferenceParameterList typeArgList = reference.getParameterList(); + return typeArgList == null ? PsiTypeElement.EMPTY_ARRAY : typeArgList.getTypeParameterElements(); + } + + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + PsiExpression rExpression = expression.getRExpression(); + if (rExpression == null) { + return; + } + checkNestedGenericClasses(holder, expression.getOperationSign(), + expression.getLExpression().getType(), + rExpression.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM, state + ); + } + + @Override + public void visitLocalVariable(PsiLocalVariable variable) { + PsiIdentifier identifier = variable.getNameIdentifier(); + if (identifier == null) { + return; + } + PsiExpression initializer = variable.getInitializer(); + if (initializer == null) { + return; + } + checkNestedGenericClasses(holder, identifier, variable.getType(), initializer.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM, state + ); + } + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + PsiExpression returnValue = statement.getReturnValue(); + if (returnValue == null) { + return; + } + checkNestedGenericClasses(holder, returnValue, + PsiTypesUtil.getMethodReturnType(statement), returnValue.getType(), + ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM, state + ); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression lambda) { + super.visitLambdaExpression(lambda); + PsiElement body = lambda.getBody(); + if (body instanceof PsiExpression psiExpression) { + checkNestedGenericClasses( + holder, + body, + LambdaUtil.getFunctionalInterfaceReturnType(lambda), + psiExpression.getType(), + ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM, + state + ); + } + } + + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + PsiExpressionList argList = callExpression.getArgumentList(); + JavaResolveResult result = callExpression.resolveMethodGenerics(); + PsiMethod method = (PsiMethod) result.getElement(); + if (method == null || argList == null) { + return; + } + + PsiSubstitutor substitutor = result.getSubstitutor(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiExpression[] arguments = argList.getExpressions(); + for (int i = 0; i < arguments.length; i++) { + PsiExpression argument = arguments[i]; + if (i < parameters.length && + (i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) { + PsiType expectedType = substitutor.substitute(parameters[i].getType()); + checkNestedGenericClasses(holder, argument, expectedType, argument.getType(), + ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM, state + ); + } + } + } + }; + } + + private boolean checkNestedGenericClasses( + ProblemsHolder holder, + PsiElement errorElement, + @Nullable PsiType expectedType, + @Nullable PsiType actualType, + ConflictNestedTypeProblem problem, + NullableStuffInspectionState state + ) { + if (expectedType == null || actualType == null) { + return false; } - } - - private boolean hasStringConstructor(PsiClass aClass) { - for (PsiMethod method : aClass.getConstructors()) { - PsiParameterList list = method.getParameterList(); - if (list.getParametersCount() == 1 && - list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) { - return true; - } + JavaTypeNullabilityUtil.NullabilityConflictContext context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment( + expectedType, + actualType, + state.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS + ); + JavaTypeNullabilityUtil.NullabilityConflict conflict = context.nullabilityConflict(); + + if (conflict == JavaTypeNullabilityUtil.NullabilityConflict.UNKNOWN) { + return false; } - return false; - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - - checkNullableNotNullInstantiationConflict(reference); - - PsiElement list = reference.getParent(); - PsiElement psiClass = list instanceof PsiReferenceList ? list.getParent() : null; - PsiElement intf = reference.resolve(); - if (psiClass instanceof PsiClass && list == ((PsiClass) psiClass).getImplementsList() && - intf instanceof PsiClass && ((PsiClass) intf).isInterface()) { - String error = checkIndirectInheritance(psiClass, (PsiClass) intf); - if (error != null) { - holder.registerProblem(reference, error); - } + + Function messageKey = switch (conflict) { + case NOT_NULL_TO_NULL -> (Function) problem::notNullToNullProblem; + case NULL_TO_NOT_NULL -> (Function) problem::nullToNotNullProblem; + default -> (Function) problem::complexProblem; + }; + + reportProblem( + holder, + errorElement, + LocalQuickFix.EMPTY_ARRAY, + messageKey.apply(""), + messageKey.apply(NullableStuffInspectionUtil.getNullabilityConflictPresentation(context)) + ); + return true; + } + + private void checkConflictingContainerAnnotations(ProblemsHolder holder, @Nullable PsiModifierList list) { + if (list == null || list.getAnnotations().length == 0) { + return; } - } - - private void checkNullableNotNullInstantiationConflict(PsiJavaCodeReferenceElement reference) { - PsiElement element = reference.resolve(); - if (element instanceof PsiClass) { - PsiTypeParameter[] typeParameters = ((PsiClass) element).getTypeParameters(); - PsiTypeElement[] typeArguments = getReferenceTypeArguments(reference); - if (typeParameters.length > 0 && typeParameters.length == typeArguments.length && !(typeArguments[0].getType() instanceof PsiDiamondType)) { - for (int i = 0; i < typeParameters.length; i++) { - if (DfaPsiUtil.getTypeNullability(JavaPsiFacade.getElementFactory(element.getProject()).createType(typeParameters[i])) == - Nullability.NOT_NULL && DfaPsiUtil.getTypeNullability(typeArguments[i].getType()) != Nullability.NOT_NULL) { - holder.registerProblem(typeArguments[i], - "Non-null type argument is expected", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } + NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject()); + List conflictingAnnotations = manager.getConflictingContainerAnnotations(list); + if (!conflictingAnnotations.isEmpty()) { + for (PsiAnnotation annotation : conflictingAnnotations) { + reportProblem(holder, annotation, JavaAnalysisLocalize.conflictingNullabilityAnnotations()); } - } } - } - - private PsiTypeElement[] getReferenceTypeArguments(PsiJavaCodeReferenceElement reference) { - PsiReferenceParameterList typeArgList = reference.getParameterList(); - return typeArgList == null ? PsiTypeElement.EMPTY_ARRAY : typeArgList.getTypeParameterElements(); - } - - @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { - checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression()); - } - - @Override - public void visitLocalVariable(PsiLocalVariable variable) { - PsiIdentifier identifier = variable.getNameIdentifier(); - if (identifier != null) { - checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer()); - } - } + } - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - PsiExpression returnValue = statement.getReturnValue(); - if (returnValue == null) { - return; - } + private void reportProblem(ProblemsHolder holder, PsiElement anchor, LocalizeValue message) { + reportProblem(holder, anchor, LocalQuickFix.EMPTY_ARRAY, message); + } - checkCollectionNullityOnAssignment(statement.getReturnValue(), PsiTypesUtil.getMethodReturnType(statement), returnValue); - } + private void reportProblem( + ProblemsHolder holder, + PsiElement anchor, + @Nullable LocalQuickFix fix, + LocalizeValue message + ) { + reportProblem(holder, anchor, fix == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[]{fix}, message); + } - @Override - public void visitLambdaExpression(PsiLambdaExpression lambda) { - super.visitLambdaExpression(lambda); - PsiElement body = lambda.getBody(); - if (body instanceof PsiExpression) { - checkCollectionNullityOnAssignment(body, LambdaUtil.getFunctionalInterfaceReturnType(lambda), (PsiExpression) body); - } - } - - @Override - public void visitCallExpression(PsiCallExpression callExpression) { - PsiExpressionList argList = callExpression.getArgumentList(); - JavaResolveResult result = callExpression.resolveMethodGenerics(); - PsiMethod method = (PsiMethod) result.getElement(); - if (method == null || argList == null) { - return; + protected void reportProblem( + ProblemsHolder holder, + PsiElement anchor, + LocalQuickFix[] fixes, + LocalizeValue message + ) { + reportProblem(holder, anchor, fixes, message, message); + } + + @Override + public InspectionToolState createStateProvider() { + return new NullableStuffInspectionState(); + } + + protected void reportProblem( + ProblemsHolder holder, + PsiElement anchor, + LocalQuickFix[] fixes, + LocalizeValue description, + LocalizeValue tooltip + ) { + ProblemBuilder builder = holder.newProblem(description) + .range(anchor) + .highlightType(ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + for (LocalQuickFix quickFix : fixes) { + builder.withFix(quickFix); } + builder.create(); + } - PsiSubstitutor substitutor = result.getSubstitutor(); - PsiParameter[] parameters = method.getParameterList().getParameters(); - PsiExpression[] arguments = argList.getExpressions(); - for (int i = 0; i < arguments.length; i++) { - PsiExpression argument = arguments[i]; - if (i < parameters.length && - (i < parameters.length - 1 || !MethodCallInstruction.isVarArgCall(method, substitutor, arguments, parameters))) { - checkCollectionNullityOnAssignment(argument, substitutor.substitute(parameters[i].getType()), argument); - } + @RequiredReadAction + private LocalizeValue checkIndirectInheritance(PsiClass psiClass, PsiClass intf, NullableStuffInspectionState state) { + PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(intf, psiClass, PsiSubstitutor.EMPTY); + for (PsiMethod intfMethod : intf.getAllMethods()) { + PsiClass intfMethodClass = intfMethod.getContainingClass(); + PsiMethod overridingMethod = intfMethodClass == null ? null : + JavaOverridingMethodsSearcher.findOverridingMethod(psiClass, intfMethod, intfMethodClass); + PsiClass overridingMethodClass = overridingMethod == null ? null : overridingMethod.getContainingClass(); + if (overridingMethodClass != null && overridingMethodClass != psiClass) { + LocalizeValue error = + checkIndirectInheritance(intfMethod, intfMethodClass, overridingMethod, overridingMethodClass, substitutor, state); + if (error.isNotEmpty()) { + return error; + } + } } - } + return LocalizeValue.empty(); + } - private void checkCollectionNullityOnAssignment(@Nonnull PsiElement errorElement, - @Nullable PsiType expectedType, - @Nullable PsiExpression assignedExpression) { - if (assignedExpression == null) { - return; + @RequiredReadAction + private LocalizeValue checkIndirectInheritance( + PsiMethod intfMethod, + PsiClass intfMethodClass, + PsiMethod overridingMethod, + PsiClass overridingMethodClass, + PsiSubstitutor substitutor, + NullableStuffInspectionState state + ) { + if (isNullableOverridingNotNull(Annotated.from(overridingMethod), intfMethod, state)) { + return JavaAnalysisLocalize.inspectionMessageNullableMethodImplementsNonNullMethod( + overridingMethod.getName(), + overridingMethodClass.getName(), + intfMethodClass.getName() + ); + } + if (isNonAnnotatedOverridingNotNull(overridingMethod, intfMethod, state)) { + return JavaAnalysisLocalize.inspectionMessageNonAnnotatedMethodImplementsNonNullMethod( + overridingMethod.getName(), + overridingMethodClass.getName(), + intfMethodClass.getName() + ); } - checkCollectionNullityOnAssignment(errorElement, expectedType, assignedExpression.getType()); - } + PsiParameter[] overridingParameters = overridingMethod.getParameterList().getParameters(); + PsiParameter[] superParameters = intfMethod.getParameterList().getParameters(); + if (overridingParameters.length == superParameters.length) { + NullableNotNullManager manager = getNullityManager(intfMethod); + for (int i = 0; i < overridingParameters.length; i++) { + PsiParameter parameter = overridingParameters[i]; + List supers = Collections.singletonList(superParameters[i]); + if (findNullableSuperForNotNullParameter(parameter, supers, substitutor, state) != null) { + return JavaAnalysisLocalize.inspectionMessageNonNullParameterShouldNotOverrideNullableParameter( + parameter.getName(), + overridingMethod.getName(), + overridingMethodClass.getName(), + intfMethodClass.getName() + ); + } + if (findNotNullSuperForNonAnnotatedParameter(manager, parameter, supers, state) != null) { + return JavaAnalysisLocalize.inspectionMessageNonAnnotatedParameterShouldNotOverrideNonNullParameter( + parameter.getName(), + overridingMethod.getName(), + overridingMethodClass.getName(), + intfMethodClass.getName() + ); + } + if (isNotNullParameterOverridingNonAnnotated(manager, parameter, supers, substitutor, state)) { + return JavaAnalysisLocalize.inspectionMessageNonNullParameterShouldNotOverrideNonAnnotatedParameter( + parameter.getName(), + overridingMethod.getName(), + overridingMethodClass.getName(), + intfMethodClass.getName() + ); + } + } + } - private void checkCollectionNullityOnAssignment(@Nonnull PsiElement errorElement, - @Nullable PsiType expectedType, - @Nullable PsiType assignedType) { - if (isNullableNotNullCollectionConflict(expectedType, assignedType, new HashSet<>())) { - holder.registerProblem(errorElement, - "Assigning a collection of nullable elements into a collection of non-null elements", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + return LocalizeValue.empty(); + } + @RequiredReadAction + private void checkMethodReference(PsiMethodReferenceExpression expression, ProblemsHolder holder, NullableStuffInspectionState state) { + PsiMethod superMethod = LambdaUtil.getFunctionalInterfaceMethod(expression); + PsiMethod targetMethod = tryCast(expression.resolve(), PsiMethod.class); + if (superMethod == null || targetMethod == null) { + return; } - } - private boolean isNullableNotNullCollectionConflict(@Nullable PsiType expectedType, - @Nullable PsiType assignedType, - @Nonnull Set> visited) { - if (!visited.add(Couple.of(expectedType, assignedType))) { - return false; + PsiElement refName = expression.getReferenceNameElement(); + assert refName != null; + if (isNullableOverridingNotNull(check(targetMethod, holder, expression.getType()), superMethod, state)) { + reportProblem( + holder, + refName, + JavaAnalysisLocalize.inspectionNullableProblemsNullableMethodOverridesNotnull( + getPresentableAnnoName(targetMethod), + getPresentableAnnoName(superMethod) + ) + ); } + } - GlobalSearchScope scope = holder.getFile().getResolveScope(); - if (isNullityConflict(JavaGenericsUtil.getCollectionItemType(expectedType, scope), - JavaGenericsUtil.getCollectionItemType(assignedType, scope))) { - return true; - } + protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { + return null; + } - for (int i = 0; i <= 1; i++) { - PsiType expectedArg = PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_MAP, i, false); - PsiType assignedArg = PsiUtil.substituteTypeParameter(assignedType, CommonClassNames.JAVA_UTIL_MAP, i, false); - if (isNullityConflict(expectedArg, assignedArg) || - expectedArg != null && assignedArg != null && isNullableNotNullCollectionConflict(expectedArg, assignedArg, visited)) { - return true; - } + private static boolean nullabilityAnnotationsNotAvailable(PsiFile file) { + Project project = file.getProject(); + GlobalSearchScope scope = GlobalSearchScope.allScope(project); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + return ContainerUtil.find( + NullableNotNullManager.getInstance(project).getNullables(), + s -> facade.findClass(s, scope) != null + ) == null; + } + + @RequiredReadAction + private void checkAccessors( + PsiField field, + Annotated annotated, + Project project, + NullableNotNullManager manager, + String anno, + List annoToRemove, + ProblemsHolder holder, + NullableStuffInspectionState state + ) { + String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD); + boolean isStatic = field.isStatic(); + PsiMethod getter = PropertyUtilBase.findPropertyGetter(field.getContainingClass(), propName, isStatic, false); + PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier(); + if (nameIdentifier != null && nameIdentifier.isPhysical()) { + if (PropertyUtil.getFieldOfGetter(getter) == field) { + AddAnnotationPsiFix getterAnnoFix = + new AddAnnotationPsiFix(anno, getter, ArrayUtil.toStringArray(annoToRemove)); + if (state.REPORT_NOT_ANNOTATED_GETTER) { + if (!hasNullability(manager, getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) { + reportProblem( + holder, + nameIdentifier, + getterAnnoFix, + JavaAnalysisLocalize.inspectionNullableProblemsAnnotatedFieldGetterNotAnnotated(StringUtil.getShortName(anno)) + ); + } + } + if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) || + annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) { + reportProblem( + holder, + nameIdentifier, + getterAnnoFix, + JavaAnalysisLocalize.inspectionNullableProblemsAnnotatedFieldGetterConflict( + getPresentableAnnoName(field), + getPresentableAnnoName(getter) + ) + ); + } + } } - return false; - } - - private boolean isNullityConflict(PsiType expected, PsiType assigned) { - return DfaPsiUtil.getTypeNullability(expected) == Nullability.NOT_NULL && DfaPsiUtil.getTypeNullability(assigned) == Nullability.NULLABLE; - } - }; - } - - @Nullable - private String checkIndirectInheritance(PsiElement psiClass, PsiClass intf) { - for (PsiMethod intfMethod : intf.getAllMethods()) { - PsiClass intfMethodClass = intfMethod.getContainingClass(); - PsiMethod overridingMethod = intfMethodClass == null ? null : - JavaOverridingMethodsSearcher.findOverridingMethod((PsiClass) psiClass, intfMethod, intfMethodClass); - PsiClass overridingMethodClass = overridingMethod == null ? null : overridingMethod.getContainingClass(); - if (overridingMethodClass != null && overridingMethodClass != psiClass) { - String error = checkIndirectInheritance(intfMethod, intfMethodClass, overridingMethod, overridingMethodClass); - if (error != null) { - return error; + PsiClass containingClass = field.getContainingClass(); + PsiMethod setter = PropertyUtilBase.findPropertySetter(containingClass, propName, isStatic, false); + if (setter != null && setter.isPhysical() && PropertyUtil.getFieldOfSetter(setter) == field) { + PsiParameter[] parameters = setter.getParameterList().getParameters(); + assert parameters.length == 1 : setter.getText(); + PsiParameter parameter = parameters[0]; + LOG.assertTrue(parameter != null, setter.getText()); + AddAnnotationFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter); + if (state.REPORT_NOT_ANNOTATED_GETTER && !hasNullability(manager, parameter) + && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { + PsiIdentifier parameterName = parameter.getNameIdentifier(); + assertValidElement(setter, parameter, parameterName); + reportProblem( + holder, + parameterName, + addAnnoFix, + JavaAnalysisLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterNotAnnotated(getPresentableAnnoName(field)) + ); + } + if (PropertyUtil.isSimpleSetter(setter) + && annotated.isDeclaredNotNull + && isNullableNotInferred(parameter, false)) { + PsiIdentifier parameterName = parameter.getNameIdentifier(); + assertValidElement(setter, parameter, parameterName); + reportProblem( + holder, + parameterName, + addAnnoFix, + JavaAnalysisLocalize.inspectionNullableProblemsAnnotatedFieldSetterParameterConflict( + getPresentableAnnoName(field), + getPresentableAnnoName(parameter) + ) + ); + } } - } } - return null; - } - - @Nullable - private String checkIndirectInheritance(PsiMethod intfMethod, - PsiClass intfMethodClass, - PsiMethod overridingMethod, - PsiClass overridingMethodClass) { - if (isNullableOverridingNotNull(Annotated.from(overridingMethod), intfMethod)) { - return "Nullable method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' implements non-null method from '" + intfMethodClass.getName() + "'"; + + @RequiredReadAction + private static AddAnnotationFix createAddAnnotationFix(String anno, List annoToRemove, PsiParameter parameter) { + return new AddAnnotationFix(anno, parameter, annoToRemove.toArray(ArrayUtil.EMPTY_STRING_ARRAY)); } - if (isNonAnnotatedOverridingNotNull(overridingMethod, intfMethod)) { - return "Non-annotated method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' implements non-null method from '" + intfMethodClass.getName() + "'"; + + @Contract("_,_,null -> fail") + @RequiredReadAction + private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) { + LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText()); + LOG.assertTrue(parameter.isPhysical(), setter.getText()); } - PsiParameter[] overridingParameters = overridingMethod.getParameterList().getParameters(); - PsiParameter[] superParameters = intfMethod.getParameterList().getParameters(); - if (overridingParameters.length == superParameters.length) { - NullableNotNullManager manager = getNullityManager(intfMethod); - for (int i = 0; i < overridingParameters.length; i++) { - PsiParameter parameter = overridingParameters[i]; - List supers = Collections.singletonList(superParameters[i]); - if (findNullableSuperForNotNullParameter(parameter, supers) != null) { - return "Non-null parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override nullable parameter from '" + intfMethodClass.getName() + "'"; + @RequiredReadAction + private void checkConstructorParameters( + PsiField field, + Annotated annotated, + String anno, + List annoToRemove, + ProblemsHolder holder, + NullableStuffInspectionState state + ) { + List initializers = DfaPsiUtil.findAllConstructorInitializers(field); + if (initializers.isEmpty()) { + return; } - if (findNotNullSuperForNonAnnotatedParameter(manager, parameter, supers) != null) { - return "Non-annotated parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override non-null parameter from '" + intfMethodClass.getName() + "'"; + + if (state.REPORT_NOT_ANNOTATED_GETTER) { + reportConstructorParameterFromField(field, anno, annoToRemove, holder, initializers); } - if (isNotNullParameterOverridingNonAnnotated(manager, parameter, supers)) { - return "Non-null parameter '" + parameter.getName() + - "' in method '" + overridingMethod.getName() + - "' from '" + overridingMethodClass.getName() + - "' should not override non-annotated parameter from '" + intfMethodClass.getName() + "'"; + + if (field.isFinal()) { + checkFinalFieldInitializedNotNull(field, annotated, holder, initializers); } - } } - return null; - } + @RequiredReadAction + private void checkFinalFieldInitializedNotNull( + PsiField field, + Annotated annotated, + ProblemsHolder holder, + List initializers + ) { + List notNullParams = new ArrayList<>(); + for (PsiExpression rhs : initializers) { + if (rhs instanceof PsiReferenceExpression ref + && ref.resolve() instanceof PsiParameter parameter + && isConstructorParameter(parameter) + && parameter.isPhysical() + && annotated.isDeclaredNullable + && isNotNullNotInferred(parameter, false, false)) { + notNullParams.add(parameter); + } + } - private void checkMethodReference(PsiMethodReferenceExpression expression, @Nonnull ProblemsHolder holder) { - PsiMethod superMethod = LambdaUtil.getFunctionalInterfaceMethod(expression); - PsiMethod targetMethod = ObjectUtil.tryCast(expression.resolve(), PsiMethod.class); - if (superMethod == null || targetMethod == null) { - return; - } + if (notNullParams.size() != initializers.size()) { + // it's not the case that the field is and @Nullable and always initialized via parameters, + // so there might be other initializers that could justify it being nullable, + // so don't highlight field and constructor parameter annotation inconsistency + return; + } - PsiElement refName = expression.getReferenceNameElement(); - assert refName != null; - if (isNullableOverridingNotNull(check(targetMethod, holder, expression.getType()), superMethod)) { - holder.registerProblem(refName, - InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull", - getPresentableAnnoName(targetMethod), getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } else if (isNonAnnotatedOverridingNotNull(targetMethod, superMethod)) { - holder.registerProblem(refName, - "Not annotated method is used as an override for a method annotated with " + getPresentableAnnoName(superMethod), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createFixForNonAnnotatedOverridesNotNull(targetMethod, superMethod)); + PsiIdentifier nameIdentifier = field.getNameIdentifier(); + if (nameIdentifier.isPhysical()) { + reportProblem( + holder, + nameIdentifier, + AddAnnotationPsiFix.createAddNotNullFix(field), + JavaAnalysisLocalize.zeroFieldIsAlwaysInitializedNotNull(getPresentableAnnoName(field)) + ); + } } - } - - protected LocalQuickFix createNavigateToNullParameterUsagesFix(PsiParameter parameter) { - return null; - } - - private static boolean nullabilityAnnotationsNotAvailable(final PsiFile file) { - final Project project = file.getProject(); - final GlobalSearchScope scope = GlobalSearchScope.allScope(project); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - return ContainerUtil.find(NullableNotNullManager.getInstance(project).getNullables(), s -> facade.findClass(s, scope) != null) == null; - } - - private static boolean checkNonStandardAnnotations(PsiField field, - Annotated annotated, - NullableNotNullManager manager, String anno, @Nonnull ProblemsHolder holder) { - if (!AnnotationUtil.isAnnotatingApplicable(field, anno)) { - String message = "Not \'"; - PsiAnnotation annotation = Objects.requireNonNull(annotated.isDeclaredNullable ? annotated.nullable : annotated.notNull); - message += annotation.getQualifiedName(); - message += "\' but \'" + anno + "\' would be used for code generation."; - final PsiJavaCodeReferenceElement annotationNameReferenceElement = annotation.getNameReferenceElement(); - holder.registerProblem(annotationNameReferenceElement != null && annotationNameReferenceElement.isPhysical() ? annotationNameReferenceElement : field.getNameIdentifier(), - message, - ProblemHighlightType.WEAK_WARNING, - new ChangeNullableDefaultsFix(annotated.notNull, annotated.nullable, manager)); - return false; + + @RequiredReadAction + private void reportConstructorParameterFromField( + PsiField field, + String anno, + List annoToRemove, + ProblemsHolder holder, + List initializers + ) { + Map> ctorToInitializers = StreamEx.of(initializers) + .mapToEntry(e -> PsiTreeUtil.getParentOfType(e, PsiMethod.class), e -> e) + .nonNullKeys() + .filterKeys(PsiMethod::isConstructor) + .grouping(); + + ctorToInitializers.forEach((ctor, exprs) -> { + List parameters = StreamEx.of(exprs) + .map( + e -> PsiUtil.skipParenthesizedExprDown(e) instanceof PsiReferenceExpression ref + ? tryCast(ref.resolve(), PsiParameter.class) + : null + ) + .distinct() + .limit(2) + .toList(); + if (parameters.size() != 1) { + return; + } + PsiParameter parameter = parameters.getFirst(); + if (parameter != null && parameter.getDeclarationScope() == ctor + && !hasNullability(NullableNotNullManager.getInstance(field.getProject()), parameter) + && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { + PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); + if (nameIdentifier != null && nameIdentifier.isPhysical()) { + reportProblem( + holder, + nameIdentifier, + createAddAnnotationFix(anno, annoToRemove, parameter), + JavaAnalysisLocalize.inspectionNullableProblemsAnnotatedFieldConstructorParameterNotAnnotated( + StringUtil.getShortName(anno) + ) + ); + } + } + }); } - return true; - } - - private void checkAccessors(PsiField field, - Annotated annotated, - Project project, - NullableNotNullManager manager, final String anno, final List annoToRemove, @Nonnull ProblemsHolder holder) { - String propName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD); - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - final PsiMethod getter = PropertyUtilBase.findPropertyGetter(field.getContainingClass(), propName, isStatic, false); - final PsiIdentifier nameIdentifier = getter == null ? null : getter.getNameIdentifier(); - if (nameIdentifier != null && nameIdentifier.isPhysical()) { - if (PropertyUtil.getFieldOfGetter(getter) == field) { - AnnotateMethodFix getterAnnoFix = new AnnotateMethodFix(anno, ArrayUtil.toStringArray(annoToRemove)); - if (REPORT_NOT_ANNOTATED_GETTER) { - if (!manager.hasNullability(getter) && !TypeConversionUtil.isPrimitiveAndNotNull(getter.getReturnType())) { - holder.registerProblem(nameIdentifier, InspectionsBundle - .message("inspection.nullable.problems.annotated.field.getter.not.annotated", getPresentableAnnoName(field)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix); - } - } - if (annotated.isDeclaredNotNull && isNullableNotInferred(getter, false) || - annotated.isDeclaredNullable && isNotNullNotInferred(getter, false, false)) { - holder.registerProblem(nameIdentifier, InspectionsBundle.message( - "inspection.nullable.problems.annotated.field.getter.conflict", getPresentableAnnoName(field), getPresentableAnnoName(getter)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, getterAnnoFix); - } - } + + private static boolean isConstructorParameter(@Nullable PsiElement parameter) { + return parameter instanceof PsiParameter + && psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent()); } - final PsiClass containingClass = field.getContainingClass(); - final PsiMethod setter = PropertyUtilBase.findPropertySetter(containingClass, propName, isStatic, false); - if (setter != null && setter.isPhysical() && PropertyUtil.getFieldOfSetter(setter) == field) { - final PsiParameter[] parameters = setter.getParameterList().getParameters(); - assert parameters.length == 1 : setter.getText(); - final PsiParameter parameter = parameters[0]; - LOG.assertTrue(parameter != null, setter.getText()); - AddAnnotationPsiFix addAnnoFix = createAddAnnotationFix(anno, annoToRemove, parameter); - if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { - final PsiIdentifier parameterName = parameter.getNameIdentifier(); - assertValidElement(setter, parameter, parameterName); - holder.registerProblem(parameterName, - InspectionsBundle.message("inspection.nullable.problems.annotated.field.setter.parameter.not.annotated", - getPresentableAnnoName(field)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - addAnnoFix); - } - if (PropertyUtil.isSimpleSetter(setter)) { - if (annotated.isDeclaredNotNull && isNullableNotInferred(parameter, false)) { - final PsiIdentifier parameterName = parameter.getNameIdentifier(); - assertValidElement(setter, parameter, parameterName); - holder.registerProblem(parameterName, InspectionsBundle.message( - "inspection.nullable.problems.annotated.field.setter.parameter.conflict", - getPresentableAnnoName(field), getPresentableAnnoName(parameter)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - addAnnoFix); + private static String getPresentableAnnoName(PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + String name = info == null ? null : info.getAnnotation().getQualifiedName(); + if (name == null) { + return "???"; } - } + return StringUtil.getShortName(name); } - } - - @Nonnull - private static AddAnnotationPsiFix createAddAnnotationFix(String anno, List annoToRemove, PsiParameter parameter) { - return new AddAnnotationPsiFix(anno, parameter, PsiNameValuePair.EMPTY_ARRAY, ArrayUtil.toStringArray(annoToRemove)); - } - - @Contract("_,_,null -> fail") - private static void assertValidElement(PsiMethod setter, PsiParameter parameter, PsiIdentifier nameIdentifier1) { - LOG.assertTrue(nameIdentifier1 != null && nameIdentifier1.isPhysical(), setter.getText()); - LOG.assertTrue(parameter.isPhysical(), setter.getText()); - } - - private void checkConstructorParameters(PsiField field, - Annotated annotated, - NullableNotNullManager manager, - String anno, List annoToRemove, @Nonnull ProblemsHolder holder) { - List initializers = DfaPsiUtil.findAllConstructorInitializers(field); - if (initializers.isEmpty()) { - return; + + public static String getPresentableAnnoName(PsiAnnotation annotation) { + return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???")); } - List notNullParams = new ArrayList<>(); - - boolean isFinal = field.hasModifierProperty(PsiModifier.FINAL); - - for (PsiExpression rhs : initializers) { - if (rhs instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression) rhs).resolve(); - if (isConstructorParameter(target) && target.isPhysical()) { - PsiParameter parameter = (PsiParameter) target; - if (REPORT_NOT_ANNOTATED_GETTER && !manager.hasNullability(parameter) && !TypeConversionUtil.isPrimitiveAndNotNull(parameter.getType())) { - final PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); - if (nameIdentifier != null && nameIdentifier.isPhysical()) { - holder.registerProblem( - nameIdentifier, - InspectionsBundle.message("inspection.nullable.problems.annotated.field.constructor.parameter.not.annotated", getPresentableAnnoName(field)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, createAddAnnotationFix(anno, annoToRemove, parameter)); - continue; - } - } + /** + * @return true if the owner has a or @Nullable annotation, + * or is in scope of @ParametersAreNullableByDefault or ParametersAreNonnullByDefault + */ + private static boolean hasNullability(NullableNotNullManager manager, PsiModifierListOwner owner) { + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + return info != null && info.getNullability() != Nullability.UNKNOWN && info.getInheritedFrom() == null; + } - if (isFinal && annotated.isDeclaredNullable && isNotNullNotInferred(parameter, false, false)) { - notNullParams.add(parameter); - } + private static class Annotated { + private boolean isDeclaredNotNull; + private boolean isDeclaredNullable; + private @Nullable PsiAnnotation notNull; + private @Nullable PsiAnnotation nullable; + + private Annotated(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { + this.isDeclaredNotNull = notNull != null; + this.isDeclaredNullable = nullable != null; + this.notNull = notNull; + this.nullable = nullable; + } + static Annotated from(PsiModifierListOwner owner) { + NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); + NullabilityAnnotationInfo notNullInfo = + manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NOT_NULL)); + NullabilityAnnotationInfo nullableInfo = + manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NULLABLE)); + PsiAnnotation notNullAnno = + notNullInfo == null || (notNullInfo.isContainer() && nullableInfo != null && !nullableInfo.isContainer()) + ? null : notNullInfo.getAnnotation(); + PsiAnnotation nullableAnno = + nullableInfo == null || (nullableInfo.isContainer() && notNullInfo != null && !notNullInfo.isContainer()) + ? null : nullableInfo.getAnnotation(); + return new Annotated(notNullAnno, nullableAnno); } - } } - if (notNullParams.size() != initializers.size()) { - // it's not the case that the field is final and @Nullable and always initialized via @NotNull parameters - // so there might be other initializers that could justify it being nullable - // so don't highlight field and constructor parameter annotation inconsistency - return; + @RequiredReadAction + private Annotated check(PsiModifierListOwner owner, ProblemsHolder holder, PsiType type) { + Annotated annotated = Annotated.from(owner); + PsiAnnotation annotation = annotated.notNull == null ? annotated.nullable : annotated.notNull; + if (annotation != null && !annotation.isPhysical() && type instanceof PsiPrimitiveType) { + reportIncorrectLocation(holder, annotation, owner, JavaAnalysisLocalize.inspectionNullableProblemsPrimitiveTypeAnnotation()); + } + if (owner instanceof PsiParameter parameter) { + Nullability expectedNullability = DfaPsiUtil.inferParameterNullability(parameter); + if (annotated.notNull != null && expectedNullability == Nullability.NULLABLE) { + reportParameterNullabilityMismatch(parameter, annotated.notNull, holder, JavaAnalysisLocalize.parameterCanBeNull()); + } + else if (annotated.nullable != null && expectedNullability == Nullability.NOT_NULL) { + reportParameterNullabilityMismatch(parameter, annotated.nullable, holder, JavaAnalysisLocalize.parameterIsAlwaysNotNull()); + } + } + return annotated; } - PsiIdentifier nameIdentifier = field.getNameIdentifier(); - if (nameIdentifier.isPhysical()) { - holder.registerProblem(nameIdentifier, "@" + getPresentableAnnoName(field) + " field is always initialized not-null", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, AddAnnotationPsiFix.createAddNotNullFix(field)); + @RequiredReadAction + private void reportParameterNullabilityMismatch( + PsiParameter owner, + PsiAnnotation annotation, + ProblemsHolder holder, + LocalizeValue message + ) { + if (annotation.isPhysical() && !PsiTreeUtil.isAncestor(owner, annotation, true)) { + return; + } + PsiElement anchor = annotation.isPhysical() ? annotation : owner.getNameIdentifier(); + if (anchor != null && !anchor.getTextRange().isEmpty()) { + reportProblem(holder, anchor, new RemoveAnnotationQuickFix(annotation, owner), message); + } } - } - - private static boolean isConstructorParameter(@Nullable PsiElement parameter) { - return parameter instanceof PsiParameter && psiElement(PsiParameterList.class).withParent(psiMethod().constructor(true)).accepts(parameter.getParent()); - } - - @Nonnull - private static String getPresentableAnnoName(@Nonnull PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); - String name = info == null ? null : info.getAnnotation().getQualifiedName(); - if (name == null) { - return "???"; + + private void reportIncorrectLocation( + ProblemsHolder holder, + PsiAnnotation annotation, + @Nullable PsiModifierListOwner listOwner, + LocalizeValue message, + LocalQuickFix... additionalFixes + ) { + RemoveAnnotationQuickFix removeFix = new RemoveAnnotationQuickFix(annotation, listOwner, true); + MoveAnnotationToBoundFix moveToBoundFix = MoveAnnotationToBoundFix.create(annotation); + LocalQuickFix[] fixes = moveToBoundFix == null ? new LocalQuickFix[]{removeFix} : new LocalQuickFix[]{moveToBoundFix, removeFix}; + reportProblem( + holder, + !annotation.isPhysical() && listOwner != null ? listOwner.getNavigationElement() : annotation, + ArrayUtil.mergeArrays(additionalFixes, fixes), + message + ); } - return StringUtil.getShortName(name); - } - - public static String getPresentableAnnoName(@Nonnull PsiAnnotation annotation) { - return StringUtil.getShortName(StringUtil.notNullize(annotation.getQualifiedName(), "???")); - } - - private static class Annotated { - private final boolean isDeclaredNotNull; - private final boolean isDeclaredNullable; - @Nullable - private final PsiAnnotation notNull; - @Nullable - private final PsiAnnotation nullable; - - private Annotated(@Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { - this.isDeclaredNotNull = notNull != null; - this.isDeclaredNullable = nullable != null; - this.notNull = notNull; - this.nullable = nullable; + + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionNullableProblemsDisplayName(); } - @Nonnull - static Annotated from(@Nonnull PsiModifierListOwner owner) { - NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject()); - return new Annotated(manager.findExplicitNullabilityAnnotation(owner, Nullability.NOT_NULL), - manager.findExplicitNullabilityAnnotation(owner, Nullability.NULLABLE)); + @Override + public String getShortName() { + return "NullableProblems"; } - } - - private static Annotated check(final PsiModifierListOwner owner, final ProblemsHolder holder, PsiType type) { - Annotated annotated = Annotated.from(owner); - checkType(owner, holder, type, annotated.notNull, annotated.nullable); - return annotated; - } - - private static void checkType(@Nullable PsiModifierListOwner listOwner, - ProblemsHolder holder, - PsiType type, - @Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable) { - if (nullable != null && notNull != null) { - reportNullableNotNullConflict(holder, listOwner, nullable, notNull); + + @RequiredReadAction + private void checkNullableStuffForMethod(PsiMethod method, ProblemsHolder holder, NullableStuffInspectionState state) { + Annotated annotated = check(method, holder, method.getReturnType()); + + List superMethods = ContainerUtil.map( + method.findSuperMethodSignaturesIncludingStatic(true), + MethodSignatureBackedByPsiMethod::getMethod + ); + + NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject()); + + checkSupers(method, holder, annotated, superMethods, state); + checkParameters(method, holder, superMethods, nullableManager, state); + checkOverriders(method, holder, annotated, nullableManager, state); + checkConflictingContainerAnnotations(holder, method.getModifierList()); } - if ((notNull != null || nullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) { - PsiAnnotation annotation = notNull == null ? nullable : notNull; - reportPrimitiveType(holder, annotation, listOwner); + + private void checkSupers( + PsiMethod method, + ProblemsHolder holder, + Annotated annotated, + List superMethods, + NullableStuffInspectionState state + ) { + PsiIdentifier identifier = method.getNameIdentifier(); + if (identifier == null) { + return; + } + for (PsiMethod superMethod : superMethods) { + if (isNullableOverridingNotNull(annotated, superMethod, state)) { + PsiAnnotation annotation = findAnnotation(method, getNullityManager(method).getNullables(), true); + reportProblem( + holder, + annotation != null ? annotation : identifier, + JavaAnalysisLocalize.inspectionNullableProblemsNullableMethodOverridesNotnull( + getPresentableAnnoName(method), + getPresentableAnnoName(superMethod) + ) + ); + break; + } + + if (isNonAnnotatedOverridingNotNull(method, superMethod, state)) { + reportProblem( + holder, + identifier, + createFixForNonAnnotatedOverridesNotNull(method), + JavaAnalysisLocalize.inspectionNullableProblemsMethodOverridesNotnull(getPresentableAnnoName(superMethod)) + ); + break; + } + + PsiTypeElement returnTypeElement = method.getReturnTypeElement(); + if (returnTypeElement != null + && checkNestedGenericClasses( + holder, + returnTypeElement, + superMethod.getReturnType(), + method.getReturnType(), + ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM, + state + )) { + break; + } + } } - if (listOwner instanceof PsiParameter) { - checkLoopParameterNullability(holder, notNull, nullable, DfaPsiUtil.inferParameterNullability((PsiParameter) listOwner)); + + private static NullableNotNullManager getNullityManager(PsiMethod method) { + return NullableNotNullManager.getInstance(method.getProject()); } - } - - private static void checkLoopParameterNullability(ProblemsHolder holder, @Nullable PsiAnnotation notNull, @Nullable PsiAnnotation nullable, Nullability expectedNullability) { - if (notNull != null && expectedNullability == Nullability.NULLABLE) { - holder.registerProblem(notNull, "Parameter can be null", - new RemoveAnnotationQuickFix(notNull, null)); - } else if (nullable != null && expectedNullability == Nullability.NOT_NULL) { - holder.registerProblem(nullable, "Parameter is always not-null", - new RemoveAnnotationQuickFix(nullable, null)); + + private static @Nullable LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method) { + NullableNotNullManager nullableManager = getNullityManager(method); + return isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) + ? AddAnnotationPsiFix.createAddNotNullFix(method) + : null; } - } - - private static void reportPrimitiveType(ProblemsHolder holder, PsiAnnotation annotation, - @Nullable PsiModifierListOwner listOwner) { - holder.registerProblem(!annotation.isPhysical() && listOwner != null ? listOwner.getNavigationElement() : annotation, - InspectionsBundle.message("inspection.nullable.problems.primitive.type.annotation"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(annotation, listOwner)); - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionsBundle.message("inspection.nullable.problems.display.name"); - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.BUGS_GROUP_NAME; - } - - @Override - @Nonnull - public String getShortName() { - return "NullableProblems"; - } - - private void checkNullableStuffForMethod(PsiMethod method, final ProblemsHolder holder, boolean isOnFly) { - Annotated annotated = check(method, holder, method.getReturnType()); - - List superMethods = ContainerUtil.map( - method.findSuperMethodSignaturesIncludingStatic(true), MethodSignatureBackedByPsiMethod::getMethod); - - final NullableNotNullManager nullableManager = NullableNotNullManager.getInstance(holder.getProject()); - - checkSupers(method, holder, annotated, superMethods); - checkParameters(method, holder, superMethods, nullableManager, isOnFly); - checkOverriders(method, holder, annotated, nullableManager); - } - - private void checkSupers(PsiMethod method, - ProblemsHolder holder, - Annotated annotated, - List superMethods) { - for (PsiMethod superMethod : superMethods) { - if (isNullableOverridingNotNull(annotated, superMethod)) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, getNullityManager(method).getNullables(), true); - holder.registerProblem(annotation != null ? annotation : method.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.Nullable.method.overrides.NotNull", - getPresentableAnnoName(method), getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - break; - } - - if (isNonAnnotatedOverridingNotNull(method, superMethod)) { - holder.registerProblem(method.getNameIdentifier(), - InspectionsBundle - .message("inspection.nullable.problems.method.overrides.NotNull", getPresentableAnnoName(superMethod)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createFixForNonAnnotatedOverridesNotNull(method, superMethod)); - break; - } + + private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod, NullableStuffInspectionState state) { + return state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE + && methodInfo.isDeclaredNullable + && isNotNullNotInferred(superMethod, true, false); } - } - - private static NullableNotNullManager getNullityManager(PsiMethod method) { - return NullableNotNullManager.getInstance(method.getProject()); - } - - @Nullable - private static LocalQuickFix createFixForNonAnnotatedOverridesNotNull(PsiMethod method, - PsiMethod superMethod) { - NullableNotNullManager nullableManager = getNullityManager(method); - return AnnotationUtil.isAnnotatingApplicable(method, nullableManager.getDefaultNotNull()) - ? AddAnnotationPsiFix.createAddNotNullFix(method) - : createChangeDefaultNotNullFix(nullableManager, superMethod); - } - - private boolean isNullableOverridingNotNull(Annotated methodInfo, PsiMethod superMethod) { - return REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && methodInfo.isDeclaredNullable && isNotNullNotInferred(superMethod, true, false); - } - - private boolean isNonAnnotatedOverridingNotNull(PsiMethod method, PsiMethod superMethod) { - return REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && - !(method.getReturnType() instanceof PsiPrimitiveType) && - !method.isConstructor() && - !getNullityManager(method).hasNullability(method) && - isNotNullNotInferred(superMethod, true, IGNORE_EXTERNAL_SUPER_NOTNULL) && - !hasInheritableNotNull(superMethod); - } - - private static boolean hasInheritableNotNull(PsiModifierListOwner owner) { - return AnnotationUtil.isAnnotated(owner, "javax.annotation.constraints.NotNull", CHECK_HIERARCHY | CHECK_TYPE); - } - - private void checkParameters(PsiMethod method, - ProblemsHolder holder, - List superMethods, - NullableNotNullManager nullableManager, - boolean isOnFly) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - if (parameter.getType() instanceof PsiPrimitiveType) { - continue; - } - - List superParameters = new ArrayList<>(); - for (PsiMethod superMethod : superMethods) { - PsiParameter[] _superParameters = superMethod.getParameterList().getParameters(); - if (_superParameters.length == parameters.length) { - superParameters.add(_superParameters[i]); + + private boolean isNonAnnotatedOverridingNotNull(PsiMethod method, PsiMethod superMethod, NullableStuffInspectionState state) { + if (state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL + && !(method.getReturnType() instanceof PsiPrimitiveType) + && !method.isConstructor()) { + NullableNotNullManager manager = getNullityManager(method); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(method); + if ((info == null || info.isInferred() || + (info.getInheritedFrom() != null && manager.findContainerAnnotation(method) == null)) + && isNotNullNotInferred(superMethod, true, state.IGNORE_EXTERNAL_SUPER_NOTNULL) + && !hasInheritableNotNull(superMethod)) { + return true; + } } - } - - PsiParameter nullableSuper = findNullableSuperForNotNullParameter(parameter, superParameters); - if (nullableSuper != null) { - PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, nullableManager.getNotNulls(), true); - holder.registerProblem(annotation != null ? annotation : parameter.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.Nullable", - getPresentableAnnoName(parameter), - getPresentableAnnoName(nullableSuper)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING); - } - PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters); - if (notNullSuper != null) { - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, nullableManager.getDefaultNotNull()) - ? AddAnnotationPsiFix.createAddNotNullFix(parameter) - : createChangeDefaultNotNullFix(nullableManager, notNullSuper); - holder.registerProblem(parameter.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - } - if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters)) { - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - assert info != null; - PsiAnnotation notNullAnnotation = info.getAnnotation(); - boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); - final LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null; - holder.registerProblem(physical ? notNullAnnotation : parameter.getNameIdentifier(), - InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.overrides.not.annotated", getPresentableAnnoName(parameter)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - } - - checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, isOnFly, i, parameter); - } - } - - @Nullable - private PsiParameter findNotNullSuperForNonAnnotatedParameter(NullableNotNullManager nullableManager, - PsiParameter parameter, - List superParameters) { - return REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && !nullableManager.hasNullability(parameter) - ? ContainerUtil.find(superParameters, - sp -> isNotNullNotInferred(sp, false, IGNORE_EXTERNAL_SUPER_NOTNULL) && !hasInheritableNotNull(sp)) - : null; - } - - @Nullable - private PsiParameter findNullableSuperForNotNullParameter(PsiParameter parameter, List superParameters) { - return REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE && isNotNullNotInferred(parameter, false, false) - ? ContainerUtil.find(superParameters, sp -> isNullableNotInferred(sp, false)) - : null; - } - - private boolean isNotNullParameterOverridingNonAnnotated(NullableNotNullManager nullableManager, - PsiParameter parameter, - List superParameters) { - if (!REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) { - return false; - } - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() && - ContainerUtil.exists(superParameters, sp -> !nullableManager.hasNullability(sp)); - } - - private void checkNullLiteralArgumentOfNotNullParameterUsages(PsiMethod method, - ProblemsHolder holder, - NullableNotNullManager nullableManager, - boolean isOnFly, - int parameterIdx, - PsiParameter parameter) { - if (!REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER || !isOnFly) { - return; + return false; } - PsiElement elementToHighlight; - if (DfaPsiUtil.getTypeNullability(getMemberType(parameter)) == Nullability.NOT_NULL) { - elementToHighlight = parameter.getNameIdentifier(); - } else { - NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter); - if (info == null || info.getNullability() != Nullability.NOT_NULL || info.isInferred()) { - return; - } - PsiAnnotation notNullAnnotation = info.getAnnotation(); - boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); - elementToHighlight = physical ? notNullAnnotation : parameter.getNameIdentifier(); - } - if (elementToHighlight == null || !JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) { - return; + private static boolean hasInheritableNotNull(PsiModifierListOwner owner) { + return isAnnotated(owner, "javax.annotation.constraints.NotNull", CHECK_HIERARCHY | CHECK_TYPE); } - holder.registerProblem(elementToHighlight, - InspectionsBundle.message("inspection.nullable.problems.NotNull.parameter.receives.null.literal", - getPresentableAnnoName(parameter)), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - createNavigateToNullParameterUsagesFix(parameter)); - } - - private void checkOverriders(@Nonnull PsiMethod method, - @Nonnull ProblemsHolder holder, - @Nonnull Annotated annotated, - @Nonnull NullableNotNullManager nullableManager) { - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) { - boolean[] checkParameter = new boolean[parameters.length]; - boolean[] parameterQuickFixSuggested = new boolean[parameters.length]; - boolean hasAnnotatedParameter = false; - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - checkParameter[i] = isNotNullNotInferred(parameter, false, false) && - !hasInheritableNotNull(parameter) && - !(parameter.getType() instanceof PsiPrimitiveType); - hasAnnotatedParameter |= checkParameter[i]; - } - boolean checkReturnType = annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); - if (hasAnnotatedParameter || checkReturnType) { - final String defaultNotNull = nullableManager.getDefaultNotNull(); - final boolean superMethodApplicable = AnnotationUtil.isAnnotatingApplicable(method, defaultNotNull); - PsiMethod[] overridings = - OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); - boolean methodQuickFixSuggested = false; - for (PsiMethod overriding : overridings) { - if (shouldSkipOverriderAsGenerated(overriding)) { - continue; - } - - if (!methodQuickFixSuggested - && checkReturnType - && !isNotNullNotInferred(overriding, false, false) - && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true)) - && AddAnnotationPsiFix.isAvailable(overriding, defaultNotNull)) { - PsiIdentifier identifier = method.getNameIdentifier();//load tree - PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, nullableManager.getNotNulls()); - final String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables()); - - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(overriding, defaultNotNull) - ? new MyAnnotateMethodFix(defaultNotNull, annotationsToRemove) - : superMethodApplicable ? null : createChangeDefaultNotNullFix(nullableManager, method); - - PsiElement psiElement = annotation; - if (annotation != null && !annotation.isPhysical()) { - psiElement = identifier; - if (psiElement == null) { + private void checkParameters( + PsiMethod method, + ProblemsHolder holder, + List superMethods, + NullableNotNullManager nullableManager, + NullableStuffInspectionState state + ) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + if (parameter.getType() instanceof PsiPrimitiveType) { continue; - } } - holder.registerProblem(psiElement, InspectionsBundle.message("nullable.stuff.problems.overridden.methods.are.not.annotated"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - methodQuickFixSuggested = true; - } - if (hasAnnotatedParameter) { - PsiParameter[] psiParameters = overriding.getParameterList().getParameters(); - for (int i = 0; i < psiParameters.length; i++) { - if (parameterQuickFixSuggested[i]) { - continue; - } - PsiParameter parameter = psiParameters[i]; - if (checkParameter[i] && - !isNotNullNotInferred(parameter, false, false) && - !isNullableNotInferred(parameter, false) && - AddAnnotationPsiFix.isAvailable(parameter, defaultNotNull)) { - PsiIdentifier identifier = parameters[i].getNameIdentifier(); //be sure that corresponding tree element available - PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameters[i], nullableManager.getNotNulls()); - PsiElement psiElement = annotation; - if (annotation == null || !annotation.isPhysical()) { - psiElement = identifier; - if (psiElement == null) { - continue; - } - } - LocalQuickFix fix = AnnotationUtil.isAnnotatingApplicable(parameter, defaultNotNull) - ? new AnnotateOverriddenMethodParameterFix(defaultNotNull, nullableManager.getDefaultNullable()) - : createChangeDefaultNotNullFix(nullableManager, parameters[i]); - holder.registerProblem(psiElement, - InspectionsBundle.message("nullable.stuff.problems.overridden.method.parameters.are.not.annotated"), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - fix); - parameterQuickFixSuggested[i] = true; - } + + List superParameters = getSuperParameters(superMethods, parameters, i); + + checkSuperParameterAnnotations(holder, nullableManager, parameter, superParameters, state); + + checkNullLiteralArgumentOfNotNullParameterUsages(method, holder, nullableManager, i, parameter, state); + } + } + + private static List getSuperParameters(List superMethods, PsiParameter[] parameters, int i) { + List superParameters = new ArrayList<>(); + for (PsiMethod superMethod : superMethods) { + PsiParameter[] _superParameters = superMethod.getParameterList().getParameters(); + if (_superParameters.length == parameters.length) { + superParameters.add(_superParameters[i]); } - } } - } + return superParameters; } - } - public static boolean shouldSkipOverriderAsGenerated(PsiMethod overriding) { - if (Registry.is("idea.report.nullity.missing.in.generated.overriders", true)) { - return false; + private void checkSuperParameterAnnotations( + ProblemsHolder holder, + NullableNotNullManager nullableManager, + PsiParameter parameter, + List superParameters, + NullableStuffInspectionState state + ) { + PsiIdentifier nameIdentifier = parameter.getNameIdentifier(); + if (nameIdentifier == null) { + return; + } + PsiParameter nullableSuper = findNullableSuperForNotNullParameter(parameter, superParameters, PsiSubstitutor.EMPTY, state); + if (nullableSuper != null) { + PsiAnnotation annotation = findAnnotation(parameter, nullableManager.getNotNulls(), true); + reportProblem( + holder, + annotation != null ? annotation : nameIdentifier, + JavaAnalysisLocalize.inspectionNullableProblemsNotnullParameterOverridesNullable( + getPresentableAnnoName(parameter), + getPresentableAnnoName(nullableSuper) + ) + ); + } + PsiParameter notNullSuper = findNotNullSuperForNonAnnotatedParameter(nullableManager, parameter, superParameters, state); + if (notNullSuper != null) { + LocalQuickFix fix = isAnnotatingApplicable(parameter, nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, parameter)) + ? AddAnnotationPsiFix.createAddNotNullFix(parameter) + : null; + reportProblem( + holder, + nameIdentifier, + fix, + JavaAnalysisLocalize.inspectionNullableProblemsParameterOverridesNotnull(getPresentableAnnoName(notNullSuper)) + ); + } + if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters, PsiSubstitutor.EMPTY, state)) { + NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter); + assert info != null; + PsiAnnotation notNullAnnotation = info.getAnnotation(); + boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true); + LocalQuickFix fix = physical ? new RemoveAnnotationQuickFix(notNullAnnotation, parameter) : null; + reportProblem( + holder, + physical ? notNullAnnotation : nameIdentifier, + fix, + JavaAnalysisLocalize.inspectionNullableProblemsNotnullParameterOverridesNotAnnotated(getPresentableAnnoName(parameter)) + ); + } + + PsiTypeElement typeElement = parameter.getTypeElement(); + if (typeElement != null) { + for (PsiParameter superParameter : superParameters) { + if (checkNestedGenericClasses( + holder, + typeElement, + parameter.getType(), + superParameter.getType(), + ConflictNestedTypeProblem.OVERRIDING_NESTED_TYPE_PROBLEM, + state + )) { + break; + } + } + } } - PsiFile file = overriding.getContainingFile(); - VirtualFile virtualFile = file != null ? file.getVirtualFile() : null; - return virtualFile != null && GeneratedSourcesFilter.isGenerated(overriding.getProject(), virtualFile); - } + private @Nullable PsiParameter findNotNullSuperForNonAnnotatedParameter( + NullableNotNullManager nullableManager, + PsiParameter parameter, + List superParameters, + NullableStuffInspectionState state + ) { + return state.REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL && !hasNullability(nullableManager, parameter) + ? ContainerUtil.find( + superParameters, + sp -> isNotNullNotInferred(sp, false, state.IGNORE_EXTERNAL_SUPER_NOTNULL) && !hasInheritableNotNull(sp) + ) + : null; + } - private static boolean isNotNullNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) { - Project project = owner.getProject(); - NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - if (!manager.isNotNull(owner, checkBases)) { - return false; + private @Nullable PsiParameter findNullableSuperForNotNullParameter( + PsiParameter parameter, + List superParameters, + PsiSubstitutor superSubstitutor, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE || !isNotNullNotInferred(parameter, false, false)) { + return null; + } + PsiClass derived = PsiUtil.getContainingClass(parameter); + for (PsiParameter superParameter : superParameters) { + PsiClass base = PsiUtil.getContainingClass(superParameter); + PsiSubstitutor substitutor = base == null || derived == null ? PsiSubstitutor.EMPTY : + TypeConversionUtil.getMaybeSuperClassSubstitutor(base, derived, superSubstitutor); + if (substitutor == null) { + // may happen if A extends B implements C and methods are declared in B and C + substitutor = superSubstitutor; + } + PsiType superType = substitutor.substitute(superParameter.getType()); + if (DfaPsiUtil.getElementNullabilityForRead(superType, superParameter) == Nullability.NULLABLE) { + return superParameter; + } + } + return null; } - if (DfaPsiUtil.getTypeNullability(getMemberType(owner)) == Nullability.NOT_NULL) { - return true; + + private boolean isNotNullParameterOverridingNonAnnotated( + NullableNotNullManager nullableManager, + PsiParameter parameter, + List superParameters, + PsiSubstitutor substitutor, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) { + return false; + } + NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter); + return info != null + && info.getNullability() == Nullability.NOT_NULL + && info.getInheritedFrom() == null + && !info.isInferred() + && ContainerUtil.exists(superParameters, sp -> isSuperNotAnnotated(nullableManager, parameter, sp, substitutor)); } - PsiAnnotation anno = manager.getNotNullAnnotation(owner, checkBases); - if (anno == null || AnnotationUtil.isInferredAnnotation(anno)) { - return false; + private static boolean isSuperNotAnnotated( + NullableNotNullManager nullableManager, + PsiParameter parameter, + PsiParameter superParameter, + PsiSubstitutor superSubstitutor + ) { + if (hasNullability(nullableManager, superParameter)) { + return false; + } + if (ContainerUtil.exists( + getSuperAnnotationOwners(superParameter), + superSuperParameter -> hasNullability(nullableManager, superSuperParameter) + )) { + return false; + } + PsiType type = superParameter.getType(); + if (TypeUtils.isTypeParameter(type)) { + PsiClass childClass = PsiUtil.getContainingClass(parameter); + PsiClass superClass = PsiUtil.getContainingClass(superParameter); + if (superClass != null && childClass != null) { + PsiSubstitutor substitutor = TypeConversionUtil.getMaybeSuperClassSubstitutor(superClass, childClass, superSubstitutor); + if (substitutor == null) { + // may happen if A extends B implements C and methods are declared in B and C + substitutor = superSubstitutor; + } + PsiType substituted = substitutor.substitute(type); + return DfaPsiUtil.getTypeNullability(substituted) == Nullability.UNKNOWN; + } + } + return true; } - if (skipExternal && AnnotationUtil.isExternalAnnotation(anno)) { - return false; + + private void checkNullLiteralArgumentOfNotNullParameterUsages( + PsiMethod method, + ProblemsHolder holder, + NullableNotNullManager nullableManager, + int parameterIdx, + PsiParameter parameter, + NullableStuffInspectionState state + ) { + if (!state.REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER || !holder.isOnTheFly()) { + return; + } + + PsiVariable owner = parameter.isPhysical() ? parameter : JavaPsiRecordUtil.getComponentForCanonicalConstructorParameter(parameter); + if (owner == null) { + return; + } + + PsiElement elementToHighlight = null; + NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(owner); + if (info != null && !info.isInferred()) { + if (info.getNullability() == Nullability.NOT_NULL) { + PsiAnnotation notNullAnnotation = info.getAnnotation(); + boolean physical = PsiTreeUtil.isAncestor(owner, notNullAnnotation, true); + elementToHighlight = physical ? notNullAnnotation : owner.getNameIdentifier(); + } + } + else { + info = DfaPsiUtil.getTypeNullabilityInfo(owner.getType()); + if (info != null && info.getNullability() == Nullability.NOT_NULL) { + elementToHighlight = owner.getNameIdentifier(); + } + } + if (elementToHighlight == null || !JavaNullMethodArgumentUtil.hasNullArgument(method, parameterIdx)) { + return; + } + + reportProblem( + holder, + elementToHighlight, + createNavigateToNullParameterUsagesFix(parameter), + JavaAnalysisLocalize.inspectionNullableProblemsNotnullParameterReceivesNullLiteral( + StringUtil.getShortName(Objects.requireNonNull(info.getAnnotation().getQualifiedName())) + ) + ); } - return true; - } - - public static boolean isNullableNotInferred(@Nonnull PsiModifierListOwner owner, boolean checkBases) { - Project project = owner.getProject(); - NullableNotNullManager manager = NullableNotNullManager.getInstance(project); - PsiAnnotation anno = manager.getNullableAnnotation(owner, checkBases); - if (anno == null) { - return false; + + @RequiredReadAction + private void checkOverriders( + PsiMethod method, + ProblemsHolder holder, + Annotated annotated, + NullableNotNullManager nullableManager, + NullableStuffInspectionState state + ) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (state.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS) { + boolean[] checkParameter = new boolean[parameters.length]; + boolean[] parameterQuickFixSuggested = new boolean[parameters.length]; + boolean hasAnnotatedParameter = false; + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + checkParameter[i] = isNotNullNotInferred(parameter, false, false) + && !hasInheritableNotNull(parameter) + && !(parameter.getType() instanceof PsiPrimitiveType); + hasAnnotatedParameter |= checkParameter[i]; + } + boolean checkReturnType = + annotated.isDeclaredNotNull && !hasInheritableNotNull(method) && !(method.getReturnType() instanceof PsiPrimitiveType); + if (hasAnnotatedParameter || checkReturnType) { + PsiMethod[] overridings = + OverridingMethodsSearch.search(method).toArray(PsiMethod.EMPTY_ARRAY); + boolean methodQuickFixSuggested = false; + for (PsiMethod overriding : overridings) { + if (shouldSkipOverriderAsGenerated(overriding)) { + continue; + } + + String defaultNotNull = nullableManager.getDefaultAnnotation(Nullability.NOT_NULL, overriding); + if (!methodQuickFixSuggested + && checkReturnType + && !isNotNullNotInferred(overriding, false, false) + && (isNullableNotInferred(overriding, false) || !isNullableNotInferred(overriding, true)) + && AddAnnotationPsiFix.isAvailable(overriding, defaultNotNull)) { + PsiIdentifier identifier = method.getNameIdentifier();//load tree + NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(method); + if (info != null) { + PsiAnnotation annotation = info.getAnnotation(); + String[] annotationsToRemove = ArrayUtil.toStringArray(nullableManager.getNullables()); + + LocalQuickFix fix = isAnnotatingApplicable(overriding, defaultNotNull) + ? new MyAnnotateMethodFix(Nullability.NOT_NULL, annotationsToRemove) + : null; + + PsiElement psiElement = annotation; + if (!annotation.isPhysical() || !PsiTreeUtil.isAncestor(method, annotation, true)) { + psiElement = identifier; + if (psiElement == null) { + continue; + } + } + reportProblem( + holder, + psiElement, + fix, + JavaAnalysisLocalize.nullableStuffProblemsOverriddenMethodsAreNotAnnotated() + ); + methodQuickFixSuggested = true; + } + } + if (hasAnnotatedParameter) { + PsiParameter[] psiParameters = overriding.getParameterList().getParameters(); + for (int i = 0; i < psiParameters.length; i++) { + if (parameterQuickFixSuggested[i]) { + continue; + } + PsiParameter parameter = psiParameters[i]; + if (checkParameter[i] && + !isNotNullNotInferred(parameter, false, false) && + !isNullableNotInferred(parameter, false) && + AddAnnotationPsiFix.isAvailable(parameter, defaultNotNull)) { + PsiIdentifier identifier = + parameters[i].getNameIdentifier(); //be sure that corresponding tree element available + NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameters[i]); + PsiElement psiElement = info == null ? null : info.getAnnotation(); + if (psiElement == null || !psiElement.isPhysical()) { + psiElement = identifier; + if (psiElement == null) { + continue; + } + } + LocalQuickFix fix = isAnnotatingApplicable(parameter, defaultNotNull) + ? new AnnotateOverriddenMethodParameterFix(Nullability.NOT_NULL) + : null; + reportProblem( + holder, + psiElement, + fix, + JavaAnalysisLocalize.nullableStuffProblemsOverriddenMethodParametersAreNotAnnotated() + ); + parameterQuickFixSuggested[i] = true; + } + } + } + } + } + } } - return DfaPsiUtil.getTypeNullability(getMemberType(owner)) == Nullability.NULLABLE || !AnnotationUtil.isInferredAnnotation(anno); - } - - private static PsiType getMemberType(@Nonnull PsiModifierListOwner owner) { - return owner instanceof PsiMethod ? ((PsiMethod) owner).getReturnType() : owner instanceof PsiVariable ? ((PsiVariable) owner).getType() : null; - } - - private static LocalQuickFix createChangeDefaultNotNullFix(NullableNotNullManager nullableManager, PsiModifierListOwner modifierListOwner) { - final PsiAnnotation annotation = AnnotationUtil.findAnnotation(modifierListOwner, nullableManager.getNotNulls()); - if (annotation != null) { - final PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); - if (referenceElement != null) { - JavaResolveResult resolveResult = referenceElement.advancedResolve(false); - if (resolveResult.getElement() != null && - resolveResult.isValidResult() && - !nullableManager.getDefaultNotNull().equals(annotation.getQualifiedName())) { - return new ChangeNullableDefaultsFix(annotation.getQualifiedName(), null, nullableManager); + @RequiredReadAction + public static boolean shouldSkipOverriderAsGenerated(PsiMethod overriding) { + if (Boolean.TRUE) { + return false; } - } + + PsiFile file = overriding.getContainingFile(); + VirtualFile virtualFile = file != null ? file.getVirtualFile() : null; + return virtualFile != null && GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(virtualFile, overriding.getProject()); } - return null; - } - - private static void reportNullableNotNullConflict(final ProblemsHolder holder, final PsiModifierListOwner listOwner, final PsiAnnotation declaredNullable, - final PsiAnnotation declaredNotNull) { - final String bothNullableNotNullMessage = InspectionsBundle.message("inspection.nullable.problems.Nullable.NotNull.conflict", - getPresentableAnnoName(declaredNullable), - getPresentableAnnoName(declaredNotNull)); - holder.registerProblem(declaredNotNull.isPhysical() ? declaredNotNull : listOwner.getNavigationElement(), - bothNullableNotNullMessage, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNotNull, listOwner)); - holder.registerProblem(declaredNullable.isPhysical() ? declaredNullable : listOwner.getNavigationElement(), - bothNullableNotNullMessage, - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveAnnotationQuickFix(declaredNullable, listOwner)); - } - - @Override - public JComponent createOptionsPanel() { - throw new RuntimeException("No UI in headless mode"); - } - - private static class MyAnnotateMethodFix extends AnnotateMethodFix { - MyAnnotateMethodFix(String defaultNotNull, String[] annotationsToRemove) { - super(defaultNotNull, annotationsToRemove); + + private static boolean isNotNullNotInferred(PsiModifierListOwner owner, boolean checkBases, boolean skipExternal) { + Project project = owner.getProject(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + if (info == null || info.isInferred() || info.getNullability() != Nullability.NOT_NULL) { + return false; + } + if (!checkBases && info.getInheritedFrom() != null) { + return false; + } + if (skipExternal && info.isExternal()) { + return false; + } + return true; } - @Nonnull - @Override - protected String getPreposition() { - return "as"; + public static boolean isNullableNotInferred(PsiModifierListOwner owner, boolean checkBases) { + Project project = owner.getProject(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(owner); + return info != null + && !info.isInferred() + && info.getNullability() == Nullability.NULLABLE + && (checkBases || info.getInheritedFrom() == null); } - @Override - protected boolean annotateOverriddenMethods() { - return true; + private static class MyAnnotateMethodFix implements LocalQuickFix { + private Nullability myNullability; + private String[] myAnnotationsToRemove; + + MyAnnotateMethodFix(Nullability nullability, String... annotationsToRemove) { + myNullability = nullability; + myAnnotationsToRemove = annotationsToRemove.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : annotationsToRemove; + } + +// @Override +// public String getFamilyName() { +// return JavaAnalysisLocalize.inspectionAnnotateOverriddenMethodQuickfixFamilyName().get(); +// } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + @RequiredUIAccess + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement psiElement = descriptor.getPsiElement(); + + PsiMethod method = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class); + if (method == null) { + return; + } + List toAnnotate = new ArrayList<>(); + NullableNotNullManager manager = NullableNotNullManager.getInstance(project); + + if (!AnnotateOverriddenMethodParameterFix.processModifiableInheritorsUnderProgress( + method, + (Consumer) psiMethod -> { + NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(psiMethod); + if (info != null && info.getNullability() == myNullability && !info.isInferred() && info.getInheritedFrom() == null) { + return; + } + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + if (isAnnotatingApplicable(psiMethod, annotation) + && !isAnnotated(psiMethod, annotation, CHECK_EXTERNAL | CHECK_TYPE)) { + toAnnotate.add(psiMethod); + } + } + )) { + return; + } + + FileModificationService.getInstance().preparePsiElementsForWrite(toAnnotate); + for (PsiMethod psiMethod : toAnnotate) { + String annotation = manager.getDefaultAnnotation(myNullability, psiMethod); + AddAnnotationPsiFix fix = new AddAnnotationPsiFix(annotation, psiMethod, myAnnotationsToRemove); + fix.invoke(psiMethod.getProject(), psiMethod.getContainingFile(), psiMethod, psiMethod); + } + LanguageUndoUtil.markPsiFileForUndo(method.getContainingFile()); + } + + @Override + public LocalizeValue getName() { + return JavaAnalysisLocalize.inspectionAnnotateOverriddenMethodNullableQuickfixName( + myNullability == Nullability.NOT_NULL ? "NotNull" : "Nullable" + ); + } } - @Override - protected boolean annotateSelf() { - return false; + private enum ConflictNestedTypeProblem { + RETURN_NESTED_TYPE_PROBLEM( + JavaAnalysisLocalize::returningAClassWithNotnullArguments, + JavaAnalysisLocalize::returningAClassWithNullableArguments + ), + ASSIGNMENT_NESTED_TYPE_PROBLEM( + JavaAnalysisLocalize::assigningAClassWithNotnullElements, + JavaAnalysisLocalize::assigningAClassWithNullableElements + ), + OVERRIDING_NESTED_TYPE_PROBLEM( + JavaAnalysisLocalize::overridingAClassWithNotnullElements, + JavaAnalysisLocalize::overridingAClassWithNullableElements + ); + + private Function notNullToNullProblemMessage; + private Function nullToNotNullProblemMessage; + private Function myComplexProblem; + + ConflictNestedTypeProblem(Function notNullToNullProblemMessage, Function nullToNotNullProblemMessage) { + this.notNullToNullProblemMessage = notNullToNullProblemMessage; + this.nullToNotNullProblemMessage = nullToNotNullProblemMessage; + this.myComplexProblem = JavaAnalysisLocalize::complexProblemWithNullability; + } + + LocalizeValue notNullToNullProblem(Object param) { + return notNullToNullProblemMessage.apply(param); + } + + LocalizeValue nullToNotNullProblem(Object param) { + return nullToNotNullProblemMessage.apply(param); + } + + LocalizeValue complexProblem(Object param) { + return myComplexProblem.apply(param); + } } - } -} +} \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionState.java new file mode 100644 index 0000000000..4d51da2ad8 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionState.java @@ -0,0 +1,86 @@ +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import consulo.application.Application; +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.util.xml.serializer.XmlSerializerUtil; +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 07/09/2023 + */ +public class NullableStuffInspectionState implements InspectionToolState { + @Deprecated + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL = true; + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = true; + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = true; + @Deprecated + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true; + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOT_ANNOTATED_GETTER = true; + @SuppressWarnings({"WeakerAccess"}) + public boolean IGNORE_EXTERNAL_SUPER_NOTNULL; + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED; + @Deprecated + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true; + @Deprecated + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test + @SuppressWarnings({"WeakerAccess"}) + public boolean REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD = true; + public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; + + public boolean REPORT_NULLABILITY_ANNOTATION_ON_LOCALS = true; + public boolean REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = false; + public boolean REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE = false; + + public boolean REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER = true; + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder builder = ConfigurableBuilder.newBuilder(); + builder.checkBox(JavaInspectionsLocalize.inspectionNullableProblemsMethodOverridesNotnullOption(), + () -> REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE, + b -> REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE = b); + builder.checkBox(JavaInspectionsLocalize.inspectionNullableProblemsMethodOverridesOption(), + () -> REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL, + b -> REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL = b); + builder.checkBox(LocalizeValue.localizeTODO("&Ignore external @NotNull"), + () -> IGNORE_EXTERNAL_SUPER_NOTNULL, + b -> IGNORE_EXTERNAL_SUPER_NOTNULL = b); + builder.checkBox(LocalizeValue.localizeTODO("Report @NotNull ¶meters overriding non-annotated"), + () -> REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED, + b -> REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = b); + builder.checkBox(JavaInspectionsLocalize.inspectionNullableProblemsNotAnnotatedGettersForAnnotatedFields(), + () -> REPORT_NOT_ANNOTATED_GETTER, + b -> REPORT_NOT_ANNOTATED_GETTER = b); + builder.checkBox(LocalizeValue.localizeTODO("Report @NotNull parameters with null-literal argument usages"), + () -> REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER, + b -> REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = b); + builder.component(() -> Application.get().getInstance(NullableNotNullDialogProxy.class).createConfigureAnnotationsButton()); + return builder.buildUnnamed(); + } + + @Nullable + @Override + public NullableStuffInspectionState getState() { + return this; + } + + @Override + public void loadState(NullableStuffInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionUtil.java new file mode 100644 index 0000000000..bf299671cd --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/nullable/NullableStuffInspectionUtil.java @@ -0,0 +1,213 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.java.analysis.impl.codeInspection.nullable; + +import com.intellij.java.analysis.JavaAnalysisBundle; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.util.JavaTypeNullabilityUtil; +import consulo.application.util.HtmlChunk; +import consulo.language.psi.PsiElement; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.util.collection.ArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +final class NullableStuffInspectionUtil { + static @NotNull HtmlChunk getNullabilityConflictPresentation(@NotNull JavaTypeNullabilityUtil.NullabilityConflictContext context) { + HtmlChunk expectedChunk = getSideChunk(context, JavaTypeNullabilityUtil.Side.EXPECTED); + HtmlChunk actualChunk = getSideChunk(context, JavaTypeNullabilityUtil.Side.ACTUAL); + if (expectedChunk.isEmpty() || actualChunk.isEmpty()) return HtmlChunk.empty(); + return HtmlChunk.tag("table") + .children(expectedChunk, actualChunk); + } + + private static @NotNull HtmlChunk getSideChunk(@NotNull JavaTypeNullabilityUtil.NullabilityConflictContext context, + @NotNull JavaTypeNullabilityUtil.Side side) { + PsiElement place = context.getPlace(side); + if (place == null) return HtmlChunk.empty(); + int dimensionsInArray = (context.getType(side) instanceof PsiArrayType type) ? type.getArrayDimensions() : 0; + + PsiTypeElement topmostType = PsiTreeUtil.getTopmostParentOfType(place, PsiTypeElement.class); + if (topmostType == null) return HtmlChunk.empty(); + + PsiTypeElement target = PsiTreeUtil.getParentOfType(place, PsiTypeElement.class, false); + if (target == null) return HtmlChunk.empty(); + + PsiType outerType = topmostType.getType(); + if (target == topmostType) { + // Presentation is not handling with the whole type + return HtmlChunk.empty(); + } + + List path = computeTypeArgumentPath(topmostType, target, dimensionsInArray); + if (path == null) return HtmlChunk.empty(); + + Context presentationContext = getPresentationContext(outerType, path, outerType instanceof PsiArrayType); + + String typeText = presentationContext.sb.toString(); + String annotationText = getAnnotationText(context, side); + if (presentationContext.position == null || annotationText == null) return HtmlChunk.empty(); + HtmlChunk result = generateHtmlChunk(typeText, "@" + annotationText, side, presentationContext.position); + return result; + } + + private static @Nullable String getAnnotationText(@NotNull JavaTypeNullabilityUtil.NullabilityConflictContext context, @NotNull JavaTypeNullabilityUtil.Side side) { + PsiAnnotation annotation = context.getAnnotation(side); + if (annotation == null) return null; + PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); + if (ref == null) return null; + return ref.getReferenceName(); + } + + private static @NotNull HtmlChunk generateHtmlChunk(String text, + String annotationText, + JavaTypeNullabilityUtil.Side side, + int position) { + return HtmlChunk.tag("tr") + .children( + HtmlChunk.tag("td") + .addText( + JavaAnalysisBundle.message( + side == JavaTypeNullabilityUtil.Side.EXPECTED ? "expected.type.nullability.conflict.message" + : "actual.type.nullability.conflict.message" + ) + ), + HtmlChunk.tag("td").children( + HtmlChunk.text(text.substring(0, position)), + HtmlChunk.tag("b").addText(annotationText), + HtmlChunk.text(" "), + HtmlChunk.text(text.substring(position)) + ) + ); + } + + private static @Nullable List<@NotNull Integer> computeTypeArgumentPath(@NotNull PsiTypeElement top, @NotNull PsiTypeElement target, int firstArrayDepth) { + List indices = new ArrayList<>(); + PsiTypeElement current = target; + boolean isFirstArrayOccurence = true; + while (true) { + PsiTypeElement parentTypeElement = PsiTreeUtil.getParentOfType(current, PsiTypeElement.class, true); + if (parentTypeElement == null) return null; + PsiType parentType = parentTypeElement.getType(); + if (parentType instanceof PsiClassType) { + PsiJavaCodeReferenceElement ref = parentTypeElement.getInnermostComponentReferenceElement(); + if (ref == null) return null; + PsiReferenceParameterList params = ref.getParameterList(); + if (params == null) return null; + PsiTypeElement[] args = params.getTypeParameterElements(); + int found = ArrayUtil.indexOf(args, current); + if (found < 0) return null; + indices.add(found); + } + else if (parentType instanceof PsiWildcardType || + parentType instanceof PsiCapturedWildcardType) { + indices.add(0); + } + else if (parentType instanceof PsiArrayType arrayType) { + int candidateArrayDepth = arrayType.getArrayDimensions(); + int numberToAdd; + if (isFirstArrayOccurence && candidateArrayDepth == firstArrayDepth) { + numberToAdd = 0; + } + else if (isFirstArrayOccurence && firstArrayDepth > 0) { + numberToAdd = firstArrayDepth; + } + else { + numberToAdd = candidateArrayDepth; + } + for (int i = 0; i < numberToAdd; i++) { + indices.add(0); + } + } + if (parentTypeElement == top) break; + current = parentTypeElement; + isFirstArrayOccurence = false; + } + Collections.reverse(indices); + return indices; + } + + private static @NotNull Context getPresentationContext(@NotNull PsiType type, List path, boolean isInsideArray) { + Context context = new Context(); + buildTextRepresentation(type, context, path, 0, isInsideArray); + return context; + } + + /** + * Constructs the simplified presentable view of the type and detects the place in which the nullability annotation should be inserted. + * The result of this method is stored in the {@link Context#sb} member. + * + * @param isInsideArray corresponds to the state whether the method is called within the array dimension. It is used to distinguish + * cases {@code @Nullable String[]} and {@code String @Nullable [][]}. + */ + private static void buildTextRepresentation(@NotNull PsiType type, + @NotNull Context context, + @NotNull List<@NotNull Integer> path, + int depth, + boolean isInsideArray) { + if (depth == path.size()) { + if (type instanceof PsiArrayType arrayType && isInsideArray) { + context.sb.append(arrayType.getPresentableText(false)); + context.sb.append(" "); + context.position = context.sb.length(); + } + else if (type instanceof PsiArrayType arrayType) { + context.sb.append(arrayType.getDeepComponentType().getPresentableText(false)); + context.sb.append(" "); + context.position = context.sb.length(); + context.sb.repeat("[]", Math.max(0, arrayType.getArrayDimensions())); + } + else { + String text = type.getPresentableText(false); + context.position = context.sb.length(); + context.sb.append(text); + } + return; + } + if (type instanceof PsiArrayType arrayType) { + PsiType component = arrayType.getComponentType(); + buildTextRepresentation(component, context, path, depth + 1, true); + context.sb.append("[]"); + return; + } + if (type instanceof PsiWildcardType wc) { + String prefix = wc.isExtends() ? "? extends " : wc.isSuper() ? "? super " : "?"; + context.sb.append(prefix); + PsiType bound = wc.getBound(); + if (bound != null) { + buildTextRepresentation(bound, context, path, depth + 1, false); + } + return; + } + if (type instanceof PsiClassType cls) { + String name = cls.getClassName(); + if (name == null) name = cls.getPresentableText(false); + context.sb.append(name); + PsiType[] params = cls.getParameters(); + if (params.length > 0) { + context.sb.append('<'); + for (int i = 0; i < params.length; i++) { + if (i > 0) context.sb.append(", "); + if (i == path.get(depth)) { + buildTextRepresentation(params[i], context, path, depth + 1, false); + } + else { + context.sb.append(params[i].getPresentableText(false)); + } + } + context.sb.append('>'); + } + return; + } + + context.sb.append(type.getPresentableText(false)); + } + + private static final class Context { + private @Nullable Integer position = null; + private final @NotNull StringBuilder sb = new StringBuilder(); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspection.java index 5f6586a611..85d9a04c8e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspection.java @@ -15,30 +15,27 @@ */ package com.intellij.java.analysis.impl.codeInspection.redundantCast; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.analysis.impl.codeInspection.miscGenerics.GenericsInspectionToolBase; import com.intellij.java.analysis.impl.codeInspection.miscGenerics.SuspiciousMethodCallUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiExpressionTrimRenderer; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.RedundantCastUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.annotation.component.ExtensionImpl; -import consulo.language.editor.inspection.InspectionsBundle; +import consulo.language.editor.inspection.InspectionToolState; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.inspection.ProblemHighlightType; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.inspection.scheme.InspectionManager; -import consulo.language.editor.inspection.ui.MultipleCheckboxOptionsPanel; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import consulo.util.xml.serializer.WriteExternalException; -import org.jdom.Element; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; import java.util.ArrayList; import java.util.List; @@ -46,126 +43,126 @@ * @author max */ @ExtensionImpl -public class RedundantCastInspection extends GenericsInspectionToolBase { - private final LocalQuickFix myQuickFixAction; - private static final String DISPLAY_NAME = InspectionsBundle.message("inspection.redundant.cast.display.name"); - @NonNls - private static final String SHORT_NAME = "RedundantCast"; - - public boolean IGNORE_SUSPICIOUS_METHOD_CALLS; - - - public RedundantCastInspection() { - myQuickFixAction = new AcceptSuggested(); - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Override - @Nullable - public ProblemDescriptor[] getDescriptions(@Nonnull PsiElement where, @Nonnull InspectionManager manager, boolean isOnTheFly) { - List redundantCasts = RedundantCastUtil.getRedundantCastsInside(where); - if (redundantCasts.isEmpty()) { - return null; +public class RedundantCastInspection extends GenericsInspectionToolBase { + private static final String SHORT_NAME = "RedundantCast"; + + private final LocalQuickFix myQuickFixAction = new AcceptSuggested(); + + @Override + public InspectionToolState createStateProvider() { + return new RedundantCastInspectionState(); } - List descriptions = new ArrayList<>(redundantCasts.size()); - for (PsiTypeCastExpression redundantCast : redundantCasts) { - ProblemDescriptor descriptor = createDescription(redundantCast, manager, isOnTheFly); - if (descriptor != null) { - descriptions.add(descriptor); - } + + @Override + public boolean isEnabledByDefault() { + return true; } - if (descriptions.isEmpty()) { - return null; + + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; } - return descriptions.toArray(ProblemDescriptor.EMPTY_ARRAY); - } - - @Override - public ProblemDescriptor[] checkField(@Nonnull PsiField field, @Nonnull InspectionManager manager, boolean isOnTheFly) { - return getDescriptions(field, manager, isOnTheFly); - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - if (IGNORE_SUSPICIOUS_METHOD_CALLS) { - super.writeSettings(node); + + @Nullable + @Override + @RequiredReadAction + public ProblemDescriptor[] getDescriptions( + PsiElement where, + InspectionManager manager, + boolean isOnTheFly, + RedundantCastInspectionState state + ) { + List redundantCasts = RedundantCastUtil.getRedundantCastsInside(where); + if (redundantCasts.isEmpty()) { + return null; + } + List descriptions = new ArrayList<>(redundantCasts.size()); + for (PsiTypeCastExpression redundantCast : redundantCasts) { + ProblemDescriptor descriptor = createDescription(redundantCast, manager, isOnTheFly, state); + if (descriptor != null) { + descriptions.add(descriptor); + } + } + if (descriptions.isEmpty()) { + return null; + } + return descriptions.toArray(ProblemDescriptor.EMPTY_ARRAY); } - } - - @Override - public JComponent createOptionsPanel() { - final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); - optionsPanel.addCheckbox("Ignore casts in suspicious collections method calls", "IGNORE_SUSPICIOUS_METHOD_CALLS"); - return optionsPanel; - } - - @Nullable - private ProblemDescriptor createDescription(@Nonnull PsiTypeCastExpression cast, @Nonnull InspectionManager manager, boolean onTheFly) { - PsiExpression operand = cast.getOperand(); - PsiTypeElement castType = cast.getCastType(); - if (operand == null || castType == null) { - return null; + + @Override + @RequiredReadAction + public ProblemDescriptor[] checkField( + PsiField field, + InspectionManager manager, + boolean isOnTheFly, + RedundantCastInspectionState state + ) { + return getDescriptions(field, manager, isOnTheFly, state); } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(cast.getParent()); - if (parent instanceof PsiExpressionList) { - final PsiElement gParent = parent.getParent(); - if (gParent instanceof PsiMethodCallExpression && IGNORE_SUSPICIOUS_METHOD_CALLS) { - final String message = SuspiciousMethodCallUtil - .getSuspiciousMethodCallMessage((PsiMethodCallExpression) gParent, operand, operand.getType(), true, new ArrayList<>(), 0); - if (message != null) { - return null; + + @Nullable + @RequiredReadAction + private ProblemDescriptor createDescription( + PsiTypeCastExpression cast, + InspectionManager manager, + boolean onTheFly, + RedundantCastInspectionState state + ) { + PsiExpression operand = cast.getOperand(); + PsiTypeElement castType = cast.getCastType(); + if (operand == null || castType == null) { + return null; } - } + if (PsiUtil.skipParenthesizedExprUp(cast.getParent()) instanceof PsiExpressionList exprList + && exprList.getParent() instanceof PsiMethodCallExpression methodCall + && state.IGNORE_SUSPICIOUS_METHOD_CALLS) { + String message = SuspiciousMethodCallUtil + .getSuspiciousMethodCallMessage(methodCall, operand, operand.getType(), true, new ArrayList<>(), 0); + if (message != null) { + return null; + } + } + + return manager.newProblemDescriptor(InspectionLocalize.inspectionRedundantCastProblemDescriptor( + "" + PsiExpressionTrimRenderer.render(operand) + "", + "#ref #loc" + )) + .range(castType) + .highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL) + .onTheFly(onTheFly) + .withFix(myQuickFixAction) + .create(); } - String message = InspectionsBundle.message("inspection.redundant.cast.problem.descriptor", - "" + PsiExpressionTrimRenderer.render(operand) + "", "#ref #loc"); - return manager.createProblemDescriptor(castType, message, myQuickFixAction, ProblemHighlightType.LIKE_UNUSED_SYMBOL, onTheFly); - } + private static class AcceptSuggested implements LocalQuickFix { + @Override + public LocalizeValue getName() { + return InspectionLocalize.inspectionRedundantCastRemoveQuickfix(); + } + @Override + @RequiredWriteAction + public void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement castTypeElement = descriptor.getPsiElement(); + PsiTypeCastExpression cast = castTypeElement == null ? null : (PsiTypeCastExpression) castTypeElement.getParent(); + if (cast != null) { + RemoveRedundantCastUtil.removeCast(cast); + } + } + } + + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionRedundantCastDisplayName(); + } - private static class AcceptSuggested implements LocalQuickFix { @Override - @Nonnull - public String getFamilyName() { - return InspectionsBundle.message("inspection.redundant.cast.remove.quickfix"); + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesVerboseOrRedundantCodeConstructs(); } @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { - PsiElement castTypeElement = descriptor.getPsiElement(); - PsiTypeCastExpression cast = castTypeElement == null ? null : (PsiTypeCastExpression) castTypeElement.getParent(); - if (cast != null) { - RemoveRedundantCastUtil.removeCast(cast); - } + public String getShortName() { + return SHORT_NAME; } - } - - @Override - @Nonnull - public String getDisplayName() { - return DISPLAY_NAME; - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.VERBOSE_GROUP_NAME; - } - - @Override - @Nonnull - public String getShortName() { - return SHORT_NAME; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspectionState.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspectionState.java new file mode 100644 index 0000000000..fb8b39b625 --- /dev/null +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/redundantCast/RedundantCastInspectionState.java @@ -0,0 +1,39 @@ +package com.intellij.java.analysis.impl.codeInspection.redundantCast; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 20/03/2023 + */ +public class RedundantCastInspectionState implements InspectionToolState { + public boolean IGNORE_SUSPICIOUS_METHOD_CALLS; + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder builder = ConfigurableBuilder.newBuilder(); + builder.checkBox(LocalizeValue.localizeTODO("Ignore casts in suspicious collections method calls"), + () -> IGNORE_SUSPICIOUS_METHOD_CALLS, + b -> IGNORE_SUSPICIOUS_METHOD_CALLS = b); + return builder.buildUnnamed(); + } + + @Nullable + @Override + public RedundantCastInspectionState getState() { + return this; + } + + @Override + public void loadState(RedundantCastInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefClassImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefClassImpl.java index 2ca6c289e6..37b21073c0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefClassImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefClassImpl.java @@ -45,8 +45,7 @@ import consulo.language.util.ModuleUtilCore; import consulo.module.Module; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; public class RefClassImpl extends RefJavaElementImpl implements RefClass { @@ -291,7 +290,6 @@ public void accept(final RefVisitor visitor) { } @Override - @Nonnull public Set getBaseClasses() { if (myBases == null) { return EMPTY_CLASS_SET; @@ -312,7 +310,6 @@ private void addBaseClass(RefClass refClass) { } @Override - @Nonnull public Set getSubClasses() { if (mySubClasses == null) { return EMPTY_CLASS_SET; @@ -320,7 +317,7 @@ public Set getSubClasses() { return mySubClasses; } - private void addSubClass(@Nonnull RefClass refClass) { + private void addSubClass(RefClass refClass) { if (mySubClasses == null) { mySubClasses = Collections.singleton(refClass); return; @@ -344,7 +341,6 @@ private void removeSubClass(RefClass refClass) { } @Override - @Nonnull public List getConstructors() { if (myConstructors == null) { return EMPTY_METHOD_LIST; @@ -353,7 +349,6 @@ public List getConstructors() { } @Override - @Nonnull public Set getInTypeReferences() { if (myInTypeReferences == null) { return EMPTY_SET; @@ -367,13 +362,12 @@ public void addTypeReference(RefJavaElement from) { myInTypeReferences = new HashSet<>(1); } myInTypeReferences.add(from); - ((RefJavaElementImpl) from).addOutTypeRefernce(this); + ((RefJavaElementImpl) from).addOutTypeReference(this); getRefManager().fireNodeMarkedReferenced(this, from, false, false, false); } } @Override - @Nonnull public Set getInstanceReferences() { if (myInstanceReferences == null) { return EMPTY_SET; @@ -408,7 +402,6 @@ public void addLibraryOverrideMethod(RefMethod refMethod) { } @Override - @Nonnull public List getLibraryMethods() { if (myOverridingMethods == null) { return EMPTY_METHOD_LIST; @@ -590,7 +583,6 @@ private void setIsLocal(boolean isLocal) { } @Override - @Nonnull public RefElement getContainingEntry() { RefElement defaultConstructor = getDefaultConstructor(); if (defaultConstructor != null) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefImplicitConstructorImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefImplicitConstructorImpl.java index b6a14ccbb1..0607819988 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefImplicitConstructorImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefImplicitConstructorImpl.java @@ -13,87 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * Created by IntelliJ IDEA. - * User: max - * Date: Nov 28, 2001 - * Time: 4:17:17 PM - * To change template for new class use - * Code Style | Class Templates options (Tools | IDE Options). - */ package com.intellij.java.analysis.impl.codeInspection.reference; -import consulo.language.editor.inspection.InspectionsBundle; import com.intellij.java.analysis.codeInspection.reference.RefClass; import com.intellij.java.analysis.codeInspection.reference.RefImplicitConstructor; import com.intellij.java.analysis.codeInspection.reference.RefJavaUtil; -import consulo.application.ApplicationManager; -import consulo.application.util.function.Computable; -import consulo.language.psi.PsiFile; import com.intellij.java.language.psi.PsiModifierListOwner; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.ReadAction; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; +import consulo.language.psi.PsiFile; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; - +/** + * @author max + * @since 2001-11-28 + */ public class RefImplicitConstructorImpl extends RefMethodImpl implements RefImplicitConstructor { + RefImplicitConstructorImpl(RefClass ownerClass) { + super(JavaInspectionsLocalize.inspectionReferenceImplicitConstructorName(ownerClass.getName()), ownerClass); + } - RefImplicitConstructorImpl(RefClass ownerClass) { - super(InspectionsBundle.message("inspection.reference.implicit.constructor.name", ownerClass.getName()), ownerClass); - } - - @Override - public void buildReferences() { - getRefManager().fireBuildReferences(this); - } + @Override + @RequiredReadAction + public void buildReferences() { + getRefManager().fireBuildReferences(this); + } - @Override - public boolean isSuspicious() { - return ((RefClassImpl) getOwnerClass()).isSuspicious(); - } + @Override + public boolean isSuspicious() { + return ((RefClassImpl) getOwnerClass()).isSuspicious(); + } - @Override - public String getName() { - return InspectionsBundle.message("inspection.reference.implicit.constructor.name", getOwnerClass().getName()); - } + @Override + public String getName() { + return JavaInspectionsLocalize.inspectionReferenceImplicitConstructorName(getOwnerClass().getName()).get(); + } - @Override - public String getExternalName() { - return getOwnerClass().getExternalName(); - } + @Override + public String getExternalName() { + return getOwnerClass().getExternalName(); + } - @Override - public boolean isValid() { - return ApplicationManager.getApplication().runReadAction(new Computable() { - @Override - public Boolean compute() { - return getOwnerClass().isValid(); - } - }).booleanValue(); - } + @Override + public boolean isValid() { + return ReadAction.compute(() -> getOwnerClass().isValid()); + } - @Override - public String getAccessModifier() { - return getOwnerClass().getAccessModifier(); - } + @Override + public String getAccessModifier() { + return getOwnerClass().getAccessModifier(); + } - @Override - public void setAccessModifier(String am) { - RefJavaUtil.getInstance().setAccessModifier(getOwnerClass(), am); - } + @Override + public void setAccessModifier(String am) { + RefJavaUtil.getInstance().setAccessModifier(getOwnerClass(), am); + } - @Override - public PsiModifierListOwner getElement() { - return getOwnerClass().getElement(); - } + @Override + public PsiModifierListOwner getElement() { + return getOwnerClass().getElement(); + } - @Override - @Nullable - public PsiFile getContainingFile() { - return ((RefClassImpl) getOwnerClass()).getContainingFile(); - } + @Nullable + @Override + public PsiFile getContainingFile() { + return getOwnerClass().getContainingFile(); + } - @Override - public RefClass getOwnerClass() { - return myOwnerClass == null ? super.getOwnerClass() : myOwnerClass; - } + @Override + public RefClass getOwnerClass() { + return myOwnerClass == null ? super.getOwnerClass() : myOwnerClass; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefJavaElementImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefJavaElementImpl.java index 05abd02e13..f6bfa2146e 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefJavaElementImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefJavaElementImpl.java @@ -13,289 +13,332 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * User: anna - * Date: 20-Dec-2007 - */ package com.intellij.java.analysis.impl.codeInspection.reference; import com.intellij.java.analysis.codeInspection.reference.*; import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiFormatUtil; +import com.intellij.java.language.psi.util.PsiFormatUtilBase; +import consulo.annotation.access.RequiredReadAction; import consulo.component.util.Iconable; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; import consulo.language.editor.impl.inspection.reference.RefElementImpl; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefManager; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiNamedElement; import consulo.language.psi.SyntheticElement; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.ui.image.Image; import consulo.util.collection.Stack; import consulo.virtualFileSystem.VirtualFileManager; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; +/** + * @author anna + * @since 2007-12-20 + */ public abstract class RefJavaElementImpl extends RefElementImpl implements RefJavaElement { - private Set myOutTypeReferences; - private static final int ACCESS_MODIFIER_MASK = 0x03; - private static final int ACCESS_PRIVATE = 0x00; - private static final int ACCESS_PROTECTED = 0x01; - private static final int ACCESS_PACKAGE = 0x02; - private static final int ACCESS_PUBLIC = 0x03; - private static final int IS_STATIC_MASK = 0x04; - private static final int IS_FINAL_MASK = 0x08; - private static final int IS_USES_DEPRECATION_MASK = 0x200; - private static final int IS_SYNTHETIC_JSP_ELEMENT = 0x400; - - protected RefJavaElementImpl(String name, RefJavaElement owner) { - super(name, owner); - String am = owner.getAccessModifier(); - doSetAccessModifier(am); - - final boolean synthOwner = owner.isSyntheticJSP(); - if (synthOwner) { - setSyntheticJSP(true); + private Set myOutTypeReferences; + private static final int ACCESS_MODIFIER_MASK = 0x03; + private static final int ACCESS_PRIVATE = 0x00; + private static final int ACCESS_PROTECTED = 0x01; + private static final int ACCESS_PACKAGE = 0x02; + private static final int ACCESS_PUBLIC = 0x03; + private static final int IS_STATIC_MASK = 0x04; + private static final int IS_FINAL_MASK = 0x08; + private static final int IS_USES_DEPRECATION_MASK = 0x200; + private static final int IS_SYNTHETIC_JSP_ELEMENT = 0x400; + + protected RefJavaElementImpl(LocalizeValue name, RefJavaElement owner) { + super(name.get(), owner); + String am = owner.getAccessModifier(); + doSetAccessModifier(am); + + boolean synthOwner = owner.isSyntheticJSP(); + if (synthOwner) { + setSyntheticJSP(true); + } + } + + protected RefJavaElementImpl(PsiFile file, RefManager manager) { + super(file, manager); } - } - protected RefJavaElementImpl(PsiFile file, RefManager manager) { - super(file, manager); - } + @RequiredReadAction + protected RefJavaElementImpl(PsiModifierListOwner elem, RefManager manager) { + super(getName(elem).get(), elem, manager); - protected RefJavaElementImpl(PsiModifierListOwner elem, RefManager manager) { - super(getName(elem), elem, manager); + setAccessModifier(RefJavaUtil.getInstance().getAccessModifier(elem)); + boolean isSynth = elem instanceof PsiMethod && elem instanceof SyntheticElement || elem instanceof PsiSyntheticClass; + if (isSynth) { + setSyntheticJSP(true); + } - setAccessModifier(RefJavaUtil.getInstance().getAccessModifier(elem)); - final boolean isSynth = elem instanceof PsiMethod && elem instanceof SyntheticElement || elem instanceof PsiSyntheticClass; - if (isSynth) { - setSyntheticJSP(true); + setIsStatic(elem.hasModifierProperty(PsiModifier.STATIC)); + setIsFinal(elem.hasModifierProperty(PsiModifier.FINAL)); } - setIsStatic(elem.hasModifierProperty(PsiModifier.STATIC)); - setIsFinal(elem.hasModifierProperty(PsiModifier.FINAL)); - } + @Override + public Collection getOutTypeReferences() { + if (myOutTypeReferences == null) { + return Collections.emptySet(); + } + return myOutTypeReferences; + } - @Override - @Nonnull - public Collection getOutTypeReferences() { - if (myOutTypeReferences == null) { - return Collections.emptySet(); + public void addOutTypeReference(RefClass refClass) { + if (myOutTypeReferences == null) { + myOutTypeReferences = new HashSet<>(); + } + myOutTypeReferences.add(refClass); } - return myOutTypeReferences; - } - public void addOutTypeRefernce(RefClass refClass) { - if (myOutTypeReferences == null) { - myOutTypeReferences = new HashSet(); + @RequiredReadAction + public static LocalizeValue getName(PsiElement element) { + if (element instanceof PsiAnonymousClass psiAnonymousClass) { + String name = psiAnonymousClass.getBaseClassType().resolve() instanceof PsiClass psiBaseClass ? psiBaseClass.getName() : null; + return name == null + ? JavaInspectionsLocalize.inspectionReferenceAnonymousClass() + : JavaInspectionsLocalize.inspectionReferenceAnonymousName(name); + } + + if (element instanceof PsiSyntheticClass jspClass) { + PsiFile jspxFile = jspClass.getContainingFile(); + return JavaInspectionsLocalize.inspectionReferenceJspSyntheticClassName(jspxFile.getName()); + } + + if (element instanceof PsiMethod method) { + if (element instanceof SyntheticElement) { + return JavaInspectionsLocalize.inspectionReferenceJspHolderMethodAnonymousName(); + } + + return LocalizeValue.of(PsiFormatUtil.formatMethod( + method, + PsiSubstitutor.EMPTY, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE + )); + } + + if (element instanceof PsiLambdaExpression || element instanceof PsiMethodReferenceExpression) { + boolean isMethodReference = element instanceof PsiMethodReferenceExpression; + PsiElement parentDeclaration = PsiTreeUtil.getParentOfType( + element, + PsiMethod.class, + PsiClass.class, + PsiLambdaExpression.class, + PsiField.class + ); + String name = parentDeclaration instanceof PsiNamedElement namedDeclaration ? namedDeclaration.getName() : null; + if (name != null) { + return isMethodReference + ? JavaInspectionsLocalize.inspectionReferenceMethodReferenceName(name) + : JavaInspectionsLocalize.inspectionReferenceLambdaName(name); + } + return isMethodReference + ? JavaInspectionsLocalize.inspectionReferenceDefaultMethodReferenceName() + : JavaInspectionsLocalize.inspectionReferenceDefaultLambdaName(); + } + + String name = element instanceof PsiNamedElement namedElement ? namedElement.getName() : null; + return name == null ? JavaInspectionsLocalize.inspectionReferenceAnonymous() : LocalizeValue.of(name); + } + + @Override + public boolean isFinal() { + return checkFlag(IS_FINAL_MASK); } - myOutTypeReferences.add(refClass); - } - - public static String getName(PsiElement element) { - if (element instanceof PsiAnonymousClass) { - PsiAnonymousClass psiAnonymousClass = (PsiAnonymousClass) element; - PsiClass psiBaseClass = psiAnonymousClass.getBaseClassType().resolve(); - return InspectionsBundle.message("inspection.reference.anonymous.name", psiBaseClass == null ? "" : psiBaseClass.getQualifiedName()); + + @Override + public boolean isStatic() { + return checkFlag(IS_STATIC_MASK); + } + + public void setIsStatic(boolean isStatic) { + setFlag(isStatic, IS_STATIC_MASK); } - if (element instanceof PsiSyntheticClass) { - final PsiSyntheticClass jspClass = (PsiSyntheticClass) element; - final PsiFile jspxFile = jspClass.getContainingFile(); - return "<" + jspxFile.getName() + ">"; + @Override + public boolean isUsesDeprecatedApi() { + return checkFlag(IS_USES_DEPRECATION_MASK); } - if (element instanceof PsiMethod && element instanceof SyntheticElement) { - return InspectionsBundle.message("inspection.reference.jsp.holder.method.anonymous.name"); + public void setUsesDeprecatedApi(boolean usesDeprecatedApi) { + setFlag(usesDeprecatedApi, IS_USES_DEPRECATION_MASK); } - String name = null; - if (element instanceof PsiNamedElement) { - name = ((PsiNamedElement) element).getName(); + public void setIsFinal(boolean isFinal) { + setFlag(isFinal, IS_FINAL_MASK); } - return name == null ? InspectionsBundle.message("inspection.reference.anonymous") : name; - } - - @Override - public boolean isFinal() { - return checkFlag(IS_FINAL_MASK); - } - - @Override - public boolean isStatic() { - return checkFlag(IS_STATIC_MASK); - } - - public void setIsStatic(boolean isStatic) { - setFlag(isStatic, IS_STATIC_MASK); - } - - @Override - public boolean isUsesDeprecatedApi() { - return checkFlag(IS_USES_DEPRECATION_MASK); - } - - public void setUsesDeprecatedApi(boolean usesDeprecatedApi) { - setFlag(usesDeprecatedApi, IS_USES_DEPRECATION_MASK); - } - - public void setIsFinal(boolean isFinal) { - setFlag(isFinal, IS_FINAL_MASK); - } - - public void setReachable(boolean reachable) { - setFlag(reachable, IS_REACHABLE_MASK); - } - - @Override - public boolean isSyntheticJSP() { - return checkFlag(IS_SYNTHETIC_JSP_ELEMENT); - } - - public void setSyntheticJSP(boolean b) { - setFlag(b, IS_SYNTHETIC_JSP_ELEMENT); - } - - @Override - @Nullable - public String getAccessModifier() { - long access_id = myFlags & ACCESS_MODIFIER_MASK; - if (access_id == ACCESS_PRIVATE) { - return PsiModifier.PRIVATE; + @Override + public void setReachable(boolean reachable) { + setFlag(reachable, IS_REACHABLE_MASK); } - if (access_id == ACCESS_PUBLIC) { - return PsiModifier.PUBLIC; + + @Override + public boolean isSyntheticJSP() { + return checkFlag(IS_SYNTHETIC_JSP_ELEMENT); } - if (access_id == ACCESS_PACKAGE) { - return PsiModifier.PACKAGE_LOCAL; + + public void setSyntheticJSP(boolean b) { + setFlag(b, IS_SYNTHETIC_JSP_ELEMENT); } - return PsiModifier.PROTECTED; - } - - public void setAccessModifier(String am) { - doSetAccessModifier(am); - } - - private void doSetAccessModifier(String am) { - final int access_id; - - if (PsiModifier.PRIVATE.equals(am)) { - access_id = ACCESS_PRIVATE; - } else if (PsiModifier.PUBLIC.equals(am)) { - access_id = ACCESS_PUBLIC; - } else if (PsiModifier.PACKAGE_LOCAL.equals(am)) { - access_id = ACCESS_PACKAGE; - } else { - access_id = ACCESS_PROTECTED; + + @Override + @Nullable + public String getAccessModifier() { + long accessId = myFlags & ACCESS_MODIFIER_MASK; + if (accessId == ACCESS_PRIVATE) { + return PsiModifier.PRIVATE; + } + if (accessId == ACCESS_PUBLIC) { + return PsiModifier.PUBLIC; + } + if (accessId == ACCESS_PACKAGE) { + return PsiModifier.PACKAGE_LOCAL; + } + return PsiModifier.PROTECTED; } - myFlags = myFlags & ~0x3 | access_id; - } + public void setAccessModifier(String am) { + doSetAccessModifier(am); + } - public boolean isSuspiciousRecursive() { - return isCalledOnlyFrom(this, new Stack()); - } + private void doSetAccessModifier(String am) { + int accessId = switch (am) { + case PsiModifier.PRIVATE -> ACCESS_PRIVATE; + case PsiModifier.PUBLIC -> ACCESS_PUBLIC; + case PsiModifier.PACKAGE_LOCAL -> ACCESS_PACKAGE; + default -> ACCESS_PROTECTED; + }; - private boolean isCalledOnlyFrom(RefJavaElement refElement, Stack callStack) { - if (callStack.contains(this)) { - return refElement == this; + myFlags = myFlags & ~0x3 | accessId; } - if (getInReferences().isEmpty()) { - return false; + + public boolean isSuspiciousRecursive() { + return isCalledOnlyFrom(this, new Stack<>()); } - if (refElement instanceof RefMethod) { - RefMethod refMethod = (RefMethod) refElement; - for (RefMethod refSuper : refMethod.getSuperMethods()) { - if (!refSuper.getInReferences().isEmpty()) { - return false; + private boolean isCalledOnlyFrom(RefJavaElement refElement, Stack callStack) { + if (callStack.contains(this)) { + return refElement == this; } - } - if (refMethod.isConstructor()) { - boolean unreachable = true; - for (RefElement refOut : refMethod.getOutReferences()) { - unreachable &= !refOut.isReachable(); + if (getInReferences().isEmpty()) { + return false; } - if (unreachable) { - return true; + + if (refElement instanceof RefMethod refMethod) { + for (RefMethod refSuper : refMethod.getSuperMethods()) { + if (!refSuper.getInReferences().isEmpty()) { + return false; + } + } + if (refMethod.isConstructor()) { + boolean unreachable = true; + for (RefElement refOut : refMethod.getOutReferences()) { + unreachable &= !refOut.isReachable(); + } + if (unreachable) { + return true; + } + } + } + + callStack.push(this); + for (RefElement refCaller : getInReferences()) { + if (!((RefElementImpl) refCaller).isSuspicious() || !((RefJavaElementImpl) refCaller).isCalledOnlyFrom(refElement, callStack)) { + callStack.pop(); + return false; + } } - } - } - callStack.push(this); - for (RefElement refCaller : getInReferences()) { - if (!((RefElementImpl) refCaller).isSuspicious() || !((RefJavaElementImpl) refCaller).isCalledOnlyFrom(refElement, callStack)) { callStack.pop(); - return false; - } + return true; } - callStack.pop(); - return true; - } - - public void addReference(RefElement refWhat, PsiElement psiWhat, PsiElement psiFrom, boolean forWriting, boolean forReading, PsiReferenceExpression expression) { - if (refWhat != null) { - if (refWhat instanceof RefParameter) { - if (forWriting) { - ((RefParameter) refWhat).parameterReferenced(true); - } - if (forReading) { - ((RefParameter) refWhat).parameterReferenced(false); + public void addReference( + RefElement refWhat, + PsiElement psiWhat, + PsiElement psiFrom, + boolean forWriting, + boolean forReading, + PsiReferenceExpression expression + ) { + if (refWhat != null) { + if (refWhat instanceof RefParameter refParameter) { + if (forWriting) { + refParameter.parameterReferenced(true); + } + // TODO: else if? + if (forReading) { + refParameter.parameterReferenced(false); + } + } + addOutReference(refWhat); + ((RefJavaElementImpl) refWhat).markReferenced(this, psiFrom, psiWhat, forWriting, forReading, expression); } - } - addOutReference(refWhat); - ((RefJavaElementImpl) refWhat).markReferenced(this, psiFrom, psiWhat, forWriting, forReading, expression); - } else { - if (psiWhat instanceof PsiMethod) { - final PsiClass containingClass = ((PsiMethod) psiWhat).getContainingClass(); - if (containingClass != null && containingClass.isEnum() && "values".equals(((PsiMethod) psiWhat).getName())) { - for (PsiField enumConstant : containingClass.getFields()) { - if (enumConstant instanceof PsiEnumConstant) { - final RefJavaElementImpl enumConstantReference = (RefJavaElementImpl) getRefManager().getReference(enumConstant); - if (enumConstantReference != null) { - addOutReference(enumConstantReference); - enumConstantReference.markReferenced(this, psiFrom, enumConstant, false, true, expression); - } + else if (psiWhat instanceof PsiMethod method) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && containingClass.isEnum() && "values".equals(method.getName())) { + for (PsiField enumConstant : containingClass.getFields()) { + if (enumConstant instanceof PsiEnumConstant) { + RefJavaElementImpl enumConstantReference = (RefJavaElementImpl) getRefManager().getReference(enumConstant); + if (enumConstantReference != null) { + addOutReference(enumConstantReference); + enumConstantReference.markReferenced(this, psiFrom, enumConstant, false, true, expression); + } + } + } } - } } - } } - } - - protected void markReferenced(final RefElementImpl refFrom, PsiElement psiFrom, PsiElement psiWhat, final boolean forWriting, boolean forReading, PsiReferenceExpression expressionFrom) { - addInReference(refFrom); - getRefManager().fireNodeMarkedReferenced(this, refFrom, false, forReading, forWriting); - } - - protected RefJavaManager getRefJavaManager() { - return getRefManager().getExtension(RefJavaManager.MANAGER); - } - - @Override - public void referenceRemoved() { - super.referenceRemoved(); - if (isEntry()) { - getRefJavaManager().getEntryPointsManager().removeEntryPoint(this); + + protected void markReferenced( + RefElementImpl refFrom, + PsiElement psiFrom, + PsiElement psiWhat, + boolean forWriting, + boolean forReading, + PsiReferenceExpression expressionFrom + ) { + addInReference(refFrom); + getRefManager().fireNodeMarkedReferenced(this, refFrom, false, forReading, forWriting); + } + + protected RefJavaManager getRefJavaManager() { + return getRefManager().getExtension(RefJavaManager.MANAGER); } - } - - @Override - public Image getIcon(final boolean expanded) { - if (isSyntheticJSP()) { - final PsiElement element = getPsiElement(); - if (element != null && element.isValid()) { - return VirtualFileManager.getInstance().getFileIcon(element.getContainingFile().getVirtualFile(), element.getProject(), Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS); - } + + @Override + public void referenceRemoved() { + super.referenceRemoved(); + if (isEntry()) { + getRefJavaManager().getEntryPointsManager().removeEntryPoint(this); + } + } + + @Override + @RequiredReadAction + public Image getIcon(boolean expanded) { + if (isSyntheticJSP()) { + PsiElement element = getPsiElement(); + if (element != null && element.isValid()) { + return VirtualFileManager.getInstance().getFileIcon( + element.getContainingFile().getVirtualFile(), + element.getProject(), + Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS + ); + } + } + return super.getIcon(expanded); } - return super.getIcon(expanded); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefMethodImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefMethodImpl.java index 23c16b9e72..2a18661eae 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefMethodImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefMethodImpl.java @@ -19,723 +19,713 @@ import com.intellij.java.language.impl.codeInsight.ExceptionUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.*; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.ReadAction; import consulo.language.editor.impl.inspection.reference.RefElementImpl; import consulo.language.editor.inspection.reference.RefElement; import consulo.language.editor.inspection.reference.RefManager; import consulo.language.editor.inspection.reference.RefVisitor; -import consulo.language.psi.PsiElement; import consulo.language.psi.PsiManager; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.util.collection.SmartList; import consulo.util.lang.Comparing; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; /** * @author max - * Date: Oct 21, 2001 + * @since 2001-10-21 */ public class RefMethodImpl extends RefJavaElementImpl implements RefMethod { - private static final List EMPTY_METHOD_LIST = Collections.emptyList(); - private static final RefParameter[] EMPTY_PARAMS_ARRAY = new RefParameter[0]; + private static final List EMPTY_METHOD_LIST = Collections.emptyList(); + private static final RefParameter[] EMPTY_PARAMS_ARRAY = new RefParameter[0]; - private static final int IS_APPMAIN_MASK = 0x10000; - private static final int IS_LIBRARY_OVERRIDE_MASK = 0x20000; - private static final int IS_CONSTRUCTOR_MASK = 0x40000; - private static final int IS_ABSTRACT_MASK = 0x80000; - private static final int IS_BODY_EMPTY_MASK = 0x100000; - private static final int IS_ONLY_CALLS_SUPER_MASK = 0x200000; - private static final int IS_RETURN_VALUE_USED_MASK = 0x400000; + private static final int IS_APPMAIN_MASK = 0x10000; + private static final int IS_LIBRARY_OVERRIDE_MASK = 0x20000; + private static final int IS_CONSTRUCTOR_MASK = 0x40000; + private static final int IS_ABSTRACT_MASK = 0x80000; + private static final int IS_BODY_EMPTY_MASK = 0x100000; + private static final int IS_ONLY_CALLS_SUPER_MASK = 0x200000; + private static final int IS_RETURN_VALUE_USED_MASK = 0x400000; - private static final int IS_TEST_METHOD_MASK = 0x4000000; - private static final int IS_CALLED_ON_SUBCLASS = 0x8000000; + private static final int IS_TEST_METHOD_MASK = 0x4000000; + private static final int IS_CALLED_ON_SUBCLASS = 0x8000000; - private static final String RETURN_VALUE_UNDEFINED = "#"; + private static final String RETURN_VALUE_UNDEFINED = "#"; - private List mySuperMethods; - private List myDerivedMethods; - private List myUnThrownExceptions; + private List mySuperMethods; + private List myDerivedMethods; + private List myUnThrownExceptions; - private RefParameter[] myParameters; - private String myReturnValueTemplate; - protected final RefClass myOwnerClass; + private RefParameter[] myParameters; + private String myReturnValueTemplate; + protected final RefClass myOwnerClass; - public RefMethodImpl(@Nonnull RefClass ownerClass, PsiMethod method, RefManager manager) { - super(method, manager); + @RequiredReadAction + public RefMethodImpl(RefClass ownerClass, PsiMethod method, RefManager manager) { + super(method, manager); - ((RefClassImpl) ownerClass).add(this); + ((RefClassImpl) ownerClass).add(this); - myOwnerClass = ownerClass; - } + myOwnerClass = ownerClass; + } - // To be used only from RefImplicitConstructor. - protected RefMethodImpl(String name, RefClass ownerClass) { - super(name, ownerClass); - myOwnerClass = ownerClass; - ((RefClassImpl) ownerClass).add(this); + // To be used only from RefImplicitConstructor. + protected RefMethodImpl(LocalizeValue name, RefClass ownerClass) { + super(name, ownerClass); + myOwnerClass = ownerClass; + ((RefClassImpl) ownerClass).add(this); - addOutReference(getOwnerClass()); - ((RefClassImpl) getOwnerClass()).addInReference(this); + addOutReference(getOwnerClass()); + ((RefClassImpl) getOwnerClass()).addInReference(this); - setConstructor(true); - } + setConstructor(true); + } - @Override - public void initialize() { - final PsiMethod method = (PsiMethod) getElement(); - LOG.assertTrue(method != null); - setConstructor(method.isConstructor()); - setFlag(method.getReturnType() == null || PsiType.VOID.equals(method.getReturnType()), IS_RETURN_VALUE_USED_MASK); + @Override + @RequiredReadAction + public void initialize() { + PsiMethod method = (PsiMethod) getElement(); + LOG.assertTrue(method != null); + setConstructor(method.isConstructor()); + setFlag(method.getReturnType() == null || PsiType.VOID.equals(method.getReturnType()), IS_RETURN_VALUE_USED_MASK); - if (!isReturnValueUsed()) { - myReturnValueTemplate = RETURN_VALUE_UNDEFINED; - } + if (!isReturnValueUsed()) { + myReturnValueTemplate = RETURN_VALUE_UNDEFINED; + } + + if (isConstructor()) { + addReference(getOwnerClass(), getOwnerClass().getElement(), method, false, true, null); + } + + setAbstract(!getOwnerClass().isInterface() && method.isAbstract()); + + setAppMain(isAppMain(method, this)); + setLibraryOverride(method.hasModifierProperty(PsiModifier.NATIVE)); + + initializeSuperMethods(method); + if (isExternalOverride()) { + ((RefClassImpl) getOwnerClass()).addLibraryOverrideMethod(this); + } - if (isConstructor()) { - addReference(getOwnerClass(), getOwnerClass().getElement(), method, false, true, null); + String name = method.getName(); + if (getOwnerClass().isTestCase() && name.startsWith("test")) { + setTestMethod(true); + } + + PsiParameter[] paramList = method.getParameterList().getParameters(); + if (paramList.length > 0) { + myParameters = new RefParameterImpl[paramList.length]; + for (int i = 0; i < paramList.length; i++) { + PsiParameter parameter = paramList[i]; + myParameters[i] = getRefJavaManager().getParameterReference(parameter, i, this); + } + } + + if (method.hasModifierProperty(PsiModifier.NATIVE)) { + updateReturnValueTemplate(null); + updateThrowsList(null); + } + collectUncaughtExceptions(method); } - if (getOwnerClass().isInterface()) { - setAbstract(false); - } else { - setAbstract(method.hasModifierProperty(PsiModifier.ABSTRACT)); + private static boolean isAppMain(PsiMethod psiMethod, RefMethod refMethod) { + if (!refMethod.isStatic()) { + return false; + } + if (!PsiType.VOID.equals(psiMethod.getReturnType())) { + return false; + } + + PsiMethod appMainPattern = ((RefMethodImpl) refMethod).getRefJavaManager().getAppMainPattern(); + if (MethodSignatureUtil.areSignaturesEqual(psiMethod, appMainPattern)) { + return true; + } + + PsiMethod appPremainPattern = ((RefMethodImpl) refMethod).getRefJavaManager().getAppPremainPattern(); + return MethodSignatureUtil.areSignaturesEqual(psiMethod, appPremainPattern); } + @RequiredReadAction + private void checkForSuperCall(PsiMethod method) { + if (isConstructor()) { + PsiCodeBlock body = method.getBody(); + if (body == null) { + return; + } + PsiStatement[] statements = body.getStatements(); + boolean isBaseExplicitlyCalled = false; + if (statements.length > 0 + && statements[0] instanceof PsiExpressionStatement first + && first.getExpression() instanceof PsiMethodCallExpression call + && call.getMethodExpression().getQualifierExpression() instanceof PsiReferenceExpression qRefExpr) { + String text = qRefExpr.getText(); + if ("super".equals(text) || text.equals("this")) { + isBaseExplicitlyCalled = true; + } + } - setAppMain(isAppMain(method, this)); - setLibraryOverride(method.hasModifierProperty(PsiModifier.NATIVE)); + if (!isBaseExplicitlyCalled) { + for (RefClass superClass : getOwnerClass().getBaseClasses()) { + RefMethodImpl superDefaultConstructor = (RefMethodImpl) superClass.getDefaultConstructor(); - initializeSuperMethods(method); - if (isExternalOverride()) { - ((RefClassImpl) getOwnerClass()).addLibraryOverrideMethod(this); + if (superDefaultConstructor != null) { + superDefaultConstructor.addInReference(this); + addOutReference(superDefaultConstructor); + } + } + } + } } - @NonNls final String name = method.getName(); - if (getOwnerClass().isTestCase() && name.startsWith("test")) { - setTestMethod(true); + @Override + public Collection getSuperMethods() { + if (mySuperMethods == null) { + return EMPTY_METHOD_LIST; + } + if (mySuperMethods.size() > 10) { + LOG.info("method: " + getName() + " owner:" + getOwnerClass().getQualifiedName()); + } + return mySuperMethods; } - PsiParameter[] paramList = method.getParameterList().getParameters(); - if (paramList.length > 0) { - myParameters = new RefParameterImpl[paramList.length]; - for (int i = 0; i < paramList.length; i++) { - PsiParameter parameter = paramList[i]; - myParameters[i] = getRefJavaManager().getParameterReference(parameter, i, this); - } + @Override + public Collection getDerivedMethods() { + if (myDerivedMethods == null) { + return EMPTY_METHOD_LIST; + } + return myDerivedMethods; } - if (method.hasModifierProperty(PsiModifier.NATIVE)) { - updateReturnValueTemplate(null); - updateThrowsList(null); + @Override + public boolean isBodyEmpty() { + return checkFlag(IS_BODY_EMPTY_MASK); } - collectUncaughtExceptions(method); - } - private static boolean isAppMain(PsiMethod psiMethod, RefMethod refMethod) { - if (!refMethod.isStatic()) { - return false; + @Override + public boolean isOnlyCallsSuper() { + return checkFlag(IS_ONLY_CALLS_SUPER_MASK); } - if (!PsiType.VOID.equals(psiMethod.getReturnType())) { - return false; + + @Override + public boolean hasBody() { + return !isAbstract() && !getOwnerClass().isInterface() || !isBodyEmpty(); + } + + @RequiredReadAction + private void initializeSuperMethods(PsiMethod method) { + for (PsiMethod psiSuperMethod : method.findSuperMethods()) { + if (getRefManager().belongsToScope(psiSuperMethod)) { + RefMethodImpl refSuperMethod = (RefMethodImpl) getRefManager().getReference(psiSuperMethod); + if (refSuperMethod != null) { + addSuperMethod(refSuperMethod); + refSuperMethod.markExtended(this); + } + } + else { + setLibraryOverride(true); + } + } + } + + public void addSuperMethod(RefMethodImpl refSuperMethod) { + if (!getSuperMethods().contains(refSuperMethod) && !refSuperMethod.getSuperMethods().contains(this)) { + if (mySuperMethods == null) { + mySuperMethods = new ArrayList<>(1); + } + mySuperMethods.add(refSuperMethod); + } } - PsiMethod appMainPattern = ((RefMethodImpl) refMethod).getRefJavaManager().getAppMainPattern(); - if (MethodSignatureUtil.areSignaturesEqual(psiMethod, appMainPattern)) { - return true; + public void markExtended(RefMethodImpl method) { + if (!getDerivedMethods().contains(method) && !method.getDerivedMethods().contains(this)) { + if (myDerivedMethods == null) { + myDerivedMethods = new ArrayList<>(1); + } + myDerivedMethods.add(method); + } } - PsiMethod appPremainPattern = ((RefMethodImpl) refMethod).getRefJavaManager().getAppPremainPattern(); - return MethodSignatureUtil.areSignaturesEqual(psiMethod, appPremainPattern); - } + @Override + public RefParameter[] getParameters() { + if (myParameters == null) { + return EMPTY_PARAMS_ARRAY; + } + return myParameters; + } - private void checkForSuperCall(PsiMethod method) { - if (isConstructor()) { - PsiCodeBlock body = method.getBody(); - if (body == null) { - return; - } - PsiStatement[] statements = body.getStatements(); - boolean isBaseExplicitlyCalled = false; - if (statements.length > 0) { - PsiStatement first = statements[0]; - if (first instanceof PsiExpressionStatement) { - PsiExpression firstExpression = ((PsiExpressionStatement) first).getExpression(); - if (firstExpression instanceof PsiMethodCallExpression) { - PsiExpression qualifierExpression = ((PsiMethodCallExpression) firstExpression).getMethodExpression().getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression) { - @NonNls String text = qualifierExpression.getText(); - if ("super".equals(text) || text.equals("this")) { - isBaseExplicitlyCalled = true; - } + @Override + @RequiredReadAction + public void buildReferences() { + // Work on code block to find what we're referencing... + PsiMethod method = (PsiMethod) getElement(); + if (method == null) { + return; + } + PsiCodeBlock body = method.getBody(); + RefJavaUtil refUtil = RefJavaUtil.getInstance(); + refUtil.addReferences(method, this, body); + refUtil.addReferences(method, this, method.getModifierList()); + checkForSuperCall(method); + setOnlyCallsSuper(refUtil.isMethodOnlyCallsSuper(method)); + + setBodyEmpty(isOnlyCallsSuper() || !isExternalOverride() && (body == null || body.getStatements().length == 0)); + + PsiType retType = method.getReturnType(); + if (retType != null) { + PsiType psiType = retType; + RefClass ownerClass = refUtil.getOwnerClass(getRefManager(), method); + + if (ownerClass != null) { + psiType = psiType.getDeepComponentType(); + + if (psiType instanceof PsiClassType) { + PsiClass psiClass = PsiUtil.resolveClassInType(psiType); + if (psiClass != null && getRefManager().belongsToScope(psiClass)) { + RefClassImpl refClass = (RefClassImpl) getRefManager().getReference(psiClass); + if (refClass != null) { + refClass.addTypeReference(ownerClass); + refClass.addClassExporter(this); + } + } + } } - } - } - } - - if (!isBaseExplicitlyCalled) { - for (RefClass superClass : getOwnerClass().getBaseClasses()) { - RefMethodImpl superDefaultConstructor = (RefMethodImpl) superClass.getDefaultConstructor(); - - if (superDefaultConstructor != null) { - superDefaultConstructor.addInReference(this); - addOutReference(superDefaultConstructor); - } - } - } - } - } - - @Override - @Nonnull - public Collection getSuperMethods() { - if (mySuperMethods == null) { - return EMPTY_METHOD_LIST; - } - if (mySuperMethods.size() > 10) { - LOG.info("method: " + getName() + " owner:" + getOwnerClass().getQualifiedName()); - } - return mySuperMethods; - } - - @Override - @Nonnull - public Collection getDerivedMethods() { - if (myDerivedMethods == null) { - return EMPTY_METHOD_LIST; - } - return myDerivedMethods; - } - - @Override - public boolean isBodyEmpty() { - return checkFlag(IS_BODY_EMPTY_MASK); - } - - @Override - public boolean isOnlyCallsSuper() { - return checkFlag(IS_ONLY_CALLS_SUPER_MASK); - } - - @Override - public boolean hasBody() { - return !isAbstract() && !getOwnerClass().isInterface() || !isBodyEmpty(); - } - - private void initializeSuperMethods(PsiMethod method) { - for (PsiMethod psiSuperMethod : method.findSuperMethods()) { - if (getRefManager().belongsToScope(psiSuperMethod)) { - RefMethodImpl refSuperMethod = (RefMethodImpl) getRefManager().getReference(psiSuperMethod); - if (refSuperMethod != null) { - addSuperMethod(refSuperMethod); - refSuperMethod.markExtended(this); - } - } else { - setLibraryOverride(true); - } - } - } - - public void addSuperMethod(RefMethodImpl refSuperMethod) { - if (!getSuperMethods().contains(refSuperMethod) && !refSuperMethod.getSuperMethods().contains(this)) { - if (mySuperMethods == null) { - mySuperMethods = new ArrayList(1); - } - mySuperMethods.add(refSuperMethod); - } - } - - public void markExtended(RefMethodImpl method) { - if (!getDerivedMethods().contains(method) && !method.getDerivedMethods().contains(this)) { - if (myDerivedMethods == null) { - myDerivedMethods = new ArrayList(1); - } - myDerivedMethods.add(method); - } - } - - @Override - @Nonnull - public RefParameter[] getParameters() { - if (myParameters == null) { - return EMPTY_PARAMS_ARRAY; - } - return myParameters; - } - - @Override - public void buildReferences() { - // Work on code block to find what we're referencing... - PsiMethod method = (PsiMethod) getElement(); - if (method == null) { - return; - } - PsiCodeBlock body = method.getBody(); - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); - refUtil.addReferences(method, this, body); - refUtil.addReferences(method, this, method.getModifierList()); - checkForSuperCall(method); - setOnlyCallsSuper(refUtil.isMethodOnlyCallsSuper(method)); - - setBodyEmpty(isOnlyCallsSuper() || !isExternalOverride() && (body == null || body.getStatements().length == 0)); - - PsiType retType = method.getReturnType(); - if (retType != null) { - PsiType psiType = retType; - RefClass ownerClass = refUtil.getOwnerClass(getRefManager(), method); - - if (ownerClass != null) { - psiType = psiType.getDeepComponentType(); - - if (psiType instanceof PsiClassType) { - PsiClass psiClass = PsiUtil.resolveClassInType(psiType); - if (psiClass != null && getRefManager().belongsToScope(psiClass)) { - RefClassImpl refClass = (RefClassImpl) getRefManager().getReference(psiClass); - if (refClass != null) { - refClass.addTypeReference(ownerClass); - refClass.addClassExporter(this); + } + + for (RefParameter parameter : getParameters()) { + PsiParameter element = parameter.getElement(); + refUtil.setIsFinal(parameter, element != null && element.hasModifierProperty(PsiModifier.FINAL)); + } + + getRefManager().fireBuildReferences(this); + } + + @RequiredReadAction + private void collectUncaughtExceptions(PsiMethod method) { + if (isExternalOverride()) { + return; + } + String name = method.getName(); + if (getOwnerClass().isTestCase() && name.startsWith("test")) { + return; + } + + if (getSuperMethods().isEmpty()) { + PsiClassType[] throwsList = method.getThrowsList().getReferencedTypes(); + if (throwsList.length > 0) { + myUnThrownExceptions = throwsList.length == 1 ? new SmartList<>() : new ArrayList<>(throwsList.length); + for (PsiClassType type : throwsList) { + PsiClass aClass = type.resolve(); + String fqn = aClass == null ? null : aClass.getQualifiedName(); + if (fqn != null) { + myUnThrownExceptions.add(fqn); + } + } } - } } - } + + PsiCodeBlock body = method.getBody(); + if (body == null) { + return; + } + + Collection exceptionTypes = ExceptionUtil.collectUnhandledExceptions(body, method, false); + for (PsiClassType exceptionType : exceptionTypes) { + updateThrowsList(exceptionType); + } + } + + public void removeUnThrownExceptions(PsiClass unThrownException) { + if (myUnThrownExceptions != null) { + myUnThrownExceptions.remove(unThrownException.getQualifiedName()); + } + } + + @Override + public void accept(RefVisitor visitor) { + if (visitor instanceof RefJavaVisitor refJavaVisitor) { + ReadAction.run(() -> refJavaVisitor.visitMethod(RefMethodImpl.this)); + } + else { + super.accept(visitor); + } } - for (RefParameter parameter : getParameters()) { - PsiParameter element = parameter.getElement(); - refUtil.setIsFinal(parameter, element != null && element.hasModifierProperty(PsiModifier.FINAL)); + @Override + public boolean isExternalOverride() { + return isLibraryOverride(new HashSet<>()); } - getRefManager().fireBuildReferences(this); - } + private boolean isLibraryOverride(Collection processed) { + if (processed.contains(this)) { + return false; + } + processed.add(this); - private void collectUncaughtExceptions(@Nonnull PsiMethod method) { - if (isExternalOverride()) { - return; + if (checkFlag(IS_LIBRARY_OVERRIDE_MASK)) { + return true; + } + for (RefMethod superMethod : getSuperMethods()) { + if (((RefMethodImpl) superMethod).isLibraryOverride(processed)) { + setFlag(true, IS_LIBRARY_OVERRIDE_MASK); + return true; + } + } + + return false; } - @NonNls final String name = method.getName(); - if (getOwnerClass().isTestCase() && name.startsWith("test")) { - return; + + @Override + public boolean isAppMain() { + return checkFlag(IS_APPMAIN_MASK); } - if (getSuperMethods().isEmpty()) { - PsiClassType[] throwsList = method.getThrowsList().getReferencedTypes(); - if (throwsList.length > 0) { - myUnThrownExceptions = throwsList.length == 1 ? new SmartList() : new ArrayList(throwsList.length); - for (final PsiClassType type : throwsList) { - PsiClass aClass = type.resolve(); - String fqn = aClass == null ? null : aClass.getQualifiedName(); - if (fqn != null) { - myUnThrownExceptions.add(fqn); - } + @Override + public boolean isAbstract() { + return checkFlag(IS_ABSTRACT_MASK); + } + + @Override + public boolean hasSuperMethods() { + return !getSuperMethods().isEmpty() || isExternalOverride(); + } + + @Override + public boolean isReferenced() { + // Directly called from somewhere.. + for (RefElement refCaller : getInReferences()) { + if (!getDerivedMethods().contains(refCaller)) { + return true; + } } - } + + // Library override probably called from library code. + return isExternalOverride(); } - final PsiCodeBlock body = method.getBody(); - if (body == null) { - return; + @Override + public boolean hasSuspiciousCallers() { + // Directly called from somewhere.. + for (RefElement refCaller : getInReferences()) { + if (((RefElementImpl) refCaller).isSuspicious() && !getDerivedMethods().contains(refCaller)) { + return true; + } + } + + // Library override probably called from library code. + if (isExternalOverride()) { + return true; + } + + // Class isn't instantiated. Most probably we have problem with class, not method. + if (!isStatic() && !isConstructor()) { + if (((RefClassImpl) getOwnerClass()).isSuspicious()) { + return true; + } + + // Is an override. Probably called via reference to base class. + for (RefMethod refSuper : getSuperMethods()) { + if (((RefMethodImpl) refSuper).isSuspicious()) { + return true; + } + } + } + + return false; } - final Collection exceptionTypes = ExceptionUtil.collectUnhandledExceptions(body, method, false); - for (final PsiClassType exceptionType : exceptionTypes) { - updateThrowsList(exceptionType); + @Override + public boolean isConstructor() { + return checkFlag(IS_CONSTRUCTOR_MASK); } - } - public void removeUnThrownExceptions(PsiClass unThrownException) { - if (myUnThrownExceptions != null) { - myUnThrownExceptions.remove(unThrownException.getQualifiedName()); + @Override + public RefClass getOwnerClass() { + return (RefClass) getOwner(); } - } - @Override - public void accept(final RefVisitor visitor) { - if (visitor instanceof RefJavaVisitor) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - ((RefJavaVisitor) visitor).visitMethod(RefMethodImpl.this); + @Override + public String getName() { + if (isValid()) { + return ReadAction.compute(() -> { + PsiMethod method = (PsiMethod) getElement(); + /*if (psiMethod instanceof JspHolderMethod) { + result[0] = psiMethod.getName(); + } + else {*/ + return PsiFormatUtil.formatMethod( + method, + PsiSubstitutor.EMPTY, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, + PsiFormatUtilBase.SHOW_TYPE + ); + //} + }); + } + else { + return super.getName(); } - }); - } else { - super.accept(visitor); } - } - @Override - public boolean isExternalOverride() { - return isLibraryOverride(new HashSet()); - } + @Override + public String getExternalName() { + return ReadAction.compute(() -> { + PsiMethod psiMethod = (PsiMethod) getElement(); + LOG.assertTrue(psiMethod != null); + return PsiFormatUtil.getExternalName(psiMethod); + }); + } - private boolean isLibraryOverride(Collection processed) { - if (processed.contains(this)) { - return false; + @Nullable + public static RefMethod methodFromExternalName(RefManager manager, String externalName) { + return (RefMethod) manager.getReference(findPsiMethod(PsiManager.getInstance(manager.getProject()), externalName)); } - processed.add(this); - if (checkFlag(IS_LIBRARY_OVERRIDE_MASK)) { - return true; + @Nullable + public static PsiMethod findPsiMethod(PsiManager manager, String externalName) { + int spaceIdx = externalName.indexOf(' '); + String className = externalName.substring(0, spaceIdx); + PsiClass psiClass = ClassUtil.findPsiClass(manager, className); + if (psiClass == null) { + return null; + } + try { + PsiElementFactory factory = JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory(); + String methodSignature = externalName.substring(spaceIdx + 1); + PsiMethod patternMethod = factory.createMethodFromText(methodSignature, psiClass); + return psiClass.findMethodBySignature(patternMethod, false); + } + catch (IncorrectOperationException e) { + // Do nothing. Returning null is acceptable in this case. + return null; + } + } + + @Override + @RequiredReadAction + public void referenceRemoved() { + if (getOwnerClass() != null) { + ((RefClassImpl) getOwnerClass()).methodRemoved(this); + } + + super.referenceRemoved(); + + for (RefMethod superMethod : getSuperMethods()) { + superMethod.getDerivedMethods().remove(this); + } + + for (RefMethod subMethod : getDerivedMethods()) { + subMethod.getSuperMethods().remove(this); + } + + List deletedRefs = new ArrayList<>(); + for (RefParameter parameter : getParameters()) { + getRefManager().removeRefElement(parameter, deletedRefs); + } } - for (RefMethod superMethod : getSuperMethods()) { - if (((RefMethodImpl) superMethod).isLibraryOverride(processed)) { - setFlag(true, IS_LIBRARY_OVERRIDE_MASK); - return true; - } + + @Override + public boolean isSuspicious() { + //noinspection SimplifiableIfStatement + if (isConstructor() + && PsiModifier.PRIVATE.equals(getAccessModifier()) + && getParameters().length == 0 + && getOwnerClass().getConstructors().size() == 1) { + return false; + } + return super.isSuspicious(); } - return false; - } + public void setReturnValueUsed(boolean value) { + if (checkFlag(IS_RETURN_VALUE_USED_MASK) == value) { + return; + } + setFlag(value, IS_RETURN_VALUE_USED_MASK); + for (RefMethod refSuper : getSuperMethods()) { + ((RefMethodImpl) refSuper).setReturnValueUsed(value); + } + } - @Override - public boolean isAppMain() { - return checkFlag(IS_APPMAIN_MASK); - } + @Override + public boolean isReturnValueUsed() { + return checkFlag(IS_RETURN_VALUE_USED_MASK); + } - @Override - public boolean isAbstract() { - return checkFlag(IS_ABSTRACT_MASK); - } + @RequiredReadAction + public void updateReturnValueTemplate(PsiExpression expression) { + if (myReturnValueTemplate == null) { + return; + } - @Override - public boolean hasSuperMethods() { - return !getSuperMethods().isEmpty() || isExternalOverride(); - } + if (!getSuperMethods().isEmpty()) { + for (RefMethod refMethod : getSuperMethods()) { + RefMethodImpl refSuper = (RefMethodImpl) refMethod; + refSuper.updateReturnValueTemplate(expression); + } + } + else { + String newTemplate = null; + RefJavaUtil refUtil = RefJavaUtil.getInstance(); + if (expression instanceof PsiLiteralExpression literal) { + newTemplate = literal.getText(); + } + else if (expression instanceof PsiReferenceExpression referenceExpression) { + if (referenceExpression.resolve() instanceof PsiField field + && field.isStatic() && field.isFinal() + && refUtil.compareAccess(refUtil.getAccessModifier(field), getAccessModifier()) >= 0) { + newTemplate = PsiFormatUtil.formatVariable( + field, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_FQ_NAME, + PsiSubstitutor.EMPTY + ); + } + } + else if (refUtil.isCallToSuperMethod(expression, (PsiMethod) getElement())) { + return; + } - @Override - public boolean isReferenced() { - // Directly called from somewhere.. - for (RefElement refCaller : getInReferences()) { - if (!getDerivedMethods().contains(refCaller)) { - return true; - } - } - - // Library override probably called from library code. - return isExternalOverride(); - } - - @Override - public boolean hasSuspiciousCallers() { - // Directly called from somewhere.. - for (RefElement refCaller : getInReferences()) { - if (((RefElementImpl) refCaller).isSuspicious() && !getDerivedMethods().contains(refCaller)) { - return true; - } - } - - // Library override probably called from library code. - if (isExternalOverride()) { - return true; + //noinspection StringEquality + if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) { + myReturnValueTemplate = newTemplate; + } + else if (!Comparing.equal(myReturnValueTemplate, newTemplate)) { + myReturnValueTemplate = null; + } + } } - // Class isn't instantiated. Most probably we have problem with class, not method. - if (!isStatic() && !isConstructor()) { - if (((RefClassImpl) getOwnerClass()).isSuspicious()) { - return true; - } + public void updateParameterValues(PsiExpression[] args) { + if (isExternalOverride()) { + return; + } - // Is an override. Probably called via reference to base class. - for (RefMethod refSuper : getSuperMethods()) { - if (((RefMethodImpl) refSuper).isSuspicious()) { - return true; + if (!getSuperMethods().isEmpty()) { + for (RefMethod refSuper : getSuperMethods()) { + ((RefMethodImpl) refSuper).updateParameterValues(args); + } + } + else { + RefParameter[] params = getParameters(); + if (params.length <= args.length && params.length > 0) { + for (int i = 0; i < args.length; i++) { + RefParameter refParameter; + if (params.length <= i) { + refParameter = params[params.length - 1]; + } + else { + refParameter = params[i]; + } + ((RefParameterImpl) refParameter).updateTemplateValue(args[i]); + } + } } - } } - return false; - } + @Override + public String getReturnValueIfSame() { + //noinspection StringEquality + if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) { + return null; + } + return myReturnValueTemplate; + } - @Override - public boolean isConstructor() { - return checkFlag(IS_CONSTRUCTOR_MASK); - } - - @Override - public RefClass getOwnerClass() { - return (RefClass) getOwner(); - } + public void updateThrowsList(PsiClassType exceptionType) { + if (!getSuperMethods().isEmpty()) { + for (RefMethod refSuper : getSuperMethods()) { + ((RefMethodImpl) refSuper).updateThrowsList(exceptionType); + } + } + else if (myUnThrownExceptions != null) { + if (exceptionType == null) { + myUnThrownExceptions = null; + return; + } + PsiClass exceptionClass = exceptionType.resolve(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); + for (int i = myUnThrownExceptions.size() - 1; i >= 0; i--) { + String exceptionFqn = myUnThrownExceptions.get(i); + PsiClass classType = facade.findClass(exceptionFqn, GlobalSearchScope.allScope(getRefManager().getProject())); + if (InheritanceUtil.isInheritorOrSelf(exceptionClass, classType, true) || InheritanceUtil.isInheritorOrSelf( + classType, + exceptionClass, + true + )) { + myUnThrownExceptions.remove(i); + } + } + + if (myUnThrownExceptions.isEmpty()) { + myUnThrownExceptions = null; + } + } + } - @Override - public String getName() { - if (isValid()) { - final String[] result = new String[1]; - final Runnable runnable = new Runnable() { - @Override - public void run() { - PsiMethod psiMethod = (PsiMethod) getElement(); - /*if (psiMethod instanceof JspHolderMethod) { - result[0] = psiMethod.getName(); - } - else { */ - result[0] = PsiFormatUtil.formatMethod(psiMethod, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); - // } + @Nullable + @Override + public PsiClass[] getUnThrownExceptions() { + if (myUnThrownExceptions == null) { + return null; } - }; + JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); + List result = new ArrayList<>(myUnThrownExceptions.size()); + for (String exception : myUnThrownExceptions) { + PsiClass element = facade.findClass(exception, GlobalSearchScope.allScope(myManager.getProject())); + if (element != null) { + result.add(element); + } + } + return result.toArray(new PsiClass[result.size()]); + } + + public void setLibraryOverride(boolean libraryOverride) { + setFlag(libraryOverride, IS_LIBRARY_OVERRIDE_MASK); + } + + private void setAppMain(boolean appMain) { + setFlag(appMain, IS_APPMAIN_MASK); + } + + private void setAbstract(boolean anAbstract) { + setFlag(anAbstract, IS_ABSTRACT_MASK); + } - ApplicationManager.getApplication().runReadAction(runnable); + public void setBodyEmpty(boolean bodyEmpty) { + setFlag(bodyEmpty, IS_BODY_EMPTY_MASK); + } + + private void setOnlyCallsSuper(boolean onlyCallsSuper) { + setFlag(onlyCallsSuper, IS_ONLY_CALLS_SUPER_MASK); + } - return result[0]; - } else { - return super.getName(); - } - } - - @Override - public String getExternalName() { - final String[] result = new String[1]; - final Runnable runnable = new Runnable() { - @Override - public void run() { - final PsiMethod psiMethod = (PsiMethod) getElement(); - LOG.assertTrue(psiMethod != null); - result[0] = PsiFormatUtil.getExternalName(psiMethod); - } - }; - - ApplicationManager.getApplication().runReadAction(runnable); - - return result[0]; - } - - @Nullable - public static RefMethod methodFromExternalName(RefManager manager, String externalName) { - return (RefMethod) manager.getReference(findPsiMethod(PsiManager.getInstance(manager.getProject()), externalName)); - } - - @Nullable - public static PsiMethod findPsiMethod(PsiManager manager, String externalName) { - final int spaceIdx = externalName.indexOf(' '); - final String className = externalName.substring(0, spaceIdx); - final PsiClass psiClass = ClassUtil.findPsiClass(manager, className); - if (psiClass == null) { - return null; - } - try { - PsiElementFactory factory = JavaPsiFacade.getInstance(psiClass.getProject()).getElementFactory(); - String methodSignature = externalName.substring(spaceIdx + 1); - PsiMethod patternMethod = factory.createMethodFromText(methodSignature, psiClass); - return psiClass.findMethodBySignature(patternMethod, false); - } catch (IncorrectOperationException e) { - // Do nothing. Returning null is acceptable in this case. - return null; - } - } - - @Override - public void referenceRemoved() { - if (getOwnerClass() != null) { - ((RefClassImpl) getOwnerClass()).methodRemoved(this); - } - - super.referenceRemoved(); - - for (RefMethod superMethod : getSuperMethods()) { - superMethod.getDerivedMethods().remove(this); - } - - for (RefMethod subMethod : getDerivedMethods()) { - subMethod.getSuperMethods().remove(this); - } - - ArrayList deletedRefs = new ArrayList(); - for (RefParameter parameter : getParameters()) { - getRefManager().removeRefElement(parameter, deletedRefs); - } - } - - @Override - public boolean isSuspicious() { - if (isConstructor() && PsiModifier.PRIVATE.equals(getAccessModifier()) && getParameters().length == 0 && getOwnerClass().getConstructors().size() == 1) { - return false; - } - return super.isSuspicious(); - } - - public void setReturnValueUsed(boolean value) { - if (checkFlag(IS_RETURN_VALUE_USED_MASK) == value) { - return; - } - setFlag(value, IS_RETURN_VALUE_USED_MASK); - for (RefMethod refSuper : getSuperMethods()) { - ((RefMethodImpl) refSuper).setReturnValueUsed(value); - } - } - - @Override - public boolean isReturnValueUsed() { - return checkFlag(IS_RETURN_VALUE_USED_MASK); - } - - public void updateReturnValueTemplate(PsiExpression expression) { - if (myReturnValueTemplate == null) { - return; - } - - if (!getSuperMethods().isEmpty()) { - for (final RefMethod refMethod : getSuperMethods()) { - RefMethodImpl refSuper = (RefMethodImpl) refMethod; - refSuper.updateReturnValueTemplate(expression); - } - } else { - String newTemplate = null; - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); - if (expression instanceof PsiLiteralExpression) { - PsiLiteralExpression psiLiteralExpression = (PsiLiteralExpression) expression; - newTemplate = psiLiteralExpression.getText(); - } else if (expression instanceof PsiReferenceExpression) { - PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression; - PsiElement resolved = referenceExpression.resolve(); - if (resolved instanceof PsiField) { - PsiField psiField = (PsiField) resolved; - if (psiField.hasModifierProperty(PsiModifier.STATIC) && - psiField.hasModifierProperty(PsiModifier.FINAL) && - refUtil.compareAccess(refUtil.getAccessModifier(psiField), getAccessModifier()) >= 0) { - newTemplate = PsiFormatUtil.formatVariable(psiField, PsiFormatUtilBase.SHOW_NAME | - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | - PsiFormatUtilBase.SHOW_FQ_NAME, PsiSubstitutor.EMPTY); - } - } - } else if (refUtil.isCallToSuperMethod(expression, (PsiMethod) getElement())) { - return; - } - - //noinspection StringEquality - if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) { - myReturnValueTemplate = newTemplate; - } else if (!Comparing.equal(myReturnValueTemplate, newTemplate)) { - myReturnValueTemplate = null; - } - } - } - - public void updateParameterValues(PsiExpression[] args) { - if (isExternalOverride()) { - return; - } - - if (!getSuperMethods().isEmpty()) { - for (RefMethod refSuper : getSuperMethods()) { - ((RefMethodImpl) refSuper).updateParameterValues(args); - } - } else { - final RefParameter[] params = getParameters(); - if (params.length <= args.length && params.length > 0) { - for (int i = 0; i < args.length; i++) { - RefParameter refParameter; - if (params.length <= i) { - refParameter = params[params.length - 1]; - } else { - refParameter = params[i]; - } - ((RefParameterImpl) refParameter).updateTemplateValue(args[i]); - } - } - } - } - - @Override - public String getReturnValueIfSame() { - //noinspection StringEquality - if (myReturnValueTemplate == RETURN_VALUE_UNDEFINED) { - return null; - } - return myReturnValueTemplate; - } - - public void updateThrowsList(PsiClassType exceptionType) { - if (!getSuperMethods().isEmpty()) { - for (RefMethod refSuper : getSuperMethods()) { - ((RefMethodImpl) refSuper).updateThrowsList(exceptionType); - } - } else if (myUnThrownExceptions != null) { - if (exceptionType == null) { - myUnThrownExceptions = null; - return; - } - PsiClass exceptionClass = exceptionType.resolve(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); - for (int i = myUnThrownExceptions.size() - 1; i >= 0; i--) { - String exceptionFqn = myUnThrownExceptions.get(i); - PsiClass classType = facade.findClass(exceptionFqn, GlobalSearchScope.allScope(getRefManager().getProject())); - if (InheritanceUtil.isInheritorOrSelf(exceptionClass, classType, true) || InheritanceUtil.isInheritorOrSelf(classType, exceptionClass, true)) { - myUnThrownExceptions.remove(i); - } - } - - if (myUnThrownExceptions.isEmpty()) { - myUnThrownExceptions = null; - } - } - } - - @Override - @Nullable - public PsiClass[] getUnThrownExceptions() { - if (myUnThrownExceptions == null) { - return null; - } - JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); - List result = new ArrayList(myUnThrownExceptions.size()); - for (String exception : myUnThrownExceptions) { - PsiClass element = facade.findClass(exception, GlobalSearchScope.allScope(myManager.getProject())); - if (element != null) { - result.add(element); - } - } - return result.toArray(new PsiClass[result.size()]); - } - - - public void setLibraryOverride(boolean libraryOverride) { - setFlag(libraryOverride, IS_LIBRARY_OVERRIDE_MASK); - } - - private void setAppMain(boolean appMain) { - setFlag(appMain, IS_APPMAIN_MASK); - } - - private void setAbstract(boolean anAbstract) { - setFlag(anAbstract, IS_ABSTRACT_MASK); - } - - public void setBodyEmpty(boolean bodyEmpty) { - setFlag(bodyEmpty, IS_BODY_EMPTY_MASK); - } - - private void setOnlyCallsSuper(boolean onlyCallsSuper) { - setFlag(onlyCallsSuper, IS_ONLY_CALLS_SUPER_MASK); - } - - - private void setConstructor(boolean constructor) { - setFlag(constructor, IS_CONSTRUCTOR_MASK); - } - - @Override - public boolean isTestMethod() { - return checkFlag(IS_TEST_METHOD_MASK); - } - - private void setTestMethod(boolean testMethod) { - setFlag(testMethod, IS_TEST_METHOD_MASK); - } - - @Override - public PsiModifierListOwner getElement() { - return (PsiModifierListOwner) super.getElement(); - } - - @Override - public boolean isCalledOnSubClass() { - return checkFlag(IS_CALLED_ON_SUBCLASS); - } - - public void setCalledOnSubClass(boolean isCalledOnSubClass) { - setFlag(isCalledOnSubClass, IS_CALLED_ON_SUBCLASS); - } + private void setConstructor(boolean constructor) { + setFlag(constructor, IS_CONSTRUCTOR_MASK); + } + + @Override + public boolean isTestMethod() { + return checkFlag(IS_TEST_METHOD_MASK); + } + + private void setTestMethod(boolean testMethod) { + setFlag(testMethod, IS_TEST_METHOD_MASK); + } + + @Override + public PsiModifierListOwner getElement() { + return (PsiModifierListOwner) super.getElement(); + } + + @Override + public boolean isCalledOnSubClass() { + return checkFlag(IS_CALLED_ON_SUBCLASS); + } + + public void setCalledOnSubClass(boolean isCalledOnSubClass) { + setFlag(isCalledOnSubClass, IS_CALLED_ON_SUBCLASS); + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefParameterImpl.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefParameterImpl.java index d3ad173124..0bdf962920 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefParameterImpl.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/reference/RefParameterImpl.java @@ -38,8 +38,7 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiManager; import consulo.util.lang.Comparing; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class RefParameterImpl extends RefJavaElementImpl implements RefParameter { private static final int USED_FOR_READING_MASK = 0x10000; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedImport/UnusedImportLocalInspection.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedImport/UnusedImportLocalInspection.java index 912d6ebf4a..ef2f883f1c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedImport/UnusedImportLocalInspection.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedImport/UnusedImportLocalInspection.java @@ -16,14 +16,11 @@ package com.intellij.java.analysis.impl.codeInspection.unusedImport; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.analysis.impl.codeInspection.BaseJavaLocalInspectionTool; import consulo.annotation.component.ExtensionImpl; -import consulo.language.editor.inspection.InspectionsBundle; import consulo.language.editor.inspection.PairedUnfairLocalInspectionTool; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.localize.LocalizeValue; /** * User: anna @@ -31,25 +28,19 @@ */ @ExtensionImpl public class UnusedImportLocalInspection extends BaseJavaLocalInspectionTool implements PairedUnfairLocalInspectionTool { - @NonNls public static final String SHORT_NAME = "UNUSED_IMPORT"; - public static final String DISPLAY_NAME = InspectionsBundle.message("unused.import"); @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.IMPORTS_GROUP_NAME; + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesImports(); } @Override - @Nonnull - public String getDisplayName() { - return DISPLAY_NAME; + public LocalizeValue getDisplayName() { + return InspectionLocalize.unusedImport(); } @Override - @Nonnull - @NonNls public String getShortName() { return SHORT_NAME; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java index d389c05e9a..0b164d71d4 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/unusedSymbol/UnusedSymbolLocalInspectionBase.java @@ -15,218 +15,210 @@ */ package com.intellij.java.analysis.impl.codeInspection.unusedSymbol; -import com.intellij.java.analysis.codeInspection.GroupNames; import com.intellij.java.analysis.impl.codeInspection.BaseJavaLocalInspectionTool; import com.intellij.java.analysis.impl.codeInspection.deadCode.UnusedDeclarationInspectionBase; import com.intellij.java.language.psi.PsiModifier; +import consulo.language.editor.inspection.ProblemHighlightType; +import consulo.language.editor.inspection.ProblemHighlightTypeInspectionRuler; +import consulo.language.editor.inspection.localize.InspectionLocalize; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; -import consulo.language.editor.rawHighlight.HighlightInfoType; +import consulo.localize.LocalizeValue; import consulo.util.xml.serializer.InvalidDataException; import consulo.util.xml.serializer.WriteExternalException; +import org.jspecify.annotations.Nullable; import org.intellij.lang.annotations.Pattern; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class UnusedSymbolLocalInspectionBase extends BaseJavaLocalInspectionTool { - @NonNls - public static final String SHORT_NAME = HighlightInfoType.UNUSED_SYMBOL_SHORT_NAME; - @NonNls - public static final String DISPLAY_NAME = HighlightInfoType.UNUSED_SYMBOL_DISPLAY_NAME; - @NonNls - public static final String UNUSED_PARAMETERS_SHORT_NAME = "UnusedParameters"; - - @NonNls - public static final String UNUSED_ID = "unused"; - - public boolean LOCAL_VARIABLE = true; - public boolean FIELD = true; - public boolean METHOD = true; - public boolean CLASS = true; - protected boolean INNER_CLASS = true; - public boolean PARAMETER = true; - public boolean REPORT_PARAMETER_FOR_PUBLIC_METHODS = true; - - protected String myClassVisibility = PsiModifier.PUBLIC; - protected String myInnerClassVisibility = PsiModifier.PUBLIC; - protected String myFieldVisibility = PsiModifier.PUBLIC; - protected String myMethodVisibility = PsiModifier.PUBLIC; - protected String myParameterVisibility = PsiModifier.PUBLIC; - private boolean myIgnoreAccessors = false; - - @PsiModifier.ModifierConstant - @Nullable - public String getClassVisibility() { - if (!CLASS) { - return null; - } - return myClassVisibility; - } - - @PsiModifier.ModifierConstant - @Nullable - public String getFieldVisibility() { - if (!FIELD) { - return null; - } - return myFieldVisibility; - } - - @PsiModifier.ModifierConstant - @Nullable - public String getMethodVisibility() { - if (!METHOD) { - return null; - } - return myMethodVisibility; - } - - @PsiModifier.ModifierConstant - @Nullable - public String getParameterVisibility() { - if (!PARAMETER) { - return null; - } - return myParameterVisibility; - } - - @PsiModifier.ModifierConstant - @Nullable - public String getInnerClassVisibility() { - if (!INNER_CLASS) { - return null; - } - return myInnerClassVisibility; - } - - public void setInnerClassVisibility(String innerClassVisibility) { - myInnerClassVisibility = innerClassVisibility; - } - - public void setClassVisibility(String classVisibility) { - this.myClassVisibility = classVisibility; - } - - public void setFieldVisibility(String fieldVisibility) { - this.myFieldVisibility = fieldVisibility; - } - - public void setMethodVisibility(String methodVisibility) { - this.myMethodVisibility = methodVisibility; - } - - public void setParameterVisibility(String parameterVisibility) { - REPORT_PARAMETER_FOR_PUBLIC_METHODS = PsiModifier.PUBLIC.equals(parameterVisibility); - this.myParameterVisibility = parameterVisibility; - } - - public boolean isIgnoreAccessors() { - return myIgnoreAccessors; - } - - public void setIgnoreAccessors(boolean ignoreAccessors) { - myIgnoreAccessors = ignoreAccessors; - } - - - @Override - @Nonnull - public String getGroupDisplayName() { - return GroupNames.DECLARATION_REDUNDANCY; - } - - @Override - @Nonnull - public String getDisplayName() { - return DISPLAY_NAME; - } - - @Override - @Nonnull - @NonNls - public String getShortName() { - return SHORT_NAME; - } - - @Override - @Pattern(VALID_ID_PATTERN) - @Nonnull - @NonNls - public String getID() { - return UNUSED_ID; - } - - @Override - public String getAlternativeID() { - return UnusedDeclarationInspectionBase.ALTERNATIVE_ID; - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Nonnull - @Override - public HighlightDisplayLevel getDefaultLevel() { - return HighlightDisplayLevel.WARNING; - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - writeVisibility(node, myClassVisibility, "klass"); - writeVisibility(node, myInnerClassVisibility, "inner_class"); - writeVisibility(node, myFieldVisibility, "field"); - writeVisibility(node, myMethodVisibility, "method"); - writeVisibility(node, "parameter", myParameterVisibility, getParameterDefaultVisibility()); - if (myIgnoreAccessors) { - node.setAttribute("ignoreAccessors", Boolean.toString(true)); - } - if (!INNER_CLASS) { - node.setAttribute("INNER_CLASS", Boolean.toString(false)); - } - super.writeSettings(node); - } - - private static void writeVisibility(Element node, String visibility, String type) { - writeVisibility(node, type, visibility, PsiModifier.PUBLIC); - } - - private static void writeVisibility(Element node, String type, String visibility, String defaultVisibility) { - if (!defaultVisibility.equals(visibility)) { - node.setAttribute(type, visibility); - } - } - - private String getParameterDefaultVisibility() { - return REPORT_PARAMETER_FOR_PUBLIC_METHODS ? PsiModifier.PUBLIC : PsiModifier.PRIVATE; - } - - @Override - public void readSettings(@Nonnull Element node) throws InvalidDataException { - super.readSettings(node); - myClassVisibility = readVisibility(node, "klass"); - myInnerClassVisibility = readVisibility(node, "inner_class"); - myFieldVisibility = readVisibility(node, "field"); - myMethodVisibility = readVisibility(node, "method"); - myParameterVisibility = readVisibility(node, "parameter", getParameterDefaultVisibility()); - final String ignoreAccessors = node.getAttributeValue("ignoreAccessors"); - myIgnoreAccessors = ignoreAccessors != null && Boolean.parseBoolean(ignoreAccessors); - final String innerClassEnabled = node.getAttributeValue("INNER_CLASS"); - INNER_CLASS = innerClassEnabled == null || Boolean.parseBoolean(innerClassEnabled); - } - - private static String readVisibility(@Nonnull Element node, final String type) { - return readVisibility(node, type, PsiModifier.PUBLIC); - } - - private static String readVisibility(@Nonnull Element node, final String type, final String defaultVisibility) { - final String visibility = node.getAttributeValue(type); - if (visibility == null) { - return defaultVisibility; - } - return visibility; - } + +public class UnusedSymbolLocalInspectionBase extends BaseJavaLocalInspectionTool implements ProblemHighlightTypeInspectionRuler { + public static final String SHORT_NAME = "unused"; + public static final LocalizeValue DISPLAY_NAME = InspectionLocalize.inspectionDeadCodeDisplayName(); + public static final String UNUSED_PARAMETERS_SHORT_NAME = "UnusedParameters"; + + public static final String UNUSED_ID = "unused"; + + public boolean LOCAL_VARIABLE = true; + public boolean FIELD = true; + public boolean METHOD = true; + public boolean CLASS = true; + protected boolean INNER_CLASS = true; + public boolean PARAMETER = true; + public boolean REPORT_PARAMETER_FOR_PUBLIC_METHODS = true; + + protected String myClassVisibility = PsiModifier.PUBLIC; + protected String myInnerClassVisibility = PsiModifier.PUBLIC; + protected String myFieldVisibility = PsiModifier.PUBLIC; + protected String myMethodVisibility = PsiModifier.PUBLIC; + protected String myParameterVisibility = PsiModifier.PUBLIC; + private boolean myIgnoreAccessors = false; + + @PsiModifier.ModifierConstant + @Nullable + public String getClassVisibility() { + if (!CLASS) { + return null; + } + return myClassVisibility; + } + + @PsiModifier.ModifierConstant + @Nullable + public String getFieldVisibility() { + if (!FIELD) { + return null; + } + return myFieldVisibility; + } + + @PsiModifier.ModifierConstant + @Nullable + public String getMethodVisibility() { + if (!METHOD) { + return null; + } + return myMethodVisibility; + } + + @PsiModifier.ModifierConstant + @Nullable + public String getParameterVisibility() { + if (!PARAMETER) { + return null; + } + return myParameterVisibility; + } + + @PsiModifier.ModifierConstant + @Nullable + public String getInnerClassVisibility() { + if (!INNER_CLASS) { + return null; + } + return myInnerClassVisibility; + } + + public void setInnerClassVisibility(String innerClassVisibility) { + myInnerClassVisibility = innerClassVisibility; + } + + public void setClassVisibility(String classVisibility) { + this.myClassVisibility = classVisibility; + } + + public void setFieldVisibility(String fieldVisibility) { + this.myFieldVisibility = fieldVisibility; + } + + public void setMethodVisibility(String methodVisibility) { + this.myMethodVisibility = methodVisibility; + } + + public void setParameterVisibility(String parameterVisibility) { + REPORT_PARAMETER_FOR_PUBLIC_METHODS = PsiModifier.PUBLIC.equals(parameterVisibility); + this.myParameterVisibility = parameterVisibility; + } + + public boolean isIgnoreAccessors() { + return myIgnoreAccessors; + } + + public void setIgnoreAccessors(boolean ignoreAccessors) { + myIgnoreAccessors = ignoreAccessors; + } + + @Override + public LocalizeValue getGroupDisplayName() { + return InspectionLocalize.groupNamesDeclarationRedundancy(); + } + + @Override + public LocalizeValue getDisplayName() { + return InspectionLocalize.inspectionDeadCodeDisplayName(); + } + + @Override + public String getShortName() { + return SHORT_NAME; + } + + @Override + @Pattern(VALID_ID_PATTERN) + public String getID() { + return UNUSED_ID; + } + + @Override + public String getAlternativeID() { + return UnusedDeclarationInspectionBase.ALTERNATIVE_ID; + } + + @Override + public boolean isEnabledByDefault() { + return true; + } + + @Override + public HighlightDisplayLevel getDefaultLevel() { + return HighlightDisplayLevel.WARNING; + } + + @Override + public void writeSettings(Element node) throws WriteExternalException { + writeVisibility(node, myClassVisibility, "klass"); + writeVisibility(node, myInnerClassVisibility, "inner_class"); + writeVisibility(node, myFieldVisibility, "field"); + writeVisibility(node, myMethodVisibility, "method"); + writeVisibility(node, "parameter", myParameterVisibility, getParameterDefaultVisibility()); + if (myIgnoreAccessors) { + node.setAttribute("ignoreAccessors", Boolean.toString(true)); + } + if (!INNER_CLASS) { + node.setAttribute("INNER_CLASS", Boolean.toString(false)); + } + super.writeSettings(node); + } + + private static void writeVisibility(Element node, String visibility, String type) { + writeVisibility(node, type, visibility, PsiModifier.PUBLIC); + } + + private static void writeVisibility(Element node, String type, String visibility, String defaultVisibility) { + if (!defaultVisibility.equals(visibility)) { + node.setAttribute(type, visibility); + } + } + + private String getParameterDefaultVisibility() { + return REPORT_PARAMETER_FOR_PUBLIC_METHODS ? PsiModifier.PUBLIC : PsiModifier.PRIVATE; + } + + @Override + public void readSettings(Element node) throws InvalidDataException { + super.readSettings(node); + myClassVisibility = readVisibility(node, "klass"); + myInnerClassVisibility = readVisibility(node, "inner_class"); + myFieldVisibility = readVisibility(node, "field"); + myMethodVisibility = readVisibility(node, "method"); + myParameterVisibility = readVisibility(node, "parameter", getParameterDefaultVisibility()); + final String ignoreAccessors = node.getAttributeValue("ignoreAccessors"); + myIgnoreAccessors = ignoreAccessors != null && Boolean.parseBoolean(ignoreAccessors); + final String innerClassEnabled = node.getAttributeValue("INNER_CLASS"); + INNER_CLASS = innerClassEnabled == null || Boolean.parseBoolean(innerClassEnabled); + } + + private static String readVisibility(Element node, final String type) { + return readVisibility(node, type, PsiModifier.PUBLIC); + } + + private static String readVisibility(Element node, final String type, final String defaultVisibility) { + final String visibility = node.getAttributeValue(type); + if (visibility == null) { + return defaultVisibility; + } + return visibility; + } + + @Override + public ProblemHighlightType getControllableHighlightType() { + return ProblemHighlightType.LIKE_UNUSED_SYMBOL; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/OptionalUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/OptionalUtil.java index 2f9cccd4bf..b6af67a96c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/OptionalUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/OptionalUtil.java @@ -7,7 +7,6 @@ import com.siyeh.ig.psiutils.MethodCallUtils; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; import java.util.Optional; import java.util.OptionalDouble; @@ -37,7 +36,6 @@ public class OptionalUtil { public static final CallMatcher JDK_OPTIONAL_WRAP_METHOD = CallMatcher.staticCall(JAVA_UTIL_OPTIONAL, "of", "ofNullable").parameterCount(1); - @Nonnull @Contract(pure = true) public static String getOptionalClass(String type) { switch (type) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/SpecialAnnotationsUtilBase.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/SpecialAnnotationsUtilBase.java index 0b5912e3c1..2863b6f34f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/SpecialAnnotationsUtilBase.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/util/SpecialAnnotationsUtilBase.java @@ -19,6 +19,7 @@ import consulo.language.editor.inspection.scheme.InspectionProfile; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.util.lang.StringUtil; import consulo.language.editor.inspection.scheme.InspectionProfileManager; @@ -28,39 +29,30 @@ import com.intellij.java.language.psi.PsiModifierList; import com.intellij.java.language.psi.PsiModifierListOwner; import consulo.application.util.function.Processor; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; public class SpecialAnnotationsUtilBase { - public static LocalQuickFix createAddToSpecialAnnotationsListQuickFix(@Nonnull final String text, - @Nonnull final String family, - @Nonnull final List targetList, - @Nonnull final String qualifiedName, + public static LocalQuickFix createAddToSpecialAnnotationsListQuickFix(final LocalizeValue text, + final LocalizeValue family, + final List targetList, + final String qualifiedName, final PsiElement context) { return new LocalQuickFix() { @Override - @Nonnull - public String getName() { + public LocalizeValue getName() { return text; } @Override - @Nonnull - public String getFamilyName() { - return family; - } - - @Override - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + public void applyFix(final Project project, final ProblemDescriptor descriptor) { doQuickFixInternal(project, targetList, qualifiedName); } }; } - public static void doQuickFixInternal(@Nonnull Project project, @Nonnull List targetList, @Nonnull String qualifiedName) { + public static void doQuickFixInternal(Project project, List targetList, String qualifiedName) { targetList.add(qualifiedName); Collections.sort(targetList); final InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); @@ -79,12 +71,12 @@ public static void doQuickFixInternal(@Nonnull Project project, @Nonnull List processor) { + public static void createAddToSpecialAnnotationFixes(PsiModifierListOwner owner, Processor processor) { final PsiModifierList modifierList = owner.getModifierList(); if (modifierList != null) { final PsiAnnotation[] psiAnnotations = modifierList.getAnnotations(); for (PsiAnnotation psiAnnotation : psiAnnotations) { - @NonNls final String name = psiAnnotation.getQualifiedName(); + final String name = psiAnnotation.getQualifiedName(); if (name == null) continue; if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("org.jetbrains.") && AnnotationUtil.isJetbrainsAnnotation(StringUtil.getShortName(name))) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaClassFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaClassFindUsagesOptions.java index b29e03f4e9..3a35ed6b18 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaClassFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaClassFindUsagesOptions.java @@ -2,7 +2,6 @@ import java.util.LinkedHashSet; -import javax.annotation.Nonnull; import consulo.find.FindBundle; import consulo.project.Project; @@ -18,7 +17,7 @@ public class JavaClassFindUsagesOptions extends JavaFindUsagesOptions { public boolean isCheckDeepInheritance = true; public boolean isIncludeInherited = false; - public JavaClassFindUsagesOptions(@Nonnull Project project) { + public JavaClassFindUsagesOptions(Project project) { super(project); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesHelper.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesHelper.java index 0bfcd86a20..3636f53a4c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesHelper.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesHelper.java @@ -25,18 +25,17 @@ import com.intellij.java.language.psi.targets.AliasingPsiTargetMapper; import com.intellij.java.language.psi.util.MethodSignature; import com.intellij.java.language.psi.util.PsiUtil; -import consulo.application.ApplicationManager; -import consulo.application.ReadAction; +import consulo.annotation.access.RequiredReadAction; +import consulo.application.AccessRule; +import consulo.application.Application; import consulo.application.progress.ProgressIndicator; import consulo.application.progress.ProgressManager; import consulo.application.util.ReadActionProcessor; -import consulo.application.util.function.Processor; -import consulo.component.extension.Extensions; import consulo.content.scope.SearchScope; import consulo.document.util.TextRange; -import consulo.find.FindBundle; import consulo.find.FindUsagesHelper; import consulo.find.FindUsagesOptions; +import consulo.find.localize.FindLocalize; import consulo.language.inject.InjectedLanguageManager; import consulo.language.pom.PomService; import consulo.language.pom.PomTarget; @@ -51,516 +50,597 @@ import consulo.usage.UsageInfo; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Comparing; -import consulo.xml.psi.xml.XmlAttributeValue; +import consulo.util.lang.function.ThrowableSupplier; +import consulo.xml.language.psi.XmlAttributeValue; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; +import java.util.function.Predicate; import java.util.function.Supplier; public class JavaFindUsagesHelper { - private static final Logger LOG = Logger.getInstance(JavaFindUsagesHelper.class); + private static final Logger LOG = Logger.getInstance(JavaFindUsagesHelper.class); - @Nonnull - public static Set getElementNames(@Nonnull final PsiElement element) { - if (element instanceof PsiDirectory) { // normalize a directory to a corresponding package - PsiPackage aPackage = ReadAction.compute(() -> JavaDirectoryService.getInstance().getPackage((PsiDirectory) element)); - return aPackage == null ? Collections.emptySet() : getElementNames(aPackage); + public static Set getElementNames(PsiElement element) { + if (element instanceof PsiDirectory directory) { // normalize a directory to a corresponding package + PsiPackage aPackage = AccessRule.read(() -> JavaDirectoryService.getInstance().getPackage(directory)); + return aPackage == null ? Collections.emptySet() : getElementNames(aPackage); + } + + Set result = new HashSet<>(); + + Application.get().runReadAction(() -> { + if (element instanceof PsiPackage psiPackage) { + ContainerUtil.addIfNotNull(result, psiPackage.getQualifiedName()); + } + else if (element instanceof PsiClass psiClass) { + String qname = psiClass.getQualifiedName(); + if (qname != null) { + result.add(qname); + PsiClass topLevelClass = PsiUtil.getTopLevelClass(element); + if (topLevelClass != null && !(topLevelClass instanceof PsiSyntheticClass)) { + String topName = topLevelClass.getQualifiedName(); + assert topName != null : "topLevelClass : " + topLevelClass + ";" + + " element: " + element + " (" + qname + ")" + + " top level file: " + InjectedLanguageManager.getInstance(element.getProject()).getTopLevelFile(element); + if (qname.length() > topName.length()) { + result.add(topName + qname.substring(topName.length()).replace('.', '$')); + } + } + } + } + else if (element instanceof PsiMethod method) { + ContainerUtil.addIfNotNull(result, method.getName()); + } + else if (element instanceof PsiVariable variable) { + ContainerUtil.addIfNotNull(result, variable.getName()); + } + else if (element instanceof PsiMetaOwner metaOwner) { + PsiMetaData metaData = metaOwner.getMetaData(); + if (metaData != null) { + ContainerUtil.addIfNotNull(result, metaData.getName()); + } + } + else if (element instanceof PsiNamedElement namedElement) { + ContainerUtil.addIfNotNull(result, namedElement.getName()); + } + else if (element instanceof XmlAttributeValue xmlAttrValue) { + ContainerUtil.addIfNotNull(result, xmlAttrValue.getValue()); + } + else { + LOG.error("Unknown element type: " + element); + } + }); + + return result; } - final Set result = new HashSet<>(); - - ApplicationManager.getApplication().runReadAction(() -> - { - if (element instanceof PsiPackage) { - ContainerUtil.addIfNotNull(result, ((PsiPackage) element).getQualifiedName()); - } else if (element instanceof PsiClass) { - final String qname = ((PsiClass) element).getQualifiedName(); - if (qname != null) { - result.add(qname); - PsiClass topLevelClass = PsiUtil.getTopLevelClass(element); - if (topLevelClass != null && !(topLevelClass instanceof PsiSyntheticClass)) { - String topName = topLevelClass.getQualifiedName(); - assert topName != null : "topLevelClass : " + topLevelClass + "; element: " + element + " (" + qname + ") top level file: " + InjectedLanguageManager.getInstance(element - .getProject()).getTopLevelFile(element); - if (qname.length() > topName.length()) { - result.add(topName + qname.substring(topName.length()).replace('.', '$')); - } - } - } - } else if (element instanceof PsiMethod) { - ContainerUtil.addIfNotNull(result, ((PsiMethod) element).getName()); - } else if (element instanceof PsiVariable) { - ContainerUtil.addIfNotNull(result, ((PsiVariable) element).getName()); - } else if (element instanceof PsiMetaOwner) { - final PsiMetaData metaData = ((PsiMetaOwner) element).getMetaData(); - if (metaData != null) { - ContainerUtil.addIfNotNull(result, metaData.getName()); + public static boolean processElementUsages( + PsiElement element, + FindUsagesOptions options, + Predicate processor + ) { + if (options instanceof JavaVariableFindUsagesOptions varOptions) { + if (varOptions.isReadAccess || varOptions.isWriteAccess) { + if (varOptions.isReadAccess && varOptions.isWriteAccess) { + if (!addElementUsages(element, options, processor)) { + return false; + } + } + else { + if (!addElementUsages( + element, + varOptions, + info -> { + boolean isWrite = info.getElement() instanceof PsiExpression expression + && PsiUtil.isAccessedForWriting(expression); + return isWrite != varOptions.isWriteAccess || processor.test(info); + } + )) { + return false; + } + } + } } - } else if (element instanceof PsiNamedElement) { - ContainerUtil.addIfNotNull(result, ((PsiNamedElement) element).getName()); - } else if (element instanceof XmlAttributeValue) { - ContainerUtil.addIfNotNull(result, ((XmlAttributeValue) element).getValue()); - } else { - LOG.error("Unknown element type: " + element); - } - }); - - return result; - } - - public static boolean processElementUsages(@Nonnull final PsiElement element, @Nonnull final FindUsagesOptions options, @Nonnull final Processor processor) { - if (options instanceof JavaVariableFindUsagesOptions) { - final JavaVariableFindUsagesOptions varOptions = (JavaVariableFindUsagesOptions) options; - if (varOptions.isReadAccess || varOptions.isWriteAccess) { - if (varOptions.isReadAccess && varOptions.isWriteAccess) { - if (!addElementUsages(element, options, processor)) { - return false; - } - } else { - if (!addElementUsages(element, varOptions, info -> - { - final PsiElement element1 = info.getElement(); - boolean isWrite = element1 instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression) element1); - if (isWrite == varOptions.isWriteAccess) { - if (!processor.process(info)) { + else if (options.isUsages) { + if (!addElementUsages(element, options, processor)) { return false; - } + } + } + + boolean success = AccessRule.read(() -> { + if (ThrowSearchUtil.isSearchable(element) + && options instanceof JavaThrowFindUsagesOptions throwOptions + && options.isUsages) { + ThrowSearchUtil.Root root = throwOptions.getRoot(); + if (root == null) { + ThrowSearchUtil.Root[] roots = ThrowSearchUtil.getSearchRoots(element); + if (roots != null && roots.length > 0) { + root = roots[0]; + } + } + if (root != null) { + return ThrowSearchUtil.addThrowUsages(processor, root, options); + } } return true; - })) { + }); + if (!success) { return false; - } } - } - } else if (options.isUsages) { - if (!addElementUsages(element, options, processor)) { - return false; - } - } - boolean success = ReadAction.compute(() -> - { - if (ThrowSearchUtil.isSearchable(element) && options instanceof JavaThrowFindUsagesOptions && options.isUsages) { - ThrowSearchUtil.Root root = ((JavaThrowFindUsagesOptions) options).getRoot(); - if (root == null) { - final ThrowSearchUtil.Root[] roots = ThrowSearchUtil.getSearchRoots(element); - if (roots != null && roots.length > 0) { - root = roots[0]; - } - } - if (root != null) { - return ThrowSearchUtil.addThrowUsages(processor, root, options); + if (options instanceof JavaPackageFindUsagesOptions packageOptions && packageOptions.isClassesUsages + && !addClassesUsages((PsiPackage)element, packageOptions, processor)) { + return false; } - } - return true; - }); - if (!success) { - return false; - } - if (options instanceof JavaPackageFindUsagesOptions && ((JavaPackageFindUsagesOptions) options).isClassesUsages) { - if (!addClassesUsages((PsiPackage) element, (JavaPackageFindUsagesOptions) options, processor)) { - return false; - } - } - - if (options instanceof JavaClassFindUsagesOptions) { - final JavaClassFindUsagesOptions classOptions = (JavaClassFindUsagesOptions) options; - final PsiClass psiClass = (PsiClass) element; - PsiManager manager = ReadAction.compute(psiClass::getManager); - if (classOptions.isMethodsUsages) { - if (!addMethodsUsages(psiClass, manager, classOptions, processor)) { - return false; - } - } - if (classOptions.isFieldsUsages) { - if (!addFieldsUsages(psiClass, manager, classOptions, processor)) { - return false; + if (options instanceof JavaClassFindUsagesOptions classOptions) { + PsiClass psiClass = (PsiClass)element; + PsiManager manager = AccessRule.read(psiClass::getManager); + if (classOptions.isMethodsUsages && !addMethodsUsages(psiClass, manager, classOptions, processor)) { + return false; + } + if (classOptions.isFieldsUsages && !addFieldsUsages(psiClass, manager, classOptions, processor)) { + return false; + } + if (AccessRule.read(psiClass::isInterface)) { + if (classOptions.isDerivedInterfaces) { + if (classOptions.isImplementingClasses) { + if (!addInheritors(psiClass, classOptions, processor)) { + return false; + } + } + else if (!addDerivedInterfaces(psiClass, classOptions, processor)) { + return false; + } + } + else if (classOptions.isImplementingClasses && !addImplementingClasses(psiClass, classOptions, processor)) { + return false; + } + + if (classOptions.isImplementingClasses) { + FunctionalExpressionSearch.search(psiClass, classOptions.searchScope) + .forEach(new PsiElementProcessorAdapter<>(expression -> addResult(expression, options, processor))); + } + } + else if (classOptions.isDerivedClasses && !addInheritors(psiClass, classOptions, processor)) { + return false; + } } - } - if (ReadAction.compute(psiClass::isInterface)) { - if (classOptions.isDerivedInterfaces) { - if (classOptions.isImplementingClasses) { - if (!addInheritors(psiClass, classOptions, processor)) { - return false; - } - } else { - if (!addDerivedInterfaces(psiClass, classOptions, processor)) { - return false; - } - } - } else if (classOptions.isImplementingClasses) { - if (!addImplementingClasses(psiClass, classOptions, processor)) { - return false; - } + + if (options instanceof JavaMethodFindUsagesOptions methodOptions) { + PsiMethod psiMethod = (PsiMethod)element; + boolean isAbstract = AccessRule.read(psiMethod::isAbstract); + if (isAbstract && methodOptions.isImplementingMethods || methodOptions.isOverridingMethods) { + if (!processOverridingMethods(psiMethod, processor, methodOptions)) { + return false; + } + FunctionalExpressionSearch.search(psiMethod, methodOptions.searchScope) + .forEach(new PsiElementProcessorAdapter<>(expression -> addResult(expression, options, processor))); + } } - if (classOptions.isImplementingClasses) { - FunctionalExpressionSearch.search(psiClass, classOptions.searchScope).forEach(new PsiElementProcessorAdapter<>(expression -> addResult(expression, options, processor))); + if (element instanceof PomTarget pomTarget && !addAliasingUsages(pomTarget, options, processor)) { + return false; } - } else if (classOptions.isDerivedClasses) { - if (!addInheritors(psiClass, classOptions, processor)) { - return false; + Boolean isSearchable = AccessRule.read(() -> ThrowSearchUtil.isSearchable(element)); + if (!isSearchable && options.isSearchForTextOccurrences && options.searchScope instanceof GlobalSearchScope globalSearchScope) { + Collection stringsToSearch = + Application.get().runReadAction((Supplier>)() -> getElementNames(element)); + // todo add to fastTrack + if (!FindUsagesHelper.processUsagesInText(element, stringsToSearch, globalSearchScope, processor)) { + return false; + } } - } + return true; } - if (options instanceof JavaMethodFindUsagesOptions) { - final PsiMethod psiMethod = (PsiMethod) element; - boolean isAbstract = ReadAction.compute(() -> psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)); - final JavaMethodFindUsagesOptions methodOptions = (JavaMethodFindUsagesOptions) options; - if (isAbstract && methodOptions.isImplementingMethods || methodOptions.isOverridingMethods) { - if (!processOverridingMethods(psiMethod, processor, methodOptions)) { - return false; + private static boolean addAliasingUsages( + PomTarget pomTarget, + FindUsagesOptions options, + Predicate processor + ) { + for (AliasingPsiTargetMapper aliasingPsiTargetMapper : AliasingPsiTargetMapper.EP_NAME.getExtensionList()) { + for (AliasingPsiTarget psiTarget : aliasingPsiTargetMapper.getTargets(pomTarget)) { + boolean success = ReferencesSearch.search(new ReferencesSearch.SearchParameters( + AccessRule.read(() -> PomService.convertToPsi(psiTarget)), + options.searchScope, + false, + options.fastTrack + )) + .forEach(new ReadActionProcessor<>() { + @Override + @RequiredReadAction + public boolean processInReadAction(PsiReference reference) { + return addResult(reference, options, processor); + } + }); + if (!success) { + return false; + } + } } - FunctionalExpressionSearch.search(psiMethod, methodOptions.searchScope).forEach(new PsiElementProcessorAdapter<>(expression -> addResult(expression, options, processor))); - } + return true; } - if (element instanceof PomTarget) { - if (!addAliasingUsages((PomTarget) element, options, processor)) { - return false; - } - } - final Boolean isSearchable = ReadAction.compute(() -> ThrowSearchUtil.isSearchable(element)); - if (!isSearchable && options.isSearchForTextOccurrences && options.searchScope instanceof GlobalSearchScope) { - Collection stringsToSearch = ApplicationManager.getApplication().runReadAction((Supplier>) () -> getElementNames(element)); - // todo add to fastTrack - if (!FindUsagesHelper.processUsagesInText(element, stringsToSearch, (GlobalSearchScope) options.searchScope, processor)) { - return false; - } - } - return true; - } - - private static boolean addAliasingUsages(@Nonnull PomTarget pomTarget, @Nonnull final FindUsagesOptions options, @Nonnull final Processor processor) { - for (AliasingPsiTargetMapper aliasingPsiTargetMapper : Extensions.getExtensions(AliasingPsiTargetMapper.EP_NAME)) { - for (final AliasingPsiTarget psiTarget : aliasingPsiTargetMapper.getTargets(pomTarget)) { - boolean success = ReferencesSearch.search(new ReferencesSearch.SearchParameters(ReadAction.compute(() -> PomService.convertToPsi(psiTarget)), options.searchScope, false, options - .fastTrack)).forEach(new ReadActionProcessor() { - @Override - public boolean processInReadAction(final PsiReference reference) { - return addResult(reference, options, processor); - } - }); - if (!success) { - return false; - } - } - } - return true; - } - - private static boolean processOverridingMethods(@Nonnull PsiMethod psiMethod, @Nonnull final Processor processor, @Nonnull final JavaMethodFindUsagesOptions options) { - return OverridingMethodsSearch.search(psiMethod, options.searchScope, options.isCheckDeepInheritance).forEach(new PsiElementProcessorAdapter<>(element -> addResult(element - .getNavigationElement(), options, processor))); - } - - private static boolean addClassesUsages(@Nonnull PsiPackage aPackage, @Nonnull final JavaPackageFindUsagesOptions options, @Nonnull final Processor processor) { - ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); - if (progress != null) { - progress.pushState(); + private static boolean processOverridingMethods( + PsiMethod psiMethod, + Predicate processor, + JavaMethodFindUsagesOptions options + ) { + return OverridingMethodsSearch.search(psiMethod, options.searchScope, options.isCheckDeepInheritance) + .forEach(new PsiElementProcessorAdapter<>( + element -> addResult(element.getNavigationElement(), options, processor) + )); } - try { - List classes = new ArrayList<>(); - addClassesInPackage(aPackage, options.isIncludeSubpackages, classes); - for (final PsiClass aClass : classes) { + private static boolean addClassesUsages( + PsiPackage aPackage, + JavaPackageFindUsagesOptions options, + Predicate processor + ) { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); if (progress != null) { - String name = ReadAction.compute(aClass::getName); - progress.setText(FindBundle.message("find.searching.for.references.to.class.progress", name)); + progress.pushState(); } - ProgressManager.checkCanceled(); - boolean success = ReferencesSearch.search(new ReferencesSearch.SearchParameters(aClass, options.searchScope, false, options.fastTrack)).forEach(new ReadActionProcessor() { - @Override - public boolean processInReadAction(final PsiReference psiReference) { - return addResult(psiReference, options, processor); - } - }); - if (!success) { - return false; - } - } - } finally { - if (progress != null) { - progress.popState(); - } - } - return true; - } + try { + List classes = new ArrayList<>(); + addClassesInPackage(aPackage, options.isIncludeSubpackages, classes); + for (PsiClass aClass : classes) { + if (progress != null) { + String name = AccessRule.read(aClass::getName); + progress.setText(FindLocalize.findSearchingForReferencesToClassProgress(name)); + } + ProgressManager.checkCanceled(); + boolean success = ReferencesSearch.search( + new ReferencesSearch.SearchParameters(aClass, options.searchScope, false, options.fastTrack) + ) + .forEach(new ReadActionProcessor<>() { + @Override + @RequiredReadAction + public boolean processInReadAction(PsiReference psiReference) { + return addResult(psiReference, options, processor); + } + }); + if (!success) { + return false; + } + } + } + finally { + if (progress != null) { + progress.popState(); + } + } - private static void addClassesInPackage(@Nonnull final PsiPackage aPackage, boolean includeSubpackages, @Nonnull List array) { - PsiDirectory[] dirs = ReadAction.compute(aPackage::getDirectories); - for (PsiDirectory dir : dirs) { - addClassesInDirectory(dir, includeSubpackages, array); + return true; } - } - - private static void addClassesInDirectory(@Nonnull final PsiDirectory dir, final boolean includeSubdirs, @Nonnull final List array) { - ApplicationManager.getApplication().runReadAction(() -> - { - PsiClass[] classes = JavaDirectoryService.getInstance().getClasses(dir); - ContainerUtil.addAll(array, classes); - if (includeSubdirs) { - PsiDirectory[] dirs = dir.getSubdirectories(); - for (PsiDirectory directory : dirs) { - addClassesInDirectory(directory, true, array); + + private static void addClassesInPackage(PsiPackage aPackage, boolean includeSubpackages, List array) { + PsiDirectory[] dirs = AccessRule.read((ThrowableSupplier)aPackage::getDirectories); + for (PsiDirectory dir : dirs) { + addClassesInDirectory(dir, includeSubpackages, array); } - } - }); - } - - private static boolean addMethodsUsages(@Nonnull final PsiClass aClass, - @Nonnull final PsiManager manager, - @Nonnull final JavaClassFindUsagesOptions options, - @Nonnull final Processor processor) { - if (options.isIncludeInherited) { - final PsiMethod[] methods = ReadAction.compute(aClass::getAllMethods); - for (int i = 0; i < methods.length; i++) { - final PsiMethod method = methods[i]; - // filter overridden methods - final int finalI = i; - final PsiClass methodClass = ReadAction.compute(() -> - { - MethodSignature methodSignature = method.getSignature(PsiSubstitutor.EMPTY); - for (int j = 0; j < finalI; j++) { - if (methodSignature.equals(methods[j].getSignature(PsiSubstitutor.EMPTY))) { - return null; - } - } - return method.getContainingClass(); + } + + private static void addClassesInDirectory( + PsiDirectory dir, + boolean includeSubdirs, + List array + ) { + Application.get().runReadAction(() -> { + PsiClass[] classes = JavaDirectoryService.getInstance().getClasses(dir); + ContainerUtil.addAll(array, classes); + if (includeSubdirs) { + PsiDirectory[] dirs = dir.getSubdirectories(); + for (PsiDirectory directory : dirs) { + addClassesInDirectory(directory, true, array); + } + } }); - if (methodClass == null) { - continue; - } - boolean equivalent = ReadAction.compute(() -> manager.areElementsEquivalent(methodClass, aClass)); - if (equivalent) { - if (!addElementUsages(method, options, processor)) { - return false; - } - } else { - MethodReferencesSearch.SearchParameters parameters = new MethodReferencesSearch.SearchParameters(method, options.searchScope, true, options.fastTrack); - boolean success = MethodReferencesSearch.search(parameters).forEach(new PsiReferenceProcessorAdapter(reference -> - { - addResultFromReference(reference, methodClass, manager, aClass, options, processor); - return true; - })); - if (!success) { - return false; - } + } + + private static boolean addMethodsUsages( + PsiClass aClass, + PsiManager manager, + JavaClassFindUsagesOptions options, + Predicate processor + ) { + if (options.isIncludeInherited) { + PsiMethod[] methods = AccessRule.read(aClass::getAllMethods); + for (int i = 0; i < methods.length; i++) { + PsiMethod method = methods[i]; + // filter overridden methods + int finalI = i; + PsiClass methodClass = AccessRule.read(() -> { + MethodSignature methodSignature = method.getSignature(PsiSubstitutor.EMPTY); + for (int j = 0; j < finalI; j++) { + if (methodSignature.equals(methods[j].getSignature(PsiSubstitutor.EMPTY))) { + return null; + } + } + return method.getContainingClass(); + }); + if (methodClass == null) { + continue; + } + boolean equivalent = AccessRule.read(() -> manager.areElementsEquivalent(methodClass, aClass)); + if (equivalent) { + if (!addElementUsages(method, options, processor)) { + return false; + } + } + else { + MethodReferencesSearch.SearchParameters parameters = + new MethodReferencesSearch.SearchParameters(method, options.searchScope, true, options.fastTrack); + boolean success = MethodReferencesSearch.search(parameters).forEach(new PsiReferenceProcessorAdapter(reference -> { + addResultFromReference(reference, methodClass, manager, aClass, options, processor); + return true; + })); + if (!success) { + return false; + } + } + } } - } - } else { - PsiMethod[] methods = ReadAction.compute(aClass::getMethods); - for (PsiMethod method : methods) { - if (!addElementUsages(method, options, processor)) { - return false; + else { + PsiMethod[] methods = AccessRule.read(aClass::getMethods); + for (PsiMethod method : methods) { + if (!addElementUsages(method, options, processor)) { + return false; + } + } } - } + return true; } - return true; - } - - private static boolean addFieldsUsages(@Nonnull final PsiClass aClass, - @Nonnull final PsiManager manager, - @Nonnull final JavaClassFindUsagesOptions options, - @Nonnull final Processor processor) { - if (options.isIncludeInherited) { - final PsiField[] fields = ReadAction.compute(aClass::getAllFields); - for (int i = 0; i < fields.length; i++) { - final PsiField field = fields[i]; - // filter hidden fields - final int finalI = i; - final PsiClass fieldClass = ReadAction.compute(() -> - { - for (int j = 0; j < finalI; j++) { - if (Comparing.strEqual(field.getName(), fields[j].getName())) { - return null; - } - } - return field.getContainingClass(); - }); - if (fieldClass == null) { - continue; - } - boolean equivalent = ReadAction.compute(() -> manager.areElementsEquivalent(fieldClass, aClass)); - if (equivalent) { - if (!addElementUsages(fields[i], options, processor)) { - return false; - } - } else { - boolean success = ReferencesSearch.search(new ReferencesSearch.SearchParameters(field, options.searchScope, false, options.fastTrack)).forEach(new - ReadActionProcessor() { - @Override - public boolean processInReadAction(final PsiReference reference) { - return addResultFromReference(reference, fieldClass, manager, aClass, options, processor); - } - }); - if (!success) { - return false; - } + + private static boolean addFieldsUsages( + PsiClass aClass, + PsiManager manager, + JavaClassFindUsagesOptions options, + Predicate processor + ) { + if (options.isIncludeInherited) { + PsiField[] fields = AccessRule.read(aClass::getAllFields); + for (int i = 0; i < fields.length; i++) { + PsiField field = fields[i]; + // filter hidden fields + int finalI = i; + ThrowableSupplier action = () -> { + for (int j = 0; j < finalI; j++) { + if (Comparing.strEqual(field.getName(), fields[j].getName())) { + return null; + } + } + return field.getContainingClass(); + }; + PsiClass fieldClass = AccessRule.read(action); + if (fieldClass == null) { + continue; + } + boolean equivalent = AccessRule.read(() -> manager.areElementsEquivalent(fieldClass, aClass)); + if (equivalent) { + if (!addElementUsages(fields[i], options, processor)) { + return false; + } + } + else { + boolean success = ReferencesSearch.search( + new ReferencesSearch.SearchParameters(field, options.searchScope, false, options.fastTrack) + ) + .forEach(new ReadActionProcessor<>() { + @Override + @RequiredReadAction + public boolean processInReadAction(PsiReference reference) { + return addResultFromReference(reference, fieldClass, manager, aClass, options, processor); + } + }); + if (!success) { + return false; + } + } + } } - } - } else { - PsiField[] fields = ReadAction.compute(aClass::getFields); - for (PsiField field : fields) { - if (!addElementUsages(field, options, processor)) { - return false; + else { + for (PsiField field : AccessRule.read(aClass::getFields)) { + if (!addElementUsages(field, options, processor)) { + return false; + } + } } - } + return true; } - return true; - } - - @Nullable - private static PsiClass getFieldOrMethodAccessedClass(@Nonnull PsiReferenceExpression ref, @Nonnull PsiClass fieldOrMethodClass) { - PsiElement[] children = ref.getChildren(); - if (children.length > 1 && children[0] instanceof PsiExpression) { - PsiExpression expr = (PsiExpression) children[0]; - PsiType type = expr.getType(); - if (type != null) { - if (!(type instanceof PsiClassType)) { - return null; + + @Nullable + @RequiredReadAction + private static PsiClass getFieldOrMethodAccessedClass(PsiReferenceExpression ref, PsiClass fieldOrMethodClass) { + PsiElement[] children = ref.getChildren(); + if (children.length > 1 && children[0] instanceof PsiExpression expr) { + PsiType type = expr.getType(); + if (type != null) { + return type instanceof PsiClassType ? PsiUtil.resolveClassInType(type) : null; + } + else { + if (expr instanceof PsiReferenceExpression refExpr && refExpr.resolve() instanceof PsiClass psiClass) { + return psiClass; + } + return null; + } } - return PsiUtil.resolveClassInType(type); - } else { - if (expr instanceof PsiReferenceExpression) { - PsiElement refElement = ((PsiReferenceExpression) expr).resolve(); - if (refElement instanceof PsiClass) { - return (PsiClass) refElement; - } + PsiManager manager = ref.getManager(); + for (PsiElement parent = ref; parent != null; parent = parent.getParent()) { + if (parent instanceof PsiClass psiClass && (manager.areElementsEquivalent(parent, fieldOrMethodClass) + || psiClass.isInheritor(fieldOrMethodClass, true))) { + return psiClass; + } } return null; - } } - PsiManager manager = ref.getManager(); - for (PsiElement parent = ref; parent != null; parent = parent.getParent()) { - if (parent instanceof PsiClass && (manager.areElementsEquivalent(parent, fieldOrMethodClass) || ((PsiClass) parent).isInheritor(fieldOrMethodClass, true))) { - return (PsiClass) parent; - } + + private static boolean addInheritors( + PsiClass aClass, + JavaClassFindUsagesOptions options, + Predicate processor + ) { + return ClassInheritorsSearch.search(aClass, options.searchScope, options.isCheckDeepInheritance) + .forEach(new PsiElementProcessorAdapter<>(element -> addResult(element, options, processor))); } - return null; - } - - private static boolean addInheritors(@Nonnull PsiClass aClass, @Nonnull final JavaClassFindUsagesOptions options, @Nonnull final Processor processor) { - return ClassInheritorsSearch.search(aClass, options.searchScope, options.isCheckDeepInheritance).forEach(new PsiElementProcessorAdapter<>(element -> addResult(element, options, processor))); - } - - private static boolean addDerivedInterfaces(@Nonnull PsiClass anInterface, @Nonnull final JavaClassFindUsagesOptions options, @Nonnull final Processor processor) { - return ClassInheritorsSearch.search(anInterface, options.searchScope, options.isCheckDeepInheritance).forEach(new PsiElementProcessorAdapter<>(inheritor -> !inheritor.isInterface() || - addResult(inheritor, options, processor))); - } - - private static boolean addImplementingClasses(@Nonnull PsiClass anInterface, @Nonnull final JavaClassFindUsagesOptions options, @Nonnull final Processor processor) { - return ClassInheritorsSearch.search(anInterface, options.searchScope, options.isCheckDeepInheritance).forEach(new PsiElementProcessorAdapter<>(inheritor -> inheritor.isInterface() || - addResult(inheritor, options, processor))); - } - - private static boolean addResultFromReference(@Nonnull PsiReference reference, - @Nonnull PsiClass methodClass, - @Nonnull PsiManager manager, - @Nonnull PsiClass aClass, - @Nonnull FindUsagesOptions options, - @Nonnull Processor processor) { - PsiElement refElement = reference.getElement(); - if (refElement instanceof PsiReferenceExpression) { - PsiClass usedClass = getFieldOrMethodAccessedClass((PsiReferenceExpression) refElement, methodClass); - if (usedClass != null) { - if (manager.areElementsEquivalent(usedClass, aClass) || usedClass.isInheritor(aClass, true)) { - if (!addResult(refElement, options, processor)) { - return false; - } - } - } + + private static boolean addDerivedInterfaces( + PsiClass anInterface, + JavaClassFindUsagesOptions options, + Predicate processor + ) { + return ClassInheritorsSearch.search(anInterface, options.searchScope, options.isCheckDeepInheritance).forEach( + new PsiElementProcessorAdapter<>(inheritor -> !inheritor.isInterface() || addResult(inheritor, options, processor)) + ); } - return true; - } - - private static boolean addElementUsages(@Nonnull final PsiElement element, @Nonnull final FindUsagesOptions options, @Nonnull final Processor processor) { - final SearchScope searchScope = options.searchScope; - final PsiClass[] parentClass = new PsiClass[1]; - if (element instanceof PsiMethod && ReadAction.compute(() -> - { - PsiMethod method = (PsiMethod) element; - parentClass[0] = method.getContainingClass(); - return method.isConstructor(); - })) { - PsiMethod method = (PsiMethod) element; - - if (parentClass[0] != null) { - boolean strictSignatureSearch = !(options instanceof JavaMethodFindUsagesOptions) || !((JavaMethodFindUsagesOptions) options).isIncludeOverloadUsages; - return MethodReferencesSearch.search(new MethodReferencesSearch.SearchParameters(method, searchScope, strictSignatureSearch, options.fastTrack)).forEach(new - ReadActionProcessor() { - @Override - public boolean processInReadAction(final PsiReference ref) { - return addResult(ref, options, processor); - } - }); - } - return true; + + private static boolean addImplementingClasses( + PsiClass anInterface, + JavaClassFindUsagesOptions options, + Predicate processor + ) { + return ClassInheritorsSearch.search(anInterface, options.searchScope, options.isCheckDeepInheritance).forEach( + new PsiElementProcessorAdapter<>(inheritor -> inheritor.isInterface() || addResult(inheritor, options, processor)) + ); } - final ReadActionProcessor consumer = new ReadActionProcessor() { - @Override - public boolean processInReadAction(final PsiReference ref) { - return addResult(ref, options, processor); - } - }; - - if (element instanceof PsiMethod) { - final boolean strictSignatureSearch = !(options instanceof JavaMethodFindUsagesOptions) || // field with getter - !((JavaMethodFindUsagesOptions) options).isIncludeOverloadUsages; - return MethodReferencesSearch.search(new MethodReferencesSearch.SearchParameters((PsiMethod) element, searchScope, strictSignatureSearch, options.fastTrack)).forEach(consumer); + @RequiredReadAction + private static boolean addResultFromReference( + PsiReference reference, + PsiClass methodClass, + PsiManager manager, + PsiClass aClass, + FindUsagesOptions options, + Predicate processor + ) { + PsiElement refElement = reference.getElement(); + if (refElement instanceof PsiReferenceExpression refExpr) { + PsiClass usedClass = getFieldOrMethodAccessedClass(refExpr, methodClass); + if (usedClass != null && (manager.areElementsEquivalent(usedClass, aClass) || usedClass.isInheritor(aClass, true))) { + if (!addResult(refElement, options, processor)) { + return false; + } + } + } + return true; } - return ReferencesSearch.search(new ReferencesSearch.SearchParameters(element, searchScope, false, options.fastTrack)).forEach(consumer); - } - private static boolean addResult(@Nonnull PsiElement element, @Nonnull FindUsagesOptions options, @Nonnull Processor processor) { - return !filterUsage(element, options) || processor.process(new UsageInfo(element)); - } + private static boolean addElementUsages( + PsiElement element, + FindUsagesOptions options, + Predicate processor + ) { + SearchScope searchScope = options.searchScope; + PsiClass[] parentClass = new PsiClass[1]; + if (element instanceof PsiMethod method && AccessRule.read(() -> { + parentClass[0] = method.getContainingClass(); + return method.isConstructor(); + })) { + if (parentClass[0] != null) { + boolean strictSignatureSearch = + !(options instanceof JavaMethodFindUsagesOptions methodOptions) || !methodOptions.isIncludeOverloadUsages; + return MethodReferencesSearch.search(new MethodReferencesSearch.SearchParameters( + method, + searchScope, + strictSignatureSearch, + options.fastTrack + )).forEach(new ReadActionProcessor<>() { + @Override + @RequiredReadAction + public boolean processInReadAction(PsiReference ref) { + return addResult(ref, options, processor); + } + }); + } + return true; + } - private static boolean addResult(@Nonnull PsiReference ref, @Nonnull FindUsagesOptions options, @Nonnull Processor processor) { - if (filterUsage(ref.getElement(), options)) { - TextRange rangeInElement = ref.getRangeInElement(); - return processor.process(new UsageInfo(ref.getElement(), rangeInElement.getStartOffset(), rangeInElement.getEndOffset(), false)); + ReadActionProcessor consumer = new ReadActionProcessor<>() { + @Override + @RequiredReadAction + public boolean processInReadAction(PsiReference ref) { + return addResult(ref, options, processor); + } + }; + + if (element instanceof PsiMethod method) { + boolean strictSignatureSearch = !(options instanceof JavaMethodFindUsagesOptions methodOptions) || // field with getter + !methodOptions.isIncludeOverloadUsages; + return MethodReferencesSearch.search(new MethodReferencesSearch.SearchParameters( + method, + searchScope, + strictSignatureSearch, + options.fastTrack + )).forEach(consumer); + } + return ReferencesSearch.search(new ReferencesSearch.SearchParameters(element, searchScope, false, options.fastTrack)) + .forEach(consumer); } - return true; - } - private static boolean filterUsage(PsiElement usage, @Nonnull FindUsagesOptions options) { - if (!(usage instanceof PsiJavaCodeReferenceElement)) { - return true; + @RequiredReadAction + private static boolean addResult( + PsiElement element, + FindUsagesOptions options, + Predicate processor + ) { + return !filterUsage(element, options) || processor.test(new UsageInfo(element)); } - if (options instanceof JavaPackageFindUsagesOptions && !((JavaPackageFindUsagesOptions) options).isIncludeSubpackages && ((PsiReference) usage).resolve() instanceof PsiPackage) { - PsiElement parent = usage.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement) parent).resolve() instanceof PsiPackage) { - return false; - } + + @RequiredReadAction + private static boolean addResult( + PsiReference ref, + FindUsagesOptions options, + Predicate processor + ) { + if (filterUsage(ref.getElement(), options)) { + TextRange rangeInElement = ref.getRangeInElement(); + return processor.test(new UsageInfo( + ref.getElement(), + rangeInElement.getStartOffset(), + rangeInElement.getEndOffset(), + false + )); + } + return true; } - if (!(usage instanceof PsiReferenceExpression)) { - if (options instanceof JavaFindUsagesOptions && ((JavaFindUsagesOptions) options).isSkipImportStatements) { - PsiElement parent = usage.getParent(); - while (parent instanceof PsiJavaCodeReferenceElement) { - parent = parent.getParent(); + @RequiredReadAction + private static boolean filterUsage(PsiElement usage, FindUsagesOptions options) { + if (!(usage instanceof PsiJavaCodeReferenceElement javaCodeRef)) { + return true; } - if (parent instanceof PsiImportStatement) { - return false; + if (options instanceof JavaPackageFindUsagesOptions packageOptions + && !packageOptions.isIncludeSubpackages + && javaCodeRef.resolve() instanceof PsiPackage + && usage.getParent() instanceof PsiJavaCodeReferenceElement codeRef + && codeRef.resolve() instanceof PsiPackage) { + return false; } - } - if (options instanceof JavaPackageFindUsagesOptions && ((JavaPackageFindUsagesOptions) options).isSkipPackageStatements) { - PsiElement parent = usage.getParent(); - while (parent instanceof PsiJavaCodeReferenceElement) { - parent = parent.getParent(); - } - if (parent instanceof PsiPackageStatement) { - return false; + if (!(usage instanceof PsiReferenceExpression)) { + if (options instanceof JavaFindUsagesOptions javaOptions && javaOptions.isSkipImportStatements) { + PsiElement parent = usage.getParent(); + while (parent instanceof PsiJavaCodeReferenceElement) { + parent = parent.getParent(); + } + if (parent instanceof PsiImportStatement) { + return false; + } + } + + if (options instanceof JavaPackageFindUsagesOptions packageOptions && packageOptions.isSkipPackageStatements) { + PsiElement parent = usage.getParent(); + while (parent instanceof PsiJavaCodeReferenceElement) { + parent = parent.getParent(); + } + if (parent instanceof PsiPackageStatement) { + return false; + } + } } - } + return true; } - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesOptions.java index 7dd034e7ad..3c43e98e90 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaFindUsagesOptions.java @@ -5,7 +5,6 @@ import consulo.project.Project; import consulo.util.lang.StringUtil; -import javax.annotation.Nonnull; import java.util.LinkedHashSet; /** @@ -15,7 +14,7 @@ public abstract class JavaFindUsagesOptions extends FindUsagesOptions { public boolean isSkipImportStatements = false; - public JavaFindUsagesOptions(@Nonnull Project project) { + public JavaFindUsagesOptions(Project project) { super(project, null); isUsages = true; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaMethodFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaMethodFindUsagesOptions.java index a1eeff9e0f..c3c78d9a9c 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaMethodFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaMethodFindUsagesOptions.java @@ -3,7 +3,6 @@ import consulo.find.FindBundle; import consulo.project.Project; -import javax.annotation.Nonnull; import java.util.LinkedHashSet; /** @@ -16,7 +15,7 @@ public class JavaMethodFindUsagesOptions extends JavaFindUsagesOptions { public boolean isIncludeInherited = false; public boolean isIncludeOverloadUsages = false; - public JavaMethodFindUsagesOptions(@Nonnull Project project) { + public JavaMethodFindUsagesOptions(Project project) { super(project); isSearchForTextOccurrences = false; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaPackageFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaPackageFindUsagesOptions.java index 32a88745c3..1089392212 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaPackageFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaPackageFindUsagesOptions.java @@ -3,7 +3,6 @@ import consulo.find.FindBundle; import consulo.project.Project; -import javax.annotation.Nonnull; import java.util.LinkedHashSet; /** @@ -14,7 +13,7 @@ public class JavaPackageFindUsagesOptions extends JavaFindUsagesOptions { public boolean isIncludeSubpackages = true; public boolean isSkipPackageStatements = false; - public JavaPackageFindUsagesOptions(@Nonnull Project project) { + public JavaPackageFindUsagesOptions(Project project) { super(project); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaThrowFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaThrowFindUsagesOptions.java index 62038f2615..86d42b3364 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaThrowFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaThrowFindUsagesOptions.java @@ -18,8 +18,6 @@ import com.intellij.java.analysis.impl.psi.impl.search.ThrowSearchUtil; import consulo.project.Project; -import javax.annotation.Nonnull; - /** * @author peter */ @@ -27,7 +25,7 @@ public class JavaThrowFindUsagesOptions extends JavaFindUsagesOptions { private ThrowSearchUtil.Root root; - public JavaThrowFindUsagesOptions(@Nonnull Project project) + public JavaThrowFindUsagesOptions(Project project) { super(project); isSearchForTextOccurrences = false; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaVariableFindUsagesOptions.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaVariableFindUsagesOptions.java index 60a8b80289..ae05e01f20 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaVariableFindUsagesOptions.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/find/findUsages/JavaVariableFindUsagesOptions.java @@ -2,8 +2,6 @@ import consulo.project.Project; -import javax.annotation.Nonnull; - /** * @author peter */ @@ -11,7 +9,7 @@ public class JavaVariableFindUsagesOptions extends JavaFindUsagesOptions { public boolean isReadAccess = true; public boolean isWriteAccess = true; - public JavaVariableFindUsagesOptions(@Nonnull Project project) { + public JavaVariableFindUsagesOptions(Project project) { super(project); isSearchForTextOccurrences = false; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/generate/config/FilterPattern.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/generate/config/FilterPattern.java index 95b13eab3f..bf959db6ab 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/generate/config/FilterPattern.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/generate/config/FilterPattern.java @@ -23,7 +23,6 @@ import consulo.logging.Logger; import org.jetbrains.java.generate.psi.PsiAdapter; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -94,7 +93,7 @@ public boolean fieldMatches(PsiField field) return false; } - public boolean methodMatches(@Nonnull PsiMethod method) + public boolean methodMatches(PsiMethod method) { final String methodName = method.getName(); final Pattern methodNamePattern = getMethodNamePattern(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/AllVariablesControlFlowPolicy.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/AllVariablesControlFlowPolicy.java index 41347170c2..42415e4a63 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/AllVariablesControlFlowPolicy.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/AllVariablesControlFlowPolicy.java @@ -31,24 +31,22 @@ import com.intellij.java.language.psi.PsiVariable; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; - public class AllVariablesControlFlowPolicy implements ControlFlowPolicy { private static final AllVariablesControlFlowPolicy INSTANCE = new AllVariablesControlFlowPolicy(); @Override - public PsiVariable getUsedVariable(@Nonnull PsiReferenceExpression refExpr) { + public PsiVariable getUsedVariable(PsiReferenceExpression refExpr) { PsiElement resolved = refExpr.resolve(); return resolved instanceof PsiVariable ? (PsiVariable) resolved : null; } @Override - public boolean isParameterAccepted(@Nonnull PsiParameter psiParameter) { + public boolean isParameterAccepted(PsiParameter psiParameter) { return true; } @Override - public boolean isLocalVariableAccepted(@Nonnull PsiLocalVariable psiVariable) { + public boolean isLocalVariableAccepted(PsiLocalVariable psiVariable) { return true; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/DefUseUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/DefUseUtil.java index 7a35da7104..89e9b93259 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/DefUseUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/DefUseUtil.java @@ -28,8 +28,7 @@ import consulo.util.collection.primitive.ints.IntLists; import consulo.util.lang.ExceptionUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; /** @@ -72,7 +71,7 @@ private static class InstructionState implements Comparable { private final List myBackwardTraces; private boolean myIsVisited; - public InstructionState(@Nonnull InstructionKey instructionKey) { + public InstructionState(InstructionKey instructionKey) { myInstructionKey = instructionKey; myBackwardTraces = new ArrayList<>(2); myUsed = null; @@ -124,7 +123,7 @@ public boolean isVisited() { } @Override - public int compareTo(@Nonnull InstructionState other) { + public int compareTo(InstructionState other) { return myInstructionKey.compareTo(other.myInstructionKey); } @@ -260,12 +259,10 @@ public static List getUnusedDefs(PsiCodeBlock body, Set outUs return unusedDefs; } - @Nonnull public static PsiElement[] getDefs(PsiCodeBlock body, final PsiVariable def, PsiElement ref) { return getDefs(body, def, ref, false); } - @Nonnull public static PsiElement[] getDefs(PsiCodeBlock body, final PsiVariable def, PsiElement ref, boolean rethrow) { try { RefsDefs refsDefs = new RefsDefs(body) { @@ -323,7 +320,6 @@ public void visitVariable(PsiVariable var) { } } - @Nonnull public static PsiElement[] getRefs(PsiCodeBlock body, final PsiVariable def, PsiElement ref) { try { RefsDefs refsDefs = new RefsDefs(body) { @@ -387,7 +383,6 @@ protected RefsDefs(PsiCodeBlock body) throws AnalysisCanceledException { protected abstract boolean defs(); - @Nonnull private PsiElement[] get(final PsiVariable def, PsiElement refOrDef) { if (body == null) { return PsiElement.EMPTY_ARRAY; @@ -455,7 +450,6 @@ void traverse(int index) { } - @Nonnull private static IntList[] getBackwardTraces(final List instructions) { final IntList[] states = new IntList[instructions.size()]; for (int i = 0; i < states.length; i++) { @@ -588,7 +582,7 @@ static Map getStates(final List i private static final ControlFlowPolicy ourPolicy = new ControlFlowPolicy() { @Override - public PsiVariable getUsedVariable(@Nonnull PsiReferenceExpression refExpr) { + public PsiVariable getUsedVariable(PsiReferenceExpression refExpr) { if (refExpr.isQualified()) { return null; } @@ -602,12 +596,12 @@ public PsiVariable getUsedVariable(@Nonnull PsiReferenceExpression refExpr) { } @Override - public boolean isParameterAccepted(@Nonnull PsiParameter psiParameter) { + public boolean isParameterAccepted(PsiParameter psiParameter) { return true; } @Override - public boolean isLocalVariableAccepted(@Nonnull PsiLocalVariable psiVariable) { + public boolean isLocalVariableAccepted(PsiLocalVariable psiVariable) { return true; } }; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKey.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKey.java index 59eea272ba..4e8d70c778 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKey.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKey.java @@ -17,7 +17,6 @@ import consulo.util.collection.ArrayUtil; -import javax.annotation.Nonnull; import java.util.Arrays; /** @@ -28,13 +27,12 @@ class InstructionKey implements Comparable private final int myOffset; private final int[] myCallStack; // shared between instructions on the same stack level - private InstructionKey(int offset, @Nonnull int[] callStack) + private InstructionKey(int offset, int[] callStack) { myOffset = offset; myCallStack = callStack; } - @Nonnull static InstructionKey create(int offset) { return new InstructionKey(offset, ArrayUtil.EMPTY_INT_ARRAY); @@ -68,7 +66,6 @@ int getOffset() return myOffset; } - @Nonnull int[] getCallStack() { return myCallStack; @@ -116,7 +113,7 @@ public String toString() } @Override - public int compareTo(@Nonnull InstructionKey key) + public int compareTo(InstructionKey key) { int c = myOffset - key.myOffset; if(c != 0) diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKeySet.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKeySet.java index 3c2abe75fc..831b9f6e5d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKeySet.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/controlFlow/InstructionKeySet.java @@ -18,7 +18,6 @@ import consulo.java.analysis.impl.util.DelegateIntObjectMap; import consulo.util.collection.primitive.ints.IntMaps; -import javax.annotation.Nonnull; import java.util.Arrays; /** @@ -36,7 +35,6 @@ class InstructionKeySet { private final - @Nonnull Node myRoot; InstructionKeySet(int initialCapacity) @@ -44,12 +42,12 @@ class InstructionKeySet this.myRoot = new Node(initialCapacity); } - void add(@Nonnull InstructionKey key) + void add(InstructionKey key) { myRoot.add(key.getOffset(), key.getCallStack(), 0); } - boolean contains(@Nonnull InstructionKey key) + boolean contains(InstructionKey key) { return myRoot.contains(key.getOffset(), key.getCallStack(), 0); } @@ -71,7 +69,7 @@ private Node(int initialCapacity) super(IntMaps.newIntObjectHashMap(Math.max(initialCapacity, 2))); } - private void add(int offset, @Nonnull int[] stack, int level) + private void add(int offset, int[] stack, int level) { if(level < stack.length) { @@ -92,7 +90,7 @@ private void add(int offset, @Nonnull int[] stack, int level) } } - private boolean contains(int offset, @Nonnull int[] stack, int level) + private boolean contains(int offset, int[] stack, int level) { if(level < stack.length) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentIndex.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentIndex.java index 5745e695de..9d5954e802 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentIndex.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentIndex.java @@ -38,8 +38,7 @@ import consulo.util.collection.primitive.ints.IntLists; import consulo.virtualFileSystem.VirtualFile; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -55,13 +54,11 @@ public class JavaNullMethodArgumentIndex extends ScalarIndexExtension getName() { return INDEX_ID; } - @Nonnull @Override public DataIndexer getIndexer() { return inputData -> @@ -97,7 +94,6 @@ public DataIndexer getIndexer() { }; } - @Nonnull private static Set findCallsWithNulls(LighterAST lighterAst, int[] nullOffsets) { Set calls = new HashSet<>(); for (int offset : nullOffsets) { @@ -114,7 +110,7 @@ private static Set findCallsWithNulls(LighterAST lighterAst, int } @Nullable - private static IntList getNullParameterIndices(LighterAST lighterAst, @Nonnull LighterASTNode methodCall) { + private static IntList getNullParameterIndices(LighterAST lighterAst, LighterASTNode methodCall) { final LighterASTNode node = LightTreeUtil.firstChildOfType(lighterAst, methodCall, EXPRESSION_LIST); if (node == null) { return null; @@ -134,7 +130,7 @@ private static boolean isNullLiteral(LighterAST lighterAst, @Nullable LighterAST } @Nullable - private static String getMethodName(LighterAST lighterAst, @Nonnull LighterASTNode call, IElementType elementType) { + private static String getMethodName(LighterAST lighterAst, LighterASTNode call, IElementType elementType) { if (elementType == NEW_EXPRESSION || elementType == ANONYMOUS_CLASS) { final List refs = LightTreeUtil.getChildrenOfType(lighterAst, call, JAVA_CODE_REFERENCE); if (refs.isEmpty()) { @@ -152,18 +148,17 @@ private static String getMethodName(LighterAST lighterAst, @Nonnull LighterASTNo return null; } - @Nonnull @Override public KeyDescriptor getKeyDescriptor() { return new KeyDescriptor() { @Override - public void save(@Nonnull DataOutput out, MethodCallData value) throws IOException { + public void save(DataOutput out, MethodCallData value) throws IOException { EnumeratorStringDescriptor.INSTANCE.save(out, value.getMethodName()); DataInputOutputUtil.writeINT(out, value.getNullParameterIndex()); } @Override - public MethodCallData read(@Nonnull DataInput in) throws IOException { + public MethodCallData read(DataInput in) throws IOException { return new MethodCallData(EnumeratorStringDescriptor.INSTANCE.read(in), DataInputOutputUtil.readINT(in)); } }; @@ -174,12 +169,11 @@ public int getVersion() { return 0; } - @Nonnull @Override public FileBasedIndex.InputFilter getInputFilter() { return new DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE) { @Override - public boolean acceptInput(@Nullable Project project, @Nonnull VirtualFile file) { + public boolean acceptInput(@Nullable Project project, VirtualFile file) { return JavaStubElementTypes.JAVA_FILE.shouldBuildStubFor(file); } }; @@ -191,16 +185,14 @@ public boolean dependsOnFileContent() { } public static final class MethodCallData { - @Nonnull private final String myMethodName; private final int myNullParameterIndex; - public MethodCallData(@Nonnull String name, int index) { + public MethodCallData(String name, int index) { myMethodName = name; myNullParameterIndex = index; } - @Nonnull public String getMethodName() { return myMethodName; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentUtil.java index 6004596014..f29aaeace0 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/JavaNullMethodArgumentUtil.java @@ -29,8 +29,7 @@ import consulo.logging.Logger; import consulo.virtualFileSystem.VirtualFile; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,7 +37,7 @@ public class JavaNullMethodArgumentUtil { private static final Logger LOG = Logger.getInstance(JavaNullMethodArgumentUtil.class); - public static boolean hasNullArgument(@Nonnull PsiMethod method, final int argumentIdx) { + public static boolean hasNullArgument(PsiMethod method, final int argumentIdx) { final boolean[] result = {false}; searchNullArgument(method, argumentIdx, expression -> { @@ -48,7 +47,7 @@ public static boolean hasNullArgument(@Nonnull PsiMethod method, final int argum return result[0]; } - public static void searchNullArgument(@Nonnull PsiMethod method, final int argumentIdx, @Nonnull Processor nullArgumentProcessor) { + public static void searchNullArgument(PsiMethod method, final int argumentIdx, Processor nullArgumentProcessor) { final PsiParameter parameter = method.getParameterList().getParameters()[argumentIdx]; if (parameter.getType() instanceof PsiEllipsisType) { return; @@ -65,7 +64,7 @@ public static void searchNullArgument(@Nonnull PsiMethod method, final int argum } } - private static void processCallsWithNullArguments(@Nonnull PsiMethod method, int argumentIdx, @Nonnull Processor nullArgumentProcessor, Collection candidateFiles) { + private static void processCallsWithNullArguments(PsiMethod method, int argumentIdx, Processor nullArgumentProcessor, Collection candidateFiles) { if (candidateFiles.isEmpty()) { return; } @@ -99,8 +98,7 @@ private static PsiExpressionList getCallArgumentList(@Nullable PsiElement psi) { return null; } - @Nonnull - private static Collection getFilesWithPotentialNullPassingCalls(@Nonnull PsiMethod method, int parameterIndex) { + private static Collection getFilesWithPotentialNullPassingCalls(PsiMethod method, int parameterIndex) { final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); final CommonProcessors.CollectProcessor collector = new CommonProcessors.CollectProcessor<>(new ArrayList<>()); GlobalSearchScope searchScope = GlobalSearchScopeUtil.toGlobalSearchScope(method.getUseScope(), method.getProject()); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/ThrowSearchUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/ThrowSearchUtil.java index 4004ea5809..9732d6b879 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/ThrowSearchUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/search/ThrowSearchUtil.java @@ -19,160 +19,162 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiFormatUtil; import com.intellij.java.language.psi.util.PsiFormatUtilBase; -import consulo.application.util.function.Processor; +import consulo.annotation.access.RequiredReadAction; import consulo.find.FindUsagesOptions; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiReference; import consulo.logging.Logger; import consulo.usage.UsageInfo; import consulo.util.dataholder.Key; +import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; /** * Author: msk */ public class ThrowSearchUtil { - private static final Logger LOG = Logger.getInstance(ThrowSearchUtil.class); + private static final Logger LOG = Logger.getInstance(ThrowSearchUtil.class); - private ThrowSearchUtil() { - } + private ThrowSearchUtil() { + } - public static class Root { - final PsiElement myElement; - final PsiType myType; - final boolean isExact; + public static class Root { + PsiElement myElement; + PsiType myType; + boolean isExact; - public Root(final PsiElement root, final PsiType type, final boolean exact) { - myElement = root; - myType = type; - isExact = exact; - } + public Root(PsiElement root, PsiType type, boolean exact) { + myElement = root; + myType = type; + isExact = exact; + } - public String toString() { - return PsiFormatUtil.formatType(myType, PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES, PsiSubstitutor.EMPTY); - } - } - - public static Key THROW_SEARCH_ROOT_KEY = Key.create("ThrowSearchUtil.root"); - - /** - * @param aCatch - * @param processor - * @param root - * @return true, if we should continue processing - */ - private static boolean processExn(@Nonnull PsiParameter aCatch, @Nonnull Processor processor, @Nonnull Root root) { - final PsiType type = aCatch.getType(); - if (type.isAssignableFrom(root.myType)) { - processor.process(new UsageInfo(aCatch)); - return false; - } - if (!root.isExact && root.myType.isAssignableFrom(type)) { - processor.process(new UsageInfo(aCatch)); - return true; - } - return true; - } - - private static boolean scanCatches(@Nonnull PsiElement elem, - @Nonnull Processor processor, - @Nonnull Root root, - @Nonnull FindUsagesOptions options, - @Nonnull Set processed) { - while (elem != null) { - final PsiElement parent = elem.getParent(); - if (elem instanceof PsiMethod) { - final PsiMethod deepestSuperMethod = ((PsiMethod) elem).findDeepestSuperMethod(); - final PsiMethod method = deepestSuperMethod != null ? deepestSuperMethod : (PsiMethod) elem; - if (!processed.contains(method)) { - processed.add(method); - final PsiReference[] refs = MethodReferencesSearch.search(method, options.searchScope, true).toArray(PsiReference.EMPTY_ARRAY); - for (int i = 0; i != refs.length; ++i) { - if (!scanCatches(refs[i].getElement(), processor, root, options, processed)) return false; - } + @Override + public String toString() { + return PsiFormatUtil.formatType(myType, PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES, PsiSubstitutor.EMPTY); } - return true; - } - if (elem instanceof PsiTryStatement) { - final PsiTryStatement aTry = (PsiTryStatement) elem; - final PsiParameter[] catches = aTry.getCatchBlockParameters(); - for (int i = 0; i != catches.length; ++i) { - if (!processExn(catches[i], processor, root)) { + } + + public static Key THROW_SEARCH_ROOT_KEY = Key.create("ThrowSearchUtil.root"); + + /** + * @param aCatch + * @param processor + * @param root + * @return true, if we should continue processing + */ + private static boolean processExn(PsiParameter aCatch, Predicate processor, Root root) { + PsiType type = aCatch.getType(); + if (type.isAssignableFrom(root.myType)) { + processor.test(new UsageInfo(aCatch)); return false; - } } - } else if (parent instanceof PsiTryStatement) { - final PsiTryStatement tryStmt = (PsiTryStatement) parent; - if (elem != tryStmt.getTryBlock()) { - elem = parent.getParent(); - continue; + if (!root.isExact && root.myType.isAssignableFrom(type)) { + processor.test(new UsageInfo(aCatch)); + return true; + } + return true; + } + + @RequiredReadAction + private static boolean scanCatches( + PsiElement elem, + Predicate processor, + Root root, + FindUsagesOptions options, + Set processed + ) { + while (elem != null) { + PsiElement parent = elem.getParent(); + if (elem instanceof PsiMethod m) { + PsiMethod method = ObjectUtil.chooseNotNull(m.findDeepestSuperMethod(), m); + if (!processed.contains(method)) { + processed.add(method); + PsiReference[] refs = + MethodReferencesSearch.search(method, options.searchScope, true).toArray(PsiReference.EMPTY_ARRAY); + for (int i = 0; i != refs.length; ++i) { + if (!scanCatches(refs[i].getElement(), processor, root, options, processed)) { + return false; + } + } + } + return true; + } + if (elem instanceof PsiTryStatement tryStmt) { + PsiParameter[] catches = tryStmt.getCatchBlockParameters(); + for (int i = 0; i != catches.length; ++i) { + if (!processExn(catches[i], processor, root)) { + return false; + } + } + } + else if (parent instanceof PsiTryStatement tryStmt) { + if (elem != tryStmt.getTryBlock()) { + elem = parent.getParent(); + continue; + } + } + elem = parent; } - } - elem = parent; + return true; + } + + @RequiredReadAction + public static boolean addThrowUsages(Predicate processor, Root root, FindUsagesOptions options) { + Set processed = new HashSet<>(); + return scanCatches(root.myElement, processor, root, options, processed); } - return true; - } - - public static boolean addThrowUsages(@Nonnull Processor processor, @Nonnull Root root, @Nonnull FindUsagesOptions options) { - Set processed = new HashSet(); - return scanCatches(root.myElement, processor, root, options, processed); - } - - /** - * @param exn - * @return is type of exn exactly known - */ - - private static boolean isExactExnType(final PsiExpression exn) { - return exn instanceof PsiNewExpression; - } - - @Nullable - public static Root[] getSearchRoots(final PsiElement element) { - if (element instanceof PsiThrowStatement) { - final PsiThrowStatement aThrow = (PsiThrowStatement) element; - final PsiExpression exn = aThrow.getException(); - return new Root[]{new Root(aThrow.getParent(), exn.getType(), isExactExnType(exn))}; + + /** + * @param exn + * @return is type of exn exactly known + */ + + private static boolean isExactExnType(PsiExpression exn) { + return exn instanceof PsiNewExpression; } - if (element instanceof PsiKeyword) { - final PsiKeyword kwd = (PsiKeyword) element; - if (PsiKeyword.THROWS.equals(kwd.getText())) { - final PsiElement parent = kwd.getParent(); - if (parent != null && parent.getParent() instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) parent.getParent(); - final PsiReferenceList throwsList = method.getThrowsList(); - final PsiClassType[] exns = throwsList.getReferencedTypes(); - final Root[] roots = new Root[exns.length]; - for (int i = 0; i != roots.length; ++i) { - final PsiClassType exn = exns[i]; - roots[i] = new Root(method, exn, false); // TODO: test for final - } - return roots; + + @Nullable + public static Root[] getSearchRoots(PsiElement element) { + if (element instanceof PsiThrowStatement aThrow) { + PsiExpression exn = aThrow.getException(); + return new Root[]{new Root(aThrow.getParent(), exn.getType(), isExactExnType(exn))}; + } + if (element instanceof PsiKeyword kwd && PsiKeyword.THROWS.equals(kwd.getText())) { + PsiElement parent = kwd.getParent(); + if (parent != null && parent.getParent() instanceof PsiMethod method) { + PsiReferenceList throwsList = method.getThrowsList(); + PsiClassType[] exns = throwsList.getReferencedTypes(); + Root[] roots = new Root[exns.length]; + for (int i = 0; i != roots.length; ++i) { + PsiClassType exn = exns[i]; + roots[i] = new Root(method, exn, false); // TODO: test for final + } + return roots; + } } - } + return null; } - return null; - } - - public static boolean isSearchable(final PsiElement element) { - return getSearchRoots(element) != null; - } - - public static String getSearchableTypeName(final PsiElement e) { - if (e instanceof PsiThrowStatement) { - final PsiThrowStatement aThrow = (PsiThrowStatement) e; - final PsiType type = aThrow.getException().getType(); - return PsiFormatUtil.formatType(type, PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES, PsiSubstitutor.EMPTY); + + public static boolean isSearchable(PsiElement element) { + return getSearchRoots(element) != null; } - if (e instanceof PsiKeyword && PsiKeyword.THROWS.equals(e.getText())) { - return e.getParent().getText(); + + @RequiredReadAction + public static String getSearchableTypeName(PsiElement e) { + if (e instanceof PsiThrowStatement aThrow) { + PsiType type = aThrow.getException().getType(); + return PsiFormatUtil.formatType(type, PsiFormatUtilBase.SHOW_FQ_CLASS_NAMES, PsiSubstitutor.EMPTY); + } + if (e instanceof PsiKeyword && PsiKeyword.THROWS.equals(e.getText())) { + return e.getParent().getText(); + } + LOG.error("invalid searchable element"); + return e.getText(); } - LOG.error("invalid searchable element"); - return e.getText(); - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java index 9bc4e5c27d..b164d0d232 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java @@ -10,6 +10,7 @@ import com.siyeh.ig.psiutils.DeclarationSearchUtils; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.RecursionGuard; import consulo.application.util.RecursionManager; import consulo.component.util.Iconable; @@ -27,10 +28,9 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; @@ -38,752 +38,744 @@ * @author Pavel.Dolgov */ public final class JavaReflectionReferenceUtil { - // MethodHandle (Java 7) and VarHandle (Java 9) infrastructure - public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; - public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; - - public static final String METHOD_TYPE = "methodType"; - public static final String GENERIC_METHOD_TYPE = "genericMethodType"; - - public static final String FIND_VIRTUAL = "findVirtual"; - public static final String FIND_STATIC = "findStatic"; - public static final String FIND_SPECIAL = "findSpecial"; - - public static final String FIND_GETTER = "findGetter"; - public static final String FIND_SETTER = "findSetter"; - public static final String FIND_STATIC_GETTER = "findStaticGetter"; - public static final String FIND_STATIC_SETTER = "findStaticSetter"; - - public static final String FIND_VAR_HANDLE = "findVarHandle"; - public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; - - public static final String FIND_CONSTRUCTOR = "findConstructor"; - public static final String FIND_CLASS = "findClass"; - - public static final String[] HANDLE_FACTORY_METHOD_NAMES = { - FIND_VIRTUAL, - FIND_STATIC, - FIND_SPECIAL, - FIND_GETTER, - FIND_SETTER, - FIND_STATIC_GETTER, - FIND_STATIC_SETTER, - FIND_VAR_HANDLE, - FIND_STATIC_VAR_HANDLE - }; - - // Classic reflection infrastructure - public static final String GET_FIELD = "getField"; - public static final String GET_DECLARED_FIELD = "getDeclaredField"; - public static final String GET_METHOD = "getMethod"; - public static final String GET_DECLARED_METHOD = "getDeclaredMethod"; - public static final String GET_CONSTRUCTOR = "getConstructor"; - public static final String GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; - - public static final String JAVA_LANG_CLASS_LOADER = "java.lang.ClassLoader"; - public static final String FOR_NAME = "forName"; - public static final String LOAD_CLASS = "loadClass"; - public static final String GET_CLASS = "getClass"; - public static final String NEW_INSTANCE = "newInstance"; - public static final String TYPE = "TYPE"; - - // Atomic field updaters - public static final String NEW_UPDATER = "newUpdater"; - public static final String ATOMIC_LONG_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicLongFieldUpdater"; - public static final String ATOMIC_INTEGER_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicIntegerFieldUpdater"; - public static final String ATOMIC_REFERENCE_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicReferenceFieldUpdater"; - - private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); - - @Contract("null -> null") - public static ReflectiveType getReflectiveType(@Nullable PsiExpression context) { - context = PsiUtil.skipParenthesizedExprDown(context); - if (context == null) { - return null; - } - if (context instanceof PsiClassObjectAccessExpression) { - final PsiTypeElement operand = ((PsiClassObjectAccessExpression) context).getOperand(); - return ReflectiveType.create(operand.getType(), true); - } - - if (context instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) context; - final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); - if (FOR_NAME.equals(methodReferenceName)) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && isJavaLangClass(method.getContainingClass())) { - final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); - if (expressions.length == 1) { - final PsiExpression argument = findDefinition(PsiUtil.skipParenthesizedExprDown(expressions[0])); - final String className = computeConstantExpression(argument, String.class); - if (className != null) { - return ReflectiveType.create(findClass(className, context), true); + // MethodHandle (Java 7) and VarHandle (Java 9) infrastructure + public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; + public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; + + public static final String METHOD_TYPE = "methodType"; + public static final String GENERIC_METHOD_TYPE = "genericMethodType"; + + public static final String FIND_VIRTUAL = "findVirtual"; + public static final String FIND_STATIC = "findStatic"; + public static final String FIND_SPECIAL = "findSpecial"; + + public static final String FIND_GETTER = "findGetter"; + public static final String FIND_SETTER = "findSetter"; + public static final String FIND_STATIC_GETTER = "findStaticGetter"; + public static final String FIND_STATIC_SETTER = "findStaticSetter"; + + public static final String FIND_VAR_HANDLE = "findVarHandle"; + public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; + + public static final String FIND_CONSTRUCTOR = "findConstructor"; + public static final String FIND_CLASS = "findClass"; + + public static final String[] HANDLE_FACTORY_METHOD_NAMES = { + FIND_VIRTUAL, + FIND_STATIC, + FIND_SPECIAL, + FIND_GETTER, + FIND_SETTER, + FIND_STATIC_GETTER, + FIND_STATIC_SETTER, + FIND_VAR_HANDLE, + FIND_STATIC_VAR_HANDLE + }; + + // Classic reflection infrastructure + public static final String GET_FIELD = "getField"; + public static final String GET_DECLARED_FIELD = "getDeclaredField"; + public static final String GET_METHOD = "getMethod"; + public static final String GET_DECLARED_METHOD = "getDeclaredMethod"; + public static final String GET_CONSTRUCTOR = "getConstructor"; + public static final String GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; + + public static final String JAVA_LANG_CLASS_LOADER = "java.lang.ClassLoader"; + public static final String FOR_NAME = "forName"; + public static final String LOAD_CLASS = "loadClass"; + public static final String GET_CLASS = "getClass"; + public static final String NEW_INSTANCE = "newInstance"; + public static final String TYPE = "TYPE"; + + // Atomic field updaters + public static final String NEW_UPDATER = "newUpdater"; + public static final String ATOMIC_LONG_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicLongFieldUpdater"; + public static final String ATOMIC_INTEGER_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicIntegerFieldUpdater"; + public static final String ATOMIC_REFERENCE_FIELD_UPDATER = "java.util.concurrent.atomic.AtomicReferenceFieldUpdater"; + + private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); + + @Contract("null -> null") + @RequiredReadAction + public static ReflectiveType getReflectiveType(@Nullable PsiExpression context) { + context = PsiUtil.skipParenthesizedExprDown(context); + if (context == null) { + return null; + } + if (context instanceof PsiClassObjectAccessExpression classObjectAccess) { + PsiTypeElement operand = classObjectAccess.getOperand(); + return ReflectiveType.create(operand.getType(), true); + } + + if (context instanceof PsiMethodCallExpression methodCall) { + String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); + if (FOR_NAME.equals(methodReferenceName)) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null && isJavaLangClass(method.getContainingClass())) { + PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); + if (expressions.length == 1) { + PsiExpression argument = findDefinition(PsiUtil.skipParenthesizedExprDown(expressions[0])); + String className = computeConstantExpression(argument, String.class); + if (className != null) { + return ReflectiveType.create(findClass(className, context), true); + } + } + } } - } - } - } else if (GET_CLASS.equals(methodReferenceName) && methodCall.getArgumentList().isEmpty()) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && isJavaLangObject(method.getContainingClass())) { - final PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()); - if (qualifier instanceof PsiReferenceExpression) { - final PsiExpression definition = findVariableDefinition((PsiReferenceExpression) qualifier); - if (definition != null) { - return getClassInstanceType(definition); + else if (GET_CLASS.equals(methodReferenceName) && methodCall.getArgumentList().isEmpty()) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null && isJavaLangObject(method.getContainingClass())) { + PsiExpression qualifier = + PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()); + if (qualifier instanceof PsiReferenceExpression qRefExpr) { + PsiExpression definition = findVariableDefinition(qRefExpr); + if (definition != null) { + return getClassInstanceType(definition); + } + } + //TODO type of the qualifier may be a supertype of the actual value - need to compute the type of the actual value + // otherwise getDeclaredField and getDeclaredMethod may work not reliably + if (qualifier != null) { + return getClassInstanceType(qualifier); + } + } } - } - //TODO type of the qualifier may be a supertype of the actual value - need to compute the type of the actual value - // otherwise getDeclaredField and getDeclaredMethod may work not reliably - if (qualifier != null) { - return getClassInstanceType(qualifier); - } - } - } - } - - if (context instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) context; - final PsiElement resolved = reference.resolve(); - if (resolved instanceof PsiVariable) { - PsiVariable variable = (PsiVariable) resolved; - if (isJavaLangClass(PsiTypesUtil.getPsiClass(variable.getType()))) { - final PsiExpression definition = findVariableDefinition(reference, variable); - if (definition != null) { - ReflectiveType result = ourGuard.doPreventingRecursion(variable, false, () -> getReflectiveType(definition)); - if (result != null) { - return result; + } + + if (context instanceof PsiReferenceExpression reference + && reference.resolve() instanceof PsiVariable variable + && isJavaLangClass(PsiTypesUtil.getPsiClass(variable.getType()))) { + PsiExpression definition = findVariableDefinition(reference, variable); + if (definition != null) { + ReflectiveType result = ourGuard.doPreventingRecursion(variable, false, () -> getReflectiveType(definition)); + if (result != null) { + return result; + } } - } } - } - } - final PsiType type = context.getType(); - if (type instanceof PsiClassType) { - final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType) type).resolveGenerics(); - final PsiClass resolvedElement = resolveResult.getElement(); - if (!isJavaLangClass(resolvedElement)) { - return null; - } - - if (context instanceof PsiReferenceExpression && TYPE.equals(((PsiReferenceExpression) context).getReferenceName())) { - final PsiElement resolved = ((PsiReferenceExpression) context).resolve(); - if (resolved instanceof PsiField) { - final PsiField field = (PsiField) resolved; - if (field.hasModifierProperty(PsiModifier.FINAL) && field.hasModifierProperty(PsiModifier.STATIC)) { - final PsiType[] classTypeArguments = ((PsiClassType) type).getParameters(); - final PsiPrimitiveType unboxedType = classTypeArguments.length == 1 - ? PsiPrimitiveType.getUnboxedType(classTypeArguments[0]) : null; - if (unboxedType != null && field.getContainingClass() == PsiUtil.resolveClassInClassTypeOnly(classTypeArguments[0])) { - return ReflectiveType.create(unboxedType, true); + PsiType type = context.getType(); + if (type instanceof PsiClassType classType) { + PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + PsiClass resolvedElement = resolveResult.getElement(); + if (!isJavaLangClass(resolvedElement)) { + return null; } - } - } - } - final PsiTypeParameter[] parameters = resolvedElement.getTypeParameters(); - if (parameters.length == 1) { - final PsiType typeArgument = resolveResult.getSubstitutor().substitute(parameters[0]); - final PsiType erasure = TypeConversionUtil.erasure(typeArgument); - final PsiClass argumentClass = PsiTypesUtil.getPsiClass(erasure); - if (argumentClass != null && !isJavaLangObject(argumentClass)) { - return ReflectiveType.create(argumentClass, false); - } - } - } - return null; - } - - @Nullable - private static ReflectiveType getClassInstanceType(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression == null) { - return null; - } - if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression) expression; - final String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); - - if (NEW_INSTANCE.equals(methodReferenceName)) { - final PsiMethod method = methodCall.resolveMethod(); - if (method != null) { - final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); - if (arguments.length == 0 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_CLASS)) { - final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); - if (qualifier != null) { - return ourGuard.doPreventingRecursion(qualifier, false, () -> getReflectiveType(qualifier)); + + if (context instanceof PsiReferenceExpression refExpr + && TYPE.equals(refExpr.getReferenceName()) + && refExpr.resolve() instanceof PsiField field + && field.isFinal() && field.isStatic()) { + PsiType[] classTypeArguments = classType.getParameters(); + PsiPrimitiveType unboxedType = classTypeArguments.length == 1 + ? PsiPrimitiveType.getUnboxedType(classTypeArguments[0]) : null; + if (unboxedType != null && field.getContainingClass() == PsiUtil.resolveClassInClassTypeOnly(classTypeArguments[0])) { + return ReflectiveType.create(unboxedType, true); + } } - } else if (arguments.length > 1 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_REFLECT_ARRAY)) { - final PsiExpression typeExpression = arguments[0]; - if (typeExpression != null) { - final ReflectiveType itemType = - ourGuard.doPreventingRecursion(typeExpression, false, () -> getReflectiveType(typeExpression)); - return ReflectiveType.arrayOf(itemType); + PsiTypeParameter[] parameters = resolvedElement.getTypeParameters(); + if (parameters.length == 1) { + PsiType typeArgument = resolveResult.getSubstitutor().substitute(parameters[0]); + PsiType erasure = TypeConversionUtil.erasure(typeArgument); + PsiClass argumentClass = PsiTypesUtil.getPsiClass(erasure); + if (argumentClass != null && !isJavaLangObject(argumentClass)) { + return ReflectiveType.create(argumentClass, false); + } } - } - } - } - } - return ReflectiveType.create(expression.getType(), false); - } - - @Contract("null,_->null") - @Nullable - public static T computeConstantExpression(@Nullable PsiExpression expression, @Nonnull Class expectedType) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - final Object computed = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); - return ObjectUtil.tryCast(computed, expectedType); - } - - @Nullable - public static ReflectiveClass getReflectiveClass(PsiExpression context) { - final ReflectiveType reflectiveType = getReflectiveType(context); - return reflectiveType != null ? reflectiveType.getReflectiveClass() : null; - } - - @Nullable - public static PsiExpression findDefinition(@Nullable PsiExpression expression) { - int preventEndlessLoop = 5; - while (expression instanceof PsiReferenceExpression) { - if (--preventEndlessLoop == 0) { - return null; - } - expression = findVariableDefinition((PsiReferenceExpression) expression); - } - return expression; - } - - @Nullable - private static PsiExpression findVariableDefinition(@Nonnull PsiReferenceExpression referenceExpression) { - final PsiElement resolved = referenceExpression.resolve(); - return resolved instanceof PsiVariable ? findVariableDefinition(referenceExpression, (PsiVariable) resolved) : null; - } - - @Nullable - private static PsiExpression findVariableDefinition(@Nonnull PsiReferenceExpression referenceExpression, @Nonnull PsiVariable variable) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer != null) { - return initializer; - } - if (variable instanceof PsiField) { - return findFinalFieldDefinition(referenceExpression, (PsiField) variable); - } - } - return DeclarationSearchUtils.findDefinition(referenceExpression, variable); - } - - @Nullable - private static PsiExpression findFinalFieldDefinition(@Nonnull PsiReferenceExpression referenceExpression, @Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - final PsiClass psiClass = ObjectUtil.tryCast(field.getParent(), PsiClass.class); - if (psiClass != null) { - final boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC); - final List initializers = - ContainerUtil.filter(psiClass.getInitializers(), initializer -> initializer.hasModifierProperty(PsiModifier.STATIC) == isStatic); - for (PsiClassInitializer initializer : initializers) { - final PsiExpression assignedExpression = getAssignedExpression(initializer, field); - if (assignedExpression != null) { - return assignedExpression; - } - } - if (!isStatic) { - final PsiMethod[] constructors = psiClass.getConstructors(); - if (constructors.length == 1) { - return getAssignedExpression(constructors[0], field); - } - for (PsiMethod constructor : constructors) { - if (PsiTreeUtil.isAncestor(constructor, referenceExpression, true)) { - return getAssignedExpression(constructor, field); - } - } - } - } - return null; - } - - @Nullable - private static PsiExpression getAssignedExpression(@Nonnull PsiMember maybeContainsAssignment, @Nonnull PsiField field) { - final PsiAssignmentExpression assignment = SyntaxTraverser.psiTraverser(maybeContainsAssignment) - .filter(PsiAssignmentExpression.class) - .find(expression -> ExpressionUtils.isReferenceTo(expression.getLExpression(), field)); - return assignment != null ? assignment.getRExpression() : null; - } - - private static PsiClass findClass(@Nonnull String qualifiedName, @Nonnull PsiElement context) { - final Project project = context.getProject(); - return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); - } - - @Contract("null -> false") - public static boolean isJavaLangClass(@Nullable PsiClass aClass) { - return isClassWithName(aClass, CommonClassNames.JAVA_LANG_CLASS); - } - - @Contract("null -> false") - public static boolean isJavaLangObject(@Nullable PsiClass aClass) { - return isClassWithName(aClass, CommonClassNames.JAVA_LANG_OBJECT); - } - - @Contract("null, _ -> false") - public static boolean isClassWithName(@Nullable PsiClass aClass, @Nonnull String name) { - return aClass != null && name.equals(aClass.getQualifiedName()); - } - - @Contract("null -> false") - public static boolean isRegularMethod(@Nullable PsiMethod method) { - return method != null && !method.isConstructor(); - } - - public static boolean isPublic(@Nonnull PsiMember member) { - return member.hasModifierProperty(PsiModifier.PUBLIC); - } - - public static boolean isAtomicallyUpdateable(@Nonnull PsiField field) { - if (field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.VOLATILE)) { - return false; - } - final PsiType type = field.getType(); - return !(type instanceof PsiPrimitiveType) || PsiType.INT.equals(type) || PsiType.LONG.equals(type); - } - - @Nullable - public static String getParameterTypesText(@Nonnull PsiMethod method) { - final StringJoiner joiner = new StringJoiner(", "); - for (PsiParameter parameter : method.getParameterList().getParameters()) { - final String typeText = getTypeText(parameter.getType()); - joiner.add(typeText + ".class"); - } - return joiner.toString(); - } - - public static void shortenArgumentsClassReferences(@Nonnull InsertionContext context) { - final PsiElement parameter = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); - final PsiExpressionList parameterList = PsiTreeUtil.getParentOfType(parameter, PsiExpressionList.class); - if (parameterList != null && parameterList.getParent() instanceof PsiMethodCallExpression) { - JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(parameterList); - } - } - - @Nonnull - public static LookupElement withPriority(@Nonnull LookupElement lookupElement, boolean hasPriority) { - return hasPriority ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, -1); - } - - @Nullable - public static LookupElement withPriority(@Nullable LookupElement lookupElement, int priority) { - return priority == 0 || lookupElement == null ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, priority); - } - - public static int getMethodSortOrder(@Nonnull PsiMethod method) { - return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0; - } - - @Nullable - public static String getMemberType(@Nullable PsiElement element) { - final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); - return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null; - } - - @Nullable - public static LookupElement lookupMethod(@Nonnull PsiMethod method, @Nullable InsertHandler insertHandler) { - final ReflectiveSignature signature = getMethodSignature(method); - return signature != null - ? LookupElementBuilder.create(signature, method.getName()) - .withIcon(signature.getIcon()) - .withTailText(signature.getShortArgumentTypes()) - .withInsertHandler(insertHandler) - : null; - } - - public static void replaceText(@Nonnull InsertionContext context, @Nonnull String text) { - final PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); - final PsiElement params = newElement.getParent().getParent(); - final int end = params.getTextRange().getEndOffset() - 1; - final int start = Math.min(newElement.getTextRange().getEndOffset(), end); - - context.getDocument().replaceString(start, end, text); - context.commitDocument(); - shortenArgumentsClassReferences(context); - } - - @Nonnull - public static String getTypeText(@Nonnull PsiType type) { - final ReflectiveType reflectiveType = ReflectiveType.create(type, false); - return reflectiveType.getQualifiedName(); - } - - @Nullable - public static String getTypeText(@Nullable PsiExpression argument) { - final ReflectiveType reflectiveType = getReflectiveType(argument); - return reflectiveType != null ? reflectiveType.getQualifiedName() : null; - } - - @Contract("null -> null") - @Nullable - public static ReflectiveSignature getMethodSignature(@Nullable PsiMethod method) { - if (method != null) { - final List types = new ArrayList<>(); - final PsiType returnType = method.getReturnType(); - types.add(getTypeText(returnType != null ? returnType : PsiType.VOID)); // null return type means it's a constructor - - for (PsiParameter parameter : method.getParameterList().getParameters()) { - types.add(getTypeText(parameter.getType())); - } - final Image icon = IconDescriptorUpdaters.getIcon(method, Iconable.ICON_FLAG_VISIBILITY); - return ReflectiveSignature.create(icon, types); - } - return null; - } - - @Nonnull - public static String getMethodTypeExpressionText(@Nonnull ReflectiveSignature signature) { - final String types = signature.getText(true, type -> type + ".class"); - return JAVA_LANG_INVOKE_METHOD_TYPE + "." + METHOD_TYPE + types; - } - - public static boolean isCallToMethod(@Nonnull PsiMethodCallExpression methodCall, @Nonnull String className, @Nonnull String methodName) { - return MethodCallUtils.isCallToMethod(methodCall, className, null, methodName, (PsiType[]) null); - } - - /** - * Tries to unwrap array and find its components - * - * @param maybeArray an array to unwrap - * @return list of unwrapped array components, some or all of them could be null if unknown (but the length is known); - * returns null if nothing is known. - */ - @Nullable - public static List getVarargs(@Nullable PsiExpression maybeArray) { - if (ExpressionUtils.isNullLiteral(maybeArray)) { - return Collections.emptyList(); - } - if (isVarargAsArray(maybeArray)) { - final PsiExpression argumentsDefinition = findDefinition(maybeArray); - if (argumentsDefinition instanceof PsiArrayInitializerExpression) { - return Arrays.asList(((PsiArrayInitializerExpression) argumentsDefinition).getInitializers()); - } - if (argumentsDefinition instanceof PsiNewExpression) { - final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression) argumentsDefinition).getArrayInitializer(); - if (arrayInitializer != null) { - return Arrays.asList(arrayInitializer.getInitializers()); - } - final PsiExpression[] dimensions = ((PsiNewExpression) argumentsDefinition).getArrayDimensions(); - if (dimensions.length == 1) { // new Object[length] or new Class[length] - final Integer itemCount = computeConstantExpression(findDefinition(dimensions[0]), Integer.class); - if (itemCount != null && itemCount >= 0 && itemCount < 256) { - return Collections.nCopies(itemCount, null); - } - } - } - } - return null; - } - - @Contract("null -> false") - public static boolean isVarargAsArray(@Nullable PsiExpression maybeArray) { - final PsiType type = maybeArray != null ? maybeArray.getType() : null; - return type instanceof PsiArrayType && - type.getArrayDimensions() == 1 && - type.getDeepComponentType() instanceof PsiClassType; - } - - /** - * Take method's return type and parameter types - * from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) - */ - @Nullable - public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { - final PsiExpression typeDefinition = findDefinition(methodTypeExpression); - if (typeDefinition instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) typeDefinition; - final String referenceName = methodCallExpression.getMethodExpression().getReferenceName(); - - Function composer = null; - if (METHOD_TYPE.equals(referenceName)) { - composer = JavaReflectionReferenceUtil::composeMethodSignatureFromTypes; - } else if (GENERIC_METHOD_TYPE.equals(referenceName)) { - composer = JavaReflectionReferenceUtil::composeGenericMethodSignature; - } - - if (composer != null) { - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method != null) { - final PsiClass psiClass = method.getContainingClass(); - if (psiClass != null && JAVA_LANG_INVOKE_METHOD_TYPE.equals(psiClass.getQualifiedName())) { - final PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); - return composer.apply(arguments); - } } - } + return null; } - return null; - } - - @Nullable - private static ReflectiveSignature composeMethodSignatureFromTypes(@Nonnull PsiExpression[] returnAndParameterTypes) { - final List typeTexts = ContainerUtil.map(returnAndParameterTypes, JavaReflectionReferenceUtil::getTypeText); - return ReflectiveSignature.create(typeTexts); - } - @Nullable - public static Pair.NonNull getGenericSignature(@Nonnull PsiExpression[] genericSignatureShape) { - if (genericSignatureShape.length == 0 || genericSignatureShape.length > 2) { - return null; + @Nullable + private static ReflectiveType getClassInstanceType(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression == null) { + return null; + } + if (expression instanceof PsiMethodCallExpression methodCall) { + String methodReferenceName = methodCall.getMethodExpression().getReferenceName(); + + if (NEW_INSTANCE.equals(methodReferenceName)) { + PsiMethod method = methodCall.resolveMethod(); + if (method != null) { + PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length == 0 && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_CLASS)) { + PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression(); + if (qualifier != null) { + return ourGuard.doPreventingRecursion(qualifier, false, () -> getReflectiveType(qualifier)); + } + } + else if (arguments.length > 1 + && isClassWithName(method.getContainingClass(), CommonClassNames.JAVA_LANG_REFLECT_ARRAY)) { + PsiExpression typeExpression = arguments[0]; + if (typeExpression != null) { + ReflectiveType itemType = + ourGuard.doPreventingRecursion(typeExpression, false, () -> getReflectiveType(typeExpression)); + return ReflectiveType.arrayOf(itemType); + } + } + } + } + } + return ReflectiveType.create(expression.getType(), false); } - final Integer objectArgCount = computeConstantExpression(genericSignatureShape[0], Integer.class); - final Boolean finalArray = // there's an additional parameter which is an ellipsis or an array - genericSignatureShape.length > 1 ? computeConstantExpression(genericSignatureShape[1], Boolean.class) : false; + @Contract("null,_->null") + @Nullable + public static T computeConstantExpression(@Nullable PsiExpression expression, Class expectedType) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + Object computed = JavaConstantExpressionEvaluator.computeConstantExpression(expression, false); + return ObjectUtil.tryCast(computed, expectedType); + } - if (objectArgCount == null || objectArgCount < 0 || objectArgCount > 255) { - return null; + @Nullable + @RequiredReadAction + public static ReflectiveClass getReflectiveClass(PsiExpression context) { + ReflectiveType reflectiveType = getReflectiveType(context); + return reflectiveType != null ? reflectiveType.getReflectiveClass() : null; } - if (finalArray == null || finalArray && objectArgCount > 254) { - return null; + + @Nullable + @RequiredReadAction + public static PsiExpression findDefinition(@Nullable PsiExpression expression) { + int preventEndlessLoop = 5; + while (expression instanceof PsiReferenceExpression refExpr) { + if (--preventEndlessLoop == 0) { + return null; + } + expression = findVariableDefinition(refExpr); + } + return expression; } - return Pair.createNonNull(objectArgCount, finalArray); - } - /** - * All the types in the method signature are either unbounded type parameters or java.lang.Object (with possible vararg) - */ - @Nullable - private static ReflectiveSignature composeGenericMethodSignature(@Nonnull PsiExpression[] genericSignatureShape) { - final Pair.NonNull signature = getGenericSignature(genericSignatureShape); - if (signature == null) { - return null; + @Nullable + @RequiredReadAction + private static PsiExpression findVariableDefinition(PsiReferenceExpression referenceExpression) { + return referenceExpression.resolve() instanceof PsiVariable variable + ? findVariableDefinition(referenceExpression, variable) + : null; } - final int objectArgCount = signature.getFirst(); - final boolean finalArray = signature.getSecond(); - final List typeNames = new ArrayList<>(); - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); // return type + @Nullable + private static PsiExpression findVariableDefinition( + PsiReferenceExpression referenceExpression, + PsiVariable variable + ) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + PsiExpression initializer = variable.getInitializer(); + if (initializer != null) { + return initializer; + } + if (variable instanceof PsiField field) { + return findFinalFieldDefinition(referenceExpression, field); + } + } + return DeclarationSearchUtils.findDefinition(referenceExpression, variable); + } - for (int i = 0; i < objectArgCount; i++) { - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); + @Nullable + private static PsiExpression findFinalFieldDefinition(PsiReferenceExpression referenceExpression, PsiField field) { + if (!field.isFinal()) { + return null; + } + PsiClass psiClass = ObjectUtil.tryCast(field.getParent(), PsiClass.class); + if (psiClass != null) { + boolean isStatic = field.isStatic(); + List initializers = ContainerUtil.filter( + psiClass.getInitializers(), + initializer -> initializer.isStatic() == isStatic + ); + for (PsiClassInitializer initializer : initializers) { + PsiExpression assignedExpression = getAssignedExpression(initializer, field); + if (assignedExpression != null) { + return assignedExpression; + } + } + if (!isStatic) { + PsiMethod[] constructors = psiClass.getConstructors(); + if (constructors.length == 1) { + return getAssignedExpression(constructors[0], field); + } + for (PsiMethod constructor : constructors) { + if (PsiTreeUtil.isAncestor(constructor, referenceExpression, true)) { + return getAssignedExpression(constructor, field); + } + } + } + } + return null; } - if (finalArray) { - typeNames.add(CommonClassNames.JAVA_LANG_OBJECT + "[]"); + + @Nullable + private static PsiExpression getAssignedExpression(PsiMember maybeContainsAssignment, PsiField field) { + PsiAssignmentExpression assignment = SyntaxTraverser.psiTraverser(maybeContainsAssignment) + .filter(PsiAssignmentExpression.class) + .find(expression -> ExpressionUtils.isReferenceTo(expression.getLExpression(), field)); + return assignment != null ? assignment.getRExpression() : null; } - return ReflectiveSignature.create(typeNames); - } + private static PsiClass findClass(String qualifiedName, PsiElement context) { + Project project = context.getProject(); + return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); + } - public static final class ReflectiveType { - final PsiType myType; - final boolean myIsExact; + @Contract("null -> false") + public static boolean isJavaLangClass(@Nullable PsiClass aClass) { + return isClassWithName(aClass, CommonClassNames.JAVA_LANG_CLASS); + } - private ReflectiveType(@Nonnull PsiType erasedType, boolean isExact) { - myType = erasedType; - myIsExact = isExact; + @Contract("null -> false") + public static boolean isJavaLangObject(@Nullable PsiClass aClass) { + return isClassWithName(aClass, CommonClassNames.JAVA_LANG_OBJECT); } - @Nonnull - public String getQualifiedName() { - return myType.getCanonicalText(); + @Contract("null, _ -> false") + public static boolean isClassWithName(@Nullable PsiClass aClass, String name) { + return aClass != null && name.equals(aClass.getQualifiedName()); } - @Override - public String toString() { - return myType.getCanonicalText(); + @Contract("null -> false") + public static boolean isRegularMethod(@Nullable PsiMethod method) { + return method != null && !method.isConstructor(); } - public boolean isEqualTo(@Nullable PsiType otherType) { - return otherType != null && myType.equals(erasure(otherType)); + public static boolean isPublic(PsiMember member) { + return member.isPublic(); } - public boolean isAssignableFrom(@Nonnull PsiType type) { - return myType.isAssignableFrom(type); + public static boolean isAtomicallyUpdateable(PsiField field) { + if (field.isStatic() || !field.hasModifierProperty(PsiModifier.VOLATILE)) { + return false; + } + PsiType type = field.getType(); + return !(type instanceof PsiPrimitiveType) || PsiType.INT.equals(type) || PsiType.LONG.equals(type); } - public boolean isPrimitive() { - return myType instanceof PsiPrimitiveType; + @Nullable + public static String getParameterTypesText(PsiMethod method) { + StringJoiner joiner = new StringJoiner(", "); + for (PsiParameter parameter : method.getParameterList().getParameters()) { + String typeText = getTypeText(parameter.getType()); + joiner.add(typeText + ".class"); + } + return joiner.toString(); } - @Nonnull - public PsiType getType() { - return myType; + public static void shortenArgumentsClassReferences(InsertionContext context) { + PsiElement parameter = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); + PsiExpressionList parameterList = PsiTreeUtil.getParentOfType(parameter, PsiExpressionList.class); + if (parameterList != null && parameterList.getParent() instanceof PsiMethodCallExpression) { + JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(parameterList); + } } - public boolean isExact() { - return myIsExact; + public static LookupElement withPriority(LookupElement lookupElement, boolean hasPriority) { + return hasPriority ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, -1); } @Nullable - public ReflectiveClass getReflectiveClass() { - PsiClass psiClass = getPsiClass(); - if (psiClass != null) { - return new ReflectiveClass(psiClass, myIsExact); - } - return null; + public static LookupElement withPriority(@Nullable LookupElement lookupElement, int priority) { + return priority == 0 || lookupElement == null ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, priority); } - @Nullable - public ReflectiveType getArrayComponentType() { - if (myType instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType) myType).getComponentType(); - return new ReflectiveType(componentType, myIsExact); - } - return null; + public static int getMethodSortOrder(PsiMethod method) { + return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0; } @Nullable - public PsiClass getPsiClass() { - return PsiTypesUtil.getPsiClass(myType); + public static String getMemberType(@Nullable PsiElement element) { + PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); + return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null; } - @Contract("!null,_ -> !null; null,_ -> null") @Nullable - public static ReflectiveType create(@Nullable PsiType originalType, boolean isExact) { - if (originalType != null) { - return new ReflectiveType(erasure(originalType), isExact); - } - return null; + @RequiredReadAction + public static LookupElement lookupMethod(PsiMethod method, @Nullable InsertHandler insertHandler) { + ReflectiveSignature signature = getMethodSignature(method); + return signature != null + ? LookupElementBuilder.create(signature, method.getName()) + .withIcon(signature.getIcon()) + .withTailText(signature.getShortArgumentTypes()) + .withInsertHandler(insertHandler) + : null; } - @Contract("!null,_ -> !null; null,_ -> null") - @Nullable - public static ReflectiveType create(@Nullable PsiClass psiClass, boolean isExact) { - if (psiClass != null) { - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject()); - return new ReflectiveType(factory.createType(psiClass), isExact); - } - return null; + @RequiredReadAction + public static void replaceText(InsertionContext context, String text) { + PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); + PsiElement params = newElement.getParent().getParent(); + int end = params.getTextRange().getEndOffset() - 1; + int start = Math.min(newElement.getTextRange().getEndOffset(), end); + + context.getDocument().replaceString(start, end, text); + context.commitDocument(); + shortenArgumentsClassReferences(context); } - @Contract("!null -> !null; null -> null") - @Nullable - public static ReflectiveType arrayOf(@Nullable ReflectiveType itemType) { - if (itemType != null) { - return new ReflectiveType(itemType.myType.createArrayType(), itemType.myIsExact); - } - return null; + public static String getTypeText(PsiType type) { + ReflectiveType reflectiveType = ReflectiveType.create(type, false); + return reflectiveType.getQualifiedName(); } - @Nonnull - private static PsiType erasure(@Nonnull PsiType type) { - final PsiType erasure = TypeConversionUtil.erasure(type); - if (erasure instanceof PsiEllipsisType) { - return ((PsiEllipsisType) erasure).toArrayType(); - } - return erasure; + @Nullable + @RequiredReadAction + public static String getTypeText(@Nullable PsiExpression argument) { + ReflectiveType reflectiveType = getReflectiveType(argument); + return reflectiveType != null ? reflectiveType.getQualifiedName() : null; } - } - public static class ReflectiveClass { - final PsiClass myPsiClass; - final boolean myIsExact; + @Contract("null -> null") + @Nullable + @RequiredReadAction + public static ReflectiveSignature getMethodSignature(@Nullable PsiMethod method) { + if (method != null) { + List types = new ArrayList<>(); + PsiType returnType = method.getReturnType(); + types.add(getTypeText(returnType != null ? returnType : PsiType.VOID)); // null return type means it's a constructor - public ReflectiveClass(@Nonnull PsiClass psiClass, boolean isExact) { - myPsiClass = psiClass; - myIsExact = isExact; + for (PsiParameter parameter : method.getParameterList().getParameters()) { + types.add(getTypeText(parameter.getType())); + } + Image icon = IconDescriptorUpdaters.getIcon(method, Iconable.ICON_FLAG_VISIBILITY); + return ReflectiveSignature.create(icon, types); + } + return null; } - @Nonnull - public PsiClass getPsiClass() { - return myPsiClass; + public static String getMethodTypeExpressionText(ReflectiveSignature signature) { + String types = signature.getText(true, type -> type + ".class"); + return JAVA_LANG_INVOKE_METHOD_TYPE + "." + METHOD_TYPE + types; } - public boolean isExact() { - return myIsExact || myPsiClass.hasModifierProperty(PsiModifier.FINAL); + public static boolean isCallToMethod( + PsiMethodCallExpression methodCall, + String className, + String methodName + ) { + return MethodCallUtils.isCallToMethod(methodCall, className, null, methodName); } - } - public static final class ReflectiveSignature implements Comparable { - public static final ReflectiveSignature NO_ARGUMENT_CONSTRUCTOR_SIGNATURE = - new ReflectiveSignature(null, PsiKeyword.VOID, ArrayUtil.EMPTY_STRING_ARRAY); + /** + * Tries to unwrap array and find its components + * + * @param maybeArray an array to unwrap + * @return list of unwrapped array components, some or all of them could be null if unknown (but the length is known); + * returns null if nothing is known. + */ + @Nullable + @RequiredReadAction + public static List getVarargs(@Nullable PsiExpression maybeArray) { + if (ExpressionUtils.isNullLiteral(maybeArray)) { + return Collections.emptyList(); + } + if (isVarargAsArray(maybeArray)) { + PsiExpression argumentsDefinition = findDefinition(maybeArray); + if (argumentsDefinition instanceof PsiArrayInitializerExpression arrayInitializer) { + return Arrays.asList(arrayInitializer.getInitializers()); + } + if (argumentsDefinition instanceof PsiNewExpression newExpr) { + PsiArrayInitializerExpression arrayInitializer = newExpr.getArrayInitializer(); + if (arrayInitializer != null) { + return Arrays.asList(arrayInitializer.getInitializers()); + } + PsiExpression[] dimensions = newExpr.getArrayDimensions(); + if (dimensions.length == 1) { // new Object[length] or new Class[length] + Integer itemCount = computeConstantExpression(findDefinition(dimensions[0]), Integer.class); + if (itemCount != null && itemCount >= 0 && itemCount < 256) { + return Collections.nCopies(itemCount, null); + } + } + } + } + return null; + } - private final Image myIcon; - @Nonnull - private final String myReturnType; - @Nonnull - private final String[] myArgumentTypes; + @Contract("null -> false") + public static boolean isVarargAsArray(@Nullable PsiExpression maybeArray) { + return maybeArray != null + && maybeArray.getType() instanceof PsiArrayType arrayType + && arrayType.getArrayDimensions() == 1 + && arrayType.getDeepComponentType() instanceof PsiClassType; + } + /** + * Take method's return type and parameter types + * from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) + */ @Nullable - public static ReflectiveSignature create(@Nonnull List typeTexts) { - return create(null, typeTexts); + @RequiredReadAction + public static ReflectiveSignature composeMethodSignature(@Nullable PsiExpression methodTypeExpression) { + PsiExpression typeDefinition = findDefinition(methodTypeExpression); + if (typeDefinition instanceof PsiMethodCallExpression methodCallExpression) { + String referenceName = methodCallExpression.getMethodExpression().getReferenceName(); + + Function composer = null; + if (METHOD_TYPE.equals(referenceName)) { + composer = JavaReflectionReferenceUtil::composeMethodSignatureFromTypes; + } + else if (GENERIC_METHOD_TYPE.equals(referenceName)) { + composer = JavaReflectionReferenceUtil::composeGenericMethodSignature; + } + + if (composer != null) { + PsiMethod method = methodCallExpression.resolveMethod(); + if (method != null) { + PsiClass psiClass = method.getContainingClass(); + if (psiClass != null && JAVA_LANG_INVOKE_METHOD_TYPE.equals(psiClass.getQualifiedName())) { + PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); + return composer.apply(arguments); + } + } + } + } + return null; } @Nullable - public static ReflectiveSignature create(@Nullable Image icon, @Nonnull List typeTexts) { - if (!typeTexts.isEmpty() && !typeTexts.contains(null)) { - final String[] argumentTypes = ArrayUtil.toStringArray(typeTexts.subList(1, typeTexts.size())); - return new ReflectiveSignature(icon, typeTexts.get(0), argumentTypes); - } - return null; + private static ReflectiveSignature composeMethodSignatureFromTypes(PsiExpression[] returnAndParameterTypes) { + List typeTexts = ContainerUtil.map(returnAndParameterTypes, JavaReflectionReferenceUtil::getTypeText); + return ReflectiveSignature.create(typeTexts); } - private ReflectiveSignature(@Nullable Image icon, @Nonnull String returnType, @Nonnull String[] argumentTypes) { - myIcon = icon; - myReturnType = returnType; - myArgumentTypes = argumentTypes; - } + public static Pair.@Nullable NonNull getGenericSignature(PsiExpression[] genericSignatureShape) { + if (genericSignatureShape.length == 0 || genericSignatureShape.length > 2) { + return null; + } - public String getText(boolean withReturnType, @Nonnull Function transformation) { - return getText(withReturnType, true, transformation); - } + Integer objectArgCount = computeConstantExpression(genericSignatureShape[0], Integer.class); + Boolean finalArray = // there's an additional parameter which is an ellipsis or an array + genericSignatureShape.length > 1 ? computeConstantExpression(genericSignatureShape[1], Boolean.class) : false; - public String getText(boolean withReturnType, boolean withParentheses, @Nonnull Function transformation) { - final StringJoiner joiner = new StringJoiner(", ", withParentheses ? "(" : "", withParentheses ? ")" : ""); - if (withReturnType) { - joiner.add(transformation.apply(myReturnType)); - } - for (String argumentType : myArgumentTypes) { - joiner.add(transformation.apply(argumentType)); - } - return joiner.toString(); + if (objectArgCount == null || objectArgCount < 0 || objectArgCount > 255) { + return null; + } + if (finalArray == null || finalArray && objectArgCount > 254) { + return null; + } + return Pair.createNonNull(objectArgCount, finalArray); } - @Nonnull - public String getShortReturnType() { - return PsiNameHelper.getShortClassName(myReturnType); - } + /** + * All the types in the method signature are either unbounded type parameters or java.lang.Object (with possible vararg) + */ + @Nullable + private static ReflectiveSignature composeGenericMethodSignature(PsiExpression[] genericSignatureShape) { + Pair.NonNull signature = getGenericSignature(genericSignatureShape); + if (signature == null) { + return null; + } + int objectArgCount = signature.getFirst(); + boolean finalArray = signature.getSecond(); - @Nonnull - public String getShortArgumentTypes() { - return getText(false, PsiNameHelper::getShortClassName); - } + List typeNames = new ArrayList<>(); + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); // return type - @Nonnull - public Image getIcon() { - return myIcon != null ? myIcon : PlatformIconGroup.nodesMethod(); + for (int i = 0; i < objectArgCount; i++) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); + } + if (finalArray) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT + "[]"); + } + return ReflectiveSignature.create(typeNames); } - @Override - public int compareTo(@Nonnull ReflectiveSignature other) { - int c = myArgumentTypes.length - other.myArgumentTypes.length; - if (c != 0) { - return c; - } - c = ArrayUtil.lexicographicCompare(myArgumentTypes, other.myArgumentTypes); - if (c != 0) { - return c; - } - return myReturnType.compareTo(other.myReturnType); - } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ReflectiveSignature)) { - return false; - } - final ReflectiveSignature other = (ReflectiveSignature) o; - return Objects.equals(myReturnType, other.myReturnType) && - Arrays.equals(myArgumentTypes, other.myArgumentTypes); + public static final class ReflectiveType { + final PsiType myType; + final boolean myIsExact; + + private ReflectiveType(PsiType erasedType, boolean isExact) { + myType = erasedType; + myIsExact = isExact; + } + + public String getQualifiedName() { + return myType.getCanonicalText(); + } + + @Override + public String toString() { + return myType.getCanonicalText(); + } + + public boolean isEqualTo(@Nullable PsiType otherType) { + return otherType != null && myType.equals(erasure(otherType)); + } + + public boolean isAssignableFrom(PsiType type) { + return myType.isAssignableFrom(type); + } + + public boolean isPrimitive() { + return myType instanceof PsiPrimitiveType; + } + + public PsiType getType() { + return myType; + } + + public boolean isExact() { + return myIsExact; + } + + @Nullable + public ReflectiveClass getReflectiveClass() { + PsiClass psiClass = getPsiClass(); + if (psiClass != null) { + return new ReflectiveClass(psiClass, myIsExact); + } + return null; + } + + @Nullable + public ReflectiveType getArrayComponentType() { + return myType instanceof PsiArrayType arrayType ? new ReflectiveType(arrayType.getComponentType(), myIsExact) : null; + } + + @Nullable + public PsiClass getPsiClass() { + return PsiTypesUtil.getPsiClass(myType); + } + + @Contract("!null,_ -> !null; null,_ -> null") + @Nullable + public static ReflectiveType create(@Nullable PsiType originalType, boolean isExact) { + if (originalType != null) { + return new ReflectiveType(erasure(originalType), isExact); + } + return null; + } + + @Contract("!null,_ -> !null; null,_ -> null") + @Nullable + public static ReflectiveType create(@Nullable PsiClass psiClass, boolean isExact) { + if (psiClass != null) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject()); + return new ReflectiveType(factory.createType(psiClass), isExact); + } + return null; + } + + @Contract("!null -> !null; null -> null") + @Nullable + public static ReflectiveType arrayOf(@Nullable ReflectiveType itemType) { + if (itemType != null) { + return new ReflectiveType(itemType.myType.createArrayType(), itemType.myIsExact); + } + return null; + } + + private static PsiType erasure(PsiType type) { + PsiType erasure = TypeConversionUtil.erasure(type); + if (erasure instanceof PsiEllipsisType ellipsisType) { + return ellipsisType.toArrayType(); + } + return erasure; + } } - @Override - public int hashCode() { - return Objects.hash(myReturnType, myArgumentTypes); + public static class ReflectiveClass { + final PsiClass myPsiClass; + final boolean myIsExact; + + public ReflectiveClass(PsiClass psiClass, boolean isExact) { + myPsiClass = psiClass; + myIsExact = isExact; + } + + public PsiClass getPsiClass() { + return myPsiClass; + } + + public boolean isExact() { + return myIsExact || myPsiClass.isFinal(); + } } - @Override - public String toString() { - return myReturnType + " " + Arrays.toString(myArgumentTypes); + public static final class ReflectiveSignature implements Comparable { + public static final ReflectiveSignature NO_ARGUMENT_CONSTRUCTOR_SIGNATURE = + new ReflectiveSignature(null, PsiKeyword.VOID, ArrayUtil.EMPTY_STRING_ARRAY); + + private final Image myIcon; + private final String myReturnType; + private final String[] myArgumentTypes; + + @Nullable + public static ReflectiveSignature create(List typeTexts) { + return create(null, typeTexts); + } + + @Nullable + public static ReflectiveSignature create(@Nullable Image icon, List typeTexts) { + if (!typeTexts.isEmpty() && !typeTexts.contains(null)) { + String[] argumentTypes = ArrayUtil.toStringArray(typeTexts.subList(1, typeTexts.size())); + return new ReflectiveSignature(icon, typeTexts.get(0), argumentTypes); + } + return null; + } + + private ReflectiveSignature(@Nullable Image icon, String returnType, String[] argumentTypes) { + myIcon = icon; + myReturnType = returnType; + myArgumentTypes = argumentTypes; + } + + public String getText(boolean withReturnType, Function transformation) { + return getText(withReturnType, true, transformation); + } + + public String getText(boolean withReturnType, boolean withParentheses, Function transformation) { + StringJoiner joiner = new StringJoiner(", ", withParentheses ? "(" : "", withParentheses ? ")" : ""); + if (withReturnType) { + joiner.add(transformation.apply(myReturnType)); + } + for (String argumentType : myArgumentTypes) { + joiner.add(transformation.apply(argumentType)); + } + return joiner.toString(); + } + + public String getShortReturnType() { + return PsiNameHelper.getShortClassName(myReturnType); + } + + public String getShortArgumentTypes() { + return getText(false, PsiNameHelper::getShortClassName); + } + + public Image getIcon() { + return myIcon != null ? myIcon : PlatformIconGroup.nodesMethod(); + } + + @Override + public int compareTo(ReflectiveSignature other) { + int c = myArgumentTypes.length - other.myArgumentTypes.length; + if (c != 0) { + return c; + } + c = ArrayUtil.lexicographicCompare(myArgumentTypes, other.myArgumentTypes); + if (c != 0) { + return c; + } + return myReturnType.compareTo(other.myReturnType); + } + + @Override + public boolean equals(Object o) { + return o == this + || o instanceof ReflectiveSignature that + && Objects.equals(myReturnType, that.myReturnType) + && Arrays.equals(myArgumentTypes, that.myArgumentTypes); + } + + @Override + public int hashCode() { + return Objects.hash(myReturnType, myArgumentTypes); + } + + @Override + public String toString() { + return myReturnType + " " + Arrays.toString(myArgumentTypes); + } } - } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/JavaMatchers.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/JavaMatchers.java index 6c4166feac..b251f8b209 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/JavaMatchers.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/JavaMatchers.java @@ -22,24 +22,19 @@ import consulo.language.psi.util.PsiMatcherExpression; public class JavaMatchers { - public static PsiMatcherExpression isConstructor(final boolean shouldBe) { - return new PsiMatcherExpression() { - @Override - public Boolean match(PsiElement element) { - return element instanceof PsiMethod && ((PsiMethod) element).isConstructor() == shouldBe; - } - }; - } + public static PsiMatcherExpression isConstructor(boolean shouldBe) { + return element -> element instanceof PsiMethod method && method.isConstructor() == shouldBe; + } - public static PsiMatcherExpression hasModifier(@PsiModifier.ModifierConstant final String modifier, final boolean shouldHave) { - return new PsiMatcherExpression() { - @Override - public Boolean match(PsiElement element) { - PsiModifierListOwner owner = element instanceof PsiModifierListOwner ? (PsiModifierListOwner) element : null; + public static PsiMatcherExpression hasModifier(@PsiModifier.ModifierConstant String modifier) { + return element -> element instanceof PsiModifierListOwner owner && owner.hasModifierProperty(modifier); + } - if (owner != null && owner.hasModifierProperty(modifier) == shouldHave) return Boolean.TRUE; - return Boolean.FALSE; - } - }; - } + public static PsiMatcherExpression hasNoModifier(@PsiModifier.ModifierConstant String modifier) { + return element -> element instanceof PsiModifierListOwner owner && !owner.hasModifierProperty(modifier); + } + + public static PsiMatcherExpression hasModifier(@PsiModifier.ModifierConstant String modifier, boolean shouldHave) { + return element -> element instanceof PsiModifierListOwner owner && owner.hasModifierProperty(modifier) == shouldHave; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/PsiMatchers.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/PsiMatchers.java index 74b7c2eee4..d3fe56a793 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/PsiMatchers.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/psi/util/PsiMatchers.java @@ -26,9 +26,7 @@ import consulo.language.psi.PsiNamedElement; import consulo.language.psi.util.PsiMatcherExpression; import consulo.util.collection.ArrayUtil; -import consulo.xml.psi.xml.XmlTag; - -import javax.annotation.Nonnull; +import consulo.xml.language.psi.XmlTag; public class PsiMatchers { @@ -57,7 +55,7 @@ public Boolean match(PsiElement element) { }; } - public static PsiMatcherExpression hasText(@Nonnull final String... texts) { + public static PsiMatcherExpression hasText(final String... texts) { return new PsiMatcherExpression() { @Override public Boolean match(PsiElement element) { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ExtractMethodUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ExtractMethodUtil.java index a8742f2ea2..d4f898e613 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ExtractMethodUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ExtractMethodUtil.java @@ -29,7 +29,6 @@ import consulo.logging.Logger; import consulo.util.dataholder.Key; -import javax.annotation.Nonnull; import java.util.HashMap; import java.util.Map; @@ -110,7 +109,7 @@ public void visitMethodCallExpression(PsiMethodCallExpression expression) { } } - public static void addCastsToEnsureResolveTarget(@Nonnull final PsiMethod oldTarget, @Nonnull final PsiMethodCallExpression call) throws IncorrectOperationException { + public static void addCastsToEnsureResolveTarget(final PsiMethod oldTarget, final PsiMethodCallExpression call) throws IncorrectOperationException { final PsiMethod newTarget = call.resolveMethod(); final PsiManager manager = oldTarget.getManager(); final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/InputVariables.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/InputVariables.java index d203dd3f45..7f6ce78078 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/InputVariables.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/InputVariables.java @@ -36,8 +36,8 @@ import consulo.project.Project; import consulo.util.collection.ArrayUtil; import consulo.util.lang.Pair; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; public class InputVariables { diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ParametersFolder.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ParametersFolder.java index ee5a606c72..c7849ededf 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ParametersFolder.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/extractMethod/ParametersFolder.java @@ -36,8 +36,7 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.util.lang.Pair; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; public class ParametersFolder { @@ -56,7 +55,7 @@ public void clear() { myDeleted.clear(); } - public boolean isParameterSafeToDelete(@Nonnull VariableData data, @Nonnull LocalSearchScope scope) { + public boolean isParameterSafeToDelete(VariableData data, LocalSearchScope scope) { Next: for (PsiReference reference : ReferencesSearch.search(data.variable, scope)) { PsiElement expression = reference.getElement(); @@ -84,7 +83,7 @@ public boolean isParameterSafeToDelete(@Nonnull VariableData data, @Nonnull Loca return false; } - public void foldParameterUsagesInBody(@Nonnull VariableData data, PsiElement[] elements, SearchScope scope) { + public void foldParameterUsagesInBody(VariableData data, PsiElement[] elements, SearchScope scope) { if (myDeleted.contains(data.variable)) return; final PsiExpression psiExpression = myExpressions.get(data.variable); if (psiExpression == null) return; @@ -110,9 +109,9 @@ public void foldParameterUsagesInBody(@Nonnull VariableData data, PsiElement[] e } } - public boolean isParameterFoldable(@Nonnull VariableData data, - @Nonnull LocalSearchScope scope, - @Nonnull final List inputVariables) { + public boolean isParameterFoldable(VariableData data, + LocalSearchScope scope, + final List inputVariables) { final List mentionedInExpressions = getMentionedExpressions(data.variable, scope, inputVariables); if (mentionedInExpressions == null) return false; @@ -276,12 +275,11 @@ public void visitReferenceExpression(PsiReferenceExpression expression) { return localVarsUsed[0]; } - @Nonnull - public String getGeneratedCallArgument(@Nonnull VariableData data) { + public String getGeneratedCallArgument(VariableData data) { return myExpressions.containsKey(data.variable) ? myExpressions.get(data.variable).getText() : data.variable.getName(); } - public boolean annotateWithParameter(@Nonnull VariableData data, @Nonnull PsiElement element) { + public boolean annotateWithParameter(VariableData data, PsiElement element) { final PsiExpression psiExpression = myExpressions.get(data.variable); if (psiExpression != null) { final PsiExpression expression = findEquivalent(psiExpression, element); diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/util/LambdaRefactoringUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/LambdaRefactoringUtil.java similarity index 55% rename from plugin/src/main/java/com/intellij/java/impl/refactoring/util/LambdaRefactoringUtil.java rename to java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/LambdaRefactoringUtil.java index f1b6a77145..61f58cdcc3 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/util/LambdaRefactoringUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/LambdaRefactoringUtil.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.java.impl.refactoring.util; +package com.intellij.java.analysis.impl.refactoring.util; import com.intellij.java.analysis.impl.codeInspection.RedundantLambdaCodeBlockInspection; -import com.intellij.java.impl.refactoring.introduceField.ElementToWorkOn; -import com.intellij.java.impl.refactoring.introduceVariable.IntroduceVariableHandler; +import com.intellij.java.analysis.impl.codeInspection.SideEffectChecker; import com.intellij.java.language.impl.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; @@ -27,22 +26,16 @@ import com.intellij.java.language.psi.util.MethodSignature; import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.PsiUtil; -import com.siyeh.ig.psiutils.SideEffectChecker; -import consulo.application.ApplicationManager; -import consulo.codeEditor.Editor; +import consulo.annotation.access.RequiredReadAction; import consulo.component.util.text.UniqueNameGenerator; import consulo.language.editor.refactoring.rename.SuggestedNameInfo; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; import consulo.logging.Logger; -import consulo.ui.ex.awt.Messages; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.function.Function; @@ -51,12 +44,12 @@ public class LambdaRefactoringUtil { @Nullable public static PsiExpression convertToMethodCallInLambdaBody(PsiMethodReferenceExpression element) { - final PsiLambdaExpression lambdaExpression = convertMethodReferenceToLambda(element, false, true); + PsiLambdaExpression lambdaExpression = convertMethodReferenceToLambda(element, false, true); return lambdaExpression != null ? LambdaUtil.extractSingleExpressionFromBody(lambdaExpression.getBody()) : null; } @Nullable - public static PsiLambdaExpression convertMethodReferenceToLambda(final PsiMethodReferenceExpression referenceExpression, final boolean ignoreCast, final boolean simplifyToExpressionLambda) { + public static PsiLambdaExpression convertMethodReferenceToLambda(PsiMethodReferenceExpression referenceExpression, boolean ignoreCast, boolean simplifyToExpressionLambda) { PsiLambdaExpression lambdaExpression = createLambda(referenceExpression, ignoreCast); if (lambdaExpression == null) { return null; @@ -87,9 +80,9 @@ public static PsiLambdaExpression createLambda(PsiMethodReferenceExpression refe return null; } - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); PsiLambdaExpression lambdaExpression = (PsiLambdaExpression) elementFactory.createExpressionFromText(lambda, referenceExpression); - final PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType(); + PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType(); boolean needToSpecifyFormalTypes = !doNotAddParameterTypes && !isInferredSameTypeAfterConversion(lambdaExpression, referenceExpression, functionalInterfaceType); if (needToSpecifyFormalTypes) { PsiParameterList typedParamList = specifyLambdaParameterTypes(functionalInterfaceType, lambdaExpression); @@ -100,53 +93,57 @@ public static PsiLambdaExpression createLambda(PsiMethodReferenceExpression refe return lambdaExpression; } + @RequiredReadAction private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpression referenceExpression) { PsiType functionalInterfaceType = referenceExpression.getFunctionalInterfaceType(); - final PsiElement resolve = referenceExpression.resolve(); + PsiElement resolve = referenceExpression.resolve(); if (resolve == null) { return null; } - final PsiClassType.ClassResolveResult functionalInterfaceResolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); + PsiClassType.ClassResolveResult functionalInterfaceResolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); if (interfaceMethod == null) { return null; } - final PsiSubstitutor psiSubstitutor = LambdaUtil.getSubstitutor(interfaceMethod, functionalInterfaceResolveResult); - final MethodSignature signature = interfaceMethod.getSignature(psiSubstitutor); - final boolean isReceiver; - if (resolve instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) resolve; - isReceiver = PsiMethodReferenceUtil.isResolvedBySecondSearch(referenceExpression, signature, method.isVarArgs(), method.hasModifierProperty(PsiModifier.STATIC), method.getParameterList() - .getParametersCount()); - } else { - isReceiver = false; - } - final PsiParameter[] psiParameters = resolve instanceof PsiMethod ? ((PsiMethod) resolve).getParameterList().getParameters() : null; - - final PsiParameterList parameterList = interfaceMethod.getParameterList(); - final PsiParameter[] parameters = parameterList.getParameters(); - - final Map map = new HashMap<>(); - final UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(referenceExpression.getProject()); + PsiSubstitutor psiSubstitutor = LambdaUtil.getSubstitutor(interfaceMethod, functionalInterfaceResolveResult); + MethodSignature signature = interfaceMethod.getSignature(psiSubstitutor); + boolean isReceiver = resolve instanceof PsiMethod method && PsiMethodReferenceUtil.isResolvedBySecondSearch( + referenceExpression, + signature, + method.isVarArgs(), + method.hasModifierProperty(PsiModifier.STATIC), + method.getParameterList().getParametersCount() + ); + PsiParameter[] psiParameters = resolve instanceof PsiMethod method ? method.getParameterList().getParameters() : null; + + PsiParameterList parameterList = interfaceMethod.getParameterList(); + PsiParameter[] parameters = parameterList.getParameters(); + + Map map = new HashMap<>(); + UniqueNameGenerator nameGenerator = new UniqueNameGenerator(); + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(referenceExpression.getProject()); Function paramPresentationFunction = parameter -> { - final int parameterIndex = parameterList.getParameterIndex(parameter); + int parameterIndex = parameterList.getParameterIndex(parameter); String baseName; if (isReceiver && parameterIndex == 0) { - final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); + SuggestedNameInfo nameInfo = + codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); baseName = nameInfo.names.length > 0 ? nameInfo.names[0] : parameter.getName(); } else { String initialName; if (psiParameters != null) { - final int idx = parameterIndex - (isReceiver ? 1 : 0); - initialName = psiParameters.length > 0 ? psiParameters[idx < psiParameters.length ? idx : psiParameters.length - 1].getName() : parameter.getName(); + int idx = parameterIndex - (isReceiver ? 1 : 0); + initialName = psiParameters.length > 0 + ? psiParameters[idx < psiParameters.length ? idx : psiParameters.length - 1].getName() + : parameter.getName(); } else { initialName = parameter.getName(); } LOG.assertTrue(initialName != null); if ("_".equals(initialName)) { - SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); + SuggestedNameInfo nameInfo = + codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, psiSubstitutor.substitute(parameter.getType())); if (nameInfo.names.length > 0) { initialName = nameInfo.names[0]; } @@ -155,7 +152,8 @@ private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpr } if (baseName != null) { - String parameterName = nameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(baseName, referenceExpression, true)); + String parameterName = + nameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(baseName, referenceExpression, true)); map.put(parameter, parameterName); return parameterName; } @@ -169,30 +167,30 @@ private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpr } buf.append(" -> "); - - final JavaResolveResult resolveResult = referenceExpression.advancedResolve(false); - final PsiElement resolveElement = resolveResult.getElement(); + JavaResolveResult resolveResult = referenceExpression.advancedResolve(false); + PsiElement resolveElement = resolveResult.getElement(); if (resolveElement instanceof PsiMember) { - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(referenceExpression.getProject()); buf.append("{"); if (!PsiType.VOID.equals(interfaceMethod.getReturnType())) { buf.append("return "); } - final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(referenceExpression); - final PsiElement qualifier = referenceExpression.getQualifier(); + PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = + PsiMethodReferenceUtil.getQualifierResolveResult(referenceExpression); + PsiElement qualifier = referenceExpression.getQualifier(); PsiClass containingClass = qualifierResolveResult.getContainingClass(); - final boolean onArrayRef = elementFactory.getArrayClass(PsiUtil.getLanguageLevel(referenceExpression)) == containingClass; + boolean onArrayRef = elementFactory.getArrayClass(PsiUtil.getLanguageLevel(referenceExpression)) == containingClass; - final PsiElement referenceNameElement = referenceExpression.getReferenceNameElement(); + PsiElement referenceNameElement = referenceExpression.getReferenceNameElement(); if (isReceiver) { buf.append(map.get(parameters[0])).append("."); } else { if (!(referenceNameElement instanceof PsiKeyword)) { - if (qualifier instanceof PsiTypeElement) { - final PsiJavaCodeReferenceElement referenceElement = ((PsiTypeElement) qualifier).getInnermostComponentReferenceElement(); + if (qualifier instanceof PsiTypeElement typeElement) { + PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement(); LOG.assertTrue(referenceElement != null); if (!PsiTreeUtil.isAncestor(containingClass, referenceExpression, false)) { buf.append(referenceElement.getReferenceName()).append("."); @@ -210,13 +208,11 @@ private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpr //class name buf.append(" "); if (onArrayRef) { - if (qualifier instanceof PsiTypeElement) { - final PsiType type = ((PsiTypeElement) qualifier).getType(); + if (qualifier instanceof PsiTypeElement typeElement) { + PsiType type = typeElement.getType(); int dim = type.getArrayDimensions(); buf.append(type.getDeepComponentType().getCanonicalText()); - buf.append("["); - buf.append(map.get(parameters[0])); - buf.append("]"); + buf.append("[").append(map.get(parameters[0])).append("]"); while (--dim > 0) { buf.append("[]"); } @@ -224,16 +220,20 @@ private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpr } else { buf.append(((PsiMember) resolveElement).getName()); - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); LOG.assertTrue(containingClass != null); if (containingClass.hasTypeParameters() && !PsiUtil.isRawSubstitutor(containingClass, substitutor)) { - buf.append("<").append(StringUtil.join(containingClass.getTypeParameters(), parameter -> - { - final PsiType psiType = substitutor.substitute(parameter); - LOG.assertTrue(psiType != null); - return psiType.getCanonicalText(); - }, ", ")).append(">"); + buf.append("<").append(StringUtil.join( + containingClass.getTypeParameters(), + parameter -> + { + PsiType psiType = substitutor.substitute(parameter); + LOG.assertTrue(psiType != null); + return psiType.getCanonicalText(); + }, + ", " + )).append(">"); } } } @@ -259,41 +259,45 @@ private static String createLambdaWithoutFormalParameters(PsiMethodReferenceExpr return buf.toString(); } + @RequiredReadAction private static boolean isQualifierUnnecessary(PsiElement qualifier, PsiClass containingClass) { - if (qualifier instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) qualifier; - if (reference.resolve() instanceof PsiClass && reference.getQualifier() == null && PsiTreeUtil.isContextAncestor(containingClass, qualifier, false)) { + if (qualifier instanceof PsiReferenceExpression reference) { + if (reference.resolve() instanceof PsiClass && reference.getQualifier() == null + && PsiTreeUtil.isContextAncestor(containingClass, qualifier, false)) { return true; } } - if (qualifier instanceof PsiThisExpression && ((PsiThisExpression) qualifier).getQualifier() == null) { - return true; - } - return false; + return qualifier instanceof PsiThisExpression thisExpression && thisExpression.getQualifier() == null; } - private static boolean isInferredSameTypeAfterConversion(PsiLambdaExpression lambdaExpression, PsiMethodReferenceExpression methodReferenceExpression, PsiType functionalInterfaceType) { + private static boolean isInferredSameTypeAfterConversion( + PsiLambdaExpression lambdaExpression, + PsiMethodReferenceExpression methodReferenceExpression, + PsiType functionalInterfaceType + ) { PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodReferenceExpression.getParent()); if (!(parent instanceof PsiExpressionList)) { return true; } PsiElement gParent = parent.getParent(); - if (gParent instanceof PsiCall) { - if (gParent instanceof PsiCallExpression && ((PsiCallExpression) gParent).getTypeArguments().length > 0) { + if (gParent instanceof PsiCall call) { + if (gParent instanceof PsiCallExpression callExpression && callExpression.getTypeArguments().length > 0) { return true; } - JavaResolveResult result = ((PsiCall) gParent).resolveMethodGenerics(); - if (result instanceof MethodCandidateInfo) { - PsiMethod method = ((MethodCandidateInfo) result).getElement(); + JavaResolveResult result = call.resolveMethodGenerics(); + if (result instanceof MethodCandidateInfo methodCandidateInfo) { + PsiMethod method = methodCandidateInfo.getElement(); if (!method.hasTypeParameters()) { return true; } PsiExpression[] args = ((PsiExpressionList) parent).getExpressions(); int lambdaIdx = LambdaUtil.getLambdaIdx((PsiExpressionList) parent, methodReferenceExpression); args[lambdaIdx] = lambdaExpression; - final PsiParameter[] methodParams = method.getParameterList().getParameters(); - final PsiSubstitutor substitutor = ((MethodCandidateInfo) result).inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, args, true); - PsiType formalTargetType = substitutor.substitute(PsiTypesUtil.getParameterType(methodParams, lambdaIdx, ((MethodCandidateInfo) result).isVarargs())); + PsiParameter[] methodParams = method.getParameterList().getParameters(); + PsiSubstitutor substitutor = + methodCandidateInfo.inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, args, true); + PsiType formalTargetType = + substitutor.substitute(PsiTypesUtil.getParameterType(methodParams, lambdaIdx, methodCandidateInfo.isVarargs())); return functionalInterfaceType.equals(FunctionalInterfaceParameterizationUtil.getGroundTargetType(formalTargetType)); } } @@ -302,17 +306,17 @@ private static boolean isInferredSameTypeAfterConversion(PsiLambdaExpression lam @Nullable public static String createLambdaParameterListWithFormalTypes(PsiType functionalInterfaceType, PsiLambdaExpression lambdaExpression, boolean checkApplicability) { - final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); - final StringBuilder buf = new StringBuilder(); + PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); + StringBuilder buf = new StringBuilder(); buf.append("("); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); + PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType); LOG.assertTrue(interfaceMethod != null); - final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); - final PsiParameter[] lambdaParameters = lambdaExpression.getParameterList().getParameters(); + PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters(); + PsiParameter[] lambdaParameters = lambdaExpression.getParameterList().getParameters(); if (parameters.length != lambdaParameters.length) { return null; } - final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult); + PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult); for (int i = 0; i < parameters.length; i++) { PsiType psiType = substitutor.substitute(parameters[i].getType()); if (psiType == null) { @@ -347,43 +351,14 @@ public static PsiParameterList specifyLambdaParameterTypes(PsiType functionalInt return null; } - public static void simplifyToExpressionLambda(@Nonnull final PsiLambdaExpression lambdaExpression) { - final PsiElement body = lambdaExpression.getBody(); - final PsiExpression singleExpression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(body); + public static void simplifyToExpressionLambda(PsiLambdaExpression lambdaExpression) { + PsiElement body = lambdaExpression.getBody(); + PsiExpression singleExpression = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(body); if (singleExpression != null) { body.replace(singleExpression); } } - /** - * Works for expression lambdas/one statement code block lambdas to ensures equivalent method ref -> lambda transformation. - */ - public static void removeSideEffectsFromLambdaBody(Editor editor, PsiLambdaExpression lambdaExpression) { - if (lambdaExpression != null && lambdaExpression.isValid()) { - final PsiElement body = lambdaExpression.getBody(); - PsiExpression methodCall = LambdaUtil.extractSingleExpressionFromBody(body); - PsiExpression qualifierExpression = null; - if (methodCall instanceof PsiMethodCallExpression) { - qualifierExpression = ((PsiMethodCallExpression) methodCall).getMethodExpression().getQualifierExpression(); - } else if (methodCall instanceof PsiNewExpression) { - qualifierExpression = ((PsiNewExpression) methodCall).getQualifier(); - } - - if (qualifierExpression != null) { - final List sideEffects = new ArrayList<>(); - SideEffectChecker.checkSideEffects(qualifierExpression, sideEffects); - if (!sideEffects.isEmpty()) { - if (ApplicationManager.getApplication().isUnitTestMode() || Messages.showYesNoDialog(lambdaExpression.getProject(), "There are possible side effects found in method reference " + - "qualifier." + "\nIntroduce local variable?", "Side Effects Detected", Messages.getQuestionIcon()) == Messages.YES) { - //ensure introduced before lambda - qualifierExpression.putUserData(ElementToWorkOn.PARENT, lambdaExpression); - new IntroduceVariableHandler().invoke(qualifierExpression.getProject(), editor, qualifierExpression); - } - } - } - } - } - /** * Checks whether method reference can be converted to lambda without significant semantics change * (i.e. method reference qualifier has no side effects) @@ -391,14 +366,16 @@ public static void removeSideEffectsFromLambdaBody(Editor editor, PsiLambdaExpre * @param methodReferenceExpression method reference to check * @return true if method reference can be converted to lambda */ + @RequiredReadAction public static boolean canConvertToLambdaWithoutSideEffects(PsiMethodReferenceExpression methodReferenceExpression) { - final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression(); + PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression(); if (qualifierExpression == null) { PsiElement resolved = methodReferenceExpression.resolve(); if (resolved == null) { return false; } - PsiClass arrayClass = JavaPsiFacade.getInstance(methodReferenceExpression.getProject()).getElementFactory().getArrayClass(PsiUtil.getLanguageLevel(methodReferenceExpression)); + PsiClass arrayClass = JavaPsiFacade.getInstance(methodReferenceExpression.getProject()).getElementFactory() + .getArrayClass(PsiUtil.getLanguageLevel(methodReferenceExpression)); return resolved == arrayClass; } return !SideEffectChecker.mayHaveSideEffects(qualifierExpression); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/VariableData.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/VariableData.java index ee30c89b12..122c0c4d94 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/VariableData.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/VariableData.java @@ -15,7 +15,6 @@ */ package com.intellij.java.analysis.impl.refactoring.util; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.PsiVariable; @@ -27,12 +26,12 @@ public class VariableData { public String name; public boolean passAsParameter; - public VariableData(@Nonnull PsiVariable var) { + public VariableData(PsiVariable var) { variable = var; type = var.getType(); } - public VariableData(@Nonnull PsiVariable var, @Nonnull PsiType type) { + public VariableData(PsiVariable var, PsiType type) { variable = var; this.type = SmartTypePointerManager.getInstance(var.getProject()).createSmartTypePointer(type).getType(); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/BreakReturnValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/BreakReturnValue.java index 6052d5de50..439b83621b 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/BreakReturnValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/BreakReturnValue.java @@ -20,14 +20,12 @@ */ package com.intellij.java.analysis.impl.refactoring.util.duplicates; -import org.jetbrains.annotations.NonNls; public class BreakReturnValue extends GotoReturnValue{ public boolean isEquivalent(final ReturnValue other) { return other instanceof BreakReturnValue; } - @NonNls public String getGotoStatement() { return "if(a) break;"; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ContinueReturnValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ContinueReturnValue.java index 673c08a1e4..77bf92a75f 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ContinueReturnValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ContinueReturnValue.java @@ -20,7 +20,6 @@ */ package com.intellij.java.analysis.impl.refactoring.util.duplicates; -import org.jetbrains.annotations.NonNls; public class ContinueReturnValue extends GotoReturnValue { public boolean isEquivalent(final ReturnValue other) { @@ -28,7 +27,6 @@ public boolean isEquivalent(final ReturnValue other) { } - @NonNls public String getGotoStatement() { return "if (a) continue;"; } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/DuplicatesFinder.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/DuplicatesFinder.java index 0d30bac214..710680843a 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/DuplicatesFinder.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/DuplicatesFinder.java @@ -41,8 +41,7 @@ import consulo.util.collection.primitive.ints.IntLists; import consulo.util.dataholder.Key; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; /** @@ -59,7 +58,7 @@ public class DuplicatesFinder { @Nullable private final ReturnValue myReturnValue; - public DuplicatesFinder(PsiElement[] pattern, InputVariables parameters, @Nullable ReturnValue returnValue, @Nonnull List outputParameters) { + public DuplicatesFinder(PsiElement[] pattern, InputVariables parameters, @Nullable ReturnValue returnValue, List outputParameters) { myReturnValue = returnValue; LOG.assertTrue(pattern.length > 0); myPattern = pattern; diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/GotoReturnValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/GotoReturnValue.java index fe9607deb9..fc04b3c95d 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/GotoReturnValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/GotoReturnValue.java @@ -20,8 +20,7 @@ */ package com.intellij.java.analysis.impl.refactoring.util.duplicates; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import com.intellij.java.language.psi.JavaPsiFacade; import com.intellij.java.language.psi.PsiElementFactory; import com.intellij.java.language.psi.PsiExpression; @@ -46,6 +45,5 @@ public PsiStatement createReplacement(final PsiMethod extractedMethod, final Psi return (PsiStatement)CodeStyleManager.getInstance(statement.getManager().getProject()).reformat(statement); } - @NonNls public abstract String getGotoStatement(); } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/Match.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/Match.java index c027d3e1f7..f28b4fd3ee 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/Match.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/Match.java @@ -34,9 +34,8 @@ import consulo.util.collection.ArrayUtil; import consulo.language.util.IncorrectOperationException; import consulo.logging.Logger; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; /** @@ -201,7 +200,7 @@ boolean registerInstanceExpression(PsiExpression instanceExpression, final PsiCl } } - boolean putDeclarationCorrespondence(PsiElement patternDeclaration, @Nonnull PsiElement matchDeclaration) { + boolean putDeclarationCorrespondence(PsiElement patternDeclaration, PsiElement matchDeclaration) { PsiElement originalValue = myDeclarationCorrespondence.get(patternDeclaration); if (originalValue == null) { myDeclarationCorrespondence.put(patternDeclaration, matchDeclaration); diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ReturnValue.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ReturnValue.java index 6c9d5a0763..c84b51d2f3 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ReturnValue.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/refactoring/util/duplicates/ReturnValue.java @@ -19,8 +19,7 @@ import com.intellij.java.language.psi.PsiMethodCallExpression; import com.intellij.java.language.psi.PsiStatement; import consulo.language.util.IncorrectOperationException; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author dsl diff --git a/java-analysis-impl/src/main/java/com/siyeh/HardcodedMethodConstants.java b/java-analysis-impl/src/main/java/com/siyeh/HardcodedMethodConstants.java index ecec136755..c1cae76207 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/HardcodedMethodConstants.java +++ b/java-analysis-impl/src/main/java/com/siyeh/HardcodedMethodConstants.java @@ -15,14 +15,12 @@ */ package com.siyeh; -import org.jetbrains.annotations.NonNls; import com.intellij.java.language.impl.codeInsight.daemon.impl.analysis.HighlightUtilBase; /** * User: anna * Date: 30-Aug-2005 */ -@NonNls public class HardcodedMethodConstants { public static final String CLONE = "clone"; public static final String CLOSE = "close"; diff --git a/java-analysis-impl/src/main/java/com/siyeh/InspectionGadgetsBundle.java b/java-analysis-impl/src/main/java/com/siyeh/InspectionGadgetsBundle.java index 9e27f1f2f9..850d09583b 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/InspectionGadgetsBundle.java +++ b/java-analysis-impl/src/main/java/com/siyeh/InspectionGadgetsBundle.java @@ -15,17 +15,17 @@ */ package com.siyeh; +import consulo.application.CommonBundle; +import org.jetbrains.annotations.PropertyKey; + import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.ResourceBundle; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.PropertyKey; -import consulo.application.CommonBundle; - /** * @author max */ +@Deprecated public class InspectionGadgetsBundle { private static Reference ourBundle; diff --git a/java-analysis-impl/src/main/java/com/siyeh/IntentionPowerPackBundle.java b/java-analysis-impl/src/main/java/com/siyeh/IntentionPowerPackBundle.java index 4a61f30527..273de31e7b 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/IntentionPowerPackBundle.java +++ b/java-analysis-impl/src/main/java/com/siyeh/IntentionPowerPackBundle.java @@ -15,21 +15,21 @@ */ package com.siyeh; +import consulo.application.CommonBundle; +import org.jetbrains.annotations.PropertyKey; + import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.util.ResourceBundle; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.PropertyKey; -import consulo.application.CommonBundle; - /** * @author max */ +@Deprecated public class IntentionPowerPackBundle { private static Reference ourBundle; - @NonNls private static final String BUNDLE = "com.siyeh.IntentionPowerPackBundle"; + private static final String BUNDLE = "com.siyeh.IntentionPowerPackBundle"; private IntentionPowerPackBundle() { } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspection.java b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspection.java index 06cf615a5c..9eafc31839 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspection.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspection.java @@ -17,28 +17,21 @@ import com.intellij.java.analysis.codeInspection.BaseJavaBatchLocalInspectionTool; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.language.editor.inspection.LocalInspectionToolSession; import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.editor.inspection.scheme.InspectionProfileEntry; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.PsiFile; +import consulo.localize.LocalizeValue; import consulo.ui.ex.awt.UIUtil; import consulo.ui.ex.awt.event.DocumentAdapter; import consulo.util.lang.StringUtil; -import consulo.util.lang.reflect.ReflectionUtil; -import consulo.util.xml.serializer.DefaultJDOMExternalizer; -import consulo.util.xml.serializer.WriteExternalException; -import org.jdom.Element; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.text.Document; -import java.lang.reflect.Field; import java.text.NumberFormat; import java.text.ParseException; import java.util.List; @@ -46,41 +39,19 @@ import java.util.function.IntSupplier; public abstract class BaseInspection extends BaseJavaBatchLocalInspectionTool { - private String m_shortName = null; - - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; } @Override - @Nonnull - public String getShortName() { - if (m_shortName == null) { - final Class aClass = getClass(); - final String name = aClass.getSimpleName(); - m_shortName = InspectionProfileEntry.getShortName(name); - if (m_shortName.equals(name)) { - throw new AssertionError("class name must end with 'Inspection' to correctly calculate the short name: " + name); - } - } - return m_shortName; - } - - @Nls - @Nonnull - @Override - public abstract String getDisplayName(); + public abstract LocalizeValue getDisplayName(); @Override - @Nls - @Nonnull - public final String getGroupDisplayName() { + public LocalizeValue getGroupDisplayName() { return GroupDisplayNameUtil.getGroupDisplayName(getClass()); } - @Nonnull protected abstract String buildErrorString(Object... infos); protected boolean buildQuickFixesOnlyForOnTheFlyErrors() { @@ -92,40 +63,17 @@ protected InspectionGadgetsFix buildFix(Object... infos) { return null; } - @Nonnull protected InspectionGadgetsFix[] buildFixes(Object... infos) { return InspectionGadgetsFix.EMPTY_ARRAY; } - protected void writeBooleanOption(@Nonnull Element node, @Nonnull @NonNls String property, boolean defaultValueToIgnore) { - final Boolean value = ReflectionUtil.getField(this.getClass(), this, boolean.class, property); - assert value != null; - if (defaultValueToIgnore == value.booleanValue()) { - return; - } - node.addContent(new Element("option").setAttribute("name", property).setAttribute("value", value.toString())); - } - - protected void defaultWriteSettings(@Nonnull Element node, final String... excludedProperties) throws WriteExternalException { - DefaultJDOMExternalizer.writeExternal(this, node, new DefaultJDOMExternalizer.JDOMFilter() { - @Override - public boolean isAccept(@Nonnull Field field) { - final String name = field.getName(); - for (String property : excludedProperties) { - if (name.equals(property)) { - return false; - } - } - return true; - } - }); - } - public abstract BaseInspectionVisitor buildVisitor(); @Override - @Nonnull - public final PsiElementVisitor buildVisitor(@Nonnull ProblemsHolder holder, boolean isOnTheFly) { + public final PsiElementVisitor buildVisitorImpl(ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state) { final PsiFile file = holder.getFile(); assert file.isPhysical(); if (!shouldInspect(file)) { diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java index 51b242fc63..63cad05ea8 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseInspectionVisitor.java @@ -16,218 +16,220 @@ package com.siyeh.ig; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.document.util.TextRange; import consulo.language.editor.inspection.ProblemHighlightType; import consulo.language.editor.inspection.ProblemsHolder; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.PsiWhiteSpace; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; +import consulo.localize.LocalizeValue; public abstract class BaseInspectionVisitor extends JavaElementVisitor { - - private BaseInspection inspection = null; - private boolean onTheFly = false; - private ProblemsHolder holder = null; - - final void setInspection(BaseInspection inspection) { - this.inspection = inspection; - } - - final void setOnTheFly(boolean onTheFly) { - this.onTheFly = onTheFly; - } - - public final boolean isOnTheFly() { - return onTheFly; - } - - protected final void registerNewExpressionError( - @Nonnull PsiNewExpression expression, Object... infos) { - final PsiJavaCodeReferenceElement classReference = - expression.getClassOrAnonymousClassReference(); - if (classReference == null) { - registerError(expression, infos); - } else { - registerError(classReference, infos); - } - } - - protected final void registerMethodCallError( - @Nonnull PsiMethodCallExpression expression, - @NonNls Object... infos) { - final PsiReferenceExpression methodExpression = - expression.getMethodExpression(); - final PsiElement nameToken = methodExpression.getReferenceNameElement(); - if (nameToken == null) { - registerError(expression, infos); - } else { - registerError(nameToken, infos); - } - } - - protected final void registerStatementError(@Nonnull PsiStatement statement, - Object... infos) { - final PsiElement statementToken = statement.getFirstChild(); - if (statementToken == null) { - registerError(statement, infos); - } else { - registerError(statementToken, infos); - } - } - - protected final void registerClassError(@Nonnull PsiClass aClass, - Object... infos) { - PsiElement nameIdentifier; - if (aClass instanceof PsiEnumConstantInitializer) { - final PsiEnumConstantInitializer enumConstantInitializer = - (PsiEnumConstantInitializer) aClass; - final PsiEnumConstant enumConstant = - enumConstantInitializer.getEnumConstant(); - nameIdentifier = enumConstant.getNameIdentifier(); - } else if (aClass instanceof PsiAnonymousClass) { - final PsiAnonymousClass anonymousClass = (PsiAnonymousClass) aClass; - nameIdentifier = anonymousClass.getBaseClassReference(); - } else { - nameIdentifier = aClass.getNameIdentifier(); - } - if (nameIdentifier != null && !nameIdentifier.isPhysical()) { - nameIdentifier = nameIdentifier.getNavigationElement(); - } - if (nameIdentifier == null || !nameIdentifier.isPhysical()) { - registerError(aClass.getContainingFile(), infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerMethodError(@Nonnull PsiMethod method, - Object... infos) { - final PsiElement nameIdentifier = method.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(method.getContainingFile(), infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerVariableError(@Nonnull PsiVariable variable, - Object... infos) { - final PsiElement nameIdentifier = variable.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(variable, infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerTypeParameterError( - @Nonnull PsiTypeParameter typeParameter, Object... infos) { - final PsiElement nameIdentifier = typeParameter.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(typeParameter, infos); - } else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerFieldError(@Nonnull PsiField field, - Object... infos) { - final PsiElement nameIdentifier = field.getNameIdentifier(); - registerError(nameIdentifier, infos); - } - - protected final void registerModifierError( - @Nonnull String modifier, @Nonnull PsiModifierListOwner parameter, - Object... infos) { - final PsiModifierList modifiers = parameter.getModifierList(); - if (modifiers == null) { - return; - } - final PsiElement[] children = modifiers.getChildren(); - for (final PsiElement child : children) { - final String text = child.getText(); - if (modifier.equals(text)) { - registerError(child, infos); - } - } - } - - protected final void registerClassInitializerError( - @Nonnull PsiClassInitializer initializer, Object... infos) { - final PsiCodeBlock body = initializer.getBody(); - final PsiJavaToken lBrace = body.getLBrace(); - if (lBrace == null) { - registerError(initializer, infos); - } else { - registerError(lBrace, infos); - } - } - - protected final void registerError(@Nonnull PsiElement location, - Object... infos) { - registerError(location, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); - } - - protected final void registerError(@Nonnull PsiElement location, - final ProblemHighlightType highlightType, - Object... infos) { - if (location.getTextLength() == 0 && !(location instanceof PsiFile)) { - return; - } - final InspectionGadgetsFix[] fixes = createFixes(infos); - for (InspectionGadgetsFix fix : fixes) { - fix.setOnTheFly(onTheFly); - } - final String description = inspection.buildErrorString(infos); - holder.registerProblem(location, description, highlightType, fixes); - } - - protected final void registerErrorAtOffset(@Nonnull PsiElement location, - int offset, int length, Object... infos) { - if (location.getTextLength() == 0 || length == 0) { - return; - } - final InspectionGadgetsFix[] fixes = createFixes(infos); - for (InspectionGadgetsFix fix : fixes) { - fix.setOnTheFly(onTheFly); - } - final String description = inspection.buildErrorString(infos); - final TextRange range = new TextRange(offset, offset + length); - holder.registerProblem(location, range, description, fixes); - } - - @Nonnull - private InspectionGadgetsFix[] createFixes(Object... infos) { - if (!onTheFly && inspection.buildQuickFixesOnlyForOnTheFlyErrors()) { - return InspectionGadgetsFix.EMPTY_ARRAY; - } - final InspectionGadgetsFix[] fixes = inspection.buildFixes(infos); - if (fixes.length > 0) { - return fixes; - } - final InspectionGadgetsFix fix = inspection.buildFix(infos); - if (fix == null) { - return InspectionGadgetsFix.EMPTY_ARRAY; - } - return new InspectionGadgetsFix[]{fix}; - } - - @Override - public void visitReferenceExpression( - PsiReferenceExpression expression) { - visitExpression(expression); - } - - @Override - public final void visitWhiteSpace(PsiWhiteSpace space) { - // none of our inspections need to do anything with white space, - // so this is a performance optimization - } - - public final void setProblemsHolder(ProblemsHolder holder) { - this.holder = holder; - } + private BaseInspection inspection = null; + private boolean onTheFly = false; + private ProblemsHolder holder = null; + + final void setInspection(BaseInspection inspection) { + this.inspection = inspection; + } + + final void setOnTheFly(boolean onTheFly) { + this.onTheFly = onTheFly; + } + + public final boolean isOnTheFly() { + return onTheFly; + } + + @RequiredReadAction + protected final void registerNewExpressionError(PsiNewExpression expression, Object... infos) { + PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + if (classReference == null) { + registerError(expression, infos); + } + else { + registerError(classReference, infos); + } + } + + @RequiredReadAction + protected final void registerMethodCallError(PsiMethodCallExpression expression, Object... infos) { + PsiReferenceExpression methodExpression = expression.getMethodExpression(); + PsiElement nameToken = methodExpression.getReferenceNameElement(); + if (nameToken == null) { + registerError(expression, infos); + } + else { + registerError(nameToken, infos); + } + } + + @RequiredReadAction + protected final void registerStatementError(PsiStatement statement, Object... infos) { + PsiElement statementToken = statement.getFirstChild(); + if (statementToken == null) { + registerError(statement, infos); + } + else { + registerError(statementToken, infos); + } + } + + @RequiredReadAction + protected final void registerClassError(PsiClass aClass, Object... infos) { + PsiElement nameIdentifier; + if (aClass instanceof PsiEnumConstantInitializer enumConstantInitializer) { + PsiEnumConstant enumConstant = enumConstantInitializer.getEnumConstant(); + nameIdentifier = enumConstant.getNameIdentifier(); + } + else if (aClass instanceof PsiAnonymousClass anonymousClass) { + nameIdentifier = anonymousClass.getBaseClassReference(); + } + else { + nameIdentifier = aClass.getNameIdentifier(); + } + if (nameIdentifier != null && !nameIdentifier.isPhysical()) { + nameIdentifier = nameIdentifier.getNavigationElement(); + } + if (nameIdentifier == null || !nameIdentifier.isPhysical()) { + registerError(aClass.getContainingFile(), infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerMethodError(PsiMethod method, Object... infos) { + PsiElement nameIdentifier = method.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(method.getContainingFile(), infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerVariableError(PsiVariable variable, Object... infos) { + PsiElement nameIdentifier = variable.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(variable, infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerTypeParameterError(PsiTypeParameter typeParameter, Object... infos) { + PsiElement nameIdentifier = typeParameter.getNameIdentifier(); + if (nameIdentifier == null) { + registerError(typeParameter, infos); + } + else { + registerError(nameIdentifier, infos); + } + } + + @RequiredReadAction + protected final void registerFieldError(PsiField field, Object... infos) { + PsiElement nameIdentifier = field.getNameIdentifier(); + registerError(nameIdentifier, infos); + } + + @RequiredReadAction + protected final void registerModifierError(String modifier, PsiModifierListOwner parameter, Object... infos) { + PsiModifierList modifiers = parameter.getModifierList(); + if (modifiers == null) { + return; + } + PsiElement[] children = modifiers.getChildren(); + for (PsiElement child : children) { + String text = child.getText(); + if (modifier.equals(text)) { + registerError(child, infos); + } + } + } + + @RequiredReadAction + protected final void registerClassInitializerError(PsiClassInitializer initializer, Object... infos) { + PsiCodeBlock body = initializer.getBody(); + PsiJavaToken lBrace = body.getLBrace(); + if (lBrace == null) { + registerError(initializer, infos); + } + else { + registerError(lBrace, infos); + } + } + + @RequiredReadAction + protected final void registerError(PsiElement location, Object... infos) { + registerError(location, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); + } + + @RequiredReadAction + protected final void registerError(PsiElement location, ProblemHighlightType highlightType, Object... infos) { + if (location.getTextLength() == 0 && !(location instanceof PsiFile)) { + return; + } + InspectionGadgetsFix[] fixes = createFixes(infos); + for (InspectionGadgetsFix fix : fixes) { + fix.setOnTheFly(onTheFly); + } + String description = inspection.buildErrorString(infos); + holder.newProblem(LocalizeValue.of(description)) + .range(location) + .withFixes(fixes) + .highlightType(highlightType) + .create(); + } + + @RequiredReadAction + protected final void registerErrorAtOffset(PsiElement location, int offset, int length, Object... infos) { + if (location.getTextLength() == 0 || length == 0) { + return; + } + InspectionGadgetsFix[] fixes = createFixes(infos); + for (InspectionGadgetsFix fix : fixes) { + fix.setOnTheFly(onTheFly); + } + holder.newProblem(LocalizeValue.localizeTODO(inspection.buildErrorString(infos))) + .range(location, new TextRange(offset, offset + length)) + .withFixes(fixes) + .create(); + } + + private InspectionGadgetsFix[] createFixes(Object... infos) { + if (!onTheFly && inspection.buildQuickFixesOnlyForOnTheFlyErrors()) { + return InspectionGadgetsFix.EMPTY_ARRAY; + } + InspectionGadgetsFix[] fixes = inspection.buildFixes(infos); + if (fixes.length > 0) { + return fixes; + } + InspectionGadgetsFix fix = inspection.buildFix(infos); + if (fix == null) { + return InspectionGadgetsFix.EMPTY_ARRAY; + } + return new InspectionGadgetsFix[]{fix}; + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitExpression(expression); + } + + @Override + public final void visitWhiteSpace(PsiWhiteSpace space) { + // none of our inspections need to do anything with white space, + // so this is a performance optimization + } + + public final void setProblemsHolder(ProblemsHolder holder) { + this.holder = holder; + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseSharedLocalInspection.java b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseSharedLocalInspection.java index 79f1b68401..47fd8ffba7 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/BaseSharedLocalInspection.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/BaseSharedLocalInspection.java @@ -15,9 +15,7 @@ */ package com.siyeh.ig; -import javax.annotation.Nonnull; - -import org.jetbrains.annotations.Nls; +import consulo.localize.LocalizeValue; import consulo.language.editor.inspection.GlobalInspectionTool; /** @@ -25,7 +23,6 @@ */ public abstract class BaseSharedLocalInspection extends BaseInspection { - protected final T mySettingsDelegate; public BaseSharedLocalInspection(T settingsDelegate) @@ -33,17 +30,14 @@ public BaseSharedLocalInspection(T settingsDelegate) mySettingsDelegate = settingsDelegate; } - @Nonnull @Override public final String getShortName() { return mySettingsDelegate.getShortName(); } - @Nls - @Nonnull @Override - public final String getDisplayName() + public final LocalizeValue getDisplayName() { return mySettingsDelegate.getDisplayName(); } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/GroupDisplayNameUtil.java b/java-analysis-impl/src/main/java/com/siyeh/ig/GroupDisplayNameUtil.java index 7f383e8c87..bc6829de09 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/GroupDisplayNameUtil.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/GroupDisplayNameUtil.java @@ -1,7 +1,7 @@ /* * Copyright 2011 Bas Leijdekkers * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License")); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -15,64 +15,62 @@ */ package com.siyeh.ig; +import consulo.language.editor.inspection.localize.InspectionLocalize; +import consulo.localize.LocalizeValue; + import java.util.HashMap; import java.util.Map; -import org.jetbrains.annotations.NonNls; -import consulo.language.editor.inspection.InspectionsBundle; - public class GroupDisplayNameUtil { - @NonNls - private static final Map packageGroupDisplayNameMap = new HashMap(); + private static final Map packageGroupDisplayNameMap = new HashMap<>(); static { - packageGroupDisplayNameMap.put("abstraction", "group.names.abstraction.issues"); - packageGroupDisplayNameMap.put("assignment", "group.names.assignment.issues"); - packageGroupDisplayNameMap.put("bitwise", "group.names.bitwise.operation.issues"); - packageGroupDisplayNameMap.put("bugs", "group.names.probable.bugs"); - packageGroupDisplayNameMap.put("classlayout", "group.names.class.structure"); - packageGroupDisplayNameMap.put("classmetrics", "group.names.class.metrics"); - packageGroupDisplayNameMap.put("cloneable", "group.names.cloning.issues"); - packageGroupDisplayNameMap.put("controlflow", "group.names.control.flow.issues"); - packageGroupDisplayNameMap.put("dataflow", "group.names.data.flow.issues"); - packageGroupDisplayNameMap.put("dependency", "group.names.dependency.issues"); - packageGroupDisplayNameMap.put("encapsulation", "group.names.encapsulation.issues"); - packageGroupDisplayNameMap.put("errorhandling", "group.names.error.handling"); - packageGroupDisplayNameMap.put("finalization", "group.names.finalization.issues"); - packageGroupDisplayNameMap.put("imports", "group.names.imports"); - packageGroupDisplayNameMap.put("inheritance", "group.names.inheritance.issues"); - packageGroupDisplayNameMap.put("initialization", "group.names.initialization.issues"); - packageGroupDisplayNameMap.put("internationalization", "group.names.internationalization.issues"); - packageGroupDisplayNameMap.put("j2me", "group.names.j2me.issues"); - packageGroupDisplayNameMap.put("javabeans", "group.names.javabeans.issues"); - packageGroupDisplayNameMap.put("javadoc", "group.names.javadoc.issues"); - packageGroupDisplayNameMap.put("jdk", "group.names.java.language.level.issues"); - packageGroupDisplayNameMap.put("migration", - "group.names.language.level.specific.issues.and.migration.aids"); - packageGroupDisplayNameMap.put("junit", "group.names.junit.issues"); - packageGroupDisplayNameMap.put("logging", "group.names.logging.issues"); - packageGroupDisplayNameMap.put("maturity", "group.names.code.maturity.issues"); - packageGroupDisplayNameMap.put("memory", "group.names.memory.issues"); - packageGroupDisplayNameMap.put("methodmetrics", "group.names.method.metrics"); - packageGroupDisplayNameMap.put("modularization", "group.names.modularization.issues"); - packageGroupDisplayNameMap.put("naming", "group.names.naming.conventions"); - packageGroupDisplayNameMap.put("numeric", "group.names.numeric.issues"); - packageGroupDisplayNameMap.put("packaging", "group.names.packaging.issues"); - packageGroupDisplayNameMap.put("performance", "group.names.performance.issues"); - packageGroupDisplayNameMap.put("portability", "group.names.portability.issues"); - packageGroupDisplayNameMap.put("redundancy", "group.names.declaration.redundancy"); - packageGroupDisplayNameMap.put("resources", "group.names.resource.management.issues"); - packageGroupDisplayNameMap.put("security", "group.names.security.issues"); - packageGroupDisplayNameMap.put("serialization", "group.names.serialization.issues"); - packageGroupDisplayNameMap.put("style", "group.names.code.style.issues"); - packageGroupDisplayNameMap.put("threading", "group.names.threading.issues"); - packageGroupDisplayNameMap.put("visibility", "group.names.visibility.issues"); + packageGroupDisplayNameMap.put("abstraction", InspectionLocalize.groupNamesAbstractionIssues()); + packageGroupDisplayNameMap.put("assignment", InspectionLocalize.groupNamesAssignmentIssues()); + packageGroupDisplayNameMap.put("bitwise", InspectionLocalize.groupNamesBitwiseOperationIssues()); + packageGroupDisplayNameMap.put("bugs", InspectionLocalize.groupNamesProbableBugs()); + packageGroupDisplayNameMap.put("classlayout", InspectionLocalize.groupNamesClassStructure()); + packageGroupDisplayNameMap.put("classmetrics", InspectionLocalize.groupNamesClassMetrics()); + packageGroupDisplayNameMap.put("cloneable", InspectionLocalize.groupNamesCloningIssues()); + packageGroupDisplayNameMap.put("controlflow", InspectionLocalize.groupNamesControlFlowIssues()); + packageGroupDisplayNameMap.put("dataflow", InspectionLocalize.groupNamesDataFlowIssues()); + packageGroupDisplayNameMap.put("dependency", InspectionLocalize.groupNamesDependencyIssues()); + packageGroupDisplayNameMap.put("encapsulation", InspectionLocalize.groupNamesEncapsulationIssues()); + packageGroupDisplayNameMap.put("errorhandling", InspectionLocalize.groupNamesErrorHandling()); + packageGroupDisplayNameMap.put("finalization", InspectionLocalize.groupNamesFinalizationIssues()); + packageGroupDisplayNameMap.put("imports", InspectionLocalize.groupNamesImports()); + packageGroupDisplayNameMap.put("inheritance", InspectionLocalize.groupNamesInheritanceIssues()); + packageGroupDisplayNameMap.put("initialization", InspectionLocalize.groupNamesInitializationIssues()); + packageGroupDisplayNameMap.put("internationalization", InspectionLocalize.groupNamesInternationalizationIssues()); + packageGroupDisplayNameMap.put("j2me", InspectionLocalize.groupNamesJ2meIssues()); + packageGroupDisplayNameMap.put("javabeans", InspectionLocalize.groupNamesJavabeansIssues()); + packageGroupDisplayNameMap.put("javadoc", InspectionLocalize.groupNamesJavadocIssues()); + packageGroupDisplayNameMap.put("jdk", InspectionLocalize.groupNamesJavaLanguageLevelIssues()); + packageGroupDisplayNameMap.put("migration", InspectionLocalize.groupNamesLanguageLevelSpecificIssuesAndMigrationAids()); + packageGroupDisplayNameMap.put("junit", InspectionLocalize.groupNamesJunitIssues()); + packageGroupDisplayNameMap.put("logging", InspectionLocalize.groupNamesLoggingIssues()); + packageGroupDisplayNameMap.put("maturity", InspectionLocalize.groupNamesCodeMaturityIssues()); + packageGroupDisplayNameMap.put("memory", InspectionLocalize.groupNamesMemoryIssues()); + packageGroupDisplayNameMap.put("methodmetrics", InspectionLocalize.groupNamesMethodMetrics()); + packageGroupDisplayNameMap.put("modularization", InspectionLocalize.groupNamesModularizationIssues()); + packageGroupDisplayNameMap.put("naming", InspectionLocalize.groupNamesNamingConventions()); + packageGroupDisplayNameMap.put("numeric", InspectionLocalize.groupNamesNumericIssues()); + packageGroupDisplayNameMap.put("packaging", InspectionLocalize.groupNamesPackagingIssues()); + packageGroupDisplayNameMap.put("performance", InspectionLocalize.groupNamesPerformanceIssues()); + packageGroupDisplayNameMap.put("portability", InspectionLocalize.groupNamesPortabilityIssues()); + packageGroupDisplayNameMap.put("redundancy", InspectionLocalize.groupNamesDeclarationRedundancy()); + packageGroupDisplayNameMap.put("resources", InspectionLocalize.groupNamesResourceManagementIssues()); + packageGroupDisplayNameMap.put("security", InspectionLocalize.groupNamesSecurityIssues()); + packageGroupDisplayNameMap.put("serialization", InspectionLocalize.groupNamesSerializationIssues()); + packageGroupDisplayNameMap.put("style", InspectionLocalize.groupNamesCodeStyleIssues()); + packageGroupDisplayNameMap.put("threading", InspectionLocalize.groupNamesThreadingIssues()); + packageGroupDisplayNameMap.put("visibility", InspectionLocalize.groupNamesVisibilityIssues()); } private GroupDisplayNameUtil() { } - public static String getGroupDisplayName(Class aClass) { + public static LocalizeValue getGroupDisplayName(Class aClass) { final Package thisPackage = aClass.getPackage(); assert thisPackage != null : "need package to determine group display name"; final String name = thisPackage.getName(); @@ -80,8 +78,8 @@ public static String getGroupDisplayName(Class aClass) { "inspection has default package, group display name cannot be determined"; final int index = name.lastIndexOf('.'); final String key = name.substring(index + 1); - final String groupDisplayName = packageGroupDisplayNameMap.get(key); + final LocalizeValue groupDisplayName = packageGroupDisplayNameMap.get(key); assert groupDisplayName != null : "No display name found for " + key; - return InspectionsBundle.message(groupDisplayName); + return groupDisplayName; } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/InspectionGadgetsFix.java b/java-analysis-impl/src/main/java/com/siyeh/ig/InspectionGadgetsFix.java index 3ecc7b6e9e..4c056867fb 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/InspectionGadgetsFix.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/InspectionGadgetsFix.java @@ -15,246 +15,209 @@ */ package com.siyeh.ig; -import javax.annotation.Nonnull; - -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nullable; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; +import consulo.language.codeStyle.CodeStyleManager; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.language.psi.PsiFile; +import consulo.language.util.IncorrectOperationException; import consulo.logging.Logger; import consulo.project.Project; import consulo.virtualFileSystem.ReadonlyStatusHandler; import consulo.virtualFileSystem.VirtualFile; -import com.intellij.java.language.psi.JavaPsiFacade; -import consulo.language.psi.PsiElement; -import com.intellij.java.language.psi.PsiElementFactory; -import com.intellij.java.language.psi.PsiExpression; -import consulo.language.psi.PsiFile; -import com.intellij.java.language.psi.PsiMember; -import com.intellij.java.language.psi.PsiReferenceExpression; -import com.intellij.java.language.psi.PsiStatement; -import consulo.language.codeStyle.CodeStyleManager; -import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; -import consulo.language.util.IncorrectOperationException; +import org.jspecify.annotations.Nullable; public abstract class InspectionGadgetsFix implements LocalQuickFix { + public static final InspectionGadgetsFix[] EMPTY_ARRAY = {}; - public static final InspectionGadgetsFix[] EMPTY_ARRAY = {}; - private static final Logger LOG = - Logger.getInstance(InspectionGadgetsFix.class); - - private boolean myOnTheFly = false; + private boolean myOnTheFly = false; - /** - * To appear in "Apply Fix" statement when multiple Quick Fixes exist - */ - @Nonnull - public String getFamilyName() { - return ""; - } - - public final void applyFix(@Nonnull Project project, - @Nonnull ProblemDescriptor descriptor) { - final PsiElement problemElement = descriptor.getPsiElement(); - if (problemElement == null || !problemElement.isValid()) { - return; - } - if (isQuickFixOnReadOnlyFile(problemElement)) { - return; - } - try { - doFix(project, descriptor); - } - catch (IncorrectOperationException e) { - final Class aClass = getClass(); - final String className = aClass.getName(); - final Logger logger = Logger.getInstance(className); - logger.error(e); + @Override + @RequiredWriteAction + public final void applyFix(Project project, ProblemDescriptor descriptor) { + PsiElement problemElement = descriptor.getPsiElement(); + if (problemElement == null || !problemElement.isValid()) { + return; + } + if (isQuickFixOnReadOnlyFile(problemElement)) { + return; + } + try { + doFix(project, descriptor); + } + catch (IncorrectOperationException e) { + Class aClass = getClass(); + String className = aClass.getName(); + Logger logger = Logger.getInstance(className); + logger.error(e); + } } - } - protected abstract void doFix(Project project, ProblemDescriptor descriptor) - throws IncorrectOperationException; + @RequiredWriteAction + protected abstract void doFix(Project project, ProblemDescriptor descriptor) + throws IncorrectOperationException; - protected static void deleteElement(@Nonnull PsiElement element) - throws IncorrectOperationException { - element.delete(); - } + @RequiredWriteAction + protected static void deleteElement(PsiElement element) + throws IncorrectOperationException { + element.delete(); + } - protected static void replaceExpression( - @Nonnull PsiExpression expression, - @Nonnull @NonNls String newExpressionText) - throws IncorrectOperationException { - final Project project = expression.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiExpression newExpression = - factory.createExpressionFromText(newExpressionText, expression); - final PsiElement replacementExpression = - expression.replace(newExpression); - final CodeStyleManager styleManager = - CodeStyleManager.getInstance(project); - styleManager.reformat(replacementExpression); - } + @RequiredWriteAction + protected static void replaceExpression(PsiExpression expression, String newExpressionText) + throws IncorrectOperationException { + Project project = expression.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiExpression newExpression = factory.createExpressionFromText(newExpressionText, expression); + PsiElement replacementExpression = expression.replace(newExpression); + CodeStyleManager styleManager = CodeStyleManager.getInstance(project); + styleManager.reformat(replacementExpression); + } - protected static void replaceExpressionWithReferenceTo( - @Nonnull PsiExpression expression, - @Nonnull PsiMember target) - throws IncorrectOperationException { - final Project project = expression.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiReferenceExpression newExpression = (PsiReferenceExpression) - factory.createExpressionFromText("xxx", expression); - final PsiReferenceExpression replacementExpression = - (PsiReferenceExpression)expression.replace(newExpression); - final PsiElement element = replacementExpression.bindToElement(target); - final JavaCodeStyleManager styleManager = - JavaCodeStyleManager.getInstance(project); - styleManager.shortenClassReferences(element); - } + @RequiredWriteAction + protected static void replaceExpressionWithReferenceTo(PsiExpression expression, PsiMember target) + throws IncorrectOperationException { + Project project = expression.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiReferenceExpression newExpression = (PsiReferenceExpression) factory.createExpressionFromText("xxx", expression); + PsiReferenceExpression replacementExpression = (PsiReferenceExpression) expression.replace(newExpression); + PsiElement element = replacementExpression.bindToElement(target); + JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project); + styleManager.shortenClassReferences(element); + } - protected static void replaceExpressionAndShorten( - @Nonnull PsiExpression expression, - @Nonnull @NonNls String newExpressionText) - throws IncorrectOperationException { - final Project project = expression.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiExpression newExpression = - factory.createExpressionFromText(newExpressionText, expression); - final PsiElement replacementExp = expression.replace(newExpression); - final JavaCodeStyleManager javaCodeStyleManager = - JavaCodeStyleManager.getInstance(project); - javaCodeStyleManager.shortenClassReferences(replacementExp); - final CodeStyleManager styleManager = - CodeStyleManager.getInstance(project); - styleManager.reformat(replacementExp); - } + @RequiredWriteAction + protected static void replaceExpressionAndShorten(PsiExpression expression, String newExpressionText) + throws IncorrectOperationException { + Project project = expression.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiExpression newExpression = factory.createExpressionFromText(newExpressionText, expression); + PsiElement replacementExp = expression.replace(newExpression); + JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); + javaCodeStyleManager.shortenClassReferences(replacementExp); + CodeStyleManager styleManager = CodeStyleManager.getInstance(project); + styleManager.reformat(replacementExp); + } - protected static void replaceStatement( - @Nonnull PsiStatement statement, - @Nonnull @NonNls String newStatementText) - throws IncorrectOperationException { - final Project project = statement.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiStatement newStatement = - factory.createStatementFromText(newStatementText, statement); - final PsiElement replacementExp = statement.replace(newStatement); - final CodeStyleManager styleManager = - CodeStyleManager.getInstance(project); - styleManager.reformat(replacementExp); - } + @RequiredWriteAction + protected static void replaceStatement(PsiStatement statement, String newStatementText) + throws IncorrectOperationException { + Project project = statement.getProject(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = psiFacade.getElementFactory(); + PsiStatement newStatement = factory.createStatementFromText(newStatementText, statement); + PsiElement replacementExp = statement.replace(newStatement); + CodeStyleManager styleManager = CodeStyleManager.getInstance(project); + styleManager.reformat(replacementExp); + } - protected static void replaceStatementAndShortenClassNames( - @Nonnull PsiStatement statement, - @Nonnull @NonNls String newStatementText) - throws IncorrectOperationException { - final Project project = statement.getProject(); - final CodeStyleManager styleManager = - CodeStyleManager.getInstance(project); - final JavaCodeStyleManager javaStyleManager = - JavaCodeStyleManager.getInstance(project); - /* if (JspPsiUtil.isInJspFile(statement)) { - final PsiDocumentManager documentManager = - PsiDocumentManager.getInstance(project); - final JspFile file = JspPsiUtil.getJspFile(statement); - final Document document = documentManager.getDocument(file); - if (document == null) { - return; - } - documentManager.doPostponedOperationsAndUnblockDocument(document); - final TextRange textRange = statement.getTextRange(); - document.replaceString(textRange.getStartOffset(), - textRange.getEndOffset(), newStatementText); - documentManager.commitDocument(document); - final JspxFileViewProvider viewProvider = file.getViewProvider(); - PsiElement elementAt = - viewProvider.findElementAt(textRange.getStartOffset(), - StdLanguages.JAVA); - if (elementAt == null) { - return; - } - final int endOffset = textRange.getStartOffset() + - newStatementText.length(); - while (elementAt.getTextRange().getEndOffset() < endOffset || - !(elementAt instanceof PsiStatement)) { - elementAt = elementAt.getParent(); - if (elementAt == null) { - LOG.error("Cannot decode statement"); - return; + @RequiredWriteAction + protected static void replaceStatementAndShortenClassNames(PsiStatement statement, String newStatementText) + throws IncorrectOperationException { + Project project = statement.getProject(); + CodeStyleManager styleManager = CodeStyleManager.getInstance(project); + JavaCodeStyleManager javaStyleManager = JavaCodeStyleManager.getInstance(project); + /* + if (JspPsiUtil.isInJspFile(statement)) { + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + JspFile file = JspPsiUtil.getJspFile(statement); + Document document = documentManager.getDocument(file); + if (document == null) { + return; + } + documentManager.doPostponedOperationsAndUnblockDocument(document); + TextRange textRange = statement.getTextRange(); + document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), newStatementText); + documentManager.commitDocument(document); + JspxFileViewProvider viewProvider = file.getViewProvider(); + PsiElement elementAt = viewProvider.findElementAt(textRange.getStartOffset(), StdLanguages.JAVA); + if (elementAt == null) { + return; + } + int endOffset = textRange.getStartOffset() + newStatementText.length(); + while (elementAt.getTextRange().getEndOffset() < endOffset || !(elementAt instanceof PsiStatement)) { + elementAt = elementAt.getParent(); + if (elementAt == null) { + LOG.error("Cannot decode statement"); + return; + } + } + PsiStatement newStatement = (PsiStatement)elementAt; + javaStyleManager.shortenClassReferences(newStatement); + TextRange newTextRange = newStatement.getTextRange(); + Language baseLanguage = viewProvider.getBaseLanguage(); + PsiFile element = viewProvider.getPsi(baseLanguage); + if (element != null) { + styleManager.reformatRange(element, newTextRange.getStartOffset(), newTextRange.getEndOffset()); + } + } + else */ + { + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiElementFactory factory = facade.getElementFactory(); + PsiStatement newStatement = factory.createStatementFromText(newStatementText, statement); + newStatement = (PsiStatement) statement.replace(newStatement); + javaStyleManager.shortenClassReferences(newStatement); + styleManager.reformat(newStatement); } - } - final PsiStatement newStatement = (PsiStatement)elementAt; - javaStyleManager.shortenClassReferences(newStatement); - final TextRange newTextRange = newStatement.getTextRange(); - final Language baseLanguage = viewProvider.getBaseLanguage(); - final PsiFile element = viewProvider.getPsi(baseLanguage); - if (element != null) { - styleManager.reformatRange(element, - newTextRange.getStartOffset(), - newTextRange.getEndOffset()); - } - } - else */{ - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = facade.getElementFactory(); - PsiStatement newStatement = factory.createStatementFromText( - newStatementText, statement); - newStatement = (PsiStatement)statement.replace(newStatement); - javaStyleManager.shortenClassReferences(newStatement); - styleManager.reformat(newStatement); } - } - protected boolean isQuickFixOnReadOnlyFile(PsiElement problemElement) { - final PsiFile containingPsiFile = problemElement.getContainingFile(); - if (containingPsiFile == null) { - return false; + protected boolean isQuickFixOnReadOnlyFile(PsiElement problemElement) { + PsiFile containingPsiFile = problemElement.getContainingFile(); + if (containingPsiFile == null) { + return false; + } + VirtualFile virtualFile = containingPsiFile.getVirtualFile(); + Project project = problemElement.getProject(); + ReadonlyStatusHandler handler = ReadonlyStatusHandler.getInstance(project); + ReadonlyStatusHandler.OperationStatus status = handler.ensureFilesWritable(virtualFile); + return status.hasReadonlyFiles(); } - final VirtualFile virtualFile = containingPsiFile.getVirtualFile(); - final Project project = problemElement.getProject(); - final ReadonlyStatusHandler handler = - ReadonlyStatusHandler.getInstance(project); - final ReadonlyStatusHandler.OperationStatus status = - handler.ensureFilesWritable(virtualFile); - return status.hasReadonlyFiles(); - } - - protected static String getElementText(@Nonnull PsiElement element, - @Nullable PsiElement elementToReplace, - @Nullable String replacement) { - final StringBuilder out = new StringBuilder(); - getElementText(element, elementToReplace, replacement, out); - return out.toString(); - } - private static void getElementText( - @Nonnull PsiElement element, - @Nullable PsiElement elementToReplace, - @Nullable String replacement, - @Nonnull StringBuilder out) { - if (element.equals(elementToReplace)) { - out.append(replacement); - return; - } - final PsiElement[] children = element.getChildren(); - if (children.length == 0) { - out.append(element.getText()); - return; + @RequiredReadAction + protected static String getElementText( + PsiElement element, + @Nullable PsiElement elementToReplace, + @Nullable String replacement + ) { + StringBuilder out = new StringBuilder(); + getElementText(element, elementToReplace, replacement, out); + return out.toString(); } - for (PsiElement child : children) { - getElementText(child, elementToReplace, replacement, out); + + @RequiredReadAction + private static void getElementText( + PsiElement element, + @Nullable PsiElement elementToReplace, + @Nullable String replacement, + StringBuilder out + ) { + if (element.equals(elementToReplace)) { + out.append(replacement); + return; + } + PsiElement[] children = element.getChildren(); + if (children.length == 0) { + out.append(element.getText()); + return; + } + for (PsiElement child : children) { + getElementText(child, elementToReplace, replacement, out); + } } - } - public final void setOnTheFly(boolean onTheFly) { - myOnTheFly = onTheFly; - } + public final void setOnTheFly(boolean onTheFly) { + myOnTheFly = onTheFly; + } - public final boolean isOnTheFly() { - return myOnTheFly; - } + public final boolean isOnTheFly() { + return myOnTheFly; + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/PsiReplacementUtil.java b/java-analysis-impl/src/main/java/com/siyeh/ig/PsiReplacementUtil.java index d7207445ec..3f7fdd2178 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/PsiReplacementUtil.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/PsiReplacementUtil.java @@ -7,21 +7,19 @@ import com.intellij.java.language.psi.codeStyle.JavaCodeStyleManager; import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.ig.psiutils.CommentTracker; -import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import consulo.language.codeStyle.CodeStyleManager; import consulo.language.psi.PsiElement; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; public class PsiReplacementUtil { /** * Consider to use {@link #replaceExpression(PsiExpression, String, CommentTracker)} to preserve comments */ - public static void replaceExpression(@Nonnull PsiExpression expression, @Nonnull @NonNls String newExpressionText) { + public static void replaceExpression(PsiExpression expression, String newExpressionText) { final Project project = expression.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); @@ -34,13 +32,13 @@ public static void replaceExpression(@Nonnull PsiExpression expression, @Nonnull /** * @param tracker ensure to {@link CommentTracker#markUnchanged(PsiElement)} expressions used as getText in newExpressionText */ - public static void replaceExpression(@Nonnull PsiExpression expression, @Nonnull @NonNls String newExpressionText, CommentTracker tracker) { + public static void replaceExpression(PsiExpression expression, String newExpressionText, CommentTracker tracker) { final Project project = expression.getProject(); final PsiElement replacementExpression = tracker.replaceAndRestoreComments(expression, newExpressionText); CodeStyleManager.getInstance(project).reformat(replacementExpression); } - public static PsiElement replaceExpressionAndShorten(@Nonnull PsiExpression expression, @Nonnull @NonNls String newExpressionText) { + public static PsiElement replaceExpressionAndShorten(PsiExpression expression, String newExpressionText) { final Project project = expression.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); @@ -52,7 +50,7 @@ public static PsiElement replaceExpressionAndShorten(@Nonnull PsiExpression expr return styleManager.reformat(replacementExp); } - public static PsiElement replaceExpressionAndShorten(@Nonnull PsiExpression expression, @Nonnull @NonNls String newExpressionText, CommentTracker tracker) { + public static PsiElement replaceExpressionAndShorten(PsiExpression expression, String newExpressionText, CommentTracker tracker) { final Project project = expression.getProject(); final PsiElement replacementExp = tracker.replaceAndRestoreComments(expression, newExpressionText); final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); @@ -64,7 +62,7 @@ public static PsiElement replaceExpressionAndShorten(@Nonnull PsiExpression expr /** * Consider to use {@link #replaceStatement(PsiStatement, String, CommentTracker)} to preserve comments */ - public static PsiElement replaceStatement(@Nonnull PsiStatement statement, @Nonnull @NonNls String newStatementText) { + public static PsiElement replaceStatement(PsiStatement statement, String newStatementText) { final Project project = statement.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); @@ -77,7 +75,7 @@ public static PsiElement replaceStatement(@Nonnull PsiStatement statement, @Nonn /** * @param commentTracker ensure to {@link CommentTracker#markUnchanged(PsiElement)} expressions used as getText in newStatementText */ - public static PsiElement replaceStatement(@Nonnull PsiStatement statement, @Nonnull @NonNls String newStatementText, CommentTracker commentTracker) { + public static PsiElement replaceStatement(PsiStatement statement, String newStatementText, CommentTracker commentTracker) { final Project project = statement.getProject(); final PsiElement replacementExp = commentTracker.replaceAndRestoreComments(statement, newStatementText); final CodeStyleManager styleManager = CodeStyleManager.getInstance(project); @@ -87,15 +85,15 @@ public static PsiElement replaceStatement(@Nonnull PsiStatement statement, @Nonn /** * Consider to use {@link #replaceStatementAndShortenClassNames(PsiStatement, String, CommentTracker)} to preserve comments */ - public static void replaceStatementAndShortenClassNames(@Nonnull PsiStatement statement, @Nonnull @NonNls String newStatementText) { + public static void replaceStatementAndShortenClassNames(PsiStatement statement, String newStatementText) { replaceStatementAndShortenClassNames(statement, newStatementText, null); } /** * @param tracker ensure to {@link CommentTracker#markUnchanged(PsiElement)} expressions used as getText in newStatementText */ - public static PsiElement replaceStatementAndShortenClassNames(@Nonnull PsiStatement statement, - @Nonnull @NonNls String newStatementText, + public static PsiElement replaceStatementAndShortenClassNames(PsiStatement statement, + String newStatementText, @Nullable CommentTracker tracker) { final Project project = statement.getProject(); final CodeStyleManager styleManager = CodeStyleManager.getInstance(project); @@ -111,7 +109,7 @@ public static PsiElement replaceStatementAndShortenClassNames(@Nonnull PsiStatem return styleManager.reformat(javaStyleManager.shortenClassReferences(newStatement)); } - public static void replaceExpressionWithReferenceTo(@Nonnull PsiExpression expression, @Nonnull PsiMember target) { + public static void replaceExpressionWithReferenceTo(PsiExpression expression, PsiMember target) { final Project project = expression.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); @@ -123,15 +121,14 @@ public static void replaceExpressionWithReferenceTo(@Nonnull PsiExpression expre styleManager.shortenClassReferences(element); } - @Nonnull - public static String getElementText(@Nonnull PsiElement element, @Nullable PsiElement elementToReplace, @Nullable String replacement) { + public static String getElementText(PsiElement element, @Nullable PsiElement elementToReplace, @Nullable String replacement) { final StringBuilder out = new StringBuilder(); getElementText(element, elementToReplace, replacement, out); return out.toString(); } - private static void getElementText(@Nonnull PsiElement element, @Nullable PsiElement elementToReplace, - @Nullable String replacement, @Nonnull StringBuilder out) { + private static void getElementText(PsiElement element, @Nullable PsiElement elementToReplace, + @Nullable String replacement, StringBuilder out) { if (element.equals(elementToReplace)) { out.append(replacement); return; @@ -146,7 +143,7 @@ private static void getElementText(@Nonnull PsiElement element, @Nullable PsiEle } } - public static void replaceOperatorAssignmentWithAssignmentExpression(@Nonnull PsiAssignmentExpression assignmentExpression) { + public static void replaceOperatorAssignmentWithAssignmentExpression(PsiAssignmentExpression assignmentExpression) { CommentTracker tracker = new CommentTracker(); final PsiJavaToken sign = assignmentExpression.getOperationSign(); final PsiExpression lhs = assignmentExpression.getLExpression(); diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/bugs/EqualsWithItselfInspection.java b/java-analysis-impl/src/main/java/com/siyeh/ig/bugs/EqualsWithItselfInspection.java index 6b05d463f5..8a68b4fac2 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/bugs/EqualsWithItselfInspection.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/bugs/EqualsWithItselfInspection.java @@ -16,32 +16,28 @@ package com.siyeh.ig.bugs; import com.intellij.java.language.psi.*; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; -import com.siyeh.ig.psiutils.EquivalenceChecker; +import com.intellij.java.analysis.impl.codeInspection.EquivalenceChecker; import com.siyeh.ig.psiutils.MethodCallUtils; -import com.siyeh.ig.psiutils.ParenthesesUtils; -import com.siyeh.ig.psiutils.SideEffectChecker; -import org.jetbrains.annotations.Nls; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; +import com.intellij.java.analysis.impl.codeInspection.SideEffectChecker; +import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.localize.LocalizeValue; -import javax.annotation.Nonnull; /** * @author Bas Leijdekkers */ public abstract class EqualsWithItselfInspection extends BaseInspection { - @Nls - @Nonnull @Override - public String getDisplayName() { - return InspectionGadgetsBundle.message("equals.with.itself.display.name"); + public LocalizeValue getDisplayName() { + return InspectionGadgetsLocalize.equalsWithItselfDisplayName(); } - @Nonnull @Override protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("equals.with.itself.problem.descriptor"); + return InspectionGadgetsLocalize.equalsWithItselfProblemDescriptor().get(); } @Override diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java b/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java index 48928e214f..e0e2d66b1e 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/callMatcher/CallMatcher.java @@ -6,14 +6,14 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.psiutils.MethodCallUtils; +import consulo.annotation.access.RequiredReadAction; import consulo.language.psi.PsiElement; import consulo.util.collection.ArrayUtil; import consulo.util.lang.ObjectUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; import java.util.Set; import java.util.function.Predicate; @@ -26,368 +26,365 @@ * @author Tagir Valeev */ public interface CallMatcher extends Predicate { - /** - * @return names of the methods for which this matcher may return true. For any other method it guaranteed to return false - */ - Stream names(); - - @Contract(value = "null -> false", pure = true) - boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef); - - @Override - @Contract(value = "null -> false", pure = true) - boolean test(@Nullable PsiMethodCallExpression call); - - @Contract(value = "null -> false", pure = true) - boolean methodMatches(@Nullable PsiMethod method); - - /** - * Returns true if the supplied expression is (possibly parenthesized) method call which matches this matcher - * - * @param expression expression to test - * @return true if the supplied expression matches this matcher - */ - @Contract(value = "null -> false", pure = true) - default boolean matches(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - return expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression) expression); - } - - /** - * Returns a new matcher which will return true if any of supplied matchers return true - * - * @param matchers - * @return a new matcher - */ - static CallMatcher anyOf(CallMatcher... matchers) { - return new CallMatcher() { - @Override - public Stream names() { - return Stream.of(matchers).flatMap(CallMatcher::names); - } - - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - for (CallMatcher m : matchers) { - if (m.methodReferenceMatches(methodRef)) { - return true; - } - } - return false; - } - - @Override - public boolean methodMatches(PsiMethod method) { - for (CallMatcher m : matchers) { - if (m.methodMatches(method)) { - return true; - } - } - return false; - } - - @Override - public boolean test(PsiMethodCallExpression call) { - for (CallMatcher m : matchers) { - if (m.test(call)) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return Stream.of(matchers).map(CallMatcher::toString).collect(Collectors.joining(" or ", "{", "}")); - } - }; - } - - /** - * Creates a matcher which matches an instance method having one of supplied names which class (or any of superclasses) is className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple instanceCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.INSTANCE); - } - - /** - * Creates a matcher which matches an instance method having one of supplied names which class is exactly a className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple exactInstanceCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.EXACT_INSTANCE); - } - - /** - * Creates a matcher which matches a static method having one of supplied names which class is className - * - * @param className fully-qualified class name - * @param methodNames names of the methods - * @return a new matcher - */ - @Contract(pure = true) - static Simple staticCall(@Nonnull String className, String... methodNames) { - return new Simple(className, Set.of(methodNames), null, CallType.STATIC); - } - - static Simple enumValues() { - return Simple.ENUM_VALUES; - } - - static Simple enumValueOf() { - return Simple.ENUM_VALUE_OF; - } - - /** - * Matches given expression if its a call or a method reference returning a corresponding PsiReferenceExpression if match is successful. - * - * @param expression expression to match - * @return PsiReferenceExpression if match is successful, null otherwise - */ - @Nullable - @Contract(pure = true) - default PsiReferenceExpression getReferenceIfMatched(PsiExpression expression) { - if (expression instanceof PsiMethodReferenceExpression && methodReferenceMatches((PsiMethodReferenceExpression) expression)) { - return (PsiReferenceExpression) expression; - } - if (expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression) expression)) { - return ((PsiMethodCallExpression) expression).getMethodExpression(); - } - return null; - } - - /** - * @return call matcher with additional check before actual call matching - */ - @Contract(pure = true) - default CallMatcher withContextFilter(@Nonnull Predicate filter) { - return new CallMatcher() { - @Override - public Stream names() { - return CallMatcher.this.names(); - } - - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - if (methodRef == null || !filter.test(methodRef)) { - return false; - } - return CallMatcher.this.methodReferenceMatches(methodRef); - } + /** + * @return names of the methods for which this matcher may return true. For any other method it guaranteed to return false + */ + Stream names(); - @Override - public boolean test(@Nullable PsiMethodCallExpression call) { - if (call == null || !filter.test(call)) { - return false; - } - return CallMatcher.this.test(call); - } + @Contract(value = "null -> false", pure = true) + boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef); - @Override - public boolean methodMatches(@Nullable PsiMethod method) { - if (method == null || !filter.test(method)) { - return false; - } - return CallMatcher.this.methodMatches(method); - } - - @Override - public String toString() { - return CallMatcher.this.toString(); - } - }; - } - - /** - * @return call matcher, that matches element for file with given language level or higher - */ - @Contract(pure = true) - default CallMatcher withLanguageLevelAtLeast(@Nonnull LanguageLevel level) { - return withContextFilter(element -> PsiUtil.getLanguageLevel(element).isAtLeast(level)); - } - - class Simple implements CallMatcher { - static final Simple ENUM_VALUES = new Simple("", Collections.singleton("values"), ArrayUtil.EMPTY_STRING_ARRAY, CallType.ENUM_STATIC); - static final Simple ENUM_VALUE_OF = - new Simple("", Collections.singleton("valueOf"), new String[]{CommonClassNames.JAVA_LANG_STRING}, CallType.ENUM_STATIC); - private final - @Nonnull - String myClassName; - private final - @Nonnull - Set myNames; - private final - @Nullable - String[] myParameters; - private final CallType myCallType; - - private Simple(@Nonnull String className, @Nonnull Set names, @Nullable String[] parameters, CallType callType) { - myClassName = className; - myNames = names; - myParameters = parameters; - myCallType = callType; + @Override + @Contract(value = "null -> false", pure = true) + boolean test(@Nullable PsiMethodCallExpression call); + + @Contract(value = "null -> false", pure = true) + boolean methodMatches(@Nullable PsiMethod method); + + /** + * Returns true if the supplied expression is (possibly parenthesized) method call which matches this matcher + * + * @param expression expression to test + * @return true if the supplied expression matches this matcher + */ + @Contract(value = "null -> false", pure = true) + default boolean matches(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + return expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression)expression); } - @Override - public Stream names() { - return myNames.stream(); + /** + * Returns a new matcher which will return true if any of supplied matchers return true + * + * @param matchers + * @return a new matcher + */ + static CallMatcher anyOf(CallMatcher... matchers) { + return new CallMatcher() { + @Override + public Stream names() { + return Stream.of(matchers).flatMap(CallMatcher::names); + } + + @Override + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + for (CallMatcher m : matchers) { + if (m.methodReferenceMatches(methodRef)) { + return true; + } + } + return false; + } + + @Override + public boolean methodMatches(PsiMethod method) { + for (CallMatcher m : matchers) { + if (m.methodMatches(method)) { + return true; + } + } + return false; + } + + @Override + public boolean test(PsiMethodCallExpression call) { + for (CallMatcher m : matchers) { + if (m.test(call)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return Stream.of(matchers).map(CallMatcher::toString).collect(Collectors.joining(" or ", "{", "}")); + } + }; + } + + /** + * Creates a matcher which matches an instance method having one of supplied names which class (or any of superclasses) is className + * + * @param className fully-qualified class name + * @param methodNames names of the methods + * @return a new matcher + */ + @Contract(pure = true) + static Simple instanceCall(String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.INSTANCE); } /** - * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * Creates a matcher which matches an instance method having one of supplied names which class is exactly a className * - * @param count expected number of parameters + * @param className fully-qualified class name + * @param methodNames names of the methods * @return a new matcher - * @throws IllegalStateException if this matcher is already limited to parameters count or types */ @Contract(pure = true) - public Simple parameterCount(int count) { - if (myParameters != null) { - throw new IllegalStateException("Parameter count is already set to " + count); - } - return new Simple(myClassName, myNames, count == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : new String[count], myCallType); + static Simple exactInstanceCall(String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.EXACT_INSTANCE); } /** - * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method - * and their types + * Creates a matcher which matches a static method having one of supplied names which class is className * - * @param types textual representation of parameter types (may contain null to ignore checking parameter type of specific argument) + * @param className fully-qualified class name + * @param methodNames names of the methods * @return a new matcher - * @throws IllegalStateException if this matcher is already limited to parameters count or types */ @Contract(pure = true) - public Simple parameterTypes(@Nonnull String... types) { - if (myParameters != null) { - throw new IllegalStateException("Parameters are already registered"); - } - return new Simple(myClassName, myNames, types.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : types.clone(), myCallType); + static Simple staticCall(String className, String... methodNames) { + return new Simple(className, Set.of(methodNames), null, CallType.STATIC); } - private static boolean parameterTypeMatches(String type, PsiParameter parameter) { - if (type == null) { - return true; - } - PsiType psiType = parameter.getType(); - return psiType.equalsToText(type) || - psiType instanceof PsiClassType && ((PsiClassType) psiType).rawType().equalsToText(type); + static Simple enumValues() { + return Simple.ENUM_VALUES; } - @Contract(pure = true) - @Override - public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { - if (methodRef == null) { - return false; - } - String name = methodRef.getReferenceName(); - if (!myNames.contains(name)) { - return false; - } - PsiMethod method = ObjectUtil.tryCast(methodRef.resolve(), PsiMethod.class); - if (!methodMatches(method)) { - return false; - } - PsiParameterList parameterList = method.getParameterList(); - return parametersMatch(parameterList); + static Simple enumValueOf() { + return Simple.ENUM_VALUE_OF; } + /** + * Matches given expression if its a call or a method reference returning a corresponding PsiReferenceExpression if match is successful. + * + * @param expression expression to match + * @return PsiReferenceExpression if match is successful, null otherwise + */ + @Nullable @Contract(pure = true) - @Override - public boolean test(PsiMethodCallExpression call) { - if (call == null) { - return false; - } - String name = call.getMethodExpression().getReferenceName(); - if (!myNames.contains(name)) { - return false; - } - PsiExpression[] args = call.getArgumentList().getExpressions(); - if (myParameters != null && myParameters.length > 0) { - if (args.length < myParameters.length - 1) { - return false; + default PsiReferenceExpression getReferenceIfMatched(PsiExpression expression) { + if (expression instanceof PsiMethodReferenceExpression && methodReferenceMatches((PsiMethodReferenceExpression)expression)) { + return (PsiReferenceExpression)expression; + } + if (expression instanceof PsiMethodCallExpression && test((PsiMethodCallExpression)expression)) { + return ((PsiMethodCallExpression)expression).getMethodExpression(); } - } - PsiMethod method = call.resolveMethod(); - if (method == null) { - return false; - } - PsiParameterList parameterList = method.getParameterList(); - int count = parameterList.getParametersCount(); - if (count > args.length + 1 || (!MethodCallUtils.isVarArgCall(call) && count != args.length)) { - return false; - } - return methodMatches(method); + return null; } - private boolean parametersMatch(@Nonnull PsiParameterList parameterList) { - if (myParameters == null) { - return true; - } - if (myParameters.length != parameterList.getParametersCount()) { - return false; - } - return StreamEx.zip(myParameters, parameterList.getParameters(), - Simple::parameterTypeMatches).allMatch(Boolean.TRUE::equals); + /** + * @return call matcher with additional check before actual call matching + */ + @Contract(pure = true) + default CallMatcher withContextFilter(Predicate filter) { + return new CallMatcher() { + @Override + public Stream names() { + return CallMatcher.this.names(); + } + + @Override + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + if (methodRef == null || !filter.test(methodRef)) { + return false; + } + return CallMatcher.this.methodReferenceMatches(methodRef); + } + + @Override + public boolean test(@Nullable PsiMethodCallExpression call) { + if (call == null || !filter.test(call)) { + return false; + } + return CallMatcher.this.test(call); + } + + @Override + public boolean methodMatches(@Nullable PsiMethod method) { + if (method == null || !filter.test(method)) { + return false; + } + return CallMatcher.this.methodMatches(method); + } + + @Override + public String toString() { + return CallMatcher.this.toString(); + } + }; } - @Override - @Contract(value = "null -> false", pure = true) - public boolean methodMatches(PsiMethod method) { - if (method == null) { - return false; - } - if (!myNames.contains(method.getName())) { - return false; - } - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; - } - return myCallType.matches(aClass, myClassName, method.hasModifierProperty(PsiModifier.STATIC)) && - parametersMatch(method.getParameterList()); + /** + * @return call matcher, that matches element for file with given language level or higher + */ + @Contract(pure = true) + default CallMatcher withLanguageLevelAtLeast(LanguageLevel level) { + return withContextFilter(element -> PsiUtil.getLanguageLevel(element).isAtLeast(level)); } - @Override - public String toString() { - return myClassName + "." + String.join("|", myNames); + class Simple implements CallMatcher { + static final Simple ENUM_VALUES = + new Simple("", Collections.singleton("values"), ArrayUtil.EMPTY_STRING_ARRAY, CallType.ENUM_STATIC); + static final Simple ENUM_VALUE_OF = + new Simple("", Collections.singleton("valueOf"), new String[]{CommonClassNames.JAVA_LANG_STRING}, CallType.ENUM_STATIC); + private final String myClassName; + private final Set myNames; + @Nullable + private final String[] myParameters; + private final CallType myCallType; + + private Simple(String className, Set names, @Nullable String[] parameters, CallType callType) { + myClassName = className; + myNames = names; + myParameters = parameters; + myCallType = callType; + } + + @Override + public Stream names() { + return myNames.stream(); + } + + /** + * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * + * @param count expected number of parameters + * @return a new matcher + * @throws IllegalStateException if this matcher is already limited to parameters count or types + */ + @Contract(pure = true) + public Simple parameterCount(int count) { + if (myParameters != null) { + throw new IllegalStateException("Parameter count is already set to " + count); + } + return new Simple(myClassName, myNames, count == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : new String[count], myCallType); + } + + /** + * Creates a new matcher which in addition to current matcher checks the number of parameters of the called method + * and their types + * + * @param types textual representation of parameter types (may contain null to ignore checking parameter type of specific argument) + * @return a new matcher + * @throws IllegalStateException if this matcher is already limited to parameters count or types + */ + @Contract(pure = true) + public Simple parameterTypes(String... types) { + if (myParameters != null) { + throw new IllegalStateException("Parameters are already registered"); + } + return new Simple(myClassName, myNames, types.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : types.clone(), myCallType); + } + + private static boolean parameterTypeMatches(String type, PsiParameter parameter) { + if (type == null) { + return true; + } + PsiType psiType = parameter.getType(); + return psiType.equalsToText(type) || + psiType instanceof PsiClassType && ((PsiClassType)psiType).rawType().equalsToText(type); + } + + @Contract(pure = true) + @Override + @RequiredReadAction + public boolean methodReferenceMatches(PsiMethodReferenceExpression methodRef) { + if (methodRef == null) { + return false; + } + String name = methodRef.getReferenceName(); + if (!myNames.contains(name)) { + return false; + } + PsiMethod method = ObjectUtil.tryCast(methodRef.resolve(), PsiMethod.class); + if (!methodMatches(method)) { + return false; + } + PsiParameterList parameterList = method.getParameterList(); + return parametersMatch(parameterList); + } + + @Contract(pure = true) + @Override + public boolean test(PsiMethodCallExpression call) { + if (call == null) { + return false; + } + String name = call.getMethodExpression().getReferenceName(); + if (!myNames.contains(name)) { + return false; + } + PsiExpression[] args = call.getArgumentList().getExpressions(); + if (myParameters != null && myParameters.length > 0) { + if (args.length < myParameters.length - 1) { + return false; + } + } + PsiMethod method = call.resolveMethod(); + if (method == null) { + return false; + } + PsiParameterList parameterList = method.getParameterList(); + int count = parameterList.getParametersCount(); + if (count > args.length + 1 || (!MethodCallUtils.isVarArgCall(call) && count != args.length)) { + return false; + } + return methodMatches(method); + } + + private boolean parametersMatch(PsiParameterList parameterList) { + if (myParameters == null) { + return true; + } + if (myParameters.length != parameterList.getParametersCount()) { + return false; + } + return StreamEx.zip(myParameters, parameterList.getParameters(), Simple::parameterTypeMatches) + .allMatch(Boolean.TRUE::equals); + } + + @Override + @Contract(value = "null -> false", pure = true) + public boolean methodMatches(PsiMethod method) { + if (method == null) { + return false; + } + if (!myNames.contains(method.getName())) { + return false; + } + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; + } + return myCallType.matches(aClass, myClassName, method.isStatic()) + && parametersMatch(method.getParameterList()); + } + + @Override + public String toString() { + return myClassName + "." + String.join("|", myNames); + } + } + + enum CallType { + STATIC { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return isStatic && className.equals(aClass.getQualifiedName()); + } + }, + ENUM_STATIC { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return isStatic && aClass.isEnum(); + } + }, + INSTANCE { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return !isStatic && InheritanceUtil.isInheritor(aClass, className); + } + }, + EXACT_INSTANCE { + @Override + boolean matches(PsiClass aClass, String className, boolean isStatic) { + return !isStatic && className.equals(aClass.getQualifiedName()); + } + }; + + abstract boolean matches(PsiClass aClass, String className, boolean isStatic); } - } - - enum CallType { - STATIC { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return isStatic && className.equals(aClass.getQualifiedName()); - } - }, - ENUM_STATIC { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return isStatic && aClass.isEnum(); - } - }, - INSTANCE { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return !isStatic && InheritanceUtil.isInheritor(aClass, className); - } - }, - EXACT_INSTANCE { - @Override - boolean matches(PsiClass aClass, String className, boolean isStatic) { - return !isStatic && className.equals(aClass.getQualifiedName()); - } - }; - - abstract boolean matches(PsiClass aClass, String className, boolean isStatic); - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/DeleteUnnecessaryStatementFix.java b/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/DeleteUnnecessaryStatementFix.java index d43e7ac487..96f767b369 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/DeleteUnnecessaryStatementFix.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/DeleteUnnecessaryStatementFix.java @@ -16,36 +16,26 @@ package com.siyeh.ig.fixes; import com.intellij.java.language.psi.*; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.psiutils.CommentTracker; +import com.siyeh.localize.InspectionGadgetsLocalize; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; public class DeleteUnnecessaryStatementFix extends InspectionGadgetsFix { private final String name; - public DeleteUnnecessaryStatementFix(@NonNls String name) { + public DeleteUnnecessaryStatementFix(String name) { this.name = name; } @Override - @Nonnull - public String getName() { - return InspectionGadgetsBundle.message( - "smth.unnecessary.remove.quickfix", name); - } - - @Nonnull - @Override - public String getFamilyName() { - return "Remove redundant statement"; + public LocalizeValue getName() { + return InspectionGadgetsLocalize.smthUnnecessaryRemoveQuickfix(name); } @Override diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/EqualsToEqualityFix.java b/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/EqualsToEqualityFix.java index a689cc61a1..30d7ea77aa 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/EqualsToEqualityFix.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/fixes/EqualsToEqualityFix.java @@ -1,74 +1,64 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.siyeh.ig.fixes; -import consulo.language.editor.inspection.ProblemDescriptor; -import consulo.project.Project; -import consulo.language.psi.PsiElement; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiMethodCallExpression; -import consulo.language.psi.util.PsiTreeUtil; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.PsiReplacementUtil; import com.siyeh.ig.psiutils.*; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.language.editor.inspection.ProblemDescriptor; +import consulo.language.psi.PsiElement; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; +import consulo.project.Project; +import org.jspecify.annotations.Nullable; /** * @author Bas Leijdekkers */ -public class EqualsToEqualityFix extends InspectionGadgetsFix -{ - - private final boolean myNegated; +public class EqualsToEqualityFix extends InspectionGadgetsFix { + private final boolean myNegated; - private EqualsToEqualityFix(boolean negated) - { - myNegated = negated; - } + private EqualsToEqualityFix(boolean negated) { + myNegated = negated; + } - @Nullable - public static EqualsToEqualityFix buildFix(PsiMethodCallExpression expressionToFix, boolean negated) - { - if(ExpressionUtils.isVoidContext(expressionToFix)) - { - // replacing top level equals() call will produce red code - return null; - } - return new EqualsToEqualityFix(negated); - } + @Nullable + public static EqualsToEqualityFix buildFix(PsiMethodCallExpression expressionToFix, boolean negated) { + if (ExpressionUtils.isVoidContext(expressionToFix)) { + // replacing top level equals() call will produce red code + return null; + } + return new EqualsToEqualityFix(negated); + } - @Nls - @Nonnull - @Override - public String getFamilyName() - { - return myNegated - ? InspectionGadgetsBundle.message("not.equals.to.equality.quickfix") - : InspectionGadgetsBundle.message("equals.to.equality.quickfix"); - } + @Override + public LocalizeValue getName() { + return myNegated + ? InspectionGadgetsLocalize.notEqualsToEqualityQuickfix() + : InspectionGadgetsLocalize.equalsToEqualityQuickfix(); + } - @Override - protected void doFix(Project project, ProblemDescriptor descriptor) - { - final PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class, false); - EqualityCheck check = EqualityCheck.from(call); - if(check == null) - return; - PsiExpression lhs = check.getLeft(); - PsiExpression rhs = check.getRight(); - final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(call); - final CommentTracker commentTracker = new CommentTracker(); - final String lhsText = commentTracker.text(lhs, ParenthesesUtils.EQUALITY_PRECEDENCE); - final String rhsText = commentTracker.text(rhs, ParenthesesUtils.EQUALITY_PRECEDENCE); - if(parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression) parent)) - { - PsiReplacementUtil.replaceExpression((PsiExpression) parent, lhsText + "!=" + rhsText, commentTracker); - } - else - { - PsiReplacementUtil.replaceExpression(call, lhsText + "==" + rhsText, commentTracker); - } - } + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class, false); + EqualityCheck check = EqualityCheck.from(call); + if (check == null) { + return; + } + PsiExpression lhs = check.getLeft(); + PsiExpression rhs = check.getRight(); + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(call); + final CommentTracker commentTracker = new CommentTracker(); + final String lhsText = commentTracker.text(lhs, ParenthesesUtils.EQUALITY_PRECEDENCE); + final String rhsText = commentTracker.text(rhs, ParenthesesUtils.EQUALITY_PRECEDENCE); + if (parent instanceof PsiExpression && BoolUtils.isNegation((PsiExpression) parent)) { + PsiReplacementUtil.replaceExpression((PsiExpression) parent, lhsText + "!=" + rhsText, commentTracker); + } + else { + PsiReplacementUtil.replaceExpression(call, lhsText + "==" + rhsText, commentTracker); + } + } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/inheritance/ExtendsConcreteCollectionInspectionBase.java b/java-analysis-impl/src/main/java/com/siyeh/ig/inheritance/ExtendsConcreteCollectionInspectionBase.java index a59f9418a9..9381c91e3a 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/inheritance/ExtendsConcreteCollectionInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/inheritance/ExtendsConcreteCollectionInspectionBase.java @@ -16,37 +16,31 @@ package com.siyeh.ig.inheritance; import com.intellij.java.language.psi.*; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.CollectionUtils; import com.siyeh.ig.psiutils.TypeUtils; - -import javax.annotation.Nonnull; +import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.localize.LocalizeValue; public abstract class ExtendsConcreteCollectionInspectionBase extends BaseInspection { @Override - @Nonnull public String getID() { return "ClassExtendsConcreteCollection"; } @Override - @Nonnull - public String getDisplayName() { - return InspectionGadgetsBundle.message("extends.concrete.collection.display.name"); + public LocalizeValue getDisplayName() { + return InspectionGadgetsLocalize.extendsConcreteCollectionDisplayName(); } @Override - @Nonnull public String buildErrorString(Object... infos) { final PsiClass superClass = (PsiClass) infos[0]; final PsiClass aClass = (PsiClass) infos[1]; - if (aClass instanceof PsiAnonymousClass) { - return InspectionGadgetsBundle.message("anonymous.extends.concrete.collection.problem.descriptor", superClass.getQualifiedName()); - } else { - return InspectionGadgetsBundle.message("extends.concrete.collection.problem.descriptor", superClass.getQualifiedName()); - } + return aClass instanceof PsiAnonymousClass + ? InspectionGadgetsLocalize.anonymousExtendsConcreteCollectionProblemDescriptor(superClass.getQualifiedName()).get() + : InspectionGadgetsLocalize.extendsConcreteCollectionProblemDescriptor(superClass.getQualifiedName()).get(); } @Override @@ -55,9 +49,8 @@ public BaseInspectionVisitor buildVisitor() { } private static class ExtendsConcreteCollectionVisitor extends BaseInspectionVisitor { - @Override - public void visitClass(@Nonnull PsiClass aClass) { + public void visitClass(PsiClass aClass) { if (aClass.isInterface() || aClass.isAnnotationType() || aClass.isEnum()) { return; } @@ -68,7 +61,7 @@ public void visitClass(@Nonnull PsiClass aClass) { final String qualifiedName = superClass.getQualifiedName(); if ("java.util.LinkedHashMap".equals(qualifiedName)) { final PsiMethod[] methods = aClass.findMethodsByName("removeEldestEntry", false); - final PsiClassType entryType = TypeUtils.getType("java.util.Map.Entry", aClass); + final PsiClassType entryType = TypeUtils.getType(CommonClassNames.JAVA_UTIL_MAP_ENTRY, aClass); for (PsiMethod method : methods) { if (!PsiType.BOOLEAN.equals(method.getReturnType())) { continue; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/memory/ZeroLengthArrayInitializationInspectionBase.java b/java-analysis-impl/src/main/java/com/siyeh/ig/memory/ZeroLengthArrayInitializationInspectionBase.java index 0993a0490f..b476e20178 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/memory/ZeroLengthArrayInitializationInspectionBase.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/memory/ZeroLengthArrayInitializationInspectionBase.java @@ -18,33 +18,29 @@ import com.intellij.java.language.psi.PsiArrayInitializerExpression; import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiNewExpression; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.ConstructionUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.localize.InspectionGadgetsLocalize; +import consulo.localize.LocalizeValue; import org.intellij.lang.annotations.Pattern; -import javax.annotation.Nonnull; - public abstract class ZeroLengthArrayInitializationInspectionBase extends BaseInspection { @Pattern(VALID_ID_PATTERN) @Override - @Nonnull public String getID() { return "ZeroLengthArrayAllocation"; } @Override - @Nonnull - public String getDisplayName() { - return InspectionGadgetsBundle.message("array.allocation.zero.length.display.name"); + public LocalizeValue getDisplayName() { + return InspectionGadgetsLocalize.arrayAllocationZeroLengthDisplayName(); } @Override - @Nonnull public String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("array.allocation.zero.length.problem.descriptor"); + return InspectionGadgetsLocalize.arrayAllocationZeroLengthProblemDescriptor().get(); } @Override @@ -60,7 +56,7 @@ protected boolean buildQuickFixesOnlyForOnTheFlyErrors() { private static class ZeroLengthArrayInitializationVisitor extends BaseInspectionVisitor { @Override - public void visitNewExpression(@Nonnull PsiNewExpression expression) { + public void visitNewExpression(PsiNewExpression expression) { super.visitNewExpression(expression); if (!ConstructionUtils.isEmptyArrayInitializer(expression)) { return; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/numeric/UnnecessaryExplicitNumericCastInspection.java b/java-analysis-impl/src/main/java/com/siyeh/ig/numeric/UnnecessaryExplicitNumericCastInspection.java index ca10a20f58..cdd23b9e5d 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/numeric/UnnecessaryExplicitNumericCastInspection.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/numeric/UnnecessaryExplicitNumericCastInspection.java @@ -16,22 +16,21 @@ package com.siyeh.ig.numeric; import com.intellij.java.language.psi.*; -import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.ExpectedTypeUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.localize.InspectionGadgetsLocalize; import consulo.language.ast.IElementType; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.inspection.ProblemHighlightType; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; -import org.jetbrains.annotations.Nls; -import javax.annotation.Nonnull; import java.util.HashSet; import java.util.Set; @@ -56,18 +55,15 @@ public abstract class UnnecessaryExplicitNumericCastInspection extends BaseInspe binaryPromotionOperators.add(JavaTokenType.OR); } - @Nls - @Nonnull @Override - public String getDisplayName() { - return InspectionGadgetsBundle.message("unnecessary.explicit.numeric.cast.display.name"); + public LocalizeValue getDisplayName() { + return InspectionGadgetsLocalize.unnecessaryExplicitNumericCastDisplayName(); } - @Nonnull @Override protected String buildErrorString(Object... infos) { final PsiExpression expression = (PsiExpression) infos[0]; - return InspectionGadgetsBundle.message("unnecessary.explicit.numeric.cast.problem.descriptor", expression.getText()); + return InspectionGadgetsLocalize.unnecessaryExplicitNumericCastProblemDescriptor(expression.getText()).get(); } @Override @@ -77,10 +73,9 @@ protected InspectionGadgetsFix buildFix(Object... infos) { private static class UnnecessaryExplicitNumericCastFix extends InspectionGadgetsFix { - @Nonnull @Override - public String getName() { - return InspectionGadgetsBundle.message("unnecessary.explicit.numeric.cast.quickfix"); + public LocalizeValue getName() { + return InspectionGadgetsLocalize.unnecessaryExplicitNumericCastQuickfix(); } @Override diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAccessedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAccessedVisitor.java index 230d2386af..e7a0532172 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAccessedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAccessedVisitor.java @@ -18,22 +18,19 @@ import com.intellij.java.language.psi.*; import com.siyeh.HardcodedMethodConstants; import consulo.language.psi.PsiElement; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; class ArrayContentsAccessedVisitor extends JavaRecursiveElementVisitor { private boolean accessed = false; private final PsiVariable variable; - public ArrayContentsAccessedVisitor(@Nonnull PsiVariable variable) { + public ArrayContentsAccessedVisitor(PsiVariable variable) { this.variable = variable; } @Override public void visitForeachStatement( - @Nonnull PsiForeachStatement statement) { + PsiForeachStatement statement) { if (accessed) { return; } @@ -85,7 +82,7 @@ public void visitReferenceExpression(PsiReferenceExpression expression) { return; } super.visitReferenceExpression(expression); - @NonNls final String referenceName = expression.getReferenceName(); + final String referenceName = expression.getReferenceName(); if (!"length".equals(referenceName)) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAssignedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAssignedVisitor.java index d72be2c658..ee29fff257 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAssignedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ArrayContentsAssignedVisitor.java @@ -19,21 +19,20 @@ import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; class ArrayContentsAssignedVisitor extends JavaRecursiveElementVisitor { private boolean assigned = false; private final PsiVariable variable; - public ArrayContentsAssignedVisitor(@Nonnull PsiVariable variable) { + public ArrayContentsAssignedVisitor(PsiVariable variable) { this.variable = variable; } @Override public void visitAssignmentExpression( - @Nonnull PsiAssignmentExpression assignment) { + PsiAssignmentExpression assignment) { if (assigned) { return; } @@ -56,7 +55,7 @@ public void visitAssignmentExpression( @Override public void visitPrefixExpression( - @Nonnull PsiPrefixExpression expression) { + PsiPrefixExpression expression) { if (assigned) { return; } @@ -84,7 +83,7 @@ public void visitPrefixExpression( @Override public void visitPostfixExpression( - @Nonnull PsiPostfixExpression expression) { + PsiPostfixExpression expression) { if (assigned) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BoolUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BoolUtils.java index e5e9f5d50f..b05ffb2f29 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BoolUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BoolUtils.java @@ -15,22 +15,21 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.psi.*; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.util.lang.StringUtil; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.function.Function; public class BoolUtils { private BoolUtils() { } - public static boolean isNegation(@Nonnull PsiExpression expression) { + public static boolean isNegation(PsiExpression expression) { if (!(expression instanceof PsiPrefixExpression)) { return false; } @@ -63,12 +62,10 @@ public static PsiExpression getNegated(PsiExpression expression) { return ParenthesesUtils.stripParentheses(operand); } - @Nonnull public static String getNegatedExpressionText(@Nullable PsiExpression condition) { return getNegatedExpressionText(condition, ParenthesesUtils.NUM_PRECEDENCES); } - @Nonnull public static String getNegatedExpressionText(@Nullable PsiExpression expression, int precedence) { if (expression == null) { return ""; @@ -160,7 +157,7 @@ public static boolean isBooleanLiteral(PsiExpression expression) { return false; } final PsiLiteralExpression literalExpression = (PsiLiteralExpression) expression; - @NonNls final String text = literalExpression.getText(); + final String text = literalExpression.getText(); return PsiKeyword.TRUE.equals(text) || PsiKeyword.FALSE.equals(text); } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BreakConverter.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BreakConverter.java index 084516a1a9..37e28d9bde 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BreakConverter.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/BreakConverter.java @@ -1,13 +1,13 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; import com.intellij.java.language.psi.*; import com.siyeh.ig.fixes.DeleteUnnecessaryStatementFix; import consulo.language.psi.PsiElement; import consulo.language.psi.util.PsiTreeUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -36,7 +36,6 @@ public void process() { } } - @Nonnull private List collectBreaks() { List breaks = new ArrayList<>(); mySwitchBlock.accept(new JavaRecursiveElementWalkingVisitor() { diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ClassUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ClassUtils.java index aa583ae56c..60bdc00ae8 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ClassUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ClassUtils.java @@ -19,14 +19,12 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; @@ -62,15 +60,15 @@ public class ClassUtils { primitiveNumericTypes.add(PsiType.FLOAT); primitiveNumericTypes.add(PsiType.DOUBLE); - immutableTypes.add(JavaClassNames.JAVA_LANG_BOOLEAN); - immutableTypes.add(JavaClassNames.JAVA_LANG_CHARACTER); - immutableTypes.add(JavaClassNames.JAVA_LANG_SHORT); - immutableTypes.add(JavaClassNames.JAVA_LANG_INTEGER); - immutableTypes.add(JavaClassNames.JAVA_LANG_LONG); - immutableTypes.add(JavaClassNames.JAVA_LANG_FLOAT); - immutableTypes.add(JavaClassNames.JAVA_LANG_DOUBLE); - immutableTypes.add(JavaClassNames.JAVA_LANG_BYTE); - immutableTypes.add(JavaClassNames.JAVA_LANG_STRING); + immutableTypes.add(CommonClassNames.JAVA_LANG_BOOLEAN); + immutableTypes.add(CommonClassNames.JAVA_LANG_CHARACTER); + immutableTypes.add(CommonClassNames.JAVA_LANG_SHORT); + immutableTypes.add(CommonClassNames.JAVA_LANG_INTEGER); + immutableTypes.add(CommonClassNames.JAVA_LANG_LONG); + immutableTypes.add(CommonClassNames.JAVA_LANG_FLOAT); + immutableTypes.add(CommonClassNames.JAVA_LANG_DOUBLE); + immutableTypes.add(CommonClassNames.JAVA_LANG_BYTE); + immutableTypes.add(CommonClassNames.JAVA_LANG_STRING); immutableTypes.add("java.awt.Font"); immutableTypes.add("java.awt.Color"); immutableTypes.add("java.math.BigDecimal"); @@ -78,7 +76,7 @@ public class ClassUtils { immutableTypes.add("java.math.MathContext"); immutableTypes.add("java.nio.channels.FileLock"); immutableTypes.add("java.nio.charset.Charset"); - immutableTypes.add("java.io.File"); + immutableTypes.add(CommonClassNames.JAVA_IO_FILE); immutableTypes.add("java.net.URI"); immutableTypes.add("java.util.regex.Pattern"); } @@ -93,7 +91,7 @@ public static PsiClass findClass(String fqClassName, PsiElement context) { @Nullable public static PsiClass findObjectClass(PsiElement context) { - return findClass(JavaClassNames.JAVA_LANG_OBJECT, context); + return findClass(CommonClassNames.JAVA_LANG_OBJECT, context); } public static boolean isPrimitive(PsiType type) { @@ -138,7 +136,7 @@ public static boolean isImmutable(@Nullable PsiType type, boolean checkDocCommen return JCiPUtil.isImmutable(aClass, checkDocComment); } - public static boolean isImmutableClass(@Nonnull PsiClass aClass) { + public static boolean isImmutableClass(PsiClass aClass) { String qualifiedName = aClass.getQualifiedName(); return qualifiedName != null && (immutableTypes.contains(qualifiedName) || qualifiedName.startsWith("com.google.common.collect.Immutable")); diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CloneUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CloneUtils.java index 97ca2f2c86..de52408c41 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CloneUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CloneUtils.java @@ -15,38 +15,34 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; - import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.HardcodedMethodConstants; -import consulo.java.language.module.util.JavaClassNames; public class CloneUtils { private CloneUtils() {} - public static boolean isCloneable(@Nonnull PsiClass aClass) { - return InheritanceUtil.isInheritor(aClass, - JavaClassNames.JAVA_LANG_CLONEABLE); + public static boolean isCloneable(PsiClass aClass) { + return InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_LANG_CLONEABLE); } - public static boolean isDirectlyCloneable(@Nonnull PsiClass aClass) { + public static boolean isDirectlyCloneable(PsiClass aClass) { final PsiClass[] interfaces = aClass.getInterfaces(); for (PsiClass anInterface : interfaces) { if (anInterface == null) { continue; } final String qualifiedName = anInterface.getQualifiedName(); - if (JavaClassNames.JAVA_LANG_CLONEABLE.equals(qualifiedName)) { + if (CommonClassNames.JAVA_LANG_CLONEABLE.equals(qualifiedName)) { return true; } } return false; } - public static boolean isClone(@Nonnull PsiMethod method) { + public static boolean isClone(PsiMethod method) { final PsiClassType javaLangObject; if (!PsiUtil.isLanguageLevel5OrHigher(method)) { javaLangObject = TypeUtils.getObjectType(method); @@ -60,7 +56,7 @@ public static boolean isClone(@Nonnull PsiMethod method) { } public static boolean onlyThrowsCloneNotSupportedException( - @Nonnull PsiMethod method) { + PsiMethod method) { final PsiCodeBlock body = method.getBody(); if (body == null) { return false; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java index 4f41ca04d9..08cc76f76b 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CollectionUtils.java @@ -18,262 +18,246 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.siyeh.ig.callMatcher.CallMatcher; -import consulo.java.language.module.util.JavaClassNames; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; public class CollectionUtils { + /** + * @noinspection StaticCollection + */ + private static final Set s_allCollectionClassesAndInterfaces; + /** + * @noinspection StaticCollection + */ + private static final Map s_interfaceForCollection = new HashMap<>(); - /** - * @noinspection StaticCollection - */ - @NonNls - private static final Set s_allCollectionClassesAndInterfaces; - /** - * @noinspection StaticCollection - */ - @NonNls - private static final Map s_interfaceForCollection = new HashMap<>(); - - static { - final Set allCollectionClassesAndInterfaces = new HashSet<>(); - allCollectionClassesAndInterfaces.add("java.util.AbstractCollection"); - allCollectionClassesAndInterfaces.add("java.util.AbstractList"); - allCollectionClassesAndInterfaces.add("java.util.AbstractMap"); - allCollectionClassesAndInterfaces.add("java.util.AbstractQueue"); - allCollectionClassesAndInterfaces.add("java.util.AbstractSequentialList"); - allCollectionClassesAndInterfaces.add("java.util.AbstractSet"); - allCollectionClassesAndInterfaces.add("java.util.ArrayList"); - allCollectionClassesAndInterfaces.add("java.util.ArrayDeque"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_COLLECTION); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_DICTIONARY); - allCollectionClassesAndInterfaces.add("java.util.EnumMap"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_MAP); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_HASH_SET); - allCollectionClassesAndInterfaces.add("java.util.Hashtable"); - allCollectionClassesAndInterfaces.add("java.util.IdentityHashMap"); - allCollectionClassesAndInterfaces.add("java.util.LinkedHashMap"); - allCollectionClassesAndInterfaces.add("java.util.LinkedHashSet"); - allCollectionClassesAndInterfaces.add("java.util.LinkedList"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_LIST); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_MAP); - allCollectionClassesAndInterfaces.add("java.util.PriorityQueue"); - allCollectionClassesAndInterfaces.add("java.util.Queue"); - allCollectionClassesAndInterfaces.add(JavaClassNames.JAVA_UTIL_SET); - allCollectionClassesAndInterfaces.add("java.util.SortedMap"); - allCollectionClassesAndInterfaces.add("java.util.SortedSet"); - allCollectionClassesAndInterfaces.add("java.util.Stack"); - allCollectionClassesAndInterfaces.add("java.util.TreeMap"); - allCollectionClassesAndInterfaces.add("java.util.TreeSet"); - allCollectionClassesAndInterfaces.add("java.util.Vector"); - allCollectionClassesAndInterfaces.add("java.util.WeakHashMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ArrayBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentHashMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentNavigableMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListMap"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListSet"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArrayList"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArraySet"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.DelayQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingDeque"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedTransferQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.PriorityBlockingQueue"); - allCollectionClassesAndInterfaces.add("java.util.concurrent.SynchronousQueue"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.ArrayList"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Collection"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Hashtable"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.LinkedList"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.List"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Map"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Set"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeMap"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeSet"); - allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Vector"); - s_allCollectionClassesAndInterfaces = Collections.unmodifiableSet(allCollectionClassesAndInterfaces); - - s_interfaceForCollection.put("ArrayList", "List"); - s_interfaceForCollection.put("EnumMap", "Map"); - s_interfaceForCollection.put("EnumSet", "Set"); - s_interfaceForCollection.put("HashMap", "Map"); - s_interfaceForCollection.put("HashSet", "Set"); - s_interfaceForCollection.put("Hashtable", "Map"); - s_interfaceForCollection.put("IdentityHashMap", "Map"); - s_interfaceForCollection.put("LinkedHashMap", "Map"); - s_interfaceForCollection.put("LinkedHashSet", "Set"); - s_interfaceForCollection.put("LinkedList", "List"); - s_interfaceForCollection.put("PriorityQueue", "Queue"); - s_interfaceForCollection.put("TreeMap", "Map"); - s_interfaceForCollection.put("TreeSet", "SortedSet"); - s_interfaceForCollection.put("Vector", "List"); - s_interfaceForCollection.put("WeakHashMap", "Map"); - s_interfaceForCollection.put("java.util.ArrayList", JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.EnumMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.EnumSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.HashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.HashSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.Hashtable", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.IdentityHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.LinkedHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.LinkedHashSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.LinkedList", JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.PriorityQueue", "java.util.Queue"); - s_interfaceForCollection.put("java.util.TreeMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("java.util.TreeSet", JavaClassNames.JAVA_UTIL_SET); - s_interfaceForCollection.put("java.util.Vector", JavaClassNames.JAVA_UTIL_LIST); - s_interfaceForCollection.put("java.util.WeakHashMap", JavaClassNames.JAVA_UTIL_MAP); - s_interfaceForCollection.put("com.sun.java.util.collections.HashSet", "com.sun.java.util.collections.Set"); - s_interfaceForCollection.put("com.sun.java.util.collections.TreeSet", "com.sun.java.util.collections.Set"); - s_interfaceForCollection.put("com.sun.java.util.collections.Vector", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.ArrayList", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.LinkedList", "com.sun.java.util.collections.List"); - s_interfaceForCollection.put("com.sun.java.util.collections.TreeMap", "com.sun.java.util.collections.Map"); - s_interfaceForCollection.put("com.sun.java.util.collections.HashMap", "com.sun.java.util.collections.Map"); - s_interfaceForCollection.put("com.sun.java.util.collections.Hashtable", "com.sun.java.util.collections.Map"); - } - - /** - * Matches a call which creates collection of the same size as the qualifier collection - */ - public static final CallMatcher DERIVED_COLLECTION = - CallMatcher.anyOf( - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "keySet", "values", "entrySet").parameterCount(0), - CallMatcher.instanceCall("java.util.NavigableMap", "descendingKeySet", "descendingMap", "navigableKeySet").parameterCount(0), - CallMatcher.instanceCall("java.util.NavigableSet", "descendingSet").parameterCount(0) - ); + static { + Set allCollectionClassesAndInterfaces = new HashSet<>(); + allCollectionClassesAndInterfaces.add("java.util.AbstractCollection"); + allCollectionClassesAndInterfaces.add("java.util.AbstractList"); + allCollectionClassesAndInterfaces.add("java.util.AbstractMap"); + allCollectionClassesAndInterfaces.add("java.util.AbstractQueue"); + allCollectionClassesAndInterfaces.add("java.util.AbstractSequentialList"); + allCollectionClassesAndInterfaces.add("java.util.AbstractSet"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_ARRAY_LIST); + allCollectionClassesAndInterfaces.add("java.util.ArrayDeque"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_COLLECTION); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_DICTIONARY); + allCollectionClassesAndInterfaces.add("java.util.EnumMap"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_HASH_MAP); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_HASH_SET); + allCollectionClassesAndInterfaces.add("java.util.Hashtable"); + allCollectionClassesAndInterfaces.add("java.util.IdentityHashMap"); + allCollectionClassesAndInterfaces.add("java.util.LinkedHashMap"); + allCollectionClassesAndInterfaces.add("java.util.LinkedHashSet"); + allCollectionClassesAndInterfaces.add("java.util.LinkedList"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_LIST); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_MAP); + allCollectionClassesAndInterfaces.add("java.util.PriorityQueue"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_QUEUE); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_SET); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_SORTED_MAP); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_SORTED_SET); + allCollectionClassesAndInterfaces.add("java.util.Stack"); + allCollectionClassesAndInterfaces.add("java.util.TreeMap"); + allCollectionClassesAndInterfaces.add("java.util.TreeSet"); + allCollectionClassesAndInterfaces.add("java.util.Vector"); + allCollectionClassesAndInterfaces.add("java.util.WeakHashMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ArrayBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.BlockingQueue"); + allCollectionClassesAndInterfaces.add(CommonClassNames.JAVA_UTIL_CONCURRENT_HASH_MAP); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentLinkedQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentNavigableMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListMap"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.ConcurrentSkipListSet"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArrayList"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.CopyOnWriteArraySet"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.DelayQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingDeque"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.LinkedTransferQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.PriorityBlockingQueue"); + allCollectionClassesAndInterfaces.add("java.util.concurrent.SynchronousQueue"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.ArrayList"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Collection"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.HashSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Hashtable"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.LinkedList"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.List"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Map"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Set"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.SortedSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeMap"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.TreeSet"); + allCollectionClassesAndInterfaces.add("com.sun.java.util.collections.Vector"); + s_allCollectionClassesAndInterfaces = Collections.unmodifiableSet(allCollectionClassesAndInterfaces); + s_interfaceForCollection.put("ArrayList", "List"); + s_interfaceForCollection.put("EnumMap", "Map"); + s_interfaceForCollection.put("EnumSet", "Set"); + s_interfaceForCollection.put("HashMap", "Map"); + s_interfaceForCollection.put("HashSet", "Set"); + s_interfaceForCollection.put("Hashtable", "Map"); + s_interfaceForCollection.put("IdentityHashMap", "Map"); + s_interfaceForCollection.put("LinkedHashMap", "Map"); + s_interfaceForCollection.put("LinkedHashSet", "Set"); + s_interfaceForCollection.put("LinkedList", "List"); + s_interfaceForCollection.put("PriorityQueue", "Queue"); + s_interfaceForCollection.put("TreeMap", "Map"); + s_interfaceForCollection.put("TreeSet", "SortedSet"); + s_interfaceForCollection.put("Vector", "List"); + s_interfaceForCollection.put("WeakHashMap", "Map"); + s_interfaceForCollection.put(CommonClassNames.JAVA_UTIL_ARRAY_LIST, CommonClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.EnumMap", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put(CommonClassNames.JAVA_UTIL_ENUM_SET, CommonClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put(CommonClassNames.JAVA_UTIL_HASH_MAP, CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put(CommonClassNames.JAVA_UTIL_HASH_SET, CommonClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.Hashtable", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.IdentityHashMap", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.LinkedHashMap", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.LinkedHashSet", CommonClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.LinkedList", CommonClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.PriorityQueue", CommonClassNames.JAVA_UTIL_QUEUE); + s_interfaceForCollection.put("java.util.TreeMap", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("java.util.TreeSet", CommonClassNames.JAVA_UTIL_SET); + s_interfaceForCollection.put("java.util.Vector", CommonClassNames.JAVA_UTIL_LIST); + s_interfaceForCollection.put("java.util.WeakHashMap", CommonClassNames.JAVA_UTIL_MAP); + s_interfaceForCollection.put("com.sun.java.util.collections.HashSet", "com.sun.java.util.collections.Set"); + s_interfaceForCollection.put("com.sun.java.util.collections.TreeSet", "com.sun.java.util.collections.Set"); + s_interfaceForCollection.put("com.sun.java.util.collections.Vector", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.ArrayList", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.LinkedList", "com.sun.java.util.collections.List"); + s_interfaceForCollection.put("com.sun.java.util.collections.TreeMap", "com.sun.java.util.collections.Map"); + s_interfaceForCollection.put("com.sun.java.util.collections.HashMap", "com.sun.java.util.collections.Map"); + s_interfaceForCollection.put("com.sun.java.util.collections.Hashtable", "com.sun.java.util.collections.Map"); + } - private CollectionUtils() { - super(); - } + /** + * Matches a call which creates collection of the same size as the qualifier collection + */ + public static final CallMatcher DERIVED_COLLECTION = CallMatcher.anyOf( + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "keySet", "values", "entrySet").parameterCount(0), + CallMatcher.instanceCall("java.util.NavigableMap", "descendingKeySet", "descendingMap", "navigableKeySet") + .parameterCount(0), + CallMatcher.instanceCall("java.util.NavigableSet", "descendingSet").parameterCount(0) + ); - public static Set getAllCollectionNames() { - return s_allCollectionClassesAndInterfaces; - } - @Contract("null -> false") - public static boolean isConcreteCollectionClass(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; - } - final PsiClassType classType = (PsiClassType) type; - final PsiClass resolved = classType.resolve(); - if (resolved == null) { - return false; + private CollectionUtils() { + super(); } - return isConcreteCollectionClass(resolved); - } - @Contract("null -> false") - public static boolean isConcreteCollectionClass(PsiClass aClass) { - if (aClass == null || aClass.isEnum() || aClass.isInterface() || aClass.isAnnotationType() || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) { - return false; - } - if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) && !InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_MAP)) { - return false; + public static Set getAllCollectionNames() { + return s_allCollectionClassesAndInterfaces; } - @NonNls final String name = aClass.getQualifiedName(); - return name != null && name.startsWith("java.util."); - } - public static boolean isCollectionClassOrInterface(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; + @Contract("null -> false") + public static boolean isConcreteCollectionClass(@Nullable PsiType type) { + if (!(type instanceof PsiClassType classType)) { + return false; + } + PsiClass resolved = classType.resolve(); + if (resolved == null) { + return false; + } + return isConcreteCollectionClass(resolved); } - final PsiClassType classType = (PsiClassType) type; - final PsiClass resolved = classType.resolve(); - if (resolved == null) { - return false; - } - return InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_COLLECTION) || - InheritanceUtil.isInheritor(resolved, JavaClassNames.JAVA_UTIL_MAP) || - InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Multimap") || - InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Table"); - } - - public static boolean isCollectionClassOrInterface(PsiClass aClass) { - return isCollectionClassOrInterface(aClass, new HashSet<>()); - } - /** - * alreadyChecked set to avoid infinite loop in constructs like: - * class C extends C {} - */ - private static boolean isCollectionClassOrInterface(PsiClass aClass, Set visitedClasses) { - if (!visitedClasses.add(aClass)) { - return false; + @Contract("null -> false") + public static boolean isConcreteCollectionClass(PsiClass aClass) { + if (aClass == null || aClass.isEnum() || aClass.isInterface() || aClass.isAnnotationType() || aClass.isAbstract()) { + return false; + } + if (!InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_COLLECTION) + && !InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_MAP)) { + return false; + } + String name = aClass.getQualifiedName(); + return name != null && name.startsWith("java.util."); } - final String className = aClass.getQualifiedName(); - if (s_allCollectionClassesAndInterfaces.contains(className)) { - return true; + + public static boolean isCollectionClassOrInterface(@Nullable PsiType type) { + if (!(type instanceof PsiClassType classType)) { + return false; + } + PsiClass resolved = classType.resolve(); + if (resolved == null) { + return false; + } + return InheritanceUtil.isInheritor(resolved, CommonClassNames.JAVA_UTIL_COLLECTION) + || InheritanceUtil.isInheritor(resolved, CommonClassNames.JAVA_UTIL_MAP) + || InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Multimap") + || InheritanceUtil.isInheritor(resolved, "com.google.common.collect.Table"); } - final PsiClass[] supers = aClass.getSupers(); - for (PsiClass aSuper : supers) { - if (isCollectionClassOrInterface(aSuper, visitedClasses)) { - return true; - } + + public static boolean isCollectionClassOrInterface(PsiClass aClass) { + return isCollectionClassOrInterface(aClass, new HashSet<>()); } - return false; - } - public static boolean isWeakCollectionClass(@Nullable PsiType type) { - if (!(type instanceof PsiClassType)) { - return false; + /** + * alreadyChecked set to avoid infinite loop in constructs like: + * class C extends C {} + */ + private static boolean isCollectionClassOrInterface(PsiClass aClass, Set visitedClasses) { + if (!visitedClasses.add(aClass)) { + return false; + } + String className = aClass.getQualifiedName(); + if (s_allCollectionClassesAndInterfaces.contains(className)) { + return true; + } + PsiClass[] supers = aClass.getSupers(); + for (PsiClass aSuper : supers) { + if (isCollectionClassOrInterface(aSuper, visitedClasses)) { + return true; + } + } + return false; } - final String typeText = type.getCanonicalText(); - return "java.util.WeakHashMap".equals(typeText); - } - public static boolean isConstantEmptyArray(@Nonnull PsiField field) { - if (!field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) { - return false; + public static boolean isWeakCollectionClass(@Nullable PsiType type) { + if (!(type instanceof PsiClassType)) { + return false; + } + String typeText = type.getCanonicalText(); + return "java.util.WeakHashMap".equals(typeText); } - return isEmptyArray(field); - } - public static boolean isEmptyArray(PsiVariable variable) { - final PsiExpression initializer = variable.getInitializer(); - if (initializer instanceof PsiArrayInitializerExpression) { - final PsiArrayInitializerExpression arrayInitializerExpression = (PsiArrayInitializerExpression) initializer; - final PsiExpression[] initializers = arrayInitializerExpression.getInitializers(); - return initializers.length == 0; + public static boolean isConstantEmptyArray(PsiField field) { + return field.isStatic() && field.isFinal() && isEmptyArray(field); } - return ConstructionUtils.isEmptyArrayInitializer(initializer); - } - public static boolean isArrayOrCollectionField(@Nonnull PsiField field) { - final PsiType type = field.getType(); - if (isCollectionClassOrInterface(type)) { - return true; + public static boolean isEmptyArray(PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + if (initializer instanceof PsiArrayInitializerExpression arrayInitializerExpr) { + PsiExpression[] initializers = arrayInitializerExpr.getInitializers(); + return initializers.length == 0; + } + return ConstructionUtils.isEmptyArrayInitializer(initializer); } - if (!(type instanceof PsiArrayType)) { - return false; + + public static boolean isArrayOrCollectionField(PsiField field) { + PsiType type = field.getType(); + if (isCollectionClassOrInterface(type)) { + return true; + } + if (!(type instanceof PsiArrayType)) { + return false; + } + // constant empty arrays are ignored. + return !isConstantEmptyArray(field); } - // constant empty arrays are ignored. - return !isConstantEmptyArray(field); - } - public static String getInterfaceForClass(String name) { - final int parameterStart = name.indexOf((int) '<'); - final String baseName; - if (parameterStart >= 0) { - baseName = name.substring(0, parameterStart).trim(); - } else { - baseName = name; + public static String getInterfaceForClass(String name) { + int parameterStart = name.indexOf((int)'<'); + String baseName = parameterStart >= 0 ? name.substring(0, parameterStart).trim() : name; + return s_interfaceForCollection.get(baseName); } - return s_interfaceForCollection.get(baseName); - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java index 8cb332d252..39eb588c82 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CommentTracker.java @@ -1,9 +1,12 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.impl.psi.impl.source.tree.ChildRole; import com.intellij.java.language.psi.PsiElementFactory; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.document.util.TextRange; import consulo.language.ast.ASTNode; import consulo.language.impl.ast.ASTFactory; @@ -12,11 +15,11 @@ import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.collection.SmartList; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; + import java.util.*; import java.util.function.Function; import java.util.function.Predicate; @@ -29,582 +32,601 @@ * @author Tagir Valeev */ public final class CommentTracker { - private final Set ignoredParents = new HashSet<>(); - private List comments = new ArrayList<>(); - private PsiElement lastTextWithCommentsElement = null; - - /** - * Marks the element as unchanged and returns its text. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param element element to return the text - * @return a text to be inserted into refactored code - */ - public - @Nonnull - String text(@Nonnull PsiElement element) { - checkState(); - addIgnored(element); - return element.getText(); - } - - /** - * Marks the expression as unchanged and returns its text, adding parentheses if necessary. - * The unchanged elements are assumed to be preserved in the resulting code as is, - * so the comments from them will not be extracted. - * - * @param element expression to return the text - * @param precedence precedence of surrounding operation - * @return a text to be inserted into refactored code - * @see ParenthesesUtils#getText(PsiExpression, int) - */ - public - @Nonnull - String text(@Nonnull PsiExpression element, int precedence) { - checkState(); - addIgnored(element); - return ParenthesesUtils.getText(element, precedence + 1); - } - - /** - * Marks the expression as unchanged and returns a single-parameter lambda text which parameter - * is the name of supplied variable and body is the supplied expression - * - * @param variable a variable to use as lambda parameter - * @param expression an expression to use as lambda body - * @return a string representation of lambda - */ - public - @Nonnull - String lambdaText(@Nonnull PsiVariable variable, @Nonnull PsiExpression expression) { - return variable.getName() + " -> " + text(expression); - } - - /** - * Marks the element as unchanged and returns it. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param element element to mark - * @param the type of the element - * @return the passed argument - */ - @Contract("_ -> param1") - public T markUnchanged(@Nullable T element) { - checkState(); - if (element != null) - addIgnored(element); - return element; - } - - /** - * Marks the range of elements as unchanged and returns their text. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param firstElement first element to mark - * @param lastElement last element to mark (must be equal to firstElement or its sibling) - * @return a text to be inserted into refactored code - * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element - */ - public String rangeText(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { - checkState(); - PsiElement e; - StringBuilder result = new StringBuilder(); - for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { - addIgnored(e); - result.append(e.getText()); - } - if (e == null) { - throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); - } - addIgnored(lastElement); - result.append(lastElement.getText()); - return result.toString(); - } - - /** - * Marks the range of elements as unchanged. The unchanged elements are assumed to be preserved - * in the resulting code as is, so the comments from them will not be extracted. - * - * @param firstElement first element to mark - * @param lastElement last element to mark (must be equal to firstElement or its sibling) - * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element - */ - public void markRangeUnchanged(@Nonnull PsiElement firstElement, @Nonnull PsiElement lastElement) { - checkState(); - PsiElement e; - for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { - addIgnored(e); - } - if (e == null) { - throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); - } - addIgnored(lastElement); - } - - /** - * Returns the comments which are located between the supplied element - * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param element an element grab the comments before it - * @return the string containing the element text and possibly some comments. - */ - public String commentsBefore(@Nonnull PsiElement element) { - List comments = grabCommentsBefore(element); - if (comments.isEmpty()) - return ""; - StringBuilder sb = new StringBuilder(); - for (PsiElement comment : comments) { - PsiElement prev = comment.getPrevSibling(); - if (sb.length() == 0 && prev instanceof PsiWhiteSpace) { - sb.append(prev.getText()); - } - sb.append(comment.getText()); - PsiElement next = PsiTreeUtil.nextLeaf(comment); - if (next instanceof PsiWhiteSpace) { - sb.append(next.getText()); - } - } - comments.forEach(PsiElement::delete); - return sb.toString(); - } - - private List grabCommentsBefore(@Nonnull PsiElement element) { - if (lastTextWithCommentsElement == null) { - lastTextWithCommentsElement = element; - return Collections.emptyList(); - } - List result = new SmartList<>(); - int start = lastTextWithCommentsElement.getTextRange().getEndOffset(); - int end = element.getTextRange().getStartOffset(); - PsiElement parent = PsiTreeUtil.findCommonParent(lastTextWithCommentsElement, element); - if (parent != null && start < end) { - PsiTreeUtil.processElements(parent, e -> { - if (e instanceof PsiComment) { - TextRange range = e.getTextRange(); - if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore((PsiComment) e)) { - result.add(e); - } - } - return true; - }); - } - - lastTextWithCommentsElement = element; - return result; - } - - /** - * Returns an element text, possibly prepended with comments which are located between the supplied element - * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, - * because PsiExpression cannot start with comment. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param element an element to convert to the text - * @return the string containing the element text and possibly some comments. - */ - public String textWithComments(@Nonnull PsiElement element) { - return commentsBefore(element) + element.getText(); - } - - /** - * Returns an element text, adding parentheses if necessary, possibly prepended with comments which are - * located between the supplied element and the previous element passed into - * {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. - * The used comments are deleted from the original document. - * - *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, - * because PsiExpression cannot start with comment. - * - *

This method can be used if several parts of original code are reused in the generated replacement. - * - * @param expression an expression to convert to the text - * @param precedence precedence of surrounding operation - * @return the string containing the element text and possibly some comments. - */ - public String textWithComments(@Nonnull PsiExpression expression, int precedence) { - return commentsBefore(expression) + ParenthesesUtils.getText(expression, precedence + 1); - } - - /** - * Deletes given PsiElement collecting all the comments inside it. - * - * @param element element to delete - */ - public void delete(@Nonnull PsiElement element) { - grabCommentsOnDelete(element); - element.delete(); - } - - /** - * Deletes all given PsiElement's collecting all the comments inside them. - * - * @param elements elements to delete (all not null) - */ - public void delete(@Nonnull PsiElement... elements) { - for (PsiElement element : elements) { - delete(element); - } - } - - /** - * Deletes given PsiElement replacing it with the comments including comments inside the deleted element - * and previously gathered comments. - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param element element to delete - */ - public void deleteAndRestoreComments(@Nonnull PsiElement element) { - grabCommentsOnDelete(element); - PsiElement anchor = element; - while (anchor.getParent() != null && !(anchor.getParent() instanceof PsiFile) && anchor.getParent().getFirstChild() == anchor) { - anchor = anchor.getParent(); - } - insertCommentsBefore(anchor); - element.delete(); - } - - /** - * Replaces given PsiElement collecting all the comments inside it. - * - * @param element element to replace - * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) - * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) - */ - public - @Nonnull - PsiElement replace(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { - markUnchanged(replacement); - grabComments(element); - return element.replace(replacement); - } - - /** - * Creates a replacement element from the text and replaces given element, - * collecting all the comments inside it. - * - *

- * The type of the created replacement will mimic the type of supplied element. - * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, - * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. - *

- * - * @param element element to replace - * @param text replacement text - * @return the element which was actually inserted in the tree - */ - public - @Nonnull - PsiElement replace(@Nonnull PsiElement element, @Nonnull String text) { - PsiElement replacement = createElement(element, text); - return replace(element, replacement); - } - - /** - * Replaces given PsiElement collecting all the comments inside it and restores comments putting them - * to the appropriate place before replaced element. - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param element element to replace - * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) - * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) - */ - public - @Nonnull - PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull PsiElement replacement) { - List suffix = grabSuffixComments(element); - PsiElement result = replace(element, replacement); - PsiElement anchor = PsiTreeUtil - .getNonStrictParentOfType(result, PsiStatement.class, PsiLambdaExpression.class, PsiVariable.class, PsiNameValuePair.class); - if (anchor instanceof PsiLambdaExpression && anchor != result) { - anchor = ((PsiLambdaExpression) anchor).getBody(); - } - if (anchor instanceof PsiVariable && anchor.getParent() instanceof PsiDeclarationStatement) { - anchor = anchor.getParent(); - } - if (anchor instanceof PsiStatement && (anchor.getParent() instanceof PsiIfStatement || anchor.getParent() instanceof PsiLoopStatement)) { - anchor = anchor.getParent(); - } - if (anchor == null) - anchor = result; - restoreSuffixComments(result, suffix); - insertCommentsBefore(anchor); - return result; - } - - /** - * Replaces the specified expression and restores any comments to their appropriate place before and/or after the expression. - * Meant to be used with {@link #commentsBefore(PsiElement)} and {@link #commentsBetween(PsiElement, PsiElement)} - * - * @param expression the expression to replace - * @param replacementText text of the replacement expression - * @return the element which was inserted in the tree - */ - public - @Nonnull - PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText) { - return replaceExpressionAndRestoreComments(expression, replacementText, Collections.emptyList()); - } - - public - @Nonnull - PsiElement replaceExpressionAndRestoreComments(@Nonnull PsiExpression expression, @Nonnull String replacementText, - List toDelete) { - List trailingComments = new SmartList<>(); - List comments = grabCommentsBefore(PsiTreeUtil.lastChild(expression)); - if (!comments.isEmpty()) { - PsiParserFacade parser = PsiParserFacade.SERVICE.getInstance(expression.getProject()); - for (PsiElement comment : comments) { - PsiElement prev = comment.getPrevSibling(); - if (prev instanceof PsiWhiteSpace) { - String text = prev.getText(); - if (!text.contains("\n")) - trailingComments.add(parser.createWhiteSpaceFromText(" ")); - else if (text.endsWith("\n")) - trailingComments.add(parser.createWhiteSpaceFromText("\n")); // comment at first column - else - trailingComments.add(parser.createWhiteSpaceFromText("\n ")); // newline followed by space will cause formatter to indent - } - ignoredParents.add(comment); - trailingComments.add(comment.copy()); - } - Collections.reverse(trailingComments); - } - PsiElement replacement = replace(expression, replacementText); - for (PsiElement element : trailingComments) { - replacement.getParent().addAfter(element, replacement); - } - toDelete.forEach(this::delete); - insertCommentsBefore(replacement); - return replacement; - } - - @Nonnull - private List grabSuffixComments(@Nonnull PsiElement element) { - if (!(element instanceof PsiStatement)) { - return Collections.emptyList(); - } - List suffix = new ArrayList<>(); - PsiElement lastChild = element.getLastChild(); - boolean hasComment = false; - while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { - hasComment |= lastChild instanceof PsiComment; - if (!(lastChild instanceof PsiComment) || !(shouldIgnore((PsiComment) lastChild))) { - suffix.add(markUnchanged(lastChild).copy()); - } - lastChild = lastChild.getPrevSibling(); - } - return hasComment ? suffix : Collections.emptyList(); - } - - private static void restoreSuffixComments(PsiElement target, List suffix) { - if (!suffix.isEmpty()) { - PsiElement lastChild = target.getLastChild(); - if (lastChild instanceof PsiComment && JavaTokenType.END_OF_LINE_COMMENT.equals(((PsiComment) lastChild).getTokenType())) { - PsiElement nextSibling = target.getNextSibling(); - if (nextSibling instanceof PsiWhiteSpace) { - target.add(nextSibling); - } else { - target.add(PsiParserFacade.SERVICE.getInstance(target.getProject()).createWhiteSpaceFromText("\n")); - } - } - StreamEx.ofReversed(suffix).forEach(target::add); - } - } - - /** - * Creates a replacement element from the text and replaces given element, - * collecting all the comments inside it and restores comments putting them - * to the appropriate place before replaced element. - * - *

After calling this method the tracker cannot be used anymore.

- * - *

- * The type of the created replacement will mimic the type of supplied element. - * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, - * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. - *

- * - * @param element element to replace - * @param text replacement text - * @return the element which was actually inserted in the tree - */ - public - @Nonnull - PsiElement replaceAndRestoreComments(@Nonnull PsiElement element, @Nonnull String text) { - PsiElement replacement = createElement(element, text); - return replaceAndRestoreComments(element, replacement); - } - - private static - @Nonnull - PsiElement createElement(@Nonnull PsiElement element, @Nonnull String text) { - PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject()); - if (element instanceof PsiExpression) { - return factory.createExpressionFromText(text, element); - } else if (element instanceof PsiStatement) { - return factory.createStatementFromText(text, element); - } else if (element instanceof PsiTypeElement) { - return factory.createTypeElementFromText(text, element); - } else if (element instanceof PsiIdentifier) { - return factory.createIdentifier(text); - } else if (element instanceof PsiComment) { - return factory.createCommentFromText(text, element); - } else { - throw new IllegalArgumentException("Unsupported element type: " + element); - } - } - - /** - * Inserts gathered comments just before given anchor element - * - *

After calling this method the tracker cannot be used anymore.

- * - * @param anchor - */ - public void insertCommentsBefore(@Nonnull PsiElement anchor) { - checkState(); - if (!comments.isEmpty()) { - PsiElement parent = anchor.getParent(); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject()); - for (PsiComment comment : comments) { - if (shouldIgnore(comment)) - continue; - PsiElement added = parent.addBefore(factory.createCommentFromText(comment.getText(), anchor), anchor); - PsiElement prevSibling = added.getPrevSibling(); - if (prevSibling instanceof PsiWhiteSpace) { - PsiElement prev = anchor.getPrevSibling(); - ASTNode whiteSpaceBefore = normalizeWhiteSpace((PsiWhiteSpace) prevSibling, prev); - parent.getNode().addChild(whiteSpaceBefore, anchor.getNode()); - if (prev instanceof PsiWhiteSpace) { - prev.delete(); - } - } - } - } - comments = null; - } - - private static - @Nonnull - ASTNode normalizeWhiteSpace(PsiWhiteSpace whiteSpace, PsiElement nextElement) { - String text = whiteSpace.getText(); - int endLPos = text.lastIndexOf('\n'); - if (text.lastIndexOf('\n', endLPos - 1) >= 0) { - // has at least two line breaks - return ASTFactory.whitespace(text.substring(endLPos)); - } - if (nextElement instanceof PsiWhiteSpace && nextElement.getText().contains("\n") && !text.contains("\n")) { - text = '\n' + text; - } - return ASTFactory.whitespace(text); - } - - private boolean shouldIgnore(PsiComment comment) { - return ignoredParents.stream().anyMatch(p -> PsiTreeUtil.isAncestor(p, comment, false)); - } - - private void grabCommentsOnDelete(PsiElement element) { - if (element instanceof PsiExpression && element.getParent() instanceof PsiExpressionStatement || - (element.getParent() instanceof PsiDeclarationStatement && - ((PsiDeclarationStatement) element.getParent()).getDeclaredElements().length == 1)) { - element = element.getParent(); - } else if (element.getParent() instanceof PsiJavaCodeReferenceElement) { - PsiElement parent = element.getParent(); - if (element instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement) parent).getQualifier() == element) { - ASTNode dot = ((CompositeElement) parent).findChildByRole(ChildRole.DOT); - if (dot != null) { - PsiElement nextSibling = dot.getPsi().getNextSibling(); - if (nextSibling != null && nextSibling.getTextLength() == 0) { - nextSibling = skipWhitespacesAndCommentsForward(nextSibling); - } - while (nextSibling != null) { - nextSibling = markUnchanged(nextSibling).getNextSibling(); - } - } - } - element = parent; - } - grabComments(element); - } - - @SuppressWarnings("unchecked") - private static final Class[] - WS_COMMENTS = new Class[]{ - PsiWhiteSpace.class, - PsiComment.class - }; - - @Nullable - @Contract("null -> null") - @Deprecated - public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement element) { - return PsiTreeUtil.skipSiblingsForward(element, WS_COMMENTS); - } - - /** - * Grab the comments from given element which should be restored. Normally you don't need to call this method. - * It should be called only if element is about to be deleted by other code which is not CommentTracker-aware. - * - *

Calling this method repeatedly has no effect. It's also safe to call this method, then delete element using - * other methods from this class like {@link #delete(PsiElement)}. - * - * @param element element to grab the comments from. - */ - public void grabComments(PsiElement element) { - checkState(); - for (PsiComment comment : PsiTreeUtil.collectElementsOfType(element, PsiComment.class)) { - if (!shouldIgnore(comment)) { - comments.add(comment); - } - } - } - - private void checkState() { - if (comments == null) { - throw new IllegalStateException(getClass().getSimpleName() + " has been already used"); - } - } - - private void addIgnored(PsiElement element) { - if (!(element instanceof LeafPsiElement) || element instanceof PsiComment) { - ignoredParents.add(element); - } - } - - public static String textWithSurroundingComments(PsiElement element) { - Predicate commentOrWhiteSpace = e -> e instanceof PsiComment || e instanceof PsiWhiteSpace; - List prev = StreamEx.iterate(element.getPrevSibling(), commentOrWhiteSpace, PsiElement::getPrevSibling).toList(); - List next = StreamEx.iterate(element.getNextSibling(), commentOrWhiteSpace, PsiElement::getNextSibling).toList(); - if (StreamEx.of(prev, next).flatCollection(Function.identity()).anyMatch(PsiComment.class::isInstance)) { - return StreamEx.ofReversed(prev).append(element).append(next).map(PsiElement::getText).joining(); - } - return element.getText(); - } - - /** - * Returns a string containing all the comments (possibly with some white-spaces) between given elements - * (not including given elements themselves). This method also deletes all the comments actually used - * in the returned string. - * - * @param start start element - * @param end end element, must strictly follow the start element and be located in the same file - * (though possibly on another hierarchy level) - * @return a string containing all the comments between start and end. - */ - public static - @Nonnull - String commentsBetween(@Nonnull PsiElement start, @Nonnull PsiElement end) { - CommentTracker ct = new CommentTracker(); - ct.lastTextWithCommentsElement = start; - return ct.commentsBefore(end); - } + private final Set ignoredParents = new HashSet<>(); + private List comments = new ArrayList<>(); + private PsiElement lastTextWithCommentsElement = null; + + /** + * Marks the element as unchanged and returns its text. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param element element to return the text + * @return a text to be inserted into refactored code + */ + @RequiredReadAction + public String text(PsiElement element) { + checkState(); + addIgnored(element); + return element.getText(); + } + + /** + * Marks the expression as unchanged and returns its text, adding parentheses if necessary. + * The unchanged elements are assumed to be preserved in the resulting code as is, + * so the comments from them will not be extracted. + * + * @param element expression to return the text + * @param precedence precedence of surrounding operation + * @return a text to be inserted into refactored code + * @see ParenthesesUtils#getText(PsiExpression, int) + */ + public String text(PsiExpression element, int precedence) { + checkState(); + addIgnored(element); + return ParenthesesUtils.getText(element, precedence + 1); + } + + /** + * Marks the expression as unchanged and returns a single-parameter lambda text which parameter + * is the name of supplied variable and body is the supplied expression + * + * @param variable a variable to use as lambda parameter + * @param expression an expression to use as lambda body + * @return a string representation of lambda + */ + @RequiredReadAction + public String lambdaText(PsiVariable variable, PsiExpression expression) { + return variable.getName() + " -> " + text(expression); + } + + /** + * Marks the element as unchanged and returns it. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param element element to mark + * @param the type of the element + * @return the passed argument + */ + @Contract("_ -> param1") + public T markUnchanged(@Nullable T element) { + checkState(); + if (element != null) { + addIgnored(element); + } + return element; + } + + /** + * Marks the range of elements as unchanged and returns their text. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param firstElement first element to mark + * @param lastElement last element to mark (must be equal to firstElement or its sibling) + * @return a text to be inserted into refactored code + * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element + */ + @RequiredReadAction + public String rangeText(PsiElement firstElement, PsiElement lastElement) { + checkState(); + PsiElement e; + StringBuilder result = new StringBuilder(); + for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { + addIgnored(e); + result.append(e.getText()); + } + if (e == null) { + throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); + } + addIgnored(lastElement); + result.append(lastElement.getText()); + return result.toString(); + } + + /** + * Marks the range of elements as unchanged. The unchanged elements are assumed to be preserved + * in the resulting code as is, so the comments from them will not be extracted. + * + * @param firstElement first element to mark + * @param lastElement last element to mark (must be equal to firstElement or its sibling) + * @throws IllegalArgumentException if firstElement and lastElements are not siblings or firstElement goes after last element + */ + @RequiredReadAction + public void markRangeUnchanged(PsiElement firstElement, PsiElement lastElement) { + checkState(); + PsiElement e; + for (e = firstElement; e != null && e != lastElement; e = e.getNextSibling()) { + addIgnored(e); + } + if (e == null) { + throw new IllegalArgumentException("Elements must be siblings: " + firstElement + " and " + lastElement); + } + addIgnored(lastElement); + } + + /** + * Returns the comments which are located between the supplied element + * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param element an element grab the comments before it + * @return the string containing the element text and possibly some comments. + */ + @RequiredReadAction + public String commentsBefore(PsiElement element) { + List comments = grabCommentsBefore(element); + if (comments.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (PsiElement comment : comments) { + PsiElement prev = comment.getPrevSibling(); + if (sb.length() == 0 && prev instanceof PsiWhiteSpace whiteSpace) { + sb.append(whiteSpace.getText()); + } + sb.append(comment.getText()); + PsiElement next = PsiTreeUtil.nextLeaf(comment); + if (next instanceof PsiWhiteSpace whiteSpace) { + sb.append(whiteSpace.getText()); + } + } + comments.forEach(PsiElement::delete); + return sb.toString(); + } + + @RequiredReadAction + private List grabCommentsBefore(PsiElement element) { + if (lastTextWithCommentsElement == null) { + lastTextWithCommentsElement = element; + return Collections.emptyList(); + } + List result = new SmartList<>(); + int start = lastTextWithCommentsElement.getTextRange().getEndOffset(); + int end = element.getTextRange().getStartOffset(); + PsiElement parent = PsiTreeUtil.findCommonParent(lastTextWithCommentsElement, element); + if (parent != null && start < end) { + PsiTreeUtil.processElements( + parent, + e -> { + if (e instanceof PsiComment comment) { + TextRange range = comment.getTextRange(); + if (range.getStartOffset() >= start && range.getEndOffset() <= end && !shouldIgnore(comment)) { + result.add(comment); + } + } + return true; + } + ); + } + + lastTextWithCommentsElement = element; + return result; + } + + /** + * Returns an element text, possibly prepended with comments which are located between the supplied element + * and the previous element passed into {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, + * because PsiExpression cannot start with comment. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param element an element to convert to the text + * @return the string containing the element text and possibly some comments. + */ + @RequiredReadAction + public String textWithComments(PsiElement element) { + return commentsBefore(element) + element.getText(); + } + + /** + * Returns an element text, adding parentheses if necessary, possibly prepended with comments which are + * located between the supplied element and the previous element passed into + * {@link #textWithComments(PsiElement)} or {@link #commentsBefore(PsiElement)}. + * The used comments are deleted from the original document. + * + *

Note that if PsiExpression was passed, the resulting text may not parse as an PsiExpression, + * because PsiExpression cannot start with comment. + * + *

This method can be used if several parts of original code are reused in the generated replacement. + * + * @param expression an expression to convert to the text + * @param precedence precedence of surrounding operation + * @return the string containing the element text and possibly some comments. + */ + @RequiredReadAction + public String textWithComments(PsiExpression expression, int precedence) { + return commentsBefore(expression) + ParenthesesUtils.getText(expression, precedence + 1); + } + + /** + * Deletes given PsiElement collecting all the comments inside it. + * + * @param element element to delete + */ + @RequiredWriteAction + public void delete(PsiElement element) { + grabCommentsOnDelete(element); + element.delete(); + } + + /** + * Deletes all given PsiElement's collecting all the comments inside them. + * + * @param elements elements to delete (all not null) + */ + @RequiredWriteAction + public void delete(PsiElement... elements) { + for (PsiElement element : elements) { + delete(element); + } + } + + /** + * Deletes given PsiElement replacing it with the comments including comments inside the deleted element + * and previously gathered comments. + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param element element to delete + */ + @RequiredWriteAction + public void deleteAndRestoreComments(PsiElement element) { + grabCommentsOnDelete(element); + PsiElement anchor = element; + while (anchor.getParent() != null && !(anchor.getParent() instanceof PsiFile) && anchor.getParent().getFirstChild() == anchor) { + anchor = anchor.getParent(); + } + insertCommentsBefore(anchor); + element.delete(); + } + + /** + * Replaces given PsiElement collecting all the comments inside it. + * + * @param element element to replace + * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) + * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) + */ + @RequiredWriteAction + public PsiElement replace(PsiElement element, PsiElement replacement) { + markUnchanged(replacement); + grabComments(element); + return element.replace(replacement); + } + + /** + * Creates a replacement element from the text and replaces given element, + * collecting all the comments inside it. + * + *

+ * The type of the created replacement will mimic the type of supplied element. + * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, + * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. + *

+ * + * @param element element to replace + * @param text replacement text + * @return the element which was actually inserted in the tree + */ + @RequiredWriteAction + public PsiElement replace(PsiElement element, String text) { + PsiElement replacement = createElement(element, text); + return replace(element, replacement); + } + + /** + * Replaces given PsiElement collecting all the comments inside it and restores comments putting them + * to the appropriate place before replaced element. + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param element element to replace + * @param replacement replacement element. It's also marked as unchanged (see {@link #markUnchanged(PsiElement)}) + * @return the element which was actually inserted in the tree (either {@code replacement} or its copy) + */ + @RequiredWriteAction + public PsiElement replaceAndRestoreComments(PsiElement element, PsiElement replacement) { + List suffix = grabSuffixComments(element); + PsiElement result = replace(element, replacement); + PsiElement anchor = PsiTreeUtil + .getNonStrictParentOfType(result, PsiStatement.class, PsiLambdaExpression.class, PsiVariable.class, PsiNameValuePair.class); + if (anchor instanceof PsiLambdaExpression lambda && anchor != result) { + anchor = lambda.getBody(); + } + if (anchor instanceof PsiVariable variable && variable.getParent() instanceof PsiDeclarationStatement declaration) { + anchor = declaration; + } + if (anchor instanceof PsiStatement && (anchor.getParent() instanceof PsiIfStatement || anchor.getParent() instanceof PsiLoopStatement)) { + anchor = anchor.getParent(); + } + if (anchor == null) { + anchor = result; + } + restoreSuffixComments(result, suffix); + insertCommentsBefore(anchor); + return result; + } + + /** + * Replaces the specified expression and restores any comments to their appropriate place before and/or after the expression. + * Meant to be used with {@link #commentsBefore(PsiElement)} and {@link #commentsBetween(PsiElement, PsiElement)} + * + * @param expression the expression to replace + * @param replacementText text of the replacement expression + * @return the element which was inserted in the tree + */ + @RequiredWriteAction + public PsiElement replaceExpressionAndRestoreComments(PsiExpression expression, String replacementText) { + return replaceExpressionAndRestoreComments(expression, replacementText, Collections.emptyList()); + } + + @RequiredWriteAction + public PsiElement replaceExpressionAndRestoreComments( + PsiExpression expression, + String replacementText, + List toDelete + ) { + List trailingComments = new SmartList<>(); + List comments = grabCommentsBefore(PsiTreeUtil.lastChild(expression)); + if (!comments.isEmpty()) { + PsiParserFacade parser = PsiParserFacade.SERVICE.getInstance(expression.getProject()); + for (PsiElement comment : comments) { + if (comment.getPrevSibling() instanceof PsiWhiteSpace whiteSpace) { + String text = whiteSpace.getText(); + if (!text.contains("\n")) { + trailingComments.add(parser.createWhiteSpaceFromText(" ")); + } + else if (text.endsWith("\n")) { + // comment at first column + trailingComments.add(parser.createWhiteSpaceFromText("\n")); + } + else { + // newline followed by space will cause formatter to indent + trailingComments.add(parser.createWhiteSpaceFromText("\n ")); + } + } + ignoredParents.add(comment); + trailingComments.add(comment.copy()); + } + Collections.reverse(trailingComments); + } + PsiElement replacement = replace(expression, replacementText); + for (PsiElement element : trailingComments) { + replacement.getParent().addAfter(element, replacement); + } + toDelete.forEach(this::delete); + insertCommentsBefore(replacement); + return replacement; + } + + @RequiredReadAction + private List grabSuffixComments(PsiElement element) { + if (!(element instanceof PsiStatement)) { + return Collections.emptyList(); + } + List suffix = new ArrayList<>(); + PsiElement lastChild = element.getLastChild(); + boolean hasComment = false; + while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { + hasComment |= lastChild instanceof PsiComment; + if (!(lastChild instanceof PsiComment comment) || !(shouldIgnore(comment))) { + suffix.add(markUnchanged(lastChild).copy()); + } + lastChild = lastChild.getPrevSibling(); + } + return hasComment ? suffix : Collections.emptyList(); + } + + @RequiredReadAction + private static void restoreSuffixComments(PsiElement target, List suffix) { + if (!suffix.isEmpty()) { + PsiElement lastChild = target.getLastChild(); + if (lastChild instanceof PsiComment comment && JavaTokenType.END_OF_LINE_COMMENT.equals(comment.getTokenType())) { + if (target.getNextSibling() instanceof PsiWhiteSpace whiteSpace) { + target.add(whiteSpace); + } + else { + target.add(PsiParserFacade.SERVICE.getInstance(target.getProject()).createWhiteSpaceFromText("\n")); + } + } + StreamEx.ofReversed(suffix).forEach(target::add); + } + } + + /** + * Creates a replacement element from the text and replaces given element, + * collecting all the comments inside it and restores comments putting them + * to the appropriate place before replaced element. + * + *

After calling this method the tracker cannot be used anymore.

+ * + *

+ * The type of the created replacement will mimic the type of supplied element. + * Supported element types are: {@link PsiExpression}, {@link PsiStatement}, + * {@link PsiTypeElement}, {@link PsiIdentifier}, {@link PsiComment}. + *

+ * + * @param element element to replace + * @param text replacement text + * @return the element which was actually inserted in the tree + */ + @RequiredWriteAction + public PsiElement replaceAndRestoreComments(PsiElement element, String text) { + PsiElement replacement = createElement(element, text); + return replaceAndRestoreComments(element, replacement); + } + + private static PsiElement createElement(PsiElement element, String text) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(element.getProject()); + if (element instanceof PsiExpression) { + return factory.createExpressionFromText(text, element); + } + else if (element instanceof PsiStatement) { + return factory.createStatementFromText(text, element); + } + else if (element instanceof PsiTypeElement) { + return factory.createTypeElementFromText(text, element); + } + else if (element instanceof PsiIdentifier) { + return factory.createIdentifier(text); + } + else if (element instanceof PsiComment) { + return factory.createCommentFromText(text, element); + } + else { + throw new IllegalArgumentException("Unsupported element type: " + element); + } + } + + /** + * Inserts gathered comments just before given anchor element + * + *

After calling this method the tracker cannot be used anymore.

+ * + * @param anchor element to insert comments before + */ + @RequiredWriteAction + public void insertCommentsBefore(PsiElement anchor) { + checkState(); + if (!comments.isEmpty()) { + PsiElement parent = anchor.getParent(); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(anchor.getProject()); + for (PsiComment comment : comments) { + if (shouldIgnore(comment)) { + continue; + } + PsiElement added = parent.addBefore(factory.createCommentFromText(comment.getText(), anchor), anchor); + if (added.getPrevSibling() instanceof PsiWhiteSpace prevSiblingWhiteSpace) { + PsiElement prev = anchor.getPrevSibling(); + ASTNode whiteSpaceBefore = normalizeWhiteSpace(prevSiblingWhiteSpace, prev); + parent.getNode().addChild(whiteSpaceBefore, anchor.getNode()); + if (prev instanceof PsiWhiteSpace whiteSpace) { + whiteSpace.delete(); + } + } + } + } + comments = null; + } + + @RequiredReadAction + private static ASTNode normalizeWhiteSpace(PsiWhiteSpace whiteSpace, PsiElement nextElement) { + String text = whiteSpace.getText(); + int endLPos = text.lastIndexOf('\n'); + if (text.lastIndexOf('\n', endLPos - 1) >= 0) { + // has at least two line breaks + return ASTFactory.whitespace(text.substring(endLPos)); + } + if (nextElement instanceof PsiWhiteSpace && nextElement.getText().contains("\n") && !text.contains("\n")) { + text = '\n' + text; + } + return ASTFactory.whitespace(text); + } + + private boolean shouldIgnore(PsiComment comment) { + return ignoredParents.stream().anyMatch(p -> PsiTreeUtil.isAncestor(p, comment, false)); + } + + @RequiredReadAction + private void grabCommentsOnDelete(PsiElement element) { + PsiElement parent = element.getParent(); + if (element instanceof PsiExpression && parent instanceof PsiExpressionStatement + || (parent instanceof PsiDeclarationStatement declarationStmt && declarationStmt.getDeclaredElements().length == 1)) { + element = parent; + } + else if (parent instanceof PsiJavaCodeReferenceElement parentCodeRefElem) { + if (element instanceof PsiJavaCodeReferenceElement codeRefElem && codeRefElem.getQualifier() == element) { + ASTNode dot = ((CompositeElement)parentCodeRefElem).findChildByRole(ChildRole.DOT); + if (dot != null) { + PsiElement nextSibling = dot.getPsi().getNextSibling(); + if (nextSibling != null && nextSibling.getTextLength() == 0) { + nextSibling = PsiTreeUtil.skipSiblingsForward(nextSibling, WS_COMMENTS); + } + while (nextSibling != null) { + nextSibling = markUnchanged(nextSibling).getNextSibling(); + } + } + } + element = parentCodeRefElem; + } + grabComments(element); + } + + @SuppressWarnings("unchecked") + private static final Class[] + WS_COMMENTS = new Class[]{ + PsiWhiteSpace.class, + PsiComment.class + }; + + @Deprecated + @Contract("null -> null") + @Nullable + @RequiredReadAction + public static PsiElement skipWhitespacesAndCommentsForward(@Nullable PsiElement element) { + return PsiTreeUtil.skipSiblingsForward(element, WS_COMMENTS); + } + + /** + * Grab the comments from given element which should be restored. Normally you don't need to call this method. + * It should be called only if element is about to be deleted by other code which is not CommentTracker-aware. + * + *

Calling this method repeatedly has no effect. It's also safe to call this method, then delete element using + * other methods from this class like {@link #delete(PsiElement)}. + * + * @param element element to grab the comments from. + */ + @RequiredReadAction + public void grabComments(PsiElement element) { + checkState(); + for (PsiComment comment : PsiTreeUtil.collectElementsOfType(element, PsiComment.class)) { + if (!shouldIgnore(comment)) { + comments.add(comment); + } + } + } + + private void checkState() { + if (comments == null) { + throw new IllegalStateException(getClass().getSimpleName() + " has been already used"); + } + } + + private void addIgnored(PsiElement element) { + if (!(element instanceof LeafPsiElement) || element instanceof PsiComment) { + ignoredParents.add(element); + } + } + + @RequiredReadAction + public static String textWithSurroundingComments(PsiElement element) { + Predicate commentOrWhiteSpace = e -> e instanceof PsiComment || e instanceof PsiWhiteSpace; + List prev = StreamEx.iterate(element.getPrevSibling(), commentOrWhiteSpace, PsiElement::getPrevSibling).toList(); + List next = StreamEx.iterate(element.getNextSibling(), commentOrWhiteSpace, PsiElement::getNextSibling).toList(); + if (StreamEx.of(prev, next).flatCollection(Function.identity()).anyMatch(PsiComment.class::isInstance)) { + return StreamEx.ofReversed(prev).append(element).append(next).map(PsiElement::getText).joining(); + } + return element.getText(); + } + + /** + * Returns a string containing all the comments (possibly with some white-spaces) between given elements + * (not including given elements themselves). This method also deletes all the comments actually used + * in the returned string. + * + * @param start start element + * @param end end element, must strictly follow the start element and be located in the same file + * (though possibly on another hierarchy level) + * @return a string containing all the comments between start and end. + */ + @RequiredReadAction + public static String commentsBetween(PsiElement start, PsiElement end) { + CommentTracker ct = new CommentTracker(); + ct.lastTextWithCommentsElement = start; + return ct.commentsBefore(end); + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ComparisonUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ComparisonUtils.java index 2b73f99c7e..f5bffa598d 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ComparisonUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ComparisonUtils.java @@ -20,8 +20,7 @@ import java.util.Map; import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import com.intellij.java.language.psi.JavaTokenType; import com.intellij.java.language.psi.PsiExpression; @@ -83,7 +82,7 @@ public static String getFlippedComparison(IElementType tokenType) { return s_swappedComparisons.get(tokenType); } - public static boolean isEqualityComparison(@Nonnull PsiExpression expression) { + public static boolean isEqualityComparison(PsiExpression expression) { if (!(expression instanceof PsiPolyadicExpression)) { return false; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java index 74439e7522..64a5ef8709 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ConstructionUtils.java @@ -19,11 +19,11 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.ig.callMatcher.CallMatcher; -import consulo.java.language.module.util.JavaClassNames; +import consulo.annotation.access.RequiredReadAction; import consulo.language.psi.PsiElement; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import javax.annotation.Nullable; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; @@ -32,247 +32,249 @@ * @author Tagir Valeev */ public class ConstructionUtils { - private static final Set GUAVA_UTILITY_CLASSES = Set.of("com.google.common.collect.Maps", "com.google.common.collect.Lists", "com.google.common.collect.Sets"); - private static final CallMatcher ENUM_SET_NONE_OF = CallMatcher.staticCall("java.util.EnumSet", "noneOf").parameterCount(1); + private static final Set GUAVA_UTILITY_CLASSES = + Set.of("com.google.common.collect.Maps", "com.google.common.collect.Lists", "com.google.common.collect.Sets"); + private static final CallMatcher ENUM_SET_NONE_OF = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_ENUM_SET, "noneOf").parameterCount(1); - /** - * Checks that given expression initializes empty StringBuilder or StringBuffer (either with explicit default capacity or not) - * - * @param initializer initializer to check - * @return true if the initializer is empty StringBuilder or StringBuffer initializer - */ - @Contract("null -> false") - public static boolean isEmptyStringBuilderInitializer(PsiExpression initializer) { - return "\"\"".equals(getStringBuilderInitializerText(initializer)); - } - - /** - * Returns a textual representation of an expression which is equivalent to the initial value of newly created StringBuilder or StringBuffer - * - * @param construction StringBuilder/StringBuffer construction expression - * @return a textual representation of an initial value CharSequence or null if supplied expression is not StringBuilder/StringBuffer - * construction expression - */ - @Contract("null -> null") - public static String getStringBuilderInitializerText(PsiExpression construction) { - construction = PsiUtil.skipParenthesizedExprDown(construction); - if (!(construction instanceof PsiNewExpression)) { - return null; - } - final PsiNewExpression newExpression = (PsiNewExpression) construction; - final PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); - if (classReference == null) { - return null; - } - final PsiElement target = classReference.resolve(); - if (!(target instanceof PsiClass)) { - return null; - } - final PsiClass aClass = (PsiClass) target; - final String qualifiedName = aClass.getQualifiedName(); - if (!JavaClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) && !JavaClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) { - return null; - } - final PsiExpressionList argumentList = newExpression.getArgumentList(); - if (argumentList == null) { - return null; - } - final PsiExpression[] arguments = argumentList.getExpressions(); - if (arguments.length == 0) { - return "\"\""; - } - if (arguments.length != 1) { - return null; + /** + * Checks that given expression initializes empty StringBuilder or StringBuffer (either with explicit default capacity or not) + * + * @param initializer initializer to check + * @return true if the initializer is empty StringBuilder or StringBuffer initializer + */ + @Contract("null -> false") + @RequiredReadAction + public static boolean isEmptyStringBuilderInitializer(PsiExpression initializer) { + return "\"\"".equals(getStringBuilderInitializerText(initializer)); } - final PsiExpression argument = arguments[0]; - final PsiType argumentType = argument.getType(); - if (PsiType.INT.equals(argumentType)) { - return "\"\""; - } - return argument.getText(); - } - /** - * Checks that given expression initializes empty Collection or Map - * - * @param expression expression to check - * @return true if the expression is the empty Collection or Map initializer - */ - @Contract("null -> false") - public static boolean isEmptyCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList argumentList = ((PsiNewExpression) expression).getArgumentList(); - if (argumentList != null && argumentList.getExpressions().length == 0) { - PsiType type = expression.getType(); - return InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_UTIL_COLLECTION) || InheritanceUtil.isInheritor(type, JavaClassNames - .JAVA_UTIL_MAP); - } + /** + * Returns a textual representation of an expression which is equivalent to the initial value of newly created StringBuilder or StringBuffer + * + * @param construction StringBuilder/StringBuffer construction expression + * @return a textual representation of an initial value CharSequence or null if supplied expression is not StringBuilder/StringBuffer + * construction expression + */ + @Contract("null -> null") + @RequiredReadAction + public static String getStringBuilderInitializerText(PsiExpression construction) { + construction = PsiUtil.skipParenthesizedExprDown(construction); + if (!(construction instanceof PsiNewExpression newExpr)) { + return null; + } + PsiJavaCodeReferenceElement classReference = newExpr.getClassReference(); + if (classReference == null) { + return null; + } + PsiElement target = classReference.resolve(); + if (!(target instanceof PsiClass aClass)) { + return null; + } + String qualifiedName = aClass.getQualifiedName(); + if (!CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) && !CommonClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) { + return null; + } + PsiExpressionList argumentList = newExpr.getArgumentList(); + if (argumentList == null) { + return null; + } + PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length == 0) { + return "\"\""; + } + if (arguments.length != 1) { + return null; + } + PsiExpression argument = arguments[0]; + PsiType argumentType = argument.getType(); + if (PsiType.INT.equals(argumentType)) { + return "\"\""; + } + return argument.getText(); } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && argumentList.getExpressions().length == 0) { - PsiMethod method = call.resolveMethod(); - if (method != null && method.getParameterList().getParametersCount() == 0) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null) { - String qualifiedName = aClass.getQualifiedName(); - if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return true; + + /** + * Checks that given expression initializes empty Collection or Map + * + * @param expression expression to check + * @return true if the expression is the empty Collection or Map initializer + */ + @Contract("null -> false") + public static boolean isEmptyCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression newExpr) { + PsiExpressionList argumentList = newExpr.getArgumentList(); + if (argumentList != null && argumentList.getExpressions().length == 0) { + PsiType type = expression.getType(); + return InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION) + || InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP); } - } } - } + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && argumentList.getExpressions().length == 0) { + PsiMethod method = call.resolveMethod(); + if (method != null && method.getParameterList().getParametersCount() == 0) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + String qualifiedName = aClass.getQualifiedName(); + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return true; + } + } + } + } + } + return isCustomizedEmptyCollectionInitializer(expression); } - return isCustomizedEmptyCollectionInitializer(expression); - } - /** - * Checks that given expression initializes empty Collection or Map with custom initial capacity or load factor - * - * @param expression expression to check - * @return true if the expression is the empty Collection or Map initializer with custom initial capacity or load factor - */ - @Contract("null -> false") - public static boolean isCustomizedEmptyCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList argumentList = ((PsiNewExpression) expression).getArgumentList(); - if (argumentList == null || argumentList.getExpressions().length == 0) { - return false; - } - PsiMethod constructor = ((PsiNewExpression) expression).resolveConstructor(); - if (constructor == null) { - return false; - } - PsiClass aClass = constructor.getContainingClass(); - if (aClass != null && (aClass.getQualifiedName() == null || !aClass.getQualifiedName().startsWith("java.util."))) { - return false; - } - if (!InheritanceUtil.isInheritor(aClass, JavaClassNames.JAVA_UTIL_COLLECTION) && !InheritanceUtil.isInheritor(aClass, JavaClassNames - .JAVA_UTIL_MAP)) { - return false; - } - Predicate allowedParameterType = t -> t instanceof PsiPrimitiveType || InheritanceUtil.isInheritor(t, JavaClassNames.JAVA_LANG_CLASS); - return Stream.of(constructor.getParameterList().getParameters()).map(PsiParameter::getType).allMatch(allowedParameterType); - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - if (ENUM_SET_NONE_OF.test(call)) { - return true; - } - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && argumentList.getExpressions().length > 0) { - PsiMethod method = call.resolveMethod(); - if (method != null && method.getParameterList().getParametersCount() > 0) { - PsiClass aClass = method.getContainingClass(); - if (aClass != null) { - String qualifiedName = aClass.getQualifiedName(); - if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return Stream.of(method.getParameterList().getParameters()).allMatch(p -> p.getType() instanceof PsiPrimitiveType); + /** + * Checks that given expression initializes empty Collection or Map with custom initial capacity or load factor + * + * @param expression expression to check + * @return true if the expression is the empty Collection or Map initializer with custom initial capacity or load factor + */ + @Contract("null -> false") + public static boolean isCustomizedEmptyCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression newExpr) { + PsiExpressionList argumentList = newExpr.getArgumentList(); + if (argumentList == null || argumentList.getExpressions().length == 0) { + return false; + } + PsiMethod constructor = newExpr.resolveConstructor(); + if (constructor == null) { + return false; + } + PsiClass aClass = constructor.getContainingClass(); + if (aClass != null && (aClass.getQualifiedName() == null || !aClass.getQualifiedName().startsWith("java.util."))) { + return false; + } + if (!InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_COLLECTION) + && !InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_MAP)) { + return false; + } + Predicate allowedParameterType = + t -> t instanceof PsiPrimitiveType || InheritanceUtil.isInheritor(t, CommonClassNames.JAVA_LANG_CLASS); + return Stream.of(constructor.getParameterList().getParameters()).map(PsiParameter::getType).allMatch(allowedParameterType); + } + if (expression instanceof PsiMethodCallExpression call) { + if (ENUM_SET_NONE_OF.test(call)) { + return true; + } + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && argumentList.getExpressions().length > 0) { + PsiMethod method = call.resolveMethod(); + if (method != null && method.getParameterList().getParametersCount() > 0) { + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + String qualifiedName = aClass.getQualifiedName(); + if (GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return Stream.of(method.getParameterList().getParameters()) + .allMatch(p -> p.getType() instanceof PsiPrimitiveType); + } + } + } } - } } - } + return false; } - return false; - } - public static boolean isPrepopulatedCollectionInitializer(PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiNewExpression) { - PsiExpressionList args = ((PsiNewExpression) expression).getArgumentList(); - if (args == null || args.isEmpty()) { - return false; - } - PsiMethod ctor = ((PsiNewExpression) expression).resolveMethod(); - if (ctor == null) { - return false; - } - PsiClass aClass = ctor.getContainingClass(); - if (aClass == null) { - return false; - } - String name = aClass.getQualifiedName(); - if (name == null || !name.startsWith("java.util.")) { - return false; - } - for (PsiParameter parameter : ctor.getParameterList().getParameters()) { - PsiType type = parameter.getType(); - if (type instanceof PsiClassType) { - PsiClassType rawType = ((PsiClassType) type).rawType(); - if (rawType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION) || - rawType.equalsToText(CommonClassNames.JAVA_UTIL_MAP)) { - return true; - } + public static boolean isPrepopulatedCollectionInitializer(PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiNewExpression newExpr) { + PsiExpressionList args = newExpr.getArgumentList(); + if (args == null || args.isEmpty()) { + return false; + } + PsiMethod ctor = newExpr.resolveMethod(); + if (ctor == null) { + return false; + } + PsiClass aClass = ctor.getContainingClass(); + if (aClass == null) { + return false; + } + String name = aClass.getQualifiedName(); + if (name == null || !name.startsWith("java.util.")) { + return false; + } + for (PsiParameter parameter : ctor.getParameterList().getParameters()) { + PsiType type = parameter.getType(); + if (type instanceof PsiClassType) { + PsiClassType rawType = ((PsiClassType)type).rawType(); + if (rawType.equalsToText(CommonClassNames.JAVA_UTIL_COLLECTION) + || rawType.equalsToText(CommonClassNames.JAVA_UTIL_MAP)) { + return true; + } + } + } } - } - } - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression) expression; - String name = call.getMethodExpression().getReferenceName(); - PsiExpressionList argumentList = call.getArgumentList(); - if (name != null && name.startsWith("new") && !argumentList.isEmpty()) { - PsiMethod method = call.resolveMethod(); - if (method == null) { - return false; + if (expression instanceof PsiMethodCallExpression call) { + String name = call.getMethodExpression().getReferenceName(); + PsiExpressionList argumentList = call.getArgumentList(); + if (name != null && name.startsWith("new") && !argumentList.isEmpty()) { + PsiMethod method = call.resolveMethod(); + if (method == null) { + return false; + } + PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; + } + String qualifiedName = aClass.getQualifiedName(); + if (!GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { + return false; + } + for (PsiParameter parameter : method.getParameterList().getParameters()) { + PsiType type = parameter.getType(); + if (type instanceof PsiEllipsisType) { + return true; + } + if (type instanceof PsiClassType) { + PsiClassType rawType = ((PsiClassType)type).rawType(); + if (rawType.equalsToText(CommonClassNames.JAVA_LANG_ITERABLE) + || rawType.equalsToText(CommonClassNames.JAVA_UTIL_ITERATOR)) { + return true; + } + } + } + } } - PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; + return false; + } + + /** + * Returns true if given expression is an empty array initializer + * + * @param expression expression to test + * @return true if supplied expression is an empty array initializer + */ + @RequiredReadAction + public static boolean isEmptyArrayInitializer(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (!(expression instanceof PsiNewExpression newExpr)) { + return false; } - String qualifiedName = aClass.getQualifiedName(); - if (!GUAVA_UTILITY_CLASSES.contains(qualifiedName)) { - return false; + PsiExpression[] dimensions = newExpr.getArrayDimensions(); + if (dimensions.length == 0) { + PsiArrayInitializerExpression arrayInitializer = newExpr.getArrayInitializer(); + if (arrayInitializer == null) { + return false; + } + PsiExpression[] initializers = arrayInitializer.getInitializers(); + return initializers.length == 0; } - for (PsiParameter parameter : method.getParameterList().getParameters()) { - PsiType type = parameter.getType(); - if (type instanceof PsiEllipsisType) { - return true; - } - if (type instanceof PsiClassType) { - PsiClassType rawType = ((PsiClassType) type).rawType(); - if (rawType.equalsToText(CommonClassNames.JAVA_LANG_ITERABLE) || - rawType.equalsToText(CommonClassNames.JAVA_UTIL_ITERATOR)) { - return true; + for (PsiExpression dimension : dimensions) { + String dimensionText = dimension.getText(); + if (!"0".equals(dimensionText)) { + return false; } - } } - } - } - return false; - } - - /** - * Returns true if given expression is an empty array initializer - * - * @param expression expression to test - * @return true if supplied expression is an empty array initializer - */ - public static boolean isEmptyArrayInitializer(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (!(expression instanceof PsiNewExpression)) { - return false; - } - final PsiNewExpression newExpression = (PsiNewExpression) expression; - final PsiExpression[] dimensions = newExpression.getArrayDimensions(); - if (dimensions.length == 0) { - final PsiArrayInitializerExpression arrayInitializer = newExpression.getArrayInitializer(); - if (arrayInitializer == null) { - return false; - } - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - return initializers.length == 0; - } - for (PsiExpression dimension : dimensions) { - final String dimensionText = dimension.getText(); - if (!"0".equals(dimensionText)) { - return false; - } + return true; } - return true; - } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ControlFlowUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ControlFlowUtils.java deleted file mode 100644 index 919d69262a..0000000000 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ControlFlowUtils.java +++ /dev/null @@ -1,993 +0,0 @@ -/* - * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.siyeh.ig.psiutils; - -import com.intellij.java.language.psi.*; -import consulo.language.psi.PsiComment; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiWhiteSpace; -import consulo.language.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class ControlFlowUtils { - - private ControlFlowUtils() { - } - - public static boolean isElseIf(PsiIfStatement ifStatement) { - final PsiElement parent = ifStatement.getParent(); - if (!(parent instanceof PsiIfStatement)) { - return false; - } - final PsiIfStatement parentStatement = (PsiIfStatement) parent; - final PsiStatement elseBranch = parentStatement.getElseBranch(); - return ifStatement.equals(elseBranch); - } - - public static boolean statementMayCompleteNormally(@Nullable PsiStatement statement) { - if (statement == null) { - return true; - } - if (statement instanceof PsiBreakStatement || statement instanceof PsiContinueStatement || - statement instanceof PsiReturnStatement || statement instanceof PsiThrowStatement) { - return false; - } else if (statement instanceof PsiExpressionListStatement || statement instanceof PsiEmptyStatement || - statement instanceof PsiAssertStatement || statement instanceof PsiDeclarationStatement || - statement instanceof PsiSwitchLabelStatement || statement instanceof PsiForeachStatement) { - return true; - } else if (statement instanceof PsiExpressionStatement) { - final PsiExpressionStatement expressionStatement = (PsiExpressionStatement) statement; - final PsiExpression expression = expressionStatement.getExpression(); - if (!(expression instanceof PsiMethodCallExpression)) { - return true; - } - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) expression; - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method == null) { - return true; - } - @NonNls final String methodName = method.getName(); - if (!methodName.equals("exit")) { - return true; - } - final PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return true; - } - final String className = aClass.getQualifiedName(); - return !"java.lang.System".equals(className); - } else if (statement instanceof PsiForStatement) { - return forStatementMayCompleteNormally((PsiForStatement) statement); - } else if (statement instanceof PsiWhileStatement) { - return whileStatementMayCompleteNormally((PsiWhileStatement) statement); - } else if (statement instanceof PsiDoWhileStatement) { - return doWhileStatementMayCompleteNormally((PsiDoWhileStatement) statement); - } else if (statement instanceof PsiSynchronizedStatement) { - final PsiCodeBlock body = ((PsiSynchronizedStatement) statement).getBody(); - return codeBlockMayCompleteNormally(body); - } else if (statement instanceof PsiBlockStatement) { - final PsiCodeBlock codeBlock = ((PsiBlockStatement) statement).getCodeBlock(); - return codeBlockMayCompleteNormally(codeBlock); - } else if (statement instanceof PsiLabeledStatement) { - return labeledStatementMayCompleteNormally((PsiLabeledStatement) statement); - } else if (statement instanceof PsiIfStatement) { - return ifStatementMayCompleteNormally((PsiIfStatement) statement); - } else if (statement instanceof PsiTryStatement) { - return tryStatementMayCompleteNormally((PsiTryStatement) statement); - } else if (statement instanceof PsiSwitchStatement) { - return switchStatementMayCompleteNormally((PsiSwitchStatement) statement); - } else if (statement instanceof PsiTemplateStatement || statement instanceof PsiClassLevelDeclarationStatement) { - return true; - } else { - assert false : "unknown statement type: " + statement.getClass(); - return true; - } - } - - private static boolean doWhileStatementMayCompleteNormally(@Nonnull PsiDoWhileStatement loopStatement) { - final PsiExpression condition = loopStatement.getCondition(); - final Object value = ExpressionUtils.computeConstantExpression(condition); - final PsiStatement body = loopStatement.getBody(); - return statementMayCompleteNormally(body) && value != Boolean.TRUE || statementIsBreakTarget(loopStatement) || statementContainsContinueToAncestor(loopStatement); - } - - private static boolean whileStatementMayCompleteNormally(@Nonnull PsiWhileStatement loopStatement) { - final PsiExpression condition = loopStatement.getCondition(); - final Object value = ExpressionUtils.computeConstantExpression(condition); - return value != Boolean.TRUE || statementIsBreakTarget(loopStatement) || statementContainsContinueToAncestor(loopStatement); - } - - private static boolean forStatementMayCompleteNormally(@Nonnull PsiForStatement loopStatement) { - if (statementIsBreakTarget(loopStatement)) { - return true; - } - if (statementContainsContinueToAncestor(loopStatement)) { - return true; - } - final PsiExpression condition = loopStatement.getCondition(); - if (condition == null) { - return false; - } - final Object value = ExpressionUtils.computeConstantExpression(condition); - return Boolean.TRUE != value; - } - - private static boolean switchStatementMayCompleteNormally(@Nonnull PsiSwitchStatement switchStatement) { - if (statementIsBreakTarget(switchStatement)) { - return true; - } - final PsiCodeBlock body = switchStatement.getBody(); - if (body == null) { - return true; - } - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) { - return true; - } - int numCases = 0; - boolean hasDefaultCase = false; - for (PsiStatement statement : statements) { - if (statement instanceof PsiSwitchLabelStatement) { - numCases++; - final PsiSwitchLabelStatement switchLabelStatement = (PsiSwitchLabelStatement) statement; - if (switchLabelStatement.isDefaultCase()) { - hasDefaultCase = true; - } - } - if (statement instanceof PsiBreakStatement) { - final PsiBreakStatement breakStatement = (PsiBreakStatement) statement; - if (breakStatement.getLabelIdentifier() == null) { - return true; - } - } - } - final boolean isEnum = isEnumSwitch(switchStatement); - if (!hasDefaultCase && !isEnum) { - return true; - } - if (!hasDefaultCase) { - final PsiExpression expression = switchStatement.getExpression(); - if (expression == null) { - return true; - } - final PsiClassType type = (PsiClassType) expression.getType(); - if (type == null) { - return true; - } - final PsiClass aClass = type.resolve(); - if (aClass == null) { - return true; - } - final PsiField[] fields = aClass.getFields(); - int numEnums = 0; - for (final PsiField field : fields) { - final PsiType fieldType = field.getType(); - if (fieldType.equals(type)) { - numEnums++; - } - } - if (numEnums != numCases) { - return true; - } - } - return statementMayCompleteNormally(statements[statements.length - 1]); - } - - private static boolean isEnumSwitch(PsiSwitchStatement statement) { - final PsiExpression expression = statement.getExpression(); - if (expression == null) { - return false; - } - final PsiType type = expression.getType(); - if (type == null) { - return false; - } - if (!(type instanceof PsiClassType)) { - return false; - } - final PsiClass aClass = ((PsiClassType) type).resolve(); - return aClass != null && aClass.isEnum(); - } - - private static boolean tryStatementMayCompleteNormally(@Nonnull PsiTryStatement tryStatement) { - final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); - if (finallyBlock != null) { - if (!codeBlockMayCompleteNormally(finallyBlock)) { - return false; - } - } - final PsiCodeBlock tryBlock = tryStatement.getTryBlock(); - if (codeBlockMayCompleteNormally(tryBlock)) { - return true; - } - final PsiCodeBlock[] catchBlocks = tryStatement.getCatchBlocks(); - for (final PsiCodeBlock catchBlock : catchBlocks) { - if (codeBlockMayCompleteNormally(catchBlock)) { - return true; - } - } - return false; - } - - private static boolean ifStatementMayCompleteNormally(@Nonnull PsiIfStatement ifStatement) { - final PsiExpression condition = ifStatement.getCondition(); - final Object value = ExpressionUtils.computeConstantExpression(condition); - final PsiStatement thenBranch = ifStatement.getThenBranch(); - if (value == Boolean.TRUE) { - return statementMayCompleteNormally(thenBranch); - } - final PsiStatement elseBranch = ifStatement.getElseBranch(); - if (value == Boolean.FALSE) { - return statementMayCompleteNormally(elseBranch); - } - // process branch with fewer statements first - PsiStatement branch1; - PsiStatement branch2; - if ((thenBranch == null ? 0 : thenBranch.getTextLength()) < (elseBranch == null ? 0 : elseBranch.getTextLength())) { - branch1 = thenBranch; - branch2 = elseBranch; - } else { - branch2 = thenBranch; - branch1 = elseBranch; - } - return statementMayCompleteNormally(branch1) || statementMayCompleteNormally(branch2); - } - - private static boolean labeledStatementMayCompleteNormally(@Nonnull PsiLabeledStatement labeledStatement) { - final PsiStatement statement = labeledStatement.getStatement(); - if (statement == null) { - return false; - } - return statementMayCompleteNormally(statement) || statementIsBreakTarget(statement); - } - - public static boolean codeBlockMayCompleteNormally(@Nullable PsiCodeBlock block) { - if (block == null) { - return true; - } - final PsiStatement[] statements = block.getStatements(); - for (final PsiStatement statement : statements) { - if (!statementMayCompleteNormally(statement)) { - return false; - } - } - return true; - } - - private static boolean statementIsBreakTarget(@Nonnull PsiStatement statement) { - final BreakFinder breakFinder = new BreakFinder(statement); - statement.accept(breakFinder); - return breakFinder.breakFound(); - } - - private static boolean statementContainsContinueToAncestor(@Nonnull PsiStatement statement) { - PsiElement parent = statement.getParent(); - while (parent instanceof PsiLabeledStatement) { - statement = (PsiStatement) parent; - parent = parent.getParent(); - } - final ContinueToAncestorFinder continueToAncestorFinder = new ContinueToAncestorFinder(statement); - statement.accept(continueToAncestorFinder); - return continueToAncestorFinder.continueToAncestorFound(); - } - - public static boolean containsReturn(@Nonnull PsiElement element) { - final ReturnFinder returnFinder = new ReturnFinder(); - element.accept(returnFinder); - return returnFinder.returnFound(); - } - - public static boolean statementIsContinueTarget(@Nonnull PsiStatement statement) { - final ContinueFinder continueFinder = new ContinueFinder(statement); - statement.accept(continueFinder); - return continueFinder.continueFound(); - } - - public static boolean containsSystemExit(@Nonnull PsiElement element) { - final SystemExitFinder systemExitFinder = new SystemExitFinder(); - element.accept(systemExitFinder); - return systemExitFinder.exitFound(); - } - - public static boolean elementContainsCallToMethod(PsiElement context, String containingClassName, PsiType returnType, String methodName, PsiType... parameterTypes) { - final MethodCallFinder methodCallFinder = new MethodCallFinder(containingClassName, returnType, methodName, parameterTypes); - context.accept(methodCallFinder); - return methodCallFinder.containsCallToMethod(); - } - - public static boolean isInLoop(@Nonnull PsiElement element) { - final PsiLoopStatement loopStatement = PsiTreeUtil.getParentOfType(element, PsiLoopStatement.class, true, PsiClass.class); - if (loopStatement == null) { - return false; - } - final PsiStatement body = loopStatement.getBody(); - return body != null && PsiTreeUtil.isAncestor(body, element, true); - } - - public static boolean isInFinallyBlock(@Nonnull PsiElement element) { - PsiElement currentElement = element; - while (true) { - final PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(currentElement, PsiTryStatement.class, true, PsiClass.class, PsiLambdaExpression.class); - if (tryStatement == null) { - return false; - } - final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); - if (finallyBlock != null) { - if (PsiTreeUtil.isAncestor(finallyBlock, currentElement, true)) { - final PsiMethod elementMethod = PsiTreeUtil.getParentOfType(currentElement, PsiMethod.class); - final PsiMethod finallyMethod = PsiTreeUtil.getParentOfType(finallyBlock, PsiMethod.class); - return elementMethod != null && elementMethod.equals(finallyMethod); - } - } - currentElement = tryStatement; - } - } - - public static boolean isInCatchBlock(@Nonnull PsiElement element) { - return PsiTreeUtil.getParentOfType(element, PsiCatchSection.class, true, PsiClass.class) != null; - } - - public static boolean isInExitStatement(@Nonnull PsiExpression expression) { - return isInReturnStatementArgument(expression) || isInThrowStatementArgument(expression); - } - - private static boolean isInReturnStatementArgument(@Nonnull PsiExpression expression) { - return PsiTreeUtil.getParentOfType(expression, PsiReturnStatement.class) != null; - } - - public static boolean isInThrowStatementArgument(@Nonnull PsiExpression expression) { - return PsiTreeUtil.getParentOfType(expression, PsiThrowStatement.class) != null; - } - - @Nullable - public static PsiStatement stripBraces(@Nullable PsiStatement statement) { - if (statement instanceof PsiBlockStatement) { - final PsiBlockStatement block = (PsiBlockStatement) statement; - final PsiStatement onlyStatement = getOnlyStatementInBlock(block.getCodeBlock()); - return (onlyStatement != null) ? onlyStatement : block; - } else { - return statement; - } - } - - public static boolean statementCompletesWithStatement(@Nonnull PsiStatement containingStatement, @Nonnull PsiStatement statement) { - PsiElement statementToCheck = statement; - while (true) { - if (statementToCheck.equals(containingStatement)) { - return true; - } - final PsiElement container = getContainingStatementOrBlock(statementToCheck); - if (container == null) { - return false; - } - if (container instanceof PsiCodeBlock) { - if (!statementIsLastInBlock((PsiCodeBlock) container, (PsiStatement) statementToCheck)) { - return false; - } - } - if (container instanceof PsiLoopStatement) { - return false; - } - statementToCheck = container; - } - } - - public static boolean blockCompletesWithStatement(@Nonnull PsiCodeBlock body, @Nonnull PsiStatement statement) { - PsiElement statementToCheck = statement; - while (true) { - if (statementToCheck == null) { - return false; - } - final PsiElement container = getContainingStatementOrBlock(statementToCheck); - if (container == null) { - return false; - } - if (container instanceof PsiLoopStatement) { - return false; - } - if (container instanceof PsiCodeBlock) { - if (!statementIsLastInBlock((PsiCodeBlock) container, (PsiStatement) statementToCheck)) { - return false; - } - if (container.equals(body)) { - return true; - } - statementToCheck = PsiTreeUtil.getParentOfType(container, PsiStatement.class); - } else { - statementToCheck = container; - } - } - } - - @Nullable - private static PsiElement getContainingStatementOrBlock(@Nonnull PsiElement statement) { - return PsiTreeUtil.getParentOfType(statement, PsiStatement.class, PsiCodeBlock.class); - } - - private static boolean statementIsLastInBlock(@Nonnull PsiCodeBlock block, @Nonnull PsiStatement statement) { - for (PsiElement child = block.getLastChild(); child != null; child = child.getPrevSibling()) { - if (!(child instanceof PsiStatement)) { - continue; - } - final PsiStatement childStatement = (PsiStatement) child; - if (statement.equals(childStatement)) { - return true; - } - if (!(statement instanceof PsiEmptyStatement)) { - return false; - } - } - return false; - } - - @Nullable - public static PsiStatement getFirstStatementInBlock(@Nullable PsiCodeBlock codeBlock) { - return PsiTreeUtil.getChildOfType(codeBlock, PsiStatement.class); - } - - @Nullable - public static PsiStatement getLastStatementInBlock(@Nullable PsiCodeBlock codeBlock) { - return getLastChildOfType(codeBlock, PsiStatement.class); - } - - private static T getLastChildOfType(@Nullable PsiElement element, @Nonnull Class aClass) { - if (element == null) { - return null; - } - for (PsiElement child = element.getLastChild(); child != null; child = child.getPrevSibling()) { - if (aClass.isInstance(child)) { - //noinspection unchecked - return (T) child; - } - } - return null; - } - - /** - * @return null, if zero or more than one statements in the specified code block. - */ - @Nullable - public static PsiStatement getOnlyStatementInBlock(@Nullable PsiCodeBlock codeBlock) { - return getOnlyChildOfType(codeBlock, PsiStatement.class); - } - - static T getOnlyChildOfType(@Nullable PsiElement element, @Nonnull Class aClass) { - if (element == null) { - return null; - } - T result = null; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - if (result == null) { - //noinspection unchecked - result = (T) child; - } else { - return null; - } - } - } - return result; - } - - public static boolean hasStatementCount(@Nullable PsiCodeBlock codeBlock, int count) { - return hasChildrenOfTypeCount(codeBlock, count, PsiStatement.class); - } - - static boolean hasChildrenOfTypeCount(@Nullable PsiElement element, int count, @Nonnull Class aClass) { - if (element == null) { - return false; - } - int i = 0; - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - if (aClass.isInstance(child)) { - i++; - if (i > count) { - return false; - } - } - } - return i == count; - } - - public static boolean isEmptyCodeBlock(PsiCodeBlock codeBlock) { - return hasStatementCount(codeBlock, 0); - } - - public static boolean methodAlwaysThrowsException(@Nonnull PsiMethod method) { - final PsiCodeBlock body = method.getBody(); - if (body == null) { - return true; - } - return !containsReturn(body) && !codeBlockMayCompleteNormally(body); - } - - public static boolean lambdaExpressionAlwaysThrowsException(PsiLambdaExpression expression) { - final PsiElement body = expression.getBody(); - if (body instanceof PsiExpression) { - return false; - } - if (!(body instanceof PsiCodeBlock)) { - return true; - } - final PsiCodeBlock codeBlock = (PsiCodeBlock) body; - return !containsReturn(codeBlock) && !codeBlockMayCompleteNormally(codeBlock); - } - - public static boolean statementContainsNakedBreak(PsiStatement statement) { - if (statement == null) { - return false; - } - final NakedBreakFinder breakFinder = new NakedBreakFinder(); - statement.accept(breakFinder); - return breakFinder.breakFound(); - } - - /** - * Checks whether the given statement effectively breaks given loop. Returns true - * if the statement is {@link PsiBreakStatement} having given loop as a target. Also may return - * true in other cases if the statement is semantically equivalent to break like this: - *

- *

{@code
-   * int myMethod(int[] data) {
-   *   for(int val : data) {
-   *     if(val == 5) {
-   *       System.out.println(val);
-   *       return 0; // this statement is semantically equivalent to break.
-   *     }
-   *   }
-   *   return 0;
-   * }}
- * - * @param statement statement which may break the loop - * @param loop a loop to break - * @return true if the statement actually breaks the loop - */ - @Contract("null, _ -> false") - public static boolean statementBreaksLoop(PsiStatement statement, PsiLoopStatement loop) { - if (statement instanceof PsiBreakStatement) { - return ((PsiBreakStatement) statement).findExitedStatement() == loop; - } - if (statement instanceof PsiReturnStatement) { - PsiExpression returnValue = ((PsiReturnStatement) statement).getReturnValue(); - PsiElement cur = loop; - for (PsiElement parent = cur.getParent(); ; parent = cur.getParent()) { - if (parent instanceof PsiLabeledStatement) { - cur = parent; - } else if (parent instanceof PsiCodeBlock) { - PsiCodeBlock block = (PsiCodeBlock) parent; - PsiStatement[] statements = block.getStatements(); - if (block.getParent() instanceof PsiBlockStatement && statements.length > 0 && statements[statements.length - 1] == cur) { - cur = block.getParent(); - } else { - break; - } - } else if (parent instanceof PsiIfStatement) { - if (cur == ((PsiIfStatement) parent).getThenBranch() || cur == ((PsiIfStatement) parent).getElseBranch()) { - cur = parent; - } else { - break; - } - } else { - break; - } - } - PsiElement nextElement = PsiTreeUtil.skipSiblingsForward(cur, PsiComment.class, PsiWhiteSpace.class); - if (nextElement instanceof PsiReturnStatement) { - return EquivalenceChecker.getCanonicalPsiEquivalence().expressionsAreEquivalent(returnValue, ((PsiReturnStatement) nextElement).getReturnValue()); - } - if (nextElement == null && returnValue == null && cur.getParent() instanceof PsiMethod) { - return true; - } - } - return false; - } - - /** - * Checks whether control flow after executing given statement will definitely not go into the next iteration of given loop. - * - * @param statement executed statement. It's not checked whether this statement itself breaks the loop. - * @param loop a surrounding loop. Must be parent of statement - * @return true if it can be statically defined that next loop iteration will not be executed. - */ - @Contract("null, _ -> false") - public static boolean flowBreaksLoop(PsiStatement statement, PsiLoopStatement loop) { - if (statement == null || statement == loop) { - return false; - } - for (PsiStatement sibling = nextExecutedStatement(statement); sibling != null; sibling = nextExecutedStatement(sibling)) { - if (sibling instanceof PsiContinueStatement) { - return false; - } - if (sibling instanceof PsiThrowStatement || sibling instanceof PsiReturnStatement) { - return true; - } - if (sibling instanceof PsiBreakStatement) { - PsiBreakStatement breakStatement = (PsiBreakStatement) sibling; - PsiStatement exitedStatement = breakStatement.findExitedStatement(); - if (exitedStatement == loop) { - return true; - } - return flowBreaksLoop(exitedStatement, loop); - } - } - return false; - } - - /** - * Returns true if given element is an empty statement - * - * @param element element to check - * @param commentIsContent if true, empty statement containing comments is not considered empty - * @param emptyBlocks if true, empty block (or nested empty block like {@code {{}}}) is considered an empty statement - * @return true if given element is an empty statement - */ - public static boolean isEmpty(PsiElement element, boolean commentIsContent, boolean emptyBlocks) { - if (!commentIsContent && element instanceof PsiComment) { - return true; - } else if (element instanceof PsiEmptyStatement) { - return !commentIsContent || - PsiTreeUtil.getChildOfType(element, PsiComment.class) == null && - !(PsiTreeUtil.skipWhitespacesBackward(element) instanceof PsiComment); - } else if (element instanceof PsiWhiteSpace) { - return true; - } else if (element instanceof PsiBlockStatement) { - final PsiBlockStatement block = (PsiBlockStatement) element; - return isEmpty(block.getCodeBlock(), commentIsContent, emptyBlocks); - } else if (emptyBlocks && element instanceof PsiCodeBlock) { - final PsiCodeBlock codeBlock = (PsiCodeBlock) element; - final PsiElement[] children = codeBlock.getChildren(); - if (children.length == 2) { - return true; - } - for (int i = 1; i < children.length - 1; i++) { - final PsiElement child = children[i]; - if (!isEmpty(child, commentIsContent, true)) { - return false; - } - } - return true; - } - return false; - } - - @Nullable - private static PsiStatement nextExecutedStatement(PsiStatement statement) { - PsiStatement next = PsiTreeUtil.getNextSiblingOfType(statement, PsiStatement.class); - while (next instanceof PsiBlockStatement) { - PsiStatement[] statements = ((PsiBlockStatement) next).getCodeBlock().getStatements(); - if (statements.length == 0) { - break; - } - next = statements[0]; - } - if (next == null) { - PsiElement parent = statement.getParent(); - if (parent instanceof PsiCodeBlock) { - PsiElement gParent = parent.getParent(); - if (gParent instanceof PsiBlockStatement || gParent instanceof PsiSwitchStatement) { - return nextExecutedStatement((PsiStatement) gParent); - } - } else if (parent instanceof PsiLabeledStatement || parent instanceof PsiIfStatement || parent instanceof PsiSwitchLabelStatement || parent instanceof PsiSwitchStatement) { - return nextExecutedStatement((PsiStatement) parent); - } - } - return next; - } - - private static class NakedBreakFinder extends JavaRecursiveElementWalkingVisitor { - private boolean m_found; - - private boolean breakFound() { - return m_found; - } - - @Override - public void visitElement(PsiElement element) { - if (m_found) { - return; - } - super.visitElement(element); - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - } - - @Override - public void visitBreakStatement(PsiBreakStatement statement) { - if (statement.getLabelIdentifier() != null) { - return; - } - m_found = true; - } - - @Override - public void visitDoWhileStatement(PsiDoWhileStatement statement) { - // don't drill down - } - - @Override - public void visitForStatement(PsiForStatement statement) { - // don't drill down - } - - @Override - public void visitForeachStatement(PsiForeachStatement statement) { - // don't drill down - } - - @Override - public void visitWhileStatement(PsiWhileStatement statement) { - // don't drill down - } - - @Override - public void visitSwitchStatement(PsiSwitchStatement statement) { - // don't drill down - } - } - - private static class SystemExitFinder extends JavaRecursiveElementWalkingVisitor { - - private boolean m_found; - - private boolean exitFound() { - return m_found; - } - - @Override - public void visitClass(@Nonnull PsiClass aClass) { - // do nothing to keep from drilling into inner classes - } - - @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression expression) { - if (m_found) { - return; - } - super.visitMethodCallExpression(expression); - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - return; - } - @NonNls final String methodName = method.getName(); - if (!methodName.equals("exit")) { - return; - } - final PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return; - } - final String className = aClass.getQualifiedName(); - if (!"java.lang.System".equals(className) && !"java.lang.Runtime".equals(className)) { - return; - } - m_found = true; - } - } - - private static class ReturnFinder extends JavaRecursiveElementWalkingVisitor { - - private boolean m_found; - - private boolean returnFound() { - return m_found; - } - - @Override - public void visitClass(@Nonnull PsiClass psiClass) { - // do nothing, to keep drilling into inner classes - } - - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - } - - @Override - public void visitReturnStatement(@Nonnull PsiReturnStatement returnStatement) { - if (m_found) { - return; - } - super.visitReturnStatement(returnStatement); - m_found = true; - } - } - - private static class BreakFinder extends JavaRecursiveElementWalkingVisitor { - - private boolean m_found; - private final PsiStatement m_target; - - private BreakFinder(@Nonnull PsiStatement target) { - m_target = target; - } - - private boolean breakFound() { - return m_found; - } - - @Override - public void visitBreakStatement(@Nonnull PsiBreakStatement statement) { - if (m_found) { - return; - } - super.visitBreakStatement(statement); - final PsiStatement exitedStatement = statement.findExitedStatement(); - if (exitedStatement == null) { - return; - } - if (PsiTreeUtil.isAncestor(exitedStatement, m_target, false)) { - m_found = true; - } - } - - @Override - public void visitIfStatement(PsiIfStatement statement) { - if (m_found) { - return; - } - final PsiExpression condition = statement.getCondition(); - final Object value = ExpressionUtils.computeConstantExpression(condition); - if (Boolean.FALSE != value) { - final PsiStatement thenBranch = statement.getThenBranch(); - if (thenBranch != null) { - thenBranch.accept(this); - } - } - if (Boolean.TRUE != value) { - final PsiStatement elseBranch = statement.getElseBranch(); - if (elseBranch != null) { - elseBranch.accept(this); - } - } - } - } - - private static class ContinueFinder extends JavaRecursiveElementWalkingVisitor { - - private boolean m_found; - private final PsiStatement m_target; - - private ContinueFinder(@Nonnull PsiStatement target) { - m_target = target; - } - - private boolean continueFound() { - return m_found; - } - - @Override - public void visitContinueStatement(@Nonnull PsiContinueStatement statement) { - if (m_found) { - return; - } - super.visitContinueStatement(statement); - final PsiStatement continuedStatement = statement.findContinuedStatement(); - if (continuedStatement == null) { - return; - } - if (PsiTreeUtil.isAncestor(continuedStatement, m_target, false)) { - m_found = true; - } - } - - @Override - public void visitIfStatement(PsiIfStatement statement) { - if (m_found) { - return; - } - final PsiExpression condition = statement.getCondition(); - final Object value = ExpressionUtils.computeConstantExpression(condition); - if (Boolean.FALSE != value) { - final PsiStatement thenBranch = statement.getThenBranch(); - if (thenBranch != null) { - thenBranch.accept(this); - } - } - if (Boolean.TRUE != value) { - final PsiStatement elseBranch = statement.getElseBranch(); - if (elseBranch != null) { - elseBranch.accept(this); - } - } - } - } - - private static class MethodCallFinder extends JavaRecursiveElementWalkingVisitor { - - private final String containingClassName; - private final PsiType returnType; - private final String methodName; - private final PsiType[] parameterTypeNames; - private boolean containsCallToMethod; - - private MethodCallFinder(String containingClassName, PsiType returnType, String methodName, PsiType... parameterTypeNames) { - this.containingClassName = containingClassName; - this.returnType = returnType; - this.methodName = methodName; - this.parameterTypeNames = parameterTypeNames; - } - - @Override - public void visitElement(PsiElement element) { - if (containsCallToMethod) { - return; - } - super.visitElement(element); - } - - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - if (containsCallToMethod) { - return; - } - super.visitMethodCallExpression(expression); - if (!MethodCallUtils.isCallToMethod(expression, containingClassName, returnType, methodName, parameterTypeNames)) { - return; - } - containsCallToMethod = true; - } - - private boolean containsCallToMethod() { - return containsCallToMethod; - } - } - - private static class ContinueToAncestorFinder extends JavaRecursiveElementWalkingVisitor { - - private final PsiStatement statement; - private boolean found; - - private ContinueToAncestorFinder(PsiStatement statement) { - this.statement = statement; - } - - @Override - public void visitElement(PsiElement element) { - if (found) { - return; - } - super.visitElement(element); - } - - @Override - public void visitContinueStatement(PsiContinueStatement continueStatement) { - if (found) { - return; - } - super.visitContinueStatement(continueStatement); - final PsiIdentifier labelIdentifier = continueStatement.getLabelIdentifier(); - if (labelIdentifier == null) { - return; - } - final PsiStatement continuedStatement = continueStatement.findContinuedStatement(); - if (continuedStatement == null) { - return; - } - if (PsiTreeUtil.isAncestor(continuedStatement, statement, true)) { - found = true; - } - } - - private boolean continueToAncestorFound() { - return found; - } - } -} diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CountingLoop.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CountingLoop.java index 7f82496eef..16781a2e85 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CountingLoop.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/CountingLoop.java @@ -21,8 +21,7 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import consulo.language.ast.IElementType; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import static consulo.util.lang.ObjectUtil.tryCast; @@ -34,24 +33,20 @@ public class CountingLoop { final - @Nonnull PsiLocalVariable myCounter; final - @Nonnull PsiLoopStatement myLoop; final - @Nonnull PsiExpression myInitializer; final - @Nonnull PsiExpression myBound; final boolean myIncluding; final boolean myDescending; - private CountingLoop(@Nonnull PsiLoopStatement loop, - @Nonnull PsiLocalVariable counter, - @Nonnull PsiExpression initializer, - @Nonnull PsiExpression bound, + private CountingLoop(PsiLoopStatement loop, + PsiLocalVariable counter, + PsiExpression initializer, + PsiExpression bound, boolean including, boolean descending) { @@ -66,7 +61,6 @@ private CountingLoop(@Nonnull PsiLoopStatement loop, /** * @return loop counter variable */ - @Nonnull public PsiLocalVariable getCounter() { return myCounter; @@ -75,7 +69,6 @@ public PsiLocalVariable getCounter() /** * @return loop statement */ - @Nonnull public PsiLoopStatement getLoop() { return myLoop; @@ -84,7 +77,6 @@ public PsiLoopStatement getLoop() /** * @return counter variable initial value */ - @Nonnull public PsiExpression getInitializer() { return myInitializer; @@ -93,7 +85,6 @@ public PsiExpression getInitializer() /** * @return loop bound */ - @Nonnull public PsiExpression getBound() { return myBound; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/DeclarationSearchUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/DeclarationSearchUtils.java index 9f484db767..a30839cf08 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/DeclarationSearchUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/DeclarationSearchUtils.java @@ -15,6 +15,7 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.analysis.impl.psi.controlFlow.DefUseUtil; import com.intellij.java.language.psi.*; import consulo.application.progress.ProgressManager; @@ -26,8 +27,8 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.project.Project; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; + import java.util.ArrayList; import java.util.List; @@ -36,7 +37,7 @@ public class DeclarationSearchUtils { private DeclarationSearchUtils() { } - public static boolean variableNameResolvesToTarget(@Nonnull String variableName, @Nonnull PsiVariable target, @Nonnull PsiElement context) { + public static boolean variableNameResolvesToTarget(String variableName, PsiVariable target, PsiElement context) { final Project project = context.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); @@ -98,7 +99,7 @@ private static void collectFollowingBlocks(PsiElement element, List s_genericExceptionTypes = new HashSet<>(4); static { - s_genericExceptionTypes.add(JavaClassNames.JAVA_LANG_THROWABLE); - s_genericExceptionTypes.add(JavaClassNames.JAVA_LANG_EXCEPTION); - s_genericExceptionTypes.add(JavaClassNames.JAVA_LANG_RUNTIME_EXCEPTION); - s_genericExceptionTypes.add(JavaClassNames.JAVA_LANG_ERROR); + s_genericExceptionTypes.add(CommonClassNames.JAVA_LANG_THROWABLE); + s_genericExceptionTypes.add(CommonClassNames.JAVA_LANG_EXCEPTION); + s_genericExceptionTypes.add(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION); + s_genericExceptionTypes.add(CommonClassNames.JAVA_LANG_ERROR); } - @Nonnull public static Set calculateExceptionsThrown(@Nullable PsiElement element) { return calculateExceptionsThrown(element, new LinkedHashSet<>(5)); } - @Nonnull - public static Set calculateExceptionsThrown(@Nullable PsiElement element, @Nonnull Set out) { + public static Set calculateExceptionsThrown(@Nullable PsiElement element, Set out) { if (element == null) { return out; } @@ -205,11 +202,11 @@ public static Set getExceptionTypesHandled(PsiTryStatement statement) { return out; } - public static boolean isExceptionArgument(@Nonnull PsiExpression expression) { + public static boolean isExceptionArgument(PsiExpression expression) { final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(expression, PsiNewExpression.class, true, PsiCodeBlock.class, PsiClass.class); if (newExpression != null) { final PsiType newExpressionType = newExpression.getType(); - if (InheritanceUtil.isInheritor(newExpressionType, JavaClassNames.JAVA_LANG_THROWABLE)) { + if (InheritanceUtil.isInheritor(newExpressionType, CommonClassNames.JAVA_LANG_THROWABLE)) { return true; } } else { @@ -263,7 +260,7 @@ public void visitThrowStatement(PsiThrowStatement statement) { } @Override - public void visitTryStatement(@Nonnull PsiTryStatement statement) { + public void visitTryStatement(PsiTryStatement statement) { final Set exceptionsHandled = getExceptionTypesHandled(statement); for (PsiClassType resourceException : calculateExceptionsThrown(statement.getResourceList())) { @@ -282,7 +279,7 @@ public void visitTryStatement(@Nonnull PsiTryStatement statement) { } } - private static boolean isExceptionHandled(Set exceptionsHandled, @Nonnull PsiType thrownType) { + private static boolean isExceptionHandled(Set exceptionsHandled, PsiType thrownType) { if (exceptionsHandled.contains(thrownType)) { return true; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpectedTypeUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpectedTypeUtils.java index ab2bf394bc..cd3077a81b 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpectedTypeUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpectedTypeUtils.java @@ -19,16 +19,14 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiTypesUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiManager; import consulo.language.util.IncorrectOperationException; import consulo.project.Project; import consulo.util.collection.ArrayUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; @@ -38,7 +36,7 @@ private ExpectedTypeUtils() { } @Nullable - public static PsiType findExpectedType(@Nonnull PsiExpression expression, boolean calculateTypeForComplexReferences) { + public static PsiType findExpectedType(PsiExpression expression, boolean calculateTypeForComplexReferences) { return findExpectedType(expression, calculateTypeForComplexReferences, false); } @@ -100,13 +98,12 @@ private static class ExpectedTypeVisitor extends JavaElementVisitor { operatorAssignmentOps.add(JavaTokenType.GTGTGTEQ); } - @Nonnull private final PsiExpression wrappedExpression; private final boolean calculateTypeForComplexReferences; private final boolean reportCasts; private PsiType expectedType = null; - ExpectedTypeVisitor(@Nonnull PsiExpression wrappedExpression, boolean calculateTypeForComplexReferences, boolean reportCasts) { + ExpectedTypeVisitor(PsiExpression wrappedExpression, boolean calculateTypeForComplexReferences, boolean reportCasts) { this.wrappedExpression = wrappedExpression; this.calculateTypeForComplexReferences = calculateTypeForComplexReferences; this.reportCasts = reportCasts; @@ -117,7 +114,7 @@ public PsiType getExpectedType() { } @Override - public void visitField(@Nonnull PsiField field) { + public void visitField(PsiField field) { final PsiExpression initializer = field.getInitializer(); if (wrappedExpression.equals(initializer)) { expectedType = field.getType(); @@ -125,7 +122,7 @@ public void visitField(@Nonnull PsiField field) { } @Override - public void visitVariable(@Nonnull PsiVariable variable) { + public void visitVariable(PsiVariable variable) { expectedType = variable.getType(); } @@ -159,7 +156,7 @@ public void visitArrayAccessExpression(PsiArrayAccessExpression accessExpression } @Override - public void visitPolyadicExpression(@Nonnull PsiPolyadicExpression polyadicExpression) { + public void visitPolyadicExpression(PsiPolyadicExpression polyadicExpression) { final PsiExpression[] operands = polyadicExpression.getOperands(); if (operands.length < 2) { expectedType = null; @@ -225,7 +222,7 @@ private PsiType expectedPrimitiveType(PsiPrimitiveType type1, PsiType type2) { } @Override - public void visitPrefixExpression(@Nonnull PsiPrefixExpression expression) { + public void visitPrefixExpression(PsiPrefixExpression expression) { final PsiType type = expression.getType(); if (type instanceof PsiPrimitiveType) { expectedType = type; @@ -235,7 +232,7 @@ public void visitPrefixExpression(@Nonnull PsiPrefixExpression expression) { } @Override - public void visitPostfixExpression(@Nonnull PsiPostfixExpression expression) { + public void visitPostfixExpression(PsiPostfixExpression expression) { final PsiType type = expression.getType(); if (type instanceof PsiPrimitiveType) { expectedType = type; @@ -267,12 +264,12 @@ public void visitTypeCastExpression(PsiTypeCastExpression expression) { } @Override - public void visitWhileStatement(@Nonnull PsiWhileStatement whileStatement) { + public void visitWhileStatement(PsiWhileStatement whileStatement) { expectedType = PsiType.BOOLEAN; } @Override - public void visitForStatement(@Nonnull PsiForStatement statement) { + public void visitForStatement(PsiForStatement statement) { expectedType = PsiType.BOOLEAN; } @@ -290,7 +287,7 @@ public void visitForeachStatement(PsiForeachStatement statement) { } final PsiClassType classType = (PsiClassType) iteratedValueType; final PsiType[] parameters = classType.getParameters(); - final PsiClass iterableClass = ClassUtils.findClass(JavaClassNames.JAVA_LANG_ITERABLE, statement); + final PsiClass iterableClass = ClassUtils.findClass(CommonClassNames.JAVA_LANG_ITERABLE, statement); if (iterableClass == null) { expectedType = null; } else { @@ -299,22 +296,22 @@ public void visitForeachStatement(PsiForeachStatement statement) { } @Override - public void visitIfStatement(@Nonnull PsiIfStatement statement) { + public void visitIfStatement(PsiIfStatement statement) { expectedType = PsiType.BOOLEAN; } @Override - public void visitDoWhileStatement(@Nonnull PsiDoWhileStatement statement) { + public void visitDoWhileStatement(PsiDoWhileStatement statement) { expectedType = PsiType.BOOLEAN; } @Override - public void visitSynchronizedStatement(@Nonnull PsiSynchronizedStatement statement) { + public void visitSynchronizedStatement(PsiSynchronizedStatement statement) { expectedType = TypeUtils.getObjectType(statement); } @Override - public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression assignment) { + public void visitAssignmentExpression(PsiAssignmentExpression assignment) { final PsiExpression rExpression = assignment.getRExpression(); final IElementType tokenType = assignment.getOperationTokenType(); final PsiExpression lExpression = assignment.getLExpression(); @@ -358,7 +355,7 @@ public void visitConditionalExpression(PsiConditionalExpression conditional) { } @Override - public void visitReturnStatement(@Nonnull PsiReturnStatement returnStatement) { + public void visitReturnStatement(PsiReturnStatement returnStatement) { expectedType = PsiTypesUtil.getMethodReturnType(returnStatement); } @@ -409,7 +406,6 @@ public void visitNewExpression(PsiNewExpression expression) { } } - @Nonnull private static JavaResolveResult findCalledMethod(PsiExpressionList expressionList) { final PsiElement parent = expressionList.getParent(); if (parent instanceof PsiCall) { @@ -426,7 +422,7 @@ private static JavaResolveResult findCalledMethod(PsiExpressionList expressionLi } @Override - public void visitReferenceExpression(@Nonnull PsiReferenceExpression referenceExpression) { + public void visitReferenceExpression(PsiReferenceExpression referenceExpression) { if (calculateTypeForComplexReferences) { final Project project = referenceExpression.getProject(); final JavaResolveResult resolveResult = referenceExpression.advancedResolve(false); @@ -559,28 +555,28 @@ private static boolean isAccessibleFrom(PsiMember member, PsiElement referencing return ClassUtils.inSamePackage(containingClass, referencingLocation); } - private static boolean isArithmeticOperation(@Nonnull IElementType sign) { + private static boolean isArithmeticOperation(IElementType sign) { return arithmeticOps.contains(sign); } - private static boolean isBooleanOperation(@Nonnull IElementType sign) { + private static boolean isBooleanOperation(IElementType sign) { return booleanOps.contains(sign); } - private static boolean isShiftOperation(@Nonnull IElementType sign) { + private static boolean isShiftOperation(IElementType sign) { return shiftOps.contains(sign); } - private static boolean isOperatorAssignmentOperation(@Nonnull IElementType sign) { + private static boolean isOperatorAssignmentOperation(IElementType sign) { return operatorAssignmentOps.contains(sign); } - private static int getParameterPosition(@Nonnull PsiExpressionList expressionList, PsiExpression expression) { + private static int getParameterPosition(PsiExpressionList expressionList, PsiExpression expression) { return ArrayUtil.indexOf(expressionList.getExpressions(), expression); } @Nullable - private static PsiType getTypeOfParameter(@Nonnull JavaResolveResult result, int parameterPosition) { + private static PsiType getTypeOfParameter(JavaResolveResult result, int parameterPosition) { final PsiMethod method = (PsiMethod) result.getElement(); if (method == null) { return null; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpressionUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpressionUtils.java index 80d940bc0d..74254274b2 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpressionUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/ExpressionUtils.java @@ -15,6 +15,8 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.codeInsight.AnnotationUtil; import com.intellij.java.language.codeInsight.NullableNotNullManager; import com.intellij.java.language.psi.*; @@ -23,9 +25,9 @@ import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; import com.siyeh.HardcodedMethodConstants; +import consulo.annotation.access.RequiredReadAction; import consulo.application.util.CachedValueProvider; import consulo.document.util.TextRange; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiModificationTracker; @@ -34,14 +36,12 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.project.Project; import consulo.util.collection.ArrayUtil; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collections; -import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; @@ -50,1349 +50,1327 @@ import static consulo.util.lang.ObjectUtil.tryCast; public class ExpressionUtils { - @NonNls - static final Set convertableBoxedClassNames = new HashSet<>(3); - - static { - convertableBoxedClassNames.add(JavaClassNames.JAVA_LANG_BYTE); - convertableBoxedClassNames.add(JavaClassNames.JAVA_LANG_CHARACTER); - convertableBoxedClassNames.add(JavaClassNames.JAVA_LANG_SHORT); - } - - private ExpressionUtils() { - } - - @Nullable - public static Object computeConstantExpression(@Nullable PsiExpression expression) { - return computeConstantExpression(expression, false); - } - - @Nullable - public static Object computeConstantExpression(@Nullable PsiExpression expression, boolean throwConstantEvaluationOverflowException) { - if (expression == null) { - return null; - } - final Project project = expression.getProject(); - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiConstantEvaluationHelper constantEvaluationHelper = psiFacade.getConstantEvaluationHelper(); - return constantEvaluationHelper.computeConstantExpression(expression, throwConstantEvaluationOverflowException); - } - - public static boolean isConstant(PsiField field) { - if (!field.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - if (CollectionUtils.isEmptyArray(field)) { - return true; - } - final PsiType type = field.getType(); - return ClassUtils.isImmutable(type); - } - - public static boolean hasExpressionCount(@Nullable PsiExpressionList expressionList, int count) { - return ControlFlowUtils.hasChildrenOfTypeCount(expressionList, count, PsiExpression.class); - } - - @Nullable - public static PsiExpression getFirstExpressionInList(@Nullable PsiExpressionList expressionList) { - return PsiTreeUtil.getChildOfType(expressionList, PsiExpression.class); - } - - @Nullable - public static PsiExpression getOnlyExpressionInList(@Nullable PsiExpressionList expressionList) { - return ControlFlowUtils.getOnlyChildOfType(expressionList, PsiExpression.class); - } - - public static boolean isDeclaredConstant(PsiExpression expression) { - PsiField field = PsiTreeUtil.getParentOfType(expression, PsiField.class); - if (field == null) { - final PsiAssignmentExpression assignmentExpression = PsiTreeUtil.getParentOfType(expression, PsiAssignmentExpression.class); - if (assignmentExpression == null) { - return false; - } - final PsiExpression lhs = assignmentExpression.getLExpression(); - if (!(lhs instanceof PsiReferenceExpression)) { - return false; - } - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) lhs; - final PsiElement target = referenceExpression.resolve(); - if (!(target instanceof PsiField)) { - return false; - } - field = (PsiField) target; - } - return field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL); - } + static final Set STRING_BUILDER_CLASS_NAMES = Set.of(CommonClassNames.JAVA_LANG_STRING_BUILDER, CommonClassNames.JAVA_LANG_STRING_BUFFER); + static final Set PRINT_CLASS_NAMES = Set.of(CommonClassNames.JAVA_IO_PRINT_STREAM, CommonClassNames.JAVA_IO_PRINT_WRITER); + static final Set PRINT_METHOD_NAMES = Set.of("print", "println"); + static final Set SLF4J_LOGGING_CLASS_NAMES = Set.of("org.slf4j.Logger"); + static final Set SLF4J_LOGGING_METHOD_NAMES = Set.of("trace", "debug", "info", "warn", "error"); - @Contract("null -> false") - public static boolean isEvaluatedAtCompileTime(@Nullable PsiExpression expression) { - if (expression instanceof PsiLiteralExpression) { - return true; - } - if (expression instanceof PsiPolyadicExpression) { - final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) expression; - final PsiExpression[] operands = polyadicExpression.getOperands(); - for (PsiExpression operand : operands) { - if (!isEvaluatedAtCompileTime(operand)) { - return false; - } - } - return true; - } - if (expression instanceof PsiPrefixExpression) { - final PsiPrefixExpression prefixExpression = (PsiPrefixExpression) expression; - final PsiExpression operand = prefixExpression.getOperand(); - return isEvaluatedAtCompileTime(operand); - } - if (expression instanceof PsiReferenceExpression) { - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression; - final PsiElement qualifier = referenceExpression.getQualifier(); - if (qualifier instanceof PsiThisExpression) { - return false; - } - final PsiElement element = referenceExpression.resolve(); - if (element instanceof PsiField) { - final PsiField field = (PsiField) element; - final PsiExpression initializer = field.getInitializer(); - return field.hasModifierProperty(PsiModifier.FINAL) && isEvaluatedAtCompileTime(initializer); - } - if (element instanceof PsiVariable) { - final PsiVariable variable = (PsiVariable) element; - if (PsiTreeUtil.isAncestor(variable, expression, true)) { - return false; - } - final PsiExpression initializer = variable.getInitializer(); - return variable.hasModifierProperty(PsiModifier.FINAL) && isEvaluatedAtCompileTime(initializer); - } - } - if (expression instanceof PsiParenthesizedExpression) { - final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression) expression; - final PsiExpression deparenthesizedExpression = parenthesizedExpression.getExpression(); - return isEvaluatedAtCompileTime(deparenthesizedExpression); - } - if (expression instanceof PsiConditionalExpression) { - final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression) expression; - final PsiExpression condition = conditionalExpression.getCondition(); - final PsiExpression thenExpression = conditionalExpression.getThenExpression(); - final PsiExpression elseExpression = conditionalExpression.getElseExpression(); - return isEvaluatedAtCompileTime(condition) && isEvaluatedAtCompileTime(thenExpression) && isEvaluatedAtCompileTime(elseExpression); - } - if (expression instanceof PsiTypeCastExpression) { - final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression) expression; - final PsiTypeElement castType = typeCastExpression.getCastType(); - if (castType == null) { - return false; - } - final PsiType type = castType.getType(); - return TypeUtils.typeEquals(JavaClassNames.JAVA_LANG_STRING, type); - } - return false; - } - - @Nullable - public static String getLiteralString(@Nullable PsiExpression expression) { - final PsiLiteralExpression literal = getLiteral(expression); - if (literal == null) { - return null; - } - final Object value = literal.getValue(); - if (value == null) { - return null; - } - return value.toString(); - } - - @Nullable - public static PsiLiteralExpression getLiteral(@Nullable PsiExpression expression) { - expression = ParenthesesUtils.stripParentheses(expression); - if (expression instanceof PsiLiteralExpression) { - return (PsiLiteralExpression) expression; - } - if (!(expression instanceof PsiTypeCastExpression)) { - return null; - } - final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression) expression; - final PsiExpression operand = ParenthesesUtils.stripParentheses(typeCastExpression.getOperand()); - if (!(operand instanceof PsiLiteralExpression)) { - return null; - } - return (PsiLiteralExpression) operand; - } + static final Set CONVERTABLE_BOXED_CLASS_NAMES = + Set.of(CommonClassNames.JAVA_LANG_BYTE, CommonClassNames.JAVA_LANG_CHARACTER, CommonClassNames.JAVA_LANG_SHORT); - public static boolean isLiteral(@Nullable PsiExpression expression) { - return getLiteral(expression) != null; - } + static final List POLYMORPHIC_SIGNATURE_ANNOTATION = Collections.singletonList("java.lang.invoke.MethodHandle.PolymorphicSignature"); - public static boolean isEmptyStringLiteral(@Nullable PsiExpression expression) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiLiteralExpression)) { - return false; - } - final String text = expression.getText(); - return "\"\"".equals(text); - } - - @Contract("null -> false") - public static boolean isNullLiteral(@Nullable PsiExpression expression) { - expression = PsiUtil.deparenthesizeExpression(expression); - return expression != null && PsiType.NULL.equals(expression.getType()); - } - - /** - * Returns stream of sub-expressions of supplied expression which could be equal (by ==) to resulting - * value of the expression. The expressions in returned stream are guaranteed not to be each other ancestors. - * Also the expression value is guaranteed to be equal to one of returned sub-expressions. - *

- *

- * E.g. for {@code ((a) ? (Foo)b : (c))} the stream will contain b and c. - *

- * - * @param expression expression to create a stream from - * @return a new stream - */ - public static Stream nonStructuralChildren(@Nonnull PsiExpression expression) { - return StreamEx.ofTree(expression, e -> - { - if (e instanceof PsiConditionalExpression) { - PsiConditionalExpression ternary = (PsiConditionalExpression) e; - return StreamEx.of(ternary.getThenExpression(), ternary.getElseExpression()).nonNull(); - } - if (e instanceof PsiParenthesizedExpression) { - return StreamEx.ofNullable(((PsiParenthesizedExpression) e).getExpression()); - } - return null; - }).remove(e -> e instanceof PsiConditionalExpression || e instanceof PsiParenthesizedExpression).map(e -> - { - if (e instanceof PsiTypeCastExpression) { - PsiExpression operand = ((PsiTypeCastExpression) e).getOperand(); - if (operand != null && !(e.getType() instanceof PsiPrimitiveType) && (!(operand.getType() instanceof PsiPrimitiveType) || PsiType.NULL.equals(operand.getType()))) { - // Ignore to-primitive/from-primitive casts as they may actually change the value - return PsiUtil.skipParenthesizedExprDown(operand); - } - } - return e; - }); - } - - public static boolean isZero(@Nullable PsiExpression expression) { - if (expression == null) { - return false; - } - final PsiType expressionType = expression.getType(); - final Object value = ConstantExpressionUtil.computeCastTo(expression, expressionType); - if (value == null) { - return false; - } - if (value instanceof Double && ((Double) value).doubleValue() == 0.0) { - return true; + private ExpressionUtils() { } - if (value instanceof Float && ((Float) value).floatValue() == 0.0f) { - return true; - } - if (value instanceof Integer && ((Integer) value).intValue() == 0) { - return true; - } - if (value instanceof Long && ((Long) value).longValue() == 0L) { - return true; - } - if (value instanceof Short && ((Short) value).shortValue() == 0) { - return true; - } - if (value instanceof Character && ((Character) value).charValue() == 0) { - return true; - } - return value instanceof Byte && ((Byte) value).byteValue() == 0; - } - public static boolean isOne(@Nullable PsiExpression expression) { - if (expression == null) { - return false; - } - final Object value = computeConstantExpression(expression); - if (value == null) { - return false; - } - //noinspection FloatingPointEquality - if (value instanceof Double && ((Double) value).doubleValue() == 1.0) { - return true; - } - if (value instanceof Float && ((Float) value).floatValue() == 1.0f) { - return true; - } - if (value instanceof Integer && ((Integer) value).intValue() == 1) { - return true; - } - if (value instanceof Long && ((Long) value).longValue() == 1L) { - return true; - } - if (value instanceof Short && ((Short) value).shortValue() == 1) { - return true; - } - if (value instanceof Character && ((Character) value).charValue() == 1) { - return true; + @Nullable + public static Object computeConstantExpression(@Nullable PsiExpression expression) { + return computeConstantExpression(expression, false); } - return value instanceof Byte && ((Byte) value).byteValue() == 1; - } - - public static boolean isNegation(@Nullable PsiExpression condition, boolean ignoreNegatedNullComparison, boolean ignoreNegatedZeroComparison) { - condition = ParenthesesUtils.stripParentheses(condition); - if (condition instanceof PsiPrefixExpression) { - final PsiPrefixExpression prefixExpression = (PsiPrefixExpression) condition; - final IElementType tokenType = prefixExpression.getOperationTokenType(); - return tokenType.equals(JavaTokenType.EXCL); - } else if (condition instanceof PsiBinaryExpression) { - final PsiBinaryExpression binaryExpression = (PsiBinaryExpression) condition; - final PsiExpression lhs = ParenthesesUtils.stripParentheses(binaryExpression.getLOperand()); - final PsiExpression rhs = ParenthesesUtils.stripParentheses(binaryExpression.getROperand()); - if (lhs == null || rhs == null) { - return false; - } - final IElementType tokenType = binaryExpression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.NE)) { - if (ignoreNegatedNullComparison) { - final String lhsText = lhs.getText(); - final String rhsText = rhs.getText(); - if (PsiKeyword.NULL.equals(lhsText) || PsiKeyword.NULL.equals(rhsText)) { - return false; - } + + @Nullable + public static Object computeConstantExpression(@Nullable PsiExpression expression, boolean throwConstantEvaluationOverflowException) { + if (expression == null) { + return null; } - return !(ignoreNegatedZeroComparison && (isZeroLiteral(lhs) || isZeroLiteral(rhs))); - } + final Project project = expression.getProject(); + final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + final PsiConstantEvaluationHelper constantEvaluationHelper = psiFacade.getConstantEvaluationHelper(); + return constantEvaluationHelper.computeConstantExpression(expression, throwConstantEvaluationOverflowException); } - return false; - } - private static boolean isZeroLiteral(PsiExpression expression) { - if (!(expression instanceof PsiLiteralExpression)) { - return false; - } - final PsiLiteralExpression literalExpression = (PsiLiteralExpression) expression; - final Object value = literalExpression.getValue(); - if (value instanceof Integer) { - if (((Integer) value).intValue() == 0) { - return true; - } - } else if (value instanceof Long) { - if (((Long) value).longValue() == 0L) { - return true; - } + public static boolean isConstant(PsiField field) { + if (!field.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + if (CollectionUtils.isEmptyArray(field)) { + return true; + } + final PsiType type = field.getType(); + return ClassUtils.isImmutable(type); } - return false; - } - public static boolean isOffsetArrayAccess(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { - final PsiExpression strippedExpression = ParenthesesUtils.stripParentheses(expression); - if (!(strippedExpression instanceof PsiArrayAccessExpression)) { - return false; - } - final PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression) strippedExpression; - final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); - if (VariableAccessUtils.variableIsUsed(variable, arrayExpression)) { - return false; + public static boolean hasExpressionCount(@Nullable PsiExpressionList expressionList, int count) { + return ControlFlowUtils.hasChildrenOfTypeCount(expressionList, count, PsiExpression.class); } - final PsiExpression index = arrayAccessExpression.getIndexExpression(); - if (index == null) { - return false; - } - return expressionIsOffsetVariableLookup(index, variable); - } - private static boolean expressionIsOffsetVariableLookup(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { - if (VariableAccessUtils.evaluatesToVariable(expression, variable)) { - return true; - } - final PsiExpression strippedExpression = ParenthesesUtils.stripParentheses(expression); - if (!(strippedExpression instanceof PsiBinaryExpression)) { - return false; - } - final PsiBinaryExpression binaryExpression = (PsiBinaryExpression) strippedExpression; - final IElementType tokenType = binaryExpression.getOperationTokenType(); - if (!JavaTokenType.PLUS.equals(tokenType) && !JavaTokenType.MINUS.equals(tokenType)) { - return false; - } - final PsiExpression lhs = binaryExpression.getLOperand(); - if (expressionIsOffsetVariableLookup(lhs, variable)) { - return true; - } - final PsiExpression rhs = binaryExpression.getROperand(); - return expressionIsOffsetVariableLookup(rhs, variable) && !JavaTokenType.MINUS.equals(tokenType); - } - - public static boolean isVariableLessThanComparison(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiBinaryExpression)) { - return false; - } - final PsiBinaryExpression binaryExpression = (PsiBinaryExpression) expression; - final IElementType tokenType = binaryExpression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.LT) || tokenType.equals(JavaTokenType.LE)) { - final PsiExpression lhs = binaryExpression.getLOperand(); - return VariableAccessUtils.evaluatesToVariable(lhs, variable); - } else if (tokenType.equals(JavaTokenType.GT) || tokenType.equals(JavaTokenType.GE)) { - final PsiExpression rhs = binaryExpression.getROperand(); - return VariableAccessUtils.evaluatesToVariable(rhs, variable); + @Nullable + @RequiredReadAction + public static PsiExpression getFirstExpressionInList(@Nullable PsiExpressionList expressionList) { + return PsiTreeUtil.getChildOfType(expressionList, PsiExpression.class); } - return false; - } - public static boolean isVariableGreaterThanComparison(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiBinaryExpression)) { - return false; + @Nullable + public static PsiExpression getOnlyExpressionInList(@Nullable PsiExpressionList expressionList) { + return ControlFlowUtils.getOnlyChildOfType(expressionList, PsiExpression.class); } - final PsiBinaryExpression binaryExpression = (PsiBinaryExpression) expression; - final IElementType tokenType = binaryExpression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.GT) || tokenType.equals(JavaTokenType.GE)) { - final PsiExpression lhs = binaryExpression.getLOperand(); - return VariableAccessUtils.evaluatesToVariable(lhs, variable); - } else if (tokenType.equals(JavaTokenType.LT) || tokenType.equals(JavaTokenType.LE)) { - final PsiExpression rhs = binaryExpression.getROperand(); - return VariableAccessUtils.evaluatesToVariable(rhs, variable); - } - return false; - } - public static boolean isStringConcatenationOperand(PsiExpression expression) { - final PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiPolyadicExpression)) { - return false; - } - final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) parent; - if (!JavaTokenType.PLUS.equals(polyadicExpression.getOperationTokenType())) { - return false; - } - final PsiExpression[] operands = polyadicExpression.getOperands(); - if (operands.length < 2) { - return false; - } - final int index = ArrayUtil.indexOf(operands, expression); - for (int i = 0; i < index; i++) { - final PsiType type = operands[i].getType(); - if (TypeUtils.isJavaLangString(type)) { - return true; - } - } - if (index == 0) { - final PsiType type = operands[index + 1].getType(); - return TypeUtils.isJavaLangString(type); + @RequiredReadAction + public static boolean isDeclaredConstant(PsiExpression expression) { + PsiField field = PsiTreeUtil.getParentOfType(expression, PsiField.class); + if (field == null) { + PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(expression, PsiAssignmentExpression.class); + if (assignment != null && assignment.getLExpression() instanceof PsiReferenceExpression refExpr + && refExpr.resolve() instanceof PsiField targetField) { + field = targetField; + } + } + return field != null && field.isStatic() && field.isFinal(); } - return false; - } - - public static boolean isConstructorInvocation(PsiElement element) { - if (!(element instanceof PsiMethodCallExpression)) { - return false; - } - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) element; - final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); - final String callName = methodExpression.getReferenceName(); - return PsiKeyword.THIS.equals(callName) || PsiKeyword.SUPER.equals(callName); - } - - public static boolean hasType(@Nullable PsiExpression expression, @NonNls @Nonnull String typeName) { - if (expression == null) { - return false; - } - final PsiType type = expression.getType(); - return TypeUtils.typeEquals(typeName, type); - } - - public static boolean hasStringType(@Nullable PsiExpression expression) { - return hasType(expression, JavaClassNames.JAVA_LANG_STRING); - } - - public static boolean isConversionToStringNecessary(PsiExpression expression, boolean throwable) { - final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression); - if (parent instanceof PsiPolyadicExpression) { - final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) parent; - final PsiType type = polyadicExpression.getType(); - if (!TypeUtils.typeEquals(JavaClassNames.JAVA_LANG_STRING, type)) { - return true; - } - final PsiExpression[] operands = polyadicExpression.getOperands(); - boolean expressionSeen = false; - for (int i = 0, length = operands.length; i < length; i++) { - final PsiExpression operand = operands[i]; - if (PsiTreeUtil.isAncestor(operand, expression, false)) { - if (i > 0) { + @Contract("null -> false") + @RequiredReadAction + public static boolean isEvaluatedAtCompileTime(@Nullable PsiExpression expression) { + if (expression instanceof PsiLiteralExpression) { return true; - } - expressionSeen = true; - } else if ((!expressionSeen || i == 1) && TypeUtils.isJavaLangString(operand.getType())) { - return false; - } - } - return true; - } else if (parent instanceof PsiExpressionList) { - final PsiExpressionList expressionList = (PsiExpressionList) parent; - final PsiElement grandParent = expressionList.getParent(); - if (!(grandParent instanceof PsiMethodCallExpression)) { - return true; - } - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) grandParent; - final PsiReferenceExpression methodExpression1 = methodCallExpression.getMethodExpression(); - @NonNls final String name = methodExpression1.getReferenceName(); - final PsiExpression[] expressions = expressionList.getExpressions(); - if ("insert".equals(name)) { - if (expressions.length < 2 || !expression.equals(ParenthesesUtils.stripParentheses(expressions[1]))) { - return true; - } - if (!isCallToMethodIn(methodCallExpression, "java.lang.StringBuilder", "java.lang.StringBuffer")) { - return true; - } - } else if ("append".equals(name)) { - if (expressions.length < 1 || !expression.equals(ParenthesesUtils.stripParentheses(expressions[0]))) { - return true; - } - if (!isCallToMethodIn(methodCallExpression, "java.lang.StringBuilder", "java.lang.StringBuffer")) { - return true; - } - } else if ("print".equals(name) || "println".equals(name)) { - if (!isCallToMethodIn(methodCallExpression, "java.io.PrintStream", "java.io.PrintWriter")) { - return true; - } - } else if ("trace".equals(name) || "debug".equals(name) || "info".equals(name) || "warn".equals(name) || "error".equals(name)) { - if (!isCallToMethodIn(methodCallExpression, "org.slf4j.Logger")) { - return true; - } - int l = 1; - for (int i = 0; i < expressions.length; i++) { - final PsiExpression expression1 = expressions[i]; - if (i == 0 && TypeUtils.expressionHasTypeOrSubtype(expression1, "org.slf4j.Marker")) { - l = 2; - } - if (expression1 == expression) { - if (i < l || (throwable && i == expressions.length - 1)) { - return true; + } + if (expression instanceof PsiPolyadicExpression polyadic) { + for (PsiExpression operand : polyadic.getOperands()) { + if (!isEvaluatedAtCompileTime(operand)) { + return false; + } } - } + return true; } - } else { - return true; - } - } else { - return true; + if (expression instanceof PsiPrefixExpression prefixExpression) { + return isEvaluatedAtCompileTime(prefixExpression.getOperand()); + } + if (expression instanceof PsiReferenceExpression refExpr) { + if (refExpr.getQualifier() instanceof PsiThisExpression) { + return false; + } + final PsiElement element = refExpr.resolve(); + if (element instanceof PsiField field) { + final PsiExpression initializer = field.getInitializer(); + return field.isFinal() && isEvaluatedAtCompileTime(initializer); + } + if (element instanceof PsiVariable variable) { + if (PsiTreeUtil.isAncestor(variable, expression, true)) { + return false; + } + final PsiExpression initializer = variable.getInitializer(); + return variable.hasModifierProperty(PsiModifier.FINAL) && isEvaluatedAtCompileTime(initializer); + } + } + if (expression instanceof PsiParenthesizedExpression parenthesized) { + return isEvaluatedAtCompileTime(parenthesized.getExpression()); + } + if (expression instanceof PsiConditionalExpression conditional) { + return isEvaluatedAtCompileTime(conditional.getCondition()) + && isEvaluatedAtCompileTime(conditional.getThenExpression()) + && isEvaluatedAtCompileTime(conditional.getElseExpression()); + } + if (expression instanceof PsiTypeCastExpression typeCast) { + final PsiTypeElement castType = typeCast.getCastType(); + if (castType == null) { + return false; + } + final PsiType type = castType.getType(); + return TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING, type); + } + return false; } - return false; - } - private static boolean isCallToMethodIn(PsiMethodCallExpression methodCallExpression, String... classNames) { - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method == null) { - return false; - } - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return false; - } - final String qualifiedName = containingClass.getQualifiedName(); - for (String className : classNames) { - if (className.equals(qualifiedName)) { - return true; - } + @Nullable + public static String getLiteralString(@Nullable PsiExpression expression) { + final PsiLiteralExpression literal = getLiteral(expression); + if (literal == null) { + return null; + } + final Object value = literal.getValue(); + if (value == null) { + return null; + } + return value.toString(); } - return false; - } - public static boolean isNegative(@Nonnull PsiExpression expression) { - final PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiPrefixExpression)) { - return false; - } - final PsiPrefixExpression prefixExpression = (PsiPrefixExpression) parent; - final IElementType tokenType = prefixExpression.getOperationTokenType(); - return JavaTokenType.MINUS.equals(tokenType); - } - - @Contract("null, _ -> null") - @Nullable - public static PsiVariable getVariableFromNullComparison(PsiExpression expression, boolean equals) { - final PsiReferenceExpression referenceExpression = getReferenceExpressionFromNullComparison(expression, equals); - final PsiElement target = referenceExpression != null ? referenceExpression.resolve() : null; - return target instanceof PsiVariable ? (PsiVariable) target : null; - } - - @Contract("null, _ -> null") - @Nullable - public static PsiReferenceExpression getReferenceExpressionFromNullComparison(PsiExpression expression, boolean equals) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiPolyadicExpression)) { - return null; - } - final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression) expression; - final IElementType tokenType = polyadicExpression.getOperationTokenType(); - if (equals) { - if (!JavaTokenType.EQEQ.equals(tokenType)) { - return null; - } - } else { - if (!JavaTokenType.NE.equals(tokenType)) { + @Nullable + public static PsiLiteralExpression getLiteral(@Nullable PsiExpression expression) { + expression = ParenthesesUtils.stripParentheses(expression); + if (expression instanceof PsiLiteralExpression literalExpr) { + return literalExpr; + } + if (expression instanceof PsiTypeCastExpression typeCast) { + final PsiExpression operand = ParenthesesUtils.stripParentheses(typeCast.getOperand()); + if (operand instanceof PsiLiteralExpression literalExpr) { + return literalExpr; + } + } return null; - } - } - final PsiExpression[] operands = polyadicExpression.getOperands(); - if (operands.length != 2) { - return null; - } - PsiExpression comparedToNull = null; - if (PsiType.NULL.equals(operands[0].getType())) { - comparedToNull = operands[1]; - } else if (PsiType.NULL.equals(operands[1].getType())) { - comparedToNull = operands[0]; - } - comparedToNull = ParenthesesUtils.stripParentheses(comparedToNull); - - return comparedToNull instanceof PsiReferenceExpression ? (PsiReferenceExpression) comparedToNull : null; - } - - /** - * Returns the expression compared with null if the supplied {@link PsiBinaryExpression} is null check (either with {@code ==} - * or with {@code !=}). Returns null otherwise. - * - * @param binOp binary expression to extract the value compared with null from - * @return value compared with null - */ - @Nullable - public static PsiExpression getValueComparedWithNull(@Nonnull PsiBinaryExpression binOp) { - final IElementType tokenType = binOp.getOperationTokenType(); - if (!tokenType.equals(JavaTokenType.EQEQ) && !tokenType.equals(JavaTokenType.NE)) { - return null; } - final PsiExpression left = binOp.getLOperand(); - final PsiExpression right = binOp.getROperand(); - if (isNullLiteral(right)) { - return left; - } - if (isNullLiteral(left)) { - return right; - } - return null; - } - public static boolean isConcatenation(PsiElement element) { - if (!(element instanceof PsiPolyadicExpression)) { - return false; - } - final PsiPolyadicExpression expression = (PsiPolyadicExpression) element; - final PsiType type = expression.getType(); - return type != null && type.equalsToText(JavaClassNames.JAVA_LANG_STRING); - } - - public static boolean isAnnotatedNotNull(PsiExpression expression) { - return isAnnotated(expression, false); - } - - public static boolean isAnnotatedNullable(PsiExpression expression) { - return isAnnotated(expression, true); - } - - private static boolean isAnnotated(PsiExpression expression, boolean nullable) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiReferenceExpression)) { - return false; - } - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) expression; - final PsiElement target = referenceExpression.resolve(); - if (!(target instanceof PsiModifierListOwner)) { - return false; - } - final PsiModifierListOwner modifierListOwner = (PsiModifierListOwner) target; - return nullable ? NullableNotNullManager.isNullable(modifierListOwner) : NullableNotNullManager.isNotNull(modifierListOwner); - } - - /** - * Returns true if the expression can be moved to earlier point in program order without possible semantic change or - * notable performance handicap. Examples of simple expressions are: - * - literal (number, char, string, class literal, true, false, null) - * - compile-time constant - * - this - * - variable/parameter read - * - static field read - * - instance field read having 'this' as qualifier - * - * @param expression an expression to test (must be valid expression) - * @return true if the supplied expression is simple - */ - @Contract("null -> false") - public static boolean isSimpleExpression(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiLiteralExpression || expression instanceof PsiThisExpression || expression instanceof PsiClassObjectAccessExpression || isEvaluatedAtCompileTime(expression)) { - return true; - } - if (expression instanceof PsiReferenceExpression) { - PsiExpression qualifier = ((PsiReferenceExpression) expression).getQualifierExpression(); - if (qualifier == null || qualifier instanceof PsiThisExpression) { - return true; - } - if (qualifier instanceof PsiReferenceExpression) { - PsiElement resolvedQualifier = ((PsiReferenceExpression) qualifier).resolve(); - if (resolvedQualifier instanceof PsiClass) { - return true; + public static boolean isLiteral(@Nullable PsiExpression expression) { + return getLiteral(expression) != null; + } + + @RequiredReadAction + public static boolean isEmptyStringLiteral(@Nullable PsiExpression expression) { + return ParenthesesUtils.stripParentheses(expression) instanceof PsiLiteralExpression literal + && "\"\"".equals(literal.getText()); + } + + @Contract("null -> false") + public static boolean isNullLiteral(@Nullable PsiExpression expression) { + expression = PsiUtil.deparenthesizeExpression(expression); + return expression != null && PsiType.NULL.equals(expression.getType()); + } + + /** + * Returns stream of sub-expressions of supplied expression which could be equal (by ==) to resulting + * value of the expression. The expressions in returned stream are guaranteed not to be each other ancestors. + * Also the expression value is guaranteed to be equal to one of returned sub-expressions. + *

+ *

+ * E.g. for {@code ((a) ? (Foo)b : (c))} the stream will contain b and c. + *

+ * + * @param expression expression to create a stream from + * @return a new stream + */ + public static Stream nonStructuralChildren(PsiExpression expression) { + return StreamEx.ofTree( + expression, + e -> { + if (e instanceof PsiConditionalExpression ternary) { + return StreamEx.of(ternary.getThenExpression(), ternary.getElseExpression()).nonNull(); + } + if (e instanceof PsiParenthesizedExpression parenthesized) { + return StreamEx.ofNullable(parenthesized.getExpression()); + } + return null; + } + ) + .remove(e -> e instanceof PsiConditionalExpression || e instanceof PsiParenthesizedExpression) + .map(e -> { + if (e instanceof PsiTypeCastExpression typeCast) { + PsiExpression operand = typeCast.getOperand(); + if (operand != null + && !(typeCast.getType() instanceof PsiPrimitiveType) + && (!(operand.getType() instanceof PsiPrimitiveType) || PsiType.NULL.equals(operand.getType()))) { + // Ignore to-primitive/from-primitive casts as they may actually change the value + return PsiUtil.skipParenthesizedExprDown(operand); + } + } + return e; + }); + } + + public static boolean isZero(@Nullable PsiExpression expression) { + if (expression == null) { + return false; } - } - } - return false; - } - - /** - * Returns assignment expression if supplied element is a statement which contains assignment expression - * or it's an assignment expression itself. Only simple assignments are returned (like a = b, not a+= b). - * - * @param element element to get assignment expression from - * @return extracted assignment or null if assignment is not found or assignment is compound - */ - @Contract("null -> null") - @Nullable - public static PsiAssignmentExpression getAssignment(PsiElement element) { - if (element instanceof PsiExpressionStatement) { - element = ((PsiExpressionStatement) element).getExpression(); - } - if (element instanceof PsiExpression) { - element = PsiUtil.skipParenthesizedExprDown((PsiExpression) element); - if (element instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression) element; - if (assignment.getOperationTokenType().equals(JavaTokenType.EQ)) { - return assignment; - } - } - } - return null; - } - - /** - * Returns an expression assigned to the target variable if supplied element is - * either simple (non-compound) assignment expression or an expression statement containing assignment expression - * and the corresponding assignment l-value is the reference to target variable. - * - * @param element element to get assignment expression from - * @param target a variable to extract an assignment to - * @return extracted assignment r-value or null if assignment is not found or assignment is compound or it's an assignment - * to the wrong variable - */ - @Contract("null, _ -> null; _, null -> null") - public static PsiExpression getAssignmentTo(PsiElement element, PsiVariable target) { - PsiAssignmentExpression assignment = getAssignment(element); - if (assignment != null && isReferenceTo(assignment.getLExpression(), target)) { - return assignment.getRExpression(); - } - return null; - } - - @Contract("null, _ -> false") - public static boolean isLiteral(PsiElement element, Object value) { - return element instanceof PsiLiteralExpression && value.equals(((PsiLiteralExpression) element).getValue()); - } - - public static boolean isAutoBoxed(@Nonnull PsiExpression expression) { - final PsiElement parent = expression.getParent(); - if (parent instanceof PsiParenthesizedExpression) { - return false; - } - if (parent instanceof PsiExpressionList) { - final PsiElement grandParent = parent.getParent(); - if (grandParent instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) grandParent; - final PsiMethod method = methodCallExpression.resolveMethod(); - if (method != null && AnnotationUtil.isAnnotated(method, Collections.singletonList("java.lang.invoke.MethodHandle.PolymorphicSignature"))) { - return false; - } - } - } - final PsiType expressionType = expression.getType(); - if (PsiPrimitiveType.getUnboxedType(expressionType) != null && (parent instanceof PsiPrefixExpression || parent instanceof PsiPostfixExpression)) { - return true; - } - if (expressionType == null || expressionType.equals(PsiType.VOID) || !TypeConversionUtil.isPrimitiveAndNotNull(expressionType)) { - return false; - } - final PsiPrimitiveType primitiveType = (PsiPrimitiveType) expressionType; - final PsiClassType boxedType = primitiveType.getBoxedType(expression); - if (boxedType == null) { - return false; - } - final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true); - if (expectedType == null || ClassUtils.isPrimitive(expectedType)) { - return false; + final PsiType expressionType = expression.getType(); + final Object value = ConstantExpressionUtil.computeCastTo(expression, expressionType); + if (value == null) { + return false; + } + if (value instanceof Double && (Double)value == 0.0) { + return true; + } + if (value instanceof Float && (Float)value == 0.0f) { + return true; + } + if (value instanceof Integer && (Integer)value == 0) { + return true; + } + if (value instanceof Long && (Long)value == 0L) { + return true; + } + if (value instanceof Short && (Short)value == 0) { + return true; + } + if (value instanceof Character && (Character)value == 0) { + return true; + } + return value instanceof Byte && (Byte)value == 0; } - if (!expectedType.isAssignableFrom(boxedType)) { - // JLS 5.2 Assignment Conversion - // check if a narrowing primitive conversion is applicable - if (!(expectedType instanceof PsiClassType) || !PsiUtil.isConstantExpression(expression)) { - return false; - } - final PsiClassType classType = (PsiClassType) expectedType; - final String className = classType.getCanonicalText(); - if (!convertableBoxedClassNames.contains(className)) { - return false; - } - if (!PsiType.BYTE.equals(expressionType) && !PsiType.CHAR.equals(expressionType) && !PsiType.SHORT.equals(expressionType) && !PsiType.INT.equals(expressionType)) { + + public static boolean isOne(@Nullable PsiExpression expression) { + if (expression == null) { + return false; + } + final Object value = computeConstantExpression(expression); + if (value == null) { + return false; + } + //noinspection FloatingPointEquality + if (value instanceof Double && (Double)value == 1.0) { + return true; + } + if (value instanceof Float && (Float)value == 1.0f) { + return true; + } + if (value instanceof Integer && (Integer)value == 1) { + return true; + } + if (value instanceof Long && (Long)value == 1L) { + return true; + } + if (value instanceof Short && (Short)value == 1) { + return true; + } + if (value instanceof Character && (Character)value == 1) { + return true; + } + return value instanceof Byte && (Byte)value == 1; + } + + @RequiredReadAction + public static boolean isNegation( + @Nullable PsiExpression condition, + boolean ignoreNegatedNullComparison, + boolean ignoreNegatedZeroComparison + ) { + condition = ParenthesesUtils.stripParentheses(condition); + if (condition instanceof PsiPrefixExpression) { + final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)condition; + final IElementType tokenType = prefixExpression.getOperationTokenType(); + return tokenType.equals(JavaTokenType.EXCL); + } + else if (condition instanceof PsiBinaryExpression) { + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)condition; + final PsiExpression lhs = ParenthesesUtils.stripParentheses(binaryExpression.getLOperand()); + final PsiExpression rhs = ParenthesesUtils.stripParentheses(binaryExpression.getROperand()); + if (lhs == null || rhs == null) { + return false; + } + final IElementType tokenType = binaryExpression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.NE)) { + if (ignoreNegatedNullComparison) { + final String lhsText = lhs.getText(); + final String rhsText = rhs.getText(); + if (PsiKeyword.NULL.equals(lhsText) || PsiKeyword.NULL.equals(rhsText)) { + return false; + } + } + return !(ignoreNegatedZeroComparison && (isZeroLiteral(lhs) || isZeroLiteral(rhs))); + } + } return false; - } } - return true; - } - - /** - * If any operand of supplied binary expression refers to the supplied variable, returns other operand; - * otherwise returns null. - * - * @param binOp {@link PsiBinaryExpression} to extract the operand from - * @param variable variable to check against - * @return operand or null - */ - @Contract("null, _ -> null; !null, null -> null") - public static PsiExpression getOtherOperand(@Nullable PsiBinaryExpression binOp, @Nullable PsiVariable variable) { - if (binOp == null || variable == null) { - return null; - } - if (isReferenceTo(binOp.getLOperand(), variable)) { - return binOp.getROperand(); + + private static boolean isZeroLiteral(PsiExpression expression) { + if (!(expression instanceof PsiLiteralExpression)) { + return false; + } + final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expression; + final Object value = literalExpression.getValue(); + if (value instanceof Integer integerValue) { + if (integerValue == 0) { + return true; + } + } + else if (value instanceof Long longValue) { + if (longValue == 0L) { + return true; + } + } + return false; } - if (isReferenceTo(binOp.getROperand(), variable)) { - return binOp.getLOperand(); + + public static boolean isOffsetArrayAccess(@Nullable PsiExpression expression, PsiVariable variable) { + final PsiExpression strippedExpression = ParenthesesUtils.stripParentheses(expression); + if (!(strippedExpression instanceof PsiArrayAccessExpression)) { + return false; + } + final PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression)strippedExpression; + final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); + if (VariableAccessUtils.variableIsUsed(variable, arrayExpression)) { + return false; + } + final PsiExpression index = arrayAccessExpression.getIndexExpression(); + return index != null && expressionIsOffsetVariableLookup(index, variable); } - return null; - } - @Contract("null, _ -> false; _, null -> false") - public static boolean isReferenceTo(PsiExpression expression, PsiVariable variable) { - if (variable == null) { - return false; + private static boolean expressionIsOffsetVariableLookup(@Nullable PsiExpression expression, PsiVariable variable) { + if (VariableAccessUtils.evaluatesToVariable(expression, variable)) { + return true; + } + final PsiExpression strippedExpression = ParenthesesUtils.stripParentheses(expression); + if (!(strippedExpression instanceof PsiBinaryExpression)) { + return false; + } + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)strippedExpression; + final IElementType tokenType = binaryExpression.getOperationTokenType(); + if (!JavaTokenType.PLUS.equals(tokenType) && !JavaTokenType.MINUS.equals(tokenType)) { + return false; + } + final PsiExpression lhs = binaryExpression.getLOperand(); + if (expressionIsOffsetVariableLookup(lhs, variable)) { + return true; + } + final PsiExpression rhs = binaryExpression.getROperand(); + return expressionIsOffsetVariableLookup(rhs, variable) && !JavaTokenType.MINUS.equals(tokenType); } - expression = PsiUtil.skipParenthesizedExprDown(expression); - return expression instanceof PsiReferenceExpression && ((PsiReferenceExpression) expression).isReferenceTo(variable); - } - - /** - * Returns a method call expression for the supplied qualifier - * - * @param qualifier for method call - * @return a method call expression or null if the supplied expression is not a method call qualifier - */ - @Contract(value = "null -> null", pure = true) - public static PsiMethodCallExpression getCallForQualifier(PsiExpression qualifier) { - if (qualifier == null) { - return null; + + public static boolean isVariableLessThanComparison(@Nullable PsiExpression expression, PsiVariable variable) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiBinaryExpression)) { + return false; + } + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)expression; + final IElementType tokenType = binaryExpression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.LT) || tokenType.equals(JavaTokenType.LE)) { + final PsiExpression lhs = binaryExpression.getLOperand(); + return VariableAccessUtils.evaluatesToVariable(lhs, variable); + } + else if (tokenType.equals(JavaTokenType.GT) || tokenType.equals(JavaTokenType.GE)) { + final PsiExpression rhs = binaryExpression.getROperand(); + return VariableAccessUtils.evaluatesToVariable(rhs, variable); + } + return false; } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(qualifier.getParent()); - if (parent instanceof PsiReferenceExpression) { - PsiReferenceExpression methodExpression = (PsiReferenceExpression) parent; - if (PsiTreeUtil.isAncestor(methodExpression.getQualifierExpression(), qualifier, false)) { - PsiElement gParent = methodExpression.getParent(); - if (gParent instanceof PsiMethodCallExpression) { - return (PsiMethodCallExpression) gParent; - } - } + + public static boolean isVariableGreaterThanComparison(@Nullable PsiExpression expression, PsiVariable variable) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiBinaryExpression)) { + return false; + } + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)expression; + final IElementType tokenType = binaryExpression.getOperationTokenType(); + if (tokenType.equals(JavaTokenType.GT) || tokenType.equals(JavaTokenType.GE)) { + final PsiExpression lhs = binaryExpression.getLOperand(); + return VariableAccessUtils.evaluatesToVariable(lhs, variable); + } + else if (tokenType.equals(JavaTokenType.LT) || tokenType.equals(JavaTokenType.LE)) { + final PsiExpression rhs = binaryExpression.getROperand(); + return VariableAccessUtils.evaluatesToVariable(rhs, variable); + } + return false; } - return null; - } - - /** - * Returns an array expression from array length retrieval expression - * - * @param expression expression to extract an array expression from - * @return an array expression or null if supplied expression is not array length retrieval - */ - @Nullable - public static PsiExpression getArrayFromLengthExpression(PsiExpression expression) { - expression = ParenthesesUtils.stripParentheses(expression); - if (!(expression instanceof PsiReferenceExpression)) { - return null; + + public static boolean isStringConcatenationOperand(PsiExpression expression) { + final PsiElement parent = expression.getParent(); + if (!(parent instanceof PsiPolyadicExpression)) { + return false; + } + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent; + if (!JavaTokenType.PLUS.equals(polyadicExpression.getOperationTokenType())) { + return false; + } + final PsiExpression[] operands = polyadicExpression.getOperands(); + if (operands.length < 2) { + return false; + } + final int index = ArrayUtil.indexOf(operands, expression); + for (int i = 0; i < index; i++) { + final PsiType type = operands[i].getType(); + if (TypeUtils.isJavaLangString(type)) { + return true; + } + } + if (index == 0) { + final PsiType type = operands[index + 1].getType(); + return TypeUtils.isJavaLangString(type); + } + return false; } - final PsiReferenceExpression reference = (PsiReferenceExpression) expression; - final String referenceName = reference.getReferenceName(); - if (!HardcodedMethodConstants.LENGTH.equals(referenceName)) { - return null; + + + public static boolean isConstructorInvocation(PsiElement element) { + if (!(element instanceof PsiMethodCallExpression)) { + return false; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element; + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final String callName = methodExpression.getReferenceName(); + return PsiKeyword.THIS.equals(callName) || PsiKeyword.SUPER.equals(callName); } - final PsiExpression qualifier = reference.getQualifierExpression(); - if (qualifier == null) { - return null; + + public static boolean hasType(@Nullable PsiExpression expression, String typeName) { + if (expression == null) { + return false; + } + final PsiType type = expression.getType(); + return TypeUtils.typeEquals(typeName, type); } - final PsiType type = qualifier.getType(); - if (type == null || type.getArrayDimensions() <= 0) { - return null; + + public static boolean hasStringType(@Nullable PsiExpression expression) { + return hasType(expression, CommonClassNames.JAVA_LANG_STRING); } - return qualifier; - } - - /** - * Returns a qualifier for reference or creates a corresponding {@link PsiThisExpression} statement if - * a qualifier is null - * - * @param ref a reference expression to get a qualifier from - * @return a qualifier or created (non-physical) {@link PsiThisExpression}. - */ - @Nonnull - public static PsiExpression getQualifierOrThis(@Nonnull PsiReferenceExpression ref) { - PsiExpression qualifier = ref.getQualifierExpression(); - if (qualifier != null) { - return qualifier; + + public static boolean isConversionToStringNecessary(PsiExpression expression, boolean throwable) { + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression); + if (parent instanceof PsiPolyadicExpression polyadic) { + if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING, polyadic.getType())) { + return true; + } + final PsiExpression[] operands = polyadic.getOperands(); + boolean expressionSeen = false; + for (int i = 0, length = operands.length; i < length; i++) { + final PsiExpression operand = operands[i]; + if (PsiTreeUtil.isAncestor(operand, expression, false)) { + if (i > 0) { + return true; + } + expressionSeen = true; + } + else if ((!expressionSeen || i == 1) && TypeUtils.isJavaLangString(operand.getType())) { + return false; + } + } + return true; + } + else if (parent instanceof PsiExpressionList expressionList + && expressionList.getParent() instanceof PsiMethodCallExpression methodCall) { + String methodName = methodCall.getMethodExpression().getReferenceName(); + final PsiExpression[] paramExprs = expressionList.getExpressions(); + if ("insert".equals(methodName)) { + if (paramExprs.length < 2 + || !expression.equals(ParenthesesUtils.stripParentheses(paramExprs[1])) + || !isCallToMethodIn(methodCall, STRING_BUILDER_CLASS_NAMES)) { + return true; + } + } + else if ("append".equals(methodName)) { + if (paramExprs.length < 1 + || !expression.equals(ParenthesesUtils.stripParentheses(paramExprs[0])) + || !isCallToMethodIn(methodCall, STRING_BUILDER_CLASS_NAMES)) { + return true; + } + } + else if (PRINT_METHOD_NAMES.contains(methodName)) { + if (!isCallToMethodIn(methodCall, PRINT_CLASS_NAMES)) { + return true; + } + } + else if (SLF4J_LOGGING_METHOD_NAMES.contains(methodName)) { + if (!isCallToMethodIn(methodCall, SLF4J_LOGGING_CLASS_NAMES)) { + return true; + } + int l = 1; + for (int i = 0; i < paramExprs.length; i++) { + final PsiExpression paramExpr = paramExprs[i]; + if (i == 0 && TypeUtils.expressionHasTypeOrSubtype(paramExpr, "org.slf4j.Marker")) { + l = 2; + } + if (paramExpr == expression && (i < l || (throwable && i == paramExprs.length - 1))) { + return true; + } + } + } + else { + return true; + } + } + else { + return true; + } + return false; } - PsiElementFactory factory = JavaPsiFacade.getElementFactory(ref.getProject()); - PsiMember member = tryCast(ref.resolve(), PsiMember.class); - if (member != null) { - PsiClass memberClass = member.getContainingClass(); - if (memberClass != null) { - PsiClass containingClass = ClassUtils.getContainingClass(ref); + + private static boolean isCallToMethodIn(PsiMethodCallExpression methodCall, Set classNames) { + final PsiMethod method = methodCall.resolveMethod(); + if (method == null) { + return false; + } + final PsiClass containingClass = method.getContainingClass(); if (containingClass == null) { - containingClass = PsiTreeUtil.getContextOfType(ref, PsiClass.class); - } - if (!InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { - containingClass = ClassUtils.getContainingClass(containingClass); - while (containingClass != null && !InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { - containingClass = ClassUtils.getContainingClass(containingClass); - } - if (containingClass != null) { - return factory.createExpressionFromText(containingClass.getQualifiedName() + "." + PsiKeyword.THIS, ref); - } - } - } - } - return factory.createExpressionFromText(PsiKeyword.THIS, ref); - } - - /** - * Bind a reference element to a new name. The qualifier and type arguments (if present) remain the same - * - * @param ref reference element to rename - * @param newName new name - */ - public static void bindReferenceTo(@Nonnull PsiReferenceExpression ref, @Nonnull String newName) { - PsiElement nameElement = ref.getReferenceNameElement(); - if (nameElement == null) { - throw new IllegalStateException("Name element is null: " + ref); - } - if (newName.equals(nameElement.getText())) { - return; + return false; + } + String qualifiedName = containingClass.getQualifiedName(); + return qualifiedName != null && classNames.contains(qualifiedName); } - PsiIdentifier identifier = JavaPsiFacade.getElementFactory(ref.getProject()).createIdentifier(newName); - nameElement.replace(identifier); - } - - /** - * Bind method call to a new name. Everything else like qualifier, type arguments or call arguments remain the same. - * - * @param call to rename - * @param newName new name - */ - public static void bindCallTo(@Nonnull PsiMethodCallExpression call, @Nonnull String newName) { - bindReferenceTo(call.getMethodExpression(), newName); - } - - /** - * Returns the expression itself (probably with stripped parentheses) or the corresponding value if the expression is a local variable - * reference which is initialized and not used anywhere else - * - * @param expression - * @return a resolved expression or expression itself - */ - @Contract("null -> null") - @Nullable - public static PsiExpression resolveExpression(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiReferenceExpression) { - PsiReferenceExpression reference = (PsiReferenceExpression) expression; - PsiLocalVariable variable = tryCast(reference.resolve(), PsiLocalVariable.class); - if (variable != null) { - PsiExpression initializer = variable.getInitializer(); - if (initializer != null && ReferencesSearch.search(variable).forEach(ref -> ref == reference)) { - return initializer; - } - } + + public static boolean isNegative(PsiExpression expression) { + final PsiElement parent = expression.getParent(); + if (!(parent instanceof PsiPrefixExpression)) { + return false; + } + final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)parent; + final IElementType tokenType = prefixExpression.getOperationTokenType(); + return JavaTokenType.MINUS.equals(tokenType); } - return expression; - } - - @Contract(value = "null -> null") - @Nullable - public static PsiLocalVariable resolveLocalVariable(@Nullable PsiExpression expression) { - PsiReferenceExpression referenceExpression = tryCast(expression, PsiReferenceExpression.class); - if (referenceExpression == null) { - return null; + + @Contract("null, _ -> null") + @Nullable + @RequiredReadAction + public static PsiVariable getVariableFromNullComparison(PsiExpression expression, boolean equals) { + final PsiReferenceExpression referenceExpression = getReferenceExpressionFromNullComparison(expression, equals); + final PsiElement target = referenceExpression != null ? referenceExpression.resolve() : null; + return target instanceof PsiVariable ? (PsiVariable)target : null; } - return tryCast(referenceExpression.resolve(), PsiLocalVariable.class); - } - public static boolean isOctalLiteral(PsiLiteralExpression literal) { - final PsiType type = literal.getType(); - if (!PsiType.INT.equals(type) && !PsiType.LONG.equals(type)) { - return false; + @Contract("null, _ -> null") + @Nullable + public static PsiReferenceExpression getReferenceExpressionFromNullComparison(PsiExpression expression, boolean equals) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiPolyadicExpression)) { + return null; + } + final PsiPolyadicExpression polyadic = (PsiPolyadicExpression)expression; + final IElementType tokenType = polyadic.getOperationTokenType(); + if (equals) { + if (!JavaTokenType.EQEQ.equals(tokenType)) { + return null; + } + } + else { + if (!JavaTokenType.NE.equals(tokenType)) { + return null; + } + } + final PsiExpression[] operands = polyadic.getOperands(); + if (operands.length != 2) { + return null; + } + PsiExpression comparedToNull = null; + if (PsiType.NULL.equals(operands[0].getType())) { + comparedToNull = operands[1]; + } + else if (PsiType.NULL.equals(operands[1].getType())) { + comparedToNull = operands[0]; + } + comparedToNull = ParenthesesUtils.stripParentheses(comparedToNull); + + return comparedToNull instanceof PsiReferenceExpression reference ? reference : null; + } + + /** + * Returns the expression compared with null if the supplied {@link PsiBinaryExpression} is null check (either with {@code ==} + * or with {@code !=}). Returns null otherwise. + * + * @param binOp binary expression to extract the value compared with null from + * @return value compared with null + */ + @Nullable + public static PsiExpression getValueComparedWithNull(PsiBinaryExpression binOp) { + final IElementType tokenType = binOp.getOperationTokenType(); + if (!tokenType.equals(JavaTokenType.EQEQ) && !tokenType.equals(JavaTokenType.NE)) { + return null; + } + final PsiExpression left = binOp.getLOperand(); + final PsiExpression right = binOp.getROperand(); + if (isNullLiteral(right)) { + return left; + } + if (isNullLiteral(left)) { + return right; + } + return null; } - if (literal.getValue() == null) { - // red code - return false; + + public static boolean isConcatenation(PsiElement element) { + if (element instanceof PsiPolyadicExpression polyadic) { + final PsiType type = polyadic.getType(); + return type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING); + } + return false; } - @NonNls final String text = literal.getText(); - if (text.charAt(0) != '0' || text.length() < 2) { - return false; + + @RequiredReadAction + public static boolean isAnnotatedNotNull(PsiExpression expression) { + return isAnnotated(expression, false); + } + + @RequiredReadAction + public static boolean isAnnotatedNullable(PsiExpression expression) { + return isAnnotated(expression, true); + } + + @RequiredReadAction + private static boolean isAnnotated(PsiExpression expression, boolean nullable) { + return ParenthesesUtils.stripParentheses(expression) instanceof PsiReferenceExpression reference + && reference.resolve() instanceof PsiModifierListOwner modifierListOwner + && (nullable ? NullableNotNullManager.isNullable(modifierListOwner) : NullableNotNullManager.isNotNull(modifierListOwner)); + } + + /** + * Returns true if the expression can be moved to earlier point in program order without possible semantic change or + * notable performance handicap. Examples of simple expressions are: + * - literal (number, char, string, class literal, true, false, null) + * - compile-time constant + * - this + * - variable/parameter read + * - static field read + * - instance field read having 'this' as qualifier + * + * @param expression an expression to test (must be valid expression) + * @return true if the supplied expression is simple + */ + @Contract("null -> false") + @RequiredReadAction + public static boolean isSimpleExpression(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiLiteralExpression || expression instanceof PsiThisExpression + || expression instanceof PsiClassObjectAccessExpression || isEvaluatedAtCompileTime(expression)) { + return true; + } + if (expression instanceof PsiReferenceExpression refExpr) { + PsiExpression qualifier = refExpr.getQualifierExpression(); + if (qualifier == null || qualifier instanceof PsiThisExpression) { + return true; + } + if (qualifier instanceof PsiReferenceExpression qualifierRefExpr + && qualifierRefExpr.resolve() instanceof PsiClass) { + return true; + } + } + return false; } - final char c1 = text.charAt(1); - return c1 == '_' || (c1 >= '0' && c1 <= '7'); - } - - @Contract("null, _ -> false") - public static boolean isMatchingChildAlwaysExecuted(@Nullable PsiExpression root, @Nonnull Predicate matcher) { - if (root == null) { - return false; + + /** + * Returns assignment expression if supplied element is a statement which contains assignment expression + * or it's an assignment expression itself. Only simple assignments are returned (like a = b, not a+= b). + * + * @param element element to get assignment expression from + * @return extracted assignment or null if assignment is not found or assignment is compound + */ + @Contract("null -> null") + @Nullable + public static PsiAssignmentExpression getAssignment(PsiElement element) { + if (element instanceof PsiExpressionStatement expressionStmt) { + element = expressionStmt.getExpression(); + } + if (element instanceof PsiExpression expression + && PsiUtil.skipParenthesizedExprDown(expression) instanceof PsiAssignmentExpression assignment + && JavaTokenType.EQ.equals(assignment.getOperationTokenType())) { + return assignment; + } + return null; } - AtomicBoolean result = new AtomicBoolean(false); - root.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitExpression(PsiExpression expression) { - super.visitExpression(expression); - if (matcher.test(expression)) { - result.set(true); - stopWalking(); - } - } - - @Override - public void visitConditionalExpression(PsiConditionalExpression expression) { - if (isMatchingChildAlwaysExecuted(expression.getCondition(), matcher) || (isMatchingChildAlwaysExecuted(expression.getThenExpression(), matcher) && isMatchingChildAlwaysExecuted - (expression.getElseExpression(), matcher))) { - result.set(true); - stopWalking(); - } - } - - @Override - public void visitPolyadicExpression(PsiPolyadicExpression expression) { - IElementType type = expression.getOperationTokenType(); - if (type.equals(JavaTokenType.OROR) || type.equals(JavaTokenType.ANDAND)) { - PsiExpression firstOperand = ArrayUtil.getFirstElement(expression.getOperands()); - if (isMatchingChildAlwaysExecuted(firstOperand, matcher)) { - result.set(true); - stopWalking(); - } - } else { - super.visitPolyadicExpression(expression); - } - } - - @Override - public void visitClass(PsiClass aClass) { - } - - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - } - }); - return result.get(); - } - - /** - * @param expression expression to test - * @return true if the expression return value is a new object which is guaranteed to be distinct from any other object created - * in the program. - */ - @Contract("null -> false") - public static boolean isNewObject(@Nullable PsiExpression expression) { - return expression != null && nonStructuralChildren(expression).allMatch(PsiNewExpression.class::isInstance); - } - - - /** - * Returns true if expression is evaluated in void context (i.e. its return value is not used) - * - * @param expression expression to check - * @return true if expression is evaluated in void context. - */ - public static boolean isVoidContext(PsiExpression expression) { - PsiElement element = PsiUtil.skipParenthesizedExprUp(expression.getParent()); - if (element instanceof PsiExpressionStatement) { - /*if(element.getParent() instanceof PsiSwitchLabeledRuleStatement) - { - PsiSwitchBlock block = ((PsiSwitchLabeledRuleStatement) element.getParent()).getEnclosingSwitchBlock(); - return !(block instanceof PsiSwitchExpression); - */ - return true; + + /** + * Returns an expression assigned to the target variable if supplied element is + * either simple (non-compound) assignment expression or an expression statement containing assignment expression + * and the corresponding assignment l-value is the reference to target variable. + * + * @param element element to get assignment expression from + * @param target a variable to extract an assignment to + * @return extracted assignment r-value or null if assignment is not found or assignment is compound or it's an assignment + * to the wrong variable + */ + @Contract("null, _ -> null; _, null -> null") + @RequiredReadAction + public static PsiExpression getAssignmentTo(PsiElement element, PsiVariable target) { + PsiAssignmentExpression assignment = getAssignment(element); + if (assignment != null && isReferenceTo(assignment.getLExpression(), target)) { + return assignment.getRExpression(); + } + return null; } - if (element instanceof PsiExpressionList && element.getParent() instanceof PsiExpressionListStatement) { - return true; + + @Contract("null, _ -> false") + public static boolean isLiteral(PsiElement element, Object value) { + return element instanceof PsiLiteralExpression literal && value.equals(literal.getValue()); } - if (element instanceof PsiLambdaExpression) { - if (PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType((PsiLambdaExpression) element))) { + + public static boolean isAutoBoxed(PsiExpression expression) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiParenthesizedExpression) { + return false; + } + if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression methodCall) { + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && AnnotationUtil.isAnnotated(method, POLYMORPHIC_SIGNATURE_ANNOTATION)) { + return false; + } + } + final PsiType expressionType = expression.getType(); + if (PsiPrimitiveType.getUnboxedType(expressionType) != null + && (parent instanceof PsiPrefixExpression || parent instanceof PsiPostfixExpression)) { + return true; + } + if (expressionType == null || expressionType.equals(PsiType.VOID) || !TypeConversionUtil.isPrimitiveAndNotNull(expressionType)) { + return false; + } + final PsiPrimitiveType primitiveType = (PsiPrimitiveType)expressionType; + final PsiClassType boxedType = primitiveType.getBoxedType(expression); + if (boxedType == null) { + return false; + } + final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true); + if (expectedType == null || ClassUtils.isPrimitive(expectedType)) { + return false; + } + if (!expectedType.isAssignableFrom(boxedType)) { + // JLS 5.2 Assignment Conversion + // check if a narrowing primitive conversion is applicable + if (!(expectedType instanceof PsiClassType) || !PsiUtil.isConstantExpression(expression)) { + return false; + } + final PsiClassType classType = (PsiClassType)expectedType; + final String className = classType.getCanonicalText(); + if (!CONVERTABLE_BOXED_CLASS_NAMES.contains(className)) { + return false; + } + if (!PsiType.BYTE.equals(expressionType) + && !PsiType.CHAR.equals(expressionType) + && !PsiType.SHORT.equals(expressionType) + && !PsiType.INT.equals(expressionType)) { + return false; + } + } return true; - } } - return false; - } - - /** - * Returns an effective qualifier for a reference. If qualifier is not specified, then tries to construct it - * e.g. creating a corresponding {@link PsiThisExpression}. - * - * @param ref a reference expression to get an effective qualifier for - * @return a qualifier or created (non-physical) {@link PsiThisExpression}. - * May return null if reference points to local or member of anonymous class referred from inner class - */ - @Nullable - public static PsiExpression getEffectiveQualifier(@Nonnull PsiReferenceExpression ref) { - PsiExpression qualifier = ref.getQualifierExpression(); - if (qualifier != null) { - return qualifier; + + /** + * If any operand of supplied binary expression refers to the supplied variable, returns other operand; + * otherwise returns null. + * + * @param binOp {@link PsiBinaryExpression} to extract the operand from + * @param variable variable to check against + * @return operand or null + */ + @Contract("null, _ -> null; !null, null -> null") + @RequiredReadAction + public static PsiExpression getOtherOperand(@Nullable PsiBinaryExpression binOp, @Nullable PsiVariable variable) { + if (binOp == null || variable == null) { + return null; + } + if (isReferenceTo(binOp.getLOperand(), variable)) { + return binOp.getROperand(); + } + if (isReferenceTo(binOp.getROperand(), variable)) { + return binOp.getLOperand(); + } + return null; } - PsiElementFactory factory = JavaPsiFacade.getElementFactory(ref.getProject()); - PsiMember member = tryCast(ref.resolve(), PsiMember.class); - if (member == null) { - // Reference resolves to non-member: probably variable/parameter/etc. - return null; + + @Contract("null, _ -> false; _, null -> false") + @RequiredReadAction + public static boolean isReferenceTo(PsiExpression expression, PsiVariable variable) { + if (variable == null) { + return false; + } + expression = PsiUtil.skipParenthesizedExprDown(expression); + return expression instanceof PsiReferenceExpression refExpr && refExpr.isReferenceTo(variable); + } + + /** + * Returns a method call expression for the supplied qualifier + * + * @param qualifier for method call + * @return a method call expression or null if the supplied expression is not a method call qualifier + */ + @Contract(value = "null -> null", pure = true) + public static PsiMethodCallExpression getCallForQualifier(PsiExpression qualifier) { + if (qualifier != null + && PsiUtil.skipParenthesizedExprUp(qualifier.getParent()) instanceof PsiReferenceExpression methodExpression + && PsiTreeUtil.isAncestor(methodExpression.getQualifierExpression(), qualifier, false) + && methodExpression.getParent() instanceof PsiMethodCallExpression methodCall) { + return methodCall; + } + return null; } - PsiClass memberClass = member.getContainingClass(); - if (memberClass != null) { - PsiClass containingClass = ClassUtils.getContainingClass(ref); - if (containingClass == null) { - containingClass = PsiTreeUtil.getContextOfType(ref, PsiClass.class); - } - if (containingClass != null && member.hasModifierProperty(PsiModifier.STATIC)) { - return factory.createReferenceExpression(containingClass); - } - if (!InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { - containingClass = ClassUtils.getContainingClass(containingClass); - while (containingClass != null && !InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { - containingClass = ClassUtils.getContainingClass(containingClass); - } - if (containingClass != null) { - String thisQualifier = containingClass.getQualifiedName(); - if (thisQualifier == null) { - if (PsiUtil.isLocalClass(containingClass)) { - thisQualifier = containingClass.getName(); - } else { - // Cannot qualify anonymous class - return null; + + /** + * Returns an array expression from array length retrieval expression + * + * @param expression expression to extract an array expression from + * @return an array expression or null if supplied expression is not array length retrieval + */ + @Nullable + public static PsiExpression getArrayFromLengthExpression(PsiExpression expression) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiReferenceExpression)) { + return null; + } + final PsiReferenceExpression reference = (PsiReferenceExpression)expression; + final String referenceName = reference.getReferenceName(); + if (!HardcodedMethodConstants.LENGTH.equals(referenceName)) { + return null; + } + final PsiExpression qualifier = reference.getQualifierExpression(); + if (qualifier == null) { + return null; + } + final PsiType type = qualifier.getType(); + if (type == null || type.getArrayDimensions() <= 0) { + return null; + } + return qualifier; + } + + /** + * Returns a qualifier for reference or creates a corresponding {@link PsiThisExpression} statement if + * a qualifier is null + * + * @param ref a reference expression to get a qualifier from + * @return a qualifier or created (non-physical) {@link PsiThisExpression}. + */ + @RequiredReadAction + public static PsiExpression getQualifierOrThis(PsiReferenceExpression ref) { + PsiExpression qualifier = ref.getQualifierExpression(); + if (qualifier != null) { + return qualifier; + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(ref.getProject()); + PsiMember member = tryCast(ref.resolve(), PsiMember.class); + if (member != null) { + PsiClass memberClass = member.getContainingClass(); + if (memberClass != null) { + PsiClass containingClass = ClassUtils.getContainingClass(ref); + if (containingClass == null) { + containingClass = PsiTreeUtil.getContextOfType(ref, PsiClass.class); + } + if (!InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { + containingClass = ClassUtils.getContainingClass(containingClass); + while (containingClass != null && !InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { + containingClass = ClassUtils.getContainingClass(containingClass); + } + if (containingClass != null) { + return factory.createExpressionFromText(containingClass.getQualifiedName() + "." + PsiKeyword.THIS, ref); + } + } } - } - return factory.createExpressionFromText(thisQualifier + "." + PsiKeyword.THIS, ref); } - } - } - return factory.createExpressionFromText(PsiKeyword.THIS, ref); - } - - /** - * Returns an expression which represents an array element with given index if array is known to be never modified - * after initialization. - * - * @param array an array variable - * @param index an element index - * @return an expression or null if index is out of bounds or array could be modified after initialization - */ - @Nullable - public static PsiExpression getConstantArrayElement(PsiVariable array, int index) { - if (index < 0) - return null; - PsiExpression[] elements = getConstantArrayElements(array); - if (elements == null || index >= elements.length) - return null; - return elements[index]; - } - - /** - * Returns an array of expressions which represent all array elements if array is known to be never modified - * after initialization. - * - * @param array an array variable - * @return an array or null if array could be modified after initialization - * (empty array means that the initializer is known to be an empty array). - */ - @Nullable - public static PsiExpression[] getConstantArrayElements(PsiVariable array) { - PsiExpression initializer = array.getInitializer(); - if (initializer instanceof PsiNewExpression) { - initializer = ((PsiNewExpression) initializer).getArrayInitializer(); - } - if (!(initializer instanceof PsiArrayInitializerExpression)) { - return null; - } - PsiExpression[] initializers = ((PsiArrayInitializerExpression) initializer).getInitializers(); - if (array instanceof PsiField && !(array.hasModifierProperty(PsiModifier.PRIVATE) && array.hasModifierProperty(PsiModifier.STATIC))) { - return null; - } - Boolean isConstantArray = LanguageCachedValueUtil.getCachedValue(array, () -> CachedValueProvider.Result - .create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT)); - return Boolean.TRUE.equals(isConstantArray) ? initializers : null; - } - - public static PsiElement getPassThroughParent(PsiExpression expression) { - while (true) { - final PsiElement parent = expression.getParent(); - if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression) { - expression = (PsiExpression) parent; - continue; - } else if (parent instanceof PsiConditionalExpression && ((PsiConditionalExpression) parent).getCondition() != expression) { - expression = (PsiExpression) parent; - continue; - } else if (parent instanceof PsiExpressionStatement) { - final PsiElement grandParent = parent.getParent(); - if (grandParent instanceof PsiSwitchLabeledRuleStatement) { - final PsiSwitchBlock block = ((PsiSwitchLabeledRuleStatement) grandParent).getEnclosingSwitchBlock(); - if (block instanceof PsiSwitchExpression) { - expression = (PsiExpression) block; - continue; - } - } - } else if (parent instanceof PsiYieldStatement) { - PsiSwitchExpression enclosing = ((PsiYieldStatement) parent).findEnclosingExpression(); - if (enclosing != null) { - expression = enclosing; - continue; - } - } - return parent; + return factory.createExpressionFromText(PsiKeyword.THIS, ref); + } + + /** + * Bind a reference element to a new name. The qualifier and type arguments (if present) remain the same + * + * @param ref reference element to rename + * @param newName new name + */ + @RequiredReadAction + public static void bindReferenceTo(PsiReferenceExpression ref, String newName) { + PsiElement nameElement = ref.getReferenceNameElement(); + if (nameElement == null) { + throw new IllegalStateException("Name element is null: " + ref); + } + if (newName.equals(nameElement.getText())) { + return; + } + PsiIdentifier identifier = JavaPsiFacade.getElementFactory(ref.getProject()).createIdentifier(newName); + nameElement.replace(identifier); + } + + /** + * Bind method call to a new name. Everything else like qualifier, type arguments or call arguments remain the same. + * + * @param call to rename + * @param newName new name + */ + @RequiredReadAction + public static void bindCallTo(PsiMethodCallExpression call, String newName) { + bindReferenceTo(call.getMethodExpression(), newName); + } + + /** + * Returns the expression itself (probably with stripped parentheses) or the corresponding value if the expression is a local variable + * reference which is initialized and not used anywhere else + * + * @param expression + * @return a resolved expression or expression itself + */ + @Contract("null -> null") + @Nullable + @RequiredReadAction + public static PsiExpression resolveExpression(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiReferenceExpression reference) { + PsiLocalVariable variable = tryCast(reference.resolve(), PsiLocalVariable.class); + if (variable != null) { + PsiExpression initializer = variable.getInitializer(); + if (initializer != null && ReferencesSearch.search(variable).forEach(ref -> ref == reference)) { + return initializer; + } + } + } + return expression; } - } - - /** - * Tries to find the range inside the expression (relative to its start) which represents the given substring - * assuming the expression evaluates to String. - * - * @param expression expression to find the range in - * @param from start offset of substring in the String value of the expression - * @param to end offset of substring in the String value of the expression - * @return found range or null if cannot be found - */ - @Nullable - @Contract(value = "null, _, _ -> null", pure = true) - public static TextRange findStringLiteralRange(PsiExpression expression, int from, int to) { - if (to < 0 || from > to) { - return null; + + @Contract(value = "null -> null") + @Nullable + @RequiredReadAction + public static PsiLocalVariable resolveLocalVariable(@Nullable PsiExpression expression) { + PsiReferenceExpression referenceExpression = tryCast(expression, PsiReferenceExpression.class); + if (referenceExpression == null) { + return null; + } + return tryCast(referenceExpression.resolve(), PsiLocalVariable.class); } - if (expression == null || !TypeUtils.isJavaLangString(expression.getType())) { - return null; + + @RequiredReadAction + public static boolean isOctalLiteral(PsiLiteralExpression literal) { + final PsiType type = literal.getType(); + if (!PsiType.INT.equals(type) && !PsiType.LONG.equals(type)) { + return false; + } + if (literal.getValue() == null) { + // red code + return false; + } + final String text = literal.getText(); + if (text.charAt(0) != '0' || text.length() < 2) { + return false; + } + final char c1 = text.charAt(1); + return c1 == '_' || (c1 >= '0' && c1 <= '7'); } - if (expression instanceof PsiLiteralExpression) { - String value = tryCast(((PsiLiteralExpression) expression).getValue(), String.class); - if (value == null || value.length() < from || value.length() < to) { + + @Contract("null, _ -> false") + public static boolean isMatchingChildAlwaysExecuted(@Nullable PsiExpression root, Predicate matcher) { + if (root == null) { + return false; + } + AtomicBoolean result = new AtomicBoolean(false); + root.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitExpression(PsiExpression expression) { + super.visitExpression(expression); + if (matcher.test(expression)) { + result.set(true); + stopWalking(); + } + } + + @Override + public void visitConditionalExpression(PsiConditionalExpression expression) { + if (isMatchingChildAlwaysExecuted(expression.getCondition(), matcher) + || (isMatchingChildAlwaysExecuted(expression.getThenExpression(), matcher) + && isMatchingChildAlwaysExecuted(expression.getElseExpression(), matcher))) { + result.set(true); + stopWalking(); + } + } + + @Override + public void visitPolyadicExpression(PsiPolyadicExpression expression) { + IElementType type = expression.getOperationTokenType(); + if (type.equals(JavaTokenType.OROR) || type.equals(JavaTokenType.ANDAND)) { + PsiExpression firstOperand = ArrayUtil.getFirstElement(expression.getOperands()); + if (isMatchingChildAlwaysExecuted(firstOperand, matcher)) { + result.set(true); + stopWalking(); + } + } + else { + super.visitPolyadicExpression(expression); + } + } + + @Override + public void visitClass(PsiClass aClass) { + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + }); + return result.get(); + } + + /** + * @param expression expression to test + * @return true if the expression return value is a new object which is guaranteed to be distinct from any other object created + * in the program. + */ + @Contract("null -> false") + public static boolean isNewObject(@Nullable PsiExpression expression) { + return expression != null && nonStructuralChildren(expression).allMatch(PsiNewExpression.class::isInstance); + } + + + /** + * Returns true if expression is evaluated in void context (i.e. its return value is not used) + * + * @param expression expression to check + * @return true if expression is evaluated in void context. + */ + public static boolean isVoidContext(PsiExpression expression) { + PsiElement element = PsiUtil.skipParenthesizedExprUp(expression.getParent()); + if (element instanceof PsiExpressionStatement) { + /*if(element.getParent() instanceof PsiSwitchLabeledRuleStatement) { + PsiSwitchBlock block = ((PsiSwitchLabeledRuleStatement) element.getParent()).getEnclosingSwitchBlock(); + return !(block instanceof PsiSwitchExpression); + */ + return true; + } + return element instanceof PsiExpressionList && element.getParent() instanceof PsiExpressionListStatement + || element instanceof PsiLambdaExpression lambda && PsiType.VOID.equals(LambdaUtil.getFunctionalInterfaceReturnType(lambda)); + } + + /** + * Returns an effective qualifier for a reference. If qualifier is not specified, then tries to construct it + * e.g. creating a corresponding {@link PsiThisExpression}. + * + * @param ref a reference expression to get an effective qualifier for + * @return a qualifier or created (non-physical) {@link PsiThisExpression}. + * May return null if reference points to local or member of anonymous class referred from inner class + */ + @Nullable + @RequiredReadAction + public static PsiExpression getEffectiveQualifier(PsiReferenceExpression ref) { + PsiExpression qualifier = ref.getQualifierExpression(); + if (qualifier != null) { + return qualifier; + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(ref.getProject()); + PsiMember member = tryCast(ref.resolve(), PsiMember.class); + if (member == null) { + // Reference resolves to non-member: probably variable/parameter/etc. + return null; + } + PsiClass memberClass = member.getContainingClass(); + if (memberClass != null) { + PsiClass containingClass = ClassUtils.getContainingClass(ref); + if (containingClass == null) { + containingClass = PsiTreeUtil.getContextOfType(ref, PsiClass.class); + } + if (containingClass != null && member.hasModifierProperty(PsiModifier.STATIC)) { + return factory.createReferenceExpression(containingClass); + } + if (!InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { + containingClass = ClassUtils.getContainingClass(containingClass); + while (containingClass != null && !InheritanceUtil.isInheritorOrSelf(containingClass, memberClass, true)) { + containingClass = ClassUtils.getContainingClass(containingClass); + } + if (containingClass != null) { + String thisQualifier = containingClass.getQualifiedName(); + if (thisQualifier == null) { + if (PsiUtil.isLocalClass(containingClass)) { + thisQualifier = containingClass.getName(); + } + else { + // Cannot qualify anonymous class + return null; + } + } + return factory.createExpressionFromText(thisQualifier + "." + PsiKeyword.THIS, ref); + } + } + } + return factory.createExpressionFromText(PsiKeyword.THIS, ref); + } + + /** + * Returns an expression which represents an array element with given index if array is known to be never modified + * after initialization. + * + * @param array an array variable + * @param index an element index + * @return an expression or null if index is out of bounds or array could be modified after initialization + */ + @Nullable + public static PsiExpression getConstantArrayElement(PsiVariable array, int index) { + if (index < 0) { + return null; + } + PsiExpression[] elements = getConstantArrayElements(array); + if (elements == null || index >= elements.length) { + return null; + } + return elements[index]; + } + + /** + * Returns an array of expressions which represent all array elements if array is known to be never modified + * after initialization. + * + * @param array an array variable + * @return an array or null if array could be modified after initialization + * (empty array means that the initializer is known to be an empty array). + */ + @Nullable + public static PsiExpression[] getConstantArrayElements(PsiVariable array) { + PsiExpression initializer = array.getInitializer(); + if (initializer instanceof PsiNewExpression newExpr) { + initializer = newExpr.getArrayInitializer(); + } + if (initializer instanceof PsiArrayInitializerExpression arrayInitializer + && (!(array instanceof PsiField field) || field.isPrivate() && field.isStatic())) { + Boolean isConstantArray = LanguageCachedValueUtil.getCachedValue( + array, + () -> CachedValueProvider.Result.create(isConstantArray(array), PsiModificationTracker.MODIFICATION_COUNT) + ); + return Boolean.TRUE.equals(isConstantArray) ? arrayInitializer.getInitializers() : null; + } return null; - } - return mapBackStringRange(expression.getText(), from, to); } - if (expression instanceof PsiParenthesizedExpression) { - PsiExpression operand = ((PsiParenthesizedExpression) expression).getExpression(); - TextRange range = findStringLiteralRange(operand, from, to); - return range == null ? null : range.shiftRight(operand.getStartOffsetInParent()); + + public static PsiElement getPassThroughParent(PsiExpression expression) { + while (true) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression) { + expression = (PsiExpression)parent; + continue; + } + else if (parent instanceof PsiConditionalExpression conditional && conditional.getCondition() != expression) { + expression = conditional; + continue; + } + else if (parent instanceof PsiExpressionStatement) { + if (parent.getParent() instanceof PsiSwitchLabeledRuleStatement switchLabeledRule + && switchLabeledRule.getEnclosingSwitchBlock() instanceof PsiSwitchExpression switchExpr) { + expression = switchExpr; + continue; + } + } + else if (parent instanceof PsiYieldStatement yieldStmt) { + PsiSwitchExpression enclosing = yieldStmt.findEnclosingExpression(); + if (enclosing != null) { + expression = enclosing; + continue; + } + } + return parent; + } } - if (expression instanceof PsiPolyadicExpression) { - PsiPolyadicExpression concatenation = (PsiPolyadicExpression) expression; - if (concatenation.getOperationTokenType() != JavaTokenType.PLUS) { - return null; - } - PsiExpression[] operands = concatenation.getOperands(); - for (PsiExpression operand : operands) { - Object constantValue = computeConstantExpression(operand); - if (constantValue == null) { - return null; - } - String stringValue = constantValue.toString(); - if (from < stringValue.length()) { - if (to > stringValue.length()) { + + /** + * Tries to find the range inside the expression (relative to its start) which represents the given substring + * assuming the expression evaluates to String. + * + * @param expression expression to find the range in + * @param from start offset of substring in the String value of the expression + * @param to end offset of substring in the String value of the expression + * @return found range or null if cannot be found + */ + @Nullable + @Contract(value = "null, _, _ -> null", pure = true) + @RequiredReadAction + public static TextRange findStringLiteralRange(PsiExpression expression, int from, int to) { + if (to < 0 || from > to) { return null; - } - TextRange range = findStringLiteralRange(operand, from, to); - return range == null ? null : range.shiftRight(operand.getStartOffsetInParent()); } - from -= stringValue.length(); - to -= stringValue.length(); - } - } - return null; - } - - /** - * Maps the substring range inside Java String literal value back into the source code range. - * - * @param text string literal as present in source code (including quotes) - * @param from start offset inside the represented string - * @param to end offset inside the represented string - * @return the range which represents the corresponding substring inside source representation, - * or null if from/to values are out of bounds. - */ - @Nullable - public static TextRange mapBackStringRange(@Nonnull String text, int from, int to) { - if (from > to || to < 0) - return null; - if (text.startsWith("`")) { - // raw string - return new TextRange(from + 1, to + 1); - } - if (!text.startsWith("\"")) { - return null; - } - if (text.indexOf('\\') == -1) { - return new TextRange(from + 1, to + 1); - } - int curOffset = 0; - int mappedFrom = -1, mappedTo = -1; - int end = text.length() - 1; - int i = 1; - while (i <= end) { - if (curOffset == from) { - mappedFrom = i; - } - if (curOffset == to) { - mappedTo = i; - break; - } - if (i == end) - break; - char c = text.charAt(i++); - if (c == '\\') { - if (i == end) - return null; - // like \u0020 - char c1 = text.charAt(i++); - if (c1 == 'u') { - while (i < end && text.charAt(i) == 'u') - i++; - i += 4; - } else if (c1 >= '0' && c1 <= '7') { // octal escape - char c2 = i < end ? text.charAt(i) : 0; - if (c2 >= '0' && c2 <= '7') { - i++; - char c3 = i < end ? text.charAt(i) : 0; - if (c3 >= '0' && c3 <= '7' && c1 <= '3') { - i++; + if (expression == null || !TypeUtils.isJavaLangString(expression.getType())) { + return null; + } + if (expression instanceof PsiLiteralExpression literal) { + String value = tryCast(literal.getValue(), String.class); + if (value == null || value.length() < from || value.length() < to) { + return null; } - } + return mapBackStringRange(literal.getText(), from, to); } - } - curOffset++; + if (expression instanceof PsiParenthesizedExpression parenthesized) { + PsiExpression operand = parenthesized.getExpression(); + TextRange range = findStringLiteralRange(operand, from, to); + return range == null ? null : range.shiftRight(operand.getStartOffsetInParent()); + } + if (expression instanceof PsiPolyadicExpression concatenation) { + if (concatenation.getOperationTokenType() != JavaTokenType.PLUS) { + return null; + } + for (PsiExpression operand : concatenation.getOperands()) { + Object constantValue = computeConstantExpression(operand); + if (constantValue == null) { + return null; + } + String stringValue = constantValue.toString(); + if (from < stringValue.length()) { + if (to > stringValue.length()) { + return null; + } + TextRange range = findStringLiteralRange(operand, from, to); + return range == null ? null : range.shiftRight(operand.getStartOffsetInParent()); + } + from -= stringValue.length(); + to -= stringValue.length(); + } + } + return null; } - if (mappedFrom >= 0 && mappedTo >= 0) { - return new TextRange(mappedFrom, mappedTo); + + /** + * Maps the substring range inside Java String literal value back into the source code range. + * + * @param text string literal as present in source code (including quotes) + * @param from start offset inside the represented string + * @param to end offset inside the represented string + * @return the range which represents the corresponding substring inside source representation, + * or null if from/to values are out of bounds. + */ + @Nullable + public static TextRange mapBackStringRange(String text, int from, int to) { + if (from > to || to < 0) { + return null; + } + if (text.startsWith("`")) { + // raw string + return new TextRange(from + 1, to + 1); + } + if (!text.startsWith("\"")) { + return null; + } + if (text.indexOf('\\') == -1) { + return new TextRange(from + 1, to + 1); + } + int curOffset = 0; + int mappedFrom = -1, mappedTo = -1; + int end = text.length() - 1; + int i = 1; + while (i <= end) { + if (curOffset == from) { + mappedFrom = i; + } + if (curOffset == to) { + mappedTo = i; + break; + } + if (i == end) { + break; + } + char c = text.charAt(i++); + if (c == '\\') { + if (i == end) { + return null; + } + // like \u0020 + char c1 = text.charAt(i++); + if (c1 == 'u') { + while (i < end && text.charAt(i) == 'u') + i++; + i += 4; + } + else if (c1 >= '0' && c1 <= '7') { // octal escape + char c2 = i < end ? text.charAt(i) : 0; + if (c2 >= '0' && c2 <= '7') { + i++; + char c3 = i < end ? text.charAt(i) : 0; + if (c3 >= '0' && c3 <= '7' && c1 <= '3') { + i++; + } + } + } + } + curOffset++; + } + if (mappedFrom >= 0 && mappedTo >= 0) { + return new TextRange(mappedFrom, mappedTo); + } + return null; } - return null; - } - private static boolean isConstantArray(PsiVariable array) { - PsiElement scope = PsiTreeUtil.getParentOfType(array, array instanceof PsiField ? PsiClass.class : PsiCodeBlock.class); - if (scope == null) { - return false; + @RequiredReadAction + private static boolean isConstantArray(PsiVariable array) { + PsiElement scope = PsiTreeUtil.getParentOfType(array, array instanceof PsiField ? PsiClass.class : PsiCodeBlock.class); + return scope != null && PsiTreeUtil.processElements( + scope, + e -> { + if (!(e instanceof PsiReferenceExpression)) { + return true; + } + PsiReferenceExpression ref = (PsiReferenceExpression)e; + if (!ref.isReferenceTo(array)) { + return true; + } + PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); + if (parent instanceof PsiForeachStatement foreach + && PsiTreeUtil.isAncestor(foreach.getIteratedValue(), ref, false)) { + return true; + } + if (parent instanceof PsiReferenceExpression reference) { + if (isReferenceTo(getArrayFromLengthExpression(reference), array)) { + return true; + } + if (reference.getParent() instanceof PsiMethodCallExpression methodCall + && MethodCallUtils.isCallToMethod( + methodCall, + CommonClassNames.JAVA_LANG_OBJECT, + null, + "clone", + PsiType.EMPTY_ARRAY + )) { + return true; + } + } + return parent instanceof PsiArrayAccessExpression arrayAccess && !PsiUtil.isAccessedForWriting(arrayAccess); + } + ); } - return PsiTreeUtil.processElements(scope, e -> { - if (!(e instanceof PsiReferenceExpression)) { - return true; - } - PsiReferenceExpression ref = (PsiReferenceExpression) e; - if (!ref.isReferenceTo(array)) { - return true; - } - PsiElement parent = PsiUtil.skipParenthesizedExprUp(ref.getParent()); - if (parent instanceof PsiForeachStatement && PsiTreeUtil.isAncestor(((PsiForeachStatement) parent).getIteratedValue(), ref, false)) { - return true; - } - if (parent instanceof PsiReferenceExpression) { - if (isReferenceTo(getArrayFromLengthExpression((PsiExpression) parent), array)) { - return true; - } - if (parent.getParent() instanceof PsiMethodCallExpression && - MethodCallUtils.isCallToMethod((PsiMethodCallExpression) parent.getParent(), CommonClassNames.JAVA_LANG_OBJECT, - null, "clone", PsiType.EMPTY_ARRAY)) { - return true; - } - } - return parent instanceof PsiArrayAccessExpression && !PsiUtil.isAccessedForWriting((PsiExpression) parent); - }); - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java new file mode 100644 index 0000000000..f925c7669c --- /dev/null +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/JavaDeprecationUtils.java @@ -0,0 +1,67 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.siyeh.ig.psiutils; + +import com.intellij.java.language.LanguageLevel; +import com.intellij.java.language.codeInsight.AnnotationUtil; +import com.intellij.java.language.impl.psi.impl.PsiImplUtil; +import com.intellij.java.language.psi.*; +import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.language.module.extension.JavaModuleExtension; +import consulo.language.psi.PsiElement; +import consulo.language.util.ModuleUtilCore; +import consulo.util.lang.ObjectUtil; +import consulo.util.lang.ThreeState; +import org.jspecify.annotations.Nullable; + +public final class JavaDeprecationUtils { + @RequiredReadAction + private static ThreeState isDeprecatedByAnnotation(PsiModifierListOwner owner, @Nullable PsiElement context) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(owner, CommonClassNames.JAVA_LANG_DEPRECATED); + if (annotation == null) { + return ThreeState.UNSURE; + } + if (context == null) { + return ThreeState.YES; + } + String since = null; + PsiAnnotationMemberValue value = annotation.findAttributeValue("since"); + if (value instanceof PsiLiteralExpression literal) { + since = ObjectUtil.tryCast(literal.getValue(), String.class); + } + if (since == null || ModuleUtilCore.getSdk(owner, JavaModuleExtension.class) == null) { + return ThreeState.YES; + } + LanguageLevel deprecationLevel = LanguageLevel.parse(since); + return ThreeState.fromBoolean(deprecationLevel == null || PsiUtil.getLanguageLevel(context).isAtLeast(deprecationLevel)); + } + + + /** + * Checks if the given PSI element is deprecated with annotation or JavaDoc tag, taking the context into account. + *
+ * It is suitable for elements other than {@link PsiDocCommentOwner}. + * The deprecation of JDK members may depend on context. E.g., uses if a JDK method is deprecated since Java 19, + * but current module has Java 17 target, than the method is not considered as deprecated. + * + * @param psiElement element to check whether it's deprecated + * @param context context in which the check should be performed + */ + @RequiredReadAction + public static boolean isDeprecated(PsiElement psiElement, @Nullable PsiElement context) { + if (psiElement instanceof PsiModifierListOwner modifierListOwner) { + ThreeState byAnnotation = isDeprecatedByAnnotation(modifierListOwner, context); + if (byAnnotation != ThreeState.UNSURE) { + return byAnnotation.toBoolean(); + } + } + if (psiElement instanceof PsiDocCommentOwner docCommentOwner) { + return docCommentOwner.isDeprecated(); + } + //noinspection SimplifiableIfStatement + if (psiElement instanceof PsiJavaDocumentedElement javaDocumentedElement) { + return PsiImplUtil.isDeprecatedByDocTag(javaDocumentedElement); + } + return false; + } +} diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodCallUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodCallUtils.java index 9f5c54f328..4a2c076723 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodCallUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodCallUtils.java @@ -15,21 +15,19 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.analysis.impl.codeInspection.dataFlow.instructions.MethodCallInstruction; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.MethodSignatureUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.HardcodedMethodConstants; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiElement; import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; import consulo.util.collection.ArrayUtil; -import org.jetbrains.annotations.NonNls; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashSet; import java.util.Set; import java.util.regex.Matcher; @@ -38,488 +36,526 @@ import static consulo.util.lang.ObjectUtil.tryCast; public class MethodCallUtils { + /** + * @noinspection StaticCollection + */ + private static final Set regexMethodNames = new HashSet<>(5); - /** - * @noinspection StaticCollection - */ - @NonNls - private static final Set regexMethodNames = new HashSet<>(5); - - static { - regexMethodNames.add("compile"); - regexMethodNames.add("matches"); - regexMethodNames.add("replaceFirst"); - regexMethodNames.add("replaceAll"); - regexMethodNames.add("split"); - } - - private MethodCallUtils() { - } - - @Nullable - public static String getMethodName(@Nonnull PsiMethodCallExpression expression) { - final PsiReferenceExpression method = expression.getMethodExpression(); - return method.getReferenceName(); - } - - @Nullable - public static PsiType getTargetType(@Nonnull PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); - if (qualifierExpression == null) { - return null; + static { + regexMethodNames.add("compile"); + regexMethodNames.add("matches"); + regexMethodNames.add("replaceFirst"); + regexMethodNames.add("replaceAll"); + regexMethodNames.add("split"); } - return qualifierExpression.getType(); - } - public static boolean isCompareToCall(@Nonnull PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - if (!HardcodedMethodConstants.COMPARE_TO.equals(methodExpression.getReferenceName())) { - return false; + private MethodCallUtils() { } - final PsiMethod method = expression.resolveMethod(); - return MethodUtils.isCompareTo(method); - } - - public static boolean isCompareToIgnoreCaseCall(@Nonnull PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - if (!"compareToIgnoreCase".equals(methodExpression.getReferenceName())) { - return false; - } - final PsiMethod method = expression.resolveMethod(); - return MethodUtils.isCompareToIgnoreCase(method); - } - - public static boolean isEqualsCall(PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final String name = methodExpression.getReferenceName(); - if (!HardcodedMethodConstants.EQUALS.equals(name)) { - return false; - } - final PsiMethod method = expression.resolveMethod(); - return MethodUtils.isEquals(method); - } - - public static boolean isEqualsIgnoreCaseCall(PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final String name = methodExpression.getReferenceName(); - if (!HardcodedMethodConstants.EQUALS_IGNORE_CASE.equals(name)) { - return false; - } - final PsiMethod method = expression.resolveMethod(); - return MethodUtils.isEqualsIgnoreCase(method); - } - - public static boolean isSimpleCallToMethod(@Nonnull PsiMethodCallExpression expression, - @NonNls @Nullable String calledOnClassName, - @Nullable PsiType returnType, - @NonNls @Nullable String methodName, - @NonNls @Nullable String... parameterTypeStrings) { - if (parameterTypeStrings == null) { - return isCallToMethod(expression, calledOnClassName, returnType, methodName, (PsiType[]) null); - } - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(expression.getProject()); - final PsiElementFactory factory = psiFacade.getElementFactory(); - final PsiType[] parameterTypes = PsiType.createArray(parameterTypeStrings.length); - final GlobalSearchScope scope = expression.getResolveScope(); - for (int i = 0; i < parameterTypeStrings.length; i++) { - final String parameterTypeString = parameterTypeStrings[i]; - parameterTypes[i] = factory.createTypeByFQClassName(parameterTypeString, scope); - } - return isCallToMethod(expression, calledOnClassName, returnType, methodName, parameterTypes); - } - public static boolean isCallToStaticMethod(@Nonnull PsiMethodCallExpression expression, @NonNls @Nonnull String calledOnClassName, @NonNls @Nonnull String methodName, int parameterCount) { - PsiExpression[] args = expression.getArgumentList().getExpressions(); - if (!methodName.equals(getMethodName(expression)) || args.length < parameterCount) { - return false; - } - PsiMethod method = expression.resolveMethod(); - if (method == null || !method.hasModifierProperty(PsiModifier.STATIC) || method.getParameterList().getParametersCount() != parameterCount || !method.isVarArgs() && args.length != - parameterCount) { - return false; - } - PsiClass aClass = method.getContainingClass(); - return aClass != null && calledOnClassName.equals(aClass.getQualifiedName()); - } - - public static boolean isCallToMethod(@Nonnull PsiMethodCallExpression expression, - @NonNls @Nullable String calledOnClassName, - @Nullable PsiType returnType, - @Nullable Pattern methodNamePattern, - @Nullable PsiType... parameterTypes) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - if (methodNamePattern != null) { - final String referenceName = methodExpression.getReferenceName(); - if (referenceName == null) { - return false; - } - final Matcher matcher = methodNamePattern.matcher(referenceName); - if (!matcher.matches()) { - return false; - } - } - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - return false; - } - if (calledOnClassName != null) { - final PsiExpression qualifier = methodExpression.getQualifierExpression(); - if (qualifier != null) { - if (!TypeUtils.expressionHasTypeOrSubtype(qualifier, calledOnClassName)) { - return false; - } - return MethodUtils.methodMatches(method, null, returnType, methodNamePattern, parameterTypes); - } + @Nullable + public static String getMethodName(PsiMethodCallExpression expression) { + final PsiReferenceExpression method = expression.getMethodExpression(); + return method.getReferenceName(); } - return MethodUtils.methodMatches(method, calledOnClassName, returnType, methodNamePattern, parameterTypes); - } - - public static boolean isCallToMethod(@Nonnull PsiMethodCallExpression expression, - @NonNls @Nullable String calledOnClassName, - @Nullable PsiType returnType, - @NonNls @Nullable String methodName, - @Nullable PsiType... parameterTypes) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - if (methodName != null) { - final String referenceName = methodExpression.getReferenceName(); - if (!methodName.equals(referenceName)) { - return false; - } + + @Nullable + public static PsiType getTargetType(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + if (qualifierExpression == null) { + return null; + } + return qualifierExpression.getType(); } - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - return false; + + public static boolean isCompareToCall(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + if (!HardcodedMethodConstants.COMPARE_TO.equals(methodExpression.getReferenceName())) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + return MethodUtils.isCompareTo(method); } - return MethodUtils.methodMatches(method, calledOnClassName, returnType, methodName, parameterTypes); - } - - public static boolean isCallToRegexMethod(PsiMethodCallExpression expression) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final String name = methodExpression.getReferenceName(); - if (!regexMethodNames.contains(name)) { - return false; + + public static boolean isCompareToIgnoreCaseCall(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + if (!"compareToIgnoreCase".equals(methodExpression.getReferenceName())) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + return MethodUtils.isCompareToIgnoreCase(method); } - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - return false; + + public static boolean isEqualsCall(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!HardcodedMethodConstants.EQUALS.equals(name)) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + return MethodUtils.isEquals(method); } - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return false; + + public static boolean isEqualsIgnoreCaseCall(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!HardcodedMethodConstants.EQUALS_IGNORE_CASE.equals(name)) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + return MethodUtils.isEqualsIgnoreCase(method); + } + + public static boolean isSimpleCallToMethod( + PsiMethodCallExpression expression, + @Nullable String calledOnClassName, + @Nullable PsiType returnType, + @Nullable String methodName, + @Nullable String... parameterTypeStrings + ) { + if (parameterTypeStrings == null) { + return isCallToMethod(expression, calledOnClassName, returnType, methodName, (PsiType[]) null); + } + final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(expression.getProject()); + final PsiElementFactory factory = psiFacade.getElementFactory(); + final PsiType[] parameterTypes = PsiType.createArray(parameterTypeStrings.length); + final GlobalSearchScope scope = expression.getResolveScope(); + for (int i = 0; i < parameterTypeStrings.length; i++) { + final String parameterTypeString = parameterTypeStrings[i]; + parameterTypes[i] = factory.createTypeByFQClassName(parameterTypeString, scope); + } + return isCallToMethod(expression, calledOnClassName, returnType, methodName, parameterTypes); + } + + public static boolean isCallToStaticMethod( + PsiMethodCallExpression expression, + String calledOnClassName, + String methodName, + int parameterCount + ) { + PsiExpression[] args = expression.getArgumentList().getExpressions(); + if (!methodName.equals(getMethodName(expression)) || args.length < parameterCount) { + return false; + } + PsiMethod method = expression.resolveMethod(); + if (method == null || !method.hasModifierProperty(PsiModifier.STATIC) || method.getParameterList() + .getParametersCount() != parameterCount || !method.isVarArgs() && args.length != + parameterCount) { + return false; + } + PsiClass aClass = method.getContainingClass(); + return aClass != null && calledOnClassName.equals(aClass.getQualifiedName()); + } + + public static boolean isCallToMethod( + PsiMethodCallExpression expression, + @Nullable String calledOnClassName, + @Nullable PsiType returnType, + @Nullable Pattern methodNamePattern, + @Nullable PsiType... parameterTypes + ) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + if (methodNamePattern != null) { + final String referenceName = methodExpression.getReferenceName(); + if (referenceName == null) { + return false; + } + final Matcher matcher = methodNamePattern.matcher(referenceName); + if (!matcher.matches()) { + return false; + } + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return false; + } + if (calledOnClassName != null) { + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier != null) { + if (!TypeUtils.expressionHasTypeOrSubtype(qualifier, calledOnClassName)) { + return false; + } + return MethodUtils.methodMatches(method, null, returnType, methodNamePattern, parameterTypes); + } + } + return MethodUtils.methodMatches(method, calledOnClassName, returnType, methodNamePattern, parameterTypes); + } + + public static boolean isCallToMethod( + PsiMethodCallExpression expression, + @Nullable String calledOnClassName, + @Nullable PsiType returnType, + @Nullable String methodName, + @Nullable PsiType... parameterTypes + ) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + if (methodName != null) { + final String referenceName = methodExpression.getReferenceName(); + if (!methodName.equals(referenceName)) { + return false; + } + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return false; + } + return MethodUtils.methodMatches(method, calledOnClassName, returnType, methodName, parameterTypes); } - final String className = containingClass.getQualifiedName(); - return JavaClassNames.JAVA_LANG_STRING.equals(className) || "java.util.regex.Pattern".equals(className); - } - - public static boolean isCallDuringObjectConstruction(PsiMethodCallExpression expression) { - final PsiMember member = PsiTreeUtil.getParentOfType(expression, PsiMember.class, true, PsiClass.class, PsiLambdaExpression.class); - if (member == null) { - return false; + + public static boolean isCallToRegexMethod(PsiMethodCallExpression expression) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!regexMethodNames.contains(name)) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return false; + } + final PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + return false; + } + final String className = containingClass.getQualifiedName(); + return CommonClassNames.JAVA_LANG_STRING.equals(className) || "java.util.regex.Pattern".equals(className); } - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final PsiExpression qualifier = methodExpression.getQualifierExpression(); - if (qualifier != null) { - if (!(qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression)) { + + public static boolean isCallDuringObjectConstruction(PsiMethodCallExpression expression) { + final PsiMember member = PsiTreeUtil.getParentOfType(expression, PsiMember.class, true, PsiClass.class, PsiLambdaExpression.class); + if (member == null) { + return false; + } + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier != null) { + if (!(qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression)) { + return false; + } + } + final PsiClass containingClass = member.getContainingClass(); + if (containingClass == null || containingClass.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + if (member instanceof PsiClassInitializer) { + final PsiClassInitializer classInitializer = (PsiClassInitializer) member; + if (!classInitializer.hasModifierProperty(PsiModifier.STATIC)) { + return true; + } + } + else if (member instanceof PsiMethod) { + final PsiMethod method = (PsiMethod) member; + if (method.isConstructor()) { + return true; + } + if (CloneUtils.isClone(method)) { + return true; + } + if (MethodUtils.simpleMethodMatches(method, null, "void", "readObject", "java.io.ObjectInputStream")) { + return true; + } + return MethodUtils.simpleMethodMatches(method, null, "void", "readObjectNoData"); + } + else if (member instanceof PsiField) { + final PsiField field = (PsiField) member; + if (!field.hasModifierProperty(PsiModifier.STATIC)) { + return true; + } + } return false; - } - } - final PsiClass containingClass = member.getContainingClass(); - if (containingClass == null || containingClass.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - if (member instanceof PsiClassInitializer) { - final PsiClassInitializer classInitializer = (PsiClassInitializer) member; - if (!classInitializer.hasModifierProperty(PsiModifier.STATIC)) { - return true; - } - } else if (member instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) member; - if (method.isConstructor()) { - return true; - } - if (CloneUtils.isClone(method)) { - return true; - } - if (MethodUtils.simpleMethodMatches(method, null, "void", "readObject", "java.io.ObjectInputStream")) { - return true; - } - return MethodUtils.simpleMethodMatches(method, null, "void", "readObjectNoData"); - } else if (member instanceof PsiField) { - final PsiField field = (PsiField) member; - if (!field.hasModifierProperty(PsiModifier.STATIC)) { - return true; - } - } - return false; - } - - public static boolean isMethodCallOnVariable(@Nonnull PsiMethodCallExpression expression, @Nonnull PsiVariable variable, @Nonnull String methodName) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - @NonNls final String name = methodExpression.getReferenceName(); - if (!methodName.equals(name)) { - return false; - } - final PsiExpression qualifier = ParenthesesUtils.stripParentheses(methodExpression.getQualifierExpression()); - if (!(qualifier instanceof PsiReferenceExpression)) { - return false; - } - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) qualifier; - final PsiElement element = referenceExpression.resolve(); - return variable.equals(element); - } - - @Nullable - public static PsiMethod findMethodWithReplacedArgument(@Nonnull PsiCall call, @Nonnull PsiExpression target, @Nonnull PsiExpression replacement) { - final PsiExpressionList argumentList = call.getArgumentList(); - if (argumentList == null) { - return null; - } - final PsiExpression[] expressions = argumentList.getExpressions(); - int index = -1; - for (int i = 0; i < expressions.length; i++) { - final PsiExpression expression = expressions[i]; - if (expression == target) { - index = i; - } - } - if (index < 0) { - return null; - } - final PsiCall copy = (PsiCall) call.copy(); - final PsiExpressionList copyArgumentList = copy.getArgumentList(); - assert copyArgumentList != null; - final PsiExpression[] arguments = copyArgumentList.getExpressions(); - arguments[index].replace(replacement); - if (call instanceof PsiEnumConstant) { - final PsiClass containingClass = ((PsiEnumConstant) call).getContainingClass(); - assert containingClass != null; - final JavaPsiFacade facade = JavaPsiFacade.getInstance(call.getProject()); - final PsiClassType type = facade.getElementFactory().createType(containingClass); - final JavaResolveResult resolveResult = facade.getResolveHelper().resolveConstructor(type, copy.getArgumentList(), call); - return (PsiMethod) resolveResult.getElement(); - } - return copy.resolveMethod(); - } - - /** - * Checks if the specified expression is an argument for any method call (skipping parentheses in between). - * If the method call is found, checks if same method is called when argument is replaced with replacement. - * - * @param expression the expression to check - * @param replacement the replacement to replace expression with - * @return true, if method was found and a different method was called with replacement. false, otherwise. - */ - public static boolean isNecessaryForSurroundingMethodCall(PsiExpression expression, PsiExpression replacement) { - PsiElement parent = expression.getParent(); - while (parent instanceof PsiParenthesizedExpression) { - expression = (PsiExpression) parent; - parent = parent.getParent(); - } - if (!(parent instanceof PsiExpressionList)) { - return false; - } - final PsiElement grandParent = parent.getParent(); - if (!(grandParent instanceof PsiCall)) { - return false; - } - final PsiCall call = (PsiCall) grandParent; - return call.resolveMethod() != findMethodWithReplacedArgument(call, expression, replacement); - } - - public static boolean isSuperMethodCall(@Nonnull PsiMethodCallExpression expression, @Nonnull PsiMethod method) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final PsiExpression target = ParenthesesUtils.stripParentheses(methodExpression.getQualifierExpression()); - if (!(target instanceof PsiSuperExpression)) { - return false; - } - final PsiMethod targetMethod = expression.resolveMethod(); - return targetMethod != null && MethodSignatureUtil.isSuperMethod(targetMethod, method); - } - - /** - * Returns true if given method call is a var-arg call - * - * @param call a call to test - * @return true if call is resolved to the var-arg method and var-arg form is actually used - */ - public static boolean isVarArgCall(PsiCall call) { - JavaResolveResult result = call.resolveMethodGenerics(); - PsiMethod method = tryCast(result.getElement(), PsiMethod.class); - if (method == null || !method.isVarArgs()) { - return false; - } - PsiSubstitutor substitutor = result.getSubstitutor(); - return MethodCallInstruction.isVarArgCall(method, substitutor, call.getArgumentList().getExpressions(), method.getParameterList().getParameters()); - } - - public static boolean containsSuperMethodCall(@Nonnull PsiMethod method) { - final SuperCallVisitor visitor = new SuperCallVisitor(method); - method.accept(visitor); - return visitor.isSuperCallFound(); - } - - public static boolean callWithNonConstantString(@Nonnull PsiMethodCallExpression expression, boolean considerStaticFinalConstant, String className, String... methodNames) { - final PsiReferenceExpression methodExpression = expression.getMethodExpression(); - final String methodName = methodExpression.getReferenceName(); - boolean found = false; - for (String name : methodNames) { - if (name.equals(methodName)) { - found = true; - break; - } - } - if (!found) { - return false; - } - final PsiMethod method = expression.resolveMethod(); - if (method == null) { - return false; - } - final PsiClass aClass = method.getContainingClass(); - if (aClass == null) { - return false; - } - if (!InheritanceUtil.isInheritor(aClass, className)) { - return false; - } - final PsiExpressionList argumentList = expression.getArgumentList(); - final PsiExpression argument = ParenthesesUtils.stripParentheses(ExpressionUtils.getFirstExpressionInList(argumentList)); - if (argument == null) { - return false; - } - final PsiType type = argument.getType(); - if (type == null || !type.equalsToText(JavaClassNames.JAVA_LANG_STRING)) { - return false; - } - if (considerStaticFinalConstant && argument instanceof PsiReferenceExpression) { - final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) argument; - final PsiElement target = referenceExpression.resolve(); - if (target instanceof PsiField) { - final PsiField field = (PsiField) target; - if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - } - } - return !PsiUtil.isConstantExpression(argument); - } - - /** - * For given method call, returns a qualifier if it's also a method call, or null otherwise - * - * @param methodCall call to check - * @return a qualifier call - */ - @Nullable - public static PsiMethodCallExpression getQualifierMethodCall(@Nonnull PsiMethodCallExpression methodCall) { - return tryCast(PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()), PsiMethodCallExpression.class); - } - - - /** - * Returns a method/constructor parameter which corresponds to given argument - * - * @param argument an argument to find the corresponding parameter - * @return a parameter or null if supplied expression is not a call argument, call is not resolved or expression is a var-arg - * argument. - */ - @Nullable - public static PsiParameter getParameterForArgument(@Nonnull PsiExpression argument) { - PsiExpressionList argList = tryCast(argument.getParent(), PsiExpressionList.class); - if (argList == null) - return null; - PsiElement parent = argList.getParent(); - if (parent instanceof PsiAnonymousClass) { - parent = parent.getParent(); - } - PsiCall call = tryCast(parent, PsiCall.class); - if (call == null) - return null; - PsiExpression[] args = argList.getExpressions(); - int index = ArrayUtil.indexOf(args, argument); - if (index == -1) - return null; - PsiMethod method = call.resolveMethod(); - if (method == null) - return null; - PsiParameter[] parameters = method.getParameterList().getParameters(); - if (index >= parameters.length) - return null; - if (isVarArgCall(call) && index >= parameters.length - 1) - return null; - return parameters[index]; - } - - private static class SuperCallVisitor extends JavaRecursiveElementWalkingVisitor { - - private final PsiMethod myMethod; - private boolean mySuperCallFound; - - public SuperCallVisitor(@Nonnull PsiMethod method) { - this.myMethod = method; } - @Override - public void visitElement(@Nonnull PsiElement element) { - if (!mySuperCallFound) { - super.visitElement(element); - } + public static boolean isMethodCallOnVariable( + PsiMethodCallExpression expression, + PsiVariable variable, + String methodName + ) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!methodName.equals(name)) { + return false; + } + final PsiExpression qualifier = ParenthesesUtils.stripParentheses(methodExpression.getQualifierExpression()); + if (!(qualifier instanceof PsiReferenceExpression)) { + return false; + } + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) qualifier; + final PsiElement element = referenceExpression.resolve(); + return variable.equals(element); + } + + @Nullable + public static PsiMethod findMethodWithReplacedArgument( + PsiCall call, + PsiExpression target, + PsiExpression replacement + ) { + final PsiExpressionList argumentList = call.getArgumentList(); + if (argumentList == null) { + return null; + } + final PsiExpression[] expressions = argumentList.getExpressions(); + int index = -1; + for (int i = 0; i < expressions.length; i++) { + final PsiExpression expression = expressions[i]; + if (expression == target) { + index = i; + } + } + if (index < 0) { + return null; + } + final PsiCall copy = (PsiCall) call.copy(); + final PsiExpressionList copyArgumentList = copy.getArgumentList(); + assert copyArgumentList != null; + final PsiExpression[] arguments = copyArgumentList.getExpressions(); + arguments[index].replace(replacement); + if (call instanceof PsiEnumConstant) { + final PsiClass containingClass = ((PsiEnumConstant) call).getContainingClass(); + assert containingClass != null; + final JavaPsiFacade facade = JavaPsiFacade.getInstance(call.getProject()); + final PsiClassType type = facade.getElementFactory().createType(containingClass); + final JavaResolveResult resolveResult = facade.getResolveHelper().resolveConstructor(type, copy.getArgumentList(), call); + return (PsiMethod) resolveResult.getElement(); + } + return copy.resolveMethod(); + } + + /** + * Checks if the specified expression is an argument for any method call (skipping parentheses in between). + * If the method call is found, checks if same method is called when argument is replaced with replacement. + * + * @param expression the expression to check + * @param replacement the replacement to replace expression with + * @return true, if method was found and a different method was called with replacement. false, otherwise. + */ + public static boolean isNecessaryForSurroundingMethodCall(PsiExpression expression, PsiExpression replacement) { + PsiElement parent = expression.getParent(); + while (parent instanceof PsiParenthesizedExpression) { + expression = (PsiExpression) parent; + parent = parent.getParent(); + } + if (!(parent instanceof PsiExpressionList)) { + return false; + } + final PsiElement grandParent = parent.getParent(); + if (!(grandParent instanceof PsiCall)) { + return false; + } + final PsiCall call = (PsiCall) grandParent; + return call.resolveMethod() != findMethodWithReplacedArgument(call, expression, replacement); } - @Override - public void visitClass(PsiClass aClass) { - // anonymous and inner classes inside methods are visited to reduce false positives - super.visitClass(aClass); + public static boolean isSuperMethodCall(PsiMethodCallExpression expression, PsiMethod method) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final PsiExpression target = ParenthesesUtils.stripParentheses(methodExpression.getQualifierExpression()); + if (!(target instanceof PsiSuperExpression)) { + return false; + } + final PsiMethod targetMethod = expression.resolveMethod(); + return targetMethod != null && MethodSignatureUtil.isSuperMethod(targetMethod, method); + } + + /** + * Returns true if given method call is a var-arg call + * + * @param call a call to test + * @return true if call is resolved to the var-arg method and var-arg form is actually used + */ + public static boolean isVarArgCall(PsiCall call) { + JavaResolveResult result = call.resolveMethodGenerics(); + PsiMethod method = tryCast(result.getElement(), PsiMethod.class); + if (method == null || !method.isVarArgs()) { + return false; + } + PsiSubstitutor substitutor = result.getSubstitutor(); + return MethodCallInstruction.isVarArgCall( + method, + substitutor, + call.getArgumentList().getExpressions(), + method.getParameterList().getParameters() + ); + } + + public static boolean containsSuperMethodCall(PsiMethod method) { + final SuperCallVisitor visitor = new SuperCallVisitor(method); + method.accept(visitor); + return visitor.isSuperCallFound(); + } + + public static boolean callWithNonConstantString( + PsiMethodCallExpression expression, + boolean considerStaticFinalConstant, + String className, + String... methodNames + ) { + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String methodName = methodExpression.getReferenceName(); + boolean found = false; + for (String name : methodNames) { + if (name.equals(methodName)) { + found = true; + break; + } + } + if (!found) { + return false; + } + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return false; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return false; + } + if (!InheritanceUtil.isInheritor(aClass, className)) { + return false; + } + final PsiExpressionList argumentList = expression.getArgumentList(); + final PsiExpression argument = ParenthesesUtils.stripParentheses(ExpressionUtils.getFirstExpressionInList(argumentList)); + if (argument == null) { + return false; + } + final PsiType type = argument.getType(); + if (type == null || !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { + return false; + } + if (considerStaticFinalConstant && argument instanceof PsiReferenceExpression) { + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression) argument; + final PsiElement target = referenceExpression.resolve(); + if (target instanceof PsiField) { + final PsiField field = (PsiField) target; + if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL)) { + return false; + } + } + } + return !PsiUtil.isConstantExpression(argument); + } + + /** + * For given method call, returns a qualifier if it's also a method call, or null otherwise + * + * @param methodCall call to check + * @return a qualifier call + */ + @Nullable + public static PsiMethodCallExpression getQualifierMethodCall(PsiMethodCallExpression methodCall) { + return tryCast( + PsiUtil.skipParenthesizedExprDown(methodCall.getMethodExpression().getQualifierExpression()), + PsiMethodCallExpression.class + ); + } + + + /** + * Returns a method/constructor parameter which corresponds to given argument + * + * @param argument an argument to find the corresponding parameter + * @return a parameter or null if supplied expression is not a call argument, call is not resolved or expression is a var-arg + * argument. + */ + @Nullable + public static PsiParameter getParameterForArgument(PsiExpression argument) { + PsiExpressionList argList = tryCast(argument.getParent(), PsiExpressionList.class); + if (argList == null) { + return null; + } + PsiElement parent = argList.getParent(); + if (parent instanceof PsiAnonymousClass) { + parent = parent.getParent(); + } + PsiCall call = tryCast(parent, PsiCall.class); + if (call == null) { + return null; + } + PsiExpression[] args = argList.getExpressions(); + int index = ArrayUtil.indexOf(args, argument); + if (index == -1) { + return null; + } + PsiMethod method = call.resolveMethod(); + if (method == null) { + return null; + } + PsiParameter[] parameters = method.getParameterList().getParameters(); + if (index >= parameters.length) { + return null; + } + if (isVarArgCall(call) && index >= parameters.length - 1) { + return null; + } + return parameters[index]; } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - // lambda's are visited to reduce false positives - super.visitLambdaExpression(expression); - } + private static class SuperCallVisitor extends JavaRecursiveElementWalkingVisitor { + private final PsiMethod myMethod; + private boolean mySuperCallFound; - @Override - public void visitIfStatement(PsiIfStatement statement) { - final PsiExpression condition = statement.getCondition(); - final Object result = ExpressionUtils.computeConstantExpression(condition); - if (result != null && result.equals(Boolean.FALSE)) { - return; - } - super.visitIfStatement(statement); - } + public SuperCallVisitor(PsiMethod method) { + this.myMethod = method; + } - @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression expression) { - if (mySuperCallFound) { - return; - } - super.visitMethodCallExpression(expression); - if (isSuperMethodCall(expression, myMethod)) { - mySuperCallFound = true; - } - } + @Override + public void visitElement(PsiElement element) { + if (!mySuperCallFound) { + super.visitElement(element); + } + } - @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - if (mySuperCallFound) { - return; - } - final PsiExpression qualifier = expression.getQualifierExpression(); - if (qualifier instanceof PsiSuperExpression) { - final PsiElement target = expression.resolve(); - if (target instanceof PsiMethod) { - if (MethodSignatureUtil.isSuperMethod((PsiMethod) target, myMethod)) { - mySuperCallFound = true; - return; - } - } - } - super.visitMethodReferenceExpression(expression); - } + @Override + public void visitClass(PsiClass aClass) { + // anonymous and inner classes inside methods are visited to reduce false positives + super.visitClass(aClass); + } - boolean isSuperCallFound() { - return mySuperCallFound; + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + // lambda's are visited to reduce false positives + super.visitLambdaExpression(expression); + } + + @Override + public void visitIfStatement(PsiIfStatement statement) { + final PsiExpression condition = statement.getCondition(); + final Object result = ExpressionUtils.computeConstantExpression(condition); + if (result != null && result.equals(Boolean.FALSE)) { + return; + } + super.visitIfStatement(statement); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + if (mySuperCallFound) { + return; + } + super.visitMethodCallExpression(expression); + if (isSuperMethodCall(expression, myMethod)) { + mySuperCallFound = true; + } + } + + @Override + public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + if (mySuperCallFound) { + return; + } + final PsiExpression qualifier = expression.getQualifierExpression(); + if (qualifier instanceof PsiSuperExpression) { + final PsiElement target = expression.resolve(); + if (target instanceof PsiMethod) { + if (MethodSignatureUtil.isSuperMethod((PsiMethod) target, myMethod)) { + mySuperCallFound = true; + return; + } + } + } + super.visitMethodReferenceExpression(expression); + } + + boolean isSuperCallFound() { + return mySuperCallFound; + } } - } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodUtils.java index f0fbf70891..f6437cb55b 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/MethodUtils.java @@ -15,6 +15,9 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.impl.codeInspection.ControlFlowUtils; +import com.intellij.java.analysis.impl.codeInspection.EquivalenceChecker; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.indexing.search.searches.ClassInheritorsSearch; import com.intellij.java.indexing.search.searches.OverridingMethodsSearch; import com.intellij.java.language.psi.*; @@ -25,16 +28,13 @@ import com.intellij.java.language.psi.util.PsiUtil; import com.siyeh.HardcodedMethodConstants; import consulo.application.util.query.Query; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiElement; import consulo.language.util.IncorrectOperationException; import consulo.project.Project; +import org.jspecify.annotations.Nullable; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -46,13 +46,13 @@ private MethodUtils() { @Contract("null -> false") public static boolean isComparatorCompare(@Nullable PsiMethod method) { - return method != null && methodMatches(method, JavaClassNames.JAVA_UTIL_COMPARATOR, PsiType.INT, "compare", null, null); + return method != null && methodMatches(method, CommonClassNames.JAVA_UTIL_COMPARATOR, PsiType.INT, "compare", null, null); } @Contract("null -> false") public static boolean isCompareTo(@Nullable PsiMethod method) { return method != null && methodMatches(method, null, PsiType.INT, HardcodedMethodConstants.COMPARE_TO, PsiType.NULL) && InheritanceUtil.isInheritor(method.getContainingClass(), - JavaClassNames.JAVA_LANG_COMPARABLE); + CommonClassNames.JAVA_LANG_COMPARABLE); } @Contract("null -> false") @@ -61,7 +61,7 @@ public static boolean isCompareToIgnoreCase(@Nullable PsiMethod method) { return false; } final PsiClassType stringType = TypeUtils.getStringType(method); - return methodMatches(method, "java.lang.String", PsiType.INT, "compareToIgnoreCase", stringType); + return methodMatches(method, CommonClassNames.JAVA_LANG_STRING, PsiType.INT, "compareToIgnoreCase", stringType); } @Contract("null -> false") @@ -98,7 +98,7 @@ public static boolean isEqualsIgnoreCase(@Nullable PsiMethod method) { return false; } final PsiClassType stringType = TypeUtils.getStringType(method); - return methodMatches(method, "java.lang.String", PsiType.BOOLEAN, HardcodedMethodConstants.EQUALS_IGNORE_CASE, stringType); + return methodMatches(method, CommonClassNames.JAVA_LANG_STRING, PsiType.BOOLEAN, HardcodedMethodConstants.EQUALS_IGNORE_CASE, stringType); } /** @@ -113,8 +113,8 @@ public static boolean isEqualsIgnoreCase(@Nullable PsiMethod method) { * @return true, if the specified method matches the specified constraints, * false otherwise */ - public static boolean methodMatches(@Nonnull PsiMethod method, - @NonNls @Nullable String containingClassName, + public static boolean methodMatches(PsiMethod method, + @Nullable String containingClassName, @Nullable PsiType returnType, @Nullable Pattern methodNamePattern, @Nullable PsiType... parameterTypes) { @@ -140,10 +140,10 @@ public static boolean methodMatches(@Nonnull PsiMethod method, * @return true, if the specified method matches the specified constraints, * false otherwise */ - public static boolean methodMatches(@Nonnull PsiMethod method, - @NonNls @Nullable String containingClassName, + public static boolean methodMatches(PsiMethod method, + @Nullable String containingClassName, @Nullable PsiType returnType, - @NonNls @Nullable String methodName, + @Nullable String methodName, @Nullable PsiType... parameterTypes) { final String name = method.getName(); if (methodName != null && !methodName.equals(name)) { @@ -152,7 +152,7 @@ public static boolean methodMatches(@Nonnull PsiMethod method, return methodMatches(method, containingClassName, returnType, parameterTypes); } - private static boolean methodMatches(@Nonnull PsiMethod method, @NonNls @Nullable String containingClassName, @Nullable PsiType returnType, @Nullable PsiType... parameterTypes) { + private static boolean methodMatches(PsiMethod method, @Nullable String containingClassName, @Nullable PsiType returnType, @Nullable PsiType... parameterTypes) { if (parameterTypes != null) { final PsiParameterList parameterList = method.getParameterList(); if (parameterList.getParametersCount() != parameterTypes.length) { @@ -184,11 +184,11 @@ private static boolean methodMatches(@Nonnull PsiMethod method, @NonNls @Nullabl return true; } - public static boolean simpleMethodMatches(@Nonnull PsiMethod method, - @NonNls @Nullable String containingClassName, - @NonNls @Nullable String returnTypeString, - @NonNls @Nullable String methodName, - @NonNls @Nullable String... parameterTypeStrings) { + public static boolean simpleMethodMatches(PsiMethod method, + @Nullable String containingClassName, + @Nullable String returnTypeString, + @Nullable String methodName, + @Nullable String... parameterTypeStrings) { final Project project = method.getProject(); final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); final PsiElementFactory factory = psiFacade.getElementFactory(); @@ -216,12 +216,12 @@ public static boolean simpleMethodMatches(@Nonnull PsiMethod method, } } - public static boolean hasSuper(@Nonnull PsiMethod method) { + public static boolean hasSuper(PsiMethod method) { return getSuper(method) != null; } @Nullable - public static PsiMethod getSuper(@Nonnull PsiMethod method) { + public static PsiMethod getSuper(PsiMethod method) { final MethodSignatureBackedByPsiMethod signature = getSuperMethodSignature(method); if (signature == null) { return null; @@ -230,7 +230,7 @@ public static PsiMethod getSuper(@Nonnull PsiMethod method) { } @Nullable - public static MethodSignatureBackedByPsiMethod getSuperMethodSignature(@Nonnull PsiMethod method) { + public static MethodSignatureBackedByPsiMethod getSuperMethodSignature(PsiMethod method) { if (method.isConstructor() || method.hasModifierProperty(PsiModifier.STATIC) || method.hasModifierProperty(PsiModifier.PRIVATE)) { return null; } @@ -330,7 +330,7 @@ private static boolean isTrivial(PsiCodeBlock codeBlock, boolean throwIsTrivial) return true; } - public static boolean hasInThrows(@Nonnull PsiMethod method, @Nonnull String... exceptions) { + public static boolean hasInThrows(PsiMethod method, String... exceptions) { if (exceptions.length == 0) { throw new IllegalArgumentException("no exceptions specified"); } @@ -380,8 +380,7 @@ public static boolean isChainable(PsiMethod method) { * @param specificType a specific type (class type or intersection type) * @return more specific method, or base class method if more specific method cannot be found */ - @Nonnull - public static PsiMethod findSpecificMethod(@Nonnull PsiMethod method, @Nullable PsiType specificType) { + public static PsiMethod findSpecificMethod(PsiMethod method, @Nullable PsiType specificType) { PsiClass qualifierClass = method.getContainingClass(); if (qualifierClass == null) return method; @@ -424,6 +423,6 @@ public static boolean isStringLength(@Nullable PsiMethod method) { return false; } PsiClass aClass = method.getContainingClass(); - return aClass != null && JavaClassNames.JAVA_LANG_STRING.equals(aClass.getQualifiedName()); + return aClass != null && CommonClassNames.JAVA_LANG_STRING.equals(aClass.getQualifiedName()); } } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java deleted file mode 100644 index 16a7adffe7..0000000000 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/SideEffectChecker.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright 2003-2014 Dave Griffith, Bas Leijdekkers - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.siyeh.ig.psiutils; - -import com.intellij.java.analysis.impl.codeInspection.dataFlow.ContractValue; -import com.intellij.java.analysis.impl.codeInspection.dataFlow.JavaMethodContractUtil; -import com.intellij.java.language.psi.*; -import com.intellij.java.language.psi.util.InheritanceUtil; -import com.intellij.java.language.psi.util.PropertyUtil; -import com.intellij.java.language.psi.util.PsiUtil; -import consulo.language.ast.IElementType; -import consulo.language.psi.PsiDirectory; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiFile; -import consulo.language.psi.PsiPackage; -import consulo.language.psi.util.PsiTreeUtil; -import consulo.util.collection.SmartList; -import one.util.streamex.StreamEx; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.Predicate; - -import static consulo.util.lang.ObjectUtil.tryCast; - -public class SideEffectChecker { - private static final Set ourSideEffectFreeClasses = new HashSet<>(Arrays.asList( - Object.class.getName(), - Short.class.getName(), - Character.class.getName(), - Byte.class.getName(), - Integer.class.getName(), - Long.class.getName(), - Float.class.getName(), - Double.class.getName(), - String.class.getName(), - StringBuffer.class.getName(), - Boolean.class.getName(), - - ArrayList.class.getName(), - Date.class.getName(), - HashMap.class.getName(), - HashSet.class.getName(), - Hashtable.class.getName(), - LinkedHashMap.class.getName(), - LinkedHashSet.class.getName(), - LinkedList.class.getName(), - Stack.class.getName(), - TreeMap.class.getName(), - TreeSet.class.getName(), - Vector.class.getName(), - WeakHashMap.class.getName())); - - private SideEffectChecker() { - } - - public static boolean mayHaveSideEffects(@Nonnull PsiExpression exp) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(null, exp); - exp.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - public static boolean mayHaveSideEffects(@Nonnull PsiElement element, Predicate shouldIgnoreElement) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(null, element, shouldIgnoreElement); - element.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - /** - * Returns true if element execution may cause non-local side-effect. Side-effects like control flow within method; throw/return or - * local variable declaration or update are considered as local side-effects. - * - * @param element element to check - * @return true if element execution may cause non-local side-effect. - */ - public static boolean mayHaveNonLocalSideEffects(@Nonnull PsiElement element) { - return mayHaveSideEffects(element, SideEffectChecker::isLocalSideEffect); - } - - private static boolean isLocalSideEffect(PsiElement e) { - if (e instanceof PsiContinueStatement || - e instanceof PsiReturnStatement || - e instanceof PsiThrowStatement) { - return true; - } - if (e instanceof PsiLocalVariable) { - return true; - } - - PsiReferenceExpression ref = null; - if (e instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression) e; - ref = tryCast(PsiUtil.skipParenthesizedExprDown(assignment.getLExpression()), PsiReferenceExpression.class); - } - if (e instanceof PsiUnaryExpression) { - PsiExpression operand = ((PsiUnaryExpression) e).getOperand(); - ref = tryCast(PsiUtil.skipParenthesizedExprDown(operand), PsiReferenceExpression.class); - } - if (ref != null) { - PsiElement target = ref.resolve(); - if (target instanceof PsiLocalVariable || target instanceof PsiParameter) { - return true; - } - } - return false; - } - - public static boolean checkSideEffects(@Nonnull PsiExpression element, @Nullable List sideEffects) { - return checkSideEffects(element, sideEffects, e -> false); - } - - public static boolean checkSideEffects(@Nonnull PsiExpression element, - @Nullable List sideEffects, - @Nonnull Predicate ignoreElement) { - final SideEffectsVisitor visitor = new SideEffectsVisitor(sideEffects, element, ignoreElement); - element.accept(visitor); - return visitor.mayHaveSideEffects(); - } - - public static List extractSideEffectExpressions(@Nonnull PsiExpression element) { - List list = new SmartList<>(); - element.accept(new SideEffectsVisitor(list, element)); - return StreamEx.of(list).select(PsiExpression.class).toList(); - } - - private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { - private final - @Nullable - List mySideEffects; - private final - @Nonnull - PsiElement myStartElement; - private final - @Nonnull - Predicate myIgnorePredicate; - boolean found; - - SideEffectsVisitor(@Nullable List sideEffects, @Nonnull PsiElement startElement) { - this(sideEffects, startElement, call -> false); - } - - SideEffectsVisitor(@Nullable List sideEffects, @Nonnull PsiElement startElement, @Nonnull Predicate predicate) { - myStartElement = startElement; - myIgnorePredicate = predicate; - mySideEffects = sideEffects; - } - - private boolean addSideEffect(PsiElement element) { - if (myIgnorePredicate.test(element)) { - return false; - } - found = true; - if (mySideEffects != null) { - mySideEffects.add(element); - } else { - stopWalking(); - } - return true; - } - - @Override - public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { - if (addSideEffect(expression)) { - return; - } - super.visitAssignmentExpression(expression); - } - - @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression expression) { - final PsiMethod method = expression.resolveMethod(); - if (!isPure(method)) { - if (addSideEffect(expression)) { - return; - } - } - super.visitMethodCallExpression(expression); - } - - protected boolean isPure(PsiMethod method) { - if (method == null) { - return false; - } - PsiField field = PropertyUtil.getFieldOfGetter(method); - if (field != null) { - return !field.hasModifierProperty(PsiModifier.VOLATILE); - } - return JavaMethodContractUtil.isPure(method) && !mayHaveExceptionalSideEffect(method); - } - - @Override - public void visitNewExpression(@Nonnull PsiNewExpression expression) { - if (!expression.isArrayCreation() && !isSideEffectFreeConstructor(expression)) { - if (addSideEffect(expression)) { - return; - } - } - super.visitNewExpression(expression); - } - - @Override - public void visitUnaryExpression(@Nonnull PsiUnaryExpression expression) { - final IElementType tokenType = expression.getOperationTokenType(); - if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { - if (addSideEffect(expression)) { - return; - } - } - super.visitUnaryExpression(expression); - } - - @Override - public void visitVariable(PsiVariable variable) { - if (addSideEffect(variable)) { - return; - } - super.visitVariable(variable); - } - - @Override - public void visitBreakStatement(PsiBreakStatement statement) { - PsiStatement exitedStatement = statement.findExitedStatement(); - if (exitedStatement == null || !PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { - if (addSideEffect(statement)) { - return; - } - } - super.visitBreakStatement(statement); - } - - @Override - public void visitClass(PsiClass aClass) { - // local or anonymous class declaration is not side effect per se (unless it's instantiated) - } - - @Override - public void visitContinueStatement(PsiContinueStatement statement) { - PsiStatement exitedStatement = statement.findContinuedStatement(); - if (exitedStatement != null && PsiTreeUtil.isAncestor(myStartElement, exitedStatement, false)) { - return; - } - if (addSideEffect(statement)) { - return; - } - super.visitContinueStatement(statement); - } - - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - if (addSideEffect(statement)) { - return; - } - super.visitReturnStatement(statement); - } - - @Override - public void visitThrowStatement(PsiThrowStatement statement) { - if (addSideEffect(statement)) { - return; - } - super.visitThrowStatement(statement); - } - - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - // lambda is not side effect per se (unless it's called) - } - - public boolean mayHaveSideEffects() { - return found; - } - } - - /** - * Returns true if given method function is likely to throw an exception (e.g. "assertEquals"). In some cases this means that - * the method call should be preserved in source code even if it's pure (i.e. does not change the program state). - * - * @param method a method to check - * @return true if the method has exceptional side effect - */ - public static boolean mayHaveExceptionalSideEffect(PsiMethod method) { - String name = method.getName(); - if (name.startsWith("assert") || name.startsWith("check") || name.startsWith("require")) { - return true; - } - PsiClass aClass = method.getContainingClass(); - if (InheritanceUtil.isInheritor(aClass, "org.assertj.core.api.Descriptable")) { - // See com.intellij.codeInsight.DefaultInferredAnnotationProvider#getHardcodedContractAnnotation - return true; - } - return JavaMethodContractUtil.getMethodCallContracts(method, null).stream() - .filter(mc -> mc.getConditions().stream().noneMatch(ContractValue::isBoundCheckingCondition)) - .anyMatch(mc -> mc.getReturnValue().isFail()); - } - - private static boolean isSideEffectFreeConstructor(@Nonnull PsiNewExpression newExpression) { - PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass(); - if (anonymousClass != null && anonymousClass.getInitializers().length == 0) { - PsiClass baseClass = anonymousClass.getBaseClassType().resolve(); - if (baseClass != null && baseClass.isInterface()) { - return true; - } - } - PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); - PsiClass aClass = classReference == null ? null : (PsiClass) classReference.resolve(); - String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); - if (qualifiedName == null) { - return false; - } - if (ourSideEffectFreeClasses.contains(qualifiedName)) { - return true; - } - - PsiFile file = aClass.getContainingFile(); - PsiDirectory directory = file.getContainingDirectory(); - PsiPackage classPackage = directory == null ? null : JavaDirectoryService.getInstance().getPackage(directory); - String packageName = classPackage == null ? null : classPackage.getQualifiedName(); - - // all Throwable descendants from java.lang are side effects free - if (CommonClassNames.DEFAULT_PACKAGE.equals(packageName) || "java.io".equals(packageName)) { - PsiClass throwableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.Throwable", aClass.getResolveScope()); - if (throwableClass != null && InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { - return true; - } - } - return false; - } -} diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StatementExtractor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StatementExtractor.java index 97d7f1a2e5..910060465e 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StatementExtractor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StatementExtractor.java @@ -25,7 +25,6 @@ import consulo.util.lang.ObjectUtil; import one.util.streamex.StreamEx; -import javax.annotation.Nonnull; import java.util.List; public class StatementExtractor { @@ -49,7 +48,6 @@ public String toString() { * @param root a root expression * @return an array of non-physical statements which represent the same logic as passed expressions */ - @Nonnull public static PsiStatement[] generateStatements(List expressionsToKeep, PsiExpression root) { String statementsCode = generateStatementsText(expressionsToKeep, root); if (statementsCode.isEmpty()) { @@ -65,8 +63,7 @@ public static String generateStatementsText(List expressionsToKee return result.toString(); } - @Nonnull - private static Node createNode(@Nonnull PsiExpression expression, @Nonnull PsiExpression root) { + private static Node createNode(PsiExpression expression, PsiExpression root) { Node result = new Expr(expression); PsiExpression parent; while (expression != root) { @@ -85,8 +82,7 @@ private static Node createNode(@Nonnull PsiExpression expression, @Nonnull PsiEx return result; } - @Nonnull - private static Node foldNode(@Nonnull Node node, @Nonnull PsiExpression expression, @Nonnull PsiExpression parent) { + private static Node foldNode(Node node, PsiExpression expression, PsiExpression parent) { if (parent instanceof PsiPolyadicExpression) { PsiPolyadicExpression polyadic = (PsiPolyadicExpression) parent; IElementType type = polyadic.getOperationTokenType(); @@ -131,17 +127,14 @@ protected Node(PsiExpression anchor) { private static class Cond extends Node { private final - @Nonnull PsiExpression myCondition; private final - @Nonnull Node myThenBranch; private final - @Nonnull Node myElseBranch; private final int myLimit; - private Cond(@Nonnull PsiExpression anchor, @Nonnull PsiExpression condition, int limit, @Nonnull Node thenBranch, @Nonnull Node elseBranch) { + private Cond(PsiExpression anchor, PsiExpression condition, int limit, Node thenBranch, Node elseBranch) { super(anchor); myCondition = condition; myLimit = limit; @@ -192,7 +185,7 @@ public Node prepend(Node node) { } private static class Expr extends Node { - private Expr(@Nonnull PsiExpression expression) { + private Expr(PsiExpression expression) { super(expression); } @@ -208,13 +201,11 @@ public String toString() { private static class Cons extends Node { private final - @Nonnull Node myHead; private final - @Nonnull Node myTail; - private Cons(@Nonnull Node head, @Nonnull Node tail) { + private Cons(Node head, Node tail) { super(head.myAnchor); assert !(head instanceof Cons); myHead = head; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StreamApiUtil.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StreamApiUtil.java index d6231d3d50..80656f391e 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StreamApiUtil.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/StreamApiUtil.java @@ -18,7 +18,6 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; -import consulo.java.language.module.util.JavaClassNames; import org.jetbrains.annotations.Contract; /** @@ -36,19 +35,19 @@ public static PsiType getStreamElementType(PsiType type, boolean variableType) { return null; } PsiClass aClass = ((PsiClassType) type).resolve(); - if (InheritanceUtil.isInheritor(aClass, false, JavaClassNames.JAVA_UTIL_STREAM_INT_STREAM)) { + if (InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM)) { return PsiType.INT; } - if (InheritanceUtil.isInheritor(aClass, false, JavaClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) { + if (InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) { return PsiType.LONG; } - if (InheritanceUtil.isInheritor(aClass, false, JavaClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) { + if (InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) { return PsiType.DOUBLE; } - if (!InheritanceUtil.isInheritor(aClass, false, JavaClassNames.JAVA_UTIL_STREAM_STREAM)) { + if (!InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_STREAM)) { return null; } - PsiType streamType = PsiUtil.substituteTypeParameter(type, JavaClassNames.JAVA_UTIL_STREAM_STREAM, 0, false); + PsiType streamType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, false); if (variableType) { if (streamType instanceof PsiIntersectionType) { return null; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TestUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TestUtils.java index 045577638e..e579530ea4 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TestUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TestUtils.java @@ -30,10 +30,8 @@ import consulo.module.content.ProjectRootManager; import consulo.util.lang.ObjectUtil; import consulo.virtualFileSystem.VirtualFile; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -57,12 +55,12 @@ public static boolean isInTestSourceContent(@Nullable PsiElement element) { return virtualFile != null && ProjectRootManager.getInstance(file.getProject()).getFileIndex().isInTestSourceContent(virtualFile); } - public static boolean isPartOfJUnitTestMethod(@Nonnull PsiElement element) { + public static boolean isPartOfJUnitTestMethod(PsiElement element) { final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, false); return method != null && isJUnitTestMethod(method); } - public static boolean isJUnit4BeforeOrAfterMethod(@Nonnull PsiMethod method) { + public static boolean isJUnit4BeforeOrAfterMethod(PsiMethod method) { return AnnotationUtil.isAnnotated(method, "org.junit.Before", CHECK_HIERARCHY) || AnnotationUtil.isAnnotated(method, "org.junit.After", CHECK_HIERARCHY); } @@ -98,7 +96,7 @@ public static boolean isJUnit3TestMethod(@Nullable PsiMethod method) { return false; } final String methodName = method.getName(); - @NonNls final String test = "test"; + final String test = "test"; if (!methodName.startsWith(test) || !method.hasModifierProperty(PsiModifier.PUBLIC) && method.getParameterList().getParametersCount() > 0) { return false; } @@ -163,11 +161,11 @@ public static boolean isInTestCode(PsiElement element) { /** * @return true if class is annotated with {@code @TestInstance(TestInstance.Lifecycle.PER_CLASS)} */ - public static boolean testInstancePerClass(@Nonnull PsiClass containingClass) { + public static boolean testInstancePerClass(PsiClass containingClass) { return testInstancePerClass(containingClass, new HashSet<>()); } - private static boolean testInstancePerClass(@Nonnull PsiClass containingClass, HashSet classes) { + private static boolean testInstancePerClass(PsiClass containingClass, HashSet classes) { PsiAnnotation annotation = MetaAnnotationUtil.findMetaAnnotations(containingClass, Collections.singletonList(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_TEST_INSTANCE)).findFirst().orElse (null); if (annotation != null) { @@ -231,7 +229,7 @@ private static boolean hasAnnotationWithParameter(PsiModifierList modifierList, final PsiAnnotationParameterList parameterList = testAnnotation.getParameterList(); final PsiNameValuePair[] nameValuePairs = parameterList.getAttributes(); for (PsiNameValuePair nameValuePair : nameValuePairs) { - @NonNls final String parameterName = nameValuePair.getName(); + final String parameterName = nameValuePair.getName(); if (expectedParameterName.equals(parameterName)) { return true; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TypeUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TypeUtils.java index f0c4272179..e8d9becee5 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TypeUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/TypeUtils.java @@ -19,15 +19,12 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PsiUtil; import com.intellij.java.language.psi.util.TypeConversionUtil; -import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiElement; import consulo.language.psi.scope.GlobalSearchScope; import consulo.project.Project; +import org.jspecify.annotations.Nullable; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NonNls; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; @@ -49,27 +46,27 @@ private TypeUtils() { } @Contract("_, null -> false") - public static boolean typeEquals(@NonNls @Nonnull String typeName, @Nullable PsiType targetType) { + public static boolean typeEquals(String typeName, @Nullable PsiType targetType) { return targetType != null && targetType.equalsToText(typeName); } - public static PsiClassType getType(@Nonnull String fqName, @Nonnull PsiElement context) { + public static PsiClassType getType(String fqName, PsiElement context) { final Project project = context.getProject(); final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); final GlobalSearchScope scope = context.getResolveScope(); return factory.createTypeByFQClassName(fqName, scope); } - public static PsiClassType getType(@Nonnull PsiClass aClass) { + public static PsiClassType getType(PsiClass aClass) { return JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); } - public static PsiClassType getObjectType(@Nonnull PsiElement context) { - return getType(JavaClassNames.JAVA_LANG_OBJECT, context); + public static PsiClassType getObjectType(PsiElement context) { + return getType(CommonClassNames.JAVA_LANG_OBJECT, context); } - public static PsiClassType getStringType(@Nonnull PsiElement context) { - return getType(JavaClassNames.JAVA_LANG_STRING, context); + public static PsiClassType getStringType(PsiElement context) { + return getType(CommonClassNames.JAVA_LANG_STRING, context); } /** @@ -83,12 +80,12 @@ public static boolean isNarrowingConversion(@Nullable PsiType sourceType, @Nulla @Contract("null -> false") public static boolean isJavaLangObject(@Nullable PsiType targetType) { - return typeEquals(JavaClassNames.JAVA_LANG_OBJECT, targetType); + return typeEquals(CommonClassNames.JAVA_LANG_OBJECT, targetType); } @Contract("null -> false") public static boolean isJavaLangString(@Nullable PsiType targetType) { - return typeEquals(JavaClassNames.JAVA_LANG_STRING, targetType); + return typeEquals(CommonClassNames.JAVA_LANG_STRING, targetType); } public static boolean isOptional(@Nullable PsiType type) { @@ -101,11 +98,11 @@ public static boolean isOptional(PsiClass aClass) { return false; } final String qualifiedName = aClass.getQualifiedName(); - return JavaClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName) || "java.util.OptionalDouble".equals(qualifiedName) || "java.util.OptionalInt".equals(qualifiedName) || ("java.util" + + return CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName) || "java.util.OptionalDouble".equals(qualifiedName) || "java.util.OptionalInt".equals(qualifiedName) || ("java.util" + ".OptionalLong").equals(qualifiedName) || "com.google.common.base.Optional".equals(qualifiedName); } - public static boolean isExpressionTypeAssignableWith(@Nonnull PsiExpression expression, @Nonnull Iterable rhsTypeTexts) { + public static boolean isExpressionTypeAssignableWith(PsiExpression expression, Iterable rhsTypeTexts) { final PsiType type = expression.getType(); if (type == null) { return false; @@ -120,12 +117,12 @@ public static boolean isExpressionTypeAssignableWith(@Nonnull PsiExpression expr return false; } - public static boolean expressionHasTypeOrSubtype(@Nullable PsiExpression expression, @NonNls @Nonnull String typeName) { + public static boolean expressionHasTypeOrSubtype(@Nullable PsiExpression expression, String typeName) { return expressionHasTypeOrSubtype(expression, new String[]{typeName}) != null; } //getTypeIfOneOfOrSubtype - public static String expressionHasTypeOrSubtype(@Nullable PsiExpression expression, @NonNls @Nonnull String... typeNames) { + public static String expressionHasTypeOrSubtype(@Nullable PsiExpression expression, String... typeNames) { if (expression == null) { return null; } @@ -149,7 +146,7 @@ public static String expressionHasTypeOrSubtype(@Nullable PsiExpression expressi return null; } - public static boolean expressionHasTypeOrSubtype(@Nullable PsiExpression expression, @NonNls @Nonnull Iterable typeNames) { + public static boolean expressionHasTypeOrSubtype(@Nullable PsiExpression expression, Iterable typeNames) { if (expression == null) { return false; } @@ -173,7 +170,7 @@ public static boolean expressionHasTypeOrSubtype(@Nullable PsiExpression express return false; } - public static boolean variableHasTypeOrSubtype(@Nullable PsiVariable variable, @NonNls @Nonnull String... typeNames) { + public static boolean variableHasTypeOrSubtype(@Nullable PsiVariable variable, String... typeNames) { if (variable == null) { return false; } @@ -245,15 +242,19 @@ public static PsiType unaryNumericPromotion(PsiType type) { if (type == null) { return null; } - if (type.equalsToText("java.lang.Byte") || type.equalsToText("java.lang.Short") || - type.equalsToText("java.lang.Character") || type.equalsToText("java.lang.Integer") || - type.equals(PsiType.BYTE) || type.equals(PsiType.SHORT) || type.equals(PsiType.CHAR)) { + if (type.equalsToText(CommonClassNames.JAVA_LANG_BYTE) + || type.equalsToText(CommonClassNames.JAVA_LANG_SHORT) + || type.equalsToText(CommonClassNames.JAVA_LANG_CHARACTER) + || type.equalsToText(CommonClassNames.JAVA_LANG_INTEGER) + || type.equals(PsiType.BYTE) + || type.equals(PsiType.SHORT) + || type.equals(PsiType.CHAR)) { return PsiType.INT; - } else if (type.equalsToText("java.lang.Long")) { + } else if (type.equalsToText(CommonClassNames.JAVA_LANG_LONG)) { return PsiType.LONG; - } else if (type.equalsToText("java.lang.Float")) { + } else if (type.equalsToText(CommonClassNames.JAVA_LANG_FLOAT)) { return PsiType.FLOAT; - } else if (type.equalsToText("java.lang.Double")) { + } else if (type.equalsToText(CommonClassNames.JAVA_LANG_DOUBLE)) { return PsiType.DOUBLE; } return type; @@ -265,7 +266,6 @@ public static PsiType unaryNumericPromotion(PsiType type) { * @param type type to get the default value for * @return the textual representation of default value */ - @NonNls public static String getDefaultValue(PsiType type) { if (PsiType.INT.equals(type)) { return "0"; diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAccessUtils.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAccessUtils.java index 6b49c1d6cb..8d06d167cb 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAccessUtils.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAccessUtils.java @@ -15,6 +15,7 @@ */ package com.siyeh.ig.psiutils; +import com.intellij.java.analysis.codeInspection.ParenthesesUtils; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.PsiUtil; import consulo.language.ast.IElementType; @@ -23,8 +24,7 @@ import consulo.language.psi.util.PsiTreeUtil; import consulo.util.lang.ObjectUtil; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; public class VariableAccessUtils { @@ -32,7 +32,7 @@ public class VariableAccessUtils { private VariableAccessUtils() { } - public static boolean variableIsAssignedFrom(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsAssignedFrom(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -41,7 +41,7 @@ public static boolean variableIsAssignedFrom(@Nonnull PsiVariable variable, @Nul return visitor.isAssignedFrom(); } - public static boolean variableIsPassedAsMethodArgument(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsPassedAsMethodArgument(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -50,11 +50,11 @@ public static boolean variableIsPassedAsMethodArgument(@Nonnull PsiVariable vari return visitor.isPassed(); } - public static boolean variableIsPassedAsMethodArgument(@Nonnull PsiVariable variable, Set excludes, @Nullable PsiElement context) { + public static boolean variableIsPassedAsMethodArgument(PsiVariable variable, Set excludes, @Nullable PsiElement context) { return variableIsPassedAsMethodArgument(variable, excludes, context, false); } - public static boolean variableIsPassedAsMethodArgument(@Nonnull PsiVariable variable, Set excludes, @Nullable PsiElement context, + public static boolean variableIsPassedAsMethodArgument(PsiVariable variable, Set excludes, @Nullable PsiElement context, boolean builderPattern) { if (context == null) { return false; @@ -64,7 +64,7 @@ public static boolean variableIsPassedAsMethodArgument(@Nonnull PsiVariable vari return visitor.isPassed(); } - public static boolean variableIsUsedInArrayInitializer(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsUsedInArrayInitializer(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -73,7 +73,7 @@ public static boolean variableIsUsedInArrayInitializer(@Nonnull PsiVariable vari return visitor.isPassed(); } - public static boolean variableIsAssigned(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsAssigned(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -89,7 +89,7 @@ public static boolean variableIsAssigned(@Nonnull PsiVariable variable, @Nullabl * @param variable the variable to check assignments for * @return true, if the variable is assigned or too expensive to search. False otherwise. */ - public static boolean variableIsAssigned(@Nonnull PsiVariable variable) { + public static boolean variableIsAssigned(PsiVariable variable) { if (variable instanceof PsiField) { if (variable.hasModifierProperty(PsiModifier.PRIVATE)) { final PsiClass aClass = PsiUtil.getTopLevelClass(variable); @@ -106,7 +106,7 @@ public static boolean variableIsAssigned(@Nonnull PsiVariable variable) { return variableIsAssigned(variable, context); } - public static boolean variableIsAssigned(@Nonnull PsiVariable variable, @Nullable PsiElement context, boolean recurseIntoClasses) { + public static boolean variableIsAssigned(PsiVariable variable, @Nullable PsiElement context, boolean recurseIntoClasses) { if (context == null) { return false; } @@ -115,11 +115,11 @@ public static boolean variableIsAssigned(@Nonnull PsiVariable variable, @Nullabl return visitor.isAssigned(); } - public static boolean variableIsReturned(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsReturned(PsiVariable variable, @Nullable PsiElement context) { return variableIsReturned(variable, context, false); } - public static boolean variableIsReturned(@Nonnull PsiVariable variable, @Nullable PsiElement context, boolean builderPattern) { + public static boolean variableIsReturned(PsiVariable variable, @Nullable PsiElement context, boolean builderPattern) { if (context == null) { return false; } @@ -128,7 +128,7 @@ public static boolean variableIsReturned(@Nonnull PsiVariable variable, @Nullabl return visitor.isReturned(); } - public static boolean variableValueIsUsed(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableValueIsUsed(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -137,7 +137,7 @@ public static boolean variableValueIsUsed(@Nonnull PsiVariable variable, @Nullab return visitor.isVariableValueUsed(); } - public static boolean arrayContentsAreAccessed(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean arrayContentsAreAccessed(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -146,7 +146,7 @@ public static boolean arrayContentsAreAccessed(@Nonnull PsiVariable variable, @N return visitor.isAccessed(); } - public static boolean arrayContentsAreAssigned(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean arrayContentsAreAssigned(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -155,7 +155,7 @@ public static boolean arrayContentsAreAssigned(@Nonnull PsiVariable variable, @N return visitor.isAssigned(); } - public static boolean variableIsUsedInInnerClass(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsUsedInInnerClass(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -164,11 +164,11 @@ public static boolean variableIsUsedInInnerClass(@Nonnull PsiVariable variable, return visitor.isUsedInInnerClass(); } - public static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { + public static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, PsiVariable variable) { return mayEvaluateToVariable(expression, variable, false); } - public static List getVariableReferences(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static List getVariableReferences(PsiVariable variable, @Nullable PsiElement context) { if (context == null) return Collections.emptyList(); List result = new ArrayList<>(); @@ -181,7 +181,7 @@ public static List getVariableReferences(@Nonnull PsiVar return result; } - public static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, @Nonnull PsiVariable variable, boolean builderPattern) { + public static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, PsiVariable variable, boolean builderPattern) { if (expression == null) { return false; } @@ -244,7 +244,7 @@ public static boolean mayEvaluateToVariable(@Nullable PsiExpression expression, return evaluatesToVariable(expression, variable); } - public static boolean evaluatesToVariable(@Nullable PsiExpression expression, @Nonnull PsiVariable variable) { + public static boolean evaluatesToVariable(@Nullable PsiExpression expression, PsiVariable variable) { expression = ParenthesesUtils.stripParentheses(expression); if (!(expression instanceof PsiReferenceExpression)) { return false; @@ -254,7 +254,7 @@ public static boolean evaluatesToVariable(@Nullable PsiExpression expression, @N return variable.equals(target); } - public static boolean variableIsUsed(@Nonnull PsiVariable variable, @Nullable PsiElement context) { + public static boolean variableIsUsed(PsiVariable variable, @Nullable PsiElement context) { if (context == null) { return false; } @@ -263,7 +263,7 @@ public static boolean variableIsUsed(@Nonnull PsiVariable variable, @Nullable Ps return visitor.isUsed(); } - public static boolean variableIsDecremented(@Nonnull PsiVariable variable, @Nullable PsiStatement statement) { + public static boolean variableIsDecremented(PsiVariable variable, @Nullable PsiStatement statement) { if (!(statement instanceof PsiExpressionStatement)) { return false; } @@ -324,7 +324,7 @@ public static boolean variableIsDecremented(@Nonnull PsiVariable variable, @Null return false; } - public static boolean variableIsIncremented(@Nonnull PsiVariable variable, @Nullable PsiStatement statement) { + public static boolean variableIsIncremented(PsiVariable variable, @Nullable PsiStatement statement) { if (!(statement instanceof PsiExpressionStatement)) { return false; } @@ -385,7 +385,7 @@ public static boolean variableIsIncremented(@Nonnull PsiVariable variable, @Null return false; } - public static boolean variableIsAssignedBeforeReference(@Nonnull PsiReferenceExpression referenceExpression, @Nullable PsiElement context) { + public static boolean variableIsAssignedBeforeReference(PsiReferenceExpression referenceExpression, @Nullable PsiElement context) { if (context == null) { return false; } @@ -397,7 +397,7 @@ public static boolean variableIsAssignedBeforeReference(@Nonnull PsiReferenceExp return variableIsAssignedAtPoint(variable, context, referenceExpression); } - public static boolean variableIsAssignedAtPoint(@Nonnull PsiVariable variable, @Nullable PsiElement context, @Nonnull PsiElement point) { + public static boolean variableIsAssignedAtPoint(PsiVariable variable, @Nullable PsiElement context, PsiElement point) { if (context == null) { return false; } @@ -418,7 +418,7 @@ public static boolean variableIsAssignedAtPoint(@Nonnull PsiVariable variable, @ } @Nullable - private static PsiElement getDirectChildWhichContainsElement(@Nonnull PsiElement ancestor, @Nonnull PsiElement descendant) { + private static PsiElement getDirectChildWhichContainsElement(PsiElement ancestor, PsiElement descendant) { if (ancestor == descendant) { return null; } @@ -443,7 +443,7 @@ public static Set collectUsedVariables(PsiElement context) { return visitor.getUsedVariables(); } - public static boolean isAnyVariableAssigned(@Nonnull Collection variables, @Nullable PsiElement context) { + public static boolean isAnyVariableAssigned(Collection variables, @Nullable PsiElement context) { if (context == null) { return false; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedFromVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedFromVisitor.java index 6da3efdf97..506a29e1b0 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedFromVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedFromVisitor.java @@ -18,22 +18,20 @@ import com.intellij.java.language.psi.*; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; class VariableAssignedFromVisitor extends JavaRecursiveElementVisitor { private boolean assignedFrom = false; - @Nonnull private final PsiVariable variable; - public VariableAssignedFromVisitor(@Nonnull PsiVariable variable) { + public VariableAssignedFromVisitor(PsiVariable variable) { super(); this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (!assignedFrom) { super.visitElement(element); } @@ -41,7 +39,7 @@ public void visitElement(@Nonnull PsiElement element) { @Override public void visitAssignmentExpression( - @Nonnull PsiAssignmentExpression assignment) { + PsiAssignmentExpression assignment) { if (assignedFrom) { return; } @@ -54,7 +52,7 @@ public void visitAssignmentExpression( @Override public void visitDeclarationStatement( - @Nonnull PsiDeclarationStatement statement) { + PsiDeclarationStatement statement) { if (assignedFrom) { return; } @@ -77,7 +75,7 @@ public void visitDeclarationStatement( } @Override - public void visitVariable(@Nonnull PsiVariable var) { + public void visitVariable(PsiVariable var) { if (assignedFrom) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedVisitor.java index a5abeb208f..5994e23459 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableAssignedVisitor.java @@ -20,33 +20,31 @@ import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; import java.util.Collection; import java.util.Collections; public class VariableAssignedVisitor extends JavaRecursiveElementWalkingVisitor { - @Nonnull private final Collection variables; private final boolean recurseIntoClasses; private final boolean checkUnaryExpressions; private boolean assigned = false; private PsiElement excludedElement = null; - public VariableAssignedVisitor(@Nonnull Collection variables, boolean recurseIntoClasses) { + public VariableAssignedVisitor(Collection variables, boolean recurseIntoClasses) { this.variables = variables; checkUnaryExpressions = true; this.recurseIntoClasses = recurseIntoClasses; } - public VariableAssignedVisitor(@Nonnull PsiVariable variable, boolean recurseIntoClasses) { + public VariableAssignedVisitor(PsiVariable variable, boolean recurseIntoClasses) { variables = Collections.singleton(variable); final PsiType type = variable.getType(); checkUnaryExpressions = TypeConversionUtil.isNumericType(type); this.recurseIntoClasses = recurseIntoClasses; } - public VariableAssignedVisitor(@Nonnull PsiVariable variable) { + public VariableAssignedVisitor(PsiVariable variable) { this(variable, true); } @@ -55,7 +53,7 @@ public void setExcludedElement(PsiElement excludedElement) { } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (assigned || element == excludedElement) { return; } @@ -63,7 +61,7 @@ public void visitElement(@Nonnull PsiElement element) { } @Override - public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression assignment) { + public void visitAssignmentExpression(PsiAssignmentExpression assignment) { if (assigned) { return; } @@ -86,7 +84,7 @@ public void visitClass(PsiClass aClass) { } @Override - public void visitPrefixExpression(@Nonnull PsiPrefixExpression prefixExpression) { + public void visitPrefixExpression(PsiPrefixExpression prefixExpression) { if (assigned) { return; } @@ -108,7 +106,7 @@ public void visitPrefixExpression(@Nonnull PsiPrefixExpression prefixExpression) } @Override - public void visitPostfixExpression(@Nonnull PsiPostfixExpression postfixExpression) { + public void visitPostfixExpression(PsiPostfixExpression postfixExpression) { if (assigned) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentExcludedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentExcludedVisitor.java index a6cec7877d..34729e73e4 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentExcludedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentExcludedVisitor.java @@ -17,7 +17,6 @@ import java.util.Set; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiElement; @@ -30,21 +29,20 @@ class VariablePassedAsArgumentExcludedVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private final Set excludes; private final boolean myBuilderPattern; private boolean passed = false; - public VariablePassedAsArgumentExcludedVisitor(@Nonnull PsiVariable variable, @Nonnull Set excludes, boolean builderPattern) { + public VariablePassedAsArgumentExcludedVisitor(PsiVariable variable, Set excludes, boolean builderPattern) { this.variable = variable; this.excludes = excludes; myBuilderPattern = builderPattern; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (passed) { return; } @@ -52,7 +50,7 @@ public void visitElement(@Nonnull PsiElement element) { } @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression call) { + public void visitMethodCallExpression(PsiMethodCallExpression call) { if (passed) { return; } @@ -78,7 +76,7 @@ public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression call) { } @Override - public void visitNewExpression(@Nonnull PsiNewExpression newExpression) { + public void visitNewExpression(PsiNewExpression newExpression) { if (passed) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentVisitor.java index 9dd557c465..ebf2c20655 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariablePassedAsArgumentVisitor.java @@ -15,7 +15,6 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import consulo.language.psi.PsiElement; @@ -27,24 +26,23 @@ class VariablePassedAsArgumentVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private boolean passed = false; - public VariablePassedAsArgumentVisitor(@Nonnull PsiVariable variable) { + public VariablePassedAsArgumentVisitor(PsiVariable variable) { super(); this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (!passed) { super.visitElement(element); } } @Override - public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression call) { + public void visitMethodCallExpression(PsiMethodCallExpression call) { if (passed) { return; } @@ -60,7 +58,7 @@ public void visitMethodCallExpression(@Nonnull PsiMethodCallExpression call) { } @Override - public void visitNewExpression(@Nonnull PsiNewExpression newExpression) { + public void visitNewExpression(PsiNewExpression newExpression) { if (passed) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableReturnedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableReturnedVisitor.java index b5e0f33d85..3c7db1f83a 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableReturnedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableReturnedVisitor.java @@ -15,7 +15,6 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import consulo.language.psi.PsiElement; @@ -25,13 +24,12 @@ class VariableReturnedVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private final boolean myBuilderPattern; private boolean returned = false; - public VariableReturnedVisitor(@Nonnull PsiVariable variable, boolean builderPattern) { + public VariableReturnedVisitor(PsiVariable variable, boolean builderPattern) { this.variable = variable; myBuilderPattern = builderPattern; } @@ -45,7 +43,7 @@ public void visitElement(PsiElement element) { } @Override - public void visitReturnStatement(@Nonnull PsiReturnStatement returnStatement) { + public void visitReturnStatement(PsiReturnStatement returnStatement) { if (returned) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInArrayInitializerVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInArrayInitializerVisitor.java index 06f96a71c1..de76fa0aac 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInArrayInitializerVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInArrayInitializerVisitor.java @@ -15,7 +15,6 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import com.intellij.java.language.psi.PsiArrayInitializerExpression; @@ -25,17 +24,16 @@ class VariableUsedInArrayInitializerVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private boolean passed = false; - public VariableUsedInArrayInitializerVisitor(@Nonnull PsiVariable variable) { + public VariableUsedInArrayInitializerVisitor(PsiVariable variable) { super(); this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (!passed) { super.visitElement(element); } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInInnerClassVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInInnerClassVisitor.java index 1fd5adc4cb..ecc82839c4 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInInnerClassVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedInInnerClassVisitor.java @@ -15,7 +15,6 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import com.intellij.java.language.psi.PsiClass; import consulo.language.psi.PsiElement; @@ -24,25 +23,24 @@ class VariableUsedInInnerClassVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private boolean usedInInnerClass = false; private boolean inInnerClass = false; - public VariableUsedInInnerClassVisitor(@Nonnull PsiVariable variable) { + public VariableUsedInInnerClassVisitor(PsiVariable variable) { super(); this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (!usedInInnerClass) { super.visitElement(element); } } @Override - public void visitClass(@Nonnull PsiClass psiClass) { + public void visitClass(PsiClass psiClass) { if (usedInInnerClass) { return; } @@ -54,7 +52,7 @@ public void visitClass(@Nonnull PsiClass psiClass) { @Override public void visitReferenceExpression( - @Nonnull PsiReferenceExpression referenceExpression) { + PsiReferenceExpression referenceExpression) { if (usedInInnerClass) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedVisitor.java index 525b11a9d2..4638d1d453 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableUsedVisitor.java @@ -15,7 +15,6 @@ */ package com.siyeh.ig.psiutils; -import javax.annotation.Nonnull; import com.intellij.java.language.psi.JavaRecursiveElementVisitor; import consulo.language.psi.PsiElement; @@ -25,15 +24,14 @@ class VariableUsedVisitor extends JavaRecursiveElementVisitor { private boolean used = false; - @Nonnull private final PsiVariable variable; - public VariableUsedVisitor(@Nonnull PsiVariable variable) { + public VariableUsedVisitor(PsiVariable variable) { this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (used) { return; } @@ -42,7 +40,7 @@ public void visitElement(@Nonnull PsiElement element) { @Override public void visitReferenceExpression( - @Nonnull PsiReferenceExpression referenceExpression) { + PsiReferenceExpression referenceExpression) { if (used) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableValueUsedVisitor.java b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableValueUsedVisitor.java index 84d68a7122..27b670236d 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableValueUsedVisitor.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/psiutils/VariableValueUsedVisitor.java @@ -19,21 +19,19 @@ import consulo.language.ast.IElementType; import consulo.language.psi.PsiElement; -import javax.annotation.Nonnull; class VariableValueUsedVisitor extends JavaRecursiveElementVisitor { - @Nonnull private final PsiVariable variable; private boolean read = false; private boolean written = false; - VariableValueUsedVisitor(@Nonnull PsiVariable variable) { + VariableValueUsedVisitor(PsiVariable variable) { this.variable = variable; } @Override - public void visitElement(@Nonnull PsiElement element) { + public void visitElement(PsiElement element) { if (read || written) { return; } @@ -42,7 +40,7 @@ public void visitElement(@Nonnull PsiElement element) { @Override public void visitAssignmentExpression( - @Nonnull PsiAssignmentExpression assignment) { + PsiAssignmentExpression assignment) { if (read || written) { return; } @@ -69,7 +67,7 @@ public void visitAssignmentExpression( @Override public void visitPrefixExpression( - @Nonnull PsiPrefixExpression prefixExpression) { + PsiPrefixExpression prefixExpression) { if (read || written) { return; } @@ -94,7 +92,7 @@ public void visitPrefixExpression( @Override public void visitPostfixExpression( - @Nonnull PsiPostfixExpression postfixExpression) { + PsiPostfixExpression postfixExpression) { if (read || written) { return; } @@ -118,7 +116,7 @@ public void visitPostfixExpression( } @Override - public void visitVariable(@Nonnull PsiVariable variable) { + public void visitVariable(PsiVariable variable) { if (read || written) { return; } @@ -135,7 +133,7 @@ public void visitVariable(@Nonnull PsiVariable variable) { @Override public void visitMethodCallExpression( - @Nonnull PsiMethodCallExpression call) { + PsiMethodCallExpression call) { if (read || written) { return; } @@ -166,7 +164,7 @@ public void visitMethodCallExpression( @Override public void visitNewExpression( - @Nonnull PsiNewExpression newExpression) { + PsiNewExpression newExpression) { if (read || written) { return; } @@ -209,7 +207,7 @@ public void visitArrayInitializerExpression( @Override public void visitReturnStatement( - @Nonnull PsiReturnStatement returnStatement) { + PsiReturnStatement returnStatement) { if (read || written) { return; } diff --git a/java-analysis-impl/src/main/java/com/siyeh/ig/ui/ExternalizableStringSet.java b/java-analysis-impl/src/main/java/com/siyeh/ig/ui/ExternalizableStringSet.java index 39a0ef467e..f3525f259a 100644 --- a/java-analysis-impl/src/main/java/com/siyeh/ig/ui/ExternalizableStringSet.java +++ b/java-analysis-impl/src/main/java/com/siyeh/ig/ui/ExternalizableStringSet.java @@ -22,7 +22,6 @@ import consulo.util.xml.serializer.JDOMExternalizable; import consulo.util.xml.serializer.WriteExternalException; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; import java.util.List; @@ -46,7 +45,7 @@ public class ExternalizableStringSet extends OrderedSet implements JDOME * note: declare ExternalizableStringSet fields as final!
* note: reference to defaultValues is retained by this set! */ - public ExternalizableStringSet(@NonNls String... defaultValues) { + public ExternalizableStringSet(String... defaultValues) { this.defaultValues = defaultValues.length == 0 ? ArrayUtil.EMPTY_STRING_ARRAY : defaultValues; for (String defaultValue : defaultValues) { add(defaultValue); diff --git a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/JavaQuickFixBundle.java b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/JavaQuickFixBundle.java index 58f595370c..881efca30b 100644 --- a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/JavaQuickFixBundle.java +++ b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/JavaQuickFixBundle.java @@ -16,29 +16,31 @@ package consulo.java.analysis.impl; -import org.jetbrains.annotations.PropertyKey; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.internal.MigratedExtensionsTo; import consulo.component.util.localize.AbstractBundle; +import consulo.java.analysis.impl.localize.JavaQuickFixLocalize; +import org.jetbrains.annotations.PropertyKey; /** * @author VISTALL * @since 27.12.2015 */ -public class JavaQuickFixBundle extends AbstractBundle -{ - private static final JavaQuickFixBundle ourInstance = new JavaQuickFixBundle(); +@Deprecated +@DeprecationInfo("Use JavaQuickFixLocalize") +@MigratedExtensionsTo(JavaQuickFixLocalize.class) +public class JavaQuickFixBundle extends AbstractBundle { + private static final JavaQuickFixBundle ourInstance = new JavaQuickFixBundle(); - private JavaQuickFixBundle() - { - super("messages.JavaQuickFixBundle"); - } + private JavaQuickFixBundle() { + super("messages.JavaQuickFixBundle"); + } - public static String message(@PropertyKey(resourceBundle = "messages.JavaQuickFixBundle") String key) - { - return ourInstance.getMessage(key); - } + public static String message(@PropertyKey(resourceBundle = "messages.JavaQuickFixBundle") String key) { + return ourInstance.getMessage(key); + } - public static String message(@PropertyKey(resourceBundle = "messages.JavaQuickFixBundle") String key, Object... params) - { - return ourInstance.getMessage(key, params); - } + public static String message(@PropertyKey(resourceBundle = "messages.JavaQuickFixBundle") String key, Object... params) { + return ourInstance.getMessage(key, params); + } } diff --git a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/JavaInspectionsBundle.java b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/JavaInspectionsBundle.java index 0f0e2f25d0..8e563728d6 100644 --- a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/JavaInspectionsBundle.java +++ b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/JavaInspectionsBundle.java @@ -16,6 +16,9 @@ package consulo.java.analysis.impl.codeInsight; +import consulo.annotation.DeprecationInfo; +import consulo.annotation.internal.MigratedExtensionsTo; +import consulo.java.analysis.impl.localize.JavaInspectionsLocalize; import org.jetbrains.annotations.PropertyKey; import consulo.component.util.localize.AbstractBundle; @@ -23,6 +26,9 @@ * @author VISTALL * @since 15.07.14 */ +@Deprecated +@DeprecationInfo("Use JavaInspectionsLocalize") +@MigratedExtensionsTo(JavaInspectionsLocalize.class) public class JavaInspectionsBundle extends AbstractBundle { public static final String BUNDLE = "messages.JavaInspectionsBundle"; diff --git a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/completion/JavaCompletionUtilCore.java b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/completion/JavaCompletionUtilCore.java index 5a31f90adf..d3057e1eae 100644 --- a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/completion/JavaCompletionUtilCore.java +++ b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/codeInsight/completion/JavaCompletionUtilCore.java @@ -21,10 +21,9 @@ import com.intellij.java.language.psi.PsiExpression; import com.intellij.java.language.psi.PsiType; import consulo.util.lang.function.PairFunction; -import com.siyeh.ig.psiutils.SideEffectChecker; +import com.intellij.java.analysis.impl.codeInspection.SideEffectChecker; import consulo.util.dataholder.Key; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * @author VISTALL diff --git a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/DelegateIntObjectMap.java b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/DelegateIntObjectMap.java index c0982cd55e..80396fb0df 100644 --- a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/DelegateIntObjectMap.java +++ b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/DelegateIntObjectMap.java @@ -4,8 +4,7 @@ import consulo.util.collection.primitive.ints.IntObjectMap; import consulo.util.collection.primitive.ints.IntSet; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.Set; @@ -28,14 +27,12 @@ public IntObjectMap getDelegate() } @Override - @Nonnull public IntSet keySet() { return myDelegate.keySet(); } @Override - @Nonnull public int[] keys() { return myDelegate.keys(); @@ -74,14 +71,12 @@ public V remove(int i) } @Override - @Nonnull public Set> entrySet() { return myDelegate.entrySet(); } @Override - @Nonnull public Collection values() { return myDelegate.values(); diff --git a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/JavaI18nUtil.java b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/JavaI18nUtil.java index 7989925559..c6e32dffd4 100644 --- a/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/JavaI18nUtil.java +++ b/java-analysis-impl/src/main/java/consulo/java/analysis/impl/util/JavaI18nUtil.java @@ -37,8 +37,7 @@ import consulo.util.dataholder.Key; import consulo.util.lang.Pair; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; import java.util.*; /** @@ -60,9 +59,9 @@ public static TextRange getSelectedRange(Editor editor, final PsiFile psiFile) { return psiElement.getTextRange(); } - public static boolean mustBePropertyKey(@Nonnull Project project, - @Nonnull PsiLiteralExpression expression, - @Nonnull Map annotationAttributeValues) { + public static boolean mustBePropertyKey(Project project, + PsiLiteralExpression expression, + Map annotationAttributeValues) { final PsiElement parent = expression.getParent(); if (parent instanceof PsiVariable) { final PsiAnnotation annotation = AnnotationUtil.findAnnotation((PsiVariable)parent, AnnotationUtil.PROPERTY_KEY); @@ -73,8 +72,8 @@ public static boolean mustBePropertyKey(@Nonnull Project project, return isPassedToAnnotatedParam(project, expression, AnnotationUtil.PROPERTY_KEY, annotationAttributeValues, null); } - public static boolean isPassedToAnnotatedParam(@Nonnull Project project, - @Nonnull PsiExpression expression, + public static boolean isPassedToAnnotatedParam(Project project, + PsiExpression expression, final String annFqn, @Nullable Map annotationAttributeValues, @Nullable final Set nonNlsTargets) { @@ -130,8 +129,7 @@ public CachedValueProvider.Result compute(Pair annotationAttributeValues, - @Nonnull PsiAnnotation annotation) { + PsiAnnotation annotation) { if (annotationAttributeValues != null) { final PsiAnnotationParameterList parameterList = annotation.getParameterList(); final PsiNameValuePair[] attributes = parameterList.getAttributes(); @@ -247,7 +244,7 @@ private static void addAvailableMethodsOfType(final PsiClassType type, final Collection result) { PsiScopesUtil.treeWalkUp(new PsiScopeProcessor() { @Override - public boolean execute(@Nonnull PsiElement element, ResolveState state) { + public boolean execute(PsiElement element, ResolveState state) { if (element instanceof PsiMethod) { PsiMethod method = (PsiMethod)element; PsiType returnType = method.getReturnType(); @@ -260,7 +257,7 @@ public boolean execute(@Nonnull PsiElement element, ResolveState state) { } @Override - public T getHint(@Nonnull Key hintKey) { + public T getHint(Key hintKey) { return null; } diff --git a/java-analysis-impl/src/main/java/module-info.java b/java-analysis-impl/src/main/java/module-info.java index b72428fc58..f78cd41a72 100644 --- a/java-analysis-impl/src/main/java/module-info.java +++ b/java-analysis-impl/src/main/java/module-info.java @@ -3,14 +3,18 @@ * @since 03/12/2022 */ open module consulo.java.analysis.impl { - requires transitive consulo.ide.api; requires transitive consulo.java.analysis.api; requires transitive consulo.java.language.impl; requires transitive consulo.java.indexing.impl; - requires com.intellij.xml; + requires com.intellij.xml.api; requires consulo.language.editor.impl; + requires consulo.language.impl; + requires consulo.ui.ex.awt.api; + requires consulo.find.api; + requires consulo.usage.api; + requires consulo.language.editor.refactoring.api; requires one.util.streamex; @@ -72,6 +76,7 @@ exports com.intellij.java.analysis.impl.refactoring.extractMethod; exports com.intellij.java.analysis.impl.refactoring.util; exports com.intellij.java.analysis.impl.refactoring.util.duplicates; + exports consulo.java.analysis.impl.localize; exports com.siyeh; exports com.siyeh.ig; exports com.siyeh.ig.bugs; @@ -83,6 +88,7 @@ exports com.siyeh.ig.numeric; exports com.siyeh.ig.psiutils; exports com.siyeh.ig.ui; + exports com.siyeh.localize; exports consulo.java.analysis.impl; exports consulo.java.analysis.impl.codeInsight; exports consulo.java.analysis.impl.codeInsight.completion; diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringContext.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringContext.java index 1a9c1af626..68fe4a9232 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringContext.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringContext.java @@ -23,11 +23,9 @@ import consulo.component.persist.State; import consulo.component.persist.Storage; import consulo.component.persist.StoragePathMacros; -import consulo.ide.ServiceManager; +import consulo.application.Application; import jakarta.inject.Singleton; -import javax.annotation.Nonnull; - /** * Application context for this plugin. */ @@ -43,9 +41,8 @@ @ServiceAPI(ComponentScope.APPLICATION) @ServiceImpl public class GenerateToStringContext implements PersistentStateComponent { - @Nonnull public static GenerateToStringContext getInstance() { - return ServiceManager.getService(GenerateToStringContext.class); + return Application.get().getInstance(GenerateToStringContext.class); } private Config config = new Config(); diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringUtils.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringUtils.java index 777fa29e0b..8fc47ff9e7 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringUtils.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/GenerateToStringUtils.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nonnull; import com.intellij.java.analysis.impl.generate.config.Config; import com.intellij.java.analysis.impl.generate.config.FilterPattern; @@ -49,7 +48,6 @@ private GenerateToStringUtils() * @param pattern the filter pattern to filter out unwanted fields * @return fields available for this action after the filter process. */ - @Nonnull public static PsiField[] filterAvailableFields(PsiClass clazz, FilterPattern pattern) { if(log.isDebugEnabled()) @@ -84,8 +82,7 @@ public static PsiField[] filterAvailableFields(PsiClass clazz, FilterPattern pat * @param pattern the filter pattern to filter out unwanted fields * @return methods available for this action after the filter process. */ - @Nonnull - public static PsiMethod[] filterAvailableMethods(PsiClass clazz, @Nonnull FilterPattern pattern) + public static PsiMethod[] filterAvailableMethods(PsiClass clazz, FilterPattern pattern) { if(log.isDebugEnabled()) { diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/AbstractToStringInspection.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/AbstractToStringInspection.java index bf40aea36f..2c89d65c9f 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/AbstractToStringInspection.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/AbstractToStringInspection.java @@ -19,10 +19,10 @@ import consulo.language.Language; import consulo.language.editor.inspection.LocalInspectionTool; import consulo.language.editor.rawHighlight.HighlightDisplayLevel; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Base class for inspection support. @@ -30,9 +30,8 @@ public abstract class AbstractToStringInspection extends LocalInspectionTool { protected static final Logger log = Logger.getInstance(AbstractToStringInspection.class); - @Nonnull - public String getGroupDisplayName() { - return "toString() issues"; + public LocalizeValue getGroupDisplayName() { + return LocalizeValue.localizeTODO("toString() issues"); } @Nullable @@ -41,7 +40,6 @@ public Language getLanguage() { return JavaLanguage.INSTANCE; } - @Nonnull @Override public HighlightDisplayLevel getDefaultLevel() { return HighlightDisplayLevel.WARNING; diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspection.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspection.java index 3502533b08..e5cc4e5421 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspection.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspection.java @@ -18,23 +18,16 @@ import com.intellij.java.language.codeInsight.TestFrameworks; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.InheritanceUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.java.language.module.util.JavaClassNames; -import consulo.language.editor.inspection.ProblemHighlightType; +import consulo.language.editor.inspection.LocalInspectionToolSession; import consulo.language.editor.inspection.ProblemsHolder; -import consulo.language.editor.inspection.ui.CheckBox; import consulo.language.psi.PsiElementVisitor; +import consulo.localize.LocalizeValue; import consulo.util.lang.StringUtil; import org.jetbrains.java.generate.GenerateToStringContext; import org.jetbrains.java.generate.GenerateToStringUtils; -import javax.annotation.Nonnull; -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.Document; -import java.awt.*; - /** * Inspection to check if the current class overrides the toString() method. *

@@ -43,206 +36,109 @@ */ @ExtensionImpl public class ClassHasNoToStringMethodInspection extends AbstractToStringInspection { - /** - * User options for classes to exclude. Must be a regexp pattern - */ - public String excludeClassNames = ""; // must be public for JDOMSerialization - /** - * User options for excluded exception classes - */ - public boolean excludeException = true; // must be public for JDOMSerialization - /** - * User options for excluded deprecated classes - */ - public boolean excludeDeprecated = true; // must be public for JDOMSerialization - /** - * User options for excluded enum classes - */ - public boolean excludeEnum = false; // must be public for JDOMSerialization - /** - * User options for excluded abstract classes - */ - public boolean excludeAbstract = false; // must be public for JDOMSerialization - - public boolean excludeTestCode = false; - - public boolean excludeInnerClasses = false; - - @Override - @Nonnull - public String getDisplayName() { - return "Class does not override 'toString()' method"; - } - - @Override - @Nonnull - public String getShortName() { - return "ClassHasNoToStringMethod"; - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { - return new JavaElementVisitor() { - @Override - public void visitClass(PsiClass clazz) { - if (AbstractToStringInspection.log.isDebugEnabled()) { - AbstractToStringInspection.log.debug("checkClass: clazz=" + clazz); - } - - // must be a class - final PsiIdentifier nameIdentifier = clazz.getNameIdentifier(); - if (nameIdentifier == null || clazz.getName() == null) { - return; - } - - if (excludeException && InheritanceUtil.isInheritor(clazz, JavaClassNames.JAVA_LANG_THROWABLE)) { - return; - } - if (excludeDeprecated && clazz.isDeprecated()) { - return; - } - if (excludeEnum && clazz.isEnum()) { - return; - } - if (excludeAbstract && clazz.hasModifierProperty(PsiModifier.ABSTRACT)) { - return; - } - if (excludeTestCode && TestFrameworks.getInstance().isTestClass(clazz)) { - return; - } - if (excludeInnerClasses && clazz.getContainingClass() != null) { - return; - } - - // if it is an excluded class - then skip - if (StringUtil.isNotEmpty(excludeClassNames)) { - String name = clazz.getName(); - if (name != null && name.matches(excludeClassNames)) { - return; - } - } - - // must have fields - PsiField[] fields = clazz.getFields(); - if (fields.length == 0) { - return; - } - - // get list of fields and getter methods supposed to be dumped in the toString method - fields = GenerateToStringUtils.filterAvailableFields(clazz, GenerateToStringContext.getConfig().getFilterPattern()); - PsiMethod[] methods = null; - if (GenerateToStringContext.getConfig().isEnableMethods()) { - // okay 'getters in code generation' is enabled so check - methods = GenerateToStringUtils.filterAvailableMethods(clazz, GenerateToStringContext.getConfig().getFilterPattern()); - } - - // there should be any fields - if (Math.max(fields.length, methods == null ? 0 : methods.length) == 0) { - return; - } - - // okay some fields/getter methods are supposed to dumped, does a toString method exist - final PsiMethod[] toStringMethods = clazz.findMethodsByName("toString", false); - for (PsiMethod method : toStringMethods) { - final PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() == 0) { - // toString() method found - return; - } - } - final PsiMethod[] superMethods = clazz.findMethodsByName("toString", true); - for (PsiMethod method : superMethods) { - final PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() != 0) { - continue; - } - if (method.hasModifierProperty(PsiModifier.FINAL)) { - // final toString() in super class found - return; - } - } - holder.registerProblem(nameIdentifier, "Class '" + clazz.getName() + "' does not override 'toString()' method", - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, GenerateToStringQuickFix.getInstance()); - } - }; - } - - /** - * Creates the options panel in the settings for user changeable options. - * - * @return the options panel - */ - @Override - public JComponent createOptionsPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - - constraints.gridx = 0; - constraints.gridy = 0; - constraints.weightx = 0.0; - constraints.anchor = GridBagConstraints.WEST; - constraints.fill = GridBagConstraints.NONE; - panel.add(new JLabel("Exclude classes (reg exp):"), constraints); - - final JTextField excludeClassNamesField = new JTextField(excludeClassNames, 40); - excludeClassNamesField.setMinimumSize(new Dimension(140, 20)); - Document document = excludeClassNamesField.getDocument(); - document.addDocumentListener(new DocumentListener() { - @Override - public void changedUpdate(DocumentEvent e) { - textChanged(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - textChanged(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - textChanged(); - } - - private void textChanged() { - excludeClassNames = excludeClassNamesField.getText(); - } - }); - constraints.gridx = 1; - constraints.gridy = 0; - constraints.weightx = 1.0; - constraints.anchor = GridBagConstraints.NORTHWEST; - constraints.fill = GridBagConstraints.NONE; - panel.add(excludeClassNamesField, constraints); - - final CheckBox excludeExceptionCheckBox = new CheckBox("Ignore exception classes", this, "excludeException"); - constraints.gridx = 0; - constraints.gridy = 1; - constraints.gridwidth = 2; - constraints.fill = GridBagConstraints.HORIZONTAL; - panel.add(excludeExceptionCheckBox, constraints); - - final CheckBox excludeDeprecatedCheckBox = new CheckBox("Ignore deprecated classes", this, "excludeDeprecated"); - constraints.gridy = 2; - panel.add(excludeDeprecatedCheckBox, constraints); - - final CheckBox excludeEnumCheckBox = new CheckBox("Ignore enum classes", this, "excludeEnum"); - constraints.gridy = 3; - panel.add(excludeEnumCheckBox, constraints); - - final CheckBox excludeAbstractCheckBox = new CheckBox("Ignore abstract classes", this, "excludeAbstract"); - constraints.gridy = 4; - panel.add(excludeAbstractCheckBox, constraints); - - final CheckBox excludeInTestCodeCheckBox = new CheckBox("Ignore test classes", this, "excludeTestCode"); - constraints.gridy = 5; - panel.add(excludeInTestCodeCheckBox, constraints); - - final CheckBox excludeInnerClasses = new CheckBox("Ignore inner classes", this, "excludeInnerClasses"); - constraints.gridy = 6; - constraints.weighty = 1.0; - panel.add(excludeInnerClasses, constraints); - - return panel; - } + @Override + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Class does not override 'toString()' method"); + } + + @Override + public String getShortName() { + return "ClassHasNoToStringMethod"; + } + + @Override + public PsiElementVisitor buildVisitor( + final ProblemsHolder holder, + boolean isOnTheFly, + LocalInspectionToolSession session, + Object state + ) { + ClassHasNoToStringMethodInspectionState inspectionState = (ClassHasNoToStringMethodInspectionState) state; + return new JavaElementVisitor() { + @Override + @RequiredReadAction + public void visitClass(PsiClass clazz) { + if (AbstractToStringInspection.log.isDebugEnabled()) { + AbstractToStringInspection.log.debug("checkClass: clazz=" + clazz); + } + + // must be a class + PsiIdentifier nameIdentifier = clazz.getNameIdentifier(); + if (nameIdentifier == null || clazz.getName() == null) { + return; + } + + if (inspectionState.excludeException && InheritanceUtil.isInheritor(clazz, CommonClassNames.JAVA_LANG_THROWABLE)) { + return; + } + if (inspectionState.excludeDeprecated && clazz.isDeprecated()) { + return; + } + if (inspectionState.excludeEnum && clazz.isEnum()) { + return; + } + if (inspectionState.excludeAbstract && clazz.isAbstract()) { + return; + } + if (inspectionState.excludeTestCode && TestFrameworks.getInstance().isTestClass(clazz)) { + return; + } + if (inspectionState.excludeInnerClasses && clazz.getContainingClass() != null) { + return; + } + + // if it is an excluded class - then skip + if (StringUtil.isNotEmpty(inspectionState.excludeClassNames)) { + String name = clazz.getName(); + if (name != null && name.matches(inspectionState.excludeClassNames)) { + return; + } + } + + // must have fields + PsiField[] fields = clazz.getFields(); + if (fields.length == 0) { + return; + } + + // get list of fields and getter methods supposed to be dumped in the toString method + fields = GenerateToStringUtils.filterAvailableFields(clazz, GenerateToStringContext.getConfig().getFilterPattern()); + PsiMethod[] methods = null; + if (GenerateToStringContext.getConfig().isEnableMethods()) { + // okay 'getters in code generation' is enabled so check + methods = GenerateToStringUtils.filterAvailableMethods(clazz, GenerateToStringContext.getConfig().getFilterPattern()); + } + + // there should be any fields + if (Math.max(fields.length, methods == null ? 0 : methods.length) == 0) { + return; + } + + // okay some fields/getter methods are supposed to dumped, does a toString method exist + PsiMethod[] toStringMethods = clazz.findMethodsByName("toString", false); + for (PsiMethod method : toStringMethods) { + PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() == 0) { + // toString() method found + return; + } + } + PsiMethod[] superMethods = clazz.findMethodsByName("toString", true); + for (PsiMethod method : superMethods) { + PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() != 0) { + continue; + } + if (method.isFinal()) { + // final toString() in super class found + return; + } + } + holder.newProblem(LocalizeValue.of("Class '" + clazz.getName() + "' does not override 'toString()' method")) + .range(nameIdentifier) + .withFixes(GenerateToStringQuickFix.getInstance()) + .create(); + } + }; + } } diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspectionState.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspectionState.java new file mode 100644 index 0000000000..abf9840c1f --- /dev/null +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/ClassHasNoToStringMethodInspectionState.java @@ -0,0 +1,68 @@ +package org.jetbrains.java.generate.inspection; + +import consulo.configurable.ConfigurableBuilder; +import consulo.configurable.ConfigurableBuilderState; +import consulo.configurable.UnnamedConfigurable; +import consulo.language.editor.inspection.InspectionToolState; +import consulo.localize.LocalizeValue; +import consulo.ui.Label; +import consulo.util.xml.serializer.XmlSerializerUtil; + +import org.jspecify.annotations.Nullable; + +/** + * @author VISTALL + * @since 20/03/2023 + */ +public class ClassHasNoToStringMethodInspectionState implements InspectionToolState { + /** + * User options for classes to exclude. Must be a regexp pattern + */ + public String excludeClassNames = ""; + /** + * User options for excluded exception classes + */ + public boolean excludeException = true; + /** + * User options for excluded deprecated classes + */ + public boolean excludeDeprecated = true; + /** + * User options for excluded enum classes + */ + public boolean excludeEnum = false; + /** + * User options for excluded abstract classes + */ + public boolean excludeAbstract = false; + + public boolean excludeTestCode = false; + + public boolean excludeInnerClasses = false; + + @Nullable + @Override + public UnnamedConfigurable createConfigurable() { + ConfigurableBuilder state = ConfigurableBuilder.newBuilder(); + state.component(() -> Label.create(LocalizeValue.localizeTODO("Exclude classes (reg exp):"))); + state.textBox(() -> excludeClassNames, s -> excludeClassNames = s); + state.checkBox(LocalizeValue.localizeTODO("Ignore exception classes"), () -> excludeException, b -> excludeException = b); + state.checkBox(LocalizeValue.localizeTODO("Ignore deprecated classes"), () -> excludeDeprecated, b -> excludeDeprecated = b); + state.checkBox(LocalizeValue.localizeTODO("Ignore enum classes"), () -> excludeEnum, b -> excludeEnum = b); + state.checkBox(LocalizeValue.localizeTODO("Ignore abstract classes"), () -> excludeAbstract, b -> excludeAbstract = b); + state.checkBox(LocalizeValue.localizeTODO("Ignore test classes"), () -> excludeTestCode, b -> excludeTestCode = b); + state.checkBox(LocalizeValue.localizeTODO("Ignore inner classes"), () -> excludeInnerClasses, b -> excludeInnerClasses = b); + return state.buildUnnamed(); + } + + @Nullable + @Override + public ClassHasNoToStringMethodInspectionState getState() { + return this; + } + + @Override + public void loadState(ClassHasNoToStringMethodInspectionState state) { + XmlSerializerUtil.copyBean(state, this); + } +} diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/FieldNotUsedInToStringInspection.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/FieldNotUsedInToStringInspection.java index d3c56c91b6..3010029d88 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/FieldNotUsedInToStringInspection.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/FieldNotUsedInToStringInspection.java @@ -22,11 +22,10 @@ import consulo.language.editor.inspection.ProblemsHolder; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; -import org.jetbrains.annotations.NonNls; +import consulo.localize.LocalizeValue; import org.jetbrains.java.generate.GenerateToStringContext; import org.jetbrains.java.generate.GenerateToStringUtils; -import javax.annotation.Nonnull; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -40,12 +39,10 @@ @ExtensionImpl public class FieldNotUsedInToStringInspection extends AbstractToStringInspection { - @Nonnull - public String getDisplayName() { - return "Field not used in 'toString()' method"; + public LocalizeValue getDisplayName() { + return LocalizeValue.localizeTODO("Field not used in 'toString()' method"); } - @Nonnull public String getShortName() { return "FieldNotUsedInToString"; } @@ -55,9 +52,8 @@ public boolean runForWholeFile() { return true; } - @Nonnull @Override - public PsiElementVisitor buildVisitor(@Nonnull final ProblemsHolder holder, boolean isOnTheFly) { + public PsiElementVisitor buildVisitor(final ProblemsHolder holder, boolean isOnTheFly) { return new FieldNotUsedInToStringVisitor(holder); } @@ -77,7 +73,7 @@ public void visitField(PsiField field) { @Override public void visitMethod(PsiMethod method) { super.visitMethod(method); - @NonNls final String methodName = method.getName(); + final String methodName = method.getName(); if (!"toString".equals(methodName)) { return; } @@ -151,17 +147,17 @@ public void visitReferenceExpression(PsiReferenceExpression expression) { } private boolean usesReflection(PsiMethod method) { - @NonNls final String name = method.getName(); + final String name = method.getName(); final PsiClass containingClass = method.getContainingClass(); if (containingClass == null) { return false; } - @NonNls final String qualifiedName = containingClass.getQualifiedName(); + final String qualifiedName = containingClass.getQualifiedName(); if ("getDeclaredFields".equals(name)) { - return "java.lang.Class".equals(qualifiedName); + return CommonClassNames.JAVA_LANG_CLASS.equals(qualifiedName); } else if ("toString".equals(name)) { - return "org.apache.commons.lang.builder.ReflectionToStringBuilder".equals(qualifiedName) || - "java.util.Objects".equals(qualifiedName); + return "org.apache.commons.lang.builder.ReflectionToStringBuilder".equals(qualifiedName) + || CommonClassNames.JAVA_UTIL_OBJECTS.equals(qualifiedName); } return false; } diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/GenerateToStringQuickFix.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/GenerateToStringQuickFix.java index c47f7b4a14..c063e29f0a 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/GenerateToStringQuickFix.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/inspection/GenerateToStringQuickFix.java @@ -16,14 +16,14 @@ package org.jetbrains.java.generate.inspection; import com.intellij.java.language.psi.PsiClass; -import consulo.ide.ServiceManager; +import consulo.application.Application; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.project.Project; import org.jetbrains.java.generate.GenerateToStringActionHandler; -import javax.annotation.Nonnull; /** * Quick fix to run Generate toString() to fix any code inspection problems. @@ -40,24 +40,17 @@ public static GenerateToStringQuickFix getInstance() { } @Override - @Nonnull - public String getName() { - return "Generate toString()"; + public LocalizeValue getName() { + return LocalizeValue.localizeTODO("Generate toString()"); } @Override - @Nonnull - public String getFamilyName() { - return "Generate"; - } - - @Override - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor desc) { + public void applyFix(Project project, ProblemDescriptor desc) { final PsiClass clazz = PsiTreeUtil.getParentOfType(desc.getPsiElement(), PsiClass.class); if (clazz == null) { return; // no class to fix } - GenerateToStringActionHandler handler = ServiceManager.getService(GenerateToStringActionHandler.class); + GenerateToStringActionHandler handler = Application.get().getInstance(GenerateToStringActionHandler.class); handler.executeActionQuickFix(project, clazz); } } diff --git a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/psi/PsiAdapter.java b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/psi/PsiAdapter.java index b55c47bba1..7fbd903304 100644 --- a/java-analysis-impl/src/main/java/org/jetbrains/java/generate/psi/PsiAdapter.java +++ b/java-analysis-impl/src/main/java/org/jetbrains/java/generate/psi/PsiAdapter.java @@ -23,6 +23,7 @@ import com.intellij.java.language.psi.util.InheritanceUtil; import com.intellij.java.language.psi.util.PropertyUtil; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.language.codeStyle.CodeStyleManager; import consulo.language.psi.PsiComment; import consulo.language.psi.PsiElement; @@ -31,13 +32,10 @@ import consulo.project.Project; import consulo.util.collection.ArrayUtil; import consulo.util.lang.StringUtil; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.*; -import static consulo.java.language.module.util.JavaClassNames.*; - /** * Basic PSI Adapter with common function that works in all supported versions of IDEA. */ @@ -119,11 +117,7 @@ public static boolean isObjectArrayType(PsiType type) { * @return true if it's a String array type. */ public static boolean isStringArrayType(PsiType type) { - if (isPrimitiveType(type)) { - return false; - } - - return type.getCanonicalText().indexOf("String[]") > 0; + return !isPrimitiveType(type) && type.getCanonicalText().indexOf("String[]") > 0; } /** @@ -134,7 +128,7 @@ public static boolean isStringArrayType(PsiType type) { * @return true if it's a Collection type. */ public static boolean isCollectionType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, "java.util.Collection"); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_COLLECTION); } /** @@ -145,7 +139,7 @@ public static boolean isCollectionType(PsiElementFactory factory, PsiType type) * @return true if it's a Map type. */ public static boolean isMapType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, JAVA_UTIL_MAP); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_MAP); } /** @@ -156,7 +150,7 @@ public static boolean isMapType(PsiElementFactory factory, PsiType type) { * @return true if it's a Map type. */ public static boolean isSetType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, JAVA_UTIL_SET); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_SET); } /** @@ -167,7 +161,7 @@ public static boolean isSetType(PsiElementFactory factory, PsiType type) { * @return true if it's a Map type. */ public static boolean isListType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, JAVA_UTIL_LIST); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_LIST); } /** @@ -178,7 +172,7 @@ public static boolean isListType(PsiElementFactory factory, PsiType type) { * @return true if it's a String type. */ public static boolean isStringType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, JAVA_LANG_STRING); + return isTypeOf(factory, type, CommonClassNames.JAVA_LANG_STRING); } /** @@ -189,7 +183,7 @@ public static boolean isStringType(PsiElementFactory factory, PsiType type) { * @return true if it's an Object type. */ public static boolean isObjectType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, JAVA_LANG_OBJECT); + return isTypeOf(factory, type, CommonClassNames.JAVA_LANG_OBJECT); } /** @@ -200,7 +194,7 @@ public static boolean isObjectType(PsiElementFactory factory, PsiType type) { * @return true if it's a Date type. */ public static boolean isDateType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, "java.util.Date"); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_DATE); } /** @@ -211,7 +205,7 @@ public static boolean isDateType(PsiElementFactory factory, PsiType type) { * @return true if it's a Calendar type. */ public static boolean isCalendarType(PsiElementFactory factory, PsiType type) { - return isTypeOf(factory, type, "java.util.Calendar"); + return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_CALENDAR); } /** @@ -228,7 +222,7 @@ public static boolean isBooleanType(PsiElementFactory factory, PsiType type) { return "boolean".equals(s); } else { // test for Object type of Boolean - return isTypeOf(factory, type, JAVA_LANG_BOOLEAN); + return isTypeOf(factory, type, CommonClassNames.JAVA_LANG_BOOLEAN); } } @@ -246,7 +240,7 @@ public static boolean isNumericType(PsiElementFactory factory, PsiType type) { return "byte".equals(s) || "double".equals(s) || "float".equals(s) || "int".equals(s) || "long".equals(s) || "short".equals(s); } else { // test for Object type of numeric - return isTypeOf(factory, type, "java.lang.Number"); + return isTypeOf(factory, type, CommonClassNames.JAVA_LANG_NUMBER); } } @@ -257,6 +251,7 @@ public static boolean isNumericType(PsiElementFactory factory, PsiType type) { * @param importStatement import statement to test existing for. * @return true if the javafile has the import statement. */ + @RequiredReadAction public static boolean hasImportStatement(PsiJavaFile javaFile, String importStatement) { PsiImportList importList = javaFile.getImportList(); if (importList == null) { @@ -275,8 +270,9 @@ public static boolean hasImportStatement(PsiJavaFile javaFile, String importStat * * @param javaFile javafile. * @param importStatementOnDemand name of import statement, must be with a wildcard (etc. java.util.*). - * @throws consulo.language.util.IncorrectOperationException is thrown if there is an error creating the import statement. + * @throws IncorrectOperationException is thrown if there is an error creating the import statement. */ + @RequiredReadAction public static void addImportStatement(PsiJavaFile javaFile, String importStatementOnDemand) { PsiElementFactory factory = JavaPsiFacade.getInstance(javaFile.getProject()).getElementFactory(); PsiImportStatement is = factory.createImportStatementOnDemand(fixImportStatement(importStatementOnDemand)); @@ -372,12 +368,12 @@ public static PsiMethod findPublicStaticVoidMainMethod(PsiClass clazz) { // is it public static void main(String[] args) for (PsiMethod method : methods) { // must be public - if (!method.hasModifierProperty(PsiModifier.PUBLIC)) { + if (!method.isPublic()) { continue; } // must be static - if (!method.hasModifierProperty(PsiModifier.STATIC)) { + if (!method.isStatic()) { continue; } @@ -416,8 +412,9 @@ public static PsiMethod findPublicStaticVoidMainMethod(PsiClass clazz) { * @throws IncorrectOperationException is thrown if error adding/replacing the javadoc comment. */ @Nullable + @RequiredReadAction public static PsiComment addOrReplaceJavadoc(PsiMethod method, String javadoc, boolean replace) { - final Project project = method.getProject(); + Project project = method.getProject(); PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); PsiComment comment = factory.createCommentFromText(javadoc, null); @@ -427,7 +424,7 @@ public static PsiComment addOrReplaceJavadoc(PsiMethod method, String javadoc, b if (replace) { // javadoc already exists, so replace doc.replace(comment); - final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); codeStyleManager.reformat(method); // to reformat javadoc return comment; } else { @@ -437,7 +434,7 @@ public static PsiComment addOrReplaceJavadoc(PsiMethod method, String javadoc, b } else { // add new javadoc method.addBefore(comment, method.getFirstChild()); - final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); codeStyleManager.reformat(method); // to reformat javadoc return comment; } @@ -467,11 +464,8 @@ public static boolean isGetterMethod(PsiMethod method) { if (isTypeOfVoid(method.getReturnType())) { return false; } - final PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() != 0) { - return false; - } - return true; + PsiParameterList parameterList = method.getParameterList(); + return parameterList.getParametersCount() == 0; } /** @@ -509,8 +503,8 @@ public static boolean isEnumField(PsiField field) { if (!(type instanceof PsiClassType)) { return false; } - final PsiClassType classType = (PsiClassType) type; - final PsiClass aClass = classType.resolve(); + PsiClassType classType = (PsiClassType) type; + PsiClass aClass = classType.resolve(); return (aClass != null) && aClass.isEnum(); } @@ -521,7 +515,7 @@ public static boolean isEnumField(PsiField field) { * @return true if class is an exception. */ public static boolean isExceptionClass(PsiClass clazz) { - return InheritanceUtil.isInheritor(clazz, JAVA_LANG_THROWABLE); + return InheritanceUtil.isInheritor(clazz, CommonClassNames.JAVA_LANG_THROWABLE); } /** @@ -537,12 +531,12 @@ public static PsiMethod findEqualsMethod(PsiClass clazz) { // is it public boolean equals(Object o) for (PsiMethod method : methods) { // must be public - if (!method.hasModifierProperty(PsiModifier.PUBLIC)) { + if (!method.isPublic()) { continue; } // must not be static - if (method.hasModifierProperty(PsiModifier.STATIC)) { + if (method.isStatic()) { continue; } @@ -559,7 +553,7 @@ public static PsiMethod findEqualsMethod(PsiClass clazz) { } // parameter must be Object - if (!(parameters[0].getType().getCanonicalText().equals(JAVA_LANG_OBJECT))) { + if (!(parameters[0].getType().getCanonicalText().equals(CommonClassNames.JAVA_LANG_OBJECT))) { continue; } @@ -584,12 +578,12 @@ public static PsiMethod findHashCodeMethod(PsiClass clazz) { // is it public int hashCode() for (PsiMethod method : methods) { // must be public - if (!method.hasModifierProperty(PsiModifier.PUBLIC)) { + if (!method.isPublic()) { continue; } // must not be static - if (method.hasModifierProperty(PsiModifier.STATIC)) { + if (method.isStatic()) { continue; } @@ -645,6 +639,7 @@ protected static boolean isTypeOf(PsiElementFactory factory, PsiType type, Strin * @param clazz the class * @return the names. */ + @RequiredReadAction public static String[] getImplementsClassnames(PsiClass clazz) { PsiClass[] interfaces = clazz.getInterfaces(); @@ -671,7 +666,8 @@ public static boolean isPrimitiveType(PsiType type) { return type instanceof PsiPrimitiveType; } - public static int getJavaVersion(@Nonnull PsiElement element) { + @RequiredReadAction + public static int getJavaVersion(PsiElement element) { JavaSdkVersion sdkVersion = JavaVersionService.getInstance().getJavaSdkVersion(element); if (sdkVersion == null) { sdkVersion = JavaSdkVersion.fromLanguageLevel(PsiUtil.getLanguageLevel(element)); @@ -715,7 +711,7 @@ public static boolean isNestedArray(PsiType aType) { if (!(aType instanceof PsiArrayType)) { return false; } - final PsiType componentType = ((PsiArrayType) aType).getComponentType(); + PsiType componentType = ((PsiArrayType) aType).getComponentType(); return componentType instanceof PsiArrayType; } } diff --git a/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.InspectionGadgetsLocalize.yaml b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.InspectionGadgetsLocalize.yaml new file mode 100644 index 0000000000..319cfefda3 --- /dev/null +++ b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.InspectionGadgetsLocalize.yaml @@ -0,0 +1,4748 @@ +0.will.have.incompatible.access.privileges.with.super.1: + text: '{0} will have incompatible access privileges with super {1}' +0.will.no.longer.be.overridable.by.1: + text: '{0} will no longer be overridable by {1}' +0.will.no.longer.be.visible.from.overriding.1: + text: '{0} will no longer be visible from overriding {1}' +absolute.alignment.in.user.interface.display.name: + text: Absolute alignment in AWT/Swing code +absolute.alignment.in.user.interface.problem.descriptor: + text: 'Absolute alignment constant {0}.#ref used #loc' +absolute.alignment.in.user.interface.quickfix: + text: Replace with ''{0}.{1}'' +abstract.class.extends.concrete.class.display.name: + text: Abstract class extends concrete class +abstract.class.extends.concrete.class.problem.descriptor: + text: 'Class #ref is declared ''abstract'', and extends a concrete class #loc' +abstract.class.naming.convention.display.name: + text: Abstract class naming convention +abstract.class.naming.convention.element.description: + text: Abstract class +abstract.class.never.implemented.display.name: + text: Abstract class which has no concrete subclass +abstract.class.never.implemented.problem.descriptor: + text: 'Abstract class #ref has no concrete subclass #loc' +abstract.class.with.only.one.direct.inheritor.display.name: + text: Abstract class with a single direct inheritor +abstract.class.with.only.one.direct.inheritor.problem.descriptor: + text: 'Abstract class #ref has only one direct inheritor #loc' +abstract.class.without.abstract.methods.display.name: + text: Abstract class without 'abstract' methods +abstract.class.without.abstract.methods.problem.descriptor: + text: 'Class #ref is declared ''abstract'', and has no ''abstract'' methods #loc' +abstract.method.call.in.constructor.display.name: + text: Abstract method called during object construction +abstract.method.call.in.constructor.problem.descriptor: + text: 'Call to ''abstract'' method #ref() during object construction #loc' +abstract.method.overrides.abstract.method.display.name: + text: Abstract method overrides abstract method +abstract.method.overrides.abstract.method.ignore.different.annotations.option: + text: Ignore methods with different annotations than their super methods +abstract.method.overrides.abstract.method.ignore.different.javadoc.option: + text: Ignore methods with different Javadoc than their super methods +abstract.method.overrides.abstract.method.problem.descriptor: + text: 'Abstract method #ref() overrides abstract method #loc' +abstract.method.overrides.abstract.method.remove.quickfix: + text: Remove redundant abstract method declaration +abstract.method.overrides.concrete.method.display.name: + text: Abstract method overrides concrete method +abstract.method.overrides.concrete.method.problem.descriptor: + text: 'Abstract method #ref() overrides concrete method #loc' +abstract.method.with.missing.implementations.display.name: + text: Abstract method with missing implementations +abstract.method.with.missing.implementations.problem.descriptor: + text: 'Abstract method #ref() is not implemented in every subclass #loc' +access.to.non.thread.safe.static.field.from.instance.class.chooser.title: + text: Choose Non-Thread-Safe Class +access.to.non.thread.safe.static.field.from.instance.display.name: + text: Non-thread-safe 'static' field access +access.to.non.thread.safe.static.field.from.instance.field.problem.descriptor: + text: 'Access to non-thread-safe static field #ref of type ''''{0}'''' #loc' +access.to.non.thread.safe.static.field.from.instance.option.title: + text: Non-thread-safe classes +access.to.static.field.locked.on.instance.display.name: + text: Access to 'static' field locked on instance data +access.to.static.field.locked.on.instance.problem.descriptor: + text: 'Access to static field #ref locked on instance data #loc' +accessing.non.public.field.of.another.object.display.name: + text: Accessing a non-public field of another object +accessing.non.public.field.of.another.object.problem.descriptor: + text: 'Direct access to non-public field #ref of another object #loc' +add.0.to.ignore.if.annotated.by.list.quickfix: + text: Add ''{0}'' to ''Ignore if annotated by'' list +add.serialversionuidfield.quickfix: + text: Add 'serialVersionUID' field +add.this.qualifier.quickfix: + text: Add 'this' qualifier +all.levels.option: + text: all log levels +allow.resource.to.be.opened.inside.a.try.block: + text: Allow resource to be opened inside a 'try' block +ambiguous.field.access.display.name: + text: Access to inherited field looks like access to element from surrounding code +ambiguous.field.access.hides.field.problem.descriptor: + text: 'Access of field #ref from superclass ''''{0}'''' looks like access of field from surrounding class #loc' +ambiguous.field.access.hides.local.variable.problem.descriptor: + text: 'Access of field #ref from superclass ''''{0}'''' looks like access of local variable #loc' +ambiguous.field.access.hides.parameter.problem.descriptor: + text: 'Access of field #ref from superclass ''''{0}'''' looks like access of parameter #loc' +ambiguous.field.access.quickfix: + text: Add 'super' qualifier to field access +ambiguous.method.call.display.name: + text: Call to inherited method looks like call to local method +ambiguous.method.call.problem.descriptor: + text: 'Call to method #ref() from superclass ''''{0}'''' looks like call to method from class ''''{1}'''' #loc' +ambiguous.method.call.quickfix: + text: Add 'super' qualifier to method call +annotation.class.display.name: + text: Annotation interface +annotation.class.problem.descriptor: + text: 'Annotation interface #ref #loc' +annotation.display.name: + text: Annotation +annotation.naming.convention.display.name: + text: Annotation naming convention +annotation.naming.convention.element.description: + text: Annotation interface +annotation.naming.convention.problem.descriptor.long: + text: 'Annotation name #ref is too long #loc' +annotation.naming.convention.problem.descriptor.regex.mismatch: + text: 'Annotation name #ref doesn''''t match regex ''''{0}'''' #loc' +annotation.naming.convention.problem.descriptor.short: + text: 'Annotation name #ref is too short #loc' +annotation.problem.descriptor: + text: 'Annotation #ref #loc' +anonymous.class.field.hides.containing.method.variable.problem.descriptor: + text: 'Anonymous class field #ref hides variable in containing method #loc' +anonymous.class.parameter.hides.containing.method.variable.problem.descriptor: + text: 'Anonymous class parameter #ref hides variable in containing method #loc' +anonymous.class.variable.hides.containing.method.variable.display.name: + text: Anonymous class variable hides variable in containing method +anonymous.class.variable.hides.containing.method.variable.problem.descriptor: + text: 'Anonymous class local variable #ref hides variable in containing method #loc' +anonymous.extends.concrete.collection.problem.descriptor: + text: 'Anonymous class explicitly extends ''''{0}'''' #loc' +anonymous.extends.thread.problem.descriptor: + text: 'Anonymous class directly extends ''java.lang.Thread'' #loc' +anonymous.extends.throwable.problem.descriptor: + text: 'Anonymous class directly extends ''java.lang.Throwable'' #loc' +anonymous.inner.class.display.name: + text: Anonymous class can be replaced with inner class +anonymous.inner.class.problem.descriptor: + text: 'Anonymous class #ref #loc' +anonymous.inner.class.with.too.many.methods.display.name: + text: Anonymous class with too many methods +anonymous.inner.class.with.too.many.methods.problem.descriptor: + text: 'Anonymous class with too many methods (method count = {0}) #loc' +anonymous.inner.may.be.named.static.inner.class.display.name: + text: Anonymous class may be a named 'static' inner class +anonymous.inner.may.be.named.static.inner.class.problem.descriptor: + text: 'Anonymous class #ref may be a named ''static'' inner class #loc' +anonymous.inner.may.be.named.static.inner.class.quickfix: + text: Convert to named 'static' inner class +any.method.may.close.resource.argument: + text: Any method may close resource argument +archaic.system.property.accessors.display.name: + text: Use of archaic system property accessors +archaic.system.property.accessors.problem.descriptor.Boolean: + text: 'Call to Boolean.#ref() accesses system properties, perhaps confusingly #loc' +archaic.system.property.accessors.problem.descriptor.Integer: + text: 'Call to Integer.#ref() accesses system properties, perhaps confusingly #loc' +archaic.system.property.accessors.problem.descriptor.Long: + text: 'Call to Long.#ref() accesses system properties, perhaps confusingly #loc' +archaic.system.property.accessors.replace.parse.quickfix: + text: Replace with parse method +archaic.system.property.accessors.replace.standard.quickfix: + text: Replace with standard property access +array.allocation.zero.length.display.name: + text: Zero-length array allocation +array.allocation.zero.length.problem.descriptor: + text: 'Allocation of zero length array #loc' +array.comparison.display.name: + text: Array comparison using '==', instead of 'Arrays.equals()' +array.comparison.problem.descriptor: + text: 'Array objects are compared using #ref, not ''Arrays.equals()'' #loc' +array.creation.without.new.keyword.family.quickfix: + text: Add 'new' expression +array.creation.without.new.keyword.name: + text: Array creation without 'new' expression +array.creation.without.new.keyword.quickfix: + text: Add ''new {0}'' +array.hash.code.display.name: + text: '''hashCode()'' called on array' +array.hash.code.problem.descriptor: + text: '#ref() called on array should probably be ''Arrays.hashCode()'' #loc' +array.length.in.loop.condition.display.name: + text: Array.length in loop condition +array.length.in.loop.condition.problem.descriptor: + text: 'Check of array #ref in loop condition #loc' +array.objects.deep.equals.problem.descriptor: + text: 'Objects.#ref() on arrays should probably be ''Arrays.deepEquals()'' #loc' +array.objects.equals.display.name: + text: '''Objects.equals()'' called on arrays' +array.objects.equals.problem.descriptor: + text: 'Objects.#ref() on arrays should probably be ''Arrays.equals()'' #loc' +arrays.as.list.with.one.argument.problem.descriptor: + text: 'Call to #ref() with only one argument #loc' +arrays.as.list.with.one.argument.quickfix: + text: Replace with call to 'Collections.singletonList()' +arrays.as.list.with.zero.arguments.problem.descriptor: + text: 'Call to #ref() to create an empty List #loc' +arrays.as.list.with.zero.arguments.quickfix: + text: Replace with call to 'Collections.emptyList()' +arrays.as.list.with.zero.or.one.argument.display.name: + text: Call to 'Arrays.asList()' with too few arguments +arrays.deep.hash.code.quickfix: + text: Replace with 'Arrays.deepHashCode()' +arrays.hash.code.quickfix: + text: Replace with 'Arrays.hashCode()' +assert.can.be.if.quickfix: + text: Replace 'assert' with 'if' statement +assert.keyword.is.considered.an.assertion: + text: '''assert'' keyword is considered an assertion' +assert.message.not.string.display.name: + text: '''assert'' message is not a string' +assert.message.not.string.only.warn.boolean.option: + text: Only warn when 'assert' message is 'boolean' or 'java.lang.Boolean' +assert.message.of.type.boolean.problem.descriptor: + text: '''''assert'''' message of type ''''{0}'''' #loc' +assert.statement.display.name: + text: '''assert'' statement' +assert.with.side.effects.display.name: + text: '''assert'' statement with side effects' +assert.with.side.effects.problem.descriptor: + text: '#ref has side effects #loc' +assertequals.between.inconvertible.types.display.name: + text: '''assertEquals()'' between objects of inconvertible types' +assertequals.between.inconvertible.types.problem.descriptor: + text: '#ref() between objects of inconvertible types ''''{0}'''' and ''''{1}'''' #loc' +assertequals.called.on.arrays.display.name: + text: '''assertEquals()'' called on array' +assertequals.called.on.arrays.problem.descriptor: + text: '#ref() called on array #loc' +assertequals.called.on.arrays.quickfix: + text: Replace with 'assertArrayEquals()' +assertequals.may.be.assertsame.display.name: + text: '''assertEquals()'' may be ''assertSame()''' +assertequals.may.be.assertsame.problem.descriptor: + text: '#ref() may be ''assertSame()'' #loc' +assertequals.may.be.assertsame.quickfix: + text: Replace with 'assertSame()' +assertion.can.be.if.name: + text: Assertion can be replaced with 'if' statement +asserts.without.messages.display.name: + text: Message missing on assertion +asserts.without.messages.problem.descriptor: + text: 'JUnit #ref() without message #loc' +assignment.collection.array.field.from.parameter.display.name: + text: Assignment to Collection or array field from parameter +assignment.collection.array.field.from.parameter.problem.descriptor.array: + text: 'Assignment to array field #ref from parameter ''''{0}'''' #loc' +assignment.collection.array.field.from.parameter.problem.descriptor.collection: + text: 'Assignment to Collection field #ref from parameter ''''{0}'''' #loc' +assignment.collection.array.field.option: + text: Ignore assignments in 'private' methods +assignment.replaceable.with.operator.assignment.display.name: + text: Assignment can be replaced with operator assignment +assignment.replaceable.with.operator.assignment.ignore.conditional.operators.option: + text: Ignore conditional operators +assignment.replaceable.with.operator.assignment.ignore.obscure.operators.option: + text: Ignore the obscure ^ and % operators +assignment.replaceable.with.operator.assignment.problem.descriptor: + text: '#ref can be simplified to ''''{0}'''' #loc' +assignment.replaceable.with.operator.replace.quickfix: + text: Replace ''='' with ''{0}='' +assignment.to.catch.block.parameter.display.name: + text: Assignment to 'catch' block parameter +assignment.to.catch.block.parameter.problem.descriptor: + text: 'Assignment to ''catch'' block parameter #ref #loc' +assignment.to.date.calendar.field.from.parameter.display.name: + text: Assignment to Date or Calendar field from parameter +assignment.to.date.calendar.field.from.parameter.problem.descriptor: + text: 'Assignment to ''''{0}'''' field #ref from parameter {1} #loc' +assignment.to.for.loop.parameter.check.foreach.option: + text: Check enhanced 'for' loop parameters +assignment.to.for.loop.parameter.display.name: + text: Assignment to 'for' loop parameter +assignment.to.for.loop.parameter.problem.descriptor: + text: 'Assignment to for-loop parameter #ref #loc' +assignment.to.lambda.parameter.display.name: + text: Assignment to lambda parameter +assignment.to.lambda.parameter.problem.descriptor: + text: 'Assignment to lambda parameter #ref #loc' +assignment.to.method.parameter.display.name: + text: Assignment to method parameter +assignment.to.method.parameter.ignore.transformation.option: + text: Ignore if assignment is a transformation of the original parameter +assignment.to.method.parameter.problem.descriptor: + text: 'Assignment to method parameter #ref #loc' +assignment.to.null.display.name: + text: '''null'' assignment' +assignment.to.null.option: + text: Ignore assignments to fields +assignment.to.null.problem.descriptor: + text: '''null'' assigned to variable #ref #loc' +assignment.to.static.field.from.instance.method.display.name: + text: Assignment to static field from instance context +assignment.to.static.field.from.instance.method.problem.descriptor: + text: 'Assignment to static field #ref from instance context #loc' +assignment.to.superclass.field.display.name: + text: Constructor assigns value to field defined in superclass +assignment.to.superclass.field.problem.descriptor: + text: 'Assignment to field ''''{0}'''' defined in superclass ''''{1}'''' #loc' +assignment.used.as.condition.display.name: + text: Assignment used as condition +assignment.used.as.condition.problem.descriptor: + text: 'Assignment #ref used as condition #loc' +assignment.used.as.condition.replace.quickfix: + text: Replace '=' with '==' +atomic.field.updater.issues.display.name: + text: Inconsistent 'AtomicFieldUpdater' declaration +atomic.field.updater.not.static.final.display.name: + text: '''AtomicFieldUpdater'' field not declared ''static final''' +atomic.field.updater.not.static.final.problem.descriptor: + text: '{0} field #ref is not declared ''''static final'''' #loc' +auto.boxing.display.name: + text: Auto-boxing +auto.boxing.ignore.added.to.collection.option: + text: Ignore expressions added to a collection +auto.boxing.make.boxing.explicit.quickfix: + text: Make boxing explicit +auto.boxing.problem.descriptor: + text: 'Auto-boxing #ref #loc' +auto.closeable.resource.display.name: + text: AutoCloseable used without 'try'-with-resources +auto.closeable.resource.problem.descriptor: + text: '''''{0}'''' used without ''''try''''-with-resources statement #loc' +auto.closeable.resource.quickfix: + text: Ignore 'AutoCloseable' returned by this method +auto.closeable.resource.returned.option: + text: Ignore AutoCloseable instances returned from all method calls +auto.unboxing.display.name: + text: Auto-unboxing +auto.unboxing.make.unboxing.explicit.quickfix: + text: Make unboxing explicit +auto.unboxing.problem.descriptor: + text: 'Auto-unboxing #ref #loc' +await.not.in.loop.display.name: + text: '''await()'' not called in loop' +await.not.in.loop.problem.descriptor: + text: 'Call to #ref() is not in loop #loc' +await.without.corresponding.signal.display.name: + text: '''await()'' without corresponding ''signal()''' +await.without.corresponding.signal.problem.descriptor: + text: 'Call to #ref() without corresponding signal() or signalAll() #loc' +bad.exception.caught.display.name: + text: Prohibited 'Exception' caught +bad.exception.caught.problem.descriptor: + text: 'Prohibited exception #ref caught #loc' +bad.exception.declared.display.name: + text: Prohibited exception declared +bad.exception.declared.problem.descriptor: + text: 'Prohibited exception #ref declared #loc' +bad.exception.thrown.display.name: + text: Prohibited exception thrown +bad.exception.thrown.problem.descriptor: + text: 'Prohibited exception ''''{0}'''' thrown #loc' +bad.oddness.display.name: + text: Suspicious oddness check +bad.oddness.problem.descriptor: + text: 'Test for oddness #ref will fail on negative values #loc' +before.class.or.after.class.is.public.static.void.no.arg.display.name: + text: Malformed @BeforeClass/@BeforeAll or @AfterClass/@AfterAll method +before.class.or.after.class.is.public.static.void.no.arg.problem.descriptor: + text: '#ref() has incorrect signature for a @{0} method #loc' +before.or.after.is.public.void.no.arg.display.name: + text: Malformed @Before or @After method +before.or.after.is.public.void.no.arg.problem.descriptor: + text: '#ref() has incorrect signature for a @Before or @After method #loc' +big.decimal.equals.display.name: + text: '''equals()'' called on ''BigDecimal''' +big.decimal.equals.problem.descriptor: + text: '#ref() between BigDecimal values should probably be ''compareTo()'' #loc' +big.decimal.equals.replace.quickfix: + text: Replace with 'compareTo()==0' +big.decimal.method.without.rounding.called.display.name: + text: Call to 'BigDecimal' method without a rounding mode argument +big.decimal.method.without.rounding.called.problem.descriptor: + text: '''BigDecimal.#ref()'' called without a rounding mode argument' +bigdecimal.legacy.method.display.name: + text: '''BigDecimal'' legacy method called' +bigdecimal.legacy.method.problem.descriptor: + text: Call to 'BigDecimal.#ref()' can use 'RoundingMode' enum constant +bigdecimal.legacy.method.quickfix: + text: Use 'RoundingMode' enum constant +boolean.constructor.display.name: + text: Boolean constructor call +boolean.constructor.problem.descriptor: + text: 'Boolean constructor call #loc' +boolean.constructor.simplify.quickfix: + text: Simplify +boolean.expression.can.be.simplified.problem.descriptor: + text: '#ref can be simplified to ''''{0}'''' #loc' +boolean.expression.may.be.conditional.display.name: + text: Boolean expression can be replaced with conditional expression +boolean.field.always.inverted.problem.descriptor: + text: 'Boolean field #ref is always inverted #loc' +boolean.method.name.must.start.with.question.display.name: + text: Boolean method name must start with question word +boolean.method.name.must.start.with.question.problem.descriptor: + text: 'Boolean method name #ref does not start with question word #loc' +boolean.method.name.must.start.with.question.table.column.name: + text: Boolean method name prefix +boolean.parameter.constructor.problem.descriptor: + text: '''public'' constructor #ref() with ''boolean'' parameter #loc' +boolean.parameter.display.name: + text: '''public'' method with ''boolean'' parameter' +boolean.parameter.only.report.multiple.option: + text: Only report methods with multiple boolean parameters +boolean.parameter.problem.descriptor: + text: '''public'' method #ref() with ''boolean'' parameter #loc' +boolean.parameters.constructor.problem.descriptor: + text: '''public'' constructor #ref() with ''boolean'' parameters #loc' +boolean.parameters.problem.descriptor: + text: '''public'' method #ref() with ''boolean'' parameters #loc' +boolean.variable.always.inverted.display.name: + text: Boolean variable is always inverted +boolean.variable.always.inverted.problem.descriptor: + text: 'Boolean variable #ref is always inverted #loc' +boolean.variable.always.inverted.quickfix: + text: Invert ''{0}'' +boxing.boxed.value.display.name: + text: Boxing of already boxed value +boxing.boxed.value.problem.descriptor: + text: 'Boxing of already boxed #ref #loc' +boxing.boxed.value.quickfix: + text: Remove unnecessary boxing +break.statement.display.name: + text: '''break'' statement' +break.statement.with.label.display.name: + text: '''break'' statement with label' +break.statement.with.label.problem.descriptor: + text: '#ref statement with label #loc' +busy.wait.display.name: + text: Busy wait +busy.wait.problem.descriptor: + text: 'Call to Thread.#ref() in a loop, probably busy-waiting #loc' +c.style.array.declaration.display.name: + text: C-style array declaration +c.style.array.declaration.problem.descriptor: + text: 'C-style array declaration #ref #loc' +c.style.array.declaration.replace.quickfix: + text: Replace with Java-style array declaration +cached.number.constructor.call.display.name: + text: Number constructor call with primitive argument +cached.number.constructor.call.ignore.string.arguments.option: + text: Ignore new number expressions with a String argument +cached.number.constructor.call.problem.descriptor: + text: 'Number constructor call with primitive argument #loc' +cached.number.constructor.call.quickfix: + text: Replace with ''{0}.valueOf()'' call +call.to.date.tostring.display.name: + text: Call to 'Date.toString()' +call.to.date.tostring.problem.descriptor: + text: 'Date.#ref() used in an internationalized context #loc' +call.to.native.method.while.locked.display.name: + text: Call to a 'native' method while locked +call.to.native.method.while.locked.problem.descriptor: + text: 'Call to native method #ref() in a synchronized context #loc' +call.to.numeric.tostring.display.name: + text: Call to 'Number.toString()' +call.to.numeric.tostring.problem.descriptor: + text: 'Number.#ref() called in an internationalized context #loc' +call.to.private.setter.in.class.option: + text: Only report when setter is 'private' +call.to.private.simple.getter.in.class.option: + text: Only report when getter is 'private' +call.to.simple.getter.in.class.display.name: + text: Call to simple getter from within class +call.to.simple.getter.in.class.ignore.option: + text: Ignore getter calls on other objects +call.to.simple.getter.in.class.inline.quickfix: + text: Inline call to getter +call.to.simple.getter.in.class.problem.descriptor: + text: 'Call to simple getter #ref() from within class #loc' +call.to.simple.setter.in.class.display.name: + text: Call to simple setter from within class +call.to.simple.setter.in.class.ignore.option: + text: Ignore setter calls on other objects +call.to.simple.setter.in.class.inline.quickfix: + text: Inline call to setter +call.to.simple.setter.in.class.problem.descriptor: + text: 'Call to simple setter #ref() from within class #loc' +call.to.string.concat.can.be.replaced.by.operator.display.name: + text: Call to 'String.concat()' can be replaced with '+' +call.to.string.concat.can.be.replaced.by.operator.problem.descriptor: + text: 'Call to #ref() can be replaced with ''+'' expression #loc' +call.to.string.concat.can.be.replaced.by.operator.quickfix: + text: Replace 'concat()' call with '+' +call.to.suspicious.string.method.display.name: + text: Call to suspicious 'String' method +call.to.suspicious.string.method.problem.descriptor: + text: 'String.#ref() called in internationalized context #loc' +cast.conflicts.with.instanceof.display.name: + text: Cast conflicts with 'instanceof' +cast.conflicts.with.instanceof.problem.descriptor: + text: 'Cast #ref conflicts with surrounding ''instanceof'' check #loc' +cast.conflicts.with.instanceof.quickfix1: + text: Replace ''{0}'' with ''{1}'' in cast +cast.conflicts.with.instanceof.quickfix2: + text: Replace ''{0}'' with ''{1}'' in instanceof +cast.that.loses.precision.display.name: + text: Numeric cast that loses precision +cast.that.loses.precision.negative.problem.descriptor: + text: 'Cast from ''''{0}'''' to #ref may result in loss of precision for negative argument #loc' +cast.that.loses.precision.option: + text: Ignore casts from int to char +cast.that.loses.precision.problem.descriptor: + text: 'Cast from ''''{0}'''' to #ref may result in loss of precision #loc' +cast.to.concrete.class.display.name: + text: Cast to a concrete class +cast.to.concrete.class.ignore.equals.option: + text: Ignore in equals() +cast.to.concrete.class.option: + text: Ignore casts to an abstract class type +cast.to.concrete.class.problem.descriptor: + text: 'Cast to concrete class {0} #loc' +casting.to.incompatible.interface.display.name: + text: Cast to incompatible type +casting.to.incompatible.interface.problem.descriptor: + text: 'Cast to incompatible interface #ref #loc' +caught.exception.immediately.rethrown.display.name: + text: Caught exception is immediately rethrown +caught.exception.immediately.rethrown.problem.descriptor: + text: 'Caught exception #ref is immediately rethrown #loc' +chain.of.class.equality.checks.problem.descriptor: + text: 'Chain of class equality checks indicates abstraction failure #loc' +chain.of.instanceof.checks.display.name: + text: Chain of 'instanceof' checks +chain.of.instanceof.checks.problem.descriptor: + text: 'Chain of ''instanceof'' checks indicates abstraction failure #loc' +chained.equality.comparisons.display.name: + text: Chained equality comparisons +chained.equality.comparisons.problem.descriptor: + text: 'Chained equality comparison #ref #loc' +chained.method.call.display.name: + text: Chained method calls +chained.method.call.ignore.option: + text: Ignore chained method calls in field initializers +chained.method.call.ignore.this.super.option: + text: Ignore chained method calls in 'this()' and 'super()' calls +chained.method.call.problem.descriptor: + text: 'Chained method call #ref() #loc' +change.modifier.quickfix: + text: Make ''{0}'' +channel.opened.not.closed.display.name: + text: '''Channel'' opened but not safely closed' +channel.opened.not.closed.problem.descriptor: + text: '''''{0}'''' should be opened in front of a ''''try'''' block and closed in the corresponding ''''finally'''' block #loc' +char.used.in.arithmetic.context.cast.quickfix: + text: Insert cast to {0} +char.used.in.arithmetic.context.display.name: + text: '''char'' expression used in arithmetic context' +char.used.in.arithmetic.context.problem.descriptor: + text: '''char'' #ref used in arithmetic context #loc' +char.used.in.arithmetic.context.quickfix: + text: Convert to String literal +character.comparison.display.name: + text: Character comparison +character.comparison.problem.descriptor: + text: 'Character comparison #ref in an internationalized context #loc' +checked.exception.class.display.name: + text: Checked exception class +checked.exception.class.problem.descriptor: + text: 'Checked exception class #ref #loc' +choose.autocloseable.type.to.ignore.title: + text: Choose AutoCloseable Resource Type to Ignore +choose.class: + text: Choose Class +choose.class.type.to.ignore: + text: Choose Class to Ignore +choose.exception.class: + text: Choose Exception Class +choose.io.resource.type.to.ignore: + text: Choose I/O Resource Type to Ignore +choose.logger.class: + text: Choose Logger Class +choose.super.class.to.ignore: + text: Choose class +class.escapes.defined.scope.display.module.option: + text: Report non-exported classes exposed in module API (Java 9+) +class.escapes.defined.scope.display.name: + text: Class is exposed outside of its visibility scope +class.escapes.defined.scope.display.package.option: + text: Report private classes exposed in package-local API +class.escapes.defined.scope.display.public.option: + text: Report non-accessible classes exposed in public API +class.escapes.defined.scope.java9.modules.descriptor: + text: Class #ref is not exported from module ''{0}'' +class.escapes.defined.scope.problem.descriptor: + text: 'Class #ref is exposed outside its defined visibility scope #loc' +class.extends.utility.class.display.name: + text: Class extends utility class +class.extends.utility.class.ignore.utility.class.option: + text: Ignore if overriding class is a utility class +class.extends.utility.class.problem.descriptor: + text: 'Class #ref extends utility class ''''{0}'''' #loc' +class.independent.of.module.display.name: + text: Class independent of its module +class.independent.of.module.problem.descriptor: + text: 'Class #ref has no dependencies or dependents in its module #loc' +class.initializer.display.name: + text: Non-'static' initializer +class.initializer.may.be.static.display.name: + text: Class initializer may be 'static' +class.initializer.may.be.static.problem.descriptor: + text: 'Class initializer may be ''static'' #loc' +class.initializer.move.code.to.constructor.quickfix: + text: Move initializer code to constructor +class.initializer.option: + text: Only warn when the class has one or more constructors +class.initializer.problem.descriptor: + text: 'Non-''static'' initializer #loc' +class.loader.instantiation.display.name: + text: '''ClassLoader'' instantiation' +class.loader.instantiation.problem.descriptor: + text: 'Instantiation of #ref may pose security concerns #loc' +class.may.be.interface.convert.quickfix: + text: Convert class to interface +class.may.be.interface.display.name: + text: Abstract 'class' may be 'interface' +class.may.be.interface.java8.option: + text: Report classes containing non-abstract methods when using Java 8 +class.may.be.interface.problem.descriptor: + text: 'Abstract class #ref may be interface #loc' +class.name: + text: Class Name +class.name.convention.problem.descriptor.long: + text: 'Class name #ref is too long #loc' +class.name.convention.problem.descriptor.regex.mismatch: + text: 'Class name #ref doesn''''t match regex ''''{0}'''' #loc' +class.name.convention.problem.descriptor.short: + text: 'Class name #ref is too short #loc' +class.name.differs.from.file.name.display.name: + text: Class name differs from file name +class.name.differs.from.file.name.problem.descriptor: + text: 'Class name #ref differs from file name #loc' +class.name.prefixed.with.package.name.display.name: + text: Class name prefixed with package name +class.name.prefixed.with.package.name.problem.descriptor: + text: 'Class name #ref begins with its package name #loc' +class.name.same.as.ancestor.name.display.name: + text: Class name same as ancestor name +class.name.same.as.ancestor.name.problem.descriptor: + text: 'Class name #ref is the same as one of its superclass'' names #loc' +class.naming.convention.display.name: + text: Class naming convention +class.naming.convention.element.description: + text: Class +class.new.instance.display.name: + text: Unsafe call to 'Class.newInstance()' +class.new.instance.problem.descriptor: + text: 'Call to #ref() may throw undeclared checked exceptions #loc' +class.new.instance.quickfix: + text: Replace with 'Class.getConstructor().newInstance()' call +class.only.used.from.test.code.problem.descriptor: + text: 'class #ref only used from test code #loc' +class.only.used.in.one.module.display.name: + text: Class only used from one other module +class.only.used.in.one.module.problem.descriptor: + text: 'Class #ref has only dependencies on and/or dependents in module ''''{0}'''' #loc' +class.only.used.in.one.package.display.name: + text: Class only used from one other package +class.only.used.in.one.package.problem.descriptor: + text: 'Class #ref has only dependencies on and/or dependents in package ''''{0}'''' #loc' +class.references.subclass.display.name: + text: Class references one of its subclasses +class.references.subclass.problem.descriptor: + text: 'Class ''''{0}'''' references subclass #ref #loc' +class.references.subclass.problem.descriptor.anonymous: + text: 'Anonymous class references subclass #ref #loc' +class.too.deep.display.name: + text: Class too deep in inheritance tree +class.too.deep.inheritance.depth.limit.option: + text: 'Inheritance depth limit:' +class.too.deep.problem.descriptor: + text: '#ref is too deep in inheritance tree (inheritance depth = {0}) #loc' +class.unconnected.to.package.display.name: + text: Class independent of its package +class.unconnected.to.package.problem.descriptor: + text: Class #ref has no dependencies or dependents in its package +class.with.only.private.constructors.display.name: + text: Class with only 'private' constructors should be declared 'final' +class.with.only.private.constructors.problem.descriptor: + text: Class #ref with only 'private' constructors should be declared 'final' +class.with.too.many.dependencies.display.name: + text: Class with too many dependencies +class.with.too.many.dependencies.max.option: + text: Maximum number of dependencies +class.with.too.many.dependencies.problem.descriptor: + text: Class ''{0}'' has too many dependencies ({1} > {2}) +class.with.too.many.dependents.display.name: + text: Class with too many dependents +class.with.too.many.dependents.max.option: + text: Maximum number of dependents +class.with.too.many.dependents.problem.descriptor: + text: Class ''{0}'' has too many dependents ({1} > {2}) +class.with.too.many.transitive.dependencies.display.name: + text: Class with too many transitive dependencies +class.with.too.many.transitive.dependencies.max.option: + text: Maximum number of transitive dependencies +class.with.too.many.transitive.dependencies.problem.descriptor: + text: Class ''{0}'' has too many transitive dependencies ({1} > {2}) +class.with.too.many.transitive.dependents.display.name: + text: Class with too many transitive dependents +class.with.too.many.transitive.dependents.max.option: + text: Maximum number of transitive dependents +class.with.too.many.transitive.dependents.problem.descriptor: + text: Class ''{0}'' has too many transitive dependencies ({1} > {2}) +class.without.constructor.create.quickfix: + text: Generate empty constructor +class.without.constructor.display.name: + text: Class without constructor +class.without.constructor.problem.descriptor: + text: 'Class #ref has no constructor #loc' +class.without.no.arg.constructor.display.name: + text: Class without no-arg constructor +class.without.no.arg.constructor.ignore.option: + text: Ignore if class has default constructor +class.without.no.arg.constructor.problem.descriptor: + text: 'Class #ref is missing a no-arg constructor #loc' +class.without.tostring.display.name: + text: Class without 'toString()' +class.without.tostring.problem.descriptor: + text: 'Class #ref should probably implement ''toString()'', for debugging purposes #loc' +clone.doesnt.call.super.clone.display.name: + text: '''clone()'' does not call ''super.clone()''' +clone.doesnt.call.super.clone.problem.descriptor: + text: '#ref() does not call ''super.clone()'' #loc' +clone.doesnt.declare.clonenotsupportedexception.declare.quickfix: + text: Add 'CloneNotSupportedException' to throws clause +clone.doesnt.declare.clonenotsupportedexception.display.name: + text: '''clone()'' does not declare ''CloneNotSupportedException''' +clone.doesnt.declare.clonenotsupportedexception.problem.descriptor: + text: '#ref() #loc does not declare ''CloneNotSupportedException''' +clone.instantiates.objects.with.constructor.display.name: + text: '''clone()'' instantiates objects with constructor' +clone.instantiates.objects.with.constructor.problem.descriptor: + text: '''clone()'' creates new #ref instances #loc' +clone.method.in.non.cloneable.class.display.name: + text: '''clone()'' method in non-Cloneable class' +clone.method.in.non.cloneable.class.problem.descriptor: + text: '#ref() defined in non-Cloneable class ''''{0}'''' #loc' +clone.method.in.non.cloneable.interface.problem.descriptor: + text: '#ref() defined in non-Cloneable interface ''''{0}'''' #loc' +clone.returns.class.type.display.name: + text: '''clone()'' should have return type equal to the class it contains' +clone.returns.class.type.family.quickfix: + text: Change return type to class type +clone.returns.class.type.problem.descriptor: + text: '''''clone()'''' should have return type ''''{0}'''' #loc' +clone.returns.class.type.quickfix: + text: Change return type to ''{0}'' +cloneable.class.in.secure.context.display.name: + text: Cloneable class in secure context +cloneable.class.in.secure.context.problem.descriptor: + text: 'Class #ref may be cloned, compromising security #loc' +cloneable.class.without.clone.display.name: + text: Cloneable class without 'clone()' method +cloneable.class.without.clone.ignore.option: + text: Ignore classes cloneable due to inheritance +cloneable.class.without.clone.problem.descriptor: + text: '#ref is ''Cloneable'' but does not define ''clone()'' method #loc' +cloneable.class.without.clone.quickfix: + text: Generate 'clone()' method +collection.added.to.self.display.name: + text: Collection added to itself +collection.added.to.self.problem.descriptor: + text: 'Collection #ref is added to self #loc' +collection.contains.url.display.name: + text: Map or Set may contain 'java.net.URL' objects +collection.contains.url.problem.decriptor: + text: '{0} #ref may contain URL objects #loc' +collection.declared.by.class.display.name: + text: Collection declared by class, not interface +collection.declared.by.class.ignore.locals.option: + text: Ignore local variables +collection.declared.by.class.ignore.private.members.option: + text: Ignore 'private' fields and methods +collection.declared.by.class.problem.descriptor: + text: 'Declaration of #ref should probably be weakened to ''''{0}'''' #loc' +collections.field.access.replaceable.by.method.call.display.name: + text: Reference to empty collection field can be replaced with method call +collections.field.access.replaceable.by.method.call.problem.descriptor: + text: '#ref can be replaced with ''''Collections.{0}'''' #loc' +collections.field.access.replaceable.by.method.call.quickfix: + text: Replace with ''{0}'' +collections.must.have.initial.capacity.display.name: + text: Collection without initial capacity +collections.must.have.initial.capacity.problem.descriptor: + text: 'new #ref() without initial capacity #loc' +comparable.implemented.but.equals.not.overridden.display.name: + text: '''Comparable'' implemented but ''equals()'' not overridden' +comparable.implemented.but.equals.not.overridden.problem.descriptor: + text: 'Class #ref implements ''java.lang.Comparable'' but does not override ''equals()'' #loc' +comparator.method.parameter.not.used.display.name: + text: '''Comparator.compare()'' method does not use parameter' +comparator.method.parameter.not.used.problem.descriptor: + text: '''compare()'' parameter #ref is not used #loc' +comparator.not.serializable.display.name: + text: '''Comparator'' class not declared ''Serializable''' +comparator.not.serializable.problem.descriptor: + text: 'Comparator class #ref is not declared as Serializable #loc' +comparison.of.short.and.char.display.name: + text: Comparison of 'short' and 'char' values +comparison.of.short.and.char.problem.descriptor: + text: 'Equality comparison #ref of short and char values #loc' +comparison.to.nan.display.name: + text: Comparison to 'Double.NaN' or 'Float.NaN' +comparison.to.nan.problem.descriptor1: + text: 'Comparison to #ref is always false #loc' +comparison.to.nan.problem.descriptor2: + text: 'Comparison to #ref is always true #loc' +comparison.to.nan.replace.quickfix: + text: Replace with 'isNaN()' +concrete.class.method.parameter.display.name: + text: Method parameter of concrete class +concrete.class.method.parameter.problem.descriptor: + text: 'Parameter ''''{0}'''' of concrete class #ref #loc' +condition.signal.display.name: + text: Call to 'signal()' instead of 'signalAll()' +condition.signal.problem.descriptor: + text: '#ref should probably be replaced with ''signalAll()'' #loc' +condition.signal.replace.quickfix: + text: Replace with 'signalAll()' +conditional.can.be.pushed.inside.expression.display.name: + text: Conditional can be pushed inside branch expression +conditional.can.be.pushed.inside.expression.option: + text: Ignore when conditional will be only argument of a method call +conditional.can.be.pushed.inside.expression.problem.descriptor: + text: 'Conditional expression can be pushed inside branch #loc' +conditional.can.be.pushed.inside.expression.quickfix: + text: Push conditional expression inside branch +conditional.expression.display.name: + text: Conditional expression (?:) +conditional.expression.option: + text: Ignore for simple assignments and returns +conditional.expression.problem.descriptor: + text: 'Conditional expression #ref #loc' +conditional.expression.with.identical.branches.collapse.quickfix: + text: Collapse conditional expression +conditional.expression.with.identical.branches.display.name: + text: Conditional expression with identical branches +conditional.expression.with.identical.branches.problem.descriptor: + text: 'Conditional expression #ref with identical branches #loc' +conditional.expression.with.identical.branches.push.inside.quickfix: + text: Push conditional inside expression +conditional.expression.with.similar.branches.problem.descriptor: + text: 'Conditional expression #ref with similar branches #loc' +confusing.else.display.name: + text: Confusing 'else' branch +confusing.else.option: + text: Report when there are no more statements after the 'if' statement +confusing.else.problem.descriptor: + text: '#ref branch may be unwrapped, as the ''if'' branch never completes #loc' +confusing.else.unwrap.quickfix: + text: Remove redundant 'else' +confusing.floating.point.literal.change.quickfix: + text: Change to canonical form +confusing.floating.point.literal.display.name: + text: Confusing floating-point literal +confusing.floating.point.literal.option: + text: Ignore floating-point literals in scientific notation +confusing.floating.point.literal.problem.descriptor: + text: 'Confusing floating-point literal #ref #loc' +confusing.main.method.display.name: + text: Confusing 'main()' method +confusing.main.method.problem.descriptor: + text: 'Method #ref() does not have signature ''public static void main(String[])'' #loc' +confusing.octal.escape.sequence.display.name: + text: Confusing octal escape sequence +confusing.octal.escape.sequence.problem.descriptor: + text: 'Octal escape sequence #ref immediately followed by a digit #loc' +connection.opened.not.safely.closed.display.name: + text: Connection opened but not safely closed +consider.static.final.fields.constant.option: + text: Consider 'static final' fields constant +constant.assert.condition.display.name: + text: Constant condition in 'assert' statement +constant.assert.condition.problem.descriptor: + text: 'Assert condition #ref is constant #loc' +constant.conditional.expression.display.name: + text: Constant conditional expression +constant.conditional.expression.problem.descriptor: + text: '#ref can be simplified to ''''{0}'''' #loc' +constant.conditional.expression.simplify.quickfix: + text: Simplify +constant.declared.in.abstract.class.display.name: + text: Constant declared in 'abstract' class +constant.declared.in.abstract.class.problem.descriptor: + text: 'Constant #ref declared in abstract class #loc' +constant.declared.in.interface.display.name: + text: Constant declared in interface +constant.declared.in.interface.problem.descriptor: + text: 'Constant #ref declared in interface #loc' +constant.for.zero.length.array.display.name: + text: Unnecessary zero length array usage +constant.for.zero.length.array.problem.descriptor: + text: 'Zero length array can be changed to constant #loc' +constant.for.zero.length.array.quickfix.family: + text: Replace with constant +constant.if.statement.display.name: + text: Constant 'if' statement +constant.if.statement.problem.descriptor: + text: '#ref statement can be simplified #loc' +constant.junit.assert.argument.display.name: + text: Constant assert argument +constant.junit.assert.argument.problem.descriptor: + text: 'Argument #ref is constant #loc' +constant.math.call.display.name: + text: Constant call to 'Math' +constant.math.call.problem.descriptor: + text: 'Constant call to #ref() can be simplified #loc' +constant.naming.convention.display.name: + text: Constant naming convention +constant.naming.convention.element.description: + text: Constant +constant.naming.convention.immutables.option: + text: Only check 'static final' fields with immutable types +constant.naming.convention.problem.descriptor.long: + text: 'Constant name #ref is too long #loc' +constant.naming.convention.problem.descriptor.regex.mismatch: + text: 'Constant #ref doesn''''t match regex ''''{0}'''' #loc' +constant.naming.convention.problem.descriptor.short: + text: 'Constant name #ref is too short #loc' +constant.on.lhs.of.comparison.display.name: + text: Constant on left side of comparison +constant.on.lhs.of.comparison.problem.descriptor: + text: 'Constant #ref on the left side of the comparison #loc' +constant.on.rhs.of.comparison.display.name: + text: Constant on right side of comparison +constant.on.rhs.of.comparison.problem.descriptor: + text: 'Constant #ref on the right side of the comparison #loc' +constant.string.intern.display.name: + text: Call to 'intern()' on String constant +constant.string.intern.problem.descriptor: + text: '.#ref() on compile-time constant is unnecessary #loc' +constant.string.intern.quickfix: + text: Remove 'intern()' call +constant.value.variable.use.display.name: + text: Use of variable whose value is known to be constant +constant.value.variable.use.problem.descriptor: + text: 'Value of #ref is known to be constant #loc' +constructor.visibility.option: + text: 'Ignore constructors with visibility:' +continue.or.break.from.finally.block.display.name: + text: '''continue'' or ''break'' inside ''finally'' block' +continue.or.break.from.finally.block.problem.descriptor: + text: '#ref inside ''finally'' block #loc' +continue.statement.display.name: + text: '''continue'' statement' +continue.statement.with.label.display.name: + text: '''continue'' statement with label' +continue.statement.with.label.problem.descriptor: + text: '#ref statement with label #loc' +control.flow.statement.without.braces.add.quickfix: + text: Add braces to statement +control.flow.statement.without.braces.display.name: + text: Control flow statement without braces +control.flow.statement.without.braces.message: + text: Add braces to ''{0}'' statement +control.flow.statement.without.braces.problem.descriptor: + text: '#ref without braces #loc' +convention.max.length.option: + text: 'Max length:' +convention.min.length.option: + text: 'Min length:' +convention.pattern.option: + text: 'Pattern:' +convert.junit3.test.class.quickfix: + text: Convert JUnit 3 class ''{0}'' to JUnit 4 +convert.octal.literal.to.decimal.literal.quickfix: + text: Convert octal literal to decimal literal +convert.to.variable.arity.method.quickfix: + text: Convert to varargs method +covariant.compareto.display.name: + text: Covariant 'compareTo()' +covariant.compareto.problem.descriptor: + text: '#ref() should take ''Object'' as its argument #loc' +covariant.equals.display.name: + text: Covariant 'equals()' +covariant.equals.problem.descriptor: + text: '#ref() should take ''Object'' as its argument #loc' +cstyle.array.method.declaration.problem.descriptor: + text: C-style array declaration of the return type of method #ref()#loc +cstyle.array.variable.declaration.problem.descriptor: + text: 'C-style array declaration of {0, choice, 1#field|2#parameter|3#local variable} #ref #loc' +custom.classloader.display.name: + text: Custom 'ClassLoader' is declared +custom.classloader.problem.descriptor: + text: 'Custom ClassLoader class #ref #loc' +custom.security.manager.display.name: + text: Custom 'SecurityManager' +custom.security.manager.problem.descriptor: + text: 'Custom SecurityManager class #ref #loc' +cyclic.class.dependency.1.problem.descriptor: + text: Class ''{0}'' is cyclically dependent on class ''{1}'' +cyclic.class.dependency.2.problem.descriptor: + text: Class ''{0}'' is cyclically dependent on classes ''{1}'' and ''{2}'' +cyclic.class.dependency.display.name: + text: Cyclic class dependency +cyclic.class.dependency.problem.descriptor: + text: Class ''{0}'' is cyclically dependent on {1} other classes +cyclic.class.initialization.display.name: + text: Cyclic class initialization dependency +cyclic.class.initialization.problem.descriptor: + text: Initialization of class ''{0}'' is cyclically dependent on {1} other classes +cyclic.package.dependency.1.problem.descriptor: + text: Package ''{0}'' is cyclically dependent on package ''{1}'' +cyclic.package.dependency.2.problem.descriptor: + text: Package ''{0}'' is cyclically dependent on packages ''{1}'' and ''{2}'' +cyclic.package.dependency.display.name: + text: Cyclic package dependency +cyclic.package.dependency.problem.descriptor: + text: Package ''{0}'' is cyclically dependent on {1} other packages +cyclomatic.complexity.display.name: + text: Overly complex method +cyclomatic.complexity.limit.option: + text: 'Cyclomatic complexity limit:' +cyclomatic.complexity.problem.descriptor: + text: 'Overly complex method #ref() (cyclomatic complexity = {0}) #loc' +dangling.javadoc.convert.quickfix: + text: Replace with block comment +dangling.javadoc.delete.quickfix: + text: Remove dangling comment +dangling.javadoc.display.name: + text: Dangling Javadoc comment +dangling.javadoc.problem.descriptor: + text: 'Dangling Javadoc comment #loc' +debug.level.and.lower.option: + text: debug level and lower +declare.collection.as.interface.quickfix: + text: Weaken to ''{0}'' +default.not.last.case.in.switch.display.name: + text: '''default'' not last case in ''switch''' +default.not.last.case.in.switch.problem.descriptor: + text: '#ref branch not last case in ''switch'' statement #loc' +default.tostring.call.display.name: + text: Call to default 'toString()' +default.tostring.call.problem.descriptor: + text: 'Call to default ''toString()'' on #ref #loc' +delete.catch.section.quickfix: + text: Delete 'catch' section +delete.import.quickfix: + text: Delete unnecessary import +deserializable.class.in.secure.context.display.name: + text: Deserializable class in secure context +deserializable.class.in.secure.context.problem.descriptor: + text: 'Class #ref may be deserialized, compromising security #loc' +design.for.extension.display.name: + text: Design for extension +design.for.extension.problem.descriptor: + text: 'Method #ref() may be overridden and its functionality ignored #loc' +diamond.can.be.replaced.with.explicit.type.arguments.name: + text: Diamond can be replaced with explicit type arguments +diamond.can.be.replaced.with.explicit.type.arguments.quickfix: + text: Replace '<>' with explicit type arguments +disjoint.package.display.name: + text: Package with disjoint dependency graph +disjoint.package.problem.descriptor: + text: Package {0} can be decomposed into {1} independent packages +divide.by.zero.display.name: + text: Division by zero +divide.by.zero.problem.descriptor: + text: 'Division by zero #loc' +dollar.sign.in.name.display.name: + text: Use of '$' in identifier +dollar.sign.in.name.problem.descriptor: + text: 'Identifier #ref contains ''$'' #loc' +double.brace.initialization.display.name: + text: Double brace initialization +double.brace.initialization.quickfix: + text: Replace with regular initialization +double.checked.locking.display.name: + text: Double-checked locking +double.checked.locking.ignore.on.volatiles.option: + text: Ignore double-checked locking on volatile fields +double.checked.locking.problem.descriptor: + text: 'Double-checked locking #loc' +double.checked.locking.quickfix: + text: Make ''{0}'' volatile +double.literal.may.be.float.literal.display.name: + text: Cast to 'float' can be 'float' literal +double.literal.may.be.float.literal.problem.descriptor: + text: '#ref could be replaced with ''''{0}'''' #loc' +double.literal.may.be.float.literal.quickfix: + text: Replace with ''{0}'' +double.negation.display.name: + text: Double negation +double.negation.problem.descriptor: + text: 'Double negation in #ref #loc' +double.negation.quickfix: + text: Remove double negation +drivermanager.call.display.name: + text: Use of 'DriverManager' to get JDBC connection +drivermanager.call.problem.descriptor: + text: 'Call to DriverManager.#ref() #loc' +dumpstack.call.display.name: + text: Call to 'Thread.dumpStack()' +dumpstack.call.problem.descriptor: + text: 'Call to Thread.#ref() should probably be replaced with more robust logging #loc' +duplicate.boolean.branch.display.name: + text: Duplicate condition on '\&\&' or '||' +duplicate.boolean.branch.problem.descriptor: + text: 'Duplicate branch #ref #loc' +duplicate.condition.display.name: + text: Duplicate condition in 'if' statement +duplicate.condition.ignore.method.calls.option: + text: Ignore conditions with possible side effects +duplicate.condition.problem.descriptor: + text: 'Duplicate condition #ref #loc' +dynamic.regex.replaceable.by.compiled.pattern.display.name: + text: Dynamic regular expression can be replaced by compiled 'Pattern' +dynamic.regex.replaceable.by.compiled.pattern.problem.descriptor: + text: '#ref() can be replaced with compiled ''java.util.regex.Pattern'' construct #loc' +dynamic.regex.replaceable.by.compiled.pattern.quickfix: + text: Replace with call to method of compiled 'Pattern' constant +element.only.used.from.test.code.display.name: + text: Symbol only used from test code +empty.anonymous.class.problem.descriptor: + text: 'Anonymous class is empty #loc' +empty.catch.block.comments.option: + text: Comments count as content +empty.catch.block.display.name: + text: Empty 'catch' block +empty.catch.block.ignore.ignore.option: + text: Ignore when 'catch' parameter is named 'ignore' or 'ignored' +empty.catch.block.ignore.option: + text: Ignore empty 'catch' blocks in tests +empty.catch.block.problem.descriptor: + text: 'Empty #ref block #loc' +empty.class.display.name: + text: Empty class +empty.class.file.without.class.problem.descriptor: + text: 'Java file does not declare any class #loc' +empty.class.ignore.parameterization.option: + text: Ignore class if it is a parameterization of a super type +empty.class.initializer.delete.quickfix: + text: Delete empty class initializer +empty.class.initializer.display.name: + text: Empty class initializer +empty.class.initializer.problem.descriptor: + text: 'Empty class initializer #loc' +empty.class.problem.descriptor: + text: 'Class #ref is empty #loc' +empty.directories.delete.quickfix: + text: Delete empty directory ''{0}'' +empty.directories.only.under.source.roots.option: + text: Only report empty directories located under a source folder +empty.directories.problem.descriptor: + text: Empty directory {0} +empty.directory.display.name: + text: Empty directory +empty.finally.block.display.name: + text: Empty 'finally' block +empty.finally.block.problem.descriptor: + text: 'Empty #ref block #loc' +empty.synchronized.statement.display.name: + text: Empty 'synchronized' statement +empty.synchronized.statement.problem.descriptor: + text: 'Empty #ref statement #loc' +empty.try.block.display.name: + text: Empty 'try' block +empty.try.block.problem.descriptor: + text: 'Empty #ref block #loc' +encapsulate.variable.quickfix: + text: Encapsulate field ''{0}'' +enum.switch.statement.which.misses.cases.display.name: + text: Enum 'switch' statement that misses case +enum.switch.statement.which.misses.cases.option: + text: Ignore switch statements with a default branch +enum.switch.statement.which.misses.cases.problem.descriptor: + text: '#ref statement on enum type ''''{0}'''' misses cases #loc' +enumerated.class.display.name: + text: Enumerated class +enumerated.class.naming.convention.display.name: + text: Enumerated class naming convention +enumerated.class.naming.convention.element.description: + text: Enum class +enumerated.class.naming.convention.problem.descriptor.long: + text: 'Enumerated class name #ref is too long #loc' +enumerated.class.naming.convention.problem.descriptor.regex.mismatch: + text: 'Enumerated class name #ref doesn''''t match regex ''''{0}'''' #loc' +enumerated.class.naming.convention.problem.descriptor.short: + text: 'Enumerated class name #ref is too short #loc' +enumerated.class.problem.descriptor: + text: 'Enumerated class #ref #loc' +enumerated.constant.naming.convention.display.name: + text: Enumerated constant naming convention +enumerated.constant.naming.convention.element.description: + text: Enum constant +enumerated.constant.naming.convention.problem.descriptor.long: + text: 'Enumerated constant name #ref is too long #loc' +enumerated.constant.naming.convention.problem.descriptor.regex.mismatch: + text: 'Enumerated constant #ref doesn''''t match regex ''''{0}'''' #loc' +enumerated.constant.naming.convention.problem.descriptor.short: + text: 'Enumerated constant name #ref is too short #loc' +enumeration.can.be.iteration.display.name: + text: Enumeration can be iteration +enumeration.can.be.iteration.problem.descriptor: + text: '#ref() can be replaced with ''''{0}'''' construct #loc' +enumeration.can.be.iteration.quickfix: + text: Replace with 'Iterator' construct +equality.operator.compares.objects.descriptor: + text: Objects compared with ''{0}'' +equality.operator.compares.objects.family.quickfix: + text: Replace equality operator with equals() +equality.operator.compares.objects.name: + text: Objects compared with '==' +equality.operator.compares.objects.quickfix: + text: Replace ''{0}'' with ''{1}equals()'' +equality.operator.compares.objects.safe.family.quickfix: + text: Replace equality operator with safe equals() +equality.operator.compares.objects.safe.quickfix: + text: Replace ''{0}'' with safe ''{1}equals()'' +equals.between.inconvertible.types.display.name: + text: '''equals()'' between objects of inconvertible types' +equals.between.inconvertible.types.problem.descriptor: + text: '#ref() between objects of inconvertible types ''''{0}'''' and ''''{1}'''' #loc' +equals.called.on.array.display.name: + text: '''equals()'' called on array' +equals.called.on.array.problem.descriptor: + text: '#ref() between arrays should probably be ''Arrays.equals()'' #loc' +equals.called.on.enum.constant.display.name: + text: '''equals()'' called on enum value' +equals.called.on.enum.constant.problem.descriptor: + text: '#ref() called on enum value #loc' +equals.called.on.enum.constant.quickfix: + text: Replace 'equals()' with '==' +equals.called.on.suspicious.object.display.name: + text: '''equals()'' called on classes which don''t override it' +equals.called.on.suspicious.object.problem.descriptor: + text: Suspicious call to ''equals()'' on ''{0}'' object +equals.doesnt.check.class.parameter.display.name: + text: '''equals()'' method that does not check the class of its parameter' +equals.doesnt.check.class.parameter.problem.descriptor: + text: '#ref() should check the class of its parameter #loc' +equals.hashcode.called.on.url.display.name: + text: '''equals()'' or ''hashCode()'' called on ''java.net.URL'' object' +equals.hashcode.called.on.url.problem.descriptor: + text: 'Call to #ref() on URL object #loc' +equals.replaceable.by.objects.call.display.name: + text: '''equals()'' expression replaceable by ''Objects.equals()'' expression' +equals.replaceable.by.objects.call.problem.descriptor: + text: '#ref replaceable by ''Objects.equals()'' expression #loc' +equals.replaceable.by.objects.call.quickfix: + text: Replace with 'Objects.equals()' expression +equals.replaceable.by.objects.check.not.null.option: + text: Report only null safe 'equals' calls +equals.to.equality.quickfix: + text: Replace 'equals()' with '==' +equals.with.itself.display.name: + text: '''equals()'' called on itself' +equals.with.itself.problem.descriptor: + text: #ref() called on itself +error.rethrown.display.name: + text: '''Error'' not rethrown' +error.rethrown.problem.descriptor: + text: 'Error #ref not rethrown #loc' +exception.class.column.name: + text: Exception class +exception.from.catch.which.doesnt.wrap.display.name: + text: '''throw'' inside ''catch'' block which ignores the caught exception' +exception.from.catch.which.doesnt.wrap.problem.descriptor: + text: '#ref inside ''catch'' block ignores the caught exception #loc' +exception.from.catch.which.doesntwrap.ignore.cant.wrap.option: + text: Ignore if thrown exception cannot wrap an exception +exception.from.catch.which.doesntwrap.ignore.option: + text: Ignore if result of exception method call is used +exception.name.doesnt.end.with.exception.display.name: + text: Exception class name does not end with 'Exception' +exception.name.doesnt.end.with.exception.problem.descriptor: + text: 'Exception class name #ref does not end with ''Exception'' #loc' +expected.exception.never.thrown.display.name: + text: Expected exception never thrown in test method body +expected.exception.never.thrown.problem.descriptor: + text: 'Expected #ref never thrown in body of ''''{0}()'''' #loc' +explicit.array.to.string.problem.descriptor: + text: 'Call to ''#ref()'' on array #loc' +expression.can.be.replaced.problem.descriptor: + text: '#ref can be replaced with ''''{0}'''' #loc' +extended.for.statement.display.name: + text: Enhanced 'for' statement +extended.for.statement.problem.descriptor: + text: 'Extended #ref statement #loc' +extended.for.statement.replace.quickfix: + text: Replace with old-style 'for' statement +extends.annotation.display.name: + text: Class extends annotation interface +extends.annotation.interface.problem.descriptor: + text: 'Interface ''''{0}'''' extends annotation interface #ref #loc' +extends.annotation.problem.descriptor: + text: 'Class ''''{0}'''' implements annotation interface #ref #loc' +extends.concrete.collection.display.name: + text: Class explicitly extends a 'Collection' class +extends.concrete.collection.problem.descriptor: + text: 'Class #ref explicitly extends ''''{0}'''' #loc' +extends.object.display.name: + text: Class explicitly extends 'Object' +extends.object.problem.descriptor: + text: 'Class #ref explicitly extends ''java.lang.Object'' #loc' +extends.object.remove.quickfix: + text: Remove redundant 'extends Object' +extends.thread.display.name: + text: Class directly extends 'Thread' +extends.thread.problem.descriptor: + text: 'Class #ref directly extends ''java.lang.Thread'' #loc' +extends.throwable.display.name: + text: Class directly extends 'Throwable' +extends.throwable.problem.descriptor: + text: 'Class #ref directly extends ''java.lang.Throwable'' #loc' +externalizable.with.serialization.methods.display.name: + text: Externalizable class with 'readObject()' or 'writeObject()' +externalizable.with.serialization.methods.problem.descriptor.both: + text: 'Externalizable class #ref defines ''readObject()'' and ''writeObject()'' #loc' +externalizable.with.serialization.methods.problem.descriptor.read: + text: 'Externalizable class #ref defines ''readObject()'' #loc' +externalizable.with.serialization.methods.problem.descriptor.write: + text: 'Externalizable class #ref defines ''writeObject()'' #loc' +externalizable.without.public.no.arg.constructor.display.name: + text: '''Externalizable'' class without ''public'' no-arg constructor' +externalizable.without.public.no.arg.constructor.problem.descriptor: + text: 'Externalizable class #ref has no ''public'' no-arg constructor #loc' +extract.method.quickfix: + text: Extract method +extract.parameter.as.local.variable.quickfix: + text: Extract parameter as local variable +fallthru.in.switch.statement.display.name: + text: Fallthrough in 'switch' statement +fallthru.in.switch.statement.problem.descriptor: + text: 'Fallthrough in ''switch'' statement #loc' +fallthru.in.switch.statement.quickfix: + text: Add 'break' +feature.envy.display.name: + text: Feature envy +feature.envy.ignore.test.cases.option: + text: Ignore feature envy in tests +feature.envy.problem.descriptor: + text: 'Class ''''{0}'''' accessed repeatedly in method #ref() #loc' +field.accessed.synchronized.and.unsynchronized.display.name: + text: Field accessed in both 'synchronized' and unsynchronized contexts +field.accessed.synchronized.and.unsynchronized.option: + text: Simple getters and setters are considered field accesses too +field.accessed.synchronized.and.unsynchronized.problem.descriptor: + text: 'Field #ref is accessed in both synchronized and unsynchronized contexts #loc' +field.count.inspection.include.constant.fields.in.count.checkbox: + text: Include constant fields in count +field.count.inspection.include.enum.constants.in.count: + text: Include enum constants in count +field.count.inspection.static.final.fields.count.as.constant.checkbox: + text: '''static final'' fields count as constant' +field.has.setter.but.no.getter.display.name: + text: Field has setter but no getter +field.has.setter.but.no.getter.problem.descriptor: + text: 'Field #ref has setter but no getter #loc' +field.has.static.modifier.problem.descriptor: + text: Field ''{0}'' has ''static'' modifier +field.incorrect.type.problem.descriptor: + text: Field ''{0}'' does not have type ''{1}'' +field.may.be.final.display.name: + text: Field may be 'final' +field.may.be.final.problem.descriptor: + text: 'Field #ref may be ''final'' #loc' +field.may.be.static.display.name: + text: Field can be made 'static' +field.may.be.static.problem.descriptor: + text: 'Field #ref may be ''static'' #loc' +field.missing.volatile.modifier.problem.descriptor: + text: Field ''{0}'' does not have ''volatile'' modifier +field.name.hides.in.superclass.display.name: + text: Subclass field hides superclass field +field.name.hides.in.superclass.ignore.option: + text: Ignore non-accessible fields +field.name.hides.in.superclass.problem.descriptor: + text: 'Field #ref hides field in superclass #loc' +field.not.found.in.class.problem.descriptor: + text: No field named ''{0}'' found in class ''{1}'' +field.only.used.from.test.code.problem.descriptor: + text: 'field #ref only accessed from test code #loc' +field.repeatedly.accessed.in.method.display.name: + text: Field repeatedly accessed in method +field.repeatedly.accessed.in.method.ignore.option: + text: Ignore 'final' fields +field.repeatedly.accessed.in.method.problem.descriptor: + text: 'Field ''''{0}'''' accessed repeatedly in method #ref() #loc' +final.class.display.name: + text: Class is closed to inheritance +final.class.problem.descriptor: + text: 'Class declared #ref #loc' +final.method.display.name: + text: Method can't be overridden +final.method.in.final.class.display.name: + text: '''final'' method in ''final'' class' +final.method.in.final.class.problem.descriptor: + text: 'Method declared #ref in ''final'' class #loc' +final.method.problem.descriptor: + text: 'Method declared #ref #loc' +final.private.method.display.name: + text: '''private'' method declared ''final''' +final.private.method.problem.descriptor: + text: '''private'' method declared #ref #loc' +final.static.method.display.name: + text: '''static'' method declared ''final''' +final.static.method.problem.descriptor: + text: '''static'' method declared #ref #loc' +finalize.called.explicitly.display.name: + text: '''finalize()'' called explicitly' +finalize.called.explicitly.problem.descriptor: + text: '#ref() called explicitly #loc' +finalize.declaration.display.name: + text: '''finalize()'' should not be overridden' +finalize.declaration.problem.descriptor: + text: '''finalize()'' should not be overridden #loc' +finalize.doesnt.call.super.display.name: + text: '''finalize()'' does not call ''super.finalize()''' +finalize.doesnt.call.super.ignore.option: + text: Ignore for direct subclasses of 'java.lang.Object' +finalize.doesnt.call.super.problem.descriptor: + text: '#ref() #loc does not call ''super.finalize()''' +finalize.not.declared.protected.display.name: + text: '''finalize()'' should be protected, not public' +finalize.not.declared.protected.problem.descriptor: + text: '''finalize()'' should have protected access, not public #loc' +finally.block.cannot.complete.normally.display.name: + text: '''finally'' block which can not complete normally' +finally.block.cannot.complete.normally.problem.descriptor: + text: '#ref block can not complete normally #loc' +flip.comparison.quickfix: + text: Flip comparison +floating.point.equality.display.name: + text: Floating-point equality comparison +floating.point.equality.problem.descriptor: + text: '#ref: floating-point values compared for exact equality #loc' +for.can.be.foreach.display.name: + text: '''for'' loop can be replaced with enhanced for loop' +for.can.be.foreach.option: + text: Report indexed 'java.util.List' loops +for.can.be.foreach.option2: + text: Do not report iterations over untyped collections +for.can.be.foreach.problem.descriptor: + text: '#ref loop can be replaced with enhanced ''for'' #loc' +for.loop.not.use.loop.variable.display.name: + text: '''for'' loop where update or condition does not use loop variable' +for.loop.not.use.loop.variable.problem.descriptor.both.condition.and.update: + text: '#ref statement has condition and update which do not use the for loop variable #loc' +for.loop.not.use.loop.variable.problem.descriptor.condition: + text: '#ref statement has condition which does not use the for loop variable #loc' +for.loop.not.use.loop.variable.problem.descriptor.update: + text: '#ref statement has update which does not use the for loop variable #loc' +for.loop.replaceable.by.while.display.name: + text: '''for'' loop may be replaced with ''while'' loop' +for.loop.replaceable.by.while.ignore.option: + text: Ignore 'infinite' for loops without conditions +for.loop.replaceable.by.while.problem.descriptor: + text: '#ref loop statement may be replace by ''while'' loop #loc' +for.loop.replaceable.by.while.replace.quickfix: + text: Replace with 'while' +for.loop.with.missing.component.collection.loop.option: + text: Ignore collection iterations +for.loop.with.missing.component.display.name: + text: '''for'' loop with missing components' +for.loop.with.missing.component.problem.descriptor1: + text: '#ref statement lacks initializer #loc' +for.loop.with.missing.component.problem.descriptor2: + text: '#ref statement lacks condition #loc' +for.loop.with.missing.component.problem.descriptor3: + text: '#ref statement lacks update #loc' +for.loop.with.missing.component.problem.descriptor4: + text: '#ref statement lacks initializer and condition #loc' +for.loop.with.missing.component.problem.descriptor5: + text: '#ref statement lacks initializer and update #loc' +for.loop.with.missing.component.problem.descriptor6: + text: '#ref statement lacks condition and update #loc' +for.loop.with.missing.component.problem.descriptor7: + text: '#ref statement lacks initializer, condition and update #loc' +foreach.replace.quickfix: + text: Replace with enhanced 'for' +format.decode.any: + text: any +format.decode.char: + text: char +format.decode.date.time: + text: Date/Time +format.decode.error.requires.both.0.and.1: + text: requires both {0} and {1} +format.decode.floating.point: + text: floating point +format.decode.integer.type: + text: integer type +gc.call.display.name: + text: Call to 'System.gc()' or 'Runtime.gc()' +gc.call.problem.descriptor: + text: '#ref should not be called in production code #loc' +hardcoded.file.separator.display.name: + text: Hardcoded file separator +hardcoded.file.separator.include.option: + text: Include 'example/*' in recognized MIME media types +hardcoded.file.separator.problem.descriptor: + text: 'Hardcoded file separator #ref #loc' +hardcoded.line.separator.display.name: + text: Hardcoded line separator +hardcoded.line.separator.problem.descriptor: + text: 'Hardcoded line separator #ref #loc' +hibernate.resource.opened.not.closed.display.name: + text: Hibernate resource opened but not safely closed +hibernate.resource.opened.not.closed.problem.descriptor: + text: '''''{0}'''' should be opened in front of a ''''try'''' block and closed in the corresponding ''''finally'''' block #loc' +html.tag.can.be.javadoc.tag.display.name: + text: ''''' can be replaced with ''{@code …}''' +html.tag.can.be.javadoc.tag.problem.descriptor: + text: '#ref…\</code\> can be replaced with ''{@code …}'' #loc' +html.tag.can.be.javadoc.tag.quickfix: + text: Replace with '{@code ...}' +i.o.resource.opened.not.closed.display.name: + text: I/O resource opened but not safely closed +if.can.be.assertion.name: + text: If statement can be replaced with assertion +if.can.be.assertion.quickfix: + text: Replace 'if' with 'assert' statement +if.can.be.switch.display.name: + text: '''if'' can be replaced with ''switch''' +if.can.be.switch.enum.option: + text: Suggest switch on enums +if.can.be.switch.int.option: + text: Suggest switch on numbers +if.can.be.switch.minimum.branch.option: + text: 'Minimum number of ''if'' condition branches:' +if.can.be.switch.null.safe.option: + text: Only suggest on null-safe expressions +if.can.be.switch.problem.descriptor: + text: '#ref statement can be replaced with ''switch'' statement #loc' +if.can.be.switch.quickfix: + text: Replace with 'switch' +if.may.be.conditional.display.name: + text: '''if'' statement could be replaced with conditional expression' +if.may.be.conditional.problem.descriptor: + text: '#ref can be replaced with conditional expression #loc' +if.may.be.conditional.quickfix: + text: Replace with conditional expression +if.may.be.conditional.report.method.calls.option: + text: Report if statements containing method calls +if.statement.with.identical.branches.collapse.quickfix: + text: Collapse 'if' statement +if.statement.with.identical.branches.display.name: + text: '''if'' statement with identical branches' +if.statement.with.identical.branches.problem.descriptor: + text: '#ref statement with identical branches #loc' +if.statement.with.too.many.branches.display.name: + text: '''if'' statement with too many branches' +if.statement.with.too.many.branches.max.option: + text: 'Maximum number of branches:' +if.statement.with.too.many.branches.problem.descriptor: + text: '#ref has too many branches ({0}) #loc' +ignore.accesses.from.equals.method: + text: Ignore accesses from 'equals()' method +ignore.accesses.from.the.same.class: + text: Ignore accesses from the same class +ignore.anonymous.inner.classes: + text: Ignore anonymous inner classes +ignore.as.initial.capacity: + text: Ignore initial capacity argument when constructing StringBuilders and Collections +ignore.boolean.methods.in.an.interface.option: + text: Ignore boolean methods in an @&interface +ignore.branches.of.switch.statements: + text: Ignore branches of 'switch' statements +ignore.calls.to.property.getters: + text: Ignore calls to property getters +ignore.calls.to.static.methods: + text: Ignore calls to static methods +ignore.classes.extending.throwable.option: + text: Ignore classes extending 'Throwable' +ignore.classes.in.hierarchy.column.name: + text: 'Ignore subclasses of:' +ignore.cloneable.option: + text: Ignore 'java.lang.Cloneable' +ignore.equals.hashcode.and.tostring: + text: Ignore 'equals()', 'hashCode()' and 'toString()' methods +ignore.exceptions.declared.in.tests.option: + text: Ignore exceptions declared in &tests +ignore.exceptions.declared.on.library.override.option: + text: Ignore exceptions declared on methods overriding a &library method +ignore.for.equals.methods.option: + text: Ignore for '&equals()' methods +ignore.guard.clauses.option: + text: Ignore &guard clauses +ignore.if.annotated.by: + text: 'Ignore if annotated by:' +ignore.in.annotations: + text: Ignore in annotations +ignore.in.module.statements.option: + text: Ignore in Java 9 module statements +ignore.in.test.code: + text: Ignore in &test code +ignore.in.tostring: + text: Ignore inside toString() methods +ignore.instanceof.on.library.classes: + text: Ignore instanceof on library classes +ignore.iterator.loop.variables: + text: Ignore 'java.util.Iterator' loop variables +ignore.methods.in.anonymous.classes: + text: Ignore methods in anonymous classes +ignore.methods.overriding.super.method: + text: Ignore methods &overriding/implementing a super method +ignore.methods.with.boolean.return.type.option: + text: Ignore methods with 'java.lang.&Boolean' return type +ignore.nullable.parameters.option: + text: Ignore @Nullable parameters +ignore.overflowing.byte.casts.option: + text: Ignore casts from int 128-255 to byte +ignore.parameter.if.annotated.by: + text: Ignore parameter if it is annotated by +ignore.parentheses.around.single.no.formal.type.lambda.parameter: + text: Ignore parentheses around single no formal type lambda parameter +ignore.serializable.option: + text: Ignore 'java.io.Serializable' +ignore.single.field.static.imports.option: + text: Ignore single &field static imports +ignore.single.method.static.imports.option: + text: Ignore single &method static imports +ignore.static.methods.accessed.from.a.non.static.inner.class: + text: Ignore '&static' methods accessed from a non-'static' inner class +ignore.test.method.in.class.extending.junit3.testcase.problem.descriptor: + text: 'JUnit 3 test method #ref() annotated with ''@Ignore'' won''t be ignored #loc' +ignore.test.method.in.class.extending.junit3.testcase.quickfix: + text: Remove ''@Ignore'' and rename method to ''{0}'' +ignore.trivial.finalizers.option: + text: Ignore for trivial 'finalize()' implementations +ignored.autocloseable.types.column.label: + text: Ignored AutoCloseable resource types +ignored.class.names: + text: Ignore Classes (Including Subclasses) +ignored.classes.table: + text: Ignored classes +ignored.io.resource.types: + text: Ignored I/O resource types +ignored.junit.test.classproblem.descriptor: + text: 'Test class ''''{0}'''' annotated with #ref #loc' +ignored.junit.test.display.name: + text: JUnit test annotated with '@Ignore'/'@Disabled' +ignored.junit.test.method.problem.descriptor: + text: 'Test method ''''{0}()'''' annotated with #ref #loc' +implicit.array.to.string.display.name: + text: Call to 'toString()' on array +implicit.array.to.string.method.call.problem.descriptor: + text: 'Implicit call to ''toString()'' on array returned by call to #ref #loc' +implicit.array.to.string.problem.descriptor: + text: 'Implicit call to ''toString()'' on array #ref #loc' +implicit.array.to.string.quickfix: + text: Wrap with ''{0}'' expression +implicit.call.to.super.display.name: + text: Implicit call to 'super()' +implicit.call.to.super.ignore.option: + text: Ignore for direct subclasses of 'java.lang.Object' +implicit.call.to.super.make.explicit.quickfix: + text: Make call to 'super()' explicit +implicit.call.to.super.problem.descriptor: + text: 'Implicit call to ''super()'' #loc' +implicit.default.charset.usage.constructor.problem.descriptor: + text: 'new #ref() call uses the platform''s default charset' +implicit.default.charset.usage.display.name: + text: Implicit usage of platform's default charset +implicit.default.charset.usage.problem.descriptor: + text: Call to #ref() uses the platform's default charset +implicit.numeric.conversion.convert.quickfix: + text: Convert to ''{0}'' +implicit.numeric.conversion.display.name: + text: Implicit numeric conversion +implicit.numeric.conversion.ignore.char.conversion.option: + text: Ignore conversions from and to 'char' +implicit.numeric.conversion.ignore.constant.conversion.option: + text: Ignore conversions from constants and literals +implicit.numeric.conversion.ignore.widening.conversion.option: + text: Ignore widening conversions +implicit.numeric.conversion.make.explicit.quickfix: + text: Make conversion explicit +implicit.numeric.conversion.problem.descriptor: + text: 'Implicit numeric conversion of #ref from ''''{0}'''' to ''''{1}'''' #loc' +import.display.name: + text: '''*'' import' +import.from.same.package.display.name: + text: Unnecessary import from the same package +import.from.same.package.problem.descriptor: + text: 'Unnecessary import from the same package #ref #loc' +import.problem.descriptor: + text: 'Package import #ref #loc' +include.java.system.classes.option: + text: Include couplings to java system classes +include.library.classes.option: + text: Include couplings to library classes +incompatible.mask.operation.display.name: + text: Incompatible bitwise mask operation +incompatible.mask.operation.problem.descriptor.always.false: + text: '#ref is always false #loc' +incompatible.mask.operation.problem.descriptor.always.true: + text: '#ref is always true #loc' +increment.decrement.display.name: + text: Result of '++' or '--' used +increment.decrement.used.as.expression.quickfix: + text: Extract ''{0}'' to separate statement +indexof.replaceable.by.contains.display.name: + text: '''indexOf()'' expression is replaceable with ''contains()''' +infinite.loop.statement.display.name: + text: Infinite loop statement +infinite.loop.statement.problem.descriptor: + text: '#ref statement cannot complete without throwing an exception #loc' +infinite.recursion.display.name: + text: Infinite recursion +infinite.recursion.problem.descriptor: + text: 'Method #ref() recurses infinitely, and can only end by throwing an exception #loc' +info.level.and.lower.option: + text: info level and lower +inline.call.quickfix: + text: Inline call +inline.variable.quickfix: + text: Inline variable +inner.class.field.hides.outer.display.name: + text: Inner class field hides outer class field +inner.class.field.hides.outer.ignore.option: + text: Ignore outer fields not visible from inner class +inner.class.field.hides.outer.problem.descriptor: + text: 'Inner class field #ref hides outer class field #loc' +inner.class.may.be.static.display.name: + text: Inner class may be 'static' +inner.class.may.be.static.problem.descriptor: + text: 'Inner class #ref may be ''static'' #loc' +inner.class.on.interface.display.name: + text: Inner class of interface +inner.class.on.interface.ignore.option: + text: Ignore inner interfaces of interfaces +inner.class.on.interface.problem.descriptor: + text: 'Interface ''''{0}'''' has inner class #ref #loc' +inner.class.referenced.via.subclass.display.name: + text: Inner class referenced via subclass +inner.class.referenced.via.subclass.problem.descriptor: + text: 'Inner class #ref declared in class ''''{0}'''' but referenced via subclass ''''{1}'''' #loc' +inner.class.referenced.via.subclass.quickfix: + text: Rationalize inner class access +inner.class.too.deeply.nested.display.name: + text: Inner class too deeply nested +inner.class.too.deeply.nested.nesting.limit.option: + text: 'Nesting limit:' +inner.class.too.deeply.nested.problem.descriptor: + text: '#ref is too deeply nested (nesting level = {0}) #loc' +inspection.option.ignore.as.initial.capacity: + text: Ignore initial capacity for StringBuilders and Collections +inspection.option.ignore.assert: + text: Ignore for assert statement description arguments +inspection.option.ignore.constant.initializers: + text: Ignore for initializers of constant fields +inspection.option.ignore.exceptions: + text: Ignore for constructor arguments of Throwable subclasses +inspection.option.ignore.in.annotations: + text: Ignore in annotations +inspection.option.ignore.in.hashcode: + text: Ignore constants in 'hashCode()' methods +inspection.option.ignore.in.tostring: + text: Ignore inside toString() methods +inspection.option.ignore.nonnls: + text: Ignore when annotated via @NonNls +inspection.option.ignore.system.err: + text: Ignore for 'System.err.print' arguments +inspection.option.ignore.system.out: + text: Ignore for 'System.out.print' arguments +inspection.suppression.annotation.display.name: + text: Inspection suppression annotation +inspection.suppression.annotation.problem.descriptor: + text: 'Inspection suppression annotation #ref #loc' +inspection.surround.if.quickfix: + text: Surround with ''if ({0}{1})'' +instance.Variable.may.not.be.initialized.problem.descriptor.junit: + text: 'Instance field #ref may not be initialized during object construction or ''setUp()'' call #loc' +instance.method.name.convention.problem.descriptor.long: + text: 'Instance method name #ref is too long #loc' +instance.method.name.convention.problem.descriptor.regex.mismatch: + text: 'Instance method name #ref doesn''''t match regex ''''{0}'''' #loc' +instance.method.name.convention.problem.descriptor.short: + text: 'Instance method name #ref is too short #loc' +instance.method.naming.convention.display.name: + text: Instance method naming convention +instance.method.naming.convention.element.description: + text: Instance method +instance.variable.may.not.be.initialized.display.name: + text: Instance field may not be initialized +instance.variable.may.not.be.initialized.problem.descriptor: + text: 'Instance field #ref may not be initialized during object construction #loc' +instance.variable.name.convention.problem.descriptor.long: + text: 'Instance field name #ref is too long #loc' +instance.variable.name.convention.problem.descriptor.regex.mismatch: + text: 'Instance field #ref doesn''''t match regex ''''{0}'''' #loc' +instance.variable.name.convention.problem.descriptor.short: + text: 'Instance field name #ref is too short #loc' +instance.variable.naming.convention.display.name: + text: Instance field naming convention +instance.variable.naming.convention.element.description: + text: Instance field +instance.variable.of.concrete.class.display.name: + text: Type of instance field is concrete class +instance.variable.of.concrete.class.option: + text: Ignore instance fields whose type is an abstract class +instance.variable.of.concrete.class.problem.descriptor: + text: 'Type of field ''''{0}'''' is concrete class #ref #loc' +instance.variable.used.before.initialized.display.name: + text: Instance field used before initialization +instance.variable.used.before.initialized.problem.descriptor: + text: 'Instance field #ref used before initialized #loc' +instanceof.catch.parameter.display.name: + text: '''instanceof'' on ''catch'' parameter' +instanceof.catch.parameter.problem.descriptor: + text: '''instanceof'' on ''catch'' parameter #ref #loc' +instanceof.check.for.this.display.name: + text: '''instanceof'' check for ''this''' +instanceof.check.for.this.problem.descriptor: + text: '''instanceof'' check for #ref #loc' +instanceof.concrete.class.display.name: + text: '''instanceof'' a concrete class' +instanceof.concrete.class.problem.descriptor: + text: '''instanceof'' concrete class #ref #loc' +instanceof.interfaces.option: + text: Ignore instanceof abstract class +instanceof.with.incompatible.interface.display.name: + text: '''instanceof'' with incompatible type' +instanceof.with.incompatible.interface.problem.descriptor: + text: '''instanceof'' incompatible interface #ref #loc' +instantiating.object.to.get.class.object.display.name: + text: Instantiating object to get Class object +instantiating.object.to.get.class.object.problem.descriptor: + text: 'Instantiating object to get Class object #loc' +instantiating.object.to.get.class.object.replace.quickfix: + text: Replace with direct class object access +instantiating.simpledateformat.without.locale.display.name: + text: '''SimpleDateFormat'' without locale' +instantiating.simpledateformat.without.locale.problem.descriptor: + text: 'Instantiating a #ref without specifying a Locale in an internationalized context #loc' +instantiation.utility.class.display.name: + text: Instantiation of utility class +instantiation.utility.class.problem.descriptor: + text: 'Instantiation of utility class #ref #loc' +int.literal.may.be.long.literal.display.name: + text: Cast to 'long' can be 'long' literal +int.literal.may.be.long.literal.problem.descriptor: + text: '#ref can be replaced with ''''{0}'''' #loc' +int.literal.may.be.long.literal.quickfix: + text: Replace with ''{0}'' +integer.division.in.floating.point.context.display.name: + text: Integer division in floating-point context +integer.division.in.floating.point.context.problem.descriptor: + text: '#ref: integer division in floating-point context #loc' +integer.multiplication.implicit.cast.to.long.display.name: + text: Integer multiplication or shift implicitly cast to 'long' +integer.multiplication.implicit.cast.to.long.option: + text: Ignore compile time constant expressions which do not overflow +integer.multiplication.implicit.cast.to.long.problem.descriptor: + text: '#ref: integer multiplication implicitly cast to long #loc' +integer.shift.implicit.cast.to.long.problem.descriptor: + text: '#ref: integer shift implicitly cast to long #loc' +interface.clashes.with.object.class.display.name: + text: Interface method clashes with method in 'Object' +interface.clashes.with.object.class.problem.descriptor: + text: #ref() clashes with method in 'java.lang.Object' +interface.may.be.annotated.functional.display.name: + text: Interface may be annotated as '@FunctionalInterface' +interface.may.be.annotated.functional.problem.descriptor: + text: Interface #ref may be annotated with @FunctionalInterface +interface.name.convention.problem.descriptor.long: + text: 'Interface name #ref is too long #loc' +interface.name.convention.problem.descriptor.regex.mismatch: + text: 'Interface name #ref doesn''''t match regex ''''{0}'''' #loc' +interface.name.convention.problem.descriptor.short: + text: 'Interface name #ref is too short #loc' +interface.naming.convention.display.name: + text: Interface naming convention +interface.naming.convention.element.description: + text: Interface +interface.never.implemented.display.name: + text: Interface which has no concrete subclass +interface.never.implemented.option: + text: Ignore interfaces which only declare constants +interface.never.implemented.problem.descriptor: + text: 'Interface #ref has no concrete subclass #loc' +interface.one.inheritor.display.name: + text: Interface with a single direct inheritor +interface.one.inheritor.problem.descriptor: + text: 'Interface #ref has only one direct inheritor #loc' +introduce.constant.quickfix: + text: Introduce constant +introduce.holder.class.quickfix: + text: Introduce holder class +introduce.variable.may.change.semantics.quickfix: + text: Introduce variable (may change semantics) +introduce.variable.quickfix: + text: Introduce variable +invert.method.quickfix: + text: Invert method +invert.quickfix: + text: Invert ''{0}'' +invert.quickfix.family.name: + text: Invert boolean +iterator.hasnext.which.calls.next.display.name: + text: '''Iterator.hasNext()'' which calls ''next()''' +iterator.hasnext.which.calls.next.problem.descriptor: + text: 'Iterator.#ref() contains call to ''next()'' #loc' +iterator.next.does.not.throw.nosuchelementexception.display.name: + text: '''Iterator.next()'' which can''t throw ''NoSuchElementException''' +iterator.next.does.not.throw.nosuchelementexception.problem.descriptor: + text: 'Iterator.#ref() which can''t throw ''NoSuchElementException'' #loc' +java.lang.import.display.name: + text: Unnecessary import from the 'java.lang' package +java.lang.import.problem.descriptor: + text: 'Unnecessary import from the ''java.lang'' package #loc' +java.lang.reflect.display.name: + text: Use of 'java.lang.reflect' +java.lang.reflect.problem.descriptor: + text: 'Use of type #ref from ''java.lang.reflect'' #loc' +jdbc.execute.with.non.constant.string.display.name: + text: Call to 'Statement.execute()' with non-constant string +jdbc.execute.with.non.constant.string.problem.descriptor: + text: 'Call to Statement.#ref() with non-constant argument #loc' +jdbc.prepare.statement.with.non.constant.string.display.name: + text: Call to 'Connection.prepare*()' with non-constant string +jdbc.prepare.statement.with.non.constant.string.problem.descriptor: + text: 'Call to Connection.#ref() with non-constant argument #loc' +jdbc.resource.opened.not.closed.display.name: + text: JDBC resource opened but not safely closed +jdbc.resource.opened.not.closed.problem.descriptor: + text: 'JDBC ''''{0}'''' should be opened in front of a ''''try'''' block and closed in the corresponding ''''finally'''' block #loc' +jndi.resource.opened.not.closed.display.name: + text: JNDI resource opened but not safely closed +junit.abstract.test.class.naming.convention.display.name: + text: JUnit abstract test class naming convention +junit.abstract.test.class.naming.convention.element.description: + text: Abstract JUnit test class +junit.abstract.test.class.naming.convention.problem.descriptor.long: + text: 'Abstract JUnit test class name #ref is too long #loc' +junit.abstract.test.class.naming.convention.problem.descriptor.regex.mismatch: + text: 'Abstract JUnit test class name #ref doesn''''t match regex ''''{0}'''' #loc' +junit.abstract.test.class.naming.convention.problem.descriptor.short: + text: 'Abstract JUnit test class name #ref is too short #loc' +junit.datapoint.display.name: + text: Malformed @DataPoint field +junit.datapoint.problem.descriptor: + text: '{1}s annotated with @DataPoint should be {0}' +junit.rule.display.name: + text: Malformed @Rule/@ClassRule field +junit.rule.problem.descriptor: + text: Fields annotated with ''@{0}'' should be {1} +junit.rule.type.problem.descriptor: + text: Field type should be subtype of 'org.junit.rules.TestRule' +junit.test.class.naming.convention.display.name: + text: JUnit test class naming convention +junit.test.class.naming.convention.element.description: + text: Test class +junit.test.class.naming.convention.problem.descriptor.long: + text: 'JUnit test class name #ref is too long #loc' +junit.test.class.naming.convention.problem.descriptor.regex.mismatch: + text: 'JUnit test class name #ref doesn''''t match regex ''''{0}'''' #loc' +junit.test.class.naming.convention.problem.descriptor.short: + text: 'JUnit test class name #ref is too short #loc' +junit3.method.naming.convention.display.name: + text: JUnit 3 test method naming convention +junit3.method.naming.convention.element.description: + text: JUnit 3 test method +junit3.style.test.method.in.junit4.class.display.name: + text: Old style JUnit test method in JUnit 4 class +junit3.style.test.method.in.junit4.class.problem.descriptor: + text: 'Old style JUnit test method #ref() in JUnit 4 class #loc' +junit4.method.naming.convention.display.name: + text: JUnit 4+ test method naming convention +junit4.method.naming.convention.element.description: + text: JUnit 4+ test method +junit4.test.method.in.class.extending.junit3.testcase.display.name: + text: JUnit 4 test method in class extending JUnit 3 TestCase +junit4.test.method.in.class.extending.junit3.testcase.problem.descriptor: + text: 'Method #ref() annotated with ''@Test'' inside class extending JUnit 3 TestCase #loc' +junit5.assertions.converter.display.name: + text: Obsolete assertions in JUnit 5 tests +junit5.assertions.converter.familyName: + text: Replace with JUnit 5 compatible call +junit5.assertions.converter.problem.descriptor: + text: 'Call to #ref() from ''''{0}'''' should be replaced with call to method from ''''{1}'''' #loc' +junit5.assertions.converter.quickfix: + text: Replace with ''{0}'' method call +junit5.converter.display.name: + text: JUnit 4 test can be JUnit 5 +junit5.converter.fix.name: + text: Migrate to JUnit 5 +junit5.malformed.repeated.test.display.name: + text: JUnit 5 malformed repeated test +junit5.platform.runner.display.name: + text: '@RunWith(JUnitPlatform.class) without test methods' +junit5.valid.parameterized.configuration.display.name: + text: JUnit 5 malformed parameterized test +key.set.iteration.may.use.entry.set.display.name: + text: Iteration over 'keySet()' may be replaced with 'entrySet()' iteration +key.set.iteration.may.use.entry.set.problem.descriptor: + text: 'Iteration over #ref may be replaced with ''entrySet()'' iteration #loc' +key.set.iteration.may.use.entry.set.quickfix: + text: Replace with 'entrySet()' iteration +labeled.statement.display.name: + text: Labeled statement +labeled.statement.problem.descriptor: + text: 'Labeled statement #ref: #loc' +lambda.body.can.be.code.block.name: + text: Lambda body can be code block +lambda.body.can.be.code.block.quickfix: + text: Expand lambda body to '{'...'}' +lambda.can.be.replaced.with.anonymous.name: + text: Lambda can be replaced with anonymous class +lambda.can.be.replaced.with.anonymous.quickfix: + text: Replace lambda with anonymous class +lambda.parameter.hides.member.variable.display.name: + text: Lambda parameter hides field +lambda.parameter.hides.member.variable.ignore.invisible.option: + text: Ignore fields not actually visible from the lambda +lambda.parameter.hides.member.variable.problem.descriptor: + text: 'Lambda parameter #ref hides field in class ''''{0}'''' #loc' +lambda.parameter.naming.convention.display.name: + text: Lambda parameter naming convention +lambda.parameter.naming.convention.element.description: + text: Lambda parameter +lambda.parameter.type.can.be.specified.descriptor: + text: Lambda can be expanded to {0} -> '{'...'}' +lambda.parameter.type.can.be.specified.family.quickfix: + text: Specify lambda parameter type +lambda.parameter.type.can.be.specified.name: + text: Lambda parameter type can be specified +lambda.parameter.type.can.be.specified.quickfix: + text: Expand lambda to {0} -> '{'...'}' +lambda.unfriendly.constructor.overload.problem.descriptor: + text: Lambda-unfriendly overload of constructor #ref() +lambda.unfriendly.method.overload.display.name: + text: Lambda-unfriendly method overload +lambda.unfriendly.method.overload.problem.descriptor: + text: Lambda-unfriendly overload of method #ref() +large.array.allocation.no.outofmemoryerror.display.name: + text: Large array allocation with no OutOfMemoryError check +large.array.allocation.no.outofmemoryerror.maximum.number.of.elements.option: + text: 'Maximum number of elements:' +large.array.allocation.no.outofmemoryerror.problem.descriptor: + text: 'Large array allocation which is not checked for out-of-memory condition #loc' +large.initializer.primitive.type.array.display.name: + text: Overly large initializer for array of primitive type +large.initializer.primitive.type.array.maximum.number.of.elements.option: + text: 'Maximum number of elements:' +large.initializer.primitive.type.array.problem.descriptor: + text: 'Primitive array initializer with too many elements ({0}) #loc' +law.of.demeter.display.name: + text: Method call violates Law of Demeter +law.of.demeter.ignore.library.calls.option: + text: Ignore calls on library methods +law.of.demeter.problem.descriptor: + text: '#ref() call violates Law of Demeter #loc' +length.one.string.in.indexof.display.name: + text: Single character string argument in 'String.indexOf()' call +length.one.strings.in.concatenation.display.name: + text: Single character string concatenation +length.one.strings.in.concatenation.replace.quickfix: + text: Replace with character +limited.scope.inner.class.display.name: + text: Limited-scope inner class +limited.scope.inner.class.problem.descriptor: + text: 'Limited-scope inner class #ref #loc' +listener.may.use.adapter.display.name: + text: Class may extend adapter instead of implementing listener +listener.may.use.adapter.emtpy.methods.option: + text: '&Only warn when empty implementing methods are found' +listener.may.use.adapter.problem.descriptor: + text: 'Class ''''{0}'''' may extend ''''{1}'''' instead of implementing #ref #loc' +listener.may.use.adapter.quickfix: + text: Replace with ''extends {0}'' +literal.as.arg.to.string.equals.display.name: + text: '''expression.equals("literal")'' rather than ''"literal".equals(expression)''' +literal.as.arg.to.string.equals.flip.quickfix: + text: Flip 'equals()' +literal.as.arg.to.string.equals.problem.descriptor: + text: '#ref is argument of ''''{0}()'''', instead of its target #loc' +load.library.with.non.constant.string.display.name: + text: Call to 'System.loadLibrary()' with non-constant string +load.library.with.non.constant.string.problem.descriptor: + text: 'Call to System.#ref() with non-constant argument #loc' +local.variable.hides.member.variable.display.name: + text: Local variable hides field +local.variable.hides.member.variable.ignore.option: + text: Ignore local variables in a static context +local.variable.hides.member.variable.problem.descriptor: + text: 'Local variable #ref hides field in class ''''{0}'''' #loc' +local.variable.naming.convention.display.name: + text: Local variable naming convention +local.variable.naming.convention.element.description: + text: Local variable +local.variable.naming.convention.ignore.catch.option: + text: Ignore 'catch' block parameters +local.variable.naming.convention.ignore.option: + text: Ignore for-loop parameters +local.variable.naming.convention.problem.descriptor.long: + text: 'Local variable name #ref is too long #loc' +local.variable.naming.convention.problem.descriptor.regex.mismatch: + text: 'Local variable name #ref doesn''''t match regex ''''{0}'''' #loc' +local.variable.naming.convention.problem.descriptor.short: + text: 'Local variable name #ref is too short #loc' +local.variable.of.concrete.class.display.name: + text: Local variable of concrete class +local.variable.of.concrete.class.option: + text: Ignore local variables whose type is an abstract class +local.variable.of.concrete.class.problem.descriptor: + text: 'Local variable ''''{0}'''' of concrete class #ref #loc' +log.condition.text: + text: Log Condition Text +log.method.name: + text: Logging Method Name +log.statement.guarded.by.log.condition.display.name: + text: Logging call not guarded by log condition +log.statement.guarded.by.log.condition.flag.all.unguarded.option: + text: Flag all unguarded logging calls +log.statement.guarded.by.log.condition.problem.descriptor: + text: '#ref() logging calls not guarded by log condition #loc' +log.statement.guarded.by.log.condition.quickfix: + text: Surround with log condition +logger.class.name: + text: Logger class name +logger.factory.class.name: + text: Logger Factory Class Name +logger.factory.method.name: + text: Logger Factory Method Name +logger.initialized.with.foreign.class.display.name: + text: Logger initialized with foreign class +logger.initialized.with.foreign.class.problem.descriptor: + text: 'Logger initialized with foreign class #ref #loc' +logger.initialized.with.foreign.class.quickfix: + text: Replace with ''{0}.class'' +logger.name.option: + text: 'Logger &class name:' +logging.condition.disagrees.with.log.statement.display.name: + text: Log condition does not match logging call +logging.condition.disagrees.with.log.statement.problem.descriptor: + text: 'Log condition #ref() does not match ''''{0}()'''' logging call #loc' +long.literals.ending.with.lowercase.l.display.name: + text: '''long'' literal ending with ''l'' instead of ''L''' +long.literals.ending.with.lowercase.l.problem.descriptor: + text: '''long'' literal #ref ends with lowercase ''l'' #loc' +long.literals.ending.with.lowercase.l.replace.quickfix: + text: Replace 'l' with 'L' +loop.condition.not.updated.inside.loop.display.name: + text: Loop variable not updated inside loop +loop.condition.not.updated.inside.loop.problem.descriptor: + text: 'Condition ''#ref'' is not updated inside loop #loc' +loop.statements.that.dont.loop.display.name: + text: Loop statement that does not loop +loop.statements.that.dont.loop.problem.descriptor: + text: '#ref statement does not loop #loc' +loop.variable.not.updated.inside.loop.problem.descriptor: + text: 'Variable ''#ref'' is not updated inside loop #loc' +loop.with.implicit.termination.condition.display.name: + text: Loop with implicit termination condition +loop.with.implicit.termination.condition.dowhile.problem.descriptor: + text: '#ref-while loop with implicit termination condition #loc' +loop.with.implicit.termination.condition.problem.descriptor: + text: '#ref loop with implicit termination condition #loc' +loop.with.implicit.termination.condition.quickfix: + text: Make condition explicit +magic.character.display.name: + text: Magic character +magic.character.problem.descriptor: + text: 'Magic character #ref in an internationalized context #loc' +magic.number.display.name: + text: Magic number +magic.number.ignore.option: + text: Ignore constants in 'hashCode()' methods +magic.number.problem.descriptor: + text: 'Magic number #ref #loc' +make.class.cloneable.quickfix: + text: Make class 'Cloneable' +make.class.final.fix.name: + text: Make class ''{0}'' ''final'' +make.class.serializable.quickfix: + text: Make class 'Serializable' +make.constructor.public: + text: Make constructor 'public' +make.field.final.quickfix: + text: Make ''{0}'' ''final'' +make.initialization.explicit.quickfix: + text: Make initialization explicit +make.interface.cloneable.quickfix: + text: Make interface 'Cloneable' +make.method.ctr.quickfix: + text: Make method constructor +make.method.final.fix.name: + text: Make method ''{0}()'' ''final'' +make.private.quickfix: + text: Make 'private' +make.protected.quickfix: + text: Make 'protected' +make.static.final.quickfix: + text: Make ''{0}'' static final +make.static.quickfix: + text: Make 'static' +malformed.format.string.display.name: + text: Malformed format string +malformed.format.string.problem.descriptor.arguments.do.not.match.type: + text: 'Format string #ref does not match the type of its arguments #loc' +malformed.format.string.problem.descriptor.illegal: + text: 'Illegal format string specifier: {0} #loc' +malformed.format.string.problem.descriptor.malformed: + text: 'Format string #ref is malformed #loc' +malformed.format.string.problem.descriptor.too.few.arguments: + text: 'Too few arguments for format string #ref #loc' +malformed.format.string.problem.descriptor.too.many.arguments: + text: 'Too many arguments for format string #ref #loc' +malformed.regular.expression.display.name: + text: Malformed regular expression +malformed.regular.expression.problem.descriptor1: + text: 'Regular expression #ref is malformed #loc' +malformed.regular.expression.problem.descriptor2: + text: 'Regular expression #ref is malformed: {0} #loc' +malformed.set.up.tear.down.display.name: + text: Malformed 'setUp()' or 'tearDown()' method +malformed.set.up.tear.down.problem.descriptor: + text: '''#ref()'' has incorrect signature #loc' +malformed.xpath.expression.display.name: + text: Malformed XPath expression +malformed.xpath.expression.problem.description: + text: 'XPath expression #ref is malformed #loc' +manual.array.copy.display.name: + text: Manual array copy +manual.array.copy.problem.descriptor: + text: 'Manual array copy #loc' +manual.array.copy.replace.quickfix: + text: Replace with 'System.arraycopy()' +manual.array.to.collection.copy.display.name: + text: Manual array to collection copy +manual.array.to.collection.copy.problem.descriptor: + text: 'Manual array to collection copy #loc' +manual.array.to.collection.copy.replace.quickfix: + text: Replace with 'Collections.addAll(...,...)' +map.replaceable.by.enum.map.display.name: + text: '''Map'' can be replaced with ''EnumMap''' +map.replaceable.by.enum.map.problem.descriptor: + text: '#ref can be replaced with ''EnumMap'' #loc' +marker.interface.display.name: + text: Marker interface +marker.interface.problem.descriptor: + text: 'Marker interface #ref #loc' +math.random.cast.to.int.display.name: + text: '''Math.random()'' cast to ''int''' +math.random.cast.to.int.problem.descriptor: + text: '#ref cast to ''int'' is always rounded down to ''0'' #loc' +math.random.cast.to.int.quickfix: + text: Add parentheses to perform multiplication before cast +meta.annotation.without.runtime.retention: + text: Non-runtime annotation to be used by reflection +method.call.in.loop.condition.display.name: + text: Method call in loop condition +method.call.in.loop.condition.problem.descriptor: + text: 'Call to method #ref() in loop condition #loc' +method.can.be.variable.arity.method.display.name: + text: Method can have varargs parameter +method.can.be.variable.arity.method.ignore.all.primitive.arrays.option: + text: Ignore all primitive array types +method.can.be.variable.arity.method.ignore.byte.short.option: + text: Ignore parameters with type byte[] or short[] +method.can.be.variable.arity.method.ignore.multidimensional.arrays.option: + text: Ignore multidimensional array parameters +method.can.be.variable.arity.method.ignore.multiple.arrays.option: + text: Ignore methods with multiple array parameters +method.can.be.variable.arity.method.ignore.overriding.methods: + text: Ignore methods overriding a super method +method.can.be.variable.arity.method.problem.descriptor: + text: '#ref() can be converted to varargs method #loc' +method.complexity.limit.option: + text: 'Method complexity limit:' +method.count.ignore.getters.setters.option: + text: '&Ignore simple getter and setter methods' +method.count.limit.option: + text: 'Method count limit:' +method.coupling.display.name: + text: Overly coupled method +method.coupling.limit.option: + text: 'Method coupling limit:' +method.coupling.problem.descriptor: + text: '#ref is overly coupled (# referenced classes = {0}) #loc' +method.may.be.static.display.name: + text: Method can be made 'static' +method.may.be.static.empty.option: + text: Ignore empty methods +method.may.be.static.only.option: + text: Only check 'private' or 'final' methods +method.may.be.static.problem.descriptor: + text: 'Method #ref() may be ''static'' #loc' +method.may.be.synchronized.display.name: + text: Method with single 'synchronized' block can be replaced with 'synchronized' method +method.may.be.synchronized.problem.descriptor: + text: 'Method #ref() with synchronized block can be synchronized method #loc' +method.may.be.synchronized.quickfix: + text: Make method synchronized and remove synchronized block +method.missing.return.statement.display.name: + text: Method contains logic but is missing a 'return' statement +method.missing.return.statement.problem.descriptor: + text: Method #ref contains logic but is missing a 'return' statement +method.name.pattern: + text: Method name pattern +method.name.regex: + text: Method Name Regex +method.name.same.as.class.name.display.name: + text: Method name same as class name +method.name.same.as.class.name.problem.descriptor: + text: 'Method name #ref is the same as its class name #loc' +method.name.same.as.parent.name.display.name: + text: Method name same as parent class name +method.name.same.as.parent.name.problem.descriptor: + text: 'Method name #ref is the same as its parent class name #loc' +method.names.differ.only.by.case.display.name: + text: Method names differing only by case +method.names.differ.only.by.case.ignore.override.option: + text: Ignore if method is override of super method +method.names.differ.only.by.case.problem.descriptor: + text: 'Method name #ref and method name ''''{0}'''' differ only by case #loc' +method.only.used.from.inner.class.display.name: + text: Private method only used from inner class +method.only.used.from.inner.class.ignore.option: + text: Ignore methods accessed from an &anonymous class +method.only.used.from.inner.class.problem.descriptor: + text: 'Method #ref()#loc is only used from inner class ''''{0}'''' #loc' +method.only.used.from.inner.class.problem.descriptor.anonymous.extending: + text: 'Method #ref()#loc is only used from an anonymous class extending ''''{0}'''' #loc' +method.only.used.from.inner.class.problem.descriptor.anonymous.implementing: + text: 'Method #ref()#loc is only used from an anonymous class implementing ''''{0}'''' #loc' +method.only.used.from.test.code.problem.descriptor: + text: 'method #ref() only called from test code #loc' +method.overloads.display.name: + text: Possibly unintended overload of method from superclass +method.overloads.problem.descriptor: + text: 'Method #ref() overloads a compatible method of a superclass, when overriding might have been intended #loc' +method.overloads.report.incompatible.option: + text: Report even if parameter types are not compatible +method.overrides.inaccessible.method.display.name: + text: Method overrides inaccessible method of superclass +method.overrides.package.local.method.display.name: + text: Method overrides package-private method of superclass located in other package +method.overrides.package.local.method.problem.descriptor: + text: 'Method #ref() overrides a package-private method of a superclass located in another package #loc' +method.overrides.private.display.name: + text: Method overrides 'private' method of superclass +method.overrides.private.display.name.problem.descriptor: + text: 'Method #ref() overrides a ''private'' method of a superclass #loc' +method.overrides.static.display.name: + text: Method tries to override 'static' method of superclass +method.overrides.static.problem.descriptor: + text: 'Method #ref() tries to override a static method of a superclass #loc' +method.ref.can.be.replaced.with.lambda.name: + text: Method reference can be replaced with lambda +method.ref.can.be.replaced.with.lambda.quickfix: + text: Replace method reference with lambda +method.return.always.constant.display.name: + text: Method returns per-class constant +method.return.always.constant.problem.descriptor: + text: Method #ref() and all its derivables always return constants +method.return.concrete.class.display.name: + text: Method return of concrete class +method.return.concrete.class.problem.descriptor: + text: 'Method returns a concrete class #ref #loc' +method.return.of.concrete.class.option: + text: Ignore methods whose return type is an abstract class +method.with.multiple.loops.display.name: + text: Method with multiple loops +method.with.multiple.loops.problem.descriptor: + text: '#ref contains {0} loops #loc' +mismatched.read.write.array.display.name: + text: Mismatched read and write of array +mismatched.read.write.array.problem.descriptor.read.not.write: + text: 'Contents of array #ref are read, but never written to #loc' +mismatched.read.write.array.problem.descriptor.write.not.read: + text: 'Contents of array #ref are written to, but never read #loc' +mismatched.string.builder.queried.problem.descriptor: + text: 'Contents of {0} #ref are queried, but never updated #loc' +mismatched.string.builder.query.update.display.name: + text: Mismatched query and update of 'StringBuilder' +mismatched.string.builder.updated.problem.descriptor: + text: 'Contents of {0} #ref are updated, but never queried #loc' +mismatched.update.collection.display.name: + text: Mismatched query and update of collection +mismatched.update.collection.problem.description.queried.not.updated: + text: 'Contents of collection #ref are queried, but never updated #loc' +mismatched.update.collection.problem.descriptor.updated.not.queried: + text: 'Contents of collection #ref are updated, but never queried #loc' +misordered.assert.equals.arguments.display.name: + text: Misordered 'assertEquals()' arguments +misordered.assert.equals.arguments.flip.quickfix: + text: Flip compared arguments +misordered.assert.equals.arguments.problem.descriptor: + text: 'Arguments to #ref() in wrong order #loc' +misordered.assert.equals.parameters.display.name: + text: Misordered 'assertEquals()' arguments +misordered.assert.equals.parameters.flip.quickfix: + text: Flip compared arguments +misordered.assert.equals.parameters.problem.descriptor: + text: 'Arguments to #ref() in wrong order #loc' +missing.deprecated.annotation.add.quickfix: + text: Add '@Deprecated' annotation +missing.deprecated.annotation.display.name: + text: Missing '@Deprecated' annotation +missing.deprecated.annotation.problem.descriptor: + text: 'Missing ''@Deprecated'' annotation #loc' +missing.deprecated.tag.option: + text: Warn on missing @deprecated Javadoc tag explanation +missing.deprecated.tag.problem.descriptor: + text: 'Missing ''@deprecated'' Javadoc tag explanation #loc' +missing.override.annotation.add.quickfix: + text: Add @Override annotation +missing.override.annotation.display.name: + text: Missing '@Override' annotation +missing.override.annotation.problem.descriptor: + text: 'Missing ''@Override'' annotation on #ref() #loc' +missing.package.html.problem.descriptor: + text: Package ''{0}'' is missing a package.html file +missing.package.info.display.name: + text: Missing 'package-info.java' +missing.package.info.problem.descriptor: + text: Package ''{0}'' is missing a package-info.java file +missorted.modifiers.display.name: + text: Missorted modifiers +missorted.modifiers.problem.descriptor: + text: 'Missorted modifiers #ref #loc' +missorted.modifiers.require.option: + text: Require annotations to be sorted before keywords +missorted.modifiers.sort.quickfix: + text: Sort modifiers +misspelled.compareto.display.name: + text: '''compareto()'' instead of ''compareTo()''' +misspelled.compareto.problem.descriptor: + text: '#ref() method should probably be ''compareTo()'' #loc' +misspelled.equals.display.name: + text: '''equal()'' instead of ''equals()''' +misspelled.equals.problem.descriptor: + text: '#ref() method should probably be ''equals()'' #loc' +misspelled.hashcode.display.name: + text: '''hashcode()'' instead of ''hashCode()''' +misspelled.hashcode.problem.descriptor: + text: '#ref() should probably be ''hashCode()'' #loc' +misspelled.set.up.display.name: + text: '''setup()'' instead of ''setUp()''' +misspelled.set.up.problem.descriptor: + text: '#ref() probably be ''setUp()'' #loc' +misspelled.tear.down.display.name: + text: '''teardown()'' instead of ''tearDown()''' +misspelled.tear.down.problem.descriptor: + text: '#ref() method should probably be ''tearDown()'' #loc' +misspelled.tostring.display.name: + text: '''tostring()'' instead of ''toString()''' +misspelled.tostring.problem.descriptor: + text: '#ref() method should probably be ''toString()'' #loc' +module.with.too.few.classes.display.name: + text: Module with too few classes +module.with.too.few.classes.min.option: + text: 'Minimum number of classes:' +module.with.too.few.classes.problem.descriptor: + text: Module ''{0}'' contains too few classes ({1} < {2}) +module.with.too.many.classes.display.name: + text: Module with too many classes +module.with.too.many.classes.max.option: + text: 'Maximum number of classes:' +module.with.too.many.classes.problem.descriptor: + text: Module ''{0}'' contains too many classes ({1} > {2}) +move.anonymous.to.inner.quickfix: + text: Convert to named inner class +move.class.quickfix: + text: Move class +multi.catch.can.be.split.name: + text: Multi-catch can be split into separate catch blocks +multi.catch.can.be.split.quickfix: + text: Split multi-catch into separate 'catch' blocks +multiple.declaration.display.name: + text: Multiple variables in one declaration +multiple.declaration.option: + text: Ignore 'for' loop declarations +multiple.declaration.problem.descriptor: + text: 'Multiple variables in one declaration #loc' +multiple.exceptions.declared.on.test.method.display.name: + text: Multiple exceptions declared on test method +multiple.exceptions.declared.on.test.method.problem.descriptor: + text: '#ref could be replaced with ''throws Exception'' #loc' +multiple.exceptions.declared.on.test.method.quickfix: + text: Replace with 'throws Exception' +multiple.loggers.display.name: + text: Class with multiple loggers +multiple.loggers.problem.descriptor: + text: 'Class #ref declares multiple loggers #loc' +multiple.return.points.per.method.display.name: + text: Method with multiple return points +multiple.return.points.per.method.problem.descriptor: + text: '#ref has {0} return points #loc' +multiple.top.level.classes.in.file.display.name: + text: Multiple top level classes in single file +multiple.top.level.classes.in.file.problem.descriptor: + text: Multiple top level classes in file +multiple.typed.declaration.display.name: + text: Variables of different types in one declaration +multiple.typed.declaration.problem.descriptor: + text: 'Variables of different types in one declaration #loc' +multiply.or.divide.by.power.of.two.display.name: + text: Multiplication or division by power of two +multiply.or.divide.by.power.of.two.divide.option: + text: Check divisions by a power of two also +multiply.or.divide.by.power.of.two.replace.quickfix: + text: Replace with shift +naked.notify.display.name: + text: '''notify()'' or ''notifyAll()'' without corresponding state change' +naked.notify.problem.descriptor: + text: 'Call to #ref() without corresponding state change #loc' +naming.convention.problem.descriptor.long: + text: '{0} name #ref is too long ({1} > {2}) #loc' +naming.convention.problem.descriptor.regex.mismatch: + text: '{0} name #ref doesn''''t match regex ''''{1}'''' #loc' +naming.convention.problem.descriptor.short: + text: '{0} name #ref is too short ({1} < {2}) #loc' +native.method.display.name: + text: Native method +native.method.naming.convention.display.name: + text: '''native'' method naming convention' +native.method.naming.convention.element.description: + text: '''native'' method' +native.method.problem.descriptor: + text: 'Methods declared #ref are non-portable #loc' +negated.conditional.display.name: + text: Conditional expression with negated condition +negated.conditional.expression.display.name: + text: Negated conditional expression +negated.conditional.expression.problem.descriptor: + text: 'Negating conditional expression #loc' +negated.conditional.expression.quickfix: + text: Remove negation +negated.conditional.ignore.option: + text: Ignore '!= null' comparisons +negated.conditional.invert.quickfix: + text: Invert condition +negated.conditional.problem.descriptor: + text: 'Conditional expression with negated condition #loc' +negated.equality.expression.display.name: + text: Negated equality expression +negated.equality.expression.problem.descriptor: + text: 'Negating ''''{0}'''' #loc' +negated.equality.expression.quickfix: + text: Remove negation +negated.if.else.display.name: + text: '''if'' statement with negated condition' +negated.if.else.ignore.negated.null.option: + text: Ignore '!= null' comparisons +negated.if.else.ignore.negated.zero.option: + text: Ignore '!= 0' comparisons +negated.if.else.invert.quickfix: + text: Invert 'if' condition +negated.if.else.problem.descriptor: + text: '#ref statement with negated condition #loc' +negatively.named.boolean.variable.display.name: + text: Negatively named boolean variable +negatively.named.boolean.variable.problem.descriptor: + text: 'Boolean variable #ref is negatively named #loc' +nested.assignment.display.name: + text: Result of assignment used +nested.assignment.problem.descriptor: + text: 'Result of assignment expression used #loc' +nested.conditional.expression.display.name: + text: Nested conditional expression +nested.conditional.expression.problem.descriptor: + text: 'Nested conditional expression #ref #loc' +nested.method.call.display.name: + text: Nested method call +nested.method.call.ignore.option: + text: Ignore nested method calls in field initializers +nested.method.call.problem.descriptor: + text: 'Nested method call #ref() #loc' +nested.switch.statement.display.name: + text: Nested 'switch' statement +nested.switch.statement.problem.descriptor: + text: 'Nested #ref statement #loc' +nested.synchronized.statement.display.name: + text: Nested 'synchronized' statement +nested.synchronized.statement.problem.descriptor: + text: 'Nested #ref statement #loc' +nested.try.statement.display.name: + text: Nested 'try' statement +nested.try.statement.problem.descriptor: + text: 'Nested #ref statement #loc' +nesting.depth.display.name: + text: Overly nested method +nesting.depth.limit.option: + text: 'Nesting depth limit:' +nesting.depth.problem.descriptor: + text: '#ref is overly nested (maximum nesting depth = {0}) #loc' +new.exception.without.arguments.display.name: + text: Exception constructor called without arguments +new.exception.without.arguments.ignore.option: + text: Ignore for exceptions that have no constructors with parameters +new.exception.without.arguments.problem.descriptor: + text: 'new #ref() without arguments #loc' +new.string.buffer.replaceable.by.string.problem.descriptor: + text: '#ref can be replaced with ''String'' #loc' +new.string.buffer.with.char.argument.display.name: + text: StringBuffer constructor call with 'char' argument +new.string.buffer.with.char.argument.problem.descriptor: + text: 'new #ref() with argument of type ''char'' #loc' +new.string.buffer.with.char.argument.quickfix: + text: Replace char argument with String literal +no.logger.display.name: + text: Class without logger +no.logger.problem.descriptor: + text: 'Class #ref does not declare a logger #loc' +non.atomic.operation.on.volatile.field.display.name: + text: Non-atomic operation on 'volatile' field +non.atomic.operation.on.volatile.field.problem.descriptor: + text: 'Non-atomic operation on volatile field #ref #loc' +non.boolean.method.name.must.not.start.with.question.display.name: + text: Non-boolean method name must not start with question word +non.boolean.method.name.must.not.start.with.question.problem.descriptor: + text: 'Non-boolean method name #ref starts with a question word #loc' +non.comment.source.statements.display.name: + text: Overly long method +non.comment.source.statements.limit.option: + text: 'Non-comment source statements limit:' +non.comment.source.statements.problem.descriptor: + text: '#ref is too long (# Non-comment source statements = {0}) #loc' +non.constant.logger.display.name: + text: Non-constant logger +non.constant.logger.problem.descriptor: + text: 'Non-constant logger field #ref #loc' +non.exception.name.ends.with.exception.display.name: + text: Non-exception class name ends with 'Exception' +non.exception.name.ends.with.exception.problem.descriptor: + text: 'Non-exception class name #ref ends with ''Exception'' #loc' +non.exception.name.ends.with.exception.quickfix: + text: Make ''{0}'' extend ''java.lang.Exception'' +non.final.clone.display.name: + text: Non-final 'clone()' in secure context +non.final.clone.problem.descriptor: + text: 'Non-final #ref() method, compromising security #loc' +non.final.field.compareto.display.name: + text: Non-final field referenced in 'compareTo()' +non.final.field.compareto.problem.descriptor: + text: 'Non-final field #ref accessed in ''compareTo()'' #loc' +non.final.field.in.enum.display.name: + text: Non-final field in 'enum' +non.final.field.in.enum.problem.descriptor: + text: 'Non-final field #ref in enum ''''{0}'''' #loc' +non.final.field.in.equals.display.name: + text: Non-final field referenced in 'equals()' +non.final.field.in.equals.problem.descriptor: + text: 'Non-final field #ref accessed in ''equals()'' #loc' +non.final.field.in.hashcode.display.name: + text: Non-final field referenced in 'hashCode()' +non.final.field.in.hashcode.problem.descriptor: + text: 'Non-final field #ref accessed in ''hashCode()'' #loc' +non.final.field.of.exception.display.name: + text: Non-final field of 'Exception' class +non.final.field.of.exception.problem.descriptor: + text: 'Non-final field #ref of exception class #loc' +non.final.static.variable.initialization.display.name: + text: Non-final static field is used during class initialization +non.final.static.variable.initialization.problem.descriptor: + text: 'Non-final static field #ref used during class initialization #loc' +non.final.utility.class.display.name: + text: Utility class is not 'final' +non.final.utility.class.problem.descriptor: + text: 'Utility class #ref is not ''final'' #loc' +non.protected.constructor.in.abstract.class.display.name: + text: '''public'' constructor in ''abstract'' class' +non.protected.constructor.in.abstract.class.ignore.option: + text: Ignore for non-public classes +non.protected.constructor.in.abstract.class.problem.descriptor: + text: 'Constructor #ref() is not declared ''protected'' in ''abstract'' class #loc' +non.public.clone.display.name: + text: '''clone()'' method not ''public''' +non.public.clone.problem.descriptor: + text: '#ref() method not ''public'' #loc' +non.reproducible.math.call.display.name: + text: Non-reproducible call to 'Math' +non.reproducible.math.call.problem.descriptor: + text: 'Math.#ref() may produce non-reproducible results #loc' +non.reproducible.math.call.replace.quickfix: + text: Replace with 'StrictMath' call +non.serializable.ainterface.with.serialversionuid.problem.descriptor: + text: 'Non-serializable @interface #ref defines a ''serialVersionUID'' field #loc' +non.serializable.anonymous.with.readwriteobject.problem.descriptor.both: + text: 'Non-serializable anonymous class extending #ref defines ''readObject()'' and ''writeObject()'' #loc' +non.serializable.anonymous.with.readwriteobject.problem.descriptor.read: + text: 'Non-serializable anonymous class extending #ref defines ''readObject()'' #loc' +non.serializable.anonymous.with.readwriteobject.problem.descriptor.write: + text: 'Non-serializable anonymous class extending #ref defines ''writeObject()'' #loc' +non.serializable.anonymous.with.serialversionuid.problem.descriptor: + text: 'Non-serializable anonymous class derived from #ref defines a ''serialVersionUID'' field #loc' +non.serializable.class.with.readwriteobject.display.name: + text: Non-serializable class with 'readObject()' or 'writeObject()' +non.serializable.class.with.readwriteobject.problem.descriptor.both: + text: 'Non-serializable class #ref defines ''readObject()'' and ''writeObject()'' #loc' +non.serializable.class.with.readwriteobject.problem.descriptor.read: + text: 'Non-serializable class #ref defines ''readObject()'' #loc' +non.serializable.class.with.readwriteobject.problem.descriptor.write: + text: 'Non-serializable class #ref defines ''writeObject()'' #loc' +non.serializable.class.with.serialversionuid.problem.descriptor: + text: 'Non-serializable class #ref defines a ''serialVersionUID'' field #loc' +non.serializable.field.in.serializable.class.display.name: + text: Non-serializable field in a 'Serializable' class +non.serializable.field.in.serializable.class.problem.descriptor: + text: 'Non-serializable field ''#ref'' in Serializable class #loc' +non.serializable.interface.with.serialversionuid.problem.descriptor: + text: 'Non-serializable interface #ref defines a ''serialVersionUID'' field #loc' +non.serializable.object.bound.to.http.session.display.name: + text: Non-serializable object bound to 'HttpSession' +non.serializable.object.bound.to.http.session.problem.descriptor: + text: 'Non-serializable object bound to HttpSession #loc' +non.serializable.object.passed.to.object.stream.display.name: + text: Non-serializable object passed to 'ObjectOutputStream' +non.serializable.object.passed.to.object.stream.problem.descriptor: + text: 'Non-serializable object passed to ObjectOutputStream #loc' +non.serializable.with.serialversionuid.display.name: + text: Non-serializable class with 'serialVersionUID' +non.serializable.with.serialversionuid.remove.quickfix: + text: Remove 'serialVersionUID' field +non.short.circuit.boolean.expression.display.name: + text: Non-short-circuit boolean expression +non.short.circuit.boolean.expression.problem.descriptor: + text: 'Non-short-circuit boolean expression #ref #loc' +non.short.circuit.boolean.expression.replace.quickfix: + text: Replace with short circuit expression +non.static.inner.class.in.secure.context.display.name: + text: Non-'static' inner class in secure context +non.static.inner.class.in.secure.context.problem.descriptor: + text: 'Non-''static'' inner class #ref, compromising security #loc' +non.synchronized.method.overrides.synchronized.method.display.name: + text: Unsynchronized method overrides 'synchronized' method +non.synchronized.method.overrides.synchronized.method.problem.descriptor: + text: 'Unsynchronized method #ref() overrides synchronized method #loc' +non.thread.safe.lazy.initialization.display.name: + text: Unsafe lazy initialization of 'static' field +non.thread.safe.lazy.initialization.problem.descriptor: + text: 'Lazy initialization of ''static'' field #ref is not thread-safe #loc' +none: + text: none +noop.method.in.abstract.class.display.name: + text: No-op method in 'abstract' class +noop.method.in.abstract.class.problem.descriptor: + text: 'No-op Method #ref() should be made abstract #loc' +normalize.declaration.quickfix: + text: Split into separate declarations +not.equals.to.equality.quickfix: + text: Replace '!equals()' with '!=' +notify.called.on.condition.display.name: + text: '''notify()'' or ''notifyAll()'' called on ''java.util.concurrent.locks.Condition'' object' +notify.called.on.condition.problem.descriptor: + text: 'Call to #ref() on Condition object #loc' +notify.not.in.synchronized.context.display.name: + text: '''notify()'' or ''notifyAll()'' while not synchronized' +notify.not.in.synchronized.context.problem.descriptor: + text: 'Call to #ref() is made outside of a synchronized context #loc' +notify.without.corresponding.wait.display.name: + text: '''notify()'' without corresponding ''wait()''' +notify.without.corresponding.wait.problem.descriptor: + text: 'Call to #ref() without corresponding wait() #loc' +null.argument.to.var.arg.method.display.name: + text: Confusing argument to varargs method +null.argument.to.var.arg.method.problem.descriptor: + text: 'Confusing argument #ref, unclear if a varargs or non-varargs call is desired #loc' +null.thrown.display.name: + text: '''null'' thrown' +null.thrown.problem.descriptor: + text: '#ref thrown #loc' +null.thrown.quickfix: + text: Replace with 'new NullPointerException()' +number.comparison.display.name: + text: Number comparison using '==', instead of 'equals()' +number.comparison.problem.descriptor: + text: 'Number objects are compared using #ref, not ''equals()'' #loc' +object.allocation.in.loop.display.name: + text: Object allocation in loop +object.allocation.in.loop.problem.descriptor: + text: 'Object allocation new #ref() in loop #loc' +object.comparison.display.name: + text: Object comparison using '==', instead of 'equals()' +object.comparison.enumerated.ignore.option: + text: Ignore '==' between enum variables +object.comparison.klass.ignore.option: + text: Ignore '==' on 'java.lang.Class' objects +object.comparison.problem.description: + text: 'Object values are compared using #ref, not ''equals()'' #loc' +object.comparison.replace.quickfix: + text: Replace with 'equals()' +object.equality.ignore.between.objects.of.a.type.with.only.private.constructors.option: + text: Ignore '==' between objects of a type with only 'private' constructors +object.equals.null.display.name: + text: Object.equals(null) +object.equals.null.problem.descriptor: + text: '.equals(#ref) is probably not what was intended #loc' +object.instantiation.inside.equals.or.hashcode.display.name: + text: Object instantiation inside 'equals()' or 'hashCode()' +object.instantiation.inside.equals.or.hashcode.problem.descriptor: + text: 'Object instantiation inside ''''{0}()'''' #loc' +object.instantiation.inside.equals.or.hashcode.problem.descriptor2: + text: Object instantiation inside ''{0}()'' ({1})#loc +object.notify.display.name: + text: Call to 'notify()' instead of 'notifyAll()' +object.notify.problem.descriptor: + text: '#ref should probably be replaced with ''notifyAll()'' #loc' +object.notify.replace.quickfix: + text: Replace with 'notifyAll()' +octal.and.decimal.integers.in.same.array.display.name: + text: Octal and decimal integers in same array +octal.and.decimal.integers.in.same.array.problem.descriptor: + text: 'Octal and decimal integers in the same array initializer #loc' +octal.literal.display.name: + text: Octal integer +octal.literal.problem.descriptor: + text: 'Octal integer #ref #loc' +only.report.public.methods.option: + text: Only report 'public' methods +only.report.qualified.static.usages.option: + text: Only report qualified static access from a static context +only.report.static.methods: + text: '&Only report ''static'' methods' +only.warn.on.protected.clone.methods: + text: Only warn on 'protected' clone methods +only.warn.on.public.clone.methods: + text: Only warn on 'public' clone methods +only.weaken.to.an.interface: + text: Only weaken to an interface +optional.contains.array.problem.descriptor: + text: '''Optional'' contains array #ref' +optional.contains.collection.display.name: + text: '''Optional'' contains array or collection' +optional.contains.collection.problem.descriptor: + text: '''Optional'' contains collection #ref' +optional.used.as.field.or.parameter.type.display.name: + text: '''Optional'' used as field or parameter type' +optional.used.as.field.type.problem.descriptor: + text: #ref used as type for field ''{0}'' +optional.used.as.parameter.type.problem.descriptor: + text: #ref used as type for parameter ''{0}'' +orred.not.equal.expression.display.name: + text: Identical reference in '!=' expression on both sides of '||' expression +orred.not.equal.expression.problem.descriptor: + text: '#ref is always true #loc' +orred.not.equal.expression.quickfix: + text: Change '||' to '\&\&' +overloaded.methods.with.same.number.parameters.display.name: + text: Overloaded methods with same number of parameters +overloaded.methods.with.same.number.parameters.option: + text: Ignore overloaded methods whose parameter types are definitely incompatible +overloaded.methods.with.same.number.parameters.problem.descriptor: + text: 'Multiple methods named #ref with the same number of parameters #loc' +overloaded.vararg.constructor.problem.descriptor: + text: 'Overloaded varargs constructor #ref() #loc' +overloaded.vararg.method.display.name: + text: Overloaded varargs method +overloaded.vararg.method.problem.descriptor: + text: 'Overloaded varargs method #ref() #loc' +overly.broad.throws.clause.display.name: + text: Overly broad 'throws' clause +overly.broad.throws.clause.ignore.thrown.option: + text: Ignore exceptions which &hide others but are themselves thrown +overly.broad.throws.clause.problem.descriptor1: + text: 'throws #ref is too broad, masking exception ''''{0}'''' #loc' +overly.broad.throws.clause.problem.descriptor2: + text: 'throws #ref is too broad, masking exceptions ''''{0}'''' and ''''{1}'''' #loc' +overly.broad.throws.clause.quickfix1: + text: Add specific exceptions +overly.broad.throws.clause.quickfix2: + text: Replace with specific exceptions +overly.complex.anonymous.inner.class.display.name: + text: Overly complex anonymous class +overly.complex.anonymous.inner.class.problem.descriptor: + text: 'Overly complex anonymous class (cyclomatic complexity = {0}) #loc' +overly.complex.arithmetic.expression.display.name: + text: Overly complex arithmetic expression +overly.complex.arithmetic.expression.max.number.option: + text: 'Maximum number of terms:' +overly.complex.arithmetic.expression.problem.descriptor: + text: 'Overly complex arithmetic expression #loc' +overly.complex.boolean.expression.display.name: + text: Overly complex boolean expression +overly.complex.boolean.expression.ignore.option: + text: Ignore pure conjunctions and disjunctions +overly.complex.boolean.expression.max.terms.option: + text: 'Maximum number of terms:' +overly.complex.boolean.expression.problem.descriptor: + text: 'Overly complex boolean expression ({0} terms) #loc' +overly.complex.class.display.name: + text: Overly complex class +overly.complex.class.problem.descriptor: + text: 'Overly complex class #ref (cyclomatic complexity = {0}) #loc' +overly.coupled.class.class.coupling.limit.option: + text: 'Class coupling limit:' +overly.coupled.class.display.name: + text: Overly coupled class +overly.coupled.class.problem.descriptor: + text: '#ref is overly coupled (dependencies = {0}) #loc' +overly.long.lambda.display.name: + text: Overly long lambda expression +overly.long.lambda.problem.descriptor: + text: 'Lambda expression is too long (# Non-comment source statements = {0}) #loc' +overly.strong.type.cast.display.name: + text: Overly strong type cast +overly.strong.type.cast.ignore.in.matching.instanceof.option: + text: Ignore casts with a matching instanceof expression +overly.strong.type.cast.problem.descriptor: + text: 'Cast to #ref can be weakened to ''''{0}'''' #loc' +overly.strong.type.cast.weaken.quickfix: + text: Weaken overly strong cast +overridable.method.call.in.constructor.display.name: + text: Overridable method called during object construction +overridable.method.call.in.constructor.problem.descriptor: + text: 'Call to overridable method #ref() during object construction #loc' +overridden.method.call.in.constructor.display.name: + text: Overridden method called during object construction +overridden.method.call.in.constructor.problem.descriptor: + text: 'Call to overridden method #ref() during object construction #loc' +package.dot.html.convert.command: + text: package.html to package-info.java conversion +package.dot.html.delete.command: + text: package.html deletion +package.dot.html.may.be.package.info.convert.quickfix: + text: Convert to 'package-info.java' +package.dot.html.may.be.package.info.delete.quickfix: + text: Delete 'package.html' +package.dot.html.may.be.package.info.display.name: + text: '''package.html'' may be converted to ''package-info.java''' +package.dot.html.may.be.package.info.exists.problem.descriptor: + text: package.html is ignored because package-info.java exists +package.dot.html.may.be.package.info.problem.descriptor: + text: package.html may be converted to package-info.java +package.in.multiple.modules.display.name: + text: Package with classes in multiple modules +package.in.multiple.modules.problem.descriptor: + text: Package ''{0}'' has classes in multiple modules +package.info.java.without.package.display.name: + text: '''package-info.java'' without ''package'' statement' +package.info.without.package.family.quickfix: + text: Add package statement +package.info.without.package.problem.descriptor: + text: '''package-info.java'' does not have a ''package'' statement' +package.info.without.package.quickfix: + text: Add ''package {0};'' +package.local.field.not.accessible: + text: Package-private field ''{0}'' is not accessible from here +package.local.private: + text: package-private \& private +package.naming.convention.display.name: + text: Package naming convention +package.naming.convention.problem.descriptor.long: + text: Package name {0} is too long +package.naming.convention.problem.descriptor.regex.mismatch: + text: Package name {0} doesn''t match regex ''{1}'' +package.naming.convention.problem.descriptor.short: + text: Package name {0} is too short +package.visible.field.display.name: + text: Package-visible field +package.visible.field.problem.descriptor: + text: 'Package-visible field #ref #loc' +package.visible.inner.class.display.name: + text: Package-visible nested class +package.visible.inner.class.ignore.enum.option: + text: Ignore package-visible inner enums +package.visible.inner.class.ignore.interface.option: + text: Ignore package-visible inner interfaces +package.visible.inner.class.problem.descriptor: + text: 'Package-visible nested class #ref #loc' +package.with.too.few.classes.display.name: + text: Package with too few classes +package.with.too.few.classes.min.option: + text: 'Minimum number of classes:' +package.with.too.few.classes.problem.descriptor: + text: Package ''{0}'' contains too few classes ({1} < {2}) +package.with.too.many.classes.display.name: + text: Package with too many classes +package.with.too.many.classes.max.option: + text: 'Maximum number of classes:' +package.with.too.many.classes.problem.descriptor: + text: Package ''{0}'' contains too many classes ({1} > {2}) +parameter.hides.member.variable.display.name: + text: Parameter hides field +parameter.hides.member.variable.ignore.abstract.methods.option: + text: Ignore for abstract methods +parameter.hides.member.variable.ignore.constructors.option: + text: Ignore for constructors +parameter.hides.member.variable.ignore.setters.option: + text: Ignore for property setters +parameter.hides.member.variable.ignore.static.parameters.option: + text: Ignore for static method parameters hiding instance fields +parameter.hides.member.variable.ignore.superclass.option: + text: Ignore superclass fields not visible from subclass +parameter.hides.member.variable.problem.descriptor: + text: 'Parameter #ref hides field in class ''''{0}'''' #loc' +parameter.limit.option: + text: 'Parameter limit:' +parameter.name.differs.from.overridden.parameter.display.name: + text: Parameter name differs from parameter in overridden method +parameter.name.differs.from.overridden.parameter.ignore.character.option: + text: Ignore if overridden parameter contains only one character +parameter.name.differs.from.overridden.parameter.ignore.library.option: + text: Ignore if overridden parameter is from a library +parameter.name.differs.from.overridden.parameter.problem.descriptor: + text: 'Parameter name #ref is different from parameter ''''{0}'''' overridden #loc' +parameter.naming.convention.display.name: + text: Method parameter naming convention +parameter.naming.convention.element.description: + text: Parameter +parameter.naming.convention.problem.descriptor.long: + text: 'Parameter name #ref is too long #loc' +parameter.naming.convention.problem.descriptor.regex.mismatch: + text: 'Parameter name #ref doesn''''t match regex ''''{0}'''' #loc' +parameter.naming.convention.problem.descriptor.short: + text: 'Parameter name #ref is too short #loc' +parameter.of.concrete.class.option: + text: Ignore parameters whose type is abstract class +parameter.type.prevents.overriding.display.name: + text: Parameter type prevents overriding +parameter.type.prevents.overriding.family.quickfix: + text: Change type of parameter +parameter.type.prevents.overriding.problem.descriptor: + text: 'Parameter type #ref is located in ''''{0}'''' while super method parameter type is located in ''''{1}'''' preventing overriding #loc' +parameter.type.prevents.overriding.quickfix: + text: Change type of parameter to ''{0}'' +parameters.per.constructor.display.name: + text: Constructor with too many parameters +parameters.per.constructor.problem.descriptor: + text: '#ref() has too many parameters (num parameters = {0}) #loc' +parameters.per.method.display.name: + text: Method with too many parameters +parameters.per.method.problem.descriptor: + text: '#ref() has too many parameters (num parameters = {0}) #loc' +placeholder.count.matches.argument.count.display.name: + text: Number of placeholders does not match number of arguments in logging call +placeholder.count.matches.argument.count.fewer.problem.descriptor: + text: 'Fewer arguments provided ({0}) than placeholders specified ({1}) #loc' +placeholder.count.matches.argument.count.more.problem.descriptor: + text: 'More arguments provided ({0}) than placeholders specified ({1}) #loc' +pointless.arithmetic.expression.display.name: + text: Pointless arithmetic expression +pointless.bitwise.expression.display.name: + text: Pointless bitwise expression +pointless.bitwise.expression.ignore.option: + text: Ignore named constants in determining pointless expressions +pointless.bitwise.expression.simplify.quickfix: + text: Simplify +pointless.boolean.expression.display.name: + text: Pointless boolean expression +pointless.boolean.expression.ignore.option: + text: Ignore named constants in determining pointless expressions +pointless.indexof.comparison.always.false.problem.descriptor: + text: '#ref is always false #loc' +pointless.indexof.comparison.always.true.problem.descriptor: + text: '#ref is always true #loc' +pointless.indexof.comparison.display.name: + text: Pointless 'indexOf()' comparison +pointless.nullcheck.after.problem.descriptor: + text: Unnecessary 'null' check after 'instanceof' expression +pointless.nullcheck.display.name: + text: Unnecessary 'null' check before 'instanceof' expression +pointless.nullcheck.problem.descriptor: + text: Unnecessary 'null' check before 'instanceof' expression +pointless.nullcheck.simplify.quickfix: + text: Remove unnecessary ''{0}'' condition +possible.throw.from.finally.block.problem.descriptor: + text: '{0} might be thrown inside ''''finally'''' block #loc' +press.escape.to.remove.highlighting.message: + text: Press Escape to remove the highlighting +primitive.array.argument.to.var.arg.method.display.name: + text: Confusing primitive array argument to varargs method +primitive.array.argument.to.var.arg.method.problem.descriptor: + text: 'Confusing primitive array argument to varargs method #loc' +primitive.fields.ignore.option: + text: Ignore primitive fields +printstacktrace.call.display.name: + text: Call to 'printStackTrace()' +printstacktrace.call.problem.descriptor: + text: 'Call to #ref() should probably be replaced with more robust logging #loc' +private.field.not.accessible.problem.descriptor: + text: '''''private'''' field ''''{0}'''' is not accessible from here' +private.member.access.between.outer.and.inner.classes.display.name: + text: Synthetic accessor call +private.member.access.between.outer.and.inner.classes.make.constructor.package.local.quickfix: + text: Make ''{0}'' constructor package-private +private.member.access.between.outer.and.inner.classes.make.local.quickfix: + text: Make ''{0}'' package-private +private.member.access.between.outer.and.inner.classes.problem.descriptor: + text: 'Access to ''private'' member of class ''''{0}'''' requires synthetic accessor call #loc' +private.modifier: + text: private +problematic.varargs.method.display.name: + text: Non-varargs method overrides varargs method +problematic.varargs.method.override.problem.descriptor: + text: 'Non-varargs method #ref() overrides varargs method #loc' +properties.object.as.hashtable.display.name: + text: Use of 'Properties' object as a 'Hashtable' +properties.object.as.hashtable.get.quickfix: + text: Replace with call to 'getProperty()' +properties.object.as.hashtable.problem.descriptor: + text: 'Call to Hashtable.#ref() on properties object #loc' +properties.object.as.hashtable.set.quickfix: + text: Replace with call to 'setProperty()' +property.value.set.to.itself.display.name: + text: Property value set to itself +protected.field.display.name: + text: Protected field +protected.field.not.accessible.problem.descriptor: + text: '''''protected'''' field ''''{0}'''' is not accessible from here' +protected.field.problem.descriptor: + text: 'Protected field #ref #loc' +protected.inner.class.display.name: + text: Protected nested class +protected.inner.class.ignore.enum.option: + text: Ignore 'protected' inner enums +protected.inner.class.ignore.interface.option: + text: Ignore 'protected' inner interfaces +protected.inner.class.problem.descriptor: + text: 'Protected nested class #ref #loc' +protected.member.in.final.class.display.name: + text: '''protected'' member in ''final'' class' +protected.member.in.final.class.problem.descriptor: + text: 'Class member declared #ref in ''final'' class #loc' +protected.package.local.private: + text: protected, package-private \& private +public.constructor.display.name: + text: '''public'' constructor' +public.constructor.in.non.public.class.display.name: + text: '''public'' constructor in non-public class' +public.constructor.in.non.public.class.problem.descriptor: + text: 'Constructor is declared #ref in non-public class ''''{0}'''' #loc' +public.constructor.in.non.public.class.quickfix: + text: Make constructor ''{0}'' +public.constructor.problem.descriptor: + text: 'Public constructor #ref() #loc' +public.constructor.quickfix: + text: Replace constructor with factory method +public.default.constructor.problem.descriptor: + text: Class #ref has 'public' default constructor +public.field.accessed.in.synchronized.context.display.name: + text: Non-private field accessed in 'synchronized' context +public.field.accessed.in.synchronized.context.problem.descriptor: + text: 'Non-private field #ref accessed in synchronized context #loc' +public.field.display.name: + text: '''public'' field' +public.field.ignore.enum.type.fields.option: + text: Ignore 'public final' fields of an enum type +public.field.problem.descriptor: + text: '''public'' field #ref #loc' +public.inner.class.display.name: + text: '''public'' nested class' +public.inner.class.ignore.enum.option: + text: Ignore 'public' inner enums +public.inner.class.ignore.interface.option: + text: Ignore 'public' inner interfaces +public.inner.class.problem.descriptor: + text: '''public'' nested class #ref #loc' +public.method.not.in.interface.display.name: + text: '''public'' method not exposed in interface' +public.method.not.in.interface.option: + text: Ignore if the containing class does not implement a non-library interface +public.method.not.in.interface.problem.descriptor: + text: '''public'' method #ref() is not exposed via an interface #loc' +public.method.without.logging.display.name: + text: '''public'' method without logging' +public.method.without.logging.problem.descriptor: + text: '''public'' method #ref() has no logging call #loc' +public.static.array.field.display.name: + text: '''public static'' array field' +public.static.array.field.problem.descriptor: + text: '''public static'' array field #ref, compromising security #loc' +public.static.collection.field.display.name: + text: '''public static'' collection field' +public.static.collection.field.problem.descriptor: + text: '''public static'' collection field #ref, compromising security #loc' +query.column.name: + text: Query names start with +questionable.name.column.title: + text: Name +questionable.name.display.name: + text: Questionable name +questionable.name.problem.descriptor: + text: 'Questionable name #ref #loc' +random.double.for.random.integer.display.name: + text: Using 'Random.nextDouble()' to get random integer +random.double.for.random.integer.problem.descriptor: + text: 'Using Random.#ref to create random integer #loc' +random.double.for.random.integer.replace.quickfix: + text: Replace with 'nextInt()' +raw.use.of.parameterized.type.display.name: + text: Raw use of parameterized class +raw.use.of.parameterized.type.ignore.new.objects.option: + text: Ignore construction of new objects +raw.use.of.parameterized.type.ignore.overridden.parameter.option: + text: Ignore parameter types of overriding methods +raw.use.of.parameterized.type.ignore.type.casts.option: + text: Ignore type casts +raw.use.of.parameterized.type.ignore.uncompilable.option: + text: Ignore where a type parameter would not compile +raw.use.of.parameterized.type.problem.descriptor: + text: 'Raw use of parameterized class #ref #loc' +readobject.initialization.display.name: + text: Instance field may not be initialized by 'readObject()' +readobject.initialization.problem.descriptor: + text: 'Instance field #ref may not be initialized during ''readObject()'' call #loc' +readresolve.writereplace.protected.display.name: + text: '''readResolve()'' or ''writeReplace()'' not declared ''protected''' +readresolve.writereplace.protected.problem.descriptor: + text: '#ref() not declared ''protected'' #loc' +readwriteobject.private.display.name: + text: '''readObject()'' or ''writeObject()'' not declared ''private''' +readwriteobject.private.problem.descriptor: + text: '#ref not declared ''private'' #loc' +recordstore.opened.not.safely.closed.display.name: + text: '''RecordStore'' opened but not safely closed' +redundant.else.display.name: + text: Redundant 'else' +redundant.else.problem.descriptor: + text: '#ref branch may be unwrapped, as the ''if'' branch never completes normally #loc' +redundant.else.unwrap.quickfix: + text: Remove redundant 'else' +redundant.field.initialization.display.name: + text: Redundant field initialization +redundant.field.initialization.problem.descriptor: + text: 'Field initialization to #ref is redundant #loc' +redundant.field.initialization.remove.quickfix: + text: Remove initializer +redundant.implements.display.name: + text: Redundant interface declaration +redundant.implements.problem.descriptor: + text: 'Redundant interface declaration #ref #loc' +redundant.implements.remove.quickfix: + text: Remove redundant interface declaration +redundant.import.display.name: + text: Redundant import +redundant.import.problem.descriptor: + text: 'Redundant import #ref #loc' +redundant.local.variable.annotation.option: + text: Ignore variables which have an annotation +redundant.local.variable.display.name: + text: Redundant local variable +redundant.local.variable.ignore.option: + text: Ignore immediately returned or thrown variables +redundant.method.override.display.name: + text: Method is identical to its super method +redundant.method.override.problem.descriptor: + text: 'Method #ref() is identical to its super method #loc' +redundant.method.override.quickfix: + text: Remove redundant method +redundant.string.format.call.display.name: + text: Redundant call to 'String.format()' +redundant.string.format.call.problem.descriptor: + text: 'Redundant call to #ref #loc' +redundant.string.format.call.quickfix: + text: Remove redundant call to 'String.format()' +reflection.for.unavailable.annotation.display.name: + text: Reflective access to a source-only annotation +reflection.for.unavailable.annotation.problem.descriptor: + text: 'Annotation ''#ref'' is not retained for reflective access #loc' +refused.bequest.display.name: + text: Method does not call super method +refused.bequest.ignore.empty.super.methods.option: + text: Ignore empty super methods +refused.bequest.problem.descriptor: + text: 'Method #ref() does not call ''super.#ref()'' #loc' +remove.annotation.parameter.0.fix.name: + text: Remove annotation parameter ''{0}'' +remove.finally.block.quickfix: + text: Remove 'finally' block +remove.junit4.test.annotation.and.rename.quickfix: + text: Remove ''@Test'' annotation and rename to ''{0}'' +remove.junit4.test.annotation.quickfix: + text: Remove '@Test' annotation +remove.leading.zero.to.make.decimal.quickfix: + text: Remove leading zero to make decimal +remove.modifier.quickfix: + text: Remove ''{0}'' modifier +remove.try.catch.quickfix: + text: Remove 'try catch' statement +remove.try.finally.block.quickfix: + text: Remove 'try-finally' block +rename.catch.parameter.to.ignored: + text: Rename 'catch' parameter to 'ignored' +rename.quickfix: + text: Rename +renameto.quickfix: + text: Rename to ''{0}'' +replace.all.dot.display.name: + text: Call to String.replaceAll(".", ...) +replace.all.dot.problem.descriptor: + text: 'Call to String.#ref(".", ...) #loc' +replace.assertequals.quickfix: + text: Replace with ''{0}'' +replace.indexof.with.contains.quickfix: + text: Replace 'indexOf()' with 'contains()' +replace.inheritance.with.delegation.quickfix: + text: Replace inheritance with delegation +replace.reference.with.expression.quickfix: + text: Replace with ''{0}'' +replace.with: + text: Replace with ''{0}'' +replace.with.arrays.deep.equals: + text: Replace with 'Arrays.deepEquals()' +replace.with.arrays.equals: + text: Replace with 'Arrays.equals()' +replace.with.catch.clause.for.runtime.exception.quickfix: + text: Replace with 'catch' clause for 'RuntimeException' +resource.opened.not.closed.problem.descriptor: + text: '''''{0}'''' should be opened in front of a ''''try'''' block and closed in the corresponding ''''finally'''' block #loc' +result.of.method.call.ignored.class.column.title: + text: Class Name +result.of.method.call.ignored.display.name: + text: Result of method call ignored +result.of.method.call.ignored.method.column.title: + text: Method name regex +result.of.method.call.ignored.non.library.option: + text: Report all ignored non-library calls +result.of.method.call.ignored.problem.descriptor: + text: 'Result of {0}.#ref() is ignored #loc' +result.of.object.allocation.ignored.display.name: + text: Result of object allocation ignored +result.of.object.allocation.ignored.problem.descriptor: + text: 'Result of new #ref() is ignored #loc' +return.date.calendar.field.display.name: + text: Return of Date or Calendar field +return.date.calendar.field.problem.descriptor: + text: 'Return of ''''{0}'''' field #ref #loc' +return.date.calendar.field.quickfix: + text: Return clone of ''{0}'' +return.from.finally.block.display.name: + text: '''return'' inside ''finally'' block' +return.from.finally.block.problem.descriptor: + text: '#ref inside ''finally'' block #loc' +return.of.anonymous.class.problem.descriptor: + text: 'Return of instance of anonymous class #loc' +return.of.collection.array.field.display.name: + text: Return of Collection or array field +return.of.collection.array.field.option: + text: Ignore 'private' methods returning an array or collection field +return.of.collection.array.field.problem.descriptor.array: + text: '''return'' of array field #ref #loc' +return.of.collection.array.field.problem.descriptor.collection: + text: '''return'' of Collection field #ref #loc' +return.of.collection.field.quickfix: + text: Replace with ''{0}'' +return.of.inner.class.display.name: + text: Return of instance of anonymous, local or inner class +return.of.inner.class.ignore.non.public.option: + text: Ignore returns from non-public methods +return.of.inner.class.problem.descriptor: + text: 'Return of instance of non-static inner class {0} #loc' +return.of.local.class.problem.descriptor: + text: 'Return of instance of local class {0} #loc' +return.of.null.arrays.option: + text: Report methods that return arrays +return.of.null.collections.option: + text: Report methods that return collection objects +return.of.null.display.name: + text: Return of 'null' +return.of.null.ignore.private.option: + text: Ignore 'private' methods +return.of.null.objects.option: + text: Report methods that return objects +return.of.null.optional.quickfix: + text: Replace with ''{0}.{1}()'' +return.of.null.optional.quickfix.family: + text: Replace with 'Optional.empty()' +return.of.null.problem.descriptor: + text: 'Return of #ref #loc' +return.of.null.quickfix: + text: Annotate method as @Nullable +return.point.limit.option: + text: '&Return point limit:' +return.this.display.name: + text: Return of 'this' +return.this.problem.descriptor: + text: 'Return of #ref #loc' +reuse.of.local.variable.display.name: + text: Reuse of local variable +reuse.of.local.variable.problem.descriptor: + text: 'Reuse of local variable #ref #loc' +reuse.of.local.variable.split.quickfix: + text: Split local variable +runtime.exec.call.display.name: + text: Call to 'Runtime.exec()' +runtime.exec.call.problem.descriptor: + text: 'Call to Runtime.#ref() is non-portable #loc' +runtime.exec.with.non.constant.string.display.name: + text: Call to 'Runtime.exec()' with non-constant string +runtime.exec.with.non.constant.string.problem.descriptor: + text: 'Call to Runtime.#ref() with non-constant argument #loc' +safe.lock.display.name: + text: Lock acquired but not safely unlocked +safe.lock.problem.descriptor: + text: '''''{0}'''' should be locked in front of a ''''try'''' block and unlocked in the corresponding ''''finally'''' block #loc' +serializable.anonymous.class.stores.non.serializable.problem.descriptor: + text: Serializable anonymous class implicitly stores non-Serializable object of type ''{0}'' +serializable.class.in.secure.context.display.name: + text: Serializable class in secure context +serializable.class.in.secure.context.problem.descriptor: + text: 'Class #ref may be serialized, compromising security #loc' +serializable.class.without.serialversionuid.display.name: + text: Serializable class without 'serialVersionUID' +serializable.class.without.serialversionuid.problem.descriptor: + text: '#ref does not define a ''serialVersionUID'' field #loc' +serializable.has.serialization.methods.display.name: + text: Serializable class without 'readObject()' and 'writeObject()' +serializable.has.serialization.methods.ignore.option: + text: Ignore classes that do not define instance fields +serializable.has.serialization.methods.problem.descriptor: + text: 'Serializable class #ref does not define ''readObject()'' or ''writeObject()'' #loc' +serializable.has.serialization.methods.problem.descriptor1: + text: 'Serializable class #ref does not define ''writeObject()'' #loc' +serializable.has.serialization.methods.problem.descriptor2: + text: 'Serializable class #ref does not define ''readObject()'' #loc' +serializable.inner.class.has.serial.version.uid.field.display.name: + text: Serializable non-static inner class without 'serialVersionUID' +serializable.inner.class.has.serial.version.uid.field.problem.descriptor: + text: 'Inner class #ref does not define a ''serialVersionUID'' field #loc' +serializable.inner.class.with.non.serializable.outer.class.display.name: + text: Serializable non-'static' inner class with non-Serializable outer class +serializable.inner.class.with.non.serializable.outer.class.problem.descriptor: + text: 'Inner class #ref is serializable while its outer class is not #loc' +serializable.lambda.stores.non.serializable.problem.descriptor: + text: Serializable lambda implicitly stores non-Serializable object of type ''{0}'' +serializable.local.class.stores.non.serializable.problem.descriptor: + text: Serializable local class ''{1}'' implicitly stores non-Serializable object of type ''{0}'' +serializable.stores.non.serializable.display.name: + text: '''Serializable'' object implicitly stores non-''Serializable'' object' +serializable.with.unconstructable.ancestor.display.name: + text: Serializable class with unconstructable ancestor +serializable.with.unconstructable.ancestor.problem.descriptor: + text: '#ref has a non-serializable ancestor ''''{0}'''' without no-arg constructor #loc' +serialpersistentfields.with.wrong.signature.display.name: + text: '''serialPersistentFields'' field not declared ''private static final ObjectStreamField[]''' +serialpersistentfields.with.wrong.signature.problem.descriptor: + text: '#ref field of a Serializable class is not declared ''private static final ObjectStreamField[]'' #loc' +serialversionuid.private.static.final.long.display.name: + text: '''serialVersionUID'' field not declared ''private static final long''' +serialversionuid.private.static.final.long.problem.descriptor: + text: '#ref field of a Serializable class is not declared ''private static final long'' #loc' +serialversionuid.private.static.final.long.quickfix: + text: Make serialVersionUID 'private static final' +set.annotation.parameter.0.1.fix.name: + text: Set annotation parameter {0} = "{1}" +set.replaceable.by.enum.set.display.name: + text: '''Set'' can be replaced with ''EnumSet''' +set.replaceable.by.enum.set.problem.descriptor: + text: '#ref can be replaced with ''EnumSet'' #loc' +setup.calls.super.setup.add.quickfix: + text: Add call to 'super.setUp()' +setup.calls.super.setup.display.name: + text: '''setUp()'' does not call ''super.setUp()''' +setup.calls.super.setup.problem.descriptor: + text: '#ref() does not call ''super.setUp()'' #loc' +setup.is.public.void.no.arg.display.name: + text: '''setUp()'' with incorrect signature' +setup.is.public.void.no.arg.problem.descriptor: + text: #ref() has incorrect signature +shared.thread.local.random.display.name: + text: '''ThreadLocalRandom'' instance might be shared' +shared.thread.local.random.problem.descriptor: + text: '''ThreadLocalRandom'' instance might be shared between threads' +shift.operation.by.inappropriate.constant.display.name: + text: Shift operation by inappropriate constant +shift.operation.by.inappropriate.constant.problem.descriptor.negative: + text: 'Shift operation #ref by negative constant value #loc' +shift.operation.by.inappropriate.constant.problem.descriptor.too.large: + text: 'Shift operation #ref by overly large constant value #loc' +shift.out.of.range.quickfix: + text: Replace ''{0}'' with ''{1}'' +signal.without.corresponding.await.display.name: + text: '''signal()'' without corresponding ''await()''' +signal.without.corresponding.await.problem.descriptor: + text: 'Call to #ref() without corresponding await() #loc' +simplifiable.annotation.display.name: + text: Simplifiable annotation +simplifiable.annotation.problem.descriptor: + text: 'Annotation #ref may be replaced with ''''{0}'''' #loc' +simplifiable.annotation.quickfix: + text: Simplify annotation +simplifiable.annotation.whitespace.problem.descriptor: + text: 'Unnecessary whitespace in annotation #loc' +simplifiable.boolean.expression.display.name: + text: Simplifiable boolean expression +simplifiable.conditional.expression.display.name: + text: Simplifiable conditional expression +simplifiable.conditional.expression.problem.descriptor: + text: '#ref can be simplified to ''''{0}'''' #loc' +simplifiable.equals.expression.display.name: + text: Unnecessary 'null' check before 'equals()' call +simplifiable.equals.expression.problem.descriptor: + text: 'Unnecessary ''''null'''' check before ''''{0}()'''' call #loc' +simplifiable.equals.expression.quickfix: + text: Flip ''.{0}()'' and remove unnecessary ''null'' check +simplifiable.if.statement.display.name: + text: '''if'' statement may be replaced with \&\& or || expression' +simplifiable.if.statement.problem.descriptor: + text: '#ref statement can be replaced with ''''{0}'''' #loc' +simplifiable.junit.assertion.display.name: + text: Simplifiable assertion +simplifiable.junit.assertion.problem.descriptor: + text: '#ref() can be simplified to ''''{0}'''' #loc' +simplifiable.testng.assertion.display.name: + text: Simplifiable TestNG assertion +simplify.junit.assertion.simplify.quickfix: + text: Simplify assertion +single.character.startswith.display.name: + text: Single character 'startsWith()' or 'endsWith()' +single.character.startswith.problem.descriptor: + text: 'Single character #ref() can be replaced with ''charAt()'' expression #loc' +single.character.startswith.quickfix: + text: Replace with 'charAt()' +single.class.import.display.name: + text: Single class import +single.class.import.problem.descriptor: + text: 'Single class import #ref #loc' +single.element.annotation.family.quickfix: + text: Expand annotation to normal form +single.element.annotation.name: + text: Single-element annotation +single.element.annotation.quickfix: + text: Add 'value=' +single.statement.in.block.descriptor: + text: '''''{0}'''' contains single statement' +single.statement.in.block.family.quickfix: + text: Remove braces from statement +single.statement.in.block.name: + text: Code block contains single statement +single.statement.in.block.quickfix: + text: Remove braces from ''{0}'' statement +singleton.display.name: + text: Singleton +singleton.problem.descriptor: + text: 'Class #ref is a singleton #loc' +size.replaceable.by.isempty.display.name: + text: '''size() == 0'' can be replaced with ''isEmpty()''' +size.replaceable.by.isempty.negation.ignore.option: + text: Ignore expressions which would be replaced with '!isEmpty()' +size.replaceable.by.isempty.quickfix: + text: Replace with 'isEmpty()' +sleep.while.holding.lock.display.name: + text: Call to 'Thread.sleep()' while synchronized +sleep.while.holding.lock.problem.descriptor: + text: 'Call to Thread.#ref() while synchronized #loc' +smth.unnecessary.remove.quickfix: + text: Remove unnecessary ''{0}'' +socket.opened.not.closed.display.name: + text: Socket opened but not safely closed +standard.variable.names.display.name: + text: Standard variable names +standard.variable.names.ignore.override.option: + text: Ignore for parameter names identical to super method parameters +standard.variable.names.problem.descriptor: + text: 'Variable named #ref doesn''''t have type ''''{0}'''' #loc' +standard.variable.names.problem.descriptor2: + text: 'Variable named #ref doesn''''t have type ''''{0}'''' or ''''{1}'''' #loc' +statement.problem.descriptor: + text: '#ref statement #loc' +statement.with.empty.body.display.name: + text: Statement with empty body +statement.with.empty.body.include.option: + text: Include statement bodies that are empty code blocks +statement.with.empty.body.problem.descriptor: + text: '#ref statement has empty body #loc' +static.collection.display.name: + text: Static collection +static.collection.ignore.option: + text: Ignore weak static collections or maps +static.collection.problem.descriptor: + text: 'Static collection #ref #loc' +static.field.via.subclass.display.name: + text: Static field referenced via subclass +static.field.via.subclass.problem.descriptor: + text: 'Static field #ref declared in class ''''{0}'''' but referenced via subclass ''''{1}'''' #loc' +static.field.via.subclass.rationalize.quickfix: + text: Rationalize static field access +static.import.display.name: + text: Static import +static.import.problem.descriptor: + text: 'Static import #ref #loc' +static.import.replace.quickfix: + text: Replace with non-static import +static.inheritance.display.name: + text: Static inheritance +static.inheritance.problem.descriptor: + text: 'Interface #ref is implemented only for its static constants #loc' +static.inheritance.replace.quickfix: + text: Replace inheritance with qualified references in {0} +static.initializer.references.subclass.display.name: + text: Static initializer references subclass +static.method.naming.convention.display.name: + text: '''static'' method naming convention' +static.method.naming.convention.element.description: + text: '''static'' method' +static.method.naming.convention.problem.descriptor.long: + text: '''static'' method name #ref is too long #loc' +static.method.naming.convention.problem.descriptor.regex.mismatch: + text: '''static'' method name #ref doesn''''t match regex ''''{0}'''' #loc' +static.method.naming.convention.problem.descriptor.short: + text: '''static'' method name #ref is too short #loc' +static.method.only.used.in.one.anonymous.class.problem.descriptor: + text: 'Static method #ref() is only used from an anonymous class derived from ''''{0}'''' #loc' +static.method.only.used.in.one.class.display.name: + text: '''static'' method only used from one other class' +static.method.only.used.in.one.class.ignore.anonymous.option: + text: Ignore when only used from an anonymous class +static.method.only.used.in.one.class.ignore.on.conflicts: + text: Ignore when the method cannot be moved without conflicts +static.method.only.used.in.one.class.ignore.test.option: + text: Ignore when only used from a test class +static.method.only.used.in.one.class.problem.descriptor: + text: '''static'' method #ref() is only used from class ''''{0}'''' #loc' +static.method.only.used.in.one.class.problem.descriptor.anonymous.extending: + text: '''static'' method #ref() is only used from an anonymous class extending ''''{0}'''' #loc' +static.method.only.used.in.one.class.problem.descriptor.anonymous.implementing: + text: '''static'' method #ref() is only used from an anonymous class implementing ''''{0}'''' #loc' +static.method.only.used.in.one.class.quickfix: + text: Move method +static.method.via.subclass.display.name: + text: Static method referenced via subclass +static.method.via.subclass.problem.descriptor: + text: 'Static method #ref() declared in class ''''{0}'''' but referenced via subclass ''''{1}'''' #loc' +static.method.via.subclass.rationalize.quickfix: + text: Rationalize static method call +static.non.final.field.display.name: + text: '''static'', non-''final'' field' +static.non.final.field.option: + text: Only report 'public' fields +static.non.final.field.problem.descriptor: + text: '''static'' non-''final'' field #ref #loc' +static.suite.display.name: + text: '''suite()'' method not declared ''static''' +static.suite.problem.descriptor: + text: 'JUnit #ref() methods not declared ''static'' #loc' +static.variable.may.not.be.initialized.display.name: + text: Static field may not be initialized +static.variable.may.not.be.initialized.problem.descriptor: + text: 'Static field #ref may not be initialized during class initialization #loc' +static.variable.naming.convention.display.name: + text: '''static'' field naming convention' +static.variable.naming.convention.element.description: + text: '''static'' field' +static.variable.naming.convention.mutable.option: + text: Check 'static final' fields with a mutable type +static.variable.naming.convention.problem.descriptor.long: + text: '''static'' field name #ref is too long #loc' +static.variable.naming.convention.problem.descriptor.regex.mismatch: + text: '''static'' field #ref doesn''''t match regex ''''{0}'''' #loc' +static.variable.naming.convention.problem.descriptor.short: + text: '''static'' field name #ref is too short #loc' +static.variable.of.concrete.class.display.name: + text: Static field of concrete class +static.variable.of.concrete.class.option: + text: Ignore static fields whose type is an abstract class +static.variable.of.concrete.class.problem.descriptor: + text: 'Static field ''''{0}'''' of concrete class #ref #loc' +static.variable.used.before.initialization.display.name: + text: Static field used before initialization +static.variable.used.before.initialization.problem.descriptor: + text: 'Static field #ref used before initialization #loc' +string.buffer.must.have.initial.capacity.display.name: + text: StringBuffer or StringBuilder without initial capacity +string.buffer.must.have.initial.capacity.problem.descriptor: + text: 'new #ref() without initial capacity #loc' +string.buffer.replaceable.by.string.builder.display.name: + text: '''StringBuffer'' may be ''StringBuilder''' +string.buffer.replaceable.by.string.builder.problem.descriptor: + text: 'StringBuffer #ref may be declared as ''StringBuilder'' #loc' +string.buffer.replaceable.by.string.builder.replace.quickfix: + text: Replace with 'StringBuilder' +string.buffer.replaceable.by.string.display.name: + text: '''StringBuffer'' can be replaced with ''String''' +string.buffer.replaceable.by.string.problem.descriptor: + text: '{0} #ref can be replaced with ''''String'''' #loc' +string.buffer.replaceable.by.string.quickfix: + text: Replace 'StringBuffer' with 'String' +string.buffer.to.string.in.concatenation.display.name: + text: '''StringBuffer.toString()'' in concatenation' +string.buffer.to.string.in.concatenation.problem.descriptor: + text: 'Calls to StringBuffer.#ref() in concatenation #loc' +string.buffer.to.string.in.concatenation.remove.quickfix: + text: Remove 'toString()' +string.builder.replaceable.by.string.quickfix: + text: Replace 'StringBuilder' with 'String' +string.compareto.call.display.name: + text: Call to 'String.compareTo()' +string.compareto.call.problem.descriptor: + text: 'String.#ref() called using internationalized strings #loc' +string.comparison.display.name: + text: String comparison using '==', instead of 'equals()' +string.comparison.problem.descriptor: + text: 'String values are compared using #ref, not ''equals()'' #loc' +string.concatenation.argument.to.log.call.display.name: + text: Non-constant string concatenation as argument to logging call +string.concatenation.argument.to.log.call.problem.descriptor: + text: 'Non-constant string concatenation as argument to #ref() logging call #loc' +string.concatenation.argument.to.log.call.quickfix: + text: Replace concatenation with parameterized log message +string.concatenation.display.name: + text: String concatenation +string.concatenation.ignore.assert.option: + text: Ignore for assert statement arguments +string.concatenation.ignore.constant.initializers.option: + text: Ignore for initializers of constants +string.concatenation.ignore.exceptions.option: + text: Ignore for throwable arguments +string.concatenation.ignore.system.err.option: + text: Ignore for 'System.err.print' arguments +string.concatenation.ignore.system.out.option: + text: Ignore for 'System.out.print' arguments +string.concatenation.in.format.call.display.name: + text: String concatenation as argument to 'format()' call +string.concatenation.in.format.call.plural.quickfix: + text: Replace concatenation with separate arguments +string.concatenation.in.format.call.problem.descriptor: + text: '#ref() call has a String concatenation argument #loc' +string.concatenation.in.format.call.quickfix: + text: Replace concatenation with parameterized log message +string.concatenation.in.loops.display.name: + text: String concatenation in loop +string.concatenation.in.loops.only.option: + text: Only warn if string is repeatedly appended +string.concatenation.in.loops.problem.descriptor: + text: 'String concatenation #ref in loop #loc' +string.concatenation.in.message.format.call.display.name: + text: String concatenation as argument to 'MessageFormat.format()' call +string.concatenation.in.message.format.call.problem.descriptor: + text: 'String concatenation as argument to ''MessageFormat.format()'' call #loc' +string.concatenation.inside.string.buffer.append.display.name: + text: String concatenation as argument to 'StringBuilder.append()' call +string.concatenation.inside.string.buffer.append.problem.descriptor: + text: 'String concatenation as argument to {0}.#ref() call #loc' +string.concatenation.inside.string.buffer.append.replace.quickfix: + text: Replace with chained 'append()' calls +string.concatenation.introduce.fix: + text: Introduce StringBuilder +string.concatenation.introduce.fix.name: + text: Introduce new {1} to update variable ''{0}'' +string.concatenation.missing.whitespace.display.name: + text: String literal concatenation missing whitespace +string.concatenation.missing.whitespace.option: + text: Ignore when one or both sides are not literals +string.concatenation.missing.whitespace.problem.descriptor: + text: 'String literal concatenation missing whitespace #loc' +string.concatenation.problem.descriptor: + text: 'String concatenation #ref in an internationalized context #loc' +string.concatenation.replace.fix: + text: Replace with StringBuilder +string.concatenation.replace.fix.name: + text: Convert variable ''{0}'' from String to {1} +string.constructor.display.name: + text: Redundant String constructor call +string.constructor.problem.descriptor: + text: '#ref is redundant #loc' +string.constructor.replace.arg.quickfix: + text: Replace with arg +string.constructor.replace.empty.quickfix: + text: Replace with empty string +string.constructor.substring.parameter.option: + text: Ignore string constructor calls with a 'substring()' call argument +string.equals.call.display.name: + text: Call to 'String.equals()' +string.equals.call.problem.descriptor: + text: 'String.#ref() using internationalized strings #loc' +string.equals.empty.string.display.name: + text: '''String.equals("")''' +string.equals.empty.string.is.empty.problem.descriptor: + text: '#ref("") can be replaced with ''isEmpty()'' #loc' +string.equals.empty.string.isempty.quickfix: + text: Replace with 'isEmpty()' +string.equals.empty.string.problem.descriptor: + text: '#ref("") can be replaced with ''length()==0'' #loc' +string.equals.empty.string.quickfix: + text: Replace with 'length()==0' +string.equalsignorecase.call.display.name: + text: Call to 'String.equalsIgnoreCase()' +string.equalsignorecase.call.problem.descriptor: + text: 'String.#ref() using internationalized strings #loc' +string.format.choose.class: + text: Choose Formatter Class +string.format.class.column.name: + text: Additional formatter classes +string.format.class.method.name: + text: Additional formatter methods +string.replace.quickfix: + text: Replace concatenation with ''{0}'' +string.replaceable.by.string.buffer.display.name: + text: Non-constant 'String' can be replaced with 'StringBuilder' +string.replaceable.by.string.buffer.in.loop.option: + text: Only warn when appending in a loop +string.replaceable.by.string.buffer.problem.descriptor: + text: 'Non-constant String #ref should probably be declared as ''StringBuilder'' #loc' +string.to.string.display.name: + text: Redundant 'String.toString()' +string.to.string.problem.descriptor: + text: '#ref is redundant #loc' +string.touppercase.tolowercase.without.locale.display.name: + text: Call to 'String.toUpperCase()' or 'toLowerCase()' without locale +string.touppercase.tolowercase.without.locale.problem.descriptor: + text: 'String.#ref() called without specifying a Locale using internationalized strings #loc' +stringbuffer.field.display.name: + text: '''StringBuilder'' field' +stringbuffer.field.problem.descriptor: + text: '''''{0}'''' field #ref #loc' +substring.zero.display.name: + text: Redundant 'substring(0)' call +substring.zero.problem.descriptor: + text: '#ref is redundant #loc' +subtraction.in.compareto.display.name: + text: Subtraction in 'compareTo()' +subtraction.in.compareto.problem.descriptor: + text: 'Subtraction #ref in ''compareTo()'' may result in overflow errors #loc' +super.class.logger.option: + text: Ignore classes with an accessible logger declared in a superclass +super.tear.down.in.finally.display.name: + text: '''super.tearDown()'' not called from ''finally'' block' +super.tear.down.in.finally.problem.descriptor: + text: '#ref() not called from ''finally'' block #loc' +suppress.for.tests.scope.quickfix: + text: Suppress for 'Tests' scope +suspicious.array.cast.display.name: + text: Suspicious array cast +suspicious.array.cast.problem.descriptor: + text: 'Suspicious cast to #ref #loc' +suspicious.comparator.compare.descriptor.non.reflexive: + text: Comparator does not return 0 for equal elements +suspicious.comparator.compare.descriptor.parameter.not.used: + text: '''compare()'' parameter #ref is not used #loc' +suspicious.comparator.compare.display.name: + text: Suspicious 'Comparator.compare()' implementation +suspicious.getter.problem.descriptor: + text: 'Getter #ref() returns field ''''{0}'''' #loc' +suspicious.getter.setter.display.name: + text: Suspicious getter/setter +suspicious.indent.after.control.statement.display.name: + text: Suspicious indentation after control statement without braces +suspicious.indent.after.control.statement.problem.descriptor: + text: '#ref statement has suspicious indentation #loc' +suspicious.literal.underscore.display.name: + text: Suspicious underscore in number literal +suspicious.literal.underscore.problem.descriptor: + text: 'Group in number literal with underscores does not have length 3 #loc' +suspicious.setter.problem.descriptor: + text: 'Setter #ref() assigns field ''''{0}'''' #loc' +suspicious.system.arraycopy.display.name: + text: Suspicious 'System.arraycopy()' call +suspicious.system.arraycopy.problem.descriptor1: + text: 'Parameter ''srcPos'' may not be negative #loc' +suspicious.system.arraycopy.problem.descriptor2: + text: 'Parameter ''destPos'' may not be negative #loc' +suspicious.system.arraycopy.problem.descriptor3: + text: 'Parameter ''length'' may not be negative #loc' +suspicious.system.arraycopy.problem.descriptor4: + text: '#ref is not of an array type #loc' +suspicious.system.arraycopy.problem.descriptor5: + text: '#ref is not of an array type #loc' +suspicious.system.arraycopy.problem.descriptor6: + text: 'Source parameter type ''''{0}'''' is not assignable to destination parameter #ref of type ''''{1}'''' #loc' +suspicious.to.array.call.display.name: + text: Suspicious 'Collection.toArray()' call +suspicious.to.array.call.problem.descriptor: + text: 'Array of type ''''{0}[]'''' expected #loc' +switch.expression.with.single.default.message: + text: '''switch'' expression has only ''default'' case' +switch.expression.with.too.few.branches.problem.descriptor: + text: '''''switch'''' expression has too few case labels ({0}), and should probably be replaced with an ''''if'''' statement or conditional operator #loc' +switch.statement.density.display.name: + text: '''switch'' statement with too low of a branch density' +switch.statement.density.min.option: + text: 'Minimum density of branches: %' +switch.statement.density.problem.descriptor: + text: '#ref branch density is too low ({0}%) #loc' +switch.statement.display.name: + text: '''switch'' statement' +switch.statement.with.confusing.declaration.display.name: + text: Local variable used and declared in different 'switch' branches +switch.statement.with.confusing.declaration.problem.descriptor: + text: 'Local variable #ref declared in one ''switch'' branch and used in another #loc' +switch.statement.with.single.default.message: + text: '''switch'' statement has only ''default'' case' +switch.statement.with.too.few.branches.display.name: + text: '''switch'' statement with too few branches' +switch.statement.with.too.few.branches.min.option: + text: 'Minimum number of branches:' +switch.statement.with.too.few.branches.problem.descriptor: + text: '#ref has too few branches ({0}), and should probably be replaced with an ''''if'''' statement #loc' +switch.statement.with.too.many.branches.display.name: + text: '''switch'' statement with too many branches' +switch.statement.without.default.ignore.option: + text: Ignore if all cases of an enumerated type are covered +switch.statements.without.default.display.name: + text: '''switch'' statement without ''default'' branch' +switch.statements.without.default.problem.descriptor: + text: '#ref statement without ''default'' branch #loc' +synchronization.on.get.class.display.name: + text: Synchronization on 'getClass()' +synchronization.on.get.class.problem.descriptor: + text: 'Synchronization on #ref() #loc' +synchronization.on.local.variable.or.method.parameter.display.name: + text: Synchronization on local variable or method parameter +synchronization.on.local.variable.problem.descriptor: + text: 'Synchronization on local variable #ref #loc' +synchronization.on.method.parameter.problem.descriptor: + text: 'Synchronization on method parameter #ref #loc' +synchronization.on.static.field.display.name: + text: Synchronization on 'static' field +synchronization.on.static.field.problem.descriptor: + text: 'Synchronization on ''static'' field #ref #loc' +synchronize.on.class.problem.descriptor: + text: 'Lock operations on a class may have unforeseen side-effects #loc' +synchronize.on.lock.display.name: + text: Synchronization on a 'Lock' object +synchronize.on.lock.problem.descriptor: + text: 'Synchronization on a ''''{0}'''' object is unlikely to be intentional #loc' +synchronize.on.non.final.field.display.name: + text: Synchronization on a non-final field +synchronize.on.non.final.field.problem.descriptor: + text: 'Synchronization on a non-final field #ref #loc' +synchronize.on.this.display.name: + text: Synchronization on 'this' +synchronize.on.this.problem.descriptor: + text: 'Lock operations on ''this'' may have unforeseen side-effects #loc' +synchronized.method.display.name: + text: '''synchronized'' method' +synchronized.method.ignore.synchronized.super.option: + text: Ignore methods overriding a synchronized method +synchronized.method.include.option: + text: Include native methods +synchronized.method.move.quickfix: + text: Move synchronization into method +synchronized.method.problem.descriptor: + text: 'Method ''''{0}()'''' declared #ref #loc' +synchronized.on.direct.literal.object.problem.descriptor: + text: 'Synchronization on {0} literal #ref #loc' +synchronized.on.literal.object.name: + text: Synchronization on an object initialized with a literal +synchronized.on.literal.object.problem.descriptor: + text: 'Synchronization on {0} #ref which is initialized by a literal #loc' +synchronized.on.literal.object.warn.on.all.option: + text: Warn on all possible literals +synchronized.on.possibly.literal.object.problem.descriptor: + text: 'Synchronization on {0} #ref #loc' +system.exit.call.display.name: + text: Call to 'System.exit()' or related methods +system.exit.call.ignore.option: + text: Ignore in main method +system.exit.call.problem.descriptor: + text: 'Call to {0}.#ref() is non-portable #loc' +system.getenv.call.display.name: + text: Call to 'System.getenv()' +system.getenv.call.problem.descriptor: + text: 'Call to System.#ref() is non-portable #loc' +system.properties.display.name: + text: Access of system properties +system.properties.problem.descriptor: + text: 'Call to Integer.#ref() may pose security concerns #loc' +system.properties.problem.descriptor1: + text: 'Call to Boolean.#ref() may pose security concerns #loc' +system.run.finalizers.on.exit.display.name: + text: Call to 'System.runFinalizersOnExit()' +system.run.finalizers.on.exit.problem.descriptor: + text: 'Call to System.#ref() #loc' +system.set.problem.descriptor: + text: 'Call to System.#ref() may pose security concerns #loc' +system.set.security.manager.display.name: + text: Call to 'System.setSecurityManager()' +system.set.security.manager.problem.descriptor: + text: 'Call to System.#ref() may pose security concerns #loc' +tail.recursion.display.name: + text: Tail recursion +tail.recursion.problem.descriptor: + text: 'Tail recursive call #ref() #loc' +tail.recursion.replace.quickfix: + text: Replace tail recursion with iteration +teardown.calls.super.teardown.add.quickfix: + text: Add call to 'super.tearDown()' +teardown.calls.super.teardown.display.name: + text: '''tearDown()'' does not call ''super.tearDown()''' +teardown.calls.super.teardown.problem.descriptor: + text: '#ref() does not call ''super.tearDown()'' #loc' +teardown.is.public.void.no.arg.display.name: + text: '''tearDown()'' with incorrect signature' +teardown.is.public.void.no.arg.problem.descriptor: + text: '#ref() has incorrect signature #loc' +test.case.in.product.code.display.name: + text: JUnit TestCase in product source +test.case.in.product.code.problem.descriptor: + text: 'Test case #ref should probably be placed in a test source tree #loc' +test.case.with.constructor.display.name: + text: JUnit TestCase with non-trivial constructors +test.case.with.constructor.problem.descriptor: + text: 'Initialization logic in constructor #ref() instead of ''setUp()'' #loc' +test.case.with.constructor.problem.descriptor.initializer: + text: Initialization logic in initializer instead of 'setUp()' +test.case.with.no.test.methods.display.name: + text: JUnit test case with no tests +test.case.with.no.test.methods.option: + text: Ignore test cases which have superclasses with test methods +test.case.with.no.test.methods.problem.descriptor: + text: 'JUnit test case #ref has no tests #loc' +test.method.in.product.code.display.name: + text: JUnit test method in product source +test.method.in.product.code.problem.descriptor: + text: 'Test method #ref() should probably be placed in a test source tree #loc' +test.method.is.public.void.no.arg.display.name: + text: Malformed test method +test.method.is.public.void.no.arg.problem.descriptor1: + text: 'Test method #ref() should probably not have parameters #loc' +test.method.is.public.void.no.arg.problem.descriptor2: + text: 'Test method #ref() is not declared ''public void'' #loc' +test.method.is.public.void.no.arg.problem.descriptor3: + text: 'Test method #ref() should not be ''static'' #loc' +test.method.without.assertion.display.name: + text: JUnit test method without any assertions +test.method.without.assertion.problem.descriptor: + text: 'JUnit test method #ref() contains no assertions #loc' +text.label.in.switch.statement.display.name: + text: Text label in 'switch' statement +text.label.in.switch.statement.problem.descriptor: + text: 'Text label #ref: in ''switch'' statement #loc' +the.whole.project: + text: the whole project +this.class: + text: this class +this.reference.escaped.in.construction.display.name: + text: '''this'' reference escaped in object construction' +this.reference.escaped.in.construction.problem.descriptor: + text: 'Escape of #ref during object construction #loc' +thread.death.rethrown.display.name: + text: '''ThreadDeath'' not rethrown' +thread.death.rethrown.problem.descriptor: + text: 'ThreadDeath #ref not rethrown #loc' +thread.local.not.static.final.display.name: + text: '''ThreadLocal'' field not declared ''static final''' +thread.local.not.static.final.problem.descriptor: + text: 'ThreadLocal #ref is not declared ''static final'' #loc' +thread.priority.display.name: + text: Call to 'Thread.setPriority()' +thread.priority.problem.descriptor: + text: 'Call to Thread.#ref() #loc' +thread.run.display.name: + text: Call to 'Thread.run()' +thread.run.problem.descriptor: + text: 'Calls to #ref() should probably be replaced with ''start()'' #loc' +thread.run.replace.quickfix: + text: Replace with 'start()' +thread.start.in.construction.display.name: + text: Call to 'Thread.start()' during object construction +thread.start.in.construction.problem.descriptor: + text: 'Call to #ref() during object construction #loc' +thread.stop.suspend.resume.display.name: + text: Call to 'Thread.stop()', 'suspend()' or 'resume()' +thread.stop.suspend.resume.problem.descriptor: + text: 'Call to Thread.#ref() #loc' +thread.with.default.run.method.display.name: + text: Instantiating a 'Thread' with default 'run()' method +thread.with.default.run.method.problem.descriptor: + text: 'Instantiating a #ref with default ''run()'' method #loc' +thread.yield.display.name: + text: Call to 'Thread.yield()' +thread.yield.problem.descriptor: + text: 'Call to Thread.#ref() #loc' +three.negations.per.method.display.name: + text: Method with more than three negations +three.negations.per.method.ignore.assert.option: + text: Ignore negations in 'assert' statements +three.negations.per.method.ignore.option: + text: Ignore negations in 'equals()' methods +three.negations.per.method.problem.descriptor: + text: '#ref contains {0} negations #loc' +throw.caught.locally.display.name: + text: '''throw'' caught by containing ''try'' statement' +throw.caught.locally.ignore.option: + text: Ignore rethrown exceptions +throw.caught.locally.problem.descriptor: + text: '#ref caught by containing ''try'' statement #loc' +throw.from.finally.block.display.name: + text: '''throw'' inside ''finally'' block' +throw.from.finally.block.everywhere.option: + text: Warn everywhere declared exceptions may be thrown +throw.from.finally.block.problem.descriptor: + text: '#ref inside ''finally'' block #loc' +throwable.instance.never.thrown.checked.exception.problem.descriptor: + text: 'Checked exception instance #ref is not thrown #loc' +throwable.instance.never.thrown.display.name: + text: Throwable instance not thrown +throwable.instance.never.thrown.error.problem.descriptor: + text: 'Error instance #ref is not thrown #loc' +throwable.instance.never.thrown.problem.descriptor: + text: 'Throwable instance #ref is not thrown #loc' +throwable.instance.never.thrown.runtime.exception.problem.descriptor: + text: 'Runtime exception instance new #ref() is not thrown #loc' +throwable.not.thrown.display.name: + text: '''Throwable'' not thrown' +throwable.printed.to.system.out.display.name: + text: '''Throwable'' printed to ''System.out''' +throwable.printed.to.system.out.problem.descriptor: + text: '''Throwable'' argument #ref to ''''System.{0}.{1}()'''' call' +throwable.result.of.method.call.ignored.display.name: + text: Throwable result of method call ignored +throwable.result.of.method.call.ignored.problem.descriptor: + text: 'Result of #ref() not thrown #loc' +thrown.exceptions.per.method.display.name: + text: Method with too many exceptions declared +thrown.exceptions.per.method.limit.option: + text: 'Exceptions thrown limit:' +thrown.exceptions.per.method.problem.descriptor: + text: '#ref has too many exceptions declared (num exceptions = {0}) #loc' +throws.runtime.exception.display.name: + text: Unchecked exception declared in 'throws' clause +throws.runtime.exception.move.quickfix: + text: Move ''{0}'' to Javadoc ''@throws'' tag +throws.runtime.exception.problem.descriptor: + text: 'Unchecked exception #ref declared in ''throws'' clause #loc' +throws.runtime.exception.quickfix: + text: Remove ''{0}'' from ''throws'' clause +time.tostring.call.display.name: + text: Call to 'Time.toString()' +time.tostring.call.problem.descriptor: + text: 'Time.#ref() in an internationalized context #loc' +to.array.call.with.zero.length.array.argument.display.name: + text: Call to 'Collection.toArray()' with zero-length array argument +to.array.call.with.zero.length.array.argument.problem.descriptor: + text: 'Call to #ref() with zero-length array argument ''''{0}'''' #loc' +to.array.call.with.zero.length.array.argument.quickfix: + text: Replace argument with correctly sized array +todo.comment.display.name: + text: TODO comment +todo.comment.problem.descriptor: + text: 'TODO comment #ref #loc' +too.broad.catch.display.name: + text: Overly broad 'catch' block +too.broad.catch.option: + text: '&Only warn on RuntimeException, Exception, Error or Throwable' +too.broad.catch.problem.descriptor: + text: '''''catch'''' of #ref is too broad, masking exception ''''{0}'''' #loc' +too.broad.catch.problem.descriptor1: + text: '''''catch'''' of #ref is too broad, masking exceptions ''''{0}'''' and ''''{1}'''' #loc' +too.broad.catch.quickfix: + text: Add ''catch'' clause for ''{0}'' +too.broad.scope.allow.option: + text: 'Report variables with a new expression as initializer
(potentially unsafe: quick fix may modify semantics if the constructor has non-local side-effects)' +too.broad.scope.display.name: + text: Scope of variable is too broad +too.broad.scope.narrow.quickfix: + text: Move declaration of ''{0}'' closer to usages +too.broad.scope.only.blocks.option: + text: Only report variables that can be moved into inner blocks +too.broad.scope.problem.descriptor: + text: 'Scope of variable #ref is too broad #loc' +too.many.constructors.count.limit.option: + text: 'Constructor count limit:' +too.many.constructors.display.name: + text: Class with too many constructors +too.many.constructors.ignore.deprecated.option: + text: Ignore deprecated constructors +too.many.constructors.problem.descriptor: + text: '#ref has too many constructors (constructor count = {0}) #loc' +too.many.fields.count.limit.option: + text: 'Field count limit:' +too.many.fields.display.name: + text: Class with too many fields +too.many.fields.problem.descriptor: + text: '#ref has too many fields (field count = {0}) #loc' +too.many.methods.display.name: + text: Class with too many methods +too.many.methods.problem.descriptor: + text: '#ref has too many methods (method count = {0}) #loc' +trace.level.option: + text: trace level +transient.field.in.non.serializable.class.display.name: + text: Transient field in non-serializable class +transient.field.in.non.serializable.class.problem.descriptor: + text: 'Field ''''{0}'''' is marked #ref, in non-Serializable class #loc' +transient.field.in.non.serializable.class.remove.quickfix: + text: Remove 'transient' +transient.field.not.initialized.display.name: + text: Transient field is not initialized on deserialization +transient.field.not.initialized.problem.descriptor: + text: 'Transient field #ref not initialized on deserialization #loc' +trivial.if.display.name: + text: Redundant 'if' statement +trivial.if.problem.descriptor: + text: '#ref statement can be simplified #loc' +trivial.string.concatenation.display.name: + text: Concatenation with empty string +trivial.string.concatenation.problem.descriptor: + text: Empty string in concatenation +try.finally.can.be.try.with.resources.display.name: + text: '''try finally'' can be replaced with ''try'' with resources' +try.finally.can.be.try.with.resources.problem.descriptor: + text: '#ref can use automatic resource management #loc' +try.finally.can.be.try.with.resources.quickfix: + text: Replace with 'try' with resources +try.statement.with.multiple.resources.name: + text: '''try'' statement with multiple resources can be split' +try.statement.with.multiple.resources.quickfix: + text: Split 'try' statement with multiple resources +try.with.identical.catches.display.name: + text: Identical 'catch' branches in 'try' statement +try.with.identical.catches.problem.descriptor: + text: '''''catch'''' branch identical to ''''{0}'''' branch #loc' +try.with.identical.catches.quickfix: + text: Collapse 'catch' blocks +type.may.be.weakened.collection.method.option: + text: Use ¶meterized type of collection for method call arguments +type.may.be.weakened.display.name: + text: Type may be weakened +type.may.be.weakened.do.not.weaken.to.object.option: + text: Do not &weaken to 'java.lang.Object' +type.may.be.weakened.field.problem.descriptor: + text: 'Type of field #ref may be weakened to {0} #loc' +type.may.be.weakened.ignore.option: + text: Use &righthand type as weakest type in assignments +type.may.be.weakened.method.problem.descriptor: + text: 'Return type of method #ref() may be weakened to {0} #loc' +type.may.be.weakened.parameter.problem.descriptor: + text: 'Type of parameter #ref may be weakened to {0} #loc' +type.may.be.weakened.problem.descriptor: + text: 'Type of variable #ref may be weakened to {0} #loc' +type.may.be.weakened.quickfix: + text: Weaken type to ''{0}'' +type.parameter.extends.final.class.display.name: + text: Type parameter extends 'final' class +type.parameter.extends.final.class.problem.descriptor1: + text: 'Type parameter #ref extends ''''final'''' class ''''{0}'''' #loc' +type.parameter.extends.final.class.problem.descriptor2: + text: 'Wildcard type argument #ref extends ''''final'''' class ''''{0}'''' #loc' +type.parameter.extends.final.class.quickfix: + text: Replace type parameter with actual class +type.parameter.extends.object.display.name: + text: Type parameter explicitly extends 'Object' +type.parameter.extends.object.problem.descriptor1: + text: 'Type parameter #ref explicitly extends ''java.lang.Object'' #loc' +type.parameter.extends.object.problem.descriptor2: + text: 'Wildcard type argument #ref explicitly extends ''java.lang.Object'' #loc' +type.parameter.hides.type.parameter.problem.descriptor: + text: 'Type parameter #ref hides type parameter ''''{0}'''' #loc' +type.parameter.hides.visible.type.display.name: + text: Type parameter hides visible type +type.parameter.hides.visible.type.problem.descriptor: + text: 'Type parameter #ref hides visible type ''''{0}'''' #loc' +type.parameter.naming.convention.display.name: + text: Type parameter naming convention +type.parameter.naming.convention.element.description: + text: Type parameter +type.parameter.naming.convention.problem.descriptor.long: + text: 'Type parameter name #ref is too long #loc' +type.parameter.naming.convention.problem.descriptor.short: + text: 'Type parameter name #ref is too short #loc' +unary.plus.display.name: + text: Unary plus +unary.plus.problem.descriptor: + text: 'Unary #ref operator #loc' +unary.plus.quickfix: + text: Remove unary '+' +unchecked.exception.class.display.name: + text: Unchecked 'Exception' class +unchecked.exception.class.problem.descriptor: + text: 'Unchecked exception class #ref #loc' +unclear.binary.expression.display.name: + text: Unclear expression +unclear.binary.expression.problem.descriptor: + text: 'Expression could use clarifying parentheses #loc' +unclear.binary.expression.quickfix: + text: Add clarifying parentheses +unconditional.wait.display.name: + text: Unconditional 'wait()' call +unconditional.wait.problem.descriptor: + text: 'Unconditional call to #ref() #loc' +unconstructable.test.case.display.name: + text: Unconstructable JUnit TestCase +unconstructable.test.case.problem.descriptor: + text: 'Test case #ref is not constructable by most test runners #loc' +unnecessarily.qualified.inner.class.access.display.name: + text: Unnecessarily qualified inner class access +unnecessarily.qualified.inner.class.access.option: + text: Ignore references for which an import is needed +unnecessarily.qualified.inner.class.access.problem.descriptor: + text: '''''{0}'''' is unnecessarily qualified with #ref #loc' +unnecessarily.qualified.inner.class.access.quickfix: + text: Remove qualifier +unnecessarily.qualified.static.usage.display.name: + text: Unnecessarily qualified static access +unnecessarily.qualified.static.usage.ignore.field.option: + text: Ignore unnecessarily qualified field accesses +unnecessarily.qualified.static.usage.ignore.method.option: + text: Ignore unnecessarily qualified method calls +unnecessarily.qualified.static.usage.problem.descriptor: + text: 'Unnecessarily qualified call to static method {0}() #loc' +unnecessarily.qualified.static.usage.problem.descriptor1: + text: 'Unnecessarily qualified access to static field {0} #loc' +unnecessarily.qualified.statically.imported.element.display.name: + text: Unnecessarily qualified statically imported element +unnecessarily.qualified.statically.imported.element.problem.descriptor: + text: 'Statically imported element ''''{0}'''' is unnecessarily qualified with #ref #loc' +unnecessarily.qualified.statically.imported.element.quickfix: + text: Remove unnecessary qualifier +unnecessary.block.statement.problem.descriptor: + text: 'Braces around this statement are unnecessary #loc' +unnecessary.boxing.display.name: + text: Unnecessary boxing +unnecessary.boxing.problem.descriptor: + text: 'Unnecessary boxing #ref #loc' +unnecessary.boxing.remove.quickfix: + text: Remove boxing +unnecessary.boxing.superfluous.option: + text: Only report truly superfluously boxed expressions +unnecessary.break.display.name: + text: Unnecessary 'break' statement +unnecessary.break.problem.descriptor: + text: '#ref statement is unnecessary #loc' +unnecessary.call.to.string.valueof.display.name: + text: Unnecessary call to 'String.valueOf()' +unnecessary.call.to.string.valueof.problem.descriptor: + text: '#ref can be simplified to ''''{0}'''' #loc' +unnecessary.call.to.string.valueof.quickfix: + text: Replace with ''{0}'' +unnecessary.code.block.display.name: + text: Unnecessary code block +unnecessary.code.block.unwrap.quickfix: + text: Unwrap block +unnecessary.conditional.expression.display.name: + text: Redundant conditional expression +unnecessary.constant.array.creation.expression.display.name: + text: Redundant 'new' expression in constant array creation +unnecessary.constant.array.creation.expression.family.quickfix: + text: Replace with array initializer expression +unnecessary.constant.array.creation.expression.problem.descriptor: + text: '#ref can be replaced with array initializer expression #loc' +unnecessary.constant.array.creation.expression.quickfix: + text: Replace with array initializer expression +unnecessary.constructor.annotation.option: + text: Ignore constructors with an annotation +unnecessary.constructor.display.name: + text: Redundant no-arg constructor +unnecessary.constructor.problem.descriptor: + text: 'No-arg constructor #ref() is redundant #loc' +unnecessary.constructor.remove.quickfix: + text: Remove redundant constructor +unnecessary.continue.display.name: + text: Unnecessary 'continue' statement +unnecessary.continue.problem.descriptor: + text: '#ref is unnecessary as the last statement in a loop #loc' +unnecessary.default.display.name: + text: Unnecessary 'default' for enum 'switch' statement +unnecessary.default.problem.descriptor: + text: '#ref branch is unnecessary #loc' +unnecessary.enum.modifier.display.name: + text: Unnecessary enum modifier +unnecessary.enum.modifier.problem.descriptor: + text: 'Modifier #ref is redundant for enum constructors #loc' +unnecessary.enum.modifier.problem.descriptor1: + text: 'Modifier #ref is redundant for inner enums #loc' +unnecessary.explicit.numeric.cast.display.name: + text: Unnecessary explicit numeric cast +unnecessary.explicit.numeric.cast.problem.descriptor: + text: '''''{0}'''' unnecessarily cast to #ref #loc' +unnecessary.explicit.numeric.cast.quickfix: + text: Remove cast +unnecessary.final.on.local.variable.or.parameter.display.name: + text: Unnecessary 'final' on local variable or parameter +unnecessary.final.on.local.variable.problem.descriptor: + text: 'Unnecessary #ref on variable ''''{0}'''' #loc' +unnecessary.final.on.parameter.only.interface.option: + text: Only warn on abstract or interface methods +unnecessary.final.on.parameter.problem.descriptor: + text: 'Unnecessary #ref on parameter ''''{0}'''' #loc' +unnecessary.final.report.local.variables.option: + text: Report local variables +unnecessary.final.report.parameters.option: + text: Report parameters +unnecessary.fully.qualified.name.display.name: + text: Unnecessary fully qualified name +unnecessary.fully.qualified.name.ignore.option: + text: Ignore fully qualified names in javadoc +unnecessary.fully.qualified.name.problem.descriptor1: + text: 'Qualifier #ref is unnecessary, and can be replaced with an import #loc' +unnecessary.fully.qualified.name.problem.descriptor2: + text: 'Qualifier #ref is unnecessary and can be removed #loc' +unnecessary.fully.qualified.name.remove.quickfix: + text: Remove unnecessary qualification +unnecessary.fully.qualified.name.replace.quickfix: + text: Replace qualified name with import +unnecessary.fully.qualified.name.status.bar.escape.highlighting.message: + text: '{0} fully qualified {0, choice, 1#name|2#names} replaced with import (press Escape to remove highlighting)' +unnecessary.inherit.doc.class.invalid.problem.descriptor: + text: '#ref is not valid on classes #loc' +unnecessary.inherit.doc.constructor.invalid.problem.descriptor: + text: '#ref is not valid on constructors #loc' +unnecessary.inherit.doc.constructor.no.super.problem.descriptor: + text: 'No super method found to inherit Javadoc from #loc' +unnecessary.inherit.doc.display.name: + text: Unnecessary '{@inheritDoc}' Javadoc comment +unnecessary.inherit.doc.field.invalid.problem.descriptor: + text: '#ref is not valid on fields #loc' +unnecessary.inherit.doc.module.invalid.problem.descriptor: + text: '#ref is not valid on module declarations #loc' +unnecessary.inherit.doc.problem.descriptor: + text: 'Javadoc comment containing only #ref is unnecessary #loc' +unnecessary.inherit.doc.quickfix: + text: Remove unnecessary '{@inheritDoc}' +unnecessary.initcause.display.name: + text: Unnecessary call to 'Throwable.initCause()' +unnecessary.initcause.problem.descriptor: + text: Unnecessary Throwable.#ref() call +unnecessary.initcause.quickfix: + text: Remove 'Throwable.initCause()' call +unnecessary.interface.modifier.display.name: + text: Unnecessary interface modifier +unnecessary.interface.modifier.inner.interface.of.interface.problem.descriptor: + text: 'Modifier #ref is redundant for inner interfaces #loc' +unnecessary.interface.modifier.problem.descriptor: + text: 'Modifier #ref is redundant for interfaces #loc' +unnecessary.interface.modifier.problem.descriptor2: + text: 'Modifier #ref is redundant for interface methods #loc' +unnecessary.interface.modifier.problem.descriptor3: + text: 'Modifier #ref is redundant for inner classes of interfaces #loc' +unnecessary.interface.modifier.problem.descriptor4: + text: 'Modifier #ref is redundant for interface fields #loc' +unnecessary.javadoc.link.display.name: + text: Unnecessary Javadoc link +unnecessary.javadoc.link.option: + text: Ignore inline links to super methods +unnecessary.javadoc.link.quickfix: + text: Remove unnecessary ''{0}'' +unnecessary.javadoc.link.super.method.problem.descriptor: + text: '#ref pointing to super method is unnecessary #loc' +unnecessary.javadoc.link.this.class.problem.descriptor: + text: '#ref pointing to containing class is unnecessary #loc' +unnecessary.javadoc.link.this.method.problem.descriptor: + text: '#ref pointing to this method is unnecessary #loc' +unnecessary.label.on.break.statement.display.name: + text: Unnecessary label on 'break' statement +unnecessary.label.on.break.statement.problem.descriptor: + text: 'Unnecessary label on #ref statement #loc' +unnecessary.label.on.continue.statement.display.name: + text: Unnecessary label on 'continue' statement +unnecessary.label.on.continue.statement.problem.descriptor: + text: 'Unnecessary label on #ref statement #loc' +unnecessary.label.remove.quickfix: + text: Remove label +unnecessary.local.variable.problem.descriptor: + text: 'Local variable #ref is redundant #loc' +unnecessary.parentheses.conditional.option: + text: Ignore parentheses around the condition of conditional expressions +unnecessary.parentheses.display.name: + text: Unnecessary parentheses +unnecessary.parentheses.option: + text: Ignore clarifying parentheses +unnecessary.parentheses.problem.descriptor: + text: 'Parentheses around #ref are unnecessary #loc' +unnecessary.parentheses.remove.quickfix: + text: Remove unnecessary parentheses +unnecessary.qualifier.for.super.problem.descriptor: + text: 'Qualifier #ref on ''super'' is unnecessary in this context #loc' +unnecessary.qualifier.for.this.display.name: + text: Unnecessary qualifier for 'this' or 'super' +unnecessary.qualifier.for.this.problem.descriptor: + text: 'Qualifier #ref on ''this'' is unnecessary in this context #loc' +unnecessary.qualifier.for.this.remove.quickfix: + text: Remove unnecessary qualifier +unnecessary.return.constructor.problem.descriptor: + text: '#ref is unnecessary as the last statement in a constructor #loc' +unnecessary.return.display.name: + text: Unnecessary 'return' statement +unnecessary.return.option: + text: Ignore in then branch of 'if' statement with 'else' branch +unnecessary.return.problem.descriptor: + text: '#ref is unnecessary as the last statement in a ''void'' method #loc' +unnecessary.semicolon.display.name: + text: Unnecessary semicolon +unnecessary.semicolon.problem.descriptor: + text: 'Unnecessary semicolon #ref #loc' +unnecessary.semicolon.remove.quickfix: + text: Remove unnecessary semicolon +unnecessary.super.constructor.display.name: + text: Unnecessary call to 'super()' +unnecessary.super.constructor.problem.descriptor: + text: '#ref is unnecessary #loc' +unnecessary.super.constructor.remove.quickfix: + text: Remove unnecessary 'super()' +unnecessary.super.qualifier.display.name: + text: Unnecessary 'super' qualifier +unnecessary.super.qualifier.problem.descriptor: + text: 'Qualifier #ref is unnecessary in this context #loc' +unnecessary.super.qualifier.quickfix: + text: Remove unnecessary 'super' qualifier +unnecessary.temporary.on.conversion.from.string.display.name: + text: Unnecessary temporary object in conversion from 'String' +unnecessary.temporary.on.conversion.from.string.fix.name: + text: Replace with ''{0}'' +unnecessary.temporary.on.conversion.from.string.problem.descriptor: + text: '#ref #loc can be simplified to ''''{0}''''' +unnecessary.temporary.on.conversion.to.string.display.name: + text: Unnecessary temporary object in conversion to 'String' +unnecessary.this.display.name: + text: Unnecessary 'this' qualifier +unnecessary.this.ignore.assignments.option: + text: Ignore field assignments +unnecessary.this.problem.descriptor: + text: '#ref is unnecessary in this context #loc' +unnecessary.this.remove.quickfix: + text: Remove unnecessary 'this' qualifier +unnecessary.tostring.call.display.name: + text: Unnecessary call to 'toString()' +unnecessary.tostring.call.problem.descriptor: + text: 'Unnecessary #ref() call #loc' +unnecessary.unary.minus.display.name: + text: Unnecessary unary minus +unnecessary.unary.minus.problem.descriptor: + text: 'Unnecessary unary minus #loc' +unnecessary.unary.minus.quickfix: + text: Remove unary minus and invert parent operation sign +unnecessary.unboxing.display.name: + text: Unnecessary unboxing +unnecessary.unboxing.problem.descriptor: + text: 'Unnecessary unboxing #ref #loc' +unnecessary.unboxing.remove.quickfix: + text: Remove unboxing +unnecessary.unboxing.superfluous.option: + text: Only report truly superfluously unboxed expressions +unnecessary.unicode.escape.display.name: + text: Unnecessary Unicode escape sequence +unnecessary.unicode.escape.problem.descriptor: + text: 'Unicode escape sequence #ref can be replaced with ''''{0}'''' #loc' +unpredictable.big.decimal.constructor.call.display.name: + text: Unpredictable 'BigDecimal' constructor call +unpredictable.big.decimal.constructor.call.ignore.complex.literals.option: + text: Ignore constructor calls with multiple literals (e.g. 0.1 + 0.2) +unpredictable.big.decimal.constructor.call.ignore.references.option: + text: Ignore constructor calls with variable or method call arguments +unpredictable.big.decimal.constructor.call.problem.descriptor: + text: 'Unpredictable new #ref() call #loc' +unpredictable.big.decimal.constructor.call.quickfix: + text: Replace with ''{0}'' +unqualified.field.access.display.name: + text: Instance field access not qualified with 'this' +unqualified.field.access.problem.descriptor: + text: 'Instance field access #ref is not qualified with ''this'' #loc' +unqualified.inner.class.access.display.name: + text: Unqualified inner class access +unqualified.inner.class.access.option: + text: Ignore references to local inner classes +unqualified.inner.class.access.problem.descriptor: + text: '#ref is not qualified with outer class #loc' +unqualified.inner.class.access.quickfix: + text: Qualify with outer class +unqualified.method.access.display.name: + text: Instance method call not qualified with 'this' +unqualified.method.access.problem.descriptor: + text: 'Instance method call #ref() is not qualified with ''this'' #loc' +unqualified.static.usage.display.name: + text: Unqualified static access +unqualified.static.usage.ignore.field.option: + text: Ignore unqualified field accesses +unqualified.static.usage.ignore.method.option: + text: Ignore unqualified method calls +unqualified.static.usage.only.report.static.usages.option: + text: Only report static access from a non-static context +unqualified.static.usage.problem.descriptor: + text: 'Unqualified static method call #ref() #loc' +unqualified.static.usage.problem.descriptor1: + text: 'Unqualified static field access #ref #loc' +unqualified.static.usage.qualify.field.quickfix: + text: Qualify static field access +unqualified.static.usage.qualify.method.quickfix: + text: Qualify static method call +unsecure.random.number.generation.display.name: + text: Insecure random number generation +unsecure.random.number.generation.problem.descriptor1: + text: 'For security purposes, use ''java.security.SecureRandom'' instead of java.lang.Math.#ref() #loc' +unsecure.random.number.generation.problem.descriptor2: + text: 'For security purposes, use ''java.security.SecureRandom'' instead of java.util.#ref #loc' +unsecure.random.number.generation.problem.descriptor3: + text: 'For security purposes, use ''java.security.SecureRandom'' instead of #ref #loc' +unused.catch.parameter.display.name: + text: Unused 'catch' parameter +unused.catch.parameter.ignore.catch.option: + text: Ignore when 'catch' block contains a comment +unused.catch.parameter.ignore.empty.option: + text: Ignore unused 'catch' parameters in tests +unused.catch.parameter.problem.descriptor: + text: 'Unused ''catch'' parameter #ref #loc' +unused.import.display.name: + text: Unused import +unused.import.problem.descriptor: + text: 'Unused import #ref #loc' +unused.label.display.name: + text: Unused label +unused.label.problem.descriptor: + text: 'Unused label #ref #loc' +unused.label.remove.quickfix: + text: Remove unused label +update.column.name: + text: Update names start with +upper.case.field.name.not.constant.display.name: + text: Non-constant field with upper-case name +upper.case.field.name.not.constant.problem.descriptor: + text: 'Non-constant field #ref with constant-style name #loc' +usage.of.obsolete.assert.display.name: + text: Usage of obsolete 'junit.framework.Assert' method +use.0index.in.jdbc.prepared.statement.problem.descriptor: + text: 'Use of index ''0'' in JDBC PreparedStatement #loc' +use.0index.in.jdbc.resultset.display.name: + text: Use of index 0 in JDBC ResultSet +use.0index.in.jdbc.resultset.problem.descriptor: + text: 'Use of index ''0'' in JDBC ResultSet #loc' +use.assert.as.identifier.display.name: + text: Use of 'assert' as identifier +use.assert.as.identifier.problem.descriptor: + text: 'Use of #ref as identifier #loc' +use.enum.as.identifier.display.name: + text: Use of 'enum' as identifier +use.enum.as.identifier.problem.descriptor: + text: 'Use of #ref as identifier #loc' +use.obsolete.collection.type.display.name: + text: Use of obsolete collection type +use.obsolete.collection.type.ignore.library.arguments.option: + text: Ignore obsolete collection types where they are required +use.obsolete.collection.type.problem.descriptor: + text: 'Obsolete collection type #ref used #loc' +use.of.awt.peer.class.display.name: + text: Use of AWT peer class +use.of.awt.peer.class.problem.descriptor: + text: 'Use of AWT peer class #ref is non-portable #loc' +use.of.clone.call.method.problem.descriptor: + text: Implementation of #ref() +use.of.clone.call.problem.descriptor: + text: Call to #ref() +use.of.clone.display.name: + text: Use of 'clone()' or 'Cloneable' +use.of.clone.reference.problem.descriptor: + text: Use of #ref +use.of.concrete.jdbc.driver.class.display.name: + text: Use of concrete JDBC driver class +use.of.concrete.jdbc.driver.class.problem.descriptor: + text: 'Use of concrete JDBC driver class #ref is non-portable #loc' +use.of.obsolete.assert.problem.descriptor: + text: 'Call to #ref() from ''org.junit.framework.Assert'' should be replaced with call to method from ''org.junit.Assert'' #loc' +use.of.obsolete.assert.quickfix: + text: Replace with 'org.junit.Assert' method call +use.of.obsolete.date.time.api.display.name: + text: Use of obsolete date-time API +use.of.obsolete.date.time.api.problem.descriptor: + text: 'Obsolete date-time type #ref used #loc' +use.processbuilder.class.display.name: + text: Use of 'java.lang.ProcessBuilder' class +use.processbuilder.class.problem.descriptor: + text: 'Use of #ref is non-portable #loc' +use.stringtokenizer.display.name: + text: Use of 'StringTokenizer' +use.stringtokenizer.problem.descriptor: + text: '#ref in an internationalized context #loc' +use.sun.classes.display.name: + text: Use of 'sun.*' classes +use.sun.classes.problem.descriptor: + text: 'Use of Sun-supplied class #ref is non-portable #loc' +use.system.out.err.display.name: + text: Use of 'System.out' or 'System.err' +use.system.out.err.problem.descriptor: + text: 'Uses of #ref should probably be replaced with more robust logging #loc' +used.catch.parameter.named.ignore.problem.descriptor: + text: '''catch'' parameter named #ref is used #loc' +utility.class.can.be.enum.display.name: + text: Utility class can be 'enum' +utility.class.code.can.be.enum.problem.descriptor: + text: 'Utility class #ref can be ''enum'' #loc' +utility.class.code.can.be.enum.quickfix: + text: Convert to 'enum' +utility.class.display.name: + text: Utility class +utility.class.problem.descriptor: + text: 'Class #ref has only ''static'' members, indicating procedural construction #loc' +utility.class.with.public.constructor.display.name: + text: Utility class with 'public' constructor +utility.class.with.public.constructor.make.quickfix: + text: Make {0, choice, 1#constructor|2#constructors} 'private' +utility.class.with.public.constructor.problem.descriptor: + text: 'Class #ref has only ''static'' members, and a ''public'' constructor #loc' +utility.class.without.private.constructor.create.quickfix: + text: Generate empty 'private' constructor +utility.class.without.private.constructor.display.name: + text: Utility class without 'private' constructor +utility.class.without.private.constructor.make.quickfix: + text: Make constructor 'private' +utility.class.without.private.constructor.option: + text: Ignore classes with only a main method +utility.class.without.private.constructor.problem.descriptor: + text: 'Class #ref has only ''static'' members, and lacks a ''private'' constructor #loc' +value.of.post.decrement.problem.descriptor: + text: 'Value of post-decrement expression #ref is used #loc' +value.of.post.increment.problem.descriptor: + text: 'Value of post-increment expression #ref is used #loc' +value.of.pre.decrement.problem.descriptor: + text: 'Value of pre-decrement expression #ref is used #loc' +value.of.pre.increment.problem.descriptor: + text: 'Value of pre-increment expression #ref is used #loc' +variable.argument.method.display.name: + text: Varargs method +variable.argument.method.problem.descriptor: + text: 'Varargs method #ref() #loc' +variable.argument.method.quickfix: + text: Convert varargs parameter to array +variable.not.used.inside.conditional.problem.descriptor: + text: '#ref checked for ''null'' is not used inside conditional #loc' +variable.not.used.inside.if.display.name: + text: Reference checked for 'null' is not used inside 'if' +variable.not.used.inside.if.problem.descriptor: + text: '#ref checked for ''null'' is not used inside ''if'' #loc' +volatile.array.field.display.name: + text: Volatile array field +volatile.field.problem.descriptor: + text: 'Volatile field #ref of type ''''{0}'''' #loc' +volatile.long.or.double.field.display.name: + text: Volatile long or double field +wait.called.on.condition.display.name: + text: '''wait()'' called on ''java.util.concurrent.locks.Condition'' object' +wait.called.on.condition.problem.descriptor: + text: 'Call to #ref() on Condition object #loc' +wait.not.in.loop.display.name: + text: '''wait()'' not called in loop' +wait.not.in.loop.problem.descriptor: + text: 'Call to #ref() is not in loop #loc' +wait.not.in.synchronized.context.display.name: + text: '''wait()'' while not synchronized' +wait.not.in.synchronized.context.problem.descriptor: + text: 'Call to #ref while not synchronized on ''''{0}'''' #loc' +wait.notify.not.in.synchronized.context.display.name: + text: '''wait()'' or ''notify()'' is not in synchronized context' +wait.notify.while.not.synchronized.on.problem.descriptor: + text: 'Call to #ref while not synchronized on ''''{0}'''' #loc' +wait.or.await.without.timeout.display.name: + text: '''wait()'' or ''await()'' without timeout' +wait.or.await.without.timeout.problem.descriptor: + text: '#ref without timeout #loc' +wait.while.holding.two.locks.display.name: + text: '''wait()'' while holding two locks' +wait.while.holding.two.locks.problem.descriptor: + text: 'Call to #ref() is made while holding two locks #loc' +wait.without.corresponding.notify.display.name: + text: '''wait()'' without corresponding ''notify()''' +wait.without.corresponding.notify.problem.descriptor: + text: 'Call to #ref() without corresponding notify() or notifyAll() #loc' +warn.level.and.lower.option: + text: warn level and lower +warn.on.label: + text: 'Warn on:' +while.can.be.foreach.display.name: + text: '''while'' loop can be replaced with enhanced ''for'' loop' +while.can.be.foreach.problem.descriptor: + text: '#ref loop can be replaced with enhanced ''for'' #loc' +while.loop.spins.on.field.display.name: + text: '''while'' loop spins on field' +while.loop.spins.on.field.fix.family.name: + text: Fix spin loop +while.loop.spins.on.field.fix.spinwait: + text: Add Thread.onSpinWait() +while.loop.spins.on.field.fix.volatile: + text: Make ''{0}'' volatile +while.loop.spins.on.field.fix.volatile.spinwait: + text: Make ''{0}'' volatile and add Thread.onSpinWait() +while.loop.spins.on.field.ignore.non.empty.loops.option: + text: Only warn if the loop is empty +while.loop.spins.on.field.problem.descriptor: + text: '#ref loop spins on field #loc' diff --git a/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.IntentionPowerPackLocalize.yaml b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.IntentionPowerPackLocalize.yaml new file mode 100644 index 0000000000..8fad6edd57 --- /dev/null +++ b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/com.siyeh.IntentionPowerPackLocalize.yaml @@ -0,0 +1,456 @@ +0.already.extends.1.and.will.not.compile.after.converting.2.to.a.class: + text: '{0} already extends {1} and will not compile after converting {2} to a class' +0.is.declared.in.1.but.when.public.should.be.declared.in.a.file.named.2: + text: '{0} is declared in {1} but when public should be declared in a file named {2}' +0.will.have.incompatible.access.privileges.with.overriding.1: + text: '{0} will have incompatible access privileges with overriding {1}' +0.will.have.incompatible.access.privileges.with.super.1: + text: '{0} will have incompatible access privileges with super {1}' +0.will.no.longer.be.visible.from.overriding.1: + text: '{0} will no longer be visible from overriding {1}' +1.fully.qualified.name.status.bar.escape.highlighting.message: + text: 1 fully qualified name replaced with import (press Escape to remove highlighting) +adapter.to.listener.intention.family.name: + text: Replace Adapter Extension with Listener Implementation +adapter.to.listener.intention.name: + text: Replace extension of ''{0}'' with ''Listener'' implementation +add.array.creation.expression.intention.family.name: + text: Add Array Creation Expression +add.array.creation.expression.intention.name: + text: Add ''new {0}'' +add.braces.intention.family.name: + text: Add Braces +add.braces.intention.name: + text: Add Braces to ''{0}'' statement +add.clarifying.parentheses.intention.family.name: + text: Add Clarifying Parentheses +add.clarifying.parentheses.intention.name: + text: Add clarifying parentheses +annotate.overridden.methods.intention.family.name: + text: Annotate overriding methods and their parameters +annotate.overridden.methods.intention.method.name: + text: Annotate overriding methods as ''@{0}'' +annotate.overridden.methods.intention.parameters.name: + text: Annotate same parameter of overriding methods as ''@{0}'' +assert.to.if.intention.family.name: + text: Replace Assert with If Statement +assert.to.if.intention.name: + text: Replace 'assert' with 'if' statement +change.to.c.style.comment.intention.family.name: + text: Replace with Block Comment +change.to.c.style.comment.intention.name: + text: Replace with block comment +change.to.end.of.line.comment.intention.family.name: + text: Replace with End Of Line Comment +change.to.end.of.line.comment.intention.name: + text: Replace with end-of-line comment +change.variable.type.to.rhs.type.intention.family.name: + text: Change Variable Type to Type of Initializer +change.variable.type.to.rhs.type.intention.name: + text: Declare ''{0}'' with type ''{1}'' +char.to.string.intention.family.name: + text: Replace Char with String +char.to.string.intention.name: + text: Replace character literal with string +constant.expression.intention.family.name: + text: Compute Constant Value +constant.expression.intention.name: + text: Compute constant value of ''{0}'' +constant.subexpression.intention.family.name: + text: Compute Constant Value for Subexpression +convert.catch.to.throws.intention.family.name: + text: Replace Catch Section with Throws Declaration +convert.catch.to.throws.intention.name: + text: Replace 'catch' section with 'throws' declaration +convert.integer.to.binary.intention.family.name: + text: Convert to Binary +convert.integer.to.binary.intention.name: + text: Convert to binary +convert.integer.to.decimal.intention.family.name: + text: Convert to Decimal +convert.integer.to.decimal.intention.name: + text: Convert to decimal +convert.integer.to.hex.intention.family.name: + text: Convert to Hexadecimal +convert.integer.to.hex.intention.name: + text: Convert to hex +convert.integer.to.octal.intention.family.name: + text: Convert to Octal +convert.integer.to.octal.intention.name: + text: Convert to octal +convert.interface.to.class.intention.family.name: + text: Convert Interface to Class +convert.interface.to.class.intention.name: + text: Convert to 'class' +convert.j.unit3.test.case.to.j.unit4.intention.family.name: + text: Convert JUnit3 Test Case to JUnit4 +convert.j.unit3.test.case.to.j.unit4.intention.name: + text: Convert to JUnit4 Test Case +convert.to.nested.if.intention.family.name: + text: Expand Boolean to multiple ifs +convert.to.nested.if.intention.name: + text: Convert to multiple 'if's +convert.to.plain.intention.family.name: + text: Convert to Plain +convert.to.plain.intention.name: + text: Convert to plain +convert.to.scientific.notation.intention.family.name: + text: Convert to Scientific Notation +convert.to.scientific.notation.intention.name: + text: Convert to scientific notation +convert.vararg.parameter.to.array.intention.family.name: + text: Convert Variable Argument Parameter to Array Parameter +convert.vararg.parameter.to.array.intention.name: + text: Convert variable argument parameter to array +copy.concatenated.string.to.clipboard.intention.family.name: + text: Copy String Concatenation Text to the Clipboard +copy.concatenated.string.to.clipboard.intention.name: + text: Copy String concatenation text to the clipboard +create.assert.intention.family.name: + text: Create JUnit Assertion +create.assert.intention.name: + text: Create JUnit Assertion +create.enum.switch.branches.intention.family.name: + text: Create Enum Switch Branches +create.enum.switch.branches.intention.name: + text: Create missing 'switch' branches +demorgans.intention.family.name: + text: DeMorgan Law +demorgans.intention.name1: + text: Replace '\&\&' with '||' +demorgans.intention.name2: + text: Replace '||' with '\&\&' +detail.exceptions.intention.family.name: + text: Detail Exceptions +detail.exceptions.intention.name: + text: Detail exceptions +expand.boolean.intention.family.name: + text: Expand Boolean +expand.boolean.intention.name: + text: Convert to 'if else' +expand.one.line.lambda2.code.block.intention.family.name: + text: Expand lambda expression body to code block +expand.to.normal.annotation.intention.family.name: + text: Expand Annotation to Normal Form +expand.to.normal.annotation.name: + text: Expand to ''{0}'' +extract.increment.intention.family.name: + text: Extract Increment +extract.increment.intention.name: + text: Extract ''{0}'' +extract.while.loop.condition.to.if.statement.intention.family.name: + text: Extract While Loop Condition to Internal If Statement +extract.while.loop.condition.to.if.statement.intention.name: + text: Extract condition to internal 'if' statement +flip.assert.literal.intention.family.name: + text: Flip Assert Literal +flip.assert.literal.intention.name: + text: Replace ''{0}()'' with ''{1}()'' +flip.commutative.method.call.intention.family.name: + text: Flip Commutative Method Call +flip.commutative.method.call.intention.name: + text: Flip ''.{0}()'' +flip.commutative.method.call.intention.name1: + text: Flip ''.{0}()'' (may change semantics) +flip.comparison.intention.family.name: + text: Flip Comparison +flip.comparison.intention.name: + text: Flip ''{0}'' to ''{1}'' +flip.conditional.intention.family.name: + text: Flip Conditional +flip.conditional.intention.name: + text: Flip '?:' +flip.conjunction.intention.family.name: + text: Flip Conjunction Operands +flip.expression.intention.family.name: + text: Flip Binary Expression +flip.setter.call.intention.family.name: + text: Flip Setter Call(s) +flip.setter.call.intention.name: + text: Flip Setter Call +flip.smth.intention.name: + text: Flip ''{0}'' +flip.smth.intention.name1: + text: Flip ''{0}'' (changes semantics) +if.to.assertion.intention.family.name: + text: Replace If with Assert Statement +if.to.assertion.intention.name: + text: Replace 'if' with 'assert' statement +infer.lambda.parameter.type.intention.family.name: + text: Infer lambda parameter type +intention.category.annotations: + text: Annotations +intention.category.boolean: + text: Boolean +intention.category.comments: + text: Comments +intention.category.conditional.operator: + text: Conditional Operator +intention.category.control.flow: + text: Control Flow +intention.category.declaration: + text: Declaration +intention.category.imports: + text: Imports +intention.category.junit: + text: JUnit +intention.category.modifiers: + text: Modifiers +intention.category.numbers: + text: Numbers +intention.category.other: + text: Other +intention.category.shift.operation: + text: Shift Operation +intention.category.strings: + text: Strings +join.concatenated.string.literals.intention.family.name: + text: Join Concatenated String Literals +join.concatenated.string.literals.intention.name: + text: Join concatenated String literals +make.call.chain.into.call.sequence.intention.family.name: + text: Make Call Chain Into Call Sequence +make.call.chain.into.call.sequence.intention.name: + text: Make method call chain into call sequence +make.method.varargs.intention.family.name: + text: Convert Method to Variable Argument Method +make.method.varargs.intention.name: + text: Convert to variable argument method +make.package.private.intention.family.name: + text: Make Package-Local +make.package.private.intention.name: + text: Make package-local +make.private.intention.family.name: + text: Make Private +make.private.intention.name: + text: Make 'private' +make.protected.intention.family.name: + text: Make Protected +make.protected.intention.name: + text: Make 'protected' +make.public.intention.family.name: + text: Make Public +make.public.intention.name: + text: Make 'public' +merge.call.sequence.to.chain.intention.family.name: + text: Merge Sequential Method Calls into Call Chain +merge.call.sequence.to.chain.intention.name: + text: Merge sequential method calls into call chain +merge.else.if.intention.family.name: + text: Merge Else If +merge.else.if.intention.name: + text: Merge 'else if' +merge.if.and.intention.family.name: + text: Merge Nested Ifs to ANDed Condition +merge.if.and.intention.name: + text: Merge nested 'if's +merge.if.or.intention.family.name: + text: Merge Equivalent Ifs to ORed Condition +merge.if.or.intention.name: + text: Merge sequential 'if's +merge.nested.try.statements.intention.family.name: + text: Merge Nested Try Statements +merge.nested.try.statements.intention.name: + text: Merge nested 'try' statements +merge.parallel.ifs.intention.family.name: + text: Merge Parallel Ifs +merge.parallel.ifs.intention.name: + text: Merge 'if's +move.comment.to.separate.line.intention.family.name: + text: Move Comment to Separate Line +move.comment.to.separate.line.intention.name: + text: Move comment to separate line +multiple.fully.qualified.names.status.bar.escape.highlighting.message: + text: '{0} fully qualified names replaced with import (press Escape to remove highlighting)' +negate.comparison.intention.family.name: + text: Negate Comparison +negate.comparison.intention.name: + text: Negate ''{0}'' +negate.comparison.intention.name1: + text: Negate ''{0}'' to ''{1}'' +obscure.thrown.exceptions.intention.family.name: + text: Replace Exceptions in Throws Clause with Single More General Exception +obscure.thrown.exceptions.intention.name: + text: Replace with ''throws {0}'' +plugin.IntentionPowerPack.description: + text: Adds over 80 new intention actions for IDEA. +press.escape.to.remove.highlighting.message: + text: Press Escape to remove the highlighting +remove.boolean.equality.intention.family.name: + text: Remove Boolean Equality +remove.boolean.equality.intention.name: + text: Simplify ''{0}'' +remove.braces.intention.family.name: + text: Remove Braces +remove.braces.intention.name: + text: Remove braces from ''{0}'' statement +remove.conditional.intention.family.name: + text: Remove Pointless Conditional +remove.conditional.intention.name: + text: Simplify '?:' +remove.unnecessary.parentheses.intention.family.name: + text: Remove Unnecessary Parentheses +remove.unnecessary.parentheses.intention.name: + text: Remove unnecessary parentheses +replace.arm.with.try.finally.intention.family.name: + text: Replace Try-With-Resources with Try-Finally +replace.arm.with.try.finally.intention.name: + text: Replace 'try-with-resources' with 'try finally' +replace.assert.equals.with.assert.literal.intention.family.name: + text: Replace assertEquals with assertTrue, assertFalse, or assertNull +replace.assert.equals.with.assert.literal.intention.name: + text: Replace ''assertEquals()'' with ''{0}()'' +replace.assert.literal.with.assert.equals.intention.family.name: + text: Replace assertTrue, assertFalse, or assertNull with assertEquals +replace.assert.literal.with.assert.equals.intention.name: + text: Replace ''{0}()'' with ''assertEquals({1}, ...)'' +replace.assert.literal.with.assert.equals.intention.name1: + text: Replace ''{0}()'' with ''assertEquals(..., {1}, ...)'' +replace.assert.literal.with.assert.equals.intention.name2: + text: Replace ''{0}()'' with ''assertEquals()'' +replace.assignment.with.operator.assignment.intention.name: + text: Replace ''='' with ''{0}='' +replace.assignment.with.postfix.expression.intention.family.name: + text: Replace Assignment with Postfix Expression +replace.concatenation.with.format.string.intention.family.name: + text: Replace String Concatenation with String.format() +replace.concatenation.with.format.string.intention.name: + text: Replace '+' with 'String.format()' +replace.concatenation.with.string.buffer.intention.family.name: + text: Replace + with StringBuilder.append() +replace.concatenation.with.string.buffer.intention.name: + text: Replace '+' with 'StringBuffer.append()' +replace.concatenation.with.string.builder.intention.name: + text: Replace '+' with 'StringBuilder.append()' +replace.conditional.with.if.intention.family.name: + text: Replace Conditional with If Else +replace.conditional.with.if.intention.name: + text: Replace '?:' with 'if else' +replace.diamond.with.explicit.type.arguments.intention.family.name: + text: Replace Diamond with Explicit Type Arguments +replace.diamond.with.explicit.type.arguments.intention.name: + text: Replace '<>' with explicit type arguments +replace.do.while.loop.with.while.loop.intention.family.name: + text: Replace Do While Loop with While Loop +replace.do.while.loop.with.while.loop.intention.name: + text: Replace 'do while' loop with 'while' loop +replace.equality.with.equals.intention.family.name: + text: Replace Equality with Equals +replace.equality.with.equals.intention.name: + text: Replace '==' with '.equals()' +replace.equality.with.safe.equals.intention.family.name: + text: Replace Equality with Safe Equals +replace.equality.with.safe.equals.intention.name: + text: Replace '==' with safe '.equals()' +replace.equals.with.equality.intention.family.name: + text: Replace Equals with Equality +replace.equals.with.equality.intention.name: + text: Replace '.equals()' with '==' +replace.for.each.loop.with.indexed.for.loop.intention.family.name: + text: Replace For-each Loop with Indexed For Loop +replace.for.each.loop.with.indexed.for.loop.intention.name: + text: Replace 'for each' loop with indexed 'for' loop +replace.for.each.loop.with.iterator.for.loop.intention.family.name: + text: Replace For-each Loop with Iterator For Loop +replace.for.each.loop.with.iterator.for.loop.intention.name: + text: Replace 'for each' loop with iterator 'for' loop +replace.for.each.loop.with.optimized.indexed.for.loop.intention.family.name: + text: Replace For-each Loop with Optimized Indexed For Loop +replace.for.each.loop.with.optimized.indexed.for.loop.intention.name: + text: Replace 'for each' loop with optimized indexed 'for' loop +replace.for.loop.with.while.loop.intention.family.name: + text: Replace For Loop with While Loop +replace.for.loop.with.while.loop.intention.name: + text: Replace 'for' loop with 'while' loop +replace.fully.qualified.name.with.import.intention.family.name: + text: Replace Qualified Name with Import +replace.fully.qualified.name.with.import.intention.name: + text: Replace qualified name with 'import' +replace.if.with.conditional.intention.family.name: + text: Replace If Else with Conditional +replace.if.with.conditional.intention.name: + text: Replace 'if else' with '?:' +replace.if.with.switch.intention.family.name: + text: Replace If with Switch +replace.if.with.switch.intention.name: + text: Replace 'if' with 'switch' +replace.lambda.with.anonymous.intention.family.name: + text: Replace lambda with anonymous class +replace.lambda.with.anonymous.intention.name: + text: Replace lambda with anonymous class +replace.method.ref.with.lambda.intention.family.name: + text: Replace method reference with lambda +replace.method.ref.with.lambda.intention.name: + text: Replace method reference with lambda +replace.multiply.with.shift.intention.family.name: + text: Replace Multiply with Shift +replace.on.demand.import.intention.family.name: + text: Replace On Demand Import with Single Class Imports +replace.on.demand.import.intention.name: + text: Replace with single class imports +replace.operator.assignment.with.assignment.intention.family.name: + text: Replace Operator Assignment with Assignment +replace.operator.assignment.with.assignment.intention.name: + text: Replace ''{0}'' with ''='' +replace.operator.assignment.with.postfix.expression.intention.family.name: + text: Replace Operator Assignment with Postfix Expression +replace.postfix.expression.with.assignment.intention.family.name: + text: Replace Postfix Expression with Assignment +replace.postfix.expression.with.operator.assignment.intention.family.name: + text: Replace Postfix Expression with Operator Assignment +replace.shift.with.multiply.intention.family.name: + text: Replace Shift with Multiply +replace.some.operator.with.other.intention.name: + text: Replace ''{0}'' with ''{1}'' +replace.switch.with.if.intention.family.name: + text: Replace Switch with If +replace.switch.with.if.intention.name: + text: Replace 'switch' with 'if' +replace.while.loop.with.do.while.loop.intention.family.name: + text: Replace While Loop with Do While Loop +replace.while.loop.with.do.while.loop.intention.name: + text: Replace 'while' loop with 'do while' loop +replace.with.operator.assignment.intention.family.name: + text: Replace Assignment with Operator Assignment +reverse.for.loop.direction.intention.family.name: + text: Reverse Direction of For Loop +reverse.for.loop.direction.intention.name: + text: Reverse direction of for loop +simplify.if.else.intention.family.name: + text: Simplify If Else +simplify.if.else.intention.name: + text: Simplify 'if else' +simplify.variable.intention.family.name: + text: Replace with Java Style Array Declaration +simplify.variable.intention.name: + text: Replace with Java-style array declaration +split.declaration.and.initialization.intention.family.name: + text: Split Declaration and Initialization +split.declaration.and.initialization.intention.name: + text: Split into declaration and initialization +split.else.if.intention.family.name: + text: Split Else If +split.else.if.intention.name: + text: Split 'else if' +split.multi.catch.intention.family.name: + text: Split Multi-Catch into Separate Catch Blocks +split.multi.catch.intention.name: + text: Split multi-catch into separate 'catch' blocks +split.try.with.multiple.resources.intention.family.name: + text: Split Try Statement with Multiple Resources +split.try.with.multiple.resources.intention.name: + text: Split 'try' statement with multiple resources +status.bar.escape.highlighting.message: + text: Press Escape to remove the highlighting +string.to.char.intention.family.name: + text: Replace String with Char +string.to.char.intention.name: + text: Replace string literal with character +swap.method.call.arguments.intention.family.name: + text: Swap Method Call Arguments +swap.method.call.arguments.intention.name: + text: Swap ''{0}'' and ''{1}'' +wrap.vararg.arguments.with.explicit.array.intention.family.name: + text: Wrap Vararg Arguments with Explicit Array Creation +wrap.vararg.arguments.with.explicit.array.intention.name: + text: Wrap vararg arguments with explicit array creation diff --git a/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaInspectionsLocalize.yaml b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaInspectionsLocalize.yaml new file mode 100644 index 0000000000..c263d2ca61 --- /dev/null +++ b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaInspectionsLocalize.yaml @@ -0,0 +1,1610 @@ +access.static.via.instance: + text: Access static member via instance reference +annotate.overridden.methods.parameters: + text: Annotate overridden method parameters as ''@{0}'' +annotate.overridden.methods.parameters.family.name: + text: Annotate overridden method parameters +assignment.to.declared.variable.problem.descriptor: + text: Variable ''{0}'' is initialized with self assignment +assignment.to.itself.problem.descriptor: + text: Variable ''{0}'' is assigned to itself +assignment.to.itself.quickfix.name: + text: Remove self assignment +boolean.method.is.always.inverted.display.name: + text: Boolean method is always inverted +boolean.method.is.always.inverted.problem.descriptor: + text: Boolean method #ref is always inverted +cleanup.in.file: + text: Cleanup code +cleanup.in.scope: + text: Cleanup code on... +configure.annotations.option: + text: Configure annotations +configure.checker.option.assert.false.add.method.checker.dialog.title: + text: Add Assert False Method +configure.checker.option.assert.false.method.panel.title: + text: Assert False Methods +configure.checker.option.assert.isNotNull.add.method.checker.dialog.title: + text: Add Assert IsNotNull Method +configure.checker.option.assert.isNotNull.method.panel.title: + text: Assert IsNotNull Methods +configure.checker.option.assert.isNull.add.method.checker.dialog.title: + text: Add Assert IsNull Method +configure.checker.option.assert.isNull.method.panel.title: + text: Assert IsNull Methods +configure.checker.option.assert.true.add.method.checker.dialog.title: + text: Add Assert True Method +configure.checker.option.assert.true.method.panel.title: + text: Assert True Methods +configure.checker.option.button: + text: Configure Assert/Check Methods +configure.checker.option.isNotNull.add.method.checker.dialog.title: + text: Add IsNotNull Check Method +configure.checker.option.isNotNull.method.panel.title: + text: IsNotNull Check Methods +configure.checker.option.isNull.add.method.checker.dialog.title: + text: Add IsNull Check Method +configure.checker.option.isNull.method.panel.title: + text: IsNull Check Methods +configure.checker.option.main.dialog.title: + text: Assert/Check Method Configuration +configure.checker.option.overlap.error.msg: + text: Configuration conflicts with +configure.checker.option.overlap.error.title: + text: Overlapping Check +dataflow.message.array.index.out.of.bounds: + text: Array index is out of bounds +dataflow.message.assigning.null: + text: null is assigned to a variable that is annotated with @NotNull +dataflow.message.assigning.nullable: + text: Expression #ref might evaluate to null but is assigned to a variable that is annotated with @NotNull +dataflow.message.cce: + text: 'Casting {0} to #ref #loc may produce java.lang.ClassCastException' +dataflow.message.constant.condition: + text: 'Condition #ref #loc is always {0}' +dataflow.message.constant.condition.when.reached: + text: 'Condition #ref #loc is always {0} when reached' +dataflow.message.constant.method.reference: + text: Method reference result is always ''{0}'' +dataflow.message.loop.on.empty.array: + text: Array #ref is always empty +dataflow.message.loop.on.empty.collection: + text: Collection #ref is always empty +dataflow.message.npe.array.access: + text: 'Array access #ref #loc may produce java.lang.NullPointerException' +dataflow.message.npe.field.access: + text: 'Dereference of #ref #loc may produce java.lang.NullPointerException' +dataflow.message.npe.field.access.sure: + text: 'Dereference of #ref #loc will produce java.lang.NullPointerException' +dataflow.message.npe.method.invocation: + text: 'Method invocation #ref #loc may produce java.lang.NullPointerException' +dataflow.message.npe.methodref.invocation: + text: 'Method reference invocation #ref #loc may produce java.lang.NullPointerException' +dataflow.message.optional.get.without.is.present: + text: {0}.#ref() without ''isPresent()'' check +dataflow.message.passing.null.argument: + text: Passing null argument to parameter annotated as @NotNull +dataflow.message.passing.nullable.argument: + text: 'Argument #ref #loc might be null' +dataflow.message.passing.nullable.argument.methodref: + text: Method reference argument might be null +dataflow.message.pointless.assignment.expression: + text: 'Condition #ref #loc at the left side of assignment expression is always {0}. Can be simplified' +dataflow.message.redundant.instanceof: + text: 'Condition #ref #loc is redundant and can be replaced with != null' +dataflow.message.return.null.from.notnull: + text: null is returned by the method declared as @{0} +dataflow.message.return.null.from.notnullable: + text: null is returned by the method which is not declared as @{0} +dataflow.message.return.nullable.from.notnull: + text: Expression #ref might evaluate to null but is returned by the method declared as @{0} +dataflow.message.return.nullable.from.notnull.function: + text: Function may return null, but it's not allowed here +dataflow.message.return.nullable.from.notnullable: + text: Expression #ref might evaluate to null but is returned by the method which is not declared as @{0} +dataflow.message.storing.array.null: + text: null is stored to an array of @NotNull elements +dataflow.message.storing.array.nullable: + text: Expression #ref might evaluate to null but is stored to an array of @NotNull elements +dataflow.message.unboxing: + text: 'Unboxing of #ref #loc may produce java.lang.NullPointerException' +dataflow.message.unboxing.method.reference: + text: 'Use of #ref #loc would need unboxing which may produce java.lang.NullPointerException' +dataflow.message.unreachable.switch.label: + text: 'Switch label #ref #loc is unreachable' +dataflow.method.fails.with.null.argument: + text: Method will throw an exception when parameter is null +dataflow.too.complex: + text: Method #ref is too complex to analyze by data flow algorithm +dataflow.too.complex.class: + text: Class initializer is too complex to analyze by data flow algorithm +default.file.template.description: + text: Default File template +default.file.template.display.name: + text: Default File Template Usage +default.file.template.edit.template: + text: Edit template... +default.file.template.replace.with.actual.file.template: + text: Replace with actual file template +default.file.template.report.catch.section: + text: Report default catch section +default.file.template.report.file.header: + text: Report Default file header +default.file.template.report.method.body: + text: Report default created/overridden/implemented method body +default.ide.profile.label.text: + text: 'Default IDE profile:' +dependency.injection.annotations.list: + text: Additional Dependency Injection Annotations +deprecated.symbol: + text: Deprecated symbol +detach.library.quickfix.name: + text: Detach library +detach.library.roots.quickfix.name: + text: Detach unused library roots +disable.inspection.action.name: + text: Disable inspection +duplicate.property.diff.key.option: + text: '&Duplicate keys with different values' +duplicate.property.diff.key.problem.descriptor: + text: 'Duplicate Property Key ''''{0}'''' With Different Values #treeend :
' +duplicate.property.diff.key.progress.indicator.text: + text: 'Processing duplicate property key: {0}' +duplicate.property.display.name: + text: Duplicate Property +duplicate.property.file.scope.option: + text: '&File scope' +duplicate.property.key.option: + text: Duplicate &keys +duplicate.property.key.problem.descriptor: + text: 'Duplicate Property Key ''''{0}'''' #treeend With Values:
' +duplicate.property.key.progress.indicator.text: + text: Processing duplicate property key:{0} +duplicate.property.module.scope.option: + text: '&Module Scope' +duplicate.property.project.scope.option: + text: '&Project Scope' +duplicate.property.value.option: + text: Duplicate &values +duplicate.property.value.problem.descriptor: + text: 'Duplicate Property Value ''''{0}'''' #treeend With Keys:
' +duplicate.property.value.progress.indicator.text: + text: 'Processing duplicate property value: {0}' +edit.dependency.rules.family: + text: Edit dependency rules +edit.dependency.rules.text: + text: Edit dependency rule "{0}" +edit.inspection.options: + text: Edit ''{0}'' Options +edit.options.of.reporter.inspection.family: + text: Edit options of reporter inspection +edit.options.of.reporter.inspection.text: + text: Edit inspection profile setting +error.analysis.is.in.progress: + text: Error analysis is in progress +errors.single.profile.title: + text: 'Errors: ''''{0}'''' inspection profile' +exports.to.itself.delete.module.ref.fix: + text: Delete reference to module ''{0}'' +exports.to.itself.delete.statement.fix: + text: Delete directive +fix.all.inspection.problems.in.file: + text: Fix all ''{0}'' problems in file +group.names.abstraction.issues: + text: Abstraction issues +group.names.assignment.issues: + text: Assignment issues +group.names.bitwise.operation.issues: + text: Bitwise operation issues +group.names.class.metrics: + text: Class metrics +group.names.class.structure: + text: Class structure +group.names.cloning.issues: + text: Cloning issues +group.names.code.maturity.issues: + text: Code maturity issues +group.names.code.style.issues: + text: Code style issues +group.names.compiler.issues: + text: Compiler issues +group.names.concurrency.annotation.issues: + text: Concurrency annotation issues +group.names.control.flow.issues: + text: Control flow issues +group.names.data.flow.issues: + text: Data flow issues +group.names.declaration.redundancy: + text: Declaration redundancy +group.names.dependency.issues: + text: Dependency issues +group.names.encapsulation.issues: + text: Encapsulation issues +group.names.error.handling: + text: Error handling +group.names.finalization.issues: + text: Finalization issues +group.names.imports: + text: Imports +group.names.inheritance.issues: + text: Inheritance issues +group.names.initialization.issues: + text: Initialization issues +group.names.internationalization.issues: + text: Internationalization issues +group.names.j2me.issues: + text: J2ME issues +group.names.java.language.level.issues: + text: Language level issues +group.names.javabeans.issues: + text: JavaBeans issues +group.names.javadoc.issues: + text: Javadoc issues +group.names.javaee.issues: + text: Java EE issues +group.names.junit.issues: + text: JUnit issues +group.names.language.level.specific.issues.and.migration.aids: + text: Language level migration aids +group.names.language.level.specific.issues.and.migration.aids14: + text: Java 14 +group.names.language.level.specific.issues.and.migration.aids5: + text: Java 5 +group.names.language.level.specific.issues.and.migration.aids7: + text: Java 7 +group.names.language.level.specific.issues.and.migration.aids8: + text: Java 8 +group.names.language.level.specific.issues.and.migration.aids9: + text: Java 9 +group.names.logging.issues: + text: Logging issues +group.names.memory.issues: + text: Memory issues +group.names.method.metrics: + text: Method metrics +group.names.modularization.issues: + text: Modularization issues +group.names.naming.conventions: + text: Naming conventions +group.names.numeric.issues: + text: Numeric issues +group.names.packaging.issues: + text: Packaging issues +group.names.performance.issues: + text: Performance issues +group.names.portability.issues: + text: Portability issues +group.names.potentially.confusing.code.constructs: + text: Potentially confusing code constructs +group.names.probable.bugs: + text: Probable bugs +group.names.properties.files: + text: Properties Files +group.names.reflective.access.issues: + text: Reflective access issues +group.names.resource.management.issues: + text: Resource management issues +group.names.security.issues: + text: Security issues +group.names.serialization.issues: + text: Serialization issues +group.names.threading.issues: + text: Threading issues +group.names.toString.issues: + text: toString() issues +group.names.verbose.or.redundant.code.constructs: + text: Verbose or redundant code constructs +group.names.visibility.issues: + text: Visibility issues +group.names.xml: + text: XML +highlight.severity.create.dialog.name.label: + text: Name +highlight.severity.create.dialog.title: + text: New Highlight Severity +illegal.package.dependencies: + text: Illegal package dependencies +inconsistent.bundle.property.error: + text: Inconsistent property ''{0}''. Must be defined in the parent file ''{1}''. +inconsistent.bundle.property.inconsistent.end: + text: Inconsistent property value end ''{0}'' +inconsistent.bundle.property.inconsistent.end.parent.end.from.check.symbols: + text: Inconsistent property value end ''{0}'' but found ''{1}'' in ''{2}'' +inconsistent.bundle.property.inconsistent.placeholders: + text: 'Inconsistent count of placeholders: found {0} in ''''{1}''''' +inconsistent.bundle.property.inherited.with.the.same.value: + text: Property inherited from the ''{0}'' file with the same value +inconsistent.bundle.report.duplicate.properties.values: + text: Report properties &overridden with the same value +inconsistent.bundle.report.inconsistent.properties: + text: Report &inconsistent properties +inconsistent.bundle.report.inconsistent.properties.ends: + text: Report properties with inconsistent &ends +inconsistent.bundle.report.inconsistent.properties.placeholders: + text: Report properties with inconsistent &placeholders +inconsistent.bundle.report.missing.translations: + text: Report &missing translations +inconsistent.bundle.untranslated.property.error: + text: Untranslated property ''{0}''. Should be overridden in the ''{1}''. +inconsistent.line.separators: + text: Inconsistent line separators +inconsistent.resource.bundle.display.name: + text: Inconsistent Resource Bundle +inspection.1.5.display.name: + text: Usages of API which isn't available at the configured language level +inspection.1.5.problem.descriptor: + text: Usage of API documented as @since {0}+ +inspection.1.7.problem.descriptor: + text: Usage of generified after 1.6 API which would cause compilation problems with JDK {0} +inspection.1.8.problem.descriptor: + text: Default {0, choice, 0#|1#method is|2# methods are} not overridden. It would cause compilation problems with JDK {1} +inspection.1.8.problem.single.descriptor: + text: Default method ''{0}'' is not overridden. It would cause compilation problems with JDK {1} +inspection.action.apply.quickfix: + text: Apply a quickfix +inspection.action.apply.quickfix.description: + text: Apply an inspection quickfix +inspection.action.edit.settings: + text: Edit Settings +inspection.action.export.html: + text: Export +inspection.action.export.popup.title: + text: Export To +inspection.action.go.next: + text: Go Next Problem +inspection.action.group.by.severity: + text: Group by Severity +inspection.action.group.by.severity.description: + text: Group Inspections By Severity +inspection.action.noun: + text: Inspection +inspection.action.profile.label: + text: Inspection profile +inspection.action.rerun: + text: Rerun Inspection +inspection.action.suppress: + text: Suppressing inspection ''{0}'' +inspection.action.title: + text: Inspection +inspection.actiongo.prev: + text: Go Prev Problem +inspection.annotate.method.quickfix.family.name: + text: Annotate method +inspection.annotate.overridden.method.and.self.quickfix.family.name: + text: Annotate overridden methods and self +inspection.annotate.overridden.method.quickfix.family.name: + text: Annotate overridden methods +inspection.annotate.quickfix.implements: + text: implements +inspection.annotate.quickfix.overridden.method.messages: + text: |- + Method {0} {1} method {2}. + Annotate the base method as well? +inspection.annotate.quickfix.overridden.method.warning: + text: Overridden Method Warning +inspection.annotate.quickfix.overrides: + text: overrides +inspection.application.cannot.convert.project.0: + text: 'Cannot convert project: {0}' +inspection.application.cannot.convert.the.project.the.following.files.are.read.only.0: + text: 'Cannot convert the project. The following files are read only: {0}' +inspection.application.chosen.profile.log.message: + text: Inspecting with profile ''{0}'' +inspection.application.directory.cannot.be.found: + text: Directory {0} cannot be found +inspection.application.file.cannot.be.found: + text: File {0} cannot be found +inspection.application.initializing.project: + text: Initializing project... +inspection.application.opening.project: + text: Opening project... +inspection.application.project.has.older.format.and.will.be.converted: + text: Project has an older format and will be converted. +inspection.application.project.was.succesfully.converted.old.project.files.were.saved.to.0: + text: Project was succesfully converted. Old project files were saved to {0} +inspection.application.starting.up: + text: Starting up {0} ... +inspection.as: + text: As {0} +inspection.assert.quickfix: + text: Assert ''{0}'' +inspection.can.be.final.accept.quickfix: + text: Make final +inspection.can.be.final.display.name: + text: Declaration can have final modifier +inspection.can.be.final.option: + text: Report classes +inspection.can.be.final.option1: + text: Report methods +inspection.can.be.final.option2: + text: Report fields +inspection.can.be.local.parameter.problem.descriptor: + text: Parameter #ref can have final modifier +inspection.can.be.local.variable.problem.descriptor: + text: Variable #ref can have final modifier +inspection.capitalized.done: + text: Done. +inspection.collection.factories.fix.family.name: + text: Replace with collection factory call +inspection.collection.factories.fix.name: + text: Replace with ''{0}.of'' call +inspection.collection.factories.message: + text: Can be replaced with ''{0}.of'' call +inspection.collection.factories.option.ignore.non.constant: + text: Do not warn when content is non-constant +inspection.command.line.explanation: + text: "Expected parameters: -- use here profile name configured in the project or locally or path to the inspection profile; can be stabbed when one of the -e|-profileName|-profilePath is used\n[]\n Available options are:\n -d -- directory to be inspected. Optional. Whole project is inspected by default.\n -e -- skip \n-v[0|1|2] -- verbose level. 0 - silent, 1 - verbose, 2 - most verbose. \n-profileName -- name of a profile defined in project \n -profilePath -- absolute path to the profile file" +inspection.comparator.result.comparison.display.name: + text: Suspicious usage of compare method +inspection.comparator.result.comparison.fix.family.name: + text: Fix comparator result comparison +inspection.comparator.result.comparison.problem.display.name: + text: Comparison of compare method result with specific constant +inspection.comparing.references.display.name: + text: == used instead of equals() +inspection.comparing.references.problem.descriptor: + text: 'Suspicious comparison #ref #loc' +inspection.comparing.references.use.quickfix: + text: Use equals() +inspection.compiler.javac.quirks.anno.array.comma.fix: + text: Remove trailing comma +inspection.compiler.javac.quirks.anno.array.comma.problem: + text: Trailing comma in annotation array initializer may cause compilation error in some Javac versions (e.g. JDK 5 and JDK 6). +inspection.compiler.javac.quirks.name: + text: Javac quirks +inspection.compiler.javac.quirks.qualifier.type.args.fix: + text: Remove generic parameter +inspection.compiler.javac.quirks.qualifier.type.args.problem: + text: Generics in qualifier reference may cause compilation error in some Javac versions (e.g. JDK 5 and JDK 6). +inspection.contract.display.name: + text: Contract issues +inspection.convert.to.local.quickfix: + text: Convert to local +inspection.data.flow.display.name: + text: Constant conditions & exceptions +inspection.data.flow.filter.notnull.quickfix: + text: Insert 'filter(Objects::nonNull)' step +inspection.data.flow.ignore.assert.statements: + text: Ignore assert statements +inspection.data.flow.nullable.quickfix.option: + text: Suggest @Nullable annotation for methods that may possibly return null and report nullable values passed to non-annotated parameters +inspection.data.flow.redundant.instanceof.quickfix: + text: Replace with != null +inspection.data.flow.report.constant.reference.values: + text: Warn when reading a value guaranteed to be constant +inspection.data.flow.report.not.null.required.parameter.with.null.literal.argument.usages: + text: Report non-null required parameter with null-literal argument usages +inspection.data.flow.report.nullable.methods.that.always.return.a.non.null.value: + text: Report nullable methods that always return a non-null value +inspection.data.flow.report.problems.that.happen.only.on.some.code.paths: + text: Report problems that happen only on some code paths +inspection.data.flow.simplify.boolean.expression.quickfix: + text: Simplify boolean expression +inspection.data.flow.simplify.to.assignment.quickfix.name: + text: Simplify to normal assignment +inspection.data.flow.treat.non.annotated.members.and.parameters.as.nullable: + text: Treat non-annotated members and parameters as @Nullable +inspection.data.flow.true.asserts.option: + text: Don't report assertions with condition statically proven to be always true +inspection.data.flow.use.computeifpresent.quickfix: + text: Replace 'compute' with 'computeIfPresent' +inspection.dead.code.comment: + text: // --Commented out by Inspection +inspection.dead.code.comment.quickfix: + text: Comment out +inspection.dead.code.date.comment: + text: '// --Commented out by Inspection ({0}):' +inspection.dead.code.display.name: + text: Unused declaration +inspection.dead.code.entry.point.quickfix: + text: Add as Entry Point +inspection.dead.code.entry.points.display.name: + text: Entry Points +inspection.dead.code.export.results.instantiated.from.heading: + text: Instantiated from +inspection.dead.code.export.results.no.instantiations.found: + text: No instantiations found. +inspection.dead.code.option.applet: + text: Applets +inspection.dead.code.option.external: + text: Classes with usages in non-Java files +inspection.dead.code.option.main: + text: void main(String args[]) methods +inspection.dead.code.option.servlet: + text: Servlets +inspection.dead.code.problem.synopsis: + text: Field is never assigned. +inspection.dead.code.problem.synopsis1: + text: Field has no usages. +inspection.dead.code.problem.synopsis10: + text: Anonymous class declaration context is not reachable from entry points. Class is never instantiated. +inspection.dead.code.problem.synopsis11: + text: No class references has been found. Class static initializer is not reachable. +inspection.dead.code.problem.synopsis12: + text: Class has one instantiation but it is not reachable from entry points. +inspection.dead.code.problem.synopsis13: + text: Class is not instantiated. +inspection.dead.code.problem.synopsis14: + text:

  • Abstract method is not implemented OR
  • Implementation class is never instantiated OR
  • An instantiation is not reachable from entry points.
+inspection.dead.code.problem.synopsis15: + text:
  • Method owner class is never instantiated OR
  • An instantiation is not reachable from entry points.
+inspection.dead.code.problem.synopsis16: + text: Method is never used. +inspection.dead.code.problem.synopsis17: + text: Method has usage(s) but they all belong to calls chain that has no members reachable from entry points. +inspection.dead.code.problem.synopsis18: + text: Method is not reachable from entry points. +inspection.dead.code.problem.synopsis19: + text: Neither the class nor {0, choice, 1#its implementation|2#{0,number} its implementations} are ever instantiated. +inspection.dead.code.problem.synopsis2: + text: Field is assigned but never accessed. +inspection.dead.code.problem.synopsis20: + text: Class has {0, choice, 1#instantiation|2#{0,number} instantiations} but they are not reachable from entry points. +inspection.dead.code.problem.synopsis21: + text: Method is never used as a member of this {0}, but only as a member of the implementation class(es). The project will stay compilable if the method is removed from the {0}. +inspection.dead.code.problem.synopsis22: + text: Method overrides a library method but
  • its {0} is never instantiated OR
  • its {0} instantiation is not reachable from entry points.
+inspection.dead.code.problem.synopsis23: + text: '{0} is not implemented.' +inspection.dead.code.problem.synopsis24: + text: '{0} has an implementation but
  • it is never instantiated OR
  • no instantiations are reachable from entry points.
' +inspection.dead.code.problem.synopsis25: + text: '{0} has {1, choice, 1#direct or indirect implementation|2#{1,number} direct or indirect implementations} but
  • —they are never instantiated OR
  • —no instantiations are reachable from entry points.
' +inspection.dead.code.problem.synopsis26.constructor: + text: Constructor is never used. +inspection.dead.code.problem.synopsis26.method: + text: Method is never used. +inspection.dead.code.problem.synopsis27.constructor: + text: Constructor has usage(s) but they all belong to calls chain that has no members reachable from entry points. +inspection.dead.code.problem.synopsis27.method: + text: Method has usage(s) but they all belong to calls chain that has no members reachable from entry points. +inspection.dead.code.problem.synopsis28.constructor: + text: Constructor has one usage but it is not reachable from entry points. +inspection.dead.code.problem.synopsis28.method: + text: Method has one usage but it is not reachable from entry points. +inspection.dead.code.problem.synopsis29.constructor: + text: Constructor has {0, choice, 1#usage|2#{0,number} usages} but they are not reachable from entry points. +inspection.dead.code.problem.synopsis29.method: + text: Method has {0, choice, 1#usage|2#{0,number} usages} but they are not reachable from entry points. +inspection.dead.code.problem.synopsis3: + text: Field has one usage but it is not reachable from entry points. +inspection.dead.code.problem.synopsis4: + text: Field has {0, choice, 1#1 usage|2#{0,number} usages} but they are not reachable from entry points. +inspection.dead.code.problem.synopsis6: + text: Reachable. {0, choice, 1#1 usage|2#{0, number} usages} found in the project code. +inspection.dead.code.problem.synopsis6.suspicious: + text: Not Reachable. {0, choice, 1#1 usage|2#{0, number} usages} found in the project code. +inspection.dead.code.problem.synopsis7: + text: Has reachable instantiations. {0, choice, 1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.problem.synopsis7.suspicious: + text: Has no reachable instantiations. {0, choice, 0#No instantiations|1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.problem.synopsis8: + text: Has reachable implementation instantiations. {0, choice, 1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.problem.synopsis8.suspicious: + text: Has no reachable implementation instantiations. {0, choice, 1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.problem.synopsis9: + text: Instantiated {0, choice, 1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.problem.synopsis9.suspicious: + text: Anonymous class context is not reachable. Class is not instantiated. {0, choice, 1#1 instantiation|2#{0, number} instantiations} found in the project code. +inspection.dead.code.remove.from.entry.point.quickfix: + text: Remove from Entry Points +inspection.dead.code.remove.user.defined.entry.point.quickfix: + text: Remove User Defined Entry Points +inspection.dead.code.safe.delete.quickfix: + text: Safe delete +inspection.dead.code.start.comment: + text: | + // --Commented out by Inspection START ({0}): +inspection.dead.code.stop.comment: + text: | + // --Commented out by Inspection STOP ({0}) +inspection.default.annotation.param: + text: Default annotation parameter value +inspection.dependency.configure.button.text: + text: Configure dependency rules +inspection.dependency.violator.problem.descriptor: + text: Dependency rule ''{0}.'' is violated +inspection.deprecated.display.name: + text: Deprecated API usage +inspection.description.title: + text: Description +inspection.diff.format.error: + text: 'Required parameters: []' +inspection.disabled.error: + text: 'Inspection ''''{0}'''' is disabled: {1}.' +inspection.disabled.title: + text: Inspection disabled +inspection.disabled.wrong.id: + text: 'Inspection ''''{0}'''' is disabled: ID ''''{1}'''' not matches ''''{2}'''' pattern.' +inspection.display.name: + text: Analyzing code... +inspection.done: + text: done. +inspection.duplicate.throws.display.name: + text: Duplicate throws +inspection.duplicate.throws.ignore.subclassing.option: + text: Ignore exceptions subclassing others +inspection.duplicate.throws.more.general.problem: + text: There is a more general exception, ''{0}'', in the throws list already. +inspection.duplicate.throws.problem: + text: Duplicate throws +inspection.duplicated.code.display.name: + text: Duplicated Code +inspection.duplicates.display.name: + text: Duplicate string literal +inspection.duplicates.message: + text: Duplicate string literal found in
{0} +inspection.duplicates.message.in.this.file: + text: (in this file) +inspection.duplicates.message.more: + text: '... ({0} more)' +inspection.duplicates.navigate.to.occurrences: + text: Navigate to occurrences +inspection.duplicates.occurrences.view.title: + text: Duplicates for ''{0}'' +inspection.duplicates.option: + text: '&Min string length:' +inspection.duplicates.option.report.propertykey.expressions: + text: '&Ignore @PropertyKey expressions' +inspection.duplicates.replace.family.quickfix: + text: Replace +inspection.duplicates.replace.quickfix: + text: Replace with ''{0}'' +inspection.empty.method.delete.quickfix: + text: Delete unnecessary method(s) +inspection.empty.method.display.name: + text: Empty method +inspection.empty.method.problem.descriptor: + text: Method only calls its super +inspection.empty.method.problem.descriptor1: + text: Empty method overrides empty method +inspection.empty.method.problem.descriptor2: + text: The method is empty +inspection.empty.method.problem.descriptor3: + text: The method and all its derivables are empty +inspection.empty.method.problem.descriptor4: + text: All implementations of this method are empty +inspection.equals.hashcode.display.name: + text: equals() and hashCode() not paired +inspection.equals.hashcode.generate.equals.quickfix: + text: Generate 'equals()' +inspection.equals.hashcode.generate.hashcode.quickfix: + text: Generate 'hashCode()' +inspection.equals.hashcode.only.one.defined.problem.descriptor: + text: Class has {0} defined but does not define {1} +inspection.error.level.popup.menu.title: + text: Choose Error Level +inspection.error.loading.message: + text: Error reading inspection profile {0, choice, 0#from {1}|1#} +inspection.errors.occurred.dialog.title: + text: Errors Occurred +inspection.excessive.lambda.fix.family.name: + text: Replace lambda with constant +inspection.excessive.lambda.fix.name: + text: Use ''{0}'' method without lambda +inspection.excessive.lambda.message: + text: Excessive lambda usage +inspection.export.dialog.title: + text: Export +inspection.export.error.writing.to: + text: 'Error writing to {0}: {1}' +inspection.export.generating.html.for: + text: Generating HTML:{0} +inspection.export.inspections.link.text: + text: Inspections +inspection.export.open.option: + text: Open generated HTML in &browser +inspection.export.open.source.link.text: + text: Open source +inspection.export.options.panel.title: + text: Options +inspection.export.results.abstract: + text: abstract +inspection.export.results.abstract.class: + text: abstract\ class +inspection.export.results.anonymous.ref.in.owner: + text: in +inspection.export.results.at.line: + text: at line +inspection.export.results.callees: + text: Call chain +inspection.export.results.can.be.final.description: + text: Declaration can have final modifier +inspection.export.results.capitalized.abstract.class: + text: Abstract\ class +inspection.export.results.capitalized.class: + text: Class +inspection.export.results.capitalized.interface: + text: Interface +inspection.export.results.capitalized.location: + text: Location +inspection.export.results.class: + text: class +inspection.export.results.constructor: + text: constructor +inspection.export.results.dead.code: + text: unused declaration +inspection.export.results.derived.methods: + text: Derived methods +inspection.export.results.description.tag: + text: description +inspection.export.results.error.title: + text: Inspection Results Export +inspection.export.results.extended: + text: Extended by +inspection.export.results.extended.implemented: + text: Extended/implemented by +inspection.export.results.extends.implements: + text: Extends/implements +inspection.export.results.field: + text: field +inspection.export.results.file: + text: file +inspection.export.results.implicit.constructor: + text: implicit constructor of +inspection.export.results.initializer: + text: initializer +inspection.export.results.interface: + text: interface +inspection.export.results.invalidated.item: + text: invalidated item +inspection.export.results.method: + text: method +inspection.export.results.no.problems.found: + text: No problems found +inspection.export.results.overrides.implements: + text: Overrides/implements +inspection.export.results.overrides.library.methods: + text: Overrides library methods +inspection.export.results.package: + text: package +inspection.export.results.problem.element.tag: + text: problem_class +inspection.export.results.static: + text: static +inspection.export.results.suppress: + text: Suppress +inspection.export.results.type.references: + text: The following uses this type +inspection.export.results.used.from: + text: Used from +inspection.export.results.uses: + text: Uses the following +inspection.export.save.button: + text: Save +inspection.export.title: + text: Code Inspection results +inspection.field.can.be.local.display.name: + text: Field can be local +inspection.field.can.be.local.problem.descriptor: + text: Field can be converted to a local variable +inspection.filter.resolved.action.text: + text: Filter resolved items +inspection.filter.show.diff.action.text: + text: Show diff +inspection.filter.show.diff.only.action.text: + text: Show diff only +inspection.general.tools.group.name: + text: General +inspection.generating.html.progress.title: + text: Generating HTML... +inspection.generating.xml.progress.title: + text: Dumping XML... +inspection.handle.signature.change.type.fix.name: + text: Change type to ''{0}'' +inspection.handle.signature.field.cannot.resolve: + text: Cannot resolve field ''{0}'' +inspection.handle.signature.field.not.static: + text: Field ''{0}'' is not static +inspection.handle.signature.field.static: + text: Field ''{0}'' is static +inspection.handle.signature.field.type: + text: The type of field ''{0}'' is ''{1}'' +inspection.handle.signature.method.not.static: + text: Method ''{0}'' is not static +inspection.handle.signature.method.static: + text: Method ''{0}'' is static +inspection.handle.signature.name: + text: MethodHandle/VarHandle type mismatch +inspection.handle.signature.replace.with.fix.name: + text: Replace with ''{0}'' +inspection.handle.signature.use.constructor.fix.family.name: + text: Use one of constructor overloads +inspection.handle.signature.use.constructor.fix.name: + text: Use constructor ''{0}'' +inspection.handle.signature.use.method.fix.family.name: + text: Use one of method overloads +inspection.handle.signature.use.method.fix.name: + text: Use method ''{0}'' +inspection.illegal.character: + text: Illegal character +inspection.implicit.subclass.display.forClass: + text: Class ''{0}'' could be implicitly subclassed and must not be final +inspection.implicit.subclass.display.forMethod: + text: Method ''{0}'' should be overridable +inspection.implicit.subclass.display.name: + text: Final declaration can't be overridden at runtime +inspection.implicit.subclass.extendable: + text: Make ''{0}'' overridable +inspection.implicit.subclass.make.class.extendable: + text: Make class ''{0}'' {1,choice,0#|1#and method {2} |1@param is missing for parameter {0} +inspection.javadoc.method.problem.missing.tag.description: + text: '{0} tag description is missing' +inspection.javadoc.option.ignore.deprecated: + text: Ignore elements marked as @deprecated +inspection.javadoc.option.ignore.period: + text: Ignore period problems +inspection.javadoc.option.tab.title: + text: Class +inspection.javadoc.option.tab.title.field: + text: Field +inspection.javadoc.option.tab.title.inner.class: + text: Inner class +inspection.javadoc.option.tab.title.method: + text: Method +inspection.javadoc.option.tab.title.package: + text: Package +inspection.javadoc.problem.add.param.tag: + text: Add tag @param for parameter ''{0}'' +inspection.javadoc.problem.add.param.tag.family: + text: Add missing Javadoc param tag +inspection.javadoc.problem.add.tag: + text: Add tag @{0} {1} +inspection.javadoc.problem.add.tag.family: + text: Add missing Javadoc tag +inspection.javadoc.problem.cannot.resolve: + text: Cannot resolve symbol {0} +inspection.javadoc.problem.descriptor: + text: Required Javadoc is absent +inspection.javadoc.problem.descriptor1: + text: Period in the documentation is missing. The period is used by the Javadoc tool to generate the comment for the overview page +inspection.javadoc.problem.disallowed.tag: + text: Tag {0} is not allowed here +inspection.javadoc.problem.duplicate.param: + text: Duplicate @param tag for parameter ''{0}'' +inspection.javadoc.problem.duplicate.tag: + text: Duplicate @{0} tag +inspection.javadoc.problem.duplicate.throws: + text: Duplicate @throws or @exception tag for exception ''{0}'' +inspection.javadoc.problem.missing.tag: + text: Required tag {0} is missing +inspection.javadoc.problem.missing.tag.description: + text: '{0} is missing after @{1} tag' +inspection.javadoc.problem.name.expected: + text: Name expected +inspection.javadoc.problem.pointing.to.itself: + text: Javadoc pointing to itself +inspection.javadoc.problem.see.tag.expecting.ref: + text: Class/method reference, quoted text, or HTML link are expected after @see tag +inspection.javadoc.problem.wrong.tag: + text: Wrong tag {0} +inspection.javadoc.ref.display.name: + text: Declaration has problems in Javadoc references +inspection.javadoc.required.tags.option.title: + text: Required Tags +inspection.javadoc.throws.or.exception.option: + text: '@throws or @exception' +inspection.lambda.to.method.call.fix.family.name: + text: Replace lambda expression with method call +inspection.lambda.to.method.call.fix.name: + text: Replace lambda expression with ''{0}'' +inspection.lambda.to.method.call.message: + text: Can be replaced with ''{0}'' +inspection.local.can.be.final.display.name: + text: Local variable or parameter can be final +inspection.local.can.be.final.option: + text: Report local variables +inspection.local.can.be.final.option1: + text: Report method parameters +inspection.local.can.be.final.option2: + text: Report catch parameters +inspection.local.can.be.final.option3: + text: Report foreach parameters +inspection.local.can.be.final.option4: + text: Report variables which are implicit final +inspection.map.foreach.fix.name: + text: Replace with Map.forEach +inspection.map.foreach.message: + text: Can be replaced with 'Map.forEach' +inspection.map.foreach.option.no.loops: + text: Do not report loops +inspection.marked.for.removal.display.name: + text: Usage of API marked for removal +inspection.message.redundant.default.parameter.value.assignment: + text: Redundant default parameter value assignment +inspection.module.exports.package.to.itself: + text: Module exports/opens package to itself +inspection.new.profile.dialog.title: + text: Create new profile +inspection.new.profile.ide.to.project.warning.message: + text: Do you want to save selected IDE profile as project profile? +inspection.new.profile.ide.to.project.warning.title: + text: Unable to set up IDE profile for scope +inspection.new.profile.text: + text: New Profile Name +inspection.no.jdk.error.message: + text: The JDK is not configured properly for this project. Inspection cannot proceed. +inspection.no.modules.error.message: + text: This project contains no modules. Inspection cannot proceed. +inspection.no.problems.dialog.title: + text: Code Inspection +inspection.no.problems.message: + text: No suspicious code found. {0} files processed in ''{1}''. +inspection.null.value.for.optional.context.assignment: + text: assignment +inspection.null.value.for.optional.context.declaration: + text: declaration +inspection.null.value.for.optional.context.lambda: + text: lambda expression +inspection.null.value.for.optional.context.parameter: + text: parameter +inspection.null.value.for.optional.context.return: + text: return statement +inspection.null.value.for.optional.fix.family.name: + text: Replace with empty Optional method +inspection.null.value.for.optional.fix.name: + text: Replace with ''{0}'' +inspection.null.value.for.optional.message: + text: Null is used for ''Optional'' type in {0} +inspection.nullable.problems.NotNull.parameter.overrides.Nullable: + text: Parameter annotated @{0} must not override @{1} parameter +inspection.nullable.problems.NotNull.parameter.overrides.not.annotated: + text: Parameter annotated @{0} should not override non-annotated parameter +inspection.nullable.problems.NotNull.parameter.receives.null.literal: + text: Parameter annotated @{0} should not receive 'null' as an argument +inspection.nullable.problems.Nullable.NotNull.conflict: + text: Cannot annotate with both @{0} and @{1} +inspection.nullable.problems.Nullable.method.overrides.NotNull: + text: Method annotated with @{0} must not override @{1} method +inspection.nullable.problems.annotated.field.constructor.parameter.not.annotated: + text: Constructor parameter for @{0} field might be annotated @{0} itself +inspection.nullable.problems.annotated.field.getter.conflict: + text: Getter for @{0} field is annotated @{1} +inspection.nullable.problems.annotated.field.getter.not.annotated: + text: Getter for @{0} field might be annotated @{0} itself +inspection.nullable.problems.annotated.field.setter.parameter.conflict: + text: Setter parameter for @{0} field is annotated @{1} +inspection.nullable.problems.annotated.field.setter.parameter.not.annotated: + text: Setter parameter for @{0} field might be annotated @{0} itself +inspection.nullable.problems.display.name: + text: '@NotNull/@Nullable problems' +inspection.nullable.problems.method.overrides.NotNull: + text: Not annotated method overrides method annotated with @{0} +inspection.nullable.problems.method.overrides.notnull.option: + text: Report @NotNull ¶meters overriding @Nullable and @Nullable methods overriding @NotNull +inspection.nullable.problems.method.overrides.option: + text: Report non-&annotated parameters or methods overriding @NotNull +inspection.nullable.problems.not.annotated.getters.for.annotated.fields: + text: Report non-annotated &setter parameters or getters of annotated fields +inspection.nullable.problems.parameter.overrides.NotNull: + text: Not annotated parameter overrides @{0} parameter +inspection.nullable.problems.primitive.type.annotation: + text: Primitive type members cannot be annotated +inspection.offline.view.empty.browser.text: + text: Select inspection result on the left to see details +inspection.offline.view.tool.display.name.title: + text: Name +inspection.options.action.text: + text: Inspection ''{0}'' options +inspection.parameter.can.be.local.display.name: + text: Parameter can be local +inspection.parameter.can.be.local.problem.descriptor: + text: Parameter can be converted to a local variable +inspection.problem.descriptor.count: + text: '{0, choice, 0#|1#(1 item)|2#({0,number,integer} items)}' +inspection.problem.resolution: + text: Problem resolution +inspection.problem.synopsis: + text: Problem synopsis +inspection.problems: + text: problems +inspection.processing.job.descriptor: + text: Processing project usages in +inspection.processing.job.descriptor1: + text: Processing external usages of +inspection.processing.job.descriptor2: + text: Analyzing code in +inspection.profile.unnamed: + text: Unnamed +inspection.progress.title: + text: Inspecting Code... +inspection.quickfix.assert.family: + text: Assert +inspection.raw.variable.type.can.be.generic.family.quickfix: + text: Add generic parameters to the type +inspection.raw.variable.type.can.be.generic.name: + text: Raw type can be generic +inspection.raw.variable.type.can.be.generic.quickfix: + text: Change type of {0} to {1} +inspection.redirect.template: + text: 'Injected element has problem: {0} (in {3}). ' +inspection.redundant.array.creation.display.name: + text: Redundant array creation +inspection.redundant.array.creation.for.varargs.call.descriptor: + text: Redundant array creation for calling varargs method +inspection.redundant.array.creation.quickfix: + text: Remove explicit array creation +inspection.redundant.cast.display.name: + text: Redundant type cast +inspection.redundant.cast.problem.descriptor: + text: Casting {0} to {1} is redundant +inspection.redundant.cast.remove.quickfix: + text: Remove redundant cast(s) +inspection.redundant.requires.statement.description: + text: Redundant directive ''requires {0}'' +inspection.redundant.requires.statement.fix.family: + text: Delete redundant 'requires' directive +inspection.redundant.requires.statement.fix.name: + text: Delete directive ''requires {0}'' +inspection.redundant.requires.statement.name: + text: Redundant 'requires' directive in module-info +inspection.redundant.stream.optional.call.explanation.distinct: + text: there already was a 'distinct' call in the chain +inspection.redundant.stream.optional.call.explanation.filter: + text: predicate is always true +inspection.redundant.stream.optional.call.explanation.parallel: + text: there''s subsequent ''{0}'' call which overrides this call +inspection.redundant.stream.optional.call.explanation.sorted: + text: subsequent ''{0}'' call makes sorting useless +inspection.redundant.stream.optional.call.explanation.unordered: + text: there already was an 'unordered' call in the chain +inspection.redundant.stream.optional.call.fix.family.name: + text: Remove redundant chain call +inspection.redundant.stream.optional.call.fix.name: + text: Remove ''{0}'' call +inspection.redundant.stream.optional.call.message: + text: Redundant ''{0}'' call +inspection.redundant.stream.optional.call.option.streamboxing: + text: Report useless boxing in Stream.map +inspection.redundant.suppression.description: + text: Redundant suppression +inspection.redundant.suppression.name: + text: Redundant suppression +inspection.redundant.throws.display.name: + text: Redundant throws clause +inspection.redundant.throws.problem.descriptor: + text: The declared exception {0} is never thrown in method implementations +inspection.redundant.throws.problem.descriptor1: + text: The declared exception {0} is never thrown in this method, nor in its derivables +inspection.redundant.throws.problem.descriptor2: + text: The declared exception {0} is never thrown +inspection.redundant.throws.remove.quickfix: + text: Remove unnecessary throws declarations +inspection.redundant.type.display.name: + text: Redundant type arguments +inspection.redundant.type.problem.descriptor: + text: Explicit type arguments can be inferred +inspection.redundant.type.remove.quickfix: + text: Remove explicit type arguments +inspection.reference.anonymous: + text: anonymous +inspection.reference.anonymous.class: + text: anonymous class +inspection.reference.anonymous.name: + text: anonymous ({0}) +inspection.reference.default.lambda.name: + text: lambda +inspection.reference.default.method.reference.name: + text: method reference +inspection.reference.default.package: + text: +inspection.reference.implicit.class: + text: compact source file ''{0}'' +inspection.reference.implicit.constructor.name: + text: implicit constructor of {0} +inspection.reference.invalid: + text: element no longer exists +inspection.reference.jsp.holder.method.anonymous.name: + text: <% page content %> +inspection.reference.jsp.synthetic.class.name: + text: <{0}> +inspection.reference.lambda.name: + text: lambda ({0}) +inspection.reference.method.reference.name: + text: method reference ({0}) +inspection.reference.noname: + text: noname +inspection.reflect.handle.invocation.argument.not.array: + text: Argument is not an array type +inspection.reflect.handle.invocation.argument.not.exact: + text: Argument type should be exactly ''{0}'' +inspection.reflect.handle.invocation.primitive.argument.null: + text: Argument of type ''{0}'' cannot be ''null'' +inspection.reflect.handle.invocation.receiver.incompatible: + text: 'Call receiver type is incompatible: ''''{0}'''' is expected' +inspection.reflect.handle.invocation.receiver.null: + text: Call receiver is 'null' +inspection.reflect.handle.invocation.result.not.assignable: + text: Should be cast to ''{0}'' or its superclass +inspection.reflect.handle.invocation.result.not.exact: + text: Should be cast to ''{0}'' +inspection.reflect.handle.invocation.result.null: + text: Returned value is always 'null' +inspection.reflect.handle.invocation.result.void: + text: Return type is 'void' +inspection.reflection.invocation.argument.count: + text: '{0,choice,0#No arguments are|1#One argument is|1<{0} arguments are} expected' +inspection.reflection.invocation.argument.not.assignable: + text: Argument is not assignable to ''{0}'' +inspection.reflection.invocation.array.not.assignable: + text: Array {0,choice,1#item has|1#ref is not used in any implementation +inspection.unused.parameter.composer1: + text: Parameter #ref is not used in either this method or any of its derived methods +inspection.unused.parameter.delete.quickfix: + text: Delete unused parameter(s) +inspection.unused.parameter.display.name: + text: Unused method parameters +inspection.unused.return.value.display.name: + text: Method can be void +inspection.unused.return.value.make.void.quickfix: + text: Make method 'void' +inspection.unused.return.value.problem.descriptor: + text: Return value of the method is never used +inspection.unused.symbol.check.accessors: + text: Getters/setters +inspection.unused.symbol.check.classes: + text: 'Classes:' +inspection.unused.symbol.check.fields: + text: 'Fields:' +inspection.unused.symbol.check.inner.classes: + text: 'Inner classes:' +inspection.unused.symbol.check.localvars: + text: Local variables +inspection.unused.symbol.check.methods: + text: 'Methods:' +inspection.unused.symbol.check.parameters: + text: Parameters in +inspection.unused.symbol.public.method.parameters: + text: Check parameters in &Non-private methods +inspection.useless.null.check.always.fail.message: + text: 'Null-check will always fail: {0} is never null' +inspection.useless.null.check.fix.family.name: + text: Remove useless null-check +inspection.useless.null.check.message: + text: 'Useless null-check: {0} is never null' +inspection.variable.assigned.to.itself.display.name: + text: Variable is assigned to itself +inspection.view.invalid.scope.message: + text: Inspection scope is invalid. +inspection.visibility.accept.quickfix: + text: Accept suggested access level +inspection.visibility.compose.suggestion: + text: Can be {0} +inspection.visibility.display.name: + text: Declaration access can be weaker +inspection.visibility.option: + text: Suggest package-private visibility level for class members +inspection.visibility.option1: + text: Suggest package-private visibility level for top-level classes +inspection.visibility.option2: + text: Suggest private for inner class members when referenced from outer class only +inspections.dead.code.entry.points.annotations.list.title: + text: Additional entry points annotations +inspections.view.options.title: + text: Inspection {0} options +introduce.constant.across.the.project: + text: Introduce constant across the project +long.line.display.name: + text: Line is longer than allowed by code style +lossy.encoding: + text: Lossy encoding +module.not.in.requirements: + text: The module ''{0}'' does not have the module ''{1}'' in requirements +module.package.not.exported: + text: The module ''{0}'' does not export the package ''{1}'' to the module ''{2}'' +module.package.not.open: + text: The module ''{0}'' does not open the package ''{1}'' to the module ''{2}'' +no.errors.found.in.this.file: + text: No errors found in this file +non.ascii.characters: + text: Non-ASCII characters +nothing.found: + text: Nothing found +nullable.stuff.inspection.navigate.null.argument.usages.fix.family.name: + text: Navigate to 'null' argument usages +nullable.stuff.inspection.navigate.null.argument.usages.view.name: + text: '''''null'''' argument usages for parameter {0}' +nullable.stuff.problems.overridden.method.parameters.are.not.annotated: + text: Overridden method parameters are not annotated +nullable.stuff.problems.overridden.methods.are.not.annotated: + text: Overridden methods are not annotated +offline.inspections.jdk.not.found: + text: '{0} not found' +offline.inspections.library.was.not.resolved: + text: Please configure library ''{0}'' which is used in module ''{1}'' +offline.inspections.module.jdk.not.found: + text: Please, specify sdk ''{0}'' for module ''{1}'' +offline.view.editor.settings.title: + text: Editor Settings +offline.view.parse.exception.title: + text: Nothing Found to Display +offline.view.title: + text: Offline View +parsing.inspections.dump.progress.title: + text: Parsing inspections XML dump +preparing.for.apply.fix: + text: Preparing to Apply Fix... +problematic.whitespace.display.name: + text: Problematic whitespace +problematic.whitespace.show.whitespaces.quickfix: + text: Toggle show whitespace in the editor +problematic.whitespace.spaces.problem.descriptor: + text: File ''{0}'' uses spaces for indentation +problematic.whitespace.tabs.problem.descriptor: + text: File ''{0}'' uses tabs for indentation +profile.activate.action.text: + text: Set as project default +profile.assignment.repeatable.scope.warning: + text: Repeatable scope. Correct assignments. +profile.assignments.browse.profile.scopes.dialog.title: + text: Profiles Scopes +profile.assignments.browse.scope.button.title: + text: '&Browse ...' +profile.assignments.display.name: + text: Errors +profile.assignments.table.title: + text: Project Inspection Profile Assignments +profile.banner.text: + text: 'Inspection profile: {0} {1, choice, 0#(inactive)|1#}' +profile.default.profile.title: + text: Default Project Profile +profile.ide.profile.radiobutton.title: + text: IDE Profile +profile.ide.settings.banner: + text: IDE Profiles Settings +profile.ide.tree.text: + text: IDE Profiles +profile.lock.action.text: + text: Lock +profile.mapping.inspection.profile.column.title: + text: inspection profile +profile.mapping.scope.column.title: + text: scope +profile.project.activate.action.text: + text: Set as IDE default +profile.project.display.name: + text: Project Profiles +profile.project.radiobutton.title: + text: Project Profile +profile.project.settings.banner: + text: Project Profiles Settings +profile.project.settings.disable.text: + text: < Use IDE Profile > +profile.quick.change.suggestion.dialog.title: + text: Unable to switch profile +profile.quick.change.suggestion.message: + text: There are a few scopes configured for the project. Do you want to edit profile assignments on them? +profile.save.as.ide.checkbox.title: + text: Save as IDE profile +profile.save.as.project.checkbox.title: + text: Save as project profile +profile.unlock.action.text: + text: Unlock +psi.search.overriding.progress: + text: Search for Overriding Methods +redundant.throws.declaration: + text: Redundant throws declaration +rename.inspection.profile: + text: Rename inspection profile +rename.message.prefix.inspection.profile: + text: Inspection profile +report.suspicious.but.possibly.correct.method.calls: + text: '&Report suspicious but possibly correct method calls' +run.inspection.on.file.intention.text: + text: Run inspection on ... +run.with.choosen.profile.dialog.option: + text: Run with &chosen profile +run.with.editor.settings.dialog.option: + text: Run with &editor settings +severities.default.settings.message: + text: Edit Settings|Colors \& Fonts +severities.editor.dialog.title: + text: Severities Editor +special.annotations.annotations.list: + text: Additional Special Annotations +special.annotations.list.add.annotation.class: + text: Add Annotation Class +special.annotations.list.annotation.class: + text: Annotation Class +special.annotations.list.annotation.pattern: + text: Add Annotations Pattern +special.annotations.list.remove.annotation.class: + text: Remove Annotation Class +suppress.all.for.class: + text: Suppress all inspections for class +suppress.inspection.annotation.syntax.error: + text: 'Incorrect annotation syntax: {0}' +suppress.inspection.class: + text: Suppress for class +suppress.inspection.family: + text: Suppress inspection +suppress.inspection.field: + text: Suppress for field +suppress.inspection.member: + text: Suppress for member +suppress.inspection.method: + text: Suppress for method +suppress.inspection.module: + text: Suppress for module declaration +suppress.inspection.problem: + text: Suppress +suppress.inspection.statement: + text: Suppress for statement +suspected.module.dependency.problem.descriptor: + text: Module ''{0}'' does not depend on module ''{1}''. Though ''{1}'' was not inspected for exported dependencies needed for scope ''{2}'' +suspicious.name.combination.add.titile: + text: Add Group of Names +suspicious.name.combination.display.name: + text: Suspicious variable/parameter name combination +suspicious.name.combination.edit.title: + text: Edit Group of Names +suspicious.name.combination.options.prompt: + text: 'Enter a comma-separated list of names:' +suspicious.name.combination.options.title: + text: Groups of names +todo.comment.display.name: + text: TODO comment +todo.comment.problem.descriptor: + text: 'TODO comment #ref #loc' +unchecked.warning: + text: Unchecked warning +unhandled.exception.in.jsp.name: + text: Unhandled Exception in JSP +unnecessary.module.dependency.display.name: + text: Unnecessary module dependency +unnecessary.module.dependency.exported.problem.descriptor: + text: Module ''{0}'' does not depend on ''{1}''. Though ''{2}'' depend on ''{1}'' through this exported dependency +unnecessary.module.dependency.exported.problem.descriptor1: + text: Module ''{0}'' does not depend on ''{1}''. Though ''{0}'' depends on ''{2}'' through exported dependencies of ''{1}'' +unnecessary.module.dependency.problem.descriptor: + text: Module ''{0}'' sources do not depend on module ''{1}'' sources +unsupported.character.for.the.charset: + text: Unsupported characters for the charset ''{0}'' +unused.import: + text: Unused import (editor light) +unused.import.display.name: + text: Unused import +unused.import.statement: + text: Unused import statement +unused.library.backward.analysis.job.description: + text: Perform backward analysis +unused.library.display.name: + text: Unused library +unused.library.problem.descriptor: + text: Unused library ''{0}'' +unused.library.roots.problem.descriptor: + text: Unused roots {0} from library ''{1}'' +unused.symbol: + text: Unused symbol +wrong.package.statement: + text: Wrong package statement +xml.suppressable.all.for.file.title: + text: Suppress all for file +xml.suppressable.for.file.title: + text: Suppress for file +xml.suppressable.for.tag.title: + text: Suppress for tag diff --git a/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaQuickFixLocalize.yaml b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaQuickFixLocalize.yaml new file mode 100644 index 0000000000..fb822eeccf --- /dev/null +++ b/java-analysis-impl/src/main/resources/LOCALIZE-LIB/en_US/consulo.java.analysis.impl.JavaQuickFixLocalize.yaml @@ -0,0 +1,536 @@ +access.static.via.class.reference.family: + text: Access static via class reference +access.static.via.class.reference.text: + text: Access static ''{1}.{0}'' via class ''{2}'' reference +add.catch.clause.family: + text: Add exception to catch clause +add.catch.clause.text: + text: Add 'catch' clause(s) +add.class.to.extends.list: + text: Make ''{0}'' extend ''{1}'' +add.constructor.parameter.name: + text: Add constructor parameter +add.default.constructor.family: + text: Add Default Constructor +add.default.constructor.text: + text: Add {0} no-args constructor to {1} +add.docTag.to.custom.tags: + text: Add {0} to custom tags +add.exception.from.field.initializer.to.constructor.throws.family.text: + text: Add exception to class constructors signature +add.exception.from.field.initializer.to.constructor.throws.text: + text: Add exception to class {0, choice, 0#default constructor|1#constructor|2#constructors} signature +add.exception.to.throws.family: + text: Add exception to method signature +add.exception.to.throws.inherited.method.warning.text: + text: |- + Method ''{0}'' is inherited. + Do you want to add exceptions to method signatures in the whole method hierarchy? +add.exception.to.throws.text: + text: Add {0, choice, 0#exception|2#exceptions} to method signature +add.import: + text: Add Import +add.interface.to.implements.list: + text: Make ''{0}'' implement ''{1}'' +add.library.copy.files.to.radio.button: + text: '&Copy ''''{0}'''' library files to' +add.library.description.choose.folder: + text: Choose directory where the library will be copied +add.library.error.cannot.copy: + text: |- + Cannot copy ''{0}'' to ''{1}'' + ({2}) +add.library.error.not.found: + text: Library file ''{0}'' does not exist +add.library.title.choose.folder: + text: Choose Directory +add.library.title.dialog: + text: Add ''{0}'' Library to Project +add.library.use.bundled.library.radio.button: + text: '&Use ''''{0}'''' from {1} distribution' +add.method.body.text: + text: Add method body +add.method.family: + text: Add Method +add.method.qualifier.fix.family: + text: Add method qualifier +add.method.qualifier.fix.text: + text: Add qualifier {0} to method +add.method.text: + text: Add Method ''{0}'' to Class ''{1}'' +add.missing.annotation.parameters.fix: + text: Add missing annotation parameters - {0} +add.missing.annotation.single.parameter.fix: + text: Add missing annotation parameter ''{0}'' +add.modifier.fix: + text: Make ''{0}'' {1} +add.new.array.family: + text: Add missing new expression +add.new.array.text: + text: Add ''new {0}[]'' +add.parameter.from.usage.text: + text: Add ''{0}'' as {1, choice, 1#1st|2#2nd|3#3rd|4#{1,number}th} parameter to method ''{2}'' +add.qualifier: + text: Add qualifier +add.qualifier.original.class.chooser.title: + text: Original class +add.return.statement.text: + text: Add 'return' statement +add.runtime.exception.to.throws.family: + text: Add Runtime Exception to Method Signature +add.runtime.exception.to.throws.text: + text: Add runtime exception(s) to method signature +add.type.arguments.single.argument.text: + text: Add explicit type arguments +add.type.arguments.text: + text: Add explicit type arguments to {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} argument +add.typecast.family: + text: Add TypeCast +add.typecast.text: + text: Cast to ''{0}'' +adjust.package.family: + text: Adjust Package Name +adjust.package.text: + text: Set package name to ''{0}'' +annotations.fix: + text: Annotations +anonymous.class.presentation: + text: Anonymous class derived from {0} +bring.variable.to.scope.family: + text: Bring Variable to Scope +bring.variable.to.scope.text: + text: Bring ''{0}'' into scope +cannot.change.field.exception: + text: |- + Cannot change field ''{0}'' type. + Reason: {1} +cannot.create.java.file.error.text: + text: 'Cannot create {0}.java in {1}: {2}' +cannot.create.java.file.error.title: + text: File Creation Failed +cannot.create.java.package.error.text: + text: 'Cannot create {0} in {1}: {2}' +cannot.create.java.package.error.title: + text: Package Creation Failed +cast.parameter.text: + text: Cast {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter to ''{1}'' +cast.single.parameter.text: + text: Cast parameter to ''{0}'' +change.class.signature.family: + text: Change class signature +change.class.signature.text: + text: Change signature of ''{0}'' to match ''{1}'' +change.extends.list.family: + text: Extend Class from +change.inheritors.visibility.warning.text: + text: Do you want to change inheritors' visibility to visibility of the base method? +change.inheritors.visibility.warning.title: + text: Change Inheritors +change.method.signature.from.usage.family: + text: Change method signature from usage +change.method.signature.from.usage.text: + text: Change signature of ''{0}'' to ''{1}({2})'' +change.new.operator.type.family: + text: Change new operator type +change.new.operator.type.text: + text: Change ''{0}'' to ''new {1}{2}'' +change.parameter.class.family: + text: Change Parameter Class +change.parameter.from.usage.text: + text: Change {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter of method ''{1}'' from ''{2}'' to ''{3}'' +change.to.append.family: + text: Fix StringBuilder append +change.to.append.text: + text: Change to ''{0}'' +class.initializer.presentation: + text: '{0} class initializer' +class.to.import.chooser.title: + text: Class to Import +collection.addall.can.be.replaced.with.constructor.fix.description: + text: '''''{0}()'''' method can be replaced with parametrized constructor' +collection.addall.can.be.replaced.with.constructor.fix.options.title: + text: Classes to check +collection.addall.can.be.replaced.with.constructor.fix.title: + text: Replace 'addAll/putAll' method with parametrized constructor call +collection.to.array.family.name: + text: Apply conversion '.toArray()' +collection.to.array.text: + text: Apply conversion ''.toArray({0})'' +convert.method.to.constructor: + text: Make method constructor +convert.to.string.family: + text: Fix Character Literal +convert.to.string.text: + text: Convert to string literal +create.abstract.method.from.usage.text: + text: Create abstract method ''{0}'' +create.accessor.for.unused.field.family: + text: Create Accessor for Unused Field +create.class: + text: class +create.class.from.new.family: + text: Create Class from New +create.class.from.new.text: + text: Create class ''{0}'' +create.class.from.usage.family: + text: Create Class from Usage +create.class.from.usage.text: + text: Create {0} ''{1}'' +create.class.text: + text: Create class {0} +create.class.title: + text: Create {0} +create.constant.from.usage.family: + text: Create Constant From Usage +create.constant.from.usage.text: + text: Create constant field ''{0}'' +create.constructor.from.new.family: + text: Create Constructor from New +create.constructor.from.new.text: + text: Create constructor +create.constructor.from.super.call.family: + text: Create Constructor From super() Call +create.constructor.from.this.call.family: + text: Create Constructor From this() Call +create.constructor.matching.super: + text: Create constructor matching super +create.constructor.text: + text: Create constructor in ''{0}'' +create.enum: + text: enum +create.enum.constant.from.usage.text: + text: Create enum constant ''{0}'' +create.field.from.usage.family: + text: Create field from Usage +create.field.from.usage.text: + text: Create field ''{0}'' +create.field.text: + text: Create field {0} +create.getter: + text: Create Getter +create.getter.and.setter.for.field: + text: Create getter and setter for ''{0}'' +create.getter.for.field: + text: Create getter for ''{0}'' +create.inner.class.from.usage.text: + text: Create inner {0} ''{1}'' +create.interface: + text: interface +create.interface.text: + text: Create interface {0} +create.local.from.instanceof.usage.family: + text: Create Local Var from instanceof Usage +create.local.from.instanceof.usage.text: + text: Insert ''({0}){1}'' declaration +create.local.from.usage.family: + text: Create Local from Usage +create.local.from.usage.text: + text: Create local variable ''{0}'' +create.method.from.usage.family: + text: Create method from usage +create.method.from.usage.text: + text: Create method ''{0}'' +create.package.text: + text: Create package {0} +create.parameter.from.usage.family: + text: Create parameter from Usage +create.parameter.from.usage.text: + text: Create parameter ''{0}'' +create.property.from.usage.family: + text: Create property From Usage +create.property.text: + text: Create property {0} +create.readable.property: + text: Create getter for ''{0}'' +create.readable.property.with.field: + text: Create getter and field for ''{0}'' +create.readable.writable.property.with.field: + text: Create getter, setter and field for ''{0}'' +create.setter: + text: Create Setter +create.setter.for.field: + text: Create setter for ''{0}'' +create.writable.property: + text: Create setter for ''{0}'' +create.writable.property.with.field: + text: Create setter and field for ''{0}'' +defer.final.assignment.with.temp.family: + text: Defer final assignment with temp +defer.final.assignment.with.temp.text: + text: Defer assignment to ''{0}'' using temp variable +delete.body.text: + text: Delete method body +delete.catch.family: + text: Delete Catch +delete.catch.text: + text: Delete catch for ''{0}'' +delete.element.fix.text: + text: Delete element +delete.reference.fix.text: + text: Delete reference +delete.unreachable.statement.fix.text: + text: Delete unreachable statement +enable.optimize.imports.on.the.fly: + text: Enable 'Settings|Code Style|Imports|Optimize imports on the fly' +exchange.extends.implements.keyword: + text: Change ''{0} {2}'' to ''{1} {2}'' +extract.side.effects: + text: Extract side {0, choice, 1#effect|2#effects} +extract.side.effects.convert.to.if: + text: Convert to 'if' statement +extract.side.effects.family.name: + text: Delete statement extracting side effects +extract.side.effects.multiple: + text: Extract side effects +extract.side.effects.single: + text: Extract side effect +field.to.import.chooser.title: + text: Field to Import +fix.add.special.annotation.family: + text: Add to Special Annotations +fix.add.special.annotation.text: + text: Add ''{0}'' to special annotations list +fix.argument.family: + text: Fix Arguments +fix.javadoc.family: + text: Fix Javadoc +fix.modifiers.family: + text: Fix Modifiers +fix.parameter.type.family: + text: Fix Parameter Type +fix.parameter.type.text: + text: Make ''{0}'' take parameter of type ''{1}'' here +fix.return.type.family: + text: Fix Return Type +fix.return.type.text: + text: Make ''{0}'' return ''{1}'' +fix.single.character.string.to.char.literal.family: + text: Fix literal type +fix.single.character.string.to.char.literal.text: + text: Change {0} to {1} (to {2} literal) +fix.super.method.return.type.family: + text: Fix Super Method Return Type +fix.super.method.return.type.text: + text: Make ''{0}'' return ''{1}'' +fix.throws.list.add.exception: + text: Add ''{0}'' to ''{1}'' throws list +fix.throws.list.family: + text: Fix Throws List +fix.throws.list.remove.exception: + text: Remove ''{0}'' from ''{1}'' throws list +fix.unused.symbol.injection.family: + text: Add to Dependency Injection Annotations +fix.unused.symbol.injection.text: + text: Suppress for {0} annotated by ''{1}'' +fix.variable.type.family: + text: Fix Variable Type +fix.variable.type.text: + text: Change {0} ''{1}'' type to ''{2}'' +generalize.catch.family: + text: Generalize Catch +generalize.catch.text: + text: Generalize catch for ''{0}'' to ''{1}'' +generify.family: + text: Generify File +generify.text: + text: Try to generify ''{0}'' +implement.methods.fix: + text: Implement methods +import.class.fix: + text: Import class +initialize.final.field.in.constructor.choose.dialog.title: + text: Choose constructors to add initialization to +initialize.final.field.in.constructor.name: + text: Initialize in constructor +insert.new.fix: + text: Insert new +insert.sam.method.call.fix.family.name: + text: Insert single abstract method call +insert.sam.method.call.fix.name: + text: Insert ''.{0}'' to call functional interface method +insert.super.constructor.call.family: + text: Base Ctr call +insert.super.constructor.call.text: + text: Insert ''{0}'' +java.9.merge.module.statements.fix.family.name: + text: Merge with other ''{0}'' statement +java.9.merge.module.statements.fix.name: + text: Merge with other ''{0} {1}'' statement +make.class.an.interface.family: + text: Make Class an Interface +make.class.an.interface.text: + text: Make ''{0}'' an interface +make.final.copy.to.temp: + text: Copy ''{0}'' to temp final variable +make.final.family: + text: Make Final +make.final.text: + text: Make {0} final +make.final.transform.to.one.element.array: + text: Transform {0} into final one element array +make.interface.an.class.text: + text: Make ''{0}'' a class +make.vararg.parameter.last.family: + text: Make vararg parameter last +make.vararg.parameter.last.text: + text: Move ''{0}'' to the end of the list +method.is.inherited.warning.title: + text: Method Is Inherited +method.to.import.chooser.title: + text: Method to Import +module.info.add.requires.family.name: + text: Add 'requires' statement to module-info.java +module.info.add.requires.name: + text: Add ''requires {0}'' statement to module-info.java +move.bound.class.to.front.fix.text: + text: Move bound ''{0}'' to the beginning of the bounds list of type parameter ''{1}'' +move.catch.up.family: + text: Move Catch Up +move.catch.up.text: + text: Move catch for ''{0}'' before ''{1}'' +move.class.in.extend.list.family: + text: Move Class in Extend list +move.class.to.package.family: + text: Move Class to Package +move.class.to.package.text: + text: Move to package ''{0}'' +move.class.to.separate.file.family: + text: Move Class to Separate File +move.class.to.separate.file.text: + text: Move class ''{0}'' to ''{0}.java'' +move.file.to.source.root.text: + text: Move file to a source root +negation.broader.scope.family: + text: Negation Broader Scope +negation.broader.scope.text: + text: Change to ''!({0})'' +new.method.body.template.error.text: + text: Please Correct "New Method Body" Template +new.method.body.template.error.title: + text: File Template Error +optimize.imports.fix: + text: Optimize imports +orderEntry.fix.add.dependency.on.module: + text: Add dependency on module ''{0}'' +orderEntry.fix.add.dependency.on.module.choose: + text: Add dependency on module... +orderEntry.fix.add.library.to.classpath: + text: Add library ''{0}'' to classpath +orderEntry.fix.choose.module.to.add.dependency.on: + text: Choose Module to Add Dependency on +orderEntry.fix.circular.dependency.warning: + text: |- + Adding dependency on module ''{0}'' will introduce circular dependency between modules ''{1}'' and ''{2}''. + Add dependency anyway? +orderEntry.fix.family.add.library.to.classpath: + text: Add library to classpath +orderEntry.fix.family.add.module.dependency: + text: Add module dependency +orderEntry.fix.title.circular.dependency.warning: + text: Circular Dependency Warning +permute.arguments: + text: Permute arguments +remove.class.from.extends.list: + text: Make ''{0}'' not extend ''{1}'' +remove.interface.from.implements.list: + text: Make ''{0}'' not implement ''{1}'' +remove.modifier.fix: + text: Make ''{0}'' not {1} +remove.parameter.from.usage.text: + text: Remove {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter from method ''{1}'' +remove.qualifier.action.text: + text: Remove qualifier +remove.qualifier.fix: + text: Remove qualifier +remove.redundant.arguments.family: + text: Remove redundant arguments +remove.redundant.arguments.text: + text: Remove redundant arguments to call ''{0}'' +remove.redundant.else.fix: + text: Remove redundant 'else' +remove.suppression.action.family: + text: Remove suppression +remove.suppression.action.name: + text: Remove ''{0}'' suppression +remove.unused.field: + text: Remove field ''{0}'' +remove.unused.parameter.family: + text: Remove unused parameter +remove.unused.parameter.text: + text: Remove parameter ''{0}'' +remove.unused.variable: + text: Remove variable ''{0}'' +remove.unused.variable.family: + text: Remove unused variable +rename.wrong.reference.family: + text: Rename Wrong Reference +rename.wrong.reference.text: + text: Rename reference +replace.with.list.access.text: + text: Replace with list access +reuse.variable.declaration.family: + text: Reuse variable declaration +reuse.variable.declaration.text: + text: Reuse previous variable ''{0}'' declaration +safe.delete.family: + text: Safe delete +safe.delete.text: + text: Safe delete ''{0}'' +searching.for.usages.progress.title: + text: Searching For Usages... +setup.jdk.location.family: + text: Setup JDK Location +setup.jdk.location.text: + text: Setup JDK +side.effect.action.cancel: + text: '&Cancel' +side.effect.action.remove: + text: '&Remove' +side.effect.action.transform: + text: '&Transform' +side.effect.message1: + text: There are possible side effects found in expressions assigned to the variable ''{0}''
You can: