Quick-fix for header without implementation + a set of tests #KT-14908 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-12-23 13:43:45 +03:00
parent 07de819377
commit 4774d19890
45 changed files with 890 additions and 86 deletions
@@ -176,6 +176,10 @@ class KtPsiFactory(private val project: Project) {
return createDeclaration(text)
}
fun createObject(text: String): KtObjectDeclaration {
return createDeclaration(text)
}
fun createCompanionObject(): KtObjectDeclaration {
return createClass("class A {\n companion object{\n}\n}").getCompanionObjects().first()
}
@@ -115,7 +115,9 @@ private val MODIFIERS_TO_REPLACE = mapOf(
PUBLIC_KEYWORD to listOf(PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD),
PROTECTED_KEYWORD to listOf(PUBLIC_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD),
PRIVATE_KEYWORD to listOf(PUBLIC_KEYWORD, PROTECTED_KEYWORD, INTERNAL_KEYWORD),
INTERNAL_KEYWORD to listOf(PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD)
INTERNAL_KEYWORD to listOf(PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD),
HEADER_KEYWORD to listOf(IMPL_KEYWORD),
IMPL_KEYWORD to listOf(HEADER_KEYWORD)
)
private val MODIFIERS_ORDER = listOf(PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createTypeParameter.Cr
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateLocalVariableActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByNamedArgumentActionFactory
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createVariable.CreateParameterByRefActionFactory
import org.jetbrains.kotlin.idea.quickfix.createImpl.CreateHeaderImplementationFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateExternalExtensionFix
import org.jetbrains.kotlin.idea.quickfix.migration.MigrateTypeParameterListFix
import org.jetbrains.kotlin.idea.quickfix.replaceWith.DeprecatedSymbolUsageFix
@@ -461,6 +462,8 @@ class QuickFixRegistrar : QuickFixContributor {
OVERLOADS_LOCAL.registerFactory(RemoveAnnotationFix.JvmOverloads)
OVERLOADS_WITHOUT_DEFAULT_ARGUMENTS.registerFactory(RemoveAnnotationFix.JvmOverloads)
HEADER_WITHOUT_IMPLEMENTATION.registerFactory(CreateHeaderImplementationFix)
ErrorsJs.WRONG_EXTERNAL_DECLARATION.registerFactory(MigrateExternalExtensionFix)
}
}
@@ -0,0 +1,241 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.idea.quickfix.createImpl
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.ide.util.PackageUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.psi.JavaDirectoryService
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.TemplateKind
import org.jetbrains.kotlin.idea.core.getFunctionBodyTextFromTemplate
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
sealed class CreateHeaderImplementationFix<out D : KtNamedDeclaration>(
declaration: D,
private val implPlatformKind: PlatformKind,
private val generateIt: KtPsiFactory.(Project, D) -> D?
) : KotlinQuickFixAction<D>(declaration) {
override fun getFamilyName() = text
protected abstract val elementType: String
override fun getText() = "Create header $elementType implementation for platform ${implPlatformKind.name}"
override final fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return
val factory = KtPsiFactory(project)
val implFile = getOrCreateImplementationFile(project) ?: return
val implDeclaration = implFile.add(factory.generateIt(project, element) ?: return) as KtElement
val reformatted = CodeStyleManager.getInstance(project).reformat(implDeclaration)
ShortenReferences.DEFAULT.process(reformatted as KtElement)
}
private fun Project.implementationModuleOf(headerModule: Module) =
allModules().firstOrNull {
TargetPlatformDetector.getPlatform(it).kind == implPlatformKind &&
headerModule in ModuleRootManager.getInstance(it).dependencies
}
private fun getOrCreateImplementationFile(
project: Project
): KtFile? {
val declaration = element as? KtNamedDeclaration ?: return null
val name = declaration.name ?: return null
val headerDir = declaration.containingFile.containingDirectory
val headerPackage = JavaDirectoryService.getInstance().getPackage(headerDir)
val headerModule = ModuleUtilCore.findModuleForPsiElement(declaration) ?: return null
val implModule = project.implementationModuleOf(headerModule) ?: return null
val implDirectory = PackageUtil.findOrCreateDirectoryForPackage(
implModule, headerPackage?.qualifiedName ?: "", null, false
) ?: return null
return getOrCreateKotlinFile("$name.kt", implDirectory)
}
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val d = DiagnosticFactory.cast(diagnostic, Errors.HEADER_WITHOUT_IMPLEMENTATION)
val declaration = d.psiElement as? KtNamedDeclaration ?: return null
val compatibility = d.c
if (compatibility.isNotEmpty()) return null
val implPlatformKind = d.b.platformKind
return when (declaration) {
is KtClassOrObject -> CreateHeaderClassImplementationFix(declaration, implPlatformKind)
is KtFunction -> CreateHeaderFunctionImplementationFix(declaration, implPlatformKind)
is KtProperty -> CreateHeaderPropertyImplementationFix(declaration, implPlatformKind)
else -> null
}
}
}
}
class CreateHeaderClassImplementationFix(
klass: KtClassOrObject,
implPlatformKind: PlatformKind
) : CreateHeaderImplementationFix<KtClassOrObject>(klass, implPlatformKind, { project, element ->
generateClassOrObject(project, element, implNeeded = true)
}) {
override val elementType = if ((element as? KtClass)?.isInterface() ?: false) "interface" else "class"
}
class CreateHeaderPropertyImplementationFix(
property: KtProperty,
implPlatformKind: PlatformKind
) : CreateHeaderImplementationFix<KtProperty>(property, implPlatformKind, { project, element ->
val descriptor = element.toDescriptor() as? PropertyDescriptor
descriptor?.let { generateProperty(project, element, descriptor, implNeeded = true) }
}) {
override val elementType = "property"
}
class CreateHeaderFunctionImplementationFix(
function: KtFunction,
implPlatformKind: PlatformKind
) : CreateHeaderImplementationFix<KtFunction>(function, implPlatformKind, { project, element ->
val descriptor = element.toDescriptor() as? FunctionDescriptor
descriptor?.let { generateFunction(project, element, descriptor, implNeeded = true) }
}) {
override val elementType = "function"
}
private fun KtModifierListOwner.replaceHeaderModifier(implNeeded: Boolean) {
if (implNeeded) {
addModifier(KtTokens.IMPL_KEYWORD)
}
else {
removeModifier(KtTokens.HEADER_KEYWORD)
}
}
private fun KtPsiFactory.generateClassOrObject(
project: Project,
headerClass: KtClassOrObject,
implNeeded: Boolean
): KtClassOrObject {
val header = headerClass.text
val implClass = if (headerClass is KtObjectDeclaration) createObject(header) else createClass(header)
if (headerClass !is KtClass || !headerClass.isInterface()) {
implClass.declarations.forEach {
if (it !is KtEnumEntry &&
it !is KtClassOrObject &&
!it.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
it.delete()
}
}
declLoop@ for (headerDeclaration in headerClass.declarations) {
if (headerDeclaration.hasModifier(KtTokens.ABSTRACT_KEYWORD)) continue
val descriptor = headerDeclaration.toDescriptor() ?: continue
val implDeclaration: KtDeclaration = when (headerDeclaration) {
is KtFunction -> generateFunction(project, headerDeclaration, descriptor as FunctionDescriptor, implNeeded = true)
is KtProperty -> generateProperty(project, headerDeclaration, descriptor as PropertyDescriptor, implNeeded = true)
else -> continue@declLoop
}
implClass.addDeclaration(implDeclaration)
}
}
return implClass.apply {
replaceHeaderModifier(implNeeded)
}
}
private fun KtPsiFactory.generateFunction(
project: Project,
headerFunction: KtFunction,
descriptor: FunctionDescriptor,
implNeeded: Boolean
): KtFunction {
val returnType = descriptor.returnType
val body = run {
if (returnType != null && !KotlinBuiltIns.isUnit(returnType)) {
val delegation = getFunctionBodyTextFromTemplate(
project,
TemplateKind.FUNCTION,
descriptor.name.asString(),
IdeDescriptorRenderers.SOURCE_CODE.renderType(returnType)
)
"{$delegation\n}"
}
else {
"{}"
}
}
return if (headerFunction is KtSecondaryConstructor) {
createSecondaryConstructor(headerFunction.text + " " + body)
}
else {
createFunction(headerFunction.text + " " + body).apply {
replaceHeaderModifier(implNeeded)
if (returnType != null && KotlinBuiltIns.isUnit(returnType)) {
typeReference = null
}
}
}
}
private fun KtPsiFactory.generateProperty(
project: Project,
headerProperty: KtProperty,
descriptor: PropertyDescriptor,
implNeeded: Boolean
): KtProperty {
val body = buildString {
append("\nget()")
append(" = ")
append(getFunctionBodyTextFromTemplate(
project,
TemplateKind.FUNCTION,
descriptor.name.asString(),
descriptor.returnType?.let { IdeDescriptorRenderers.SOURCE_CODE.renderType(it) } ?: ""
))
if (descriptor.isVar) {
append("\nset(value) {}")
}
}
return createProperty(headerProperty.text + " " + body).apply {
replaceHeaderModifier(implNeeded)
}
}
@@ -0,0 +1,11 @@
// "Create header class implementation for platform JS" "true"
header abstract class <caret>Abstract {
fun foo(param: String): Int
abstract fun String.bar(y: Double): Boolean
val isGood: Boolean
abstract var status: Int
}
@@ -0,0 +1,11 @@
// "Create header class implementation for platform JS" "true"
header abstract class Abstract {
fun foo(param: String): Int
abstract fun String.bar(y: Double): Boolean
val isGood: Boolean
abstract var status: Int
}
@@ -0,0 +1 @@
// Abstract: to be implemented
@@ -0,0 +1,13 @@
// Abstract: to be implemented
impl abstract class Abstract {
abstract fun String.bar(y: Double): Boolean
abstract var status: Int
impl fun foo(param: String): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl val isGood: Boolean
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -0,0 +1,15 @@
// "Create header class implementation for platform JVM" "true"
header class <caret>My {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
fun baz(): Unit
constructor(flag: Boolean)
val isGood: Boolean
var status: Int
}
@@ -0,0 +1,15 @@
// "Create header class implementation for platform JVM" "true"
header class My {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
fun baz(): Unit
constructor(flag: Boolean)
val isGood: Boolean
var status: Int
}
+1
View File
@@ -0,0 +1 @@
// My: to be implemented
+23
View File
@@ -0,0 +1,23 @@
// My: to be implemented
impl class My {
impl fun foo(param: String): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl fun String.bar(y: Double): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl fun baz() {}
constructor(flag: Boolean) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
impl val isGood: Boolean
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
impl var status: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
set(value) {}
}
+17
View File
@@ -0,0 +1,17 @@
// "Create header class implementation for platform JS" "true"
header enum class <caret>MyEnum {
FIRST,
SECOND,
LAST;
val num: Int
companion object {
fun byNum(num: Int): MyEnum = when (num) {
1 -> FIRST
2 -> SECOND
else -> LAST
}
}
}
@@ -0,0 +1,17 @@
// "Create header class implementation for platform JS" "true"
header enum class MyEnum {
FIRST,
SECOND,
LAST;
val num: Int
companion object {
fun byNum(num: Int): MyEnum = when (num) {
1 -> FIRST
2 -> SECOND
else -> LAST
}
}
}
+1
View File
@@ -0,0 +1 @@
// MyEnum: to be implemented
@@ -0,0 +1,17 @@
// MyEnum: to be implemented
impl enum class MyEnum {
FIRST,
SECOND,
LAST;
companion object {
fun byNum(num: Int): MyEnum = when (num) {
1 -> FIRST
2 -> SECOND
else -> LAST
}
}
impl val num: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -0,0 +1,3 @@
// "Create header function implementation for platform JVM" "true"
header fun <caret>foo(arg: Int): String
@@ -0,0 +1,3 @@
// "Create header function implementation for platform JVM" "true"
header fun foo(arg: Int): String
+1
View File
@@ -0,0 +1 @@
// foo: to be implemented
@@ -0,0 +1,4 @@
// foo: to be implemented
impl fun foo(arg: Int): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
@@ -0,0 +1,11 @@
// "Create header interface implementation for platform JVM" "true"
header interface <caret>Interface {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
val isGood: Boolean
var status: Int
}
@@ -0,0 +1,11 @@
// "Create header interface implementation for platform JVM" "true"
header interface Interface {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
val isGood: Boolean
var status: Int
}
@@ -0,0 +1 @@
// Interface: to be implemented
@@ -0,0 +1,10 @@
// Interface: to be implemented
impl interface Interface {
fun foo(param: String): Int
fun String.bar(y: Double): Boolean
val isGood: Boolean
var status: Int
}
@@ -0,0 +1,13 @@
// "Create header class implementation for platform JVM" "true"
header class <caret>WithNested {
fun foo(): Int
class Nested {
fun bar() = "Nested"
}
inner class Inner {
fun baz() = "Inner"
}
}
@@ -0,0 +1,13 @@
// "Create header class implementation for platform JVM" "true"
header class WithNested {
fun foo(): Int
class Nested {
fun bar() = "Nested"
}
inner class Inner {
fun baz() = "Inner"
}
}
@@ -0,0 +1 @@
// WithNested: to be implemented
@@ -0,0 +1,15 @@
// WithNested: to be implemented
impl class WithNested {
class Nested {
fun bar() = "Nested"
}
inner class Inner {
fun baz() = "Inner"
}
impl fun foo(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
@@ -0,0 +1,5 @@
// "Create header class implementation for platform JVM" "true"
header object <caret>Object {
fun foo(): String
}
@@ -0,0 +1,5 @@
// "Create header class implementation for platform JVM" "true"
header object Object {
fun foo(): String
}
@@ -0,0 +1 @@
// Object: to be implemented
@@ -0,0 +1,6 @@
// Object: to be implemented
impl object Object {
impl fun foo(): String {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
@@ -0,0 +1,3 @@
// "Create header property implementation for platform JVM" "true"
header var <caret>x: Int
@@ -0,0 +1,3 @@
// "Create header property implementation for platform JVM" "true"
header var x: Int
+1
View File
@@ -0,0 +1 @@
// x: to be implemented
@@ -0,0 +1,4 @@
// x: to be implemented
impl var x: Int
get() = TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
set(value) {}
@@ -0,0 +1,7 @@
// "Create header class implementation for platform JS" "true"
header sealed class <caret>Sealed {
object Obj : Sealed()
class Klass(val x: Int) : Sealed()
}
@@ -0,0 +1,7 @@
// "Create header class implementation for platform JS" "true"
header sealed class Sealed {
object Obj : Sealed()
class Klass(val x: Int) : Sealed()
}
+1
View File
@@ -0,0 +1 @@
// Sealed: to be implemented
@@ -0,0 +1,6 @@
// Sealed: to be implemented
impl sealed class Sealed {
object Obj : Sealed()
class Klass(val x: Int) : Sealed()
}
@@ -16,32 +16,14 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.testFramework.PsiTestUtil
import com.sampullara.cli.Argument
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.config.CompilerSettings
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.stubs.AbstractMultiHighlightingTest
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.junit.Assert
import java.io.File
abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTest() {
protected open val testPath = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleHighlighting/"
override val testPath = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleHighlighting/"
protected fun checkHighlightingInAllFiles() {
var atLeastOneFile = false
@@ -54,69 +36,4 @@ abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTe
}
Assert.assertTrue(atLeastOneFile)
}
protected fun module(name: String, hasTestRoot: Boolean = false, useFullJdk: Boolean = false): Module {
val srcDir = testPath + "${getTestName(true)}/$name"
val moduleWithSrcRootSet = createModuleFromTestData(srcDir, name, StdModuleTypes.JAVA, true)!!
if (hasTestRoot) {
setTestRoot(moduleWithSrcRootSet, name)
}
val jdkToUse = if (useFullJdk) PluginTestCaseBase.fullJdk() else PluginTestCaseBase.mockJdk()
ConfigLibraryUtil.configureSdk(moduleWithSrcRootSet, jdkToUse)
return moduleWithSrcRootSet
}
protected fun setTestRoot(module: Module, name: String) {
val testDir = testPath + "${getTestName(true)}/${name}Test"
val testRootDirInTestData = File(testDir)
val testRootDir = createTempDirectory()!!
FileUtil.copyDir(testRootDirInTestData, testRootDir)
val testRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testRootDir)!!
object : WriteCommandAction.Simple<Unit>(project) {
override fun run() {
testRoot.refresh(false, true)
}
}.execute().throwException()
PsiTestUtil.addSourceRoot(module, testRoot, true)
}
protected fun Module.addDependency(
other: Module,
dependencyScope: DependencyScope = DependencyScope.COMPILE,
exported: Boolean = false
) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported)
private fun Module.createFacet() {
val accessToken = WriteAction.start()
try {
val modelsProvider = IdeModifiableModelsProviderImpl(project)
getOrCreateFacet(modelsProvider)
modelsProvider.commit()
}
finally {
accessToken.finish()
}
}
protected fun Module.setPlatformKind(platformKind: TargetPlatformKind<*>) {
createFacet()
val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this)
val versionInfo = facetSettings.versionInfo
versionInfo.targetPlatformKind = platformKind
}
protected fun Module.enableMultiPlatform() {
createFacet()
val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this)
val compilerInfo = facetSettings.compilerInfo
val compilerSettings = CompilerSettings()
compilerSettings.additionalArguments += " -$multiPlatformArg"
compilerInfo.compilerSettings = compilerSettings
}
companion object {
private val multiPlatformArg = CommonCompilerArguments::multiPlatform.annotations.filterIsInstance<Argument>().single().value
}
}
@@ -0,0 +1,168 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.ui.UIUtil
import junit.framework.ComparisonFailure
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.inspections.findExistingEditor
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import java.util.regex.Pattern
abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest() {
override val testPath = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleQuickFix/"
override fun getTestDataPath() = testPath
protected fun shouldBeAvailableAfterExecution() = false
private fun getActionsTexts(availableActions: List<IntentionAction>) = availableActions.map { it.text }
protected fun doQuickFixTest() {
val allFilesInProject = PluginJetFilesProvider.allFilesInProject(myProject!!)
val actionFile = allFilesInProject.single { file ->
file.text.contains("// \"")
}
configureByExistingFile(actionFile.virtualFile!!)
val actionFileText = actionFile.text
val actionFileName = actionFile.name
CommandProcessor.getInstance().executeCommand(project, {
try {
val psiFile = actionFile
val pair = LightQuickFixTestCase.parseActionHint(psiFile, actionFileText)
val text = pair.getFirst()
val actionShouldBeAvailable = pair.getSecond()
if (psiFile is KtFile) {
DirectiveBasedActionUtils.checkForUnexpectedErrors(psiFile)
}
doAction(text, actionShouldBeAvailable, actionFileName)
if (actionShouldBeAvailable) {
val testDirectory = File(testPath)
val projectDirectory = File("$testPath${getTestName(true)}")
for (moduleDirectory in projectDirectory.listFiles()) {
for (file in moduleDirectory.listFiles()) {
if (!file.path.endsWith(".after")) continue
try {
val editedFile = allFilesInProject.find {
it.name.toLowerCase() == file.name.removeSuffix(".after").toLowerCase()
}!!
setActiveEditor(editedFile.findExistingEditor() ?: createEditor(editedFile.virtualFile))
checkResultByFile(file.relativeTo(testDirectory).path)
}
catch (e: ComparisonFailure) {
KotlinTestUtils.assertEqualsToFile(file, editor)
}
}
}
}
}
catch (e: ComparisonFailure) {
throw e
}
catch (e: AssertionError) {
throw e
}
catch (e: Throwable) {
e.printStackTrace()
TestCase.fail(getTestName(true))
}
}, "", "")
}
// TODO: merge with AbstractQuickFixMultiFileTest
fun doAction(text: String, actionShouldBeAvailable: Boolean, testFilePath: String) {
val pattern = if (text.startsWith("/"))
Pattern.compile(text.substring(1, text.length - 1))
else
Pattern.compile(StringUtil.escapeToRegexp(text))
val availableActions = getAvailableActions()
val action = findActionByPattern(pattern, availableActions)
if (action == null) {
if (actionShouldBeAvailable) {
val texts = getActionsTexts(availableActions)
val infos = doHighlighting()
TestCase.fail("Action with text '" + text + "' is not available in test " + testFilePath + "\n" +
"Available actions (" + texts.size + "): \n" +
StringUtil.join(texts, "\n") +
"\nActions:\n" +
StringUtil.join(availableActions, "\n") +
"\nInfos:\n" +
StringUtil.join(infos, "\n"))
}
else {
DirectiveBasedActionUtils.checkAvailableActionsAreExpected(file, availableActions)
}
}
else {
if (!actionShouldBeAvailable) {
TestCase.fail("Action '$text' is available (but must not) in test $testFilePath")
}
ShowIntentionActionsHandler.chooseActionAndInvoke(file, editor, action, action.text)
UIUtil.dispatchAllInvocationEvents()
if (!shouldBeAvailableAfterExecution()) {
val afterAction = findActionByPattern(pattern, getAvailableActions())
if (afterAction != null) {
TestCase.fail("Action '$text' is still available after its invocation in test $testFilePath")
}
}
}
}
private fun findActionByPattern(pattern: Pattern, availableActions: List<IntentionAction>): IntentionAction? {
for (availableAction in availableActions) {
if (pattern.matcher(availableAction.text).matches()) {
return availableAction
}
}
return null
}
private fun getAvailableActions(): List<IntentionAction> {
doHighlighting()
return LightQuickFixTestCase.getAvailableActions(editor, file)
}
override fun getTestProjectJdk() = PluginTestCaseBase.mockJdk()
}
@@ -0,0 +1,82 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.idea.quickfix
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.junit.Test
class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
private fun doMultiPlatformTest(headerName: String = "header",
implName: String = "jvm",
implKind: TargetPlatformKind<*> = TargetPlatformKind.Jvm.JVM_1_6) {
val header = module(headerName)
header.setPlatformKind(TargetPlatformKind.Common)
val jvm = module(implName)
jvm.setPlatformKind(implKind)
jvm.enableMultiPlatform()
jvm.addDependency(header)
doQuickFixTest()
}
@Test
fun testAbstract() {
doMultiPlatformTest(implName = "js", implKind = TargetPlatformKind.JavaScript)
}
@Test
fun testClass() {
doMultiPlatformTest()
}
@Test
fun testEnum() {
doMultiPlatformTest(implName = "js", implKind = TargetPlatformKind.JavaScript)
}
@Test
fun testFunction() {
doMultiPlatformTest()
}
@Test
fun testInterface() {
doMultiPlatformTest()
}
@Test
fun testObject() {
doMultiPlatformTest()
}
@Test
fun testNested() {
doMultiPlatformTest()
}
@Test
fun testProperty() {
doMultiPlatformTest()
}
@Test
fun testSealed() {
doMultiPlatformTest(implName = "js", implKind = TargetPlatformKind.JavaScript)
}
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.stubs
import com.intellij.codeInsight.completion.CompletionTestCase
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.openapi.application.ApplicationManager
@@ -29,7 +30,7 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.UsageSearchContext
import com.intellij.testFramework.ExpectedHighlightingData
abstract class AbstractMultiHighlightingTest : CompletionTestCase() {
abstract class AbstractMultiHighlightingTest : AbstractMultiModuleTest() {
protected open val shouldCheckLineMarkers = false
@@ -0,0 +1,109 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.idea.stubs
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.testFramework.PsiTestUtil
import com.sampullara.cli.Argument
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.config.CompilerSettings
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import java.io.File
abstract class AbstractMultiModuleTest : DaemonAnalyzerTestCase() {
protected open val testPath = PluginTestCaseBase.getTestDataPathBase()
protected fun module(name: String, hasTestRoot: Boolean = false, useFullJdk: Boolean = false): Module {
val srcDir = testPath + "${getTestName(true)}/$name"
val moduleWithSrcRootSet = createModuleFromTestData(srcDir, name, StdModuleTypes.JAVA, true)!!
if (hasTestRoot) {
setTestRoot(moduleWithSrcRootSet, name)
}
val jdkToUse = if (useFullJdk) PluginTestCaseBase.fullJdk() else PluginTestCaseBase.mockJdk()
ConfigLibraryUtil.configureSdk(moduleWithSrcRootSet, jdkToUse)
return moduleWithSrcRootSet
}
private fun setTestRoot(module: Module, name: String) {
val testDir = testPath + "${getTestName(true)}/${name}Test"
val testRootDirInTestData = File(testDir)
val testRootDir = createTempDirectory()!!
FileUtil.copyDir(testRootDirInTestData, testRootDir)
val testRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testRootDir)!!
object : WriteCommandAction.Simple<Unit>(project) {
override fun run() {
testRoot.refresh(false, true)
}
}.execute().throwException()
PsiTestUtil.addSourceRoot(module, testRoot, true)
}
protected fun Module.addDependency(
other: Module,
dependencyScope: DependencyScope = DependencyScope.COMPILE,
exported: Boolean = false
) = ModuleRootModificationUtil.addDependency(this, other, dependencyScope, exported)
private fun Module.createFacet() {
val accessToken = WriteAction.start()
try {
val modelsProvider = IdeModifiableModelsProviderImpl(project)
getOrCreateFacet(modelsProvider)
modelsProvider.commit()
}
finally {
accessToken.finish()
}
}
protected fun Module.setPlatformKind(platformKind: TargetPlatformKind<*>) {
createFacet()
val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this)
val versionInfo = facetSettings.versionInfo
versionInfo.targetPlatformKind = platformKind
}
protected fun Module.enableMultiPlatform() {
createFacet()
val facetSettings = KotlinFacetSettingsProvider.getInstance(project).getSettings(this)
val compilerInfo = facetSettings.compilerInfo
val compilerSettings = CompilerSettings()
compilerSettings.additionalArguments += " -$multiPlatformArg"
compilerInfo.compilerSettings = compilerSettings
}
companion object {
private val multiPlatformArg = CommonCompilerArguments::multiPlatform.annotations.filterIsInstance<Argument>().single().value
}
}