diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt index d53b62bc28d..2045851eca1 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiFactory.kt @@ -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() } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt index cb8aed3438c..d6ab4667209 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/addRemoveModifier.kt @@ -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, diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 45f26aea827..267cce73220 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -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) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/createImpl/CreateHeaderImplementationFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/createImpl/CreateHeaderImplementationFix.kt new file mode 100644 index 00000000000..069d2bcd2d5 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/createImpl/CreateHeaderImplementationFix.kt @@ -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( + declaration: D, + private val implPlatformKind: PlatformKind, + private val generateIt: KtPsiFactory.(Project, D) -> D? +) : KotlinQuickFixAction(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(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(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(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) + } +} + diff --git a/idea/testData/multiModuleQuickFix/abstract/header/header.kt b/idea/testData/multiModuleQuickFix/abstract/header/header.kt new file mode 100644 index 00000000000..a5c65988619 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/abstract/header/header.kt @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/abstract/header/header.kt.after b/idea/testData/multiModuleQuickFix/abstract/header/header.kt.after new file mode 100644 index 00000000000..b54d1d6758e --- /dev/null +++ b/idea/testData/multiModuleQuickFix/abstract/header/header.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt b/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt new file mode 100644 index 00000000000..7cb7506bdd5 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt @@ -0,0 +1 @@ +// Abstract: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt.after b/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt.after new file mode 100644 index 00000000000..4989755c527 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/abstract/js/abstract.kt.after @@ -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. +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/class/header/header.kt b/idea/testData/multiModuleQuickFix/class/header/header.kt new file mode 100644 index 00000000000..e046fe28806 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/class/header/header.kt @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/class/header/header.kt.after b/idea/testData/multiModuleQuickFix/class/header/header.kt.after new file mode 100644 index 00000000000..672e3eba382 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/class/header/header.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/class/jvm/my.kt b/idea/testData/multiModuleQuickFix/class/jvm/my.kt new file mode 100644 index 00000000000..b39e9e28a63 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/class/jvm/my.kt @@ -0,0 +1 @@ +// My: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/class/jvm/my.kt.after b/idea/testData/multiModuleQuickFix/class/jvm/my.kt.after new file mode 100644 index 00000000000..58df46123e5 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/class/jvm/my.kt.after @@ -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) {} + +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/enum/header/header.kt b/idea/testData/multiModuleQuickFix/enum/header/header.kt new file mode 100644 index 00000000000..bcbd2c7d99e --- /dev/null +++ b/idea/testData/multiModuleQuickFix/enum/header/header.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/enum/header/header.kt.after b/idea/testData/multiModuleQuickFix/enum/header/header.kt.after new file mode 100644 index 00000000000..95e3a9d3b2a --- /dev/null +++ b/idea/testData/multiModuleQuickFix/enum/header/header.kt.after @@ -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 + } + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt b/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt new file mode 100644 index 00000000000..36e994aca4c --- /dev/null +++ b/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt @@ -0,0 +1 @@ +// MyEnum: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt.after b/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt.after new file mode 100644 index 00000000000..627f78ffede --- /dev/null +++ b/idea/testData/multiModuleQuickFix/enum/js/myEnum.kt.after @@ -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. +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/function/header/header.kt b/idea/testData/multiModuleQuickFix/function/header/header.kt new file mode 100644 index 00000000000..c4a8a1eccfa --- /dev/null +++ b/idea/testData/multiModuleQuickFix/function/header/header.kt @@ -0,0 +1,3 @@ +// "Create header function implementation for platform JVM" "true" + +header fun foo(arg: Int): String \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/function/header/header.kt.after b/idea/testData/multiModuleQuickFix/function/header/header.kt.after new file mode 100644 index 00000000000..3b2e42b8ffe --- /dev/null +++ b/idea/testData/multiModuleQuickFix/function/header/header.kt.after @@ -0,0 +1,3 @@ +// "Create header function implementation for platform JVM" "true" + +header fun foo(arg: Int): String \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/function/jvm/foo.kt b/idea/testData/multiModuleQuickFix/function/jvm/foo.kt new file mode 100644 index 00000000000..611b3f4a6eb --- /dev/null +++ b/idea/testData/multiModuleQuickFix/function/jvm/foo.kt @@ -0,0 +1 @@ +// foo: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/function/jvm/foo.kt.after b/idea/testData/multiModuleQuickFix/function/jvm/foo.kt.after new file mode 100644 index 00000000000..221870b89d9 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/function/jvm/foo.kt.after @@ -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. +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/interface/header/header.kt b/idea/testData/multiModuleQuickFix/interface/header/header.kt new file mode 100644 index 00000000000..4a61ad2b410 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/interface/header/header.kt @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/interface/header/header.kt.after b/idea/testData/multiModuleQuickFix/interface/header/header.kt.after new file mode 100644 index 00000000000..4a673dd84e0 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/interface/header/header.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt b/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt new file mode 100644 index 00000000000..1b49d8aa33a --- /dev/null +++ b/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt @@ -0,0 +1 @@ +// Interface: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt.after b/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt.after new file mode 100644 index 00000000000..adea3e95c67 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/interface/jvm/interface.kt.after @@ -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 +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/nested/header/header.kt b/idea/testData/multiModuleQuickFix/nested/header/header.kt new file mode 100644 index 00000000000..6bb4f70a8c2 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/nested/header/header.kt @@ -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" + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/nested/header/header.kt.after b/idea/testData/multiModuleQuickFix/nested/header/header.kt.after new file mode 100644 index 00000000000..8603817fd0f --- /dev/null +++ b/idea/testData/multiModuleQuickFix/nested/header/header.kt.after @@ -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" + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt b/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt new file mode 100644 index 00000000000..d4af1a2d396 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt @@ -0,0 +1 @@ +// WithNested: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt.after b/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt.after new file mode 100644 index 00000000000..4f5d843101c --- /dev/null +++ b/idea/testData/multiModuleQuickFix/nested/jvm/withNested.kt.after @@ -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. + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/object/header/header.kt b/idea/testData/multiModuleQuickFix/object/header/header.kt new file mode 100644 index 00000000000..a79aa8c448e --- /dev/null +++ b/idea/testData/multiModuleQuickFix/object/header/header.kt @@ -0,0 +1,5 @@ +// "Create header class implementation for platform JVM" "true" + +header object Object { + fun foo(): String +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/object/header/header.kt.after b/idea/testData/multiModuleQuickFix/object/header/header.kt.after new file mode 100644 index 00000000000..31ffc36d772 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/object/header/header.kt.after @@ -0,0 +1,5 @@ +// "Create header class implementation for platform JVM" "true" + +header object Object { + fun foo(): String +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/object/jvm/object.kt b/idea/testData/multiModuleQuickFix/object/jvm/object.kt new file mode 100644 index 00000000000..7a057aa5045 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/object/jvm/object.kt @@ -0,0 +1 @@ +// Object: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/object/jvm/object.kt.after b/idea/testData/multiModuleQuickFix/object/jvm/object.kt.after new file mode 100644 index 00000000000..4ddf3781ead --- /dev/null +++ b/idea/testData/multiModuleQuickFix/object/jvm/object.kt.after @@ -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. + } +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/property/header/header.kt b/idea/testData/multiModuleQuickFix/property/header/header.kt new file mode 100644 index 00000000000..ead17244a00 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/property/header/header.kt @@ -0,0 +1,3 @@ +// "Create header property implementation for platform JVM" "true" + +header var x: Int \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/property/header/header.kt.after b/idea/testData/multiModuleQuickFix/property/header/header.kt.after new file mode 100644 index 00000000000..80def470a50 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/property/header/header.kt.after @@ -0,0 +1,3 @@ +// "Create header property implementation for platform JVM" "true" + +header var x: Int \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/property/jvm/x.kt b/idea/testData/multiModuleQuickFix/property/jvm/x.kt new file mode 100644 index 00000000000..04a172b156e --- /dev/null +++ b/idea/testData/multiModuleQuickFix/property/jvm/x.kt @@ -0,0 +1 @@ +// x: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/property/jvm/x.kt.after b/idea/testData/multiModuleQuickFix/property/jvm/x.kt.after new file mode 100644 index 00000000000..bf1590d5ba0 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/property/jvm/x.kt.after @@ -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) {} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealed/header/header.kt b/idea/testData/multiModuleQuickFix/sealed/header/header.kt new file mode 100644 index 00000000000..15944e8eefd --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealed/header/header.kt @@ -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() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealed/header/header.kt.after b/idea/testData/multiModuleQuickFix/sealed/header/header.kt.after new file mode 100644 index 00000000000..2fe1c88ee10 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealed/header/header.kt.after @@ -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() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt b/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt new file mode 100644 index 00000000000..de90a1a560a --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt @@ -0,0 +1 @@ +// Sealed: to be implemented \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt.after b/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt.after new file mode 100644 index 00000000000..3a1683353e2 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/sealed/js/sealed.kt.after @@ -0,0 +1,6 @@ +// Sealed: to be implemented +impl sealed class Sealed { + object Obj : Sealed() + + class Klass(val x: Int) : Sealed() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt index 0d670952005..b35aabba8f5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt @@ -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(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().single().value - } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt new file mode 100644 index 00000000000..573be259616 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt @@ -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) = 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? { + for (availableAction in availableActions) { + if (pattern.matcher(availableAction.text).matches()) { + return availableAction + } + } + return null + } + + private fun getAvailableActions(): List { + doHighlighting() + return LightQuickFixTestCase.getAvailableActions(editor, file) + } + + override fun getTestProjectJdk() = PluginTestCaseBase.mockJdk() +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt new file mode 100644 index 00000000000..a0ee6c75512 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt index d323011d897..bbc74f3dcab 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiHighlightingTest.kt @@ -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 diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt new file mode 100644 index 00000000000..beda6eb8868 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt @@ -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(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().single().value + } + +} \ No newline at end of file