[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
This commit is contained in:
Mikhail Pyltsin
2024-12-04 14:22:37 +01:00
committed by intellij-monorepo-bot
parent 2ce2af7f8e
commit aea70bba3b
15 changed files with 221 additions and 0 deletions

View File

@@ -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}''

View File

@@ -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;

View File

@@ -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();

View File

@@ -0,0 +1,5 @@
import module java.sql;
public static void main(String[] args) {
Date date = new Date();
}

View File

@@ -0,0 +1,6 @@
import a.b.*;
<error descr="Reference to 'List' is ambiguous, both 'a.b.List' and 'java.util.List' match">List</error><caret> a;
void main() {
}

View File

@@ -0,0 +1,6 @@
import a.b.*;
List<caret> a;
void main() {
}

View File

@@ -0,0 +1,4 @@
List<caret> a;
void main() {
}

View File

@@ -0,0 +1,6 @@
import a.b.List;
List<caret> a;
void main() {
}

View File

@@ -0,0 +1,5 @@
import module my.source.moduleB;
import module my.source.moduleA;
class AmbiguousModuleImport {
<error descr="Reference to 'Imported' is ambiguous, both 'my.source.moduleB.Imported' and 'my.source.moduleA.Imported' match">Imported</error> <caret>module;
}

View File

@@ -0,0 +1,7 @@
import module my.source.moduleB;
import my.source.moduleA.*;
class AmbiguousModuleImportWithPackageImport {
<error descr="Reference to 'Imported' is ambiguous, both 'my.source.moduleA.Imported' and 'my.source.moduleB.Imported' match">Imported</error> module;
}

View File

@@ -0,0 +1,5 @@
import module my.source.moduleB;
class ModuleImportWithDefaultPackageImport {
String<caret> a;
}

View File

@@ -0,0 +1,7 @@
import module my.source.moduleB;
import my.source.moduleA.*;
class ModuleImportWithPackageImport {
Imported <caret>module;
}

View File

@@ -0,0 +1,7 @@
import module my.source.moduleB;
import my.source.moduleA.Imported;
class ModuleImportWithSingleImport {
Imported <caret>module;
}

View File

@@ -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<PsiField>()?.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<PsiField>()?.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<PsiField>()?.type.resolve()?.qualifiedName)
})
}
private fun doTest() {
myFixture.configureByFile(getTestName(false) + ".java")
myFixture.checkHighlighting()

View File

@@ -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);