[java] module dependency quick fixes
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Fri, 9 Sep 2016 09:36:08 +0000 (12:36 +0300)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Fri, 9 Sep 2016 09:36:08 +0000 (12:36 +0300)
java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java
java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddLibraryToDependenciesFix.java
java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddModuleDependencyFix.java
java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefaultQuickFixProvider.java
java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/OrderEntryFix.java
java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt
resources-en/src/messages/QuickFixBundle.properties

index 01b3e4b1724df04229fa08c20f091d2ef8354f95..724de15c7c1a5fd2eadd00747fec24dfd61ad03e 100644 (file)
@@ -193,7 +193,11 @@ public class ModuleHighlightUtil {
       if (!(target instanceof PsiJavaModule)) {
         boolean missing = ref.multiResolve(true).length == 0;
         String message = JavaErrorMessages.message(missing ? "module.not.found" : "module.not.on.path", refElement.getReferenceText());
-        return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).description(message).create();
+        HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refElement).description(message).create();
+        if (!missing) {
+          factory().registerOrderEntryFixes(new QuickFixActionRegistrarImpl(info), ref);
+        }
+        return info;
       }
       else if (target == container) {
         String message = JavaErrorMessages.message("module.cyclic.dependence", container.getModuleName());
index 4d61af151760f9e624ac8fdbba43cbcbe3bae31c..954486906c9fab249e650322e9727f4ed758944c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -66,11 +66,11 @@ class AddLibraryToDependenciesFix extends OrderEntryFix {
   }
 
   @Override
-  public void invoke(@NotNull final Project project, @Nullable final Editor editor, PsiFile file) {
+  public void invoke(@NotNull Project project, @Nullable Editor editor, PsiFile file) {
     DependencyScope scope = suggestScopeByLocation(myCurrentModule, myReference.getElement());
     JavaProjectModelModificationService.getInstance(project).addDependency(myCurrentModule, myLibrary, scope);
-    if (editor != null) {
+    if (myQualifiedClassName != null && editor != null) {
       importClass(myCurrentModule, editor, myReference, myQualifiedClassName);
     }
   }
-}
+}
\ No newline at end of file
index 697877aa8a44c776949d20daa7a23097d10838b6..82ce01254f8e197032ef8b9e4b5b1b85a60fae59 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import com.intellij.application.options.ModuleListCellRenderer;
 import com.intellij.codeInsight.daemon.QuickFixBundle;
 import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
 import com.intellij.compiler.ModuleCompilerUtil;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.WriteAction;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
@@ -39,46 +38,52 @@ import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
+import java.util.*;
 
 /**
- * User: anna
- * Date: 11/20/12
+ * @author anna
+ * @since 20.11.2012
  */
 class AddModuleDependencyFix extends OrderEntryFix {
-  private final LinkedHashSet<Module> myModules = new LinkedHashSet<>();
+  private static final Logger LOG = Logger.getInstance(AddModuleDependencyFix.class);
+
   private final Module myCurrentModule;
-  private final VirtualFile myClassVFile;
-  private final PsiClass[] myClasses;
+  private final VirtualFile myRefVFile;
+  private final List<PsiClass> myClasses;
   private final PsiReference myReference;
-  private static final Logger LOG = Logger.getInstance("#" + AddModuleDependencyFix.class.getName());
+  private final Set<Module> myModules;
+
+  public AddModuleDependencyFix(Module currentModule, VirtualFile refVFile, List<PsiClass> classes, PsiReference reference) {
+    myCurrentModule = currentModule;
+    myRefVFile = refVFile;
+    myClasses = classes;
+    myReference = reference;
+    myModules = new LinkedHashSet<>();
 
-  public AddModuleDependencyFix(Module currentModule,
-                                VirtualFile classVFile,
-                                PsiClass[] classes,
-                                PsiReference reference) {
     final PsiElement psiElement = reference.getElement();
     final Project project = psiElement.getProject();
     final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
-
+    final ModuleRootManager rootManager = ModuleRootManager.getInstance(currentModule);
     for (PsiClass aClass : classes) {
       if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
       PsiFile psiFile = aClass.getContainingFile();
       if (psiFile == null) continue;
       VirtualFile virtualFile = psiFile.getVirtualFile();
       if (virtualFile == null) continue;
-      final Module classModule = fileIndex.getModuleForFile(virtualFile);
-      if (classModule != null && classModule != currentModule && !ModuleRootManager.getInstance(currentModule).isDependsOn(classModule)) {
+      Module classModule = fileIndex.getModuleForFile(virtualFile);
+      if (classModule != null && classModule != currentModule && !rootManager.isDependsOn(classModule)) {
         myModules.add(classModule);
       }
     }
+  }
+
+  public AddModuleDependencyFix(Module currentModule, VirtualFile refVFile, Set<Module> modules, PsiReference reference) {
     myCurrentModule = currentModule;
-    myClassVFile = classVFile;
-    myClasses = classes;
+    myRefVFile = refVFile;
+    myClasses = Collections.emptyList();
     myReference = reference;
+    myModules = modules;
   }
 
   @Override
@@ -90,7 +95,7 @@ class AddModuleDependencyFix extends OrderEntryFix {
       return QuickFixBundle.message("orderEntry.fix.add.dependency.on.module", module.getName());
     }
     else {
-      return "Add dependency on module...";
+      return QuickFixBundle.message("orderEntry.fix.add.dependency.on.module.choose");
     }
   }
 
@@ -102,74 +107,63 @@ class AddModuleDependencyFix extends OrderEntryFix {
 
   @Override
   public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
-    for (Module module : myModules) {
-      if (module.isDisposed()) return false;
-    }
-    return !project.isDisposed() && !myModules.isEmpty() && !myCurrentModule.isDisposed();
+    return !project.isDisposed() && !myCurrentModule.isDisposed() && myModules.stream().noneMatch(Module::isDisposed);
   }
 
   @Override
-  public void invoke(@NotNull final Project project, @Nullable final Editor editor, PsiFile file) {
+  public void invoke(@NotNull Project project, @Nullable Editor editor, PsiFile file) {
     if (myModules.size() == 1) {
       addDependencyOnModule(project, editor, ContainerUtil.getFirstItem(myModules));
     }
     else {
-      final JBList list = new JBList(myModules);
+      JBList<Module> list = new JBList<>(myModules);
       list.setCellRenderer(new ModuleListCellRenderer());
-      final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
-        .setTitle("Choose Module to Add Dependency on")
+      JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
+        .setTitle(QuickFixBundle.message("orderEntry.fix.choose.module.to.add.dependency.on"))
         .setMovable(false)
         .setResizable(false)
         .setRequestFocus(true)
-        .setItemChoosenCallback(() -> {
-          final Object value = list.getSelectedValue();
-          if (value instanceof Module) {
-            addDependencyOnModule(project, editor, (Module)value);
-          }
-        }).createPopup();
+        .setItemChoosenCallback(() -> addDependencyOnModule(project, editor, list.getSelectedValue()))
+        .createPopup();
       if (editor != null) {
         popup.showInBestPositionFor(editor);
-      } else {
+      }
+      else {
         popup.showCenteredInCurrentWindow(project);
       }
     }
   }
 
-  private void addDependencyOnModule(final Project project, final Editor editor, final Module module) {
-    final Runnable doit = () -> {
-      final boolean test = ModuleRootManager.getInstance(myCurrentModule).getFileIndex().isInTestSourceContent(myClassVFile);
-      JavaProjectModelModificationService.getInstance(project).addDependency(myCurrentModule, module,
-                                                                         test ? DependencyScope.TEST : DependencyScope.COMPILE);
-      if (editor != null) {
-        final List<PsiClass> targetClasses = new ArrayList<>();
-        for (PsiClass psiClass : myClasses) {
-          if (ModuleUtilCore.findModuleForPsiElement(psiClass) == module) {
-            targetClasses.add(psiClass);
-          }
-        }
-        if (!DumbService.isDumb(project)) {
-          new AddImportAction(project, myReference, editor, targetClasses.toArray(new PsiClass[targetClasses.size()])).execute();
-        }
-      }
-    };
+  private void addDependencyOnModule(Project project, Editor editor, @Nullable Module module) {
+    if (module == null) return;
     Couple<Module> circularModules = ModuleCompilerUtil.addingDependencyFormsCircularity(myCurrentModule, module);
     if (circularModules == null || showCircularWarning(project, circularModules, module)) {
-      WriteAction.run(doit::run);
+      WriteAction.run(() -> {
+        boolean test = ModuleRootManager.getInstance(myCurrentModule).getFileIndex().isInTestSourceContent(myRefVFile);
+        DependencyScope scope = test ? DependencyScope.TEST : DependencyScope.COMPILE;
+        JavaProjectModelModificationService.getInstance(project).addDependency(myCurrentModule, module, scope);
+
+        if (editor != null && !myClasses.isEmpty()) {
+          PsiClass[] targetClasses = myClasses.stream()
+            .filter(c -> ModuleUtilCore.findModuleForPsiElement(c) == module)
+            .toArray(PsiClass[]::new);
+          if (targetClasses.length > 0 && !DumbService.isDumb(project)) {
+            new AddImportAction(project, myReference, editor, targetClasses).execute();
+          }
+        }
+      });
     }
   }
 
-  private static boolean showCircularWarning(Project project, Couple<Module> circularModules, Module classModule) {
-    final String message = QuickFixBundle.message("orderEntry.fix.circular.dependency.warning", classModule.getName(),
-                                                  circularModules.getFirst().getName(), circularModules.getSecond().getName());
-    if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(message);
-
-    return Messages.showOkCancelDialog(project, message,
-                                          QuickFixBundle.message("orderEntry.fix.title.circular.dependency.warning"),
-                                          Messages.getWarningIcon()) == Messages.OK;
+  private static boolean showCircularWarning(Project project, Couple<Module> circle, Module classModule) {
+    String message = QuickFixBundle.message("orderEntry.fix.circular.dependency.warning",
+                                            classModule.getName(), circle.getFirst().getName(), circle.getSecond().getName());
+    String title = QuickFixBundle.message("orderEntry.fix.title.circular.dependency.warning");
+    return Messages.showOkCancelDialog(project, message, title, Messages.getWarningIcon()) == Messages.OK;
   }
 
   @Override
   public boolean startInWriteAction() {
     return false;
   }
-}
+}
\ No newline at end of file
index 079d24b23d4c5a219cc34c013abbc6fc5cdaaa0e..05a794b01ae00614f64ac98bfb44b2fd2afdecf9 100644 (file)
@@ -37,6 +37,11 @@ import java.util.Map;
 public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider<PsiJavaCodeReferenceElement> {
   @Override
   public void registerFixes(@NotNull PsiJavaCodeReferenceElement ref, @NotNull QuickFixActionRegistrar registrar) {
+    if (PsiUtil.isModuleFile(ref.getContainingFile())) {
+      OrderEntryFix.registerFixes(registrar, ref);
+      return;
+    }
+
     registrar.register(new ImportClassFix(ref));
     registrar.register(new StaticImportConstantFix(ref));
     registrar.register(QuickFixFactory.getInstance().createSetupJDKFix());
index 8388f8170f53d3a53e7c0a03a54bf23feac6fa57..db8ce9e740a42baaf882a089f241a2c1b786dfc8 100644 (file)
@@ -33,6 +33,7 @@ import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.packageDependencies.DependencyValidationManager;
 import com.intellij.psi.*;
+import com.intellij.psi.impl.source.PsiJavaModuleReference;
 import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.search.PsiShortNamesCache;
 import com.intellij.psi.util.PsiTreeUtil;
@@ -44,10 +45,9 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author cdr
@@ -79,13 +79,20 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
     Project project = psiElement.getProject();
     PsiFile containingFile = psiElement.getContainingFile();
     if (containingFile == null) return null;
-    VirtualFile classVFile = containingFile.getVirtualFile();
-    if (classVFile == null) return null;
+    VirtualFile refVFile = containingFile.getVirtualFile();
+    if (refVFile == null) return null;
 
-    final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
-    final Module currentModule = fileIndex.getModuleForFile(classVFile);
+    ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+    Module currentModule = fileIndex.getModuleForFile(refVFile);
     if (currentModule == null) return null;
 
+    if (reference instanceof PsiJavaModuleReference) {
+      List<LocalQuickFix> result = ContainerUtil.newSmartList();
+      createModuleFixes((PsiJavaModuleReference)reference, currentModule, refVFile, result);
+      result.forEach(fix -> registrar.register((IntentionAction)fix));
+      return result;
+    }
+
     List<LocalQuickFix> result = ContainerUtil.newSmartList();
     JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject());
 
@@ -94,25 +101,24 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
       return result;
     }
 
-    Set<Object> librariesToAdd = new THashSet<>();
     PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project));
     List<PsiClass> allowedDependencies = filterAllowedDependencies(psiElement, classes);
     if (allowedDependencies.isEmpty()) {
       return result;
     }
 
-    classes = allowedDependencies.toArray(new PsiClass[allowedDependencies.size()]);
-    OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, classVFile, classes, reference);
+    OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, refVFile, allowedDependencies, reference);
     registrar.register(moduleDependencyFix);
     result.add(moduleDependencyFix);
 
