[java] module 'exports' package completion
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Thu, 25 Aug 2016 17:50:57 +0000 (20:50 +0300)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Thu, 25 Aug 2016 17:51:25 +0000 (20:51 +0300)
java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java
java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java
java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java
java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt

index f46d214d5743604fd0cb56049c0222d46a14ab0e..23757e593965396ebd5a2ee78d35b7cbce501c17 100644 (file)
@@ -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
index fe18d6ad4907cb0bc1f31740f96c6405d6a2e787..d9414f11c9b43fd142671ee81fe2eb8506d304bd 100644 (file)
@@ -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<LookupElement> result) {
-    PsiJavaModule host = PsiTreeUtil.getParentOfType(position, PsiJavaModule.class);
+  private static void addModuleReferences(PsiElement context, Consumer<LookupElement> 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<LookupElement> 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<LookupElement> 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
index 289154d7e930102879824000204fe9ec5e8a9931..74f7d7aee44dcf4085b826faf662dc5049eddd76 100644 (file)
@@ -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
index f5289cf78fdde65b71a7f86b2d3ff28494145344..28dd8eb350898f64f143f6dfdb8081187abc0277 100644 (file)
  */
 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<caret> }", "module M { requires X; exports <caret> }")
   fun testModuleRef() = complete("module M { requires M<caret> }", "module M { requires M2;<caret> }")
 
+  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.<caret> }", "pkg.main", "pkg.other")
+  }
+
   //<editor-fold desc="Helpers.">
+  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()