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>
)
interface Replacement: Function1<JetElement, JetElement>
interface Replacement: Function2<ExtractableCodeDescriptor, JetElement, JetElement>
interface ParameterReplacement : Replacement {
val parameter: Parameter
@@ -81,19 +81,17 @@ interface ParameterReplacement : Replacement {
class RenameReplacement(override val parameter: Parameter): ParameterReplacement {
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 }
val parameterName = JetPsiUtil.unquoteIdentifier(parameter.nameForRef)
val replacingName =
if (e.getText().startsWith('`') || !KotlinNameSuggester.isIdentifier(parameterName)) "`$parameterName`" else parameterName
val psiFactory = JetPsiFactory(e)
val replacement =
if (expressionToReplace is JetOperationReferenceExpression) {
psiFactory.createOperationName(replacingName)
}
else {
psiFactory.createSimpleName(replacingName)
}
val replacement = when {
parameter == descriptor.receiverParameter -> psiFactory.createExpression("this")
expressionToReplace is JetOperationReferenceExpression -> psiFactory.createOperationName(replacingName)
else -> psiFactory.createSimpleName(replacingName)
}
return expressionToReplace.replaced(replacement)
}
}
@@ -101,17 +99,18 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacement {
override fun copy(parameter: Parameter) = AddPrefixReplacement(parameter)
override fun invoke(e: JetElement): JetElement {
val selector = (e.getParent() as? JetCallExpression) ?: e
val newExpr = selector.replace(JetPsiFactory(e).createExpression("${parameter.nameForRef}.${selector.getText()}")
) as JetQualifiedExpression
override fun invoke(descriptor: ExtractableCodeDescriptor, e: JetElement): JetElement {
if (descriptor.receiverParameter == parameter) return e
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 {
override fun invoke(e: JetElement): JetElement {
override fun invoke(descriptor: ExtractableCodeDescriptor, e: JetElement): JetElement {
val thisExpr = e.getParent() as? JetThisExpression
if (thisExpr != null) {
return thisExpr.replaced(JetPsiFactory(e).createExpression(fqName.asString())).getQualifiedElementSelector()!!
@@ -1005,8 +1005,9 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
if (resolveResult.declaration.isInsideOf(extractionData.originalElements)) continue
val currentRefExpr = result.nameByOffset[originalOffset] as JetSimpleNameExpression?
if (currentRefExpr == null) continue
val currentRefExpr = result.nameByOffset[originalOffset]?.let {
(it as? JetThisExpression)?.instanceReference ?: it as? JetSimpleNameExpression
} ?: continue
if (currentRefExpr.getParent() is JetThisExpression) continue
@@ -455,7 +455,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
fun adjustDeclarationBody(declaration: JetNamedDeclaration) {
val body = declaration.getGeneratedBody()
val exprReplacementMap = HashMap<JetElement, (JetElement) -> JetElement>()
val exprReplacementMap = HashMap<JetElement, (ExtractableCodeDescriptor, JetElement) -> JetElement>()
val originalOffsetByExpr = LinkedHashMap<JetElement, Int>()
val bodyOffset = body.getBlockContentOffset()
@@ -472,9 +472,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
originalOffsetByExpr[expr!!] = offsetInBody
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) {
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);
}
@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")
public void testQualifiedThisAsArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/extractFunction/parameters/extractThis/qualifiedThisAsArgument.kt");