From da27335c78d23f56f55161ffd8f37907530a6df5 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 25 Aug 2016 16:27:35 +0300 Subject: [PATCH] [java] module 'exports' statement highlighting --- .../impl/analysis/HighlightVisitorImpl.java | 12 +++ .../impl/analysis/ModuleHighlightUtil.java | 86 +++++++++++++++++++ .../com/intellij/psi/PsiExportsStatement.java | 3 + .../impl/source/PsiExportsStatementImpl.java | 9 ++ .../PsiJavaCodeReferenceElementImpl.java | 2 +- .../src/messages/JavaErrorMessages.properties | 4 + .../daemon/ModuleHighlightingTest.kt | 22 +++++ 7 files changed, 137 insertions(+), 1 deletion(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index c3ac0b1996bd..0188d995c859 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -1621,6 +1621,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileName(module, myFile)); if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileDuplicates(module, myFile)); if (!myHolder.hasErrorResults()) myHolder.addAll(ModuleHighlightUtil.checkDuplicateRequires(module)); + if (!myHolder.hasErrorResults()) myHolder.addAll(ModuleHighlightUtil.checkDuplicateExports(module)); if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkFileLocation(module, myFile)); } @@ -1634,6 +1635,17 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } } + @Override + public void visitExportsStatement(PsiExportsStatement statement) { + super.visitExportsStatement(statement); + if (PsiUtil.isLanguageLevel9OrHigher(myFile)) { + PsiJavaModule container = (PsiJavaModule)statement.getParent(); + PsiJavaCodeReferenceElement ref = statement.getPackageReference(); + if (!myHolder.hasErrorResults()) myHolder.add(ModuleHighlightUtil.checkPackageReference(ref)); + if (!myHolder.hasErrorResults()) myHolder.addAll(ModuleHighlightUtil.checkExportTargets(statement, container)); + } + } + @Nullable private HighlightInfo checkFeature(@NotNull PsiElement element, @NotNull Feature feature) { return HighlightUtil.checkFeature(element, feature, myLanguageLevel, myFile); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java index aac352fd7a08..f46d214d5743 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java @@ -148,6 +148,78 @@ public class ModuleHighlightUtil { return null; } + @Nullable + static HighlightInfo checkPackageReference(@Nullable PsiJavaCodeReferenceElement refElement) { + if (refElement != null) { + PsiElement target = refElement.resolve(); + if (target instanceof PsiPackage) { + Module module = ModuleUtilCore.findModuleForPsiElement(refElement); + if (module != null) { + String packageName = ((PsiPackage)target).getQualifiedName(); + PsiDirectory[] directories = ((PsiPackage)target).getDirectories(new ModulesScope(module)); + if (directories.length == 0) { + String message = JavaErrorMessages.message("package.not.found", packageName); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create(); + } + if (isEmpty(directories, packageName)) { + String message = JavaErrorMessages.message("package.is.empty", packageName); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create(); + } + } + } + } + + return null; + } + + @NotNull + static List checkExportTargets(@NotNull PsiExportsStatement statement, @NotNull PsiJavaModule container) { + List results = ContainerUtil.newSmartList(); + + Set targets = ContainerUtil.newHashSet(); + for (PsiJavaModuleReferenceElement refElement : psiTraverser().children(statement).filter(PsiJavaModuleReferenceElement.class)) { + String refText = refElement.getReferenceText(); + PsiPolyVariantReference ref = refElement.getReference(); + assert ref != null : statement; + PsiElement target = ref.resolve(); + if (!(target instanceof PsiJavaModule)) { + String message = JavaErrorMessages.message("module.not.found", refText); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).description(message).create()); + } + else if (!targets.add(refText)) { + String message = JavaErrorMessages.message("module.duplicate.export", refText); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create()); + } + else if (target == container) { + String message = JavaErrorMessages.message("module.self.export"); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(refElement).description(message).create()); + } + } + + return results; + } + + @NotNull + static List checkDuplicateExports(@NotNull PsiJavaModule module) { + List results = ContainerUtil.newSmartList(); + + Set names = ContainerUtil.newHashSet(); + for (PsiExportsStatement statement : psiTraverser().children(module).filter(PsiExportsStatement.class)) { + PsiJavaCodeReferenceElement ref = statement.getPackageReference(); + if (ref != null) { + String text = PsiNameHelper.getQualifiedClassName(ref.getText(), true); + if (!names.add(text)) { + String message = JavaErrorMessages.message("module.duplicate.export", text); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).description(message).create(); + QuickFixAction.registerQuickFixAction(info, new DeleteElementFix(statement)); + results.add(info); + } + } + } + + return results; + } + private static QuickFixFactory factory() { return QuickFixFactory.getInstance(); } @@ -155,4 +227,18 @@ public class ModuleHighlightUtil { private static TextRange range(PsiJavaModule module) { return new TextRange(module.getTextOffset(), module.getNameElement().getTextRange().getEndOffset()); } + + private static boolean isEmpty(PsiDirectory[] directories, String packageName) { + for (PsiDirectory directory : directories) { + for (PsiFile file : directory.getFiles()) { + if (file instanceof PsiClassOwner && + packageName.equals(((PsiClassOwner)file).getPackageName()) && + ((PsiClassOwner)file).getClasses().length > 0) { + return false; + } + } + } + + return true; + } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/PsiExportsStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiExportsStatement.java index 07f2c3d8f59a..3e383abc3d81 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiExportsStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiExportsStatement.java @@ -15,10 +15,13 @@ */ package com.intellij.psi; +import org.jetbrains.annotations.Nullable; + /** * Represents an {@code exports} statement of a Java module declaration. * * @since 2016.3 */ public interface PsiExportsStatement extends PsiElement { + @Nullable PsiJavaCodeReferenceElement getPackageReference(); } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiExportsStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiExportsStatementImpl.java index f83692a0be21..2e46dbd7b9ff 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiExportsStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiExportsStatementImpl.java @@ -18,15 +18,24 @@ package com.intellij.psi.impl.source; import com.intellij.psi.JavaElementVisitor; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiExportsStatement; +import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.impl.source.tree.CompositePsiElement; import com.intellij.psi.impl.source.tree.JavaElementType; +import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class PsiExportsStatementImpl extends CompositePsiElement implements PsiExportsStatement { public PsiExportsStatementImpl() { super(JavaElementType.EXPORTS_STATEMENT); } + @Nullable + @Override + public PsiJavaCodeReferenceElement getPackageReference() { + return PsiTreeUtil.getChildOfType(this, PsiJavaCodeReferenceElement.class); + } + @Override public void accept(@NotNull PsiElementVisitor visitor) { if (visitor instanceof JavaElementVisitor) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java index b3466b7694ab..123f77628e3c 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java @@ -134,7 +134,7 @@ public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement impleme return CLASS_OR_PACKAGE_NAME_KIND; // incomplete code } } - if (i == JavaElementType.PACKAGE_STATEMENT) { + if (i == JavaElementType.PACKAGE_STATEMENT || i == JavaElementType.EXPORTS_STATEMENT) { return PACKAGE_NAME_KIND; } if (i == JavaElementType.IMPORT_STATEMENT) { diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index 07c796e2f5b0..852118da2dd0 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -398,6 +398,10 @@ module.file.wrong.location=Module declaration should be located in a module's so module.open.duplicate.text=Go to duplicate module.not.found=Module not found: {0} module.cyclic.dependence=Cyclic dependence: {0} +package.not.found=Package not found: {0} +package.is.empty=Package is empty: {0} +module.duplicate.export=Duplicate export: {0} +module.self.export=Exports to itself feature.generics=Generics feature.annotations=Annotations diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt index 24c90b10c197..b667390a56cc 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt @@ -59,6 +59,28 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() { }""".trimIndent(), true) } + fun testExports() { + addFile("pkg/empty/package-info.java", "package pkg.empty;") + addFile("pkg/main/C.java", "package pkg.main;\nclass C { }") + addFile("pkg/other/C.groovy", "package pkg.other\nclass C { }") + doTest(""" + module M { + exports pkg.missing; + exports pkg.empty; + exports pkg.main to M.missing, M2, M2; + exports pkg.other to M; + }""".trimIndent()) + } + + fun testDuplicateExports() { + addFile("pkg/main/C.java", "package pkg.main;\nclass C { }") + doTest(""" + module M { + exports pkg.main; + exports pkg. main; + }""".trimIndent()) + } + // private fun addFile(path: String, text: String) = VfsTestUtil.createFile(LightPlatformTestCase.getSourceRoot(), path, text)