-    for (final PsiClass aClass : classes) {
+    Set<Object> librariesToAdd = new THashSet<>();
+    ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
+    for (PsiClass aClass : allowedDependencies) {
       if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) continue;
       PsiFile psiFile = aClass.getContainingFile();
       if (psiFile == null) continue;
       VirtualFile virtualFile = psiFile.getVirtualFile();
       if (virtualFile == null) continue;
-      ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex();
       for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) {
         if (orderEntry instanceof LibraryOrderEntry) {
           final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry;
@@ -127,10 +133,11 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
           if (entryForFile != null &&
               !(entryForFile instanceof ExportableOrderEntry &&
                 ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST &&
-                !ModuleRootManager.getInstance(currentModule).getFileIndex().isInTestSourceContent(classVFile))) {
+                !moduleFileIndex.isInTestSourceContent(refVFile))) {
             continue;
           }
-          final OrderEntryFix platformFix = new AddLibraryToDependenciesFix(currentModule, library, reference, aClass.getQualifiedName());
+
+          OrderEntryFix platformFix = new AddLibraryToDependenciesFix(currentModule, library, reference, aClass.getQualifiedName());
           registrar.register(platformFix);
           result.add(platformFix);
         }
@@ -140,6 +147,35 @@ public abstract class OrderEntryFix implements IntentionAction, LocalQuickFix {
     return result;
   }
 
+  private static void createModuleFixes(PsiJavaModuleReference reference,
+                                        Module currentModule,
+                                        VirtualFile refVFile,
+                                        List<LocalQuickFix> result) {
+    ProjectFileIndex index = ProjectRootManager.getInstance(currentModule.getProject()).getFileIndex();
+    List<PsiElement> targets = Stream.of(reference.multiResolve(true))
+      .map(ResolveResult::getElement)
+      .filter(Objects::nonNull)
+      .collect(Collectors.toList());
+
+    Set<Module> modules = targets.stream()
+      .map(e -> !(e instanceof PsiCompiledElement) ? e.getContainingFile().getVirtualFile() : null)
+      .filter(vf -> vf != null && index.isInSource(vf))
+      .map(vf -> index.getModuleForFile(vf))
+      .filter(m -> m != null && m != currentModule)
+      .collect(Collectors.toSet());
+    if (!modules.isEmpty()) {
+      result.add(0, new AddModuleDependencyFix(currentModule, refVFile, modules, reference));
+    }
+
+    targets.stream()
+      .map(e -> e instanceof PsiCompiledElement ? e.getContainingFile().getVirtualFile() : null)
+      .flatMap(vf -> vf != null ? index.getOrderEntriesForFile(vf).stream() : Stream.empty())
+      .map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry)e).getLibrary() : null)
+      .filter(Objects::nonNull)
+      .distinct()
+      .forEach(l -> result.add(new AddLibraryToDependenciesFix(currentModule, l, reference, null)));
+  }
+
   private static void registerExternalFixes(@NotNull QuickFixActionRegistrar registrar,
                                             @NotNull PsiReference reference,
                                             PsiElement psiElement,
index 0f820b9b5d598d53f79a568b1fe0b6d578376bb0..4c97b1e57bac12f21734db936832d7ca2b80b210 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor
 import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor
 import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.*
 import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
+import org.assertj.core.api.Assertions.assertThat
 
 class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
   override fun getProjectDescriptor(): LightProjectDescriptor = MultiModuleJava9ProjectDescriptor
@@ -45,7 +46,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
 
   fun testFileDuplicate() {
     addFile("pkg/module-info.java", """module M.bis { }""")
-    doTest("""<error descr="'module-info.java' already exists in the module">module M</error> { }""")
+    highlight("""<error descr="'module-info.java' already exists in the module">module M</error> { }""")
   }
 
   fun testWrongFileLocation() {
@@ -57,7 +58,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
   fun testDuplicateStatements() {
     addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
     addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
-    doTest("""
+    highlight("""
         module M {
           requires M2;
           <error descr="Duplicate requires: M2">requires M2;</error>
@@ -73,7 +74,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
   fun testUnusedStatements() {
     addFile("pkg/main/C.java", "package pkg.main;\npublic class C { }")
     addFile("pkg/main/Impl.java", "package pkg.main;\npublic class Impl extends C { }")
-    doTest("""
+    highlight("""
         module M {
           provides pkg.main.<warning descr="Service interface provided but not exported or used">C</warning> with pkg.main.Impl;
         }""".trimIndent(), true)
@@ -81,7 +82,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
 
   fun testRequires() {
     addFile("module-info.java", "module M2 { requires M1 }", M2)
-    doTest("""
+    highlight("""
         module M1 {
           requires <error descr="Module not found: M.missing">M.missing</error>;
           requires <error descr="Cyclic dependence: M1">M1</error>;
@@ -94,7 +95,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
     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("""
+    highlight("""
         module M {
           exports pkg.<error descr="Cannot resolve symbol 'missing'">missing</error>;
           exports <error descr="Package is empty: pkg.empty">pkg.empty</error>;
@@ -106,7 +107,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
   fun testUses() {
     addFile("pkg/main/C.java", "package pkg.main;\nclass C { }")
     addFile("pkg/main/E.java", "package pkg.main;\npublic enum E { }")
-    doTest("""
+    highlight("""
         module M {
           uses pkg.<error descr="Cannot resolve symbol 'main'">main</error>;
           uses pkg.main.<error descr="Cannot resolve symbol 'X'">X</error>;
@@ -122,7 +123,7 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
     addFile("pkg/main/Impl3.java", "package pkg.main;\npublic abstract class Impl3 implements C { }")
     addFile("pkg/main/Impl4.java", "package pkg.main;\npublic class Impl4 implements C {\n public Impl4(String s) { }\n}")
     addFile("pkg/main/Impl5.java", "package pkg.main;\npublic class Impl5 implements C {\n protected Impl5() { }\n}")
-    doTest("""
+    highlight("""
         module M {
           provides pkg.main.C with pkg.main.<error descr="'pkg.main.Impl1' is not public in 'pkg.main'. Cannot be accessed from outside package">Impl1</error>;
           provides pkg.main.C with pkg.main.<error descr="The service implementation type must be a subtype of the service interface type">Impl2</error>;
@@ -132,15 +133,36 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
         }""".trimIndent())
   }
 
+  fun testModuleRefFixes() {
+    fixes("module M { requires <caret>M.missing; }")
+    fixes("module M { requires <caret>M3; }", "AddModuleDependencyFix")
+  }
+
+  fun testPackageRefFixes() {
+    fixes("module M { exports <caret>pkg.missing; }")
+    addFile("pkg/m3/C3.java", "package pkg.m3;\npublic class C3 { }", M3)
+    fixes("module M { exports <caret>pkg.m3; }")
+  }
+
+  fun testClassRefFixes() {
+    addFile("pkg/m3/C3.java", "package pkg.m3;\npublic class C3 { }", M3)
+    fixes("module M { uses <caret>pkg.m3.C3; }", "AddModuleDependencyFix")
+  }
+
   //<editor-fold desc="Helpers.">
   private fun addFile(path: String, text: String, module: ModuleDescriptor = MAIN) = VfsTestUtil.createFile(module.root(), path, text)
 
-  private fun doTest(text: String, filter: Boolean = false) {
+  private fun highlight(text: String, filter: Boolean = false) {
     myFixture.configureByText("module-info.java", text)
     if (filter) {
       (myFixture as CodeInsightTestFixtureImpl).setVirtualFileFilter { it.name != PsiJavaModule.MODULE_INFO_FILE }
     }
     myFixture.checkHighlighting()
   }
+
+  private fun fixes(text: String, vararg fixes: String) {
+    myFixture.configureByText("module-info.java", text)
+    assertThat(myFixture.getAllQuickFixes().map { it.javaClass.simpleName }).containsExactlyInAnyOrder(*fixes)
+  }
   //</editor-fold>
 }
\ No newline at end of file
index 9ec2d5c163d45bdc39a0f4490a0b68507ffba648..e489562a29232d35662fec30415c9a99479b011d 100644 (file)
@@ -218,6 +218,8 @@ fix.add.special.annotation.family=Add to Special Annotations
 fix.add.special.annotation.text=Add ''{0}'' to special annotations list
 
 orderEntry.fix.add.dependency.on.module=Add dependency on module ''{0}''
+orderEntry.fix.add.dependency.on.module.choose=Add dependency on module...
+orderEntry.fix.choose.module.to.add.dependency.on=Choose Module to Add Dependency on
 orderEntry.fix.family.add.module.dependency=Add module dependency
 orderEntry.fix.add.library.to.classpath=Add library ''{0}'' to classpath
 orderEntry.fix.family.add.library.to.classpath=Add library to classpath