ConvertToExpressionBodyIntention - refactoring

This commit is contained in:
Valentin Kipyatkov
2015-04-14 22:37:02 +03:00
parent 94480e3d5c
commit c2335790b5
10 changed files with 49 additions and 54 deletions
@@ -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"
@@ -30,8 +30,9 @@ import org.jetbrains.kotlin.psi.psiUtil.parents
public abstract class JetSelfTargetingIntention<TElement : JetElement>(
public val elementType: Class<TElement>,
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<TElement>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
}
@@ -64,7 +65,14 @@ public abstract class JetSelfTargetingIntention<TElement : JetElement>(
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<TElement : JetElement>(
public abstract class JetSelfTargetingOffsetIndependentIntention<TElement : JetElement>(
elementType: Class<TElement>,
text: String,
familyName: String = text)
: JetSelfTargetingIntention<TElement>(elementType, text, familyName) {
familyName: String = text,
firstElementOfTypeOnly: Boolean = false
) : JetSelfTargetingIntention<TElement>(elementType, text, familyName, firstElementOfTypeOnly) {
deprecated("Use primary constructor, no need to use i18n")
public constructor(key: String, elementType: Class<TElement>) : this(elementType, JetBundle.message(key), JetBundle.message(key + ".family")) {
+1 -1
View File
@@ -490,7 +490,7 @@
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyAction</className>
<className>org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention</className>
<category>Kotlin</category>
</intentionAction>
@@ -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<JetDeclarationWithBody>(javaClass(), "Convert to block body") {
public class ConvertToBlockBodyIntention : JetSelfTargetingIntention<JetDeclarationWithBody>(
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
@@ -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<JetDeclarationWithBody>(
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<JetDeclarationWithBody>()
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
}
}
@@ -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)
}
}
}
@@ -1 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyAction
org.jetbrains.kotlin.idea.intentions.ConvertToExpressionBodyIntention