Quick fix for module is absent in module-info requirements (KT-20108)

#KT-20108 Fixed
This commit is contained in:
Nikolay Krasko
2017-09-26 16:05:04 +03:00
parent 228588a87c
commit 8aa4b75e1c
6 changed files with 320 additions and 0 deletions
@@ -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) }
@@ -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) }
}
}
@@ -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;
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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)
}
}