From aea70bba3bc7787c723ebd0d82f3bba38a5db08b Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Wed, 4 Dec 2024 14:22:37 +0100 Subject: [PATCH] [java-highlighting] IDEA-363617 Support JEP 494: Module Import Declarations (Second Preview) - support shadowing module imports by package-on-demand (cherry picked from commit 643fc10bcbfee2f1d41ec02e624b30bc3a48e4bb) GitOrigin-RevId: d1e49b2d48f0b69f8e15393cb823e5529f9b4452 --- .../messages/JavaPsiBundle.properties | 1 + .../com/intellij/pom/java/JavaFeature.java | 2 + .../resolve/ClassResolverProcessor.java | 19 +++++ ...ImplicitModuleImportAndExplicitImport.java | 5 ++ .../implicitClass/ImplicitWithPackages.java | 6 ++ ...mplicitWithPackagesPackagesOverModule.java | 6 ++ .../ImplicitWithSamePackage.java | 4 + .../ImplicitWithSingleImport.java | 6 ++ .../resolve/AmbiguousModuleImport.java | 5 ++ ...mbiguousModuleImportWithPackageImport.java | 7 ++ .../ModuleImportWithDefaultPackageImport.java | 5 ++ .../ModuleImportWithPackageImport.java | 7 ++ .../resolve/ModuleImportWithSingleImport.java | 7 ++ .../daemon/ImplicitClassHighlightingTest.kt | 59 +++++++++++++ .../psi/resolve/ResolveModuleImportTest.java | 82 +++++++++++++++++++ 15 files changed, 221 insertions(+) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java create mode 100644 java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java create mode 100644 java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java create mode 100644 java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java create mode 100644 java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java create mode 100644 java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java diff --git a/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties b/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties index e39be4743567..0b359f9857ed 100644 --- a/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties +++ b/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties @@ -123,6 +123,7 @@ feature.foreign.functions=Foreign Function & Memory API feature.virtual.threads=Virtual Threads feature.statements.before.super=Statements before super() feature.module.import.declarations=Module Import Declarations +feature.package.import.shadow.module.import=Import-on-demand over module import else.without.if='else' without 'if' enum.constant.context=Enum constant ''{0}'' in ''{1}'' diff --git a/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java b/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java index 70d812faee35..58262f7afa77 100644 --- a/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java +++ b/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java @@ -117,7 +117,9 @@ public enum JavaFeature { IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES(LanguageLevel.JDK_23_PREVIEW, "feature.implicit.import.in.implicit.classes"), PRIMITIVE_TYPES_IN_PATTERNS(LanguageLevel.JDK_23_PREVIEW, "feature.primitive.types.in.patterns"), + //see together with PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS MODULE_IMPORT_DECLARATIONS(LanguageLevel.JDK_23_PREVIEW, "feature.module.import.declarations"), + PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS(LanguageLevel.JDK_24_PREVIEW, "feature.package.import.shadow.module.import"), ; private final @NotNull LanguageLevel myLevel; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java index d56ab54cc214..93f218f3172d 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java @@ -5,6 +5,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; import com.intellij.psi.impl.source.PsiAnonymousClassImpl; import com.intellij.psi.infos.CandidateInfo; @@ -14,9 +15,11 @@ import com.intellij.psi.scope.JavaScopeProcessorEvent; import com.intellij.psi.scope.NameHint; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Iterator; import java.util.List; @@ -171,9 +174,25 @@ public class ClassResolverProcessor implements PsiScopeProcessor, NameHint, Elem return Domination.DOMINATES; } + // on-demand wins over module import + if (PsiUtil.isAvailable(JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS, myPlace)) { + boolean myIsModule = isImportedByModule(myCurrentFileContext); + boolean otherIsModule = isImportedByModule(info.getCurrentFileResolveScope()); + if (myIsModule && otherOnDemand && !otherIsModule) { + return Domination.DOMINATED_BY; + } + if (myOnDemand && !myIsModule && otherIsModule) { + return Domination.DOMINATES; + } + } + return Domination.EQUAL; } + private static boolean isImportedByModule(@Nullable PsiElement context) { + return context instanceof PsiImportModuleStatement; + } + private boolean isAccessible(PsiClass otherClass) { if (otherClass.hasModifierProperty(PsiModifier.PRIVATE)) { final PsiClass containingClass = otherClass.getContainingClass(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java new file mode 100644 index 000000000000..fb957b7b0ba0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java @@ -0,0 +1,5 @@ +import module java.sql; + +public static void main(String[] args) { + Date date = new Date(); +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java new file mode 100644 index 000000000000..3eacc82d58b5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java @@ -0,0 +1,6 @@ +import a.b.*; + +List a; + +void main() { +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java new file mode 100644 index 000000000000..a40f401bd2fd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java @@ -0,0 +1,6 @@ +import a.b.*; + +List a; + +void main() { +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java new file mode 100644 index 000000000000..92d376ac01a0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java @@ -0,0 +1,4 @@ +List a; + +void main() { +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java new file mode 100644 index 000000000000..9fd6742c12cd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java @@ -0,0 +1,6 @@ +import a.b.List; + +List a; + +void main() { +} \ No newline at end of file diff --git a/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java new file mode 100644 index 000000000000..4d3290c81398 --- /dev/null +++ b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java @@ -0,0 +1,5 @@ +import module my.source.moduleB; +import module my.source.moduleA; +class AmbiguousModuleImport { + Imported module; +} diff --git a/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java new file mode 100644 index 000000000000..43b8e74ec983 --- /dev/null +++ b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java @@ -0,0 +1,7 @@ +import module my.source.moduleB; + +import my.source.moduleA.*; + +class AmbiguousModuleImportWithPackageImport { + Imported module; +} diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java new file mode 100644 index 000000000000..e8fa9c5d6a18 --- /dev/null +++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java @@ -0,0 +1,5 @@ +import module my.source.moduleB; + +class ModuleImportWithDefaultPackageImport { + String a; +} diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java new file mode 100644 index 000000000000..280b4c2a6407 --- /dev/null +++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java @@ -0,0 +1,7 @@ +import module my.source.moduleB; + +import my.source.moduleA.*; + +class ModuleImportWithPackageImport { + Imported module; +} diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java new file mode 100644 index 000000000000..3c1cec17608d --- /dev/null +++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java @@ -0,0 +1,7 @@ +import module my.source.moduleB; + +import my.source.moduleA.Imported; + +class ModuleImportWithSingleImport { + Imported module; +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt index 638cefc04112..00057ae46f71 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt @@ -6,9 +6,11 @@ import com.intellij.pom.java.JavaFeature import com.intellij.pom.java.LanguageLevel import com.intellij.psi.* import com.intellij.psi.util.PsiUtil +import com.intellij.psi.util.parentOfType import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase +import org.jetbrains.plugins.groovy.intentions.style.inference.resolve class ImplicitClassHighlightingTest : LightJavaCodeInsightFixtureTestCase() { override fun getProjectDescriptor() = JAVA_21 @@ -105,6 +107,63 @@ class ImplicitClassHighlightingTest : LightJavaCodeInsightFixtureTestCase() { }) } + fun testImplicitWithPackages() { + IdeaTestUtil.withLevel(module, JavaFeature.IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES.minimumLevel, Runnable { + myFixture.addClass(""" + package a.b; + + public final class List { + } + """.trimIndent()) + val psiFile = myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + }) + } + + fun testImplicitWithPackagesPackagesOverModule() { + IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable { + myFixture.addClass(""" + package a.b; + + public final class List { + } + """.trimIndent()) + val psiFile = myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + val element = psiFile.findElementAt(myFixture.caretOffset) + assertEquals("a.b.List", element?.parentOfType()?.type.resolve()?.qualifiedName) + }) + } + + + fun testImplicitWithSingleImport() { + IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable { + myFixture.addClass(""" + package a.b; + + public final class List { + } + """.trimIndent()) + val psiFile = myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + val element = psiFile.findElementAt(myFixture.caretOffset) + assertEquals("a.b.List", element?.parentOfType()?.type.resolve()?.qualifiedName) + }) + } + + fun testImplicitWithSamePackage() { + IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable { + myFixture.addClass(""" + public final class List { + } + """.trimIndent()) + val psiFile = myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + val element = psiFile.findElementAt(myFixture.caretOffset) + assertEquals("List", element?.parentOfType()?.type.resolve()?.qualifiedName) + }) + } + private fun doTest() { myFixture.configureByFile(getTestName(false) + ".java") myFixture.checkHighlighting() diff --git a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java index 1e3c74d5e64f..0ffb518043ca 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java @@ -9,9 +9,11 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.roots.DependencyScope; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.pom.java.JavaFeature; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.testFramework.IdeaTestUtil; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -298,6 +300,86 @@ public class ResolveModuleImportTest extends LightJava9ModulesCodeInsightFixture assertEquals("my.source.moduleC.SourceTestC", psiClass.getQualifiedName()); } + public void testAmbiguousModuleImport() { + IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{ + prepareAmbiguousModuleTests(); + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + }); + } + + public void testModuleImportWithPackageImport() { + IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{ + prepareAmbiguousModuleTests(); + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + PsiClass psiClass = getPsiClass(); + assertNotNull(psiClass); + assertEquals("my.source.moduleA.Imported", psiClass.getQualifiedName()); + }); + } + + public void testModuleImportWithDefaultPackageImport() { + IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{ + addCode("module-info.java", """ + module my.source.moduleB { + exports my.source.moduleB; + } + """, M2); + addCode("my/source/moduleB/String.java", """ + package my.source.moduleB; + public class String {} + """, M2); + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + PsiClass psiClass = getPsiClass(); + assertNotNull(psiClass); + assertEquals("java.lang.String", psiClass.getQualifiedName()); + }); + } + + public void testAmbiguousModuleImportWithPackageImport() { + IdeaTestUtil.withLevel(getModule(), JavaFeature.MODULE_IMPORT_DECLARATIONS.getMinimumLevel(), ()->{ + prepareAmbiguousModuleTests(); + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + }); + } + + public void testModuleImportWithSingleImport() { + IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{ + prepareAmbiguousModuleTests(); + myFixture.configureByFile(getTestName(false) + ".java"); + myFixture.checkHighlighting(); + PsiClass psiClass = getPsiClass(); + assertNotNull(psiClass); + assertEquals("my.source.moduleA.Imported", psiClass.getQualifiedName()); + }); + } + + private void prepareAmbiguousModuleTests() { + addCode("module-info.java", """ + module my.source.moduleB { + exports my.source.moduleB; + } + """, M2); + addCode("my/source/moduleB/Imported.java", """ + package my.source.moduleB; + public class Imported {} + """, M2); + addCode("module-info.java", """ + module my.source.moduleA { + exports my.source.moduleA; + } + """, M4); + addCode("my/source/moduleA/Imported.java", """ + package my.source.moduleA; + public class Imported {} + """, M4); + addCode("Test.java", """ + """); + } + @Nullable private PsiClass getPsiClass() { PsiField field = PsiTreeUtil.getParentOfType(myFixture.getFile().findElementAt(myFixture.getCaretOffset()), PsiField.class);