From 689d26b3e247a4f2386ce4e1c4702246ad6dcf74 Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Fri, 28 Oct 2016 21:32:52 +0300 Subject: [PATCH] Java: Quick fix for importing a package the current module doesn't read by adding "requires" statement to the module info (IDEA-162762) --- .../impl/analysis/ModuleHighlightUtil.java | 4 +- .../impl/quickfix/AddRequiredModuleFix.java | 114 ++++++++++++++++++ .../AddRequiresToModuleInfo_after.java | 3 + .../daemon/quickFix/AddRequiredModuleTest.kt | 59 +++++++++ .../src/messages/QuickFixBundle.properties | 5 +- 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addRequiredModule/AddRequiresToModuleInfo_after.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddRequiredModuleTest.kt 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 d8a9d5d975ac..89a39e0cd8ca 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 @@ -393,7 +393,9 @@ public class ModuleHighlightUtil { if (!(PsiJavaModule.JAVA_BASE.equals(requiredName) || JavaModuleGraphUtil.reads(refModule, targetModule))) { String message = JavaErrorMessages.message("module.not.in.requirements", refModuleName, requiredName); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).description(message).create(); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).description(message).create(); + QuickFixAction.registerQuickFixAction(info, new AddRequiredModuleFix(refModule, requiredName)); + return info; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java new file mode 100644 index 000000000000..b793fea0af59 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddRequiredModuleFix.java @@ -0,0 +1,114 @@ +/* + * 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.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Pavel.Dolgov + */ +public class AddRequiredModuleFix implements IntentionAction { + private final SmartPsiElementPointer myModulePointer; + private final String myRequiredName; + + public AddRequiredModuleFix(PsiJavaModule module, String requiredName) { + myModulePointer = SmartPointerManager.getInstance(module.getProject()).createSmartPsiElementPointer(module); + myRequiredName = requiredName; + } + + @Nls + @NotNull + @Override + public String getText() { + return QuickFixBundle.message("module.info.add.requires.name", myRequiredName); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return QuickFixBundle.message("module.info.add.requires.family.name"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (!PsiUtil.isLanguageLevel9OrHigher(file)) return false; + PsiJavaModule module = myModulePointer.getElement(); + return module != null && module.isValid() && module.getManager().isInProject(module) && getLBrace(module) != null; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PsiJavaModule module = myModulePointer.getElement(); + if (module == null) return; + if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; + + PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(project).getParserFacade(); + PsiJavaModule tempModule = + parserFacade.createModuleFromText("module " + module.getModuleName() + " { requires " + myRequiredName + "; }"); + Iterable tempModuleRequires = tempModule.getRequires(); + PsiRequiresStatement requiresStatement = tempModuleRequires.iterator().next(); + + PsiElement addingPlace = findAddingPlace(module); + if (addingPlace != null) { + addingPlace.getParent().addAfter(requiresStatement, addingPlace); + } + } + + @Override + public boolean startInWriteAction() { + return true; + } + + @Nullable + private static PsiElement findAddingPlace(@NotNull PsiJavaModule module) { + PsiElement addingPlace = null; + for (PsiRequiresStatement requires : module.getRequires()) { + addingPlace = requires; + } + if (addingPlace != null) { + return addingPlace; + } + for (PsiExportsStatement exports : module.getExports()) { + addingPlace = exports; + } + if (addingPlace != null) { + return addingPlace; + } + return getLBrace(module); + } + + @Nullable + private static PsiElement getLBrace(@NotNull PsiJavaModule module) { + PsiJavaModuleReferenceElement nameElement = module.getNameElement(); + for (PsiElement element = nameElement.getNextSibling(); element != null; element = element.getNextSibling()) { + if (element instanceof PsiJavaToken && ((PsiJavaToken)element).getTokenType() == JavaTokenType.LBRACE) { + return element; + } + } + return null; // module-info is incomplete + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addRequiredModule/AddRequiresToModuleInfo_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addRequiredModule/AddRequiresToModuleInfo_after.java new file mode 100644 index 000000000000..43c9e92531cb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addRequiredModule/AddRequiresToModuleInfo_after.java @@ -0,0 +1,3 @@ +module MAIN { + requires M2; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddRequiredModuleTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddRequiredModuleTest.kt new file mode 100644 index 000000000000..c8dcf3d90f07 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/AddRequiredModuleTest.kt @@ -0,0 +1,59 @@ +/* + * 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.quickFix + +import com.intellij.JavaTestUtil +import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase +import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.* + +/** + * @author Pavel.Dolgov + */ +class AddRequiredModuleTest : LightJava9ModulesCodeInsightFixtureTestCase() { + override fun setUp() { + super.setUp() + addFile("module-info.java", "module M2 { exports pkgA; }", M2) + addFile("pkgA/A.java", "package pkgA; public class A {}", M2) + } + + fun testAddRequiresToModuleInfo() { + addFile("module-info.java", "module MAIN {}", MAIN) + val editedFile = addFile("pkgB/B.java", "package pkgB; " + + "import pkgA.A; " + + "public class B { A a; }", MAIN) + myFixture.configureFromExistingVirtualFile(editedFile) + + val action = myFixture.findSingleIntention("Add 'M2' as required module") + assertNotNull(action) + myFixture.launchAction(action) + + myFixture.checkHighlighting() // error is gone + myFixture.checkResultByFile("module-info.java", getTestName(false) + "_after.java", false) + } + + fun testNoIdeaModuleDependency() { + addFile("module-info.java", "module M3 {}", M3) + val editedFile = addFile("pkgB/B.java", "package pkgB; " + + "import pkgA.A; " + + "public class B { A a; }", M3) + myFixture.configureFromExistingVirtualFile(editedFile) + + val actions = myFixture.filterAvailableIntentions("Add 'M2' as required module") + assertEmpty(actions) + } + + override fun getTestDataPath() = JavaTestUtil.getJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/quickFix/addRequiredModule" +} diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index b6ba7d90abc2..b7dfe436634d 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -298,4 +298,7 @@ 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 +delete.element.fix.text=Delete element + +module.info.add.requires.family.name=Add required module +module.info.add.requires.name=Add ''{0}'' as required module \ No newline at end of file