[java, highlighting, import-module] Support Feature level check for Module Import Declarations IDEA-355536

GitOrigin-RevId: 36dcbb509e788a3243f93a52c83cd24ae0dd6985
This commit is contained in:
Aleksey Dobrynin
2024-07-22 17:28:10 +02:00
committed by intellij-monorepo-bot
parent 9af85109da
commit e927f5ac3e
6 changed files with 102 additions and 43 deletions

View File

@@ -845,6 +845,13 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
}
@Override
public void visitImportModuleStatement(@NotNull PsiImportModuleStatement statement) {
super.visitImportModuleStatement(statement);
if (!hasErrorResults()) add(checkFeature(statement, JavaFeature.MODULE_IMPORT_DECLARATIONS));
if (!hasErrorResults()) add(ModuleHighlightUtil.checkModuleReference(statement));
}
@Override
public void visitKeyword(@NotNull PsiKeyword keyword) {
super.visitKeyword(keyword);

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
@@ -214,33 +214,23 @@ final class ModuleHighlightUtil {
return null;
}
static HighlightInfo.Builder checkModuleReference(@NotNull PsiImportModuleStatement statement) {
PsiJavaModuleReferenceElement refElement = statement.getModuleReference();
if (refElement == null) return null;
PsiJavaModuleReference ref = refElement.getReference();
assert ref != null : refElement.getParent();
PsiJavaModule target = ref.resolve();
if (target != null) return null;
return getUnresolvedJavaModuleReason(statement, refElement);
}
static HighlightInfo.Builder checkModuleReference(@NotNull PsiRequiresStatement statement) {
PsiJavaModuleReferenceElement refElement = statement.getReferenceElement();
if (refElement != null) {
PsiJavaModuleReference ref = refElement.getReference();
assert ref != null : refElement.getParent();
PsiJavaModule target = ref.resolve();
if (target == null) {
if (ref.multiResolve(true).length == 0) {
if (IncompleteModelUtil.isIncompleteModel(statement)) {
return HighlightUtil.getPendingReferenceHighlightInfo(refElement);
}
String message = JavaErrorBundle.message("module.not.found", refElement.getReferenceText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).descriptionAndTooltip(message);
}
else if (ref.multiResolve(false).length > 1) {
String message = JavaErrorBundle.message("module.ambiguous", refElement.getReferenceText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).descriptionAndTooltip(message);
}
else {
String message = JavaErrorBundle.message("module.not.on.path", refElement.getReferenceText());
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).descriptionAndTooltip(message);
List<IntentionAction> registrar = new ArrayList<>();
QuickFixFactory.getInstance().registerOrderEntryFixes(ref, registrar);
QuickFixAction.registerQuickFixActions(info, null, registrar);
return info;
}
}
if (target == null) return getUnresolvedJavaModuleReason(statement, refElement);
PsiJavaModule container = (PsiJavaModule)statement.getParent();
if (target == container) {
String message = JavaErrorBundle.message("module.cyclic.dependence", container.getName());
@@ -260,6 +250,37 @@ final class ModuleHighlightUtil {
return null;
}
@NotNull
private static HighlightInfo.Builder getUnresolvedJavaModuleReason(@NotNull PsiElement parent, @NotNull PsiJavaModuleReferenceElement refElement) {
PsiJavaModuleReference ref = refElement.getReference();
assert ref != null : refElement.getParent();
ResolveResult[] results = ref.multiResolve(true);
switch (results.length) {
case 0:
if (IncompleteModelUtil.isIncompleteModel(parent)) {
return HighlightUtil.getPendingReferenceHighlightInfo(refElement);
} else {
return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF)
.range(refElement)
.descriptionAndTooltip(JavaErrorBundle.message("module.not.found", refElement.getReferenceText()));
}
case 1:
String message = JavaErrorBundle.message("module.not.on.path", refElement.getReferenceText());
HighlightInfo.Builder info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF)
.range(refElement)
.descriptionAndTooltip(message);
List<IntentionAction> registrar = new ArrayList<>();
QuickFixFactory.getInstance().registerOrderEntryFixes(ref, registrar);
QuickFixAction.registerQuickFixActions(info, null, registrar);
return info;
default:
return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING)
.range(refElement)
.descriptionAndTooltip(JavaErrorBundle.message("module.ambiguous", refElement.getReferenceText()));
}
}
static HighlightInfo.Builder checkHostModuleStrength(@NotNull PsiPackageAccessibilityStatement statement) {
PsiElement parent;
if (statement.getRole() == Role.OPENS &&

View File

@@ -272,8 +272,11 @@ public abstract class ImportClassFixBase<T extends PsiElement, R extends PsiRefe
Set<String> unresolvedImports = new HashSet<>(importStatements.length);
for (PsiImportStatementBase statement : importStatements) {
if (statement instanceof PsiImportModuleStatement importModuleStatement) {
PsiJavaModuleReference ref = importModuleStatement.getModuleReference();
if (ref != null && ref.resolve() == null) unresolvedImports.add(importModuleStatement.getReferenceName());
PsiJavaModuleReferenceElement refElement = importModuleStatement.getModuleReference();
if (refElement != null) {
PsiJavaModuleReference ref = refElement.getReference();
if (ref != null && ref.resolve() == null) unresolvedImports.add(importModuleStatement.getReferenceName());
}
} else {
PsiJavaCodeReferenceElement ref = statement.getImportReference();
String name = ref == null ? null : ref.getReferenceName();

View File

@@ -33,9 +33,9 @@ public interface PsiImportModuleStatement extends PsiImportStatementBase {
/**
* Returns the reference which specifies the imported module.
*
* @return the reference to the module imported, or null if the reference is not resolvable or missing.
* @return the reference element to the module imported, or null if the reference element is not resolvable or missing.
*/
@Nullable PsiJavaModuleReference getModuleReference();
@Nullable PsiJavaModuleReferenceElement getModuleReference();
@Override
default boolean isOnDemand() {

View File

@@ -20,7 +20,7 @@ public class PsiImportModuleStatementImpl extends PsiImportStatementBaseImpl imp
public static final ArrayFactory<PsiImportModuleStatementImpl> ARRAY_FACTORY =
count -> count == 0 ? EMPTY_ARRAY : new PsiImportModuleStatementImpl[count];
private SoftReference<PsiJavaModuleReference> myReference;
private SoftReference<PsiJavaModuleReferenceElement> myRefElement;
public PsiImportModuleStatementImpl(PsiImportStatementStub stub) {
super(stub, JavaStubElementTypes.IMPORT_MODULE_STATEMENT);
@@ -32,35 +32,38 @@ public class PsiImportModuleStatementImpl extends PsiImportStatementBaseImpl imp
@Override
public @Nullable PsiJavaModule resolveTargetModule() {
PsiJavaModuleReference moduleReference = getModuleReference();
if (moduleReference == null) return null;
return moduleReference.resolve();
PsiJavaModuleReferenceElement refElement = getModuleReference();
if (refElement == null) return null;
PsiJavaModuleReference ref = refElement.getReference();
if (ref == null) return null;
return ref.resolve();
}
@Override
public String getReferenceName() {
PsiJavaModuleReference moduleReference = getModuleReference();
if (moduleReference == null) return null;
return moduleReference.getCanonicalText();
PsiJavaModuleReferenceElement refElement = getModuleReference();
if (refElement == null) return null;
PsiJavaModuleReference ref = refElement.getReference();
if (ref == null) return null;
return ref.getCanonicalText();
}
@Override
public @Nullable PsiJavaModuleReference getModuleReference() {
public @Nullable PsiJavaModuleReferenceElement getModuleReference() {
PsiImportStatementStub stub = getStub();
if (stub != null) {
String refText = nullize(stub.getImportReferenceText());
if (refText == null) return null;
PsiJavaModuleReference ref = dereference(myReference);
if (ref == null) {
ref = JavaPsiFacade.getInstance(getProject()).getParserFacade().createModuleReferenceFromText(refText, this).getReference();
myReference = new SoftReference<>(ref);
PsiJavaModuleReferenceElement refElement = dereference(myRefElement);
if (refElement == null) {
refElement = JavaPsiFacade.getInstance(getProject()).getParserFacade().createModuleReferenceFromText(refText, this);
myRefElement = new SoftReference<>(refElement);
}
return ref;
return refElement;
}
else {
myReference = null;
PsiJavaModuleReferenceElement refElement = PsiTreeUtil.getChildOfType(this, PsiJavaModuleReferenceElement.class);
return refElement != null ? refElement.getReference() : null;
myRefElement = null;
return PsiTreeUtil.getChildOfType(this, PsiJavaModuleReferenceElement.class);
}
}
@@ -76,7 +79,9 @@ public class PsiImportModuleStatementImpl extends PsiImportStatementBaseImpl imp
@Override
public PsiElement resolve() {
PsiJavaModuleReference ref = getModuleReference();
PsiJavaModuleReferenceElement refElement = getModuleReference();
if (refElement == null) return null;
PsiJavaModuleReference ref = refElement.getReference();
return ref != null ? ref.resolve() : null;
}

View File

@@ -19,6 +19,7 @@ import com.intellij.openapi.util.TextRange
import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.workspace.jps.entities.ModuleId
import com.intellij.platform.workspace.jps.entities.modifyModuleEntity
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.JavaCompilerConfigurationProxy
import com.intellij.psi.PsiJavaModule
import com.intellij.psi.PsiManager
@@ -27,6 +28,7 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.ProjectScope
import com.intellij.psi.util.PsiUtilCore
import com.intellij.testFramework.DumbModeTestUtils
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.workspaceModel.updateProjectModel
import junit.framework.TestCase
import org.assertj.core.api.Assertions.assertThat
@@ -50,6 +52,27 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
}
}
fun testModuleImportDeclarationLevelCheck() {
IdeaTestUtil.withLevel(module, LanguageLevel.JDK_23) {
highlight("Test.java", """
<error descr="Module Import Declarations are not supported at language level '23'">import module java.sql;</error>
class Test {}
""".trimIndent())
}
}
fun testModuleImportDeclarationUnresolvedModule() {
IdeaTestUtil.withLevel(module, LanguageLevel.JDK_23_PREVIEW) {
highlight("Test.java", """
import module M2;
import module <error descr="Module is not in dependencies: M3">M3</error>;
import module <error descr="Module not found: M4">M4</error>;
class Test {}
""".trimIndent())
}
}
fun testPackageStatement() {
highlight("package pkg;")
highlight("""