Extraction Engine: Support lazy properties and properties with initializer
This commit is contained in:
@@ -535,7 +535,7 @@ public class JetPsiFactory(private val project: Project) {
|
||||
state = State.RECEIVER
|
||||
}
|
||||
|
||||
private fun blockPrefix() = when (target) {
|
||||
private fun bodyPrefix() = when (target) {
|
||||
Target.FUNCTION -> ""
|
||||
Target.READ_ONLY_PROPERTY -> "\nget()"
|
||||
}
|
||||
@@ -621,19 +621,28 @@ public class JetPsiFactory(private val project: Project) {
|
||||
return this
|
||||
}
|
||||
|
||||
public fun simpleBody(body: String): CallableBuilder {
|
||||
public fun blockBody(body: String): CallableBuilder {
|
||||
assert(state == State.BODY || state == State.TYPE_CONSTRAINTS)
|
||||
|
||||
sb.append(blockPrefix()).append(" = ").append(body)
|
||||
sb.append(bodyPrefix()).append(" {\n").append(body).append("\n}")
|
||||
state = State.DONE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public fun blockBody(body: String): CallableBuilder {
|
||||
assert(state == State.BODY || state == State.TYPE_CONSTRAINTS)
|
||||
public fun initializer(body: String): CallableBuilder {
|
||||
assert(target == Target.READ_ONLY_PROPERTY && (state == State.BODY || state == State.TYPE_CONSTRAINTS))
|
||||
|
||||
sb.append(blockPrefix()).append(" {\n").append(body).append("\n}")
|
||||
sb.append(" = ").append(body)
|
||||
state = State.DONE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public fun lazyBody(body: String): CallableBuilder {
|
||||
assert(target == Target.READ_ONLY_PROPERTY && (state == State.BODY || state == State.TYPE_CONSTRAINTS))
|
||||
|
||||
sb.append(" by kotlin.properties.Delegates.lazy {\n").append(body).append("\n}")
|
||||
state = State.DONE
|
||||
|
||||
return this
|
||||
|
||||
@@ -130,11 +130,17 @@ public fun JetElement.outermostLastBlockElement(predicate: (JetElement) -> Boole
|
||||
return JetPsiUtil.getOutermostLastBlockElement(this) { e -> e != null && predicate(e) }
|
||||
}
|
||||
|
||||
public fun JetBlockExpression.appendElement(element: JetElement): JetElement =
|
||||
addAfter(element, getRBrace()!!.getPrevSibling()!!)!! as JetElement
|
||||
|
||||
public fun JetBlockExpression.prependElement(element: JetElement): JetElement =
|
||||
addBefore(element, getLBrace()!!.getNextSibling()!!)!! as JetElement
|
||||
public fun JetBlockExpression.appendElement(element: JetElement): JetElement {
|
||||
val rBrace = getRBrace()
|
||||
val anchor = if (rBrace == null) {
|
||||
val lastChild = getLastChild()
|
||||
if (lastChild !is PsiWhiteSpace) addAfter(JetPsiFactory(this).createNewLine(), lastChild)!! else lastChild
|
||||
}
|
||||
else {
|
||||
rBrace.getPrevSibling()!!
|
||||
}
|
||||
return addAfter(element, anchor)!! as JetElement
|
||||
}
|
||||
|
||||
public fun JetElement.wrapInBlock(): JetBlockExpression {
|
||||
val block = JetPsiFactory(this).createEmptyBody()
|
||||
|
||||
+55
-26
@@ -17,37 +17,21 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.JetCallExpression
|
||||
import org.jetbrains.kotlin.psi.JetElement
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.psi.JetThisExpression
|
||||
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference.ShorteningMode
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||
import org.jetbrains.kotlin.psi.JetQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.JetTypeParameter
|
||||
import org.jetbrains.kotlin.psi.JetTypeConstraint
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringBundle
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.psi.JetProperty
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.psi.JetClassBody
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
||||
import org.jetbrains.kotlin.types.CommonSupertypes
|
||||
import java.util.Collections
|
||||
import org.jetbrains.kotlin.psi.JetExpression
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.psi.JetReturnExpression
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.*
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import kotlin.properties.Delegates
|
||||
@@ -57,6 +41,8 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.idea.util.isUnit
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import java.util.*
|
||||
|
||||
trait Parameter {
|
||||
val argumentText: String
|
||||
@@ -314,11 +300,62 @@ data class ExtractableCodeDescriptor(
|
||||
val controlFlow: ControlFlow
|
||||
) {
|
||||
val name: String get() = suggestedNames.firstOrNull() ?: ""
|
||||
val duplicates: List<DuplicateInfo> by Delegates.lazy { findDuplicates() }
|
||||
}
|
||||
|
||||
enum class ExtractionTarget(val name: String) {
|
||||
FUNCTION : ExtractionTarget("function") {
|
||||
override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
|
||||
}
|
||||
|
||||
PROPERTY_WITH_INITIALIZER : ExtractionTarget("property with initializer") {
|
||||
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
return checkSignatureAndParent(descriptor) && checkSimpleControlFlow(descriptor) && checkSimpleBody(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
PROPERTY_WITH_GETTER : ExtractionTarget("property with getter") {
|
||||
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
return checkSignatureAndParent(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
LAZY_PROPERTY : ExtractionTarget("lazy property") {
|
||||
override fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
return checkSignatureAndParent(descriptor) && checkSimpleControlFlow(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun isAvailable(descriptor: ExtractableCodeDescriptor): Boolean
|
||||
|
||||
class object {
|
||||
fun checkSimpleBody(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
val expression = descriptor.extractionData.getExpressions().singleOrNull()
|
||||
return expression != null && expression !is JetDeclaration && expression !is JetBlockExpression
|
||||
}
|
||||
|
||||
fun checkSimpleControlFlow(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
val outputValue = descriptor.controlFlow.outputValues.singleOrNull()
|
||||
return (outputValue is ExpressionValue && !outputValue.callSiteReturn) || outputValue is Initializer
|
||||
}
|
||||
|
||||
fun checkSignatureAndParent(descriptor: ExtractableCodeDescriptor): Boolean {
|
||||
if (!descriptor.parameters.isEmpty()) return false
|
||||
if (descriptor.controlFlow.outputValueBoxer.returnType.isUnit()) return false
|
||||
|
||||
val parent = descriptor.extractionData.targetSibling.getParent()
|
||||
return (parent is JetFile || parent is JetClassBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val propertyTargets: List<ExtractionTarget> = listOf(ExtractionTarget.PROPERTY_WITH_INITIALIZER,
|
||||
ExtractionTarget.PROPERTY_WITH_GETTER,
|
||||
ExtractionTarget.LAZY_PROPERTY)
|
||||
|
||||
data class ExtractionGeneratorOptions(
|
||||
val inTempFile: Boolean = false,
|
||||
val extractAsProperty: Boolean = false,
|
||||
val target: ExtractionTarget = ExtractionTarget.FUNCTION,
|
||||
val flexibleTypesAllowed: Boolean = false
|
||||
) {
|
||||
class object {
|
||||
@@ -393,11 +430,3 @@ class ExtractableCodeDescriptorWithConflicts(
|
||||
val descriptor: ExtractableCodeDescriptor,
|
||||
val conflicts: MultiMap<PsiElement, String>
|
||||
)
|
||||
|
||||
fun ExtractableCodeDescriptor.canGenerateProperty(): Boolean {
|
||||
if (!parameters.isEmpty()) return false
|
||||
if (controlFlow.outputValueBoxer.returnType.isUnit()) return false
|
||||
|
||||
val parent = extractionData.targetSibling.getParent()
|
||||
return parent is JetFile || parent is JetClassBody
|
||||
}
|
||||
|
||||
+9
-5
@@ -92,7 +92,7 @@ data class ExtractionData(
|
||||
|
||||
fun getExpressions(): List<JetExpression> = originalElements.filterIsInstance<JetExpression>()
|
||||
|
||||
fun getCodeFragmentTextRange(): TextRange? {
|
||||
private fun getCodeFragmentTextRange(): TextRange? {
|
||||
val originalElements = originalElements
|
||||
return when (originalElements.size()) {
|
||||
0 -> null
|
||||
@@ -105,8 +105,9 @@ data class ExtractionData(
|
||||
}
|
||||
}
|
||||
|
||||
fun getCodeFragmentText(): String =
|
||||
getCodeFragmentTextRange()?.let { originalFile.getText()?.substring(it.getStartOffset(), it.getEndOffset()) } ?: ""
|
||||
val codeFragmentText: String by Delegates.lazy {
|
||||
getCodeFragmentTextRange()?.let { originalFile.getText()?.substring(it.getStartOffset(), it.getEndOffset()) } ?: ""
|
||||
}
|
||||
|
||||
val originalStartOffset = originalElements.firstOrNull()?.let { e -> e.getTextRange()!!.getStartOffset() }
|
||||
|
||||
@@ -179,6 +180,9 @@ data class ExtractionData(
|
||||
// Hack:
|
||||
// we can't get first element offset through getStatement()/getChildren() since they skip comments and whitespaces
|
||||
// So we take offset of the left brace instead and increase it by 2 (which is length of "{\n" separating block start and its first element)
|
||||
private fun JetBlockExpression.getBlockContentOffset(): Int {
|
||||
return getLBrace()!!.getTextRange()!!.getStartOffset() + 2
|
||||
private fun JetExpression.getBlockContentOffset(): Int {
|
||||
(this as? JetBlockExpression)?.getLBrace()?.let {
|
||||
return it.getTextRange()!!.getStartOffset() + 2
|
||||
}
|
||||
return getTextRange()!!.getStartOffset()
|
||||
}
|
||||
|
||||
+1
-1
@@ -110,4 +110,4 @@ public fun processDuplicates(
|
||||
|
||||
project.executeWriteCommand(MethodDuplicatesHandler.REFACTORING_NAME, replacer)
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-6
@@ -69,6 +69,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.idea.refactoring.comparePossiblyOverridingDescriptors
|
||||
import org.jetbrains.kotlin.idea.util.makeNullable
|
||||
import org.jetbrains.kotlin.resolve.calls.CallTransformer
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.*
|
||||
|
||||
private val DEFAULT_FUNCTION_NAME = "myFun"
|
||||
private val DEFAULT_RETURN_TYPE = KotlinBuiltIns.getInstance().getUnitType()
|
||||
@@ -391,7 +392,7 @@ fun ExtractionData.createTemporaryDeclaration(functionText: String): JetNamedDec
|
||||
}
|
||||
|
||||
private fun ExtractionData.createTemporaryCodeBlock(): JetBlockExpression =
|
||||
(createTemporaryDeclaration("fun() {\n${getCodeFragmentText()}\n}\n") as JetNamedFunction).getBodyExpression() as JetBlockExpression
|
||||
(createTemporaryDeclaration("fun() {\n$codeFragmentText\n}\n") as JetNamedFunction).getBodyExpression() as JetBlockExpression
|
||||
|
||||
private fun JetType.collectReferencedTypes(processTypeArguments: Boolean): List<JetType> {
|
||||
if (!processTypeArguments) return Collections.singletonList(this)
|
||||
@@ -644,7 +645,7 @@ private fun ExtractionData.inferParametersInfo(
|
||||
"this$label"
|
||||
}
|
||||
else
|
||||
(thisExpr ?: ref).getText() ?: throw AssertionError("'this' reference shouldn't be empty: code fragment = ${getCodeFragmentText()}")
|
||||
(thisExpr ?: ref).getText() ?: throw AssertionError("'this' reference shouldn't be empty: code fragment = $codeFragmentText")
|
||||
|
||||
MutableParameter(argumentText, descriptorToExtract, extractThis)
|
||||
}
|
||||
@@ -835,11 +836,21 @@ fun ExtractionData.performAnalysis(): AnalysisResult {
|
||||
)
|
||||
}
|
||||
|
||||
private fun JetNamedDeclaration.getGeneratedBlockBody() =
|
||||
private fun JetNamedDeclaration.getGeneratedBody() =
|
||||
when (this) {
|
||||
is JetNamedFunction -> getBodyExpression()
|
||||
else -> (this as JetProperty).getGetter()!!.getBodyExpression()
|
||||
} as? JetBlockExpression ?: throw AssertionError("Couldn't get block body for this declaration: ${JetPsiUtil.getElementTextWithContext(this)}")
|
||||
else -> {
|
||||
val property = this as JetProperty
|
||||
|
||||
property.getGetter()?.getBodyExpression()?.let { return it }
|
||||
property.getInitializer()?.let { return it }
|
||||
// We assume lazy property here with delegate expression 'by Delegates.lazy { body }'
|
||||
property.getDelegateExpression()?.let {
|
||||
val call = it.getCalleeExpressionIfAny()?.getParent() as? JetCallExpression
|
||||
call?.getFunctionLiteralArguments()?.singleOrNull()?.getFunctionLiteral()?.getBodyExpression()
|
||||
}
|
||||
}
|
||||
} ?: throw AssertionError("Couldn't get block body for this declaration: ${JetPsiUtil.getElementTextWithContext(this)}")
|
||||
|
||||
fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts {
|
||||
val conflicts = MultiMap<PsiElement, String>()
|
||||
@@ -847,7 +858,7 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
|
||||
val result = ExtractionGeneratorConfiguration(this, ExtractionGeneratorOptions(inTempFile = true)).generateDeclaration()
|
||||
|
||||
val valueParameterList = (result.declaration as? JetNamedFunction)?.getValueParameterList()
|
||||
val bindingContext = result.declaration.getGeneratedBlockBody().analyze()
|
||||
val bindingContext = result.declaration.getGeneratedBody().analyze()
|
||||
|
||||
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
|
||||
if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue
|
||||
|
||||
+54
-8
@@ -17,6 +17,25 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.psi.JetDeclaration
|
||||
import org.jetbrains.kotlin.psi.JetProperty
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory.CallableBuilder
|
||||
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import java.util.LinkedHashMap
|
||||
import java.util.Collections
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isFunctionLiteralOutsideParentheses
|
||||
import org.jetbrains.kotlin.psi.JetFunctionLiteralArgument
|
||||
import org.jetbrains.kotlin.idea.util.psiModificationUtil.moveInsideParenthesesAndReplaceWith
|
||||
import org.jetbrains.kotlin.psi.psiUtil.appendElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.replaced
|
||||
import org.jetbrains.kotlin.psi.JetBlockExpression
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ParameterUpdate
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Jump
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.Initializer
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.OutputValue.ExpressionValue
|
||||
import org.jetbrains.kotlin.psi.JetReturnExpression
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetNameSuggester
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetNameValidatorImpl
|
||||
@@ -52,11 +71,12 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
|
||||
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
|
||||
else IdeDescriptorRenderers.SOURCE_CODE
|
||||
): String {
|
||||
if (!descriptor.canGenerateProperty() && generatorOptions.extractAsProperty) {
|
||||
throw IllegalArgumentException("Can't generate property: ${descriptor.extractionData.getCodeFragmentText()}")
|
||||
val extractionTarget = generatorOptions.target
|
||||
if (!extractionTarget.isAvailable(descriptor)) {
|
||||
throw IllegalArgumentException("Can't generate ${extractionTarget.name}: ${descriptor.extractionData.codeFragmentText}")
|
||||
}
|
||||
|
||||
val builderTarget = if (generatorOptions.extractAsProperty) Target.READ_ONLY_PROPERTY else Target.FUNCTION
|
||||
val builderTarget = if (extractionTarget == ExtractionTarget.FUNCTION) CallableBuilder.Target.FUNCTION else CallableBuilder.Target.READ_ONLY_PROPERTY
|
||||
return CallableBuilder(builderTarget).let { builder ->
|
||||
builder.modifier(descriptor.visibility)
|
||||
|
||||
@@ -78,13 +98,22 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
|
||||
}
|
||||
|
||||
with(descriptor.controlFlow.outputValueBoxer.returnType) {
|
||||
if (isDefault() || isError()) builder.noReturnType() else builder.returnType(this.typeAsString())
|
||||
if (isDefault() || isError() || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
|
||||
builder.noReturnType()
|
||||
} else {
|
||||
builder.returnType(typeAsString())
|
||||
}
|
||||
}
|
||||
|
||||
builder.typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.getText()!! })
|
||||
|
||||
if (withBody) {
|
||||
builder.blockBody(descriptor.extractionData.getCodeFragmentText())
|
||||
val bodyText = descriptor.extractionData.codeFragmentText
|
||||
when (extractionTarget) {
|
||||
ExtractionTarget.FUNCTION, ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody(bodyText)
|
||||
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer(bodyText)
|
||||
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody(bodyText)
|
||||
}
|
||||
}
|
||||
|
||||
builder.asString()
|
||||
@@ -337,7 +366,9 @@ private fun makeCall(
|
||||
}
|
||||
}
|
||||
|
||||
fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
fun ExtractionGeneratorConfiguration.generateDeclaration(
|
||||
declarationToReplace: JetNamedDeclaration? = null
|
||||
): ExtractionResult{
|
||||
val psiFactory = JetPsiFactory(descriptor.extractionData.originalFile)
|
||||
val nameByOffset = HashMap<Int, JetElement>()
|
||||
|
||||
@@ -388,7 +419,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
}
|
||||
|
||||
fun adjustDeclarationBody(declaration: JetNamedDeclaration) {
|
||||
val body = declaration.getGeneratedBlockBody()
|
||||
val body = declaration.getGeneratedBody()
|
||||
|
||||
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>()
|
||||
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
|
||||
@@ -444,6 +475,10 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
}
|
||||
}
|
||||
|
||||
if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return
|
||||
|
||||
if (body !is JetBlockExpression) throw AssertionError("Block body expected: ${descriptor.extractionData.codeFragmentText}")
|
||||
|
||||
val firstExpression = body.getStatements().firstOrNull()
|
||||
if (firstExpression != null) {
|
||||
for (param in descriptor.parameters) {
|
||||
@@ -474,6 +509,15 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory)
|
||||
if (returnExpression == null) return
|
||||
|
||||
if (generatorOptions.target == ExtractionTarget.LAZY_PROPERTY) {
|
||||
// In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
|
||||
// We just add resulting expressions without return, since returns are prohibited in the body of lazy property
|
||||
if (defaultValue == null) {
|
||||
body.appendElement(returnExpression.getReturnedExpression()!!)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
when {
|
||||
defaultValue == null ->
|
||||
body.appendElement(returnExpression)
|
||||
@@ -484,6 +528,8 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
}
|
||||
|
||||
fun insertDeclaration(declaration: JetNamedDeclaration, anchor: PsiElement): JetNamedDeclaration {
|
||||
declarationToReplace?.let { return it.replace(declaration) as JetNamedDeclaration }
|
||||
|
||||
return with(descriptor.extractionData) {
|
||||
val targetContainer = anchor.getParent()!!
|
||||
val emptyLines = psiFactory.createWhiteSpace("\n\n")
|
||||
@@ -502,7 +548,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(): ExtractionResult{
|
||||
}
|
||||
}
|
||||
|
||||
val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.findDuplicates()
|
||||
val duplicates = if (generatorOptions.inTempFile) Collections.emptyList() else descriptor.duplicates
|
||||
|
||||
val anchor = with(descriptor.extractionData) {
|
||||
val anchorCandidates = duplicates.mapTo(ArrayList<PsiElement>()) { it.range.elements.first() }
|
||||
|
||||
+4
-3
@@ -28,8 +28,8 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.*
|
||||
import kotlin.test.*
|
||||
import com.intellij.openapi.application.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.*
|
||||
import java.util.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.*
|
||||
|
||||
public class KotlinIntroducePropertyHandler(
|
||||
val helper: ExtractionEngineHelper = KotlinIntroducePropertyHandler.InteractiveExtractionHelper
|
||||
@@ -42,8 +42,9 @@ public class KotlinIntroducePropertyHandler(
|
||||
continuation: (ExtractionGeneratorConfiguration) -> Unit
|
||||
) {
|
||||
val descriptor = descriptorWithConflicts.descriptor
|
||||
if (descriptor.canGenerateProperty()) {
|
||||
continuation(ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT.copy(extractAsProperty = true)))
|
||||
val target = propertyTargets.filter { it.isAvailable(descriptor) }.firstOrNull()
|
||||
if (target != null) {
|
||||
continuation(ExtractionGeneratorConfiguration(descriptor, ExtractionGeneratorOptions.DEFAULT.copy(target = target)))
|
||||
}
|
||||
else {
|
||||
showErrorHint(project, editor, "Can't introduce property for this expression", INTRODUCE_PROPERTY)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
class A {
|
||||
fun foo(): Int {
|
||||
<selection>val a = 1 + 2
|
||||
val b = a*2</selection>
|
||||
val c = b - 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
class A {
|
||||
fun foo(): Int {
|
||||
val b = i
|
||||
val c = b - 1
|
||||
}
|
||||
|
||||
private val i: Int by Delegates.lazy {
|
||||
val a = 1 + 2
|
||||
val b = a * 2
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>m + n + 1</selection>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
|
||||
private val i: Int by Delegates.lazy {
|
||||
m + n + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
val n: Int = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
val n: Int = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
|
||||
private val i: Int by Delegates.lazy {
|
||||
n + 1
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return if (n > 1) <selection>{
|
||||
println(n)
|
||||
m + n + 1
|
||||
}</selection> else 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// WITH_RUNTIME
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return if (n > 1) {
|
||||
i
|
||||
} else 0
|
||||
}
|
||||
|
||||
private val i: Int by Delegates.lazy {
|
||||
println(n)
|
||||
m + n + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// EXTRACTION_TARGET: lazy property
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
<selection>return m + n + 1</selection>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Can't generate lazy property: return m + n + 1
|
||||
@@ -1 +1 @@
|
||||
Can't generate property: n + 1
|
||||
Can't generate property with getter: n + 1
|
||||
@@ -1 +1 @@
|
||||
Can't generate property: 1 + 1
|
||||
Can't generate property with getter: 1 + 1
|
||||
@@ -0,0 +1,13 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return if (n > 1) <selection>{
|
||||
println(n)
|
||||
m + n + 1
|
||||
}</selection> else 0
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
Can't generate property with initializer: println(n) m + n + 1
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
val t = <selection>return m + n + 1</selection>
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
Can't generate property with initializer: return m + n + 1
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return if (n > 1) <selection>{
|
||||
m + n + 1
|
||||
}</selection> else 0
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return if (n > 1) {
|
||||
i
|
||||
} else 0
|
||||
}
|
||||
|
||||
private val i = m + n + 1
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
class A {
|
||||
fun foo(): Int {
|
||||
<selection>val a = 1 + 2
|
||||
val b = a*2</selection>
|
||||
val c = b - 1
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
Can't generate property with initializer: val a = 1 + 2 val b = a*2
|
||||
@@ -0,0 +1,10 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>m + n + 1</selection>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
class A(val n: Int = 1) {
|
||||
val m: Int = 2
|
||||
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
|
||||
private val i = m + n + 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
val n: Int = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return <selection>n + 1</selection>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// EXTRACTION_TARGET: property with initializer
|
||||
|
||||
val n: Int = 1
|
||||
|
||||
fun foo(): Int {
|
||||
return i
|
||||
}
|
||||
|
||||
private val i = n + 1
|
||||
@@ -1 +1 @@
|
||||
Can't generate property: n + m + 1
|
||||
Can't generate property with getter: n + m + 1
|
||||
+4
-1
@@ -61,6 +61,9 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
|
||||
protected fun doIntroducePropertyTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
val extractionTarget = propertyTargets.single {
|
||||
it.name == InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// EXTRACTION_TARGET: ")
|
||||
}
|
||||
val helper = object : ExtractionEngineHelper() {
|
||||
override fun configure(
|
||||
descriptor: ExtractableCodeDescriptor,
|
||||
@@ -68,7 +71,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
|
||||
): ExtractionGeneratorConfiguration {
|
||||
return ExtractionGeneratorConfiguration(
|
||||
descriptor,
|
||||
generatorOptions.copy(extractAsProperty = true)
|
||||
generatorOptions.copy(target = extractionTarget)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+66
@@ -1924,6 +1924,36 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("extractLazyMultipleExpressions.kt")
|
||||
public void testExtractLazyMultipleExpressions() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractLazyMultipleExpressions.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractLazyToClass.kt")
|
||||
public void testExtractLazyToClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractLazyToClass.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractLazyToFile.kt")
|
||||
public void testExtractLazyToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractLazyToFile.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractLazyWithBlock.kt")
|
||||
public void testExtractLazyWithBlock() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractLazyWithBlock.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractLazyWithCallSiteReturn.kt")
|
||||
public void testExtractLazyWithCallSiteReturn() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractLazyWithCallSiteReturn.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractToClassWithNameClash.kt")
|
||||
public void testExtractToClassWithNameClash() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractToClassWithNameClash.kt");
|
||||
@@ -1966,6 +1996,42 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerAndBlock.kt")
|
||||
public void testExtractWithInitializerAndBlock() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerAndBlock.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerAndCallSiteReturn.kt")
|
||||
public void testExtractWithInitializerAndCallSiteReturn() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerAndCallSiteReturn.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerAndSingleElementBlock.kt")
|
||||
public void testExtractWithInitializerAndSingleElementBlock() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerAndSingleElementBlock.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerMultipleExpressions.kt")
|
||||
public void testExtractWithInitializerMultipleExpressions() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerMultipleExpressions.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerToClass.kt")
|
||||
public void testExtractWithInitializerToClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerToClass.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithInitializerToFile.kt")
|
||||
public void testExtractWithInitializerToFile() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithInitializerToFile.kt");
|
||||
doIntroducePropertyTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extractWithParams.kt")
|
||||
public void testExtractWithParams() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceProperty/extractWithParams.kt");
|
||||
|
||||
Reference in New Issue
Block a user