diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172
index 1ca575190cc..84fc15c617a 100644
--- a/idea/src/META-INF/plugin.xml.172
+++ b/idea/src/META-INF/plugin.xml.172
@@ -2786,6 +2786,8 @@
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt.172 b/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt.172
new file mode 100644
index 00000000000..6e043e9bb23
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/intentions/KotlinCommonIntentionActionsFactory.kt.172
@@ -0,0 +1,261 @@
+/*
+ * 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.intentions
+
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
+import com.intellij.codeInsight.intention.MethodInsertionInfo
+import com.intellij.codeInsight.intention.QuickFixFactory
+import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.*
+import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl
+import org.jetbrains.kotlin.asJava.LightClassUtil
+import org.jetbrains.kotlin.asJava.elements.KtLightElement
+import org.jetbrains.kotlin.builtins.DefaultBuiltIns
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.idea.core.ShortenReferences
+import org.jetbrains.kotlin.idea.core.insertMembersAfter
+import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
+import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.platform.JavaToKotlinClassMap
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder
+import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.CONSTRUCTOR
+import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.FUNCTION
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UDeclaration
+import org.jetbrains.uast.UElement
+
+class KotlinCommonIntentionActionsFactory : JvmCommonIntentionActionsFactory() {
+ override fun createChangeModifierAction(declaration: UDeclaration, modifier: String, shouldPresent: Boolean): IntentionAction? {
+ val kModifierOwner = declaration.asKtElement()
+ ?: throw IllegalArgumentException("$declaration is expected to contain KtLightElement with KtModifierListOwner")
+
+ val (kToken, shouldPresentMapped) = if (PsiModifier.FINAL == modifier)
+ KtTokens.OPEN_KEYWORD to !shouldPresent
+ else
+ javaPsiModifiersMapping[modifier] to shouldPresent
+
+ if (kToken == null) return null
+ return if (shouldPresentMapped)
+ AddModifierFix.createIfApplicable(kModifierOwner, kToken)
+ else
+ RemoveModifierFix(kModifierOwner, kToken, false)
+ }
+
+ override fun createAddBeanPropertyActions(uClass: UClass,
+ propertyName: String,
+ visibilityModifier: String,
+ propertyType: PsiType,
+ setterRequired: Boolean,
+ getterRequired: Boolean): List {
+
+ fun addPropertyFix(lateinit: Boolean = false) =
+ Fix(uClass,
+ "Add property",
+ "Add '${if (lateinit) "lateinit " else ""}" +
+ "${if (setterRequired) "var" else "val"}' property '$propertyName' to '${uClass.name}'")
+ { uClass ->
+ val visibilityStr = javaVisibilityMapping.getValue(visibilityModifier)
+ val psiFactory = KtPsiFactory(uClass)
+ val modifiersString = if (lateinit) "lateinit $visibilityStr" else visibilityStr
+ val function = psiFactory.createProperty(
+ modifiersString,
+ propertyName,
+ typeString(propertyType),
+ setterRequired,
+ if (lateinit) null else "TODO(\"initialize me\")")
+ val ktClassOrObject = uClass.asKtElement()!!
+ insertMembersAfter(null, ktClassOrObject, listOf(function), null)
+ }
+
+ if (setterRequired)
+ return listOf(addPropertyFix(), addPropertyFix(lateinit = true))
+ else
+ return listOf(addPropertyFix())
+ }
+
+ override fun createAddCallableMemberActions(info: MethodInsertionInfo): List =
+ when (info) {
+ is MethodInsertionInfo.Method ->
+ createAddMethodAction(info)?.let { listOf(it) } ?: emptyList()
+
+ is MethodInsertionInfo.Constructor ->
+ createAddConstructorActions(info)
+ }
+
+ companion object {
+ val javaPsiModifiersMapping = mapOf(
+ PsiModifier.PRIVATE to KtTokens.PRIVATE_KEYWORD,
+ PsiModifier.PUBLIC to KtTokens.PUBLIC_KEYWORD,
+ PsiModifier.PROTECTED to KtTokens.PUBLIC_KEYWORD,
+ PsiModifier.ABSTRACT to KtTokens.ABSTRACT_KEYWORD
+ )
+
+ val javaVisibilityMapping: Map = mapOf(
+ PsiModifier.PRIVATE to Visibilities.PRIVATE.displayName,
+ PsiModifier.PUBLIC to "",
+ PsiModifier.PROTECTED to Visibilities.PROTECTED.displayName,
+ PsiModifier.PACKAGE_LOCAL to Visibilities.INTERNAL.displayName
+ ).withDefault { Visibilities.DEFAULT_VISIBILITY.displayName }
+
+ fun typeString(str: PsiType): String {
+ var typeName: String? = when (str) {
+ PsiType.VOID -> ""
+ PsiType.INT -> "kotlin.Int"
+ PsiType.LONG -> "kotlin.Long"
+ PsiType.SHORT -> "kotlin.Short"
+ PsiType.BOOLEAN -> "kotlin.Boolean"
+ PsiType.BYTE -> "kotlin.Byte"
+ PsiType.CHAR -> "kotlin.Char"
+ PsiType.DOUBLE -> "kotlin.Double"
+ PsiType.FLOAT -> "kotlin.Float"
+ else -> null
+ }
+ if (typeName == null)
+ typeName = JavaToKotlinClassMap.mapJavaToKotlin(FqName(str.canonicalText), DefaultBuiltIns.Instance)?.fqNameSafe?.asString()
+
+ return typeName ?: str.canonicalText
+ }
+ }
+
+ private inline fun UElement.asKtElement(): T? =
+ (psi as? KtLightElement<*, *>?)?.kotlinOrigin as? T
+
+ private fun CallableBuilder.paramsFromInfo(info: MethodInsertionInfo) {
+ for ((index, param) in info.parameters.withIndex()) {
+ param(param.name ?: "arg${index + 1}", typeString(param.type))
+ }
+ }
+
+ private fun createAddMethodAction(info: MethodInsertionInfo.Method): IntentionAction? {
+ val visibilityStr = info.modifiers.map { javaVisibilityMapping.get(it) ?: it }.joinToString(" ")
+ val functionString = CallableBuilder(FUNCTION).apply {
+ modifier(visibilityStr)
+ typeParams()
+ name(info.name)
+ paramsFromInfo(info)
+ info.returnType.let {
+ when (it) {
+ PsiType.VOID -> noReturnType()
+ else -> returnType(typeString(it))
+ }
+ }
+ blockBody("")
+ }
+
+ return Fix(info.containingClass, "Add method", "Add method '${info.name}' to '${info.containingClass.name}'") {
+ uClass ->
+ val psiFactory = KtPsiFactory(uClass)
+ val function = psiFactory.createFunction(functionString.asString())
+ val ktClassOrObject = uClass.asKtElement()!!
+ insertMembersAfter(null, ktClassOrObject, listOf(function), ktClassOrObject.declarations.lastOrNull())
+ }
+
+ }
+
+ private fun createAddConstructorActions(info: MethodInsertionInfo.Constructor): List {
+ val constructorString = CallableBuilder(CONSTRUCTOR).apply {
+ modifier("")
+ typeParams()
+ name()
+ paramsFromInfo(info)
+ noReturnType()
+ blockBody("")
+ }.asString()
+ val primaryConstructor = info.containingClass.asKtElement()!!.primaryConstructor
+
+ val addConstructorAction = if (primaryConstructor == null)
+ Fix(info.containingClass,
+ "Add method",
+ "Add primary constructor to '${info.containingClass.name}'",
+ { uClass ->
+ val psiFactory = KtPsiFactory(uClass)
+ val constructor = psiFactory.createSecondaryConstructor(constructorString)
+ val ktClass = uClass.asKtElement()!!
+ val newPrimaryConstructor = ktClass.createPrimaryConstructorIfAbsent()
+ newPrimaryConstructor.valueParameterList!!.replace(constructor.valueParameterList!!)
+ ShortenReferences.DEFAULT.process(newPrimaryConstructor)
+ })
+ else Fix(info.containingClass,
+ "Add method",
+ "Add secondary constructor to '${info.containingClass.name}'",
+ { uClass ->
+ val psiFactory = KtPsiFactory(uClass)
+ val constructor = psiFactory.createSecondaryConstructor(constructorString)
+ val ktClassOrObject = uClass.asKtElement()!!
+ insertMembersAfter(null, ktClassOrObject, listOf(constructor), null)
+ })
+
+ val changePrimaryConstructorAction = run {
+ if (primaryConstructor == null) return@run null
+ QuickFixFactory.getInstance()
+ .createChangeMethodSignatureFromUsageFix(
+ LightClassUtil.getLightClassMethod(primaryConstructor)!!,
+ fakeParametersExpressions(info.parameters),
+ PsiSubstitutor.EMPTY, info.containingClass, false, 2
+ ).takeIf { it.isAvailable(info.containingClass.project, null, info.containingClass.containingFile) }
+ }
+
+ return listOf(changePrimaryConstructorAction, addConstructorAction).filterNotNull()
+ }
+
+ private fun fakeParametersExpressions(parameters: List): Array =
+ when {
+ parameters.isEmpty() -> emptyArray()
+ else -> JavaPsiFacade.getElementFactory(parameters.first().project)
+ .createParameterList(
+ parameters.map { it.name }.toTypedArray(),
+ parameters.map { it.type }.toTypedArray()
+ ).parameters.map { FakeExpressionFromParameter(it) }.toTypedArray()
+ }
+
+ private class FakeExpressionFromParameter(private val psiParam: PsiParameter) : PsiReferenceExpressionImpl() {
+
+ override fun getText(): String = psiParam.name!!
+
+ override fun getProject(): Project = psiParam.project
+
+ override fun getParent(): PsiElement = psiParam.parent
+
+ override fun getType(): PsiType? = psiParam.type
+
+ override fun isValid(): Boolean = true
+
+ override fun getContainingFile(): PsiFile = psiParam.containingFile
+
+ override fun getReferenceName(): String? = psiParam.name
+
+ override fun resolve(): PsiElement? = psiParam
+ }
+
+ private class Fix(uClass: UClass, private val familyName: String, private val text: String, private val action: (uClass: UClass) -> Unit) : LocalQuickFixAndIntentionActionOnPsiElement(uClass) {
+ override fun getFamilyName(): String = familyName
+
+ override fun getText(): String = text
+
+ override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) =
+ action(startElement as UClass)
+ }
+
+}
+
diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt.172 b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt.172
new file mode 100644
index 00000000000..b4e2b043f63
--- /dev/null
+++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/CommonIntentionActionsTest.kt.172
@@ -0,0 +1,307 @@
+/*
+ * 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.intention.IntentionAction
+import com.intellij.codeInsight.intention.JvmCommonIntentionActionsFactory
+import com.intellij.codeInsight.intention.MethodInsertionInfo
+import com.intellij.lang.Language
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiType
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.testFramework.fixtures.CodeInsightTestFixture
+import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
+import org.jetbrains.uast.*
+import org.junit.Assert
+
+class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
+ fun testMakeNotFinal() {
+ myFixture.configureByText("foo.kt", """
+ class Foo {
+ fun bar(){}
+ }
+ """)
+
+ myFixture.launchAction(
+ codeModifications
+ .createChangeModifierAction(atCaret(myFixture), PsiModifier.FINAL, false)!!
+ .ensureHasText("Make 'bar' open")
+ )
+ myFixture.checkResult("""
+ class Foo {
+ open fun bar(){}
+ }
+ """)
+ }
+
+ fun testMakePrivate() {
+ myFixture.configureByText("foo.kt", """
+ class Foo {
+ fun bar(){}
+ }
+ """)
+
+ myFixture.launchAction(
+ codeModifications
+ .createChangeModifierAction(atCaret(myFixture), PsiModifier.PRIVATE, true)!!
+ .ensureHasText("Make 'Foo' private")
+ )
+ myFixture.checkResult("""
+ private class Foo {
+ fun bar(){}
+ }
+ """)
+ }
+
+ fun testMakeNotPrivate() {
+ myFixture.configureByText("foo.kt", """
+ private class Foo {
+ fun bar(){}
+ }
+ """.trim())
+
+ myFixture.launchAction(
+ codeModifications
+ .createChangeModifierAction(atCaret(myFixture), PsiModifier.PRIVATE, false)!!
+ .ensureHasText("Remove 'private' modifier")
+ )
+ myFixture.checkResult("""
+ class Foo {
+ fun bar(){}
+ }
+ """.trim(), true)
+ }
+
+ fun testDontMakeFunInObjectsOpen() {
+ myFixture.configureByText("foo.kt", """
+ object Foo {
+ fun bar(){}
+ }
+ """.trim())
+ Assert.assertNull(codeModifications.createChangeModifierAction(atCaret(myFixture), PsiModifier.FINAL, false))
+ }
+
+ fun testAddVoidVoidMethod() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ | fun bar() {}
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.simpleMethodInfo(atCaret(myFixture), "baz", PsiModifier.PRIVATE, PsiType.VOID, emptyList())
+ ).findWithText("Add method 'baz' to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo {
+ | fun bar() {}
+ | private fun baz() {
+ |
+ | }
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddIntIntMethod() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ | fun bar() {}
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.simpleMethodInfo(atCaret(myFixture), "baz", PsiModifier.PUBLIC, PsiType.INT, makeParams(PsiType.INT))
+ ).findWithText("Add method 'baz' to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo {
+ | fun bar() {}
+ | fun baz(param0: Int): Int {
+ |
+ | }
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddIntPrimaryConstructor() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
+ ).findWithText("Add primary constructor to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo(param0: Int) {
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddIntSecondaryConstructor() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo() {
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
+ ).findWithText("Add secondary constructor to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo() {
+ | constructor(param0: Int) {
+ |
+ | }
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testChangePrimaryConstructorInt() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo() {
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams(PsiType.INT))
+ ).findWithText("Add 'int' as 1st parameter to method 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo(param0: Int) {
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testRemoveConstructorParameters() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo(i: Int) {
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddCallableMemberActions(
+ MethodInsertionInfo.constructorInfo(atCaret(myFixture), makeParams())
+ ).findWithText("Remove 1st parameter from method 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo() {
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddStringVarProperty() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ | fun bar() {}
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddBeanPropertyActions(
+ atCaret(myFixture),
+ "baz",
+ PsiModifier.PUBLIC,
+ PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)),
+ true,
+ true
+ ).findWithText("Add 'var' property 'baz' to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo {
+ | var baz: String = TODO("initialize me")
+ | fun bar() {}
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddLateInitStringVarProperty() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ | fun bar() {}
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(codeModifications.createAddBeanPropertyActions(
+ atCaret(myFixture), "baz", PsiModifier.PUBLIC, PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)), true, true)
+ .findWithText("Add 'lateinit var' property 'baz' to 'Foo'"))
+ myFixture.checkResult("""
+ |class Foo {
+ | lateinit var baz: String
+ | fun bar() {}
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ fun testAddStringValProperty() {
+ myFixture.configureByText("foo.kt", """
+ |class Foo {
+ | fun bar() {}
+ |}
+ """.trim().trimMargin())
+
+ myFixture.launchAction(
+ codeModifications.createAddBeanPropertyActions(
+ atCaret(myFixture),
+ "baz",
+ PsiModifier.PUBLIC,
+ PsiType.getTypeByName("java.lang.String", project, GlobalSearchScope.allScope(project)),
+ false,
+ true
+ ).findWithText("Add 'val' property 'baz' to 'Foo'")
+ )
+ myFixture.checkResult("""
+ |class Foo {
+ | val baz: String = TODO("initialize me")
+ | fun bar() {}
+ |}
+ """.trim().trimMargin(), true)
+ }
+
+ private fun makeParams(vararg psyTypes: PsiType): List {
+ val uastContext = UastContext(myFixture.project)
+ val factory = JavaPsiFacade.getElementFactory(myFixture.project)
+ val parameters = psyTypes.mapIndexed { index, psiType -> factory.createParameter("param$index", psiType) }
+ return parameters.map { uastContext.convertElement(it, null, UParameter::class.java) as UParameter }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun atCaret(myFixture: CodeInsightTestFixture): T {
+ return myFixture.elementAtCaret.toUElement() as T
+ }
+
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ private fun List.findWithText(text: String): IntentionAction =
+ this.firstOrNull { it.text == text } ?:
+ Assert.fail("intention with text '$text' was not found, only ${this.joinToString { "\"${it.text}\"" }} available") as Nothing
+
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ private fun IntentionAction.ensureHasText(text: String): IntentionAction =
+ if (this.text == text) this else Assert.fail("intention with text '$text' was not found, only \"${this.text}\" available") as Nothing
+
+ private val codeModifications: JvmCommonIntentionActionsFactory
+ get() = JvmCommonIntentionActionsFactory.forLanguage(Language.findLanguageByID("kotlin")!!)!!
+
+}
+