From ca0342f3427dca82ebd8d249ab70e676321bc5b8 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 25 Aug 2016 20:50:57 +0300 Subject: [PATCH] [java] module 'exports' package completion --- .../impl/analysis/ModuleHighlightUtil.java | 17 +-------- .../completion/JavaModuleCompletion.java | 37 +++++++++++++++++-- .../src/com/intellij/psi/util/PsiUtil.java | 14 +++++++ .../completion/ModuleCompletionTest.kt | 11 ++++++ 4 files changed, 60 insertions(+), 19 deletions(-) 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 f46d214d5743..23757e593965 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 @@ -34,6 +34,7 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.FilenameIndex; +import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.graph.Graph; import org.jetbrains.annotations.NotNull; @@ -161,7 +162,7 @@ public class ModuleHighlightUtil { String message = JavaErrorMessages.message("package.not.found", packageName); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create(); } - if (isEmpty(directories, packageName)) { + if (PsiUtil.isPackageEmpty(directories, packageName)) { String message = JavaErrorMessages.message("package.is.empty", packageName); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(refElement).description(message).create(); } @@ -227,18 +228,4 @@ 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-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java index fe18d6ad4907..d9414f11c9b4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java @@ -19,8 +19,13 @@ import com.intellij.codeInsight.TailType; import com.intellij.codeInsight.completion.JavaKeywordCompletion.OverrideableSpace; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.module.impl.scopes.ModulesScope; import com.intellij.openapi.project.Project; import com.intellij.psi.*; +import com.intellij.psi.impl.file.impl.JavaFileManager; import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; @@ -48,7 +53,10 @@ class JavaModuleCompletion { addModuleStatementKeywords(position, result); } else if (context instanceof PsiJavaModuleReferenceElement) { - addModuleReferences(position, result); + addModuleReferences(context, result); + } + else if (context instanceof PsiJavaCodeReferenceElement) { + addCodeReferences(context, result); } } } @@ -66,11 +74,11 @@ class JavaModuleCompletion { result.consume(new OverrideableSpace(createKeyword(position, PsiKeyword.PROVIDES), TailType.HUMBLE_SPACE_BEFORE_WORD)); } - private static void addModuleReferences(PsiElement position, Consumer result) { - PsiJavaModule host = PsiTreeUtil.getParentOfType(position, PsiJavaModule.class); + private static void addModuleReferences(PsiElement context, Consumer result) { + PsiJavaModule host = PsiTreeUtil.getParentOfType(context, PsiJavaModule.class); if (host != null) { String hostName = host.getModuleName(); - Project project = position.getProject(); + Project project = context.getProject(); JavaModuleNameIndex index = JavaModuleNameIndex.getInstance(); GlobalSearchScope scope = ProjectScope.getAllScope(project); index.processAllKeys(project, name -> { @@ -81,4 +89,25 @@ class JavaModuleCompletion { }); } } + + private static void addCodeReferences(PsiElement context, Consumer result) { + PsiElement statement = PsiTreeUtil.skipParentsOfType(context, PsiJavaCodeReferenceElement.class); + if (statement instanceof PsiExportsStatement) { + Module module = ModuleUtilCore.findModuleForPsiElement(context); + PsiPackage topPackage = ServiceManager.getService(context.getProject(), JavaFileManager.class).findPackage(""); + if (module != null && topPackage != null) { + processPackage(topPackage, new ModulesScope(module), result); + } + } + } + + private static void processPackage(PsiPackage pkg, ModulesScope scope, Consumer result) { + String packageName = pkg.getQualifiedName(); + if (packageName.indexOf('.') > 0 && !PsiUtil.isPackageEmpty(pkg.getDirectories(scope), packageName)) { + result.consume(new OverrideableSpace(LookupElementBuilder.create(packageName), TailType.SEMICOLON)); + } + for (PsiPackage subPackage : pkg.getSubPackages(scope)) { + processPackage(subPackage, scope, result); + } + } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java index 289154d7e930..74f7d7aee44d 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java @@ -1277,4 +1277,18 @@ public final class PsiUtil extends PsiUtilCore { public static boolean isModuleFile(@NotNull PsiFile file) { return file instanceof PsiJavaFile && ((PsiJavaFile)file).getModuleDeclaration() != null; } + + public static boolean isPackageEmpty(@NotNull PsiDirectory[] directories, @NotNull 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-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt index f5289cf78fdd..28dd8eb35089 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt @@ -15,8 +15,10 @@ */ package com.intellij.codeInsight.completion +import com.intellij.testFramework.LightPlatformTestCase import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.MultiModuleJava9ProjectDescriptor +import com.intellij.testFramework.VfsTestUtil import org.assertj.core.api.Assertions.assertThat class ModuleCompletionTest : LightFixtureCompletionTestCase() { @@ -27,7 +29,16 @@ class ModuleCompletionTest : LightFixtureCompletionTestCase() { fun testStatements2() = complete("module M { requires X; ex }", "module M { requires X; exports }") fun testModuleRef() = complete("module M { requires M }", "module M { requires M2; }") + 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 { }") + variants("module M { exports pkg. }", "pkg.main", "pkg.other") + } + // + private fun addFile(path: String, text: String) = VfsTestUtil.createFile(LightPlatformTestCase.getSourceRoot(), path, text) + private fun complete(text: String, expected: String) { myFixture.configureByText("module-info.java", text) myFixture.completeBasic()