Extraction Engine: Replace receiver occurrences with 'this' when necessary

This commit is contained in:
Alexey Sedunov
2015-09-14 13:16:20 +03:00
parent b80720209e
commit 767897dccf
6 changed files with 50 additions and 22 deletions
@@ -71,7 +71,7 @@ data class TypeParameter(
val originalConstraints: List<JetTypeConstraint> val originalConstraints: List<JetTypeConstraint>
) )
interface Replacement: Function1<JetElement, JetElement> interface Replacement: Function2<ExtractableCodeDescriptor, JetElement, JetElement>
interface ParameterReplacement : Replacement { interface ParameterReplacement : Replacement {
val parameter: Parameter val parameter: Parameter
@@ -81,19 +81,17 @@ interface ParameterReplacement : Replacement {
class RenameReplacement(override val parameter: Parameter): ParameterReplacement { class RenameReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = RenameReplacement(parameter) override fun copy(parameter: Parameter) = RenameReplacement(parameter)
override fun invoke(e: JetElement): JetElement { override fun invoke(descriptor: ExtractableCodeDescriptor, e: JetElement): JetElement {
var expressionToReplace = (e.getParent() as? JetThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it } var expressionToReplace = (e.getParent() as? JetThisExpression ?: e).let { it.getQualifiedExpressionForSelector() ?: it }
val parameterName = JetPsiUtil.unquoteIdentifier(parameter.nameForRef) val parameterName = JetPsiUtil.unquoteIdentifier(parameter.nameForRef)
val replacingName = val replacingName =
if (e.getText().startsWith('`') || !KotlinNameSuggester.isIdentifier(parameterName)) "`$parameterName`" else parameterName if (e.getText().startsWith('`') || !KotlinNameSuggester.isIdentifier(parameterName)) "`$parameterName`" else parameterName
val psiFactory = JetPsiFactory(e) val psiFactory = JetPsiFactory(e)
val replacement = val replacement = when {
if (expressionToReplace is JetOperationReferenceExpression) { parameter == descriptor.receiverParameter -> psiFactory.createExpression("this")
psiFactory.createOperationName(replacingName) expressionToReplace is JetOperationReferenceExpression -> psiFactory.createOperationName(replacingName)
} else -> psiFactory.createSimpleName(replacingName)
else { }
psiFactory.createSimpleName(replacingName)
}
return expressionToReplace.replaced(replacement) return expressionToReplace.replaced(replacement)
} }
} }
@@ -101,17 +99,18 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement { class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter) override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
override fun invoke(e: JetElement): JetElement { override fun invoke(descriptor: ExtractableCodeDescriptor, e: JetElement): JetElement {
val selector = (e.getParent() as? JetCallExpression) ?: e if (descriptor.receiverParameter == parameter) return e
val newExpr = selector.replace(JetPsiFactory(e).createExpression("${parameter.nameForRef}.${selector.getText()}")
) as JetQualifiedExpression
return with(newExpr.getSelectorExpression()!!) { if (this is JetCallExpression) getCalleeExpression()!! else this } val selector = (e.parent as? JetCallExpression) ?: e
val replacingExpression = JetPsiFactory(e).createExpression("${parameter.nameForRef}.${selector.text}")
val newExpr = (selector.replace(replacingExpression) as JetQualifiedExpression).selectorExpression!!
return (newExpr as? JetCallExpression)?.calleeExpression ?: newExpr
} }
} }
class FqNameReplacement(val fqName: FqName): Replacement { class FqNameReplacement(val fqName: FqName): Replacement {
override fun invoke(e: JetElement): JetElement { override fun invoke(descriptor: ExtractableCodeDescriptor, e: JetElement): JetElement {
val thisExpr = e.getParent() as? JetThisExpression val thisExpr = e.getParent() as? JetThisExpression
if (thisExpr != null) { if (thisExpr != null) {
return thisExpr.replaced(JetPsiFactory(e).createExpression(fqName.asString())).getQualifiedElementSelector()!! return thisExpr.replaced(JetPsiFactory(e).createExpression(fqName.asString())).getQualifiedElementSelector()!!
@@ -1005,8 +1005,9 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) { for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue
val currentRefExpr = result.nameByOffset[originalOffset] as JetSimpleNameExpression? val currentRefExpr = result.nameByOffset[originalOffset]?.let {
if (currentRefExpr == null) continue (it as? JetThisExpression)?.instanceReference ?: it as? JetSimpleNameExpression
} ?: continue
if (currentRefExpr.getParent() is JetThisExpression) continue if (currentRefExpr.getParent() is JetThisExpression) continue
@@ -455,7 +455,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
fun adjustDeclarationBody(declaration: JetNamedDeclaration) { fun adjustDeclarationBody(declaration: JetNamedDeclaration) {
val body = declaration.getGeneratedBody() val body = declaration.getGeneratedBody()
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>() val exprReplacementMap = HashMap<JetElement, (ExtractableCodeDescriptor, JetElement) -> JetElement>()
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>() val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
val bodyOffset = body.getBlockContentOffset() val bodyOffset = body.getBlockContentOffset()
@@ -472,9 +472,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
originalOffsetByExpr[expr!!] = offsetInBody originalOffsetByExpr[expr!!] = offsetInBody
descriptor.replacementMap[offsetInBody]?.let { replacement -> descriptor.replacementMap[offsetInBody]?.let { replacement ->
if (replacement !is ParameterReplacement || replacement.parameter != descriptor.receiverParameter) { exprReplacementMap[expr] = replacement
exprReplacementMap[expr] = replacement
}
} }
} }
@@ -505,7 +503,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
for ((expr, originalOffset) in originalOffsetByExpr) { for ((expr, originalOffset) in originalOffsetByExpr) {
if (expr.isValid()) { if (expr.isValid()) {
nameByOffset.put(originalOffset, exprReplacementMap[expr]?.invoke(expr) ?: expr) nameByOffset.put(originalOffset, exprReplacementMap[expr]?.invoke(descriptor, expr) ?: expr)
} }
} }
@@ -0,0 +1,11 @@
// PARAM_TYPES: kotlin.String, Comparable<String>, CharSequence, kotlin.Any
// PARAM_DESCRIPTOR: public fun kotlin.String.test(): kotlin.Unit defined in root package
fun String.foo(f: () -> Unit) {
f()
}
fun String.test() {
"sss".foo {
println(<selection>this@test</selection>)
}
}
@@ -0,0 +1,13 @@
// PARAM_TYPES: kotlin.String, Comparable<String>, CharSequence, kotlin.Any
// PARAM_DESCRIPTOR: public fun kotlin.String.test(): kotlin.Unit defined in root package
fun String.foo(f: () -> Unit) {
f()
}
fun String.test() {
"sss".foo {
println(s())
}
}
private fun String.s() = this
@@ -2003,6 +2003,12 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doExtractFunctionTest(fileName); doExtractFunctionTest(fileName);
} }
@TestMetadata("qualifiedThis.kt")
public void testQualifiedThis() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThis.kt");
doExtractFunctionTest(fileName);
}
@TestMetadata("qualifiedThisAsArgument.kt") @TestMetadata("qualifiedThisAsArgument.kt")
public void testQualifiedThisAsArgument() throws Exception { public void testQualifiedThisAsArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt");