[java] highlights duplicate 'requires' statements
authorRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 23 Aug 2016 12:22:23 +0000 (15:22 +0300)
committerRoman Shevchenko <roman.shevchenko@jetbrains.com>
Tue, 23 Aug 2016 12:22:23 +0000 (15:22 +0300)
java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java
java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java
java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DeleteElementFix.java [new file with mode: 0644]
java/java-psi-impl/src/messages/JavaErrorMessages.properties
java/java-tests/testSrc/com/intellij/codeInsight/daemon/ModuleHighlightingTest.kt
platform/testFramework/src/com/intellij/testFramework/LightProjectDescriptor.java
resources-en/src/messages/QuickFixBundle.properties

index 25426f6e326a7ffa6fe94a601e897a15fbde615f..f6e1df5d12a0e4a4f6a8afb35b245aba71422970 100644 (file)
@@ -1634,6 +1634,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
     if (!myHolder.hasErrorResults()) myHolder.add(checkFeature(module, Feature.MODULES));
     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.add(ModuleHighlightUtil.checkFileLocation(module, myFile));
   }
 
index 804bf1f51dcd829772a98636db5785ef4c04cf03..82358fa9c96fe55a992822958bc18e66782a91dc 100644 (file)
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.JavaErrorMessages;
 import com.intellij.codeInsight.daemon.QuickFixBundle;
 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
+import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
 import com.intellij.codeInsight.daemon.impl.quickfix.GoToSymbolFix;
 import com.intellij.codeInsight.daemon.impl.quickfix.MoveFileFix;
 import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
@@ -32,10 +33,13 @@ 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.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE;
 
@@ -71,6 +75,32 @@ public class ModuleHighlightUtil {
     return null;
   }
 
+  @NotNull
+  static List<HighlightInfo> checkDuplicateRequires(@NotNull PsiJavaModule module) {
+    List<HighlightInfo> results = ContainerUtil.newSmartList();
+
+    Map<String, PsiElement> map = ContainerUtil.newHashMap();
+    for (PsiElement child = module.getFirstChild(); child != null; child = child.getNextSibling()) {
+      if (child instanceof PsiRequiresStatement) {
+        PsiJavaModuleReferenceElement ref = ((PsiRequiresStatement)child).getReferenceElement();
+        if (ref != null) {
+          String text = ref.getReferenceText();
+          if (!map.containsKey(text)) {
+            map.put(text, child);
+          }
+          else {
+            String message = JavaErrorMessages.message("module.duplicate.requires", text);
+            HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(child).description(message).create();
+            QuickFixAction.registerQuickFixAction(info, new DeleteElementFix(child));
+            results.add(info);
+          }
+        }
+      }
+    }
+
+    return results;
+  }
+
   @Nullable
   static HighlightInfo checkFileLocation(@NotNull PsiJavaModule element, @NotNull PsiFile file) {
     VirtualFile vFile = file.getVirtualFile();
diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DeleteElementFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DeleteElementFix.java
new file mode 100644 (file)
index 0000000..c0b4ce1
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.codeInsight.daemon.impl.quickfix;
+
+import com.intellij.codeInsight.FileModificationService;
+import com.intellij.codeInsight.daemon.QuickFixBundle;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.SmartPointerManager;
+import com.intellij.psi.SmartPsiElementPointer;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+public class DeleteElementFix implements IntentionAction {
+  private final SmartPsiElementPointer<PsiElement> myPointer;
+
+  public DeleteElementFix(@NotNull PsiElement element) {
+    myPointer = SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element);
+  }
+
+  @Nls
+  @NotNull
+  @Override
+  public String getText() {
+    return QuickFixBundle.message("delete.element.fix.text");
+  }
+
+  @Nls
+  @NotNull
+  @Override
+  public String getFamilyName() {
+    return getText();
+  }
+
+  @Override
+  public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+    return myPointer.getElement() != null;
+  }
+
+  @Override
+  public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
+    PsiElement element = myPointer.getElement();
+    if (element != null && element.isValid() && FileModificationService.getInstance().prepareFileForWrite(file)) {
+      element.delete();
+    }
+  }
+
+  @Override
+  public boolean startInWriteAction() {
+    return true;
+  }
+}
\ No newline at end of file
index 276fc385cb80db18f811d1979ccc4364d041fc5a..4ad337b56ab34cb94b3477b409a94f01fad24f1e 100644 (file)
@@ -393,6 +393,7 @@ underscore.lambda.identifier=Use of '_' as a lambda parameter name is not allowe
 
 module.file.wrong.name=Module declaration should be in a file named 'module-info.java'
 module.file.duplicate='module-info.java' already exists in the module
+module.duplicate.requires=Duplicate requires: {0}
 module.file.wrong.location=Module declaration should be located in a module's source root
 module.open.duplicate.text=Go to duplicate
 module.ref.unknown=Cannot resolve module ''{0}''
index b1b704cfdaefb46569363f6fce9b77830d339e90..51bac7b03aaf11ec1fafc483511186e77f2c25c8 100644 (file)
 package com.intellij.codeInsight.daemon
 
 import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ContentEntry
+import com.intellij.openapi.roots.LanguageLevelModuleExtension
+import com.intellij.openapi.roots.ModifiableRootModel
+import com.intellij.openapi.util.io.FileUtil
 import com.intellij.openapi.vfs.VfsUtil
+import com.intellij.pom.java.LanguageLevel
+import com.intellij.testFramework.IdeaTestUtil
 import com.intellij.testFramework.LightPlatformTestCase
 import com.intellij.testFramework.LightProjectDescriptor
+import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
 import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
 
 class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
-  override fun getProjectDescriptor(): LightProjectDescriptor = JAVA_9
+  companion object {
+    private val DESCRIPTOR = object : DefaultLightProjectDescriptor() {
+      override fun getSdk() = IdeaTestUtil.getMockJdk18()
+
+      override fun setUpProject(project: Project, handler: SetupHandler) {
+        super.setUpProject(project, handler)
+        runWriteAction {
+          val m2 = createModule(project, FileUtil.join(FileUtil.getTempDirectory(), "light_idea_test_case_m2.iml"))
+          val src2 = createSourceRoot(m2, "src2")
+          createContentEntry(m2, src2)
+
+          VfsUtil.saveText(src2.createChildData(this, "module-info.java"), "module M2 { }")
+        }
+      }
+
+      override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
+        model.getModuleExtension(LanguageLevelModuleExtension::class.java).languageLevel = LanguageLevel.JDK_1_9
+      }
+    }
+  }
+
+  override fun getProjectDescriptor(): LightProjectDescriptor = DESCRIPTOR
 
   fun testWrongFileName() {
     myFixture.configureByText("M.java", """/* ... */ <error descr="Module declaration should be in a file named 'module-info.java'">module M</error> { }""")
@@ -39,6 +69,10 @@ class ModuleHighlightingTest : LightCodeInsightFixtureTestCase() {
     myFixture.checkHighlighting()
   }
 
+  fun testDuplicateRequires() {
+    doTest("""module M { requires M2; <error descr="Duplicate requires: M2">requires M2;</error> }""")
+  }
+
   fun testUnresolvedModule() {
     doTest("""module M { requires <error descr="Cannot resolve module 'M.missing'">M.missing</error>; }""")
   }
index e6d2ccd9b336fe6cbfd7b3aab5cfa3c76ee9428d..e4f08db8d90979be6e6488752f62200415103c38 100644 (file)
@@ -119,7 +119,7 @@ public class LightProjectDescriptor {
     return srcRoot;
   }
 
-  private void createContentEntry(@NotNull Module module, @NotNull VirtualFile srcRoot) {
+  protected void createContentEntry(@NotNull Module module, @NotNull VirtualFile srcRoot) {
     ModuleRootModificationUtil.updateModel(module, model -> {
       Sdk sdk = getSdk();
       if (sdk != null) {
index 2250faf241f1e4584331bdd1e8b8e8c912841407..40230c94545fb57c3260b375eacb7d2247d0af4e 100644 (file)
@@ -289,3 +289,5 @@ wrap.with.optional.parameter.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number
 wrap.with.optional.single.parameter.text=Wrap using 'java.util.Optional'
 
 move.file.to.source.root.text=Move file to a source root
+
+delete.element.fix.text=Delete element
\ No newline at end of file