diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties index 713466a61a7..9051052da74 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties @@ -59,8 +59,6 @@ specify.type.explicitly.action.family.name=Specify Type Explicitly specify.type.explicitly.add.return.type.action.name=Specify return type explicitly specify.type.explicitly.add.action.name=Specify type explicitly specify.type.explicitly.remove.action.name=Remove explicitly specified type -convert.to.expression.body.action.family.name=Convert to Expression Body -convert.to.expression.body.action.name=Convert to expression body rename.parameter.to.match.overridden.method=Rename parameter to match overridden method rename.family=Rename rename.kotlin.package.class.error="Can't rename kotlin package class" diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt index 7976f0dbfec..914d3aba7f3 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/intentions/JetSelfTargetingIntention.kt @@ -30,8 +30,9 @@ import org.jetbrains.kotlin.psi.psiUtil.parents public abstract class JetSelfTargetingIntention( public val elementType: Class, private var text: String, - private val familyName: String = text) -: IntentionAction { + private val familyName: String = text, + private val firstElementOfTypeOnly: Boolean = false +) : IntentionAction { deprecated("Use primary constructor, no need to use i18n") public constructor(key: String, elementType: Class) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) { } @@ -64,7 +65,14 @@ public abstract class JetSelfTargetingIntention( elementsToCheck += commonParent.parents() } - return elementsToCheck.filterIsInstance(elementType).firstOrNull { isApplicableTo(it, offset) } + val elementsOfType = elementsToCheck.filterIsInstance(elementType) + if (firstElementOfTypeOnly) { + val candidate = elementsOfType.firstOrNull() ?: return null + return if (isApplicableTo(candidate, offset)) candidate else null + } + else { + return elementsOfType.firstOrNull { isApplicableTo(it, offset) } + } } final override fun isAvailable(project: Project, editor: Editor, file: PsiFile) @@ -83,8 +91,9 @@ public abstract class JetSelfTargetingIntention( public abstract class JetSelfTargetingOffsetIndependentIntention( elementType: Class, text: String, - familyName: String = text) -: JetSelfTargetingIntention(elementType, text, familyName) { + familyName: String = text, + firstElementOfTypeOnly: Boolean = false +) : JetSelfTargetingIntention(elementType, text, familyName, firstElementOfTypeOnly) { deprecated("Use primary constructor, no need to use i18n") public constructor(key: String, elementType: Class) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) { diff --git a/idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/after.kt.template b/idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/after.kt.template similarity index 100% rename from idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/after.kt.template rename to idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/after.kt.template diff --git a/idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/before.kt.template b/idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/before.kt.template similarity index 100% rename from idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/before.kt.template rename to idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/before.kt.template diff --git a/idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/description.html b/idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/description.html similarity index 100% rename from idea/resources/intentionDescriptions/ConvertToExpressionBodyAction/description.html rename to idea/resources/intentionDescriptions/ConvertToExpressionBodyIntention/description.html diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 4060e8a3049..be7d3fc4a1b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -490,7 +490,7 @@ - org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyAction + org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt index 190a1722bbe..3a0a795c6d0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToBlockBodyIntention.kt @@ -25,7 +25,9 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -public class ConvertToBlockBodyIntention : JetSelfTargetingIntention(javaClass(), "Convert to block body") { +public class ConvertToBlockBodyIntention : JetSelfTargetingIntention( + javaClass(), "Convert to block body", firstElementOfTypeOnly = true +) { override fun isApplicableTo(element: JetDeclarationWithBody, caretOffset: Int): Boolean { if (element is JetFunctionLiteral || element.hasBlockBody() || !element.hasBody()) return false diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyAction.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt similarity index 69% rename from idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyAction.kt rename to idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt index d2b73df205a..d820a34632e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyAction.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToExpressionBodyIntention.kt @@ -16,39 +16,39 @@ package org.jetbrains.kotlin.idea.intentions -import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analyzer.analyzeInContext import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility -import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf +import kotlin.platform.platformName -public class ConvertToExpressionBodyAction : PsiElementBaseIntentionAction() { - override fun getFamilyName(): String = JetBundle.message("convert.to.expression.body.action.family.name") - - public fun isAvailable(element: PsiElement): Boolean { - val data = calcData(element) - return data != null && !containsReturn(data.value) +public class ConvertToExpressionBodyIntention : JetSelfTargetingOffsetIndependentIntention( + javaClass(), "Convert to expression body", firstElementOfTypeOnly = true +) { + override fun isApplicableTo(element: JetDeclarationWithBody): Boolean { + val value = calcValue(element) + return value != null && !containsReturn(value) } - override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean { - setText(JetBundle.message("convert.to.expression.body.action.name")) - return isAvailable(element) + override fun applyTo(element: JetDeclarationWithBody, editor: Editor) { + applyToInternal(element, editor) } - public fun invoke(element: PsiElement, editor: Editor? = null) { - val (declaration, value) = calcData(element)!! + public fun applyTo(declaration: JetDeclarationWithBody) { + applyToInternal(declaration, null) + } + + private fun applyToInternal(declaration: JetDeclarationWithBody, editor: Editor?) { + val value = calcValue(declaration)!! if (!declaration.hasDeclaredReturnType() && declaration is JetNamedFunction) { val valueType = expressionType(value) @@ -79,10 +79,6 @@ public class ConvertToExpressionBodyAction : PsiElementBaseIntentionAction() { } } - override fun invoke(project: Project, editor: Editor, element: PsiElement) { - invoke(element, editor) - } - private fun canOmitType(declaration: JetCallableDeclaration, expression: JetExpression): Boolean { if (declaration.getModifierList()?.hasModifier(JetTokens.OVERRIDE_KEYWORD) ?: false) return true @@ -101,39 +97,28 @@ public class ConvertToExpressionBodyAction : PsiElementBaseIntentionAction() { return expressionType.isSubtypeOf(declaredType) } - private data class Data(val declaration: JetDeclarationWithBody, val value: JetExpression) - - private fun calcData(element: PsiElement): Data? { - val declaration = element.getStrictParentOfType() - if (declaration == null || declaration is JetFunctionLiteral) return null + private fun calcValue(declaration: JetDeclarationWithBody): JetExpression? { + if (declaration is JetFunctionLiteral) return null val body = declaration.getBodyExpression() if (!declaration.hasBlockBody() || body !is JetBlockExpression) return null - val statements = body.getStatements() - if (statements.size != 1) return null - val statement = statements[0] - return when(statement) { + val statement = body.getStatements().singleOrNull() ?: return null + when(statement) { is JetReturnExpression -> { - val value = statement.getReturnedExpression() - if (value != null) Data(declaration, value) else null + return statement.getReturnedExpression() } //TODO: IMO this is not good code, there should be a way to detect that JetExpression does not have value - is JetDeclaration -> null // is JetExpression but does not have value - is JetLoopExpression -> null // is JetExpression but does not have value + is JetDeclaration, is JetLoopExpression -> return null // is JetExpression but does not have value is JetExpression -> { if (statement is JetBinaryExpression && statement.getOperationToken() == JetTokens.EQ) return null // assignment does not have value - - val expressionType = expressionType(statement) - if (expressionType != null && - (KotlinBuiltIns.isUnit(expressionType) || KotlinBuiltIns.isNothing(expressionType))) - Data(declaration, statement) - else - null + val expressionType = expressionType(statement) ?: return null + if (!KotlinBuiltIns.isUnit(expressionType) && !KotlinBuiltIns.isNothing(expressionType)) return null + return statement } - else -> null + else -> return null } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt index fdacacad610..c694a44d2df 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/extractorUtil.kt @@ -21,7 +21,7 @@ import com.intellij.psi.PsiFile import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester import org.jetbrains.kotlin.idea.core.refactoring.isMultiLine -import org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyAction +import org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer @@ -531,10 +531,11 @@ fun ExtractionGeneratorConfiguration.generateDeclaration( } if (generatorOptions.allowExpressionBody) { - val convertToExpressionBody = ConvertToExpressionBodyAction() + val convertToExpressionBody = ConvertToExpressionBodyIntention() val bodyExpression = body.getStatements().singleOrNull() - if (bodyExpression != null && !bodyExpression.isMultiLine() && convertToExpressionBody.isAvailable(body)) { - convertToExpressionBody.invoke(body) + val bodyOwner = body.getParent() as JetDeclarationWithBody + if (bodyExpression != null && !bodyExpression.isMultiLine() && convertToExpressionBody.isApplicableTo(bodyOwner)) { + convertToExpressionBody.applyTo(bodyOwner) } } } diff --git a/idea/testData/intentions/convertToExpressionBody/.intention b/idea/testData/intentions/convertToExpressionBody/.intention index fb1724c8764..da16192b1a9 100644 --- a/idea/testData/intentions/convertToExpressionBody/.intention +++ b/idea/testData/intentions/convertToExpressionBody/.intention @@ -1 +1 @@ -org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyAction +org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention