From 1a36c3e29a8605410bfc8d0fdd552af549884196 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 2 Oct 2015 19:57:50 +0300 Subject: [PATCH] Quick Fixes: Implement quickfix for missing library --- .idea/libraries/testng_plugin.xml | 1 + .../kotlin/idea/test/ConfigLibraryUtil.java | 67 +++++++++++-------- .../KotlinAddOrderEntryActionFactory.kt | 58 ++++++++++++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 + idea/testData/quickfix/libraries/junit.kt | 14 ++++ .../quickfix/libraries/junit.kt.after | 14 ++++ idea/testData/quickfix/libraries/testNG.kt | 12 ++++ .../quickfix/libraries/testNG.kt.after | 14 ++++ .../idea/quickfix/AbstractQuickFixTest.java | 15 ++++- .../idea/quickfix/QuickFixTestGenerated.java | 21 ++++++ 10 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt create mode 100644 idea/testData/quickfix/libraries/junit.kt create mode 100644 idea/testData/quickfix/libraries/junit.kt.after create mode 100644 idea/testData/quickfix/libraries/testNG.kt create mode 100644 idea/testData/quickfix/libraries/testNG.kt.after diff --git a/.idea/libraries/testng_plugin.xml b/.idea/libraries/testng_plugin.xml index b4c825d8436..eb3ee15d7d5 100644 --- a/.idea/libraries/testng_plugin.xml +++ b/.idea/libraries/testng_plugin.xml @@ -6,6 +6,7 @@ + diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/ConfigLibraryUtil.java b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/ConfigLibraryUtil.java index 9d8c51954de..8c878be20fa 100644 --- a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/ConfigLibraryUtil.java +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/ConfigLibraryUtil.java @@ -25,8 +25,10 @@ import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VfsUtil; +import kotlin.jvm.functions.Function0; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.js.KotlinJavaScriptLibraryManager; +import org.jetbrains.kotlin.idea.util.application.ApplicationUtilsKt; import org.jetbrains.kotlin.utils.PathUtil; import java.io.File; @@ -121,42 +123,49 @@ public class ConfigLibraryUtil { } - public static void removeLibrary(@NotNull final Module module, @NotNull final String libraryName) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - ModuleRootManager rootManager = ModuleRootManager.getInstance(module); - ModifiableRootModel model = rootManager.getModifiableModel(); + public static boolean removeLibrary(@NotNull final Module module, @NotNull final String libraryName) { + return ApplicationUtilsKt.runWriteAction( + new Function0() { + @Override + public Boolean invoke() { + boolean removed = false; - for (OrderEntry orderEntry : model.getOrderEntries()) { - if (orderEntry instanceof LibraryOrderEntry) { - LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) orderEntry; + ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + ModifiableRootModel model = rootManager.getModifiableModel(); - Library library = libraryOrderEntry.getLibrary(); - if (library != null) { - String name = library.getName(); - if (name != null && name.equals(libraryName)) { + for (OrderEntry orderEntry : model.getOrderEntries()) { + if (orderEntry instanceof LibraryOrderEntry) { + LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry) orderEntry; - // Dispose attached roots - Library.ModifiableModel modifiableModel = library.getModifiableModel(); - for (String rootUrl : library.getRootProvider().getUrls(OrderRootType.CLASSES)) { - modifiableModel.removeRoot(rootUrl, OrderRootType.CLASSES); + Library library = libraryOrderEntry.getLibrary(); + if (library != null) { + String name = library.getName(); + if (name != null && name.equals(libraryName)) { + + // Dispose attached roots + Library.ModifiableModel modifiableModel = library.getModifiableModel(); + for (String rootUrl : library.getRootProvider().getUrls(OrderRootType.CLASSES)) { + modifiableModel.removeRoot(rootUrl, OrderRootType.CLASSES); + } + for (String rootUrl : library.getRootProvider().getUrls(OrderRootType.SOURCES)) { + modifiableModel.removeRoot(rootUrl, OrderRootType.SOURCES); + } + modifiableModel.commit(); + + model.getModuleLibraryTable().removeLibrary(library); + + removed = true; + break; + } } - for (String rootUrl : library.getRootProvider().getUrls(OrderRootType.SOURCES)) { - modifiableModel.removeRoot(rootUrl, OrderRootType.SOURCES); - } - modifiableModel.commit(); - - model.getModuleLibraryTable().removeLibrary(library); - - break; } } + + model.commit(); + + return removed; } } - - model.commit(); - } - }); + ); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt new file mode 100644 index 00000000000..92e99a70a2f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddOrderEntryActionFactory.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2015 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 org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix +import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceBase +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.references.JetSimpleNameReference.ShorteningMode +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.JetElement +import org.jetbrains.kotlin.psi.JetSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElement +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +public object KotlinAddOrderEntryActionFactory : JetIntentionActionsFactory() { + override fun doCreateActions(diagnostic: Diagnostic): List? { + val simpleExpression = diagnostic.psiElement as? JetSimpleNameExpression ?: return emptyList() + val refElement = simpleExpression.getQualifiedElement() + + val reference = object: PsiReferenceBase(refElement) { + override fun resolve() = null + + override fun getVariants() = PsiReference.EMPTY_ARRAY + + override fun getRangeInElement(): TextRange? { + val offset = simpleExpression.startOffset - refElement.startOffset + return TextRange(offset, offset + simpleExpression.textLength) + } + + override fun getCanonicalText() = refElement.text + + override fun bindToElement(element: PsiElement): PsiElement { + return simpleExpression.mainReference.bindToElement(element, ShorteningMode.FORCED_SHORTENING) + } + } + @Suppress("UNCHECKED_CAST") + return OrderEntryFix.registerFixes(QuickFixActionRegistrarImpl(null), reference) as List? ?: emptyList() + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 3448bc85c0b..8ec1482dae5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -338,5 +338,7 @@ public class QuickFixRegistrar : QuickFixContributor { CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS.registerFactory(AddTypeToLHSOfCallableReferenceFix) DEPRECATED_TYPE_PARAMETER_SYNTAX.registerFactory(MigrateTypeParameterListFix) + + UNRESOLVED_REFERENCE.registerFactory(KotlinAddOrderEntryActionFactory) } } diff --git a/idea/testData/quickfix/libraries/junit.kt b/idea/testData/quickfix/libraries/junit.kt new file mode 100644 index 00000000000..6a727e42576 --- /dev/null +++ b/idea/testData/quickfix/libraries/junit.kt @@ -0,0 +1,14 @@ +// "Add 'JUnit4' to classpath" "true" +// ERROR: Unresolved reference: Before +// ERROR: Unresolved reference: junit +// UNCONFIGURE_LIBRARY: JUnit4 +package some + +import org.junit.Before + +open class KBase { + @Before + fun setUp() { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/libraries/junit.kt.after b/idea/testData/quickfix/libraries/junit.kt.after new file mode 100644 index 00000000000..2eb50cd02b7 --- /dev/null +++ b/idea/testData/quickfix/libraries/junit.kt.after @@ -0,0 +1,14 @@ +// "Add 'JUnit4' to classpath" "true" +// ERROR: Unresolved reference: Before +// ERROR: Unresolved reference: junit +// UNCONFIGURE_LIBRARY: JUnit4 +package some + +import org.junit.Before + +open class KBase { + @Before + fun setUp() { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/libraries/testNG.kt b/idea/testData/quickfix/libraries/testNG.kt new file mode 100644 index 00000000000..3462c5fda28 --- /dev/null +++ b/idea/testData/quickfix/libraries/testNG.kt @@ -0,0 +1,12 @@ +// "Add 'testng' to classpath" "true" +// ERROR: Unresolved reference: BeforeMethod +// ERROR: Unresolved reference: testng +// UNCONFIGURE_LIBRARY: testng +package some + +abstract class KBase { + @BeforeMethod + fun setUp() { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/libraries/testNG.kt.after b/idea/testData/quickfix/libraries/testNG.kt.after new file mode 100644 index 00000000000..f8bbf52fa79 --- /dev/null +++ b/idea/testData/quickfix/libraries/testNG.kt.after @@ -0,0 +1,14 @@ +// "Add 'testng' to classpath" "true" +// ERROR: Unresolved reference: BeforeMethod +// ERROR: Unresolved reference: testng +// UNCONFIGURE_LIBRARY: testng +package some + +import org.testng.annotations.BeforeMethod + +abstract class KBase { + @BeforeMethod + fun setUp() { + throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java index cf58bf78f39..890dbc61934 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java @@ -24,6 +24,7 @@ import com.intellij.codeInspection.SuppressableProblemGroup; import com.intellij.ide.startup.impl.StartupManagerImpl; import com.intellij.lang.annotation.ProblemGroup; import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.JavaSdk; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.startup.StartupManager; @@ -95,8 +96,10 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { @SuppressWarnings({"AssignmentToStaticFieldFromInstanceMethod", "CallToPrintStackTrace"}) @Override public void run() { + String fileText = ""; try { - String contents = StringUtil.convertLineSeparators(FileUtil.loadFile(testFile, CharsetToolkit.UTF8_CHARSET)); + fileText = FileUtil.loadFile(testFile, CharsetToolkit.UTF8_CHARSET); + String contents = StringUtil.convertLineSeparators(fileText); quickFixTestCase.configureFromFileText(testFile.getName(), contents); quickFixTestCase.bringRealEditorBack(); @@ -110,11 +113,21 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { catch (Throwable e) { e.printStackTrace(); fail(testName); + } finally { + unconfigureLibrariesAsSpecified(fileText); } } }, "", ""); } + private static void unconfigureLibrariesAsSpecified(String fileText) { + Module module = getModule(); + for (String libraryName : InTextDirectivesUtils.findListWithPrefixes(fileText, "// UNCONFIGURE_LIBRARY: ")) { + if (ConfigLibraryUtil.removeLibrary(module, libraryName)) continue; + fail("Library '" + libraryName + "' wasn't found"); + } + } + private static void applyAction(String contents, QuickFixTestCase quickFixTestCase, String testName, String testFullPath) throws Exception { Pair pair = quickFixTestCase.parseActionHintImpl(quickFixTestCase.getFile(), contents); String text = pair.getFirst(); diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 2eea42e2e24..db312750c6f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -4077,6 +4077,27 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/libraries") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Libraries extends AbstractQuickFixTest { + public void testAllFilesPresentInLibraries() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/libraries"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("junit.kt") + public void testJunit() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/libraries/junit.kt"); + doTest(fileName); + } + + @TestMetadata("testNG.kt") + public void testTestNG() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/libraries/testNG.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/migration") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)