From 8aa4b75e1c908b32240a384c82dc006669c7ae29 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Tue, 26 Sep 2017 16:05:04 +0300 Subject: [PATCH] Quick fix for module is absent in module-info requirements (KT-20108) #KT-20108 Fixed --- ...tJava9ModulesCodeInsightFixtureTestCase.kt | 56 +++++++++ ...KotlinMultiModuleJava9ProjectDescriptor.kt | 118 ++++++++++++++++++ .../kotlin/idea/test/PluginTestCaseBase.java | 1 + .../quickfix/KotlinAddRequiredModuleFix.kt | 39 ++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 2 + .../idea/quickfix/AddRequireModuleTest.kt | 104 +++++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinLightJava9ModulesCodeInsightFixtureTestCase.kt create mode 100644 idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinMultiModuleJava9ProjectDescriptor.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddRequiredModuleFix.kt create mode 100644 idea/tests/org/jetbrains/kotlin/idea/quickfix/AddRequireModuleTest.kt diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinLightJava9ModulesCodeInsightFixtureTestCase.kt b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinLightJava9ModulesCodeInsightFixtureTestCase.kt new file mode 100644 index 00000000000..c0f1ecafa87 --- /dev/null +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinLightJava9ModulesCodeInsightFixtureTestCase.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2017 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.test + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.EditorTestUtil +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.VfsTestUtil +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.idea.test.KotlinMultiModuleJava9ProjectDescriptor.ModuleDescriptor + +abstract class KotlinLightJava9ModulesCodeInsightFixtureTestCase : KotlinLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor(): LightProjectDescriptor = KotlinMultiModuleJava9ProjectDescriptor + + override fun tearDown() { + KotlinMultiModuleJava9ProjectDescriptor.cleanupSourceRoots() + super.tearDown() + } + + protected fun addFile(path: String, text: String, module: ModuleDescriptor = ModuleDescriptor.MAIN): VirtualFile = + VfsTestUtil.createFile(module.root(), path, text) + + protected fun addKotlinFile(path: String, @Language("kotlin") text: String, module: ModuleDescriptor = ModuleDescriptor.MAIN): VirtualFile = + addFile(path, text.toTestData(), module) + + protected fun addJavaFile(path: String, @Language("java") text: String, module: ModuleDescriptor = ModuleDescriptor.MAIN): VirtualFile = + addFile(path, text.toTestData(), module) + + protected fun moduleInfo(@Language("JAVA") text: String, module: ModuleDescriptor = ModuleDescriptor.MAIN) = + addFile("module-info.java", text.toTestData(), module) + + protected fun checkModuleInfo(@Language("JAVA") text: String) = + myFixture.checkResult("module-info.java", text.toTestData(), false) +} + +private const val IDENTIFIER_CARET = "CARET" +private const val COMMENT_CARET_CHAR = "/*|*/" +private const val COMMENT_CARET = "/*CARET*/" +private val ADDITIONAL_CARET_MARKERS = arrayOf(IDENTIFIER_CARET, COMMENT_CARET_CHAR, COMMENT_CARET) + +private fun String.toTestData(): String = + ADDITIONAL_CARET_MARKERS.fold(trimIndent()) { result, marker -> result.replace(marker, EditorTestUtil.CARET_TAG, ignoreCase = true) } \ No newline at end of file diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinMultiModuleJava9ProjectDescriptor.kt b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinMultiModuleJava9ProjectDescriptor.kt new file mode 100644 index 00000000000..00249f3d6eb --- /dev/null +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/KotlinMultiModuleJava9ProjectDescriptor.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2017 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.test + +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.* +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.ex.temp.TempFileSystem +import com.intellij.pom.java.LanguageLevel +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.kotlin.test.TestJdkKind + +/** + * Dependencies: 'main' -> 'm2', 'main' -> 'm4', 'main' -> 'm5', 'main' -> 'm6' => 'm7' + */ +object KotlinMultiModuleJava9ProjectDescriptor : DefaultLightProjectDescriptor() { + enum class ModuleDescriptor(internal val moduleName: String, internal val rootName: String) { + // Dependent for: none, Depends on: M2, M4, M5, M6 + MAIN(TEST_MODULE_NAME, "/not_used/"), + + // Dependent for: MAIN, Depends on: none + M2("${TEST_MODULE_NAME}_m2", "src_m2"), + + // Dependent for: none, Depends on: none + M3("${TEST_MODULE_NAME}_m3", "src_m3"), + + // Dependent for: MAIN, Depends on: none + M4("${TEST_MODULE_NAME}_m4", "src_m4"), + + // Dependent for: MAIN, Depends on: none + M5("${TEST_MODULE_NAME}_m5", "src_m5"), + + // Dependent for: MAIN, Depends on: M7 + M6("${TEST_MODULE_NAME}_m6", "src_m6"), + + // Dependent for: M6, Depends on: none + M7("${TEST_MODULE_NAME}_m7", "src_m7"); + + fun root(): VirtualFile = + if (this == MAIN) LightPlatformTestCase.getSourceRoot() else TempFileSystem.getInstance().findFileByPath("/$rootName")!! + + fun testRoot(): VirtualFile? = + if (this == MAIN) TempFileSystem.getInstance().findFileByPath("/test_src")!! else null + } + + override fun getSdk(): Sdk = PluginTestCaseBase.jdk(TestJdkKind.FULL_JDK_9) + + override fun setUpProject(project: Project, handler: SetupHandler) { + super.setUpProject(project, handler) + + runWriteAction { + val main = ModuleManager.getInstance(project).findModuleByName(TEST_MODULE_NAME)!! + + val m2 = makeModule(project, ModuleDescriptor.M2) + ModuleRootModificationUtil.addDependency(main, m2) + + makeModule(project, ModuleDescriptor.M3) + + val m4 = makeModule(project, ModuleDescriptor.M4) + ModuleRootModificationUtil.addDependency(main, m4) + + val m5 = makeModule(project, ModuleDescriptor.M5) + ModuleRootModificationUtil.addDependency(main, m5) + + val m6 = makeModule(project, ModuleDescriptor.M6) + ModuleRootModificationUtil.addDependency(main, m6) + + val m7 = makeModule(project, ModuleDescriptor.M7) + ModuleRootModificationUtil.addDependency(m6, m7, DependencyScope.COMPILE, true) + } + } + + private fun makeModule(project: Project, descriptor: ModuleDescriptor): Module { + val path = FileUtil.join(FileUtil.getTempDirectory(), "${descriptor.moduleName}.iml") + val module = createModule(project, path) + val sourceRoot = createSourceRoot(module, descriptor.rootName) + createContentEntry(module, sourceRoot) + return module + } + + override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) { + model.getModuleExtension(LanguageLevelModuleExtension::class.java).languageLevel = LanguageLevel.JDK_1_9 + if (module.name == TEST_MODULE_NAME) { + val testRoot = createSourceRoot(module, "test_src") + registerSourceRoot(module.project, testRoot) + model.addContentEntry(testRoot).addSourceFolder(testRoot, JavaSourceRootType.TEST_SOURCE) + } + } + + fun cleanupSourceRoots() = runWriteAction { + ModuleDescriptor.values().asSequence() + .filter { it != ModuleDescriptor.MAIN } + .flatMap { it.root().children.asSequence() } + .plus(ModuleDescriptor.MAIN.testRoot()!!.children.asSequence()) + .forEach { it.delete(this) } + } +} \ No newline at end of file diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java index f91c1e84f36..b3d86cc69dc 100644 --- a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/PluginTestCaseBase.java @@ -21,6 +21,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.TestJdkKind; diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddRequiredModuleFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddRequiredModuleFix.kt new file mode 100644 index 00000000000..85a6d107b8f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/KotlinAddRequiredModuleFix.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2017 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.analysis.JavaModuleGraphUtil +import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiredModuleFix +import com.intellij.codeInsight.intention.IntentionAction +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm + +class KotlinAddRequiredModuleFix { + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + val expression = diagnostic.psiElement as? KtExpression ?: return null + val javaModule = JavaModuleGraphUtil.findDescriptorByElement(expression) ?: return null + + val dependDiagnostic = DiagnosticFactory.cast(diagnostic, ErrorsJvm.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE) + val moduleName = dependDiagnostic.a + + return AddRequiredModuleFix(javaModule, moduleName) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 9a939866246..b212203a1bf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -519,5 +519,7 @@ class QuickFixRegistrar : QuickFixContributor { ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_ANNOTATION.registerFactory(ReplaceWithArrayCallInAnnotationFix) ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION.registerFactory(SurroundWithArrayOfWithSpreadOperatorInFunctionFix) + + JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE.registerFactory(KotlinAddRequiredModuleFix) } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AddRequireModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AddRequireModuleTest.kt new file mode 100644 index 00000000000..dd2025a2818 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AddRequireModuleTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2010-2017 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.QuickFixBundle +import org.jetbrains.kotlin.idea.test.KotlinLightJava9ModulesCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinMultiModuleJava9ProjectDescriptor.ModuleDescriptor.* + + +class KotlinAddRequiredModuleTest : KotlinLightJava9ModulesCodeInsightFixtureTestCase() { + private val messageM2 = QuickFixBundle.message("module.info.add.requires.name", "M_TWO")!! + + override fun setUp() { + super.setUp() + moduleInfo("module M_TWO { exports pkgA; }", M2) + addJavaFile("pkgA/A.java", "package pkgA; public class A {}", M2) + } + + fun testAddRequiresToModuleInfo() { + moduleInfo("module MAIN {}", MAIN) + val editedFile = addKotlinFile( + "pkgB/B.kt", + """ + package pkgB + import pkgA.A + class B(a: /*|*/A) + """, + MAIN) + myFixture.configureFromExistingVirtualFile(editedFile) + + findActionAndExecute(messageM2) + assertNoErrors() + + checkModuleInfo( + """ + module MAIN { + requires M_TWO; + } + """) + } + + fun testNoIdeaModuleDependency() { + moduleInfo("module M_THREE {}", M3) + val editedFile = addKotlinFile( + "pkgB/B.kt", + """ + package pkgB + import pkgA.A + class B(a: /*|*/A) + """, + M3) + myFixture.configureFromExistingVirtualFile(editedFile) + + val actions = myFixture.filterAvailableIntentions(messageM2) + assertEmpty(actions) + } + + fun testAddRequiresToInfoForJavaModule() { + moduleInfo("module MAIN {}", MAIN) + val editedFile = addKotlinFile( + "test.kt", + """ + fun test() { + java.util.logging./*|*/FileHandler() // <-- error; "add 'requires java.logging'" quick fix expected + } + """, + MAIN) + myFixture.configureFromExistingVirtualFile(editedFile) + + findActionAndExecute(QuickFixBundle.message("module.info.add.requires.name", "java.logging")!!) + + assertNoErrors() + checkModuleInfo( + """ + module MAIN { + requires java.logging; + } + """) + } + + private fun assertNoErrors() { + myFixture.checkHighlighting(false, false, false) + } + + private fun findActionAndExecute(message: String) { + val action = myFixture.findSingleIntention(message) + assertNotNull(action) + myFixture.launchAction(action) + } +} \ No newline at end of file