Extraction Engine: Get rid of explicit element offset manipulation and use copyable user data instead
This commit is contained in:
@@ -560,6 +560,8 @@ public class KtPsiFactory(private val project: Project) {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun transform(f: StringBuilder.() -> Unit) = sb.f()
|
||||||
|
|
||||||
public fun asString(): String {
|
public fun asString(): String {
|
||||||
if (state != State.DONE) {
|
if (state != State.DONE) {
|
||||||
state = State.DONE
|
state = State.DONE
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ import com.intellij.openapi.util.UserDataHolder
|
|||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
public class UserDataProperty<in R: UserDataHolder, T : Any>(val key: Key<T>) {
|
public class UserDataProperty<in R : UserDataHolder, T : Any>(val key: Key<T>) {
|
||||||
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key)
|
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key)
|
||||||
|
|
||||||
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T?) = thisRef.putUserData(key, value)
|
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T?) = thisRef.putUserData(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NotNullableUserDataProperty<in R: UserDataHolder, T : Any>(val key: Key<T>, val defaultValue: T) {
|
public class NotNullableUserDataProperty<in R : UserDataHolder, T : Any>(val key: Key<T>, val defaultValue: T) {
|
||||||
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key) ?: defaultValue
|
operator fun getValue(thisRef: R, desc: KProperty<*>) = thisRef.getUserData(key) ?: defaultValue
|
||||||
|
|
||||||
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T) {
|
operator fun setValue(thisRef: R, desc: KProperty<*>, value: T) {
|
||||||
@@ -35,16 +35,25 @@ public class NotNullableUserDataProperty<in R: UserDataHolder, T : Any>(val key:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CopyableUserDataProperty<in R: PsiElement, T : Any>(val key: Key<T>) {
|
public class CopyableUserDataProperty<in R : PsiElement, T : Any>(val key: Key<T>) {
|
||||||
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key)
|
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key)
|
||||||
|
|
||||||
operator fun setValue(thisRef: R, property: KProperty<*>, value: T?) = thisRef.putCopyableUserData(key, value)
|
operator fun setValue(thisRef: R, property: KProperty<*>, value: T?) = thisRef.putCopyableUserData(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NotNullableCopyableUserDataProperty<in R: PsiElement, T : Any>(val key: Key<T>, val defaultValue: T) {
|
public class NotNullableCopyableUserDataProperty<in R : PsiElement, T : Any>(val key: Key<T>, val defaultValue: T) {
|
||||||
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key) ?: defaultValue
|
operator fun getValue(thisRef: R, property: KProperty<*>) = thisRef.getCopyableUserData(key) ?: defaultValue
|
||||||
|
|
||||||
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
|
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
|
||||||
thisRef.putCopyableUserData(key, if (value != defaultValue) value else null)
|
thisRef.putCopyableUserData(key, if (value != defaultValue) value else null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NotNullableCopyableUserDataPropertyWithLazyDefault<in R : PsiElement, T : Any>(val key: Key<T>,
|
||||||
|
val computeDefaultValue: () -> T) {
|
||||||
|
private val delegate by lazy { NotNullableCopyableUserDataProperty<R, T>(key, computeDefaultValue()) }
|
||||||
|
|
||||||
|
operator fun getValue(thisRef: R, property: KProperty<*>) = delegate.getValue(thisRef, property)
|
||||||
|
|
||||||
|
operator fun setValue(thisRef: R, property: KProperty<*>, value: T) = delegate.setValue(thisRef, property, value)
|
||||||
|
}
|
||||||
+1
-1
@@ -671,7 +671,7 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val psiFactory = KtPsiFactory(callable.project)
|
val psiFactory = KtPsiFactory(callable.project)
|
||||||
val tempFile = (callable.containingFile as KtFile).createTempCopy { it }
|
val tempFile = (callable.containingFile as KtFile).createTempCopy()
|
||||||
val functionWithReceiver = tempFile.findElementAt(callable.textOffset)?.getNonStrictParentOfType<KtNamedFunction>() ?: return
|
val functionWithReceiver = tempFile.findElementAt(callable.textOffset)?.getNonStrictParentOfType<KtNamedFunction>() ?: return
|
||||||
val receiverTypeRef = psiFactory.createType(newReceiverInfo.currentTypeText)
|
val receiverTypeRef = psiFactory.createType(newReceiverInfo.currentTypeText)
|
||||||
functionWithReceiver.setReceiverTypeReference(receiverTypeRef)
|
functionWithReceiver.setReceiverTypeReference(receiverTypeRef)
|
||||||
|
|||||||
+2
-51
@@ -113,9 +113,7 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
|||||||
|
|
||||||
setOKActionEnabled(checkNames());
|
setOKActionEnabled(checkNames());
|
||||||
signaturePreviewField.setText(
|
signaturePreviewField.setText(
|
||||||
ExtractorUtilKt.getDeclarationText(getCurrentConfiguration(),
|
ExtractorUtilKt.getSignaturePreview(getCurrentConfiguration(), IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
|
||||||
false,
|
|
||||||
IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,59 +283,12 @@ public class KotlinExtractFunctionDialog extends DialogWrapper {
|
|||||||
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) {
|
for (KotlinParameterTablePanel.ParameterInfo parameterInfo : newParameterInfos) {
|
||||||
oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter());
|
oldToNewParameters.put(parameterInfo.getOriginalParameter(), parameterInfo.toParameter());
|
||||||
}
|
}
|
||||||
ArrayList<Parameter> newParameters = ContainerUtil.newArrayList(oldToNewParameters.values());
|
|
||||||
|
|
||||||
Parameter originalReceiver = originalDescriptor.getReceiverParameter();
|
Parameter originalReceiver = originalDescriptor.getReceiverParameter();
|
||||||
Parameter newReceiver = newReceiverInfo != null ? newReceiverInfo.toParameter() : null;
|
Parameter newReceiver = newReceiverInfo != null ? newReceiverInfo.toParameter() : null;
|
||||||
if (originalReceiver != null && newReceiver != null) {
|
if (originalReceiver != null && newReceiver != null) {
|
||||||
oldToNewParameters.put(originalReceiver, newReceiver);
|
oldToNewParameters.put(originalReceiver, newReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlow controlFlow = originalDescriptor.getControlFlow();
|
return ExtractableCodeDescriptorKt.copy(originalDescriptor, newName, newVisibility, oldToNewParameters, newReceiver, returnType);
|
||||||
List<OutputValue> outputValues = new ArrayList<OutputValue>(controlFlow.getOutputValues());
|
|
||||||
for (int i = 0; i < outputValues.size(); i++) {
|
|
||||||
OutputValue outputValue = outputValues.get(i);
|
|
||||||
if (outputValue instanceof OutputValue.ParameterUpdate) {
|
|
||||||
OutputValue.ParameterUpdate parameterUpdate = (OutputValue.ParameterUpdate) outputValue;
|
|
||||||
outputValues.set(i, new OutputValue.ParameterUpdate(oldToNewParameters.get(parameterUpdate.getParameter()), parameterUpdate.getOriginalExpressions()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controlFlow = new ControlFlow(outputValues, controlFlow.getBoxerFactory(), controlFlow.getDeclarationsToCopy());
|
|
||||||
|
|
||||||
MultiMap<Integer, Replacement> replacementMap = MultiMap.<Integer, Replacement>create();
|
|
||||||
for (Map.Entry<Integer, Collection<Replacement>> e : originalDescriptor.getReplacementMap().entrySet()) {
|
|
||||||
Integer offset = e.getKey();
|
|
||||||
Collection<Replacement> replacements = e.getValue();
|
|
||||||
|
|
||||||
for (Replacement replacement : replacements) {
|
|
||||||
if (replacement instanceof ParameterReplacement) {
|
|
||||||
ParameterReplacement parameterReplacement = (ParameterReplacement) replacement;
|
|
||||||
Parameter parameter = parameterReplacement.getParameter();
|
|
||||||
|
|
||||||
Parameter newParameter = oldToNewParameters.get(parameter);
|
|
||||||
if (newParameter != null) {
|
|
||||||
replacementMap.remove(offset, replacement);
|
|
||||||
replacementMap.putValue(offset, parameterReplacement.copy(newParameter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
replacementMap.put(offset, replacements);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ExtractableCodeDescriptor(
|
|
||||||
originalDescriptor.getExtractionData(),
|
|
||||||
originalDescriptor.getOriginalContext(),
|
|
||||||
Collections.singletonList(newName),
|
|
||||||
newVisibility,
|
|
||||||
newParameters,
|
|
||||||
newReceiver,
|
|
||||||
originalDescriptor.getTypeParameters(),
|
|
||||||
replacementMap,
|
|
||||||
controlFlow,
|
|
||||||
returnType != null ? returnType : originalDescriptor.getReturnType()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-18
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||||
|
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.util.containers.ContainerUtil
|
import com.intellij.util.containers.ContainerUtil
|
||||||
@@ -97,7 +98,7 @@ class RenameReplacement(override val parameter: Parameter): ParameterReplacement
|
|||||||
class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement {
|
class WrapInWithReplacement(override val parameter: Parameter): ParameterReplacement {
|
||||||
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
|
override fun invoke(descriptor: ExtractableCodeDescriptor, e: KtElement): KtElement {
|
||||||
val call = e.parents.firstIsInstance<KtCallExpression>().getQualifiedExpressionForSelectorOrThis()
|
val call = e.parents.firstIsInstance<KtCallExpression>().getQualifiedExpressionForSelectorOrThis()
|
||||||
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call.text)
|
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("with($0) { $1 }", parameter.name, call)
|
||||||
val replace = call.replace(replacingExpression)
|
val replace = call.replace(replacingExpression)
|
||||||
return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first()
|
return (replace as KtCallExpression).functionLiteralArguments.first().getFunctionLiteral().bodyExpression!!.statements.first()
|
||||||
}
|
}
|
||||||
@@ -112,7 +113,7 @@ class AddPrefixReplacement(override val parameter: Parameter): ParameterReplacem
|
|||||||
if (descriptor.receiverParameter == parameter) return e
|
if (descriptor.receiverParameter == parameter) return e
|
||||||
|
|
||||||
val selector = (e.parent as? KtCallExpression) ?: e
|
val selector = (e.parent as? KtCallExpression) ?: e
|
||||||
val replacingExpression = KtPsiFactory(e).createExpression("${parameter.nameForRef}.${selector.text}")
|
val replacingExpression = KtPsiFactory(e).createExpressionByPattern("${parameter.nameForRef}.$0", selector)
|
||||||
val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!!
|
val newExpr = (selector.replace(replacingExpression) as KtQualifiedExpression).selectorExpression!!
|
||||||
return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr
|
return (newExpr as? KtCallExpression)?.calleeExpression ?: newExpr
|
||||||
}
|
}
|
||||||
@@ -170,13 +171,13 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
|
|||||||
|
|
||||||
abstract val returnType: KotlinType
|
abstract val returnType: KotlinType
|
||||||
|
|
||||||
protected abstract fun getBoxingExpressionText(arguments: List<String>): String?
|
protected abstract fun getBoxingExpressionPattern(arguments: List<KtExpression>): String?
|
||||||
|
|
||||||
abstract val boxingRequired: Boolean
|
abstract val boxingRequired: Boolean
|
||||||
|
|
||||||
fun getReturnExpression(arguments: List<String>, psiFactory: KtPsiFactory): KtReturnExpression? {
|
fun getReturnExpression(arguments: List<KtExpression>, psiFactory: KtPsiFactory): KtReturnExpression? {
|
||||||
val expressionText = getBoxingExpressionText(arguments) ?: return null
|
val expressionPattern = getBoxingExpressionPattern(arguments) ?: return null
|
||||||
return psiFactory.createExpression("return $expressionText") as KtReturnExpression
|
return psiFactory.createExpressionByPattern("return $expressionPattern", *arguments.toTypedArray()) as KtReturnExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression?
|
protected abstract fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression?
|
||||||
@@ -230,13 +231,13 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
|
|||||||
|
|
||||||
override val boxingRequired: Boolean = outputValues.size() > 1
|
override val boxingRequired: Boolean = outputValues.size() > 1
|
||||||
|
|
||||||
override fun getBoxingExpressionText(arguments: List<String>): String? {
|
override fun getBoxingExpressionPattern(arguments: List<KtExpression>): String? {
|
||||||
return when (arguments.size()) {
|
return when (arguments.size) {
|
||||||
0 -> null
|
0 -> null
|
||||||
1 -> arguments.first()
|
1 -> "$0"
|
||||||
else -> {
|
else -> {
|
||||||
val constructorName = DescriptorUtils.getFqName(returnType.getConstructor().getDeclarationDescriptor()!!).asString()
|
val constructorName = DescriptorUtils.getFqName(returnType.constructor.declarationDescriptor!!).asString()
|
||||||
return arguments.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")")
|
return arguments.indices.joinToString(prefix = "$constructorName(", separator = ", ", postfix = ")") { "\$$it" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,9 +271,9 @@ abstract class OutputValueBoxer(val outputValues: List<OutputValue>) {
|
|||||||
|
|
||||||
override val boxingRequired: Boolean = outputValues.size() > 0
|
override val boxingRequired: Boolean = outputValues.size() > 0
|
||||||
|
|
||||||
override fun getBoxingExpressionText(arguments: List<String>): String? {
|
override fun getBoxingExpressionPattern(arguments: List<KtExpression>): String? {
|
||||||
if (arguments.isEmpty()) return null
|
if (arguments.isEmpty()) return null
|
||||||
return arguments.joinToString(prefix = "kotlin.listOf(", separator = ", ", postfix = ")")
|
return arguments.indices.joinToString(prefix = "kotlin.listOf(", separator = ", ", postfix = ")") { "\$$it" }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
|
override fun extractExpressionByIndex(boxedExpression: KtExpression, index: Int): KtExpression? {
|
||||||
@@ -324,7 +325,14 @@ val ControlFlow.possibleReturnTypes: List<KotlinType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ControlFlow.toDefault(): ControlFlow =
|
fun ControlFlow.toDefault(): ControlFlow =
|
||||||
copy(outputValues = outputValues.filterNot { it is OutputValue.Jump || it is OutputValue.ExpressionValue })
|
copy(outputValues = outputValues.filterNot { it is Jump || it is ExpressionValue })
|
||||||
|
|
||||||
|
fun ControlFlow.copy(oldToNewParameters: Map<Parameter, Parameter>): ControlFlow {
|
||||||
|
val newOutputValues = outputValues.map {
|
||||||
|
if (it is ParameterUpdate) ParameterUpdate(oldToNewParameters[it.parameter]!!, it.originalExpressions) else it
|
||||||
|
}
|
||||||
|
return copy(outputValues = newOutputValues)
|
||||||
|
}
|
||||||
|
|
||||||
data class ExtractableCodeDescriptor(
|
data class ExtractableCodeDescriptor(
|
||||||
val extractionData: ExtractionData,
|
val extractionData: ExtractionData,
|
||||||
@@ -334,7 +342,7 @@ data class ExtractableCodeDescriptor(
|
|||||||
val parameters: List<Parameter>,
|
val parameters: List<Parameter>,
|
||||||
val receiverParameter: Parameter?,
|
val receiverParameter: Parameter?,
|
||||||
val typeParameters: List<TypeParameter>,
|
val typeParameters: List<TypeParameter>,
|
||||||
val replacementMap: MultiMap<Int, Replacement>,
|
val replacementMap: MultiMap<KtSimpleNameExpression, Replacement>,
|
||||||
val controlFlow: ControlFlow,
|
val controlFlow: ControlFlow,
|
||||||
val returnType: KotlinType
|
val returnType: KotlinType
|
||||||
) {
|
) {
|
||||||
@@ -342,6 +350,39 @@ data class ExtractableCodeDescriptor(
|
|||||||
val duplicates: List<DuplicateInfo> by lazy { findDuplicates() }
|
val duplicates: List<DuplicateInfo> by lazy { findDuplicates() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ExtractableCodeDescriptor.copy(
|
||||||
|
newName: String,
|
||||||
|
newVisibility: String,
|
||||||
|
oldToNewParameters: Map<Parameter, Parameter>,
|
||||||
|
newReceiver: Parameter?,
|
||||||
|
returnType: KotlinType?
|
||||||
|
): ExtractableCodeDescriptor {
|
||||||
|
val newReplacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
|
||||||
|
for ((ref, replacements) in replacementMap.entrySet()) {
|
||||||
|
val newReplacements = replacements.map {
|
||||||
|
if (it is ParameterReplacement) {
|
||||||
|
val parameter = it.parameter
|
||||||
|
val newParameter = oldToNewParameters[parameter] ?: return@map it
|
||||||
|
it.copy(newParameter)
|
||||||
|
}
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
newReplacementMap.putValues(ref, newReplacements)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtractableCodeDescriptor(
|
||||||
|
extractionData,
|
||||||
|
originalContext,
|
||||||
|
listOf(newName),
|
||||||
|
newVisibility,
|
||||||
|
parameters.map { oldToNewParameters[it]!! },
|
||||||
|
newReceiver,
|
||||||
|
typeParameters,
|
||||||
|
newReplacementMap,
|
||||||
|
controlFlow.copy(oldToNewParameters),
|
||||||
|
returnType ?: this.returnType)
|
||||||
|
}
|
||||||
|
|
||||||
enum class ExtractionTarget(val targetName: String) {
|
enum class ExtractionTarget(val targetName: String) {
|
||||||
FUNCTION("function") {
|
FUNCTION("function") {
|
||||||
override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
|
override fun isAvailable(descriptor: ExtractableCodeDescriptor) = true
|
||||||
@@ -431,9 +472,10 @@ data class ExtractionGeneratorConfiguration(
|
|||||||
data class ExtractionResult(
|
data class ExtractionResult(
|
||||||
val config: ExtractionGeneratorConfiguration,
|
val config: ExtractionGeneratorConfiguration,
|
||||||
val declaration: KtNamedDeclaration,
|
val declaration: KtNamedDeclaration,
|
||||||
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>,
|
val duplicateReplacers: Map<KotlinPsiRange, () -> Unit>
|
||||||
val nameByOffset: MultiMap<Int, KtElement>
|
) : Disposable {
|
||||||
)
|
override fun dispose() = unmarkReferencesInside(declaration)
|
||||||
|
}
|
||||||
|
|
||||||
class AnalysisResult (
|
class AnalysisResult (
|
||||||
val descriptor: ExtractableCodeDescriptor?,
|
val descriptor: ExtractableCodeDescriptor?,
|
||||||
|
|||||||
+78
-83
@@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||||
|
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiNameIdentifierOwner
|
import com.intellij.psi.PsiNameIdentifierOwner
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
@@ -72,20 +74,21 @@ data class ResolveResult(
|
|||||||
|
|
||||||
data class ResolvedReferenceInfo(
|
data class ResolvedReferenceInfo(
|
||||||
val refExpr: KtSimpleNameExpression,
|
val refExpr: KtSimpleNameExpression,
|
||||||
val offsetInBody: Int,
|
|
||||||
val resolveResult: ResolveResult,
|
val resolveResult: ResolveResult,
|
||||||
val smartCast: KotlinType?,
|
val smartCast: KotlinType?,
|
||||||
val possibleTypes: Set<KotlinType>
|
val possibleTypes: Set<KotlinType>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
internal var KtSimpleNameExpression.resolveResult: ResolveResult? by CopyableUserDataProperty(Key.create("RESOLVE_RESULT"))
|
||||||
|
|
||||||
data class ExtractionData(
|
data class ExtractionData(
|
||||||
val originalFile: KtFile,
|
val originalFile: KtFile,
|
||||||
val originalRange: KotlinPsiRange,
|
val originalRange: KotlinPsiRange,
|
||||||
val targetSibling: PsiElement,
|
val targetSibling: PsiElement,
|
||||||
val duplicateContainer: PsiElement? = null,
|
val duplicateContainer: PsiElement? = null,
|
||||||
val options: ExtractionOptions = ExtractionOptions.DEFAULT
|
val options: ExtractionOptions = ExtractionOptions.DEFAULT
|
||||||
) {
|
) : Disposable {
|
||||||
val project: Project = originalFile.getProject()
|
val project: Project = originalFile.project
|
||||||
val originalElements: List<PsiElement> = originalRange.elements
|
val originalElements: List<PsiElement> = originalRange.elements
|
||||||
val physicalElements = originalElements.map { it.substringContextOrThis }
|
val physicalElements = originalElements.map { it.substringContextOrThis }
|
||||||
|
|
||||||
@@ -108,9 +111,6 @@ data class ExtractionData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val originalStartOffset: Int?
|
|
||||||
get() = originalElements.firstOrNull()?.let { e -> e.getTextRange()!!.getStartOffset() }
|
|
||||||
|
|
||||||
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
|
val commonParent = PsiTreeUtil.findCommonParent(physicalElements) as KtElement
|
||||||
|
|
||||||
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
|
val bindingContext: BindingContext? by lazy { commonParent.getContextForContainingDeclarationBody() }
|
||||||
@@ -118,65 +118,63 @@ data class ExtractionData(
|
|||||||
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
|
private val itFakeDeclaration by lazy { KtPsiFactory(originalFile).createParameter("it: Any?") }
|
||||||
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
|
private val synthesizedInvokeDeclaration by lazy { KtPsiFactory(originalFile).createFunction("fun invoke() {}") }
|
||||||
|
|
||||||
val refOffsetToDeclaration by lazy {
|
init {
|
||||||
fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean {
|
markReferences()
|
||||||
if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false
|
}
|
||||||
val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.getContainingDeclaration()) as? KtFunctionLiteral
|
|
||||||
return function == null || !function.isInsideOf(physicalElements)
|
private fun isExtractableIt(descriptor: DeclarationDescriptor, context: BindingContext): Boolean {
|
||||||
|
if (!(descriptor is ValueParameterDescriptor && (context[BindingContext.AUTO_CREATED_IT, descriptor] ?: false))) return false
|
||||||
|
val function = DescriptorToSourceUtils.descriptorToDeclaration(descriptor.containingDeclaration) as? KtFunctionLiteral
|
||||||
|
return function == null || !function.isInsideOf(physicalElements)
|
||||||
|
}
|
||||||
|
|
||||||
|
private tailrec fun getDeclaration(descriptor: DeclarationDescriptor, context: BindingContext): PsiNameIdentifierOwner? {
|
||||||
|
(DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? PsiNameIdentifierOwner)?.let { return it }
|
||||||
|
|
||||||
|
return when {
|
||||||
|
isExtractableIt(descriptor, context) -> itFakeDeclaration
|
||||||
|
isSynthesizedInvoke(descriptor) -> synthesizedInvokeDeclaration
|
||||||
|
descriptor is SyntheticJavaPropertyDescriptor -> getDeclaration(descriptor.getMethod, context)
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tailrec fun getDeclaration(descriptor: DeclarationDescriptor, context: BindingContext): PsiNameIdentifierOwner? {
|
private fun markReferences() {
|
||||||
(DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? PsiNameIdentifierOwner)?.let { return it }
|
val context = bindingContext ?: return
|
||||||
|
val visitor = object : KtTreeVisitorVoid() {
|
||||||
return when {
|
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
||||||
isExtractableIt(descriptor, context) -> itFakeDeclaration
|
if (context[BindingContext.SMARTCAST, expression] != null) {
|
||||||
isSynthesizedInvoke(descriptor) -> synthesizedInvokeDeclaration
|
expression.selectorExpression?.accept(this)
|
||||||
descriptor is SyntheticJavaPropertyDescriptor -> getDeclaration(descriptor.getMethod, context)
|
return
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val originalStartOffset = originalStartOffset
|
|
||||||
val context = bindingContext
|
|
||||||
|
|
||||||
if (originalStartOffset != null && context != null) {
|
|
||||||
val resultMap = HashMap<Int, ResolveResult>()
|
|
||||||
|
|
||||||
val visitor = object: KtTreeVisitorVoid() {
|
|
||||||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
|
||||||
if (context[BindingContext.SMARTCAST, expression] != null) {
|
|
||||||
expression.getSelectorExpression()?.accept(this)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
super.visitQualifiedExpression(expression)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) {
|
super.visitQualifiedExpression(expression)
|
||||||
if (ref.getParent() is KtValueArgumentName) return
|
}
|
||||||
|
|
||||||
val physicalRef = substringInfo?.let {
|
override fun visitSimpleNameExpression(ref: KtSimpleNameExpression) {
|
||||||
// If substring contains some references it must be extracted as a string template
|
if (ref.parent is KtValueArgumentName) return
|
||||||
val physicalExpression = expressions.single() as KtStringTemplateExpression
|
|
||||||
val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset
|
|
||||||
val offsetInExtracted = ref.startOffset - extractedContentOffset
|
|
||||||
val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted
|
|
||||||
it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType<KtSimpleNameExpression>()
|
|
||||||
} ?: ref
|
|
||||||
|
|
||||||
val resolvedCall = physicalRef.getResolvedCall(context)
|
val physicalRef = substringInfo?.let {
|
||||||
val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return
|
// If substring contains some references it must be extracted as a string template
|
||||||
val declaration = getDeclaration(descriptor, context) ?: return
|
val physicalExpression = expressions.single() as KtStringTemplateExpression
|
||||||
|
val extractedContentOffset = physicalExpression.getContentRange().startOffset + physicalExpression.startOffset
|
||||||
|
val offsetInExtracted = ref.startOffset - extractedContentOffset
|
||||||
|
val offsetInTemplate = it.relativeContentRange.startOffset + offsetInExtracted
|
||||||
|
it.template.findElementAt(offsetInTemplate)!!.getStrictParentOfType<KtSimpleNameExpression>()
|
||||||
|
} ?: ref
|
||||||
|
|
||||||
val offset = ref.getTextRange()!!.getStartOffset() - originalStartOffset
|
val resolvedCall = physicalRef.getResolvedCall(context)
|
||||||
resultMap[offset] = ResolveResult(physicalRef, declaration, descriptor, resolvedCall)
|
val descriptor = context[BindingContext.REFERENCE_TARGET, physicalRef] ?: return
|
||||||
|
val declaration = getDeclaration(descriptor, context) ?: return
|
||||||
|
|
||||||
|
val resolveResult = ResolveResult(physicalRef, declaration, descriptor, resolvedCall)
|
||||||
|
physicalRef.resolveResult = resolveResult
|
||||||
|
if (ref != physicalRef) {
|
||||||
|
ref.resolveResult = resolveResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expressions.forEach { it.accept(visitor) }
|
|
||||||
|
|
||||||
resultMap
|
|
||||||
}
|
}
|
||||||
else Collections.emptyMap<Int, ResolveResult>()
|
expressions.forEach { it.accept(visitor) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<KotlinType> {
|
fun getPossibleTypes(expression: KtExpression, resolvedCall: ResolvedCall<*>?, context: BindingContext): Set<KotlinType> {
|
||||||
@@ -195,75 +193,72 @@ data class ExtractionData(
|
|||||||
fun getBrokenReferencesInfo(body: KtBlockExpression): List<ResolvedReferenceInfo> {
|
fun getBrokenReferencesInfo(body: KtBlockExpression): List<ResolvedReferenceInfo> {
|
||||||
val originalContext = bindingContext ?: return listOf()
|
val originalContext = bindingContext ?: return listOf()
|
||||||
|
|
||||||
val startOffset = body.getBlockContentOffset()
|
val newReferences = body.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
|
||||||
|
|
||||||
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
|
val referencesInfo = ArrayList<ResolvedReferenceInfo>()
|
||||||
val refToContextMap = KotlinFileReferencesResolver.resolve(body)
|
val refToContextMap = KotlinFileReferencesResolver.resolve(body)
|
||||||
for ((ref, context) in refToContextMap) {
|
for (newRef in newReferences) {
|
||||||
if (ref !is KtSimpleNameExpression) continue
|
val context = refToContextMap[newRef] ?: continue
|
||||||
|
val originalResolveResult = newRef.resolveResult ?: continue
|
||||||
val offset = ref.getTextRange()!!.getStartOffset() - startOffset
|
|
||||||
val originalResolveResult = refOffsetToDeclaration[offset] ?: continue
|
|
||||||
|
|
||||||
val smartCast: KotlinType?
|
val smartCast: KotlinType?
|
||||||
val possibleTypes: Set<KotlinType>
|
val possibleTypes: Set<KotlinType>
|
||||||
|
|
||||||
// Qualified property reference: a.b
|
// Qualified property reference: a.b
|
||||||
val qualifiedExpression = ref.getQualifiedExpressionForSelector()
|
val qualifiedExpression = newRef.getQualifiedExpressionForSelector()
|
||||||
if (qualifiedExpression != null) {
|
if (qualifiedExpression != null) {
|
||||||
val smartCastTarget = originalResolveResult.originalRefExpr.getParent() as KtExpression
|
val smartCastTarget = originalResolveResult.originalRefExpr.parent as KtExpression
|
||||||
smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]
|
smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]
|
||||||
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
|
possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext)
|
||||||
val receiverDescriptor =
|
val receiverDescriptor =
|
||||||
(originalResolveResult.resolvedCall?.getDispatchReceiver() as? ImplicitReceiver)?.declarationDescriptor
|
(originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor
|
||||||
if (smartCast == null
|
if (smartCast == null
|
||||||
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
|
&& !DescriptorUtils.isCompanionObject(receiverDescriptor)
|
||||||
&& qualifiedExpression.getReceiverExpression() !is KtSuperExpression) continue
|
&& qualifiedExpression.receiverExpression !is KtSuperExpression) continue
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]
|
smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]
|
||||||
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
|
possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parent = ref.getParent()
|
val parent = newRef.parent
|
||||||
|
|
||||||
// Skip P in type references like 'P.Q'
|
// Skip P in type references like 'P.Q'
|
||||||
if (parent is KtUserType && (parent.getParent() as? KtUserType)?.getQualifier() == parent) continue
|
if (parent is KtUserType && (parent.parent as? KtUserType)?.qualifier == parent) continue
|
||||||
|
|
||||||
val descriptor = context[BindingContext.REFERENCE_TARGET, ref]
|
val descriptor = context[BindingContext.REFERENCE_TARGET, newRef]
|
||||||
val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor)
|
val isBadRef = !(compareDescriptors(project, originalResolveResult.descriptor, descriptor)
|
||||||
&& originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(ref))
|
&& originalContext.diagnostics.forElement(originalResolveResult.originalRefExpr) == context.diagnostics.forElement(newRef))
|
||||||
|| smartCast != null
|
|| smartCast != null
|
||||||
if (isBadRef && !originalResolveResult.declaration.isInsideOf(physicalElements)) {
|
if (isBadRef && !originalResolveResult.declaration.isInsideOf(physicalElements)) {
|
||||||
val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall
|
val originalResolvedCall = originalResolveResult.resolvedCall as? VariableAsFunctionResolvedCall
|
||||||
val originalFunctionCall = originalResolvedCall?.functionCall
|
val originalFunctionCall = originalResolvedCall?.functionCall
|
||||||
val originalVariableCall = originalResolvedCall?.variableCall
|
val originalVariableCall = originalResolvedCall?.variableCall
|
||||||
val invokeDescriptor = originalFunctionCall?.getResultingDescriptor()
|
val invokeDescriptor = originalFunctionCall?.resultingDescriptor
|
||||||
if (invokeDescriptor != null && isSynthesizedInvoke(invokeDescriptor) && invokeDescriptor.isExtension) {
|
if (invokeDescriptor != null && isSynthesizedInvoke(invokeDescriptor) && invokeDescriptor.isExtension) {
|
||||||
val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!,
|
val variableResolveResult = originalResolveResult.copy(resolvedCall = originalVariableCall!!,
|
||||||
descriptor = originalVariableCall.getResultingDescriptor())
|
descriptor = originalVariableCall.resultingDescriptor)
|
||||||
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!,
|
val functionResolveResult = originalResolveResult.copy(resolvedCall = originalFunctionCall!!,
|
||||||
descriptor = originalFunctionCall.getResultingDescriptor(),
|
descriptor = originalFunctionCall.resultingDescriptor,
|
||||||
declaration = synthesizedInvokeDeclaration)
|
declaration = synthesizedInvokeDeclaration)
|
||||||
referencesInfo.add(ResolvedReferenceInfo(ref, offset, variableResolveResult, smartCast, possibleTypes))
|
referencesInfo.add(ResolvedReferenceInfo(newRef, variableResolveResult, smartCast, possibleTypes))
|
||||||
referencesInfo.add(ResolvedReferenceInfo(ref, offset, functionResolveResult, smartCast, possibleTypes))
|
referencesInfo.add(ResolvedReferenceInfo(newRef, functionResolveResult, smartCast, possibleTypes))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
referencesInfo.add(ResolvedReferenceInfo(ref, offset, originalResolveResult, smartCast, possibleTypes))
|
referencesInfo.add(ResolvedReferenceInfo(newRef, originalResolveResult, smartCast, possibleTypes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return referencesInfo
|
return referencesInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
expressions.forEach { unmarkReferencesInside(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack:
|
fun unmarkReferencesInside(root: PsiElement) {
|
||||||
// we can't get first element offset through getStatement()/getChildren() since they skip comments and whitespaces
|
if (!root.isValid) return
|
||||||
// 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)
|
root.forEachDescendantOfType<KtSimpleNameExpression> { it.resolveResult = null }
|
||||||
internal fun KtExpression.getBlockContentOffset(): Int {
|
|
||||||
(this as? KtBlockExpression)?.getLBrace()?.let {
|
|
||||||
return it.getTextRange()!!.getStartOffset() + 2
|
|
||||||
}
|
|
||||||
return getTextRange()!!.getStartOffset()
|
|
||||||
}
|
}
|
||||||
+20
-15
@@ -16,21 +16,18 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||||
|
|
||||||
import com.intellij.psi.*
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import org.jetbrains.kotlin.psi.*
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.*
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.*
|
import com.intellij.openapi.ui.MessageType
|
||||||
import com.intellij.openapi.application.*
|
import com.intellij.openapi.ui.popup.Balloon
|
||||||
import com.intellij.refactoring.*
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||||
import org.jetbrains.kotlin.idea.util.application.*
|
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||||
import org.jetbrains.kotlin.idea.refactoring.*
|
import com.intellij.ui.awt.RelativePoint
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.*
|
|
||||||
import com.intellij.ui.awt.*
|
|
||||||
import com.intellij.openapi.ui.popup.*
|
|
||||||
import com.intellij.openapi.ui.*
|
|
||||||
import javax.swing.event.*
|
|
||||||
import com.intellij.openapi.project.*
|
|
||||||
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
|
import org.jetbrains.kotlin.idea.core.refactoring.checkConflictsInteractively
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import javax.swing.event.HyperlinkEvent
|
||||||
|
|
||||||
public abstract class ExtractionEngineHelper(val operationName: String) {
|
public abstract class ExtractionEngineHelper(val operationName: String) {
|
||||||
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
|
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
|
||||||
@@ -66,7 +63,15 @@ public class ExtractionEngine(
|
|||||||
fun validateAndRefactor() {
|
fun validateAndRefactor() {
|
||||||
val validationResult = analysisResult.descriptor!!.validate()
|
val validationResult = analysisResult.descriptor!!.validate()
|
||||||
project.checkConflictsInteractively(validationResult.conflicts) {
|
project.checkConflictsInteractively(validationResult.conflicts) {
|
||||||
helper.configureAndRun(project, editor, validationResult, onFinish)
|
helper.configureAndRun(project, editor, validationResult) {
|
||||||
|
try {
|
||||||
|
onFinish(it)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
it.dispose()
|
||||||
|
extractionData.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-55
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
|||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiNamedElement
|
import com.intellij.psi.PsiNamedElement
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
@@ -55,10 +56,7 @@ import org.jetbrains.kotlin.idea.util.approximateWithResolvableType
|
|||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
import org.jetbrains.kotlin.idea.util.isResolvableInScope
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
|
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||||
@@ -374,39 +372,35 @@ private fun ExtractionData.analyzeControlFlow(
|
|||||||
return controlFlow to null
|
return controlFlow to null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExtractionData.createTemporaryDeclaration(functionText: String): KtNamedDeclaration {
|
fun ExtractionData.createTemporaryDeclaration(pattern: String): KtNamedDeclaration {
|
||||||
val textRange = targetSibling.textRange!!
|
val targetSiblingMarker = Any()
|
||||||
|
PsiTreeUtil.mark(targetSibling, targetSiblingMarker)
|
||||||
|
val tmpFile = originalFile.createTempCopy("")
|
||||||
|
tmpFile.deleteChildRange(tmpFile.firstChild, tmpFile.lastChild)
|
||||||
|
tmpFile.addRange(originalFile.firstChild, originalFile.lastChild)
|
||||||
|
val newTargetSibling = PsiTreeUtil.releaseMark(tmpFile, targetSiblingMarker)!!
|
||||||
|
val newTargetParent = newTargetSibling.parent
|
||||||
|
|
||||||
val insertText: String
|
val declaration = KtPsiFactory(originalFile).createDeclarationByPattern<KtNamedDeclaration>(
|
||||||
val insertPosition: Int
|
pattern,
|
||||||
val lookupPosition: Int
|
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
|
||||||
if (insertBefore) {
|
)
|
||||||
insertPosition = textRange.startOffset
|
return if (insertBefore) {
|
||||||
lookupPosition = insertPosition
|
newTargetParent.addBefore(declaration, newTargetSibling) as KtNamedDeclaration
|
||||||
insertText = functionText
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
insertPosition = textRange.endOffset
|
newTargetParent.addAfter(declaration, newTargetSibling) as KtNamedDeclaration
|
||||||
lookupPosition = insertPosition + 1
|
|
||||||
insertText = "\n$functionText"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val tmpFile = originalFile.createTempCopy { text ->
|
|
||||||
StringBuilder(text).insert(insertPosition, insertText).toString()
|
|
||||||
}
|
|
||||||
return tmpFile.findElementAt(lookupPosition)?.getNonStrictParentOfType<KtNamedDeclaration>()!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression =
|
internal fun ExtractionData.createTemporaryCodeBlock(): KtBlockExpression =
|
||||||
(createTemporaryDeclaration("fun() {\n$codeFragmentText\n}\n") as KtNamedFunction).bodyExpression as KtBlockExpression
|
(createTemporaryDeclaration("fun() {\n$0\n}\n") as KtNamedFunction).bodyExpression as KtBlockExpression
|
||||||
|
|
||||||
private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List<KotlinType> {
|
private fun KotlinType.collectReferencedTypes(processTypeArguments: Boolean): List<KotlinType> {
|
||||||
if (!processTypeArguments) return Collections.singletonList(this)
|
if (!processTypeArguments) return Collections.singletonList(this)
|
||||||
return DFS.dfsFromNode(
|
return DFS.dfsFromNode(
|
||||||
this,
|
this,
|
||||||
object: Neighbors<KotlinType> {
|
Neighbors<KotlinType> { current -> current.arguments.map { it.type } },
|
||||||
override fun getNeighbors(current: KotlinType): Iterable<KotlinType> = current.arguments.map { it.type }
|
|
||||||
},
|
|
||||||
VisitedWithSet(),
|
VisitedWithSet(),
|
||||||
object: CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) {
|
object: CollectingNodeHandler<KotlinType, KotlinType, ArrayList<KotlinType>>(ArrayList()) {
|
||||||
override fun afterChildren(current: KotlinType) {
|
override fun afterChildren(current: KotlinType) {
|
||||||
@@ -568,17 +562,6 @@ private class DelegatingParameter(
|
|||||||
override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType
|
override fun getParameterType(allowSpecialClassNames: Boolean) = parameterType
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ParametersInfo {
|
|
||||||
var errorMessage: ErrorMessage? = null
|
|
||||||
val replacementMap: MultiMap<Int, Replacement> = MultiMap.create()
|
|
||||||
val originalRefToParameter: MultiMap<KtSimpleNameExpression, MutableParameter> = MultiMap.create()
|
|
||||||
val parameters: MutableSet<MutableParameter> = HashSet()
|
|
||||||
val typeParameters: MutableSet<TypeParameter> = HashSet()
|
|
||||||
val nonDenotableTypes: MutableSet<KotlinType> = HashSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun ExtractionData.checkDeclarationsMovingOutOfScope(
|
private fun ExtractionData.checkDeclarationsMovingOutOfScope(
|
||||||
enclosingDeclaration: KtDeclaration,
|
enclosingDeclaration: KtDeclaration,
|
||||||
controlFlow: ControlFlow,
|
controlFlow: ControlFlow,
|
||||||
@@ -773,7 +756,8 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
|
|||||||
val body = result.declaration.getGeneratedBody()
|
val body = result.declaration.getGeneratedBody()
|
||||||
val bindingContext = body.analyzeFully()
|
val bindingContext = body.analyzeFully()
|
||||||
|
|
||||||
fun processReference(resolveResult: ResolveResult, currentRefExpr: KtReferenceExpression) {
|
fun processReference(currentRefExpr: KtSimpleNameExpression) {
|
||||||
|
val resolveResult = currentRefExpr.resolveResult ?: return
|
||||||
if (currentRefExpr.parent is KtThisExpression) return
|
if (currentRefExpr.parent is KtThisExpression) return
|
||||||
|
|
||||||
val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr)
|
val diagnostics = bindingContext.diagnostics.forElement(currentRefExpr)
|
||||||
@@ -808,18 +792,6 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateBody() {
|
|
||||||
for ((originalOffset, resolveResult) in extractionData.refOffsetToDeclaration) {
|
|
||||||
if (resolveResult.declaration.isInsideOf(extractionData.physicalElements)) continue
|
|
||||||
|
|
||||||
val currentRefExprs = result.nameByOffset[originalOffset].mapNotNull {
|
|
||||||
(it as? KtThisExpression)?.instanceReference ?: it as? KtSimpleNameExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRefExprs.forEach { processReference(resolveResult, it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.declaration.accept(
|
result.declaration.accept(
|
||||||
object : KtTreeVisitorVoid() {
|
object : KtTreeVisitorVoid() {
|
||||||
override fun visitUserType(userType: KtUserType) {
|
override fun visitUserType(userType: KtUserType) {
|
||||||
@@ -831,12 +803,8 @@ fun ExtractableCodeDescriptor.validate(): ExtractableCodeDescriptorWithConflicts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitKtElement(element: KtElement) {
|
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||||
if (element == body) {
|
processReference(expression)
|
||||||
validateBody()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
super.visitKtElement(element)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+135
-170
@@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||||
import com.intellij.psi.search.LocalSearchScope
|
import com.intellij.psi.search.LocalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||||
import com.intellij.util.containers.MultiMap
|
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.idea.core.*
|
import org.jetbrains.kotlin.idea.core.*
|
||||||
@@ -56,8 +56,65 @@ import org.jetbrains.kotlin.types.isFlexible
|
|||||||
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
import org.jetbrains.kotlin.types.typeUtil.builtIns
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun ExtractionGeneratorConfiguration.getDeclarationText(
|
private fun buildSignature(config: ExtractionGeneratorConfiguration, renderer: DescriptorRenderer): CallableBuilder {
|
||||||
withBody: Boolean = true,
|
val extractionTarget = config.generatorOptions.target
|
||||||
|
if (!extractionTarget.isAvailable(config.descriptor)) {
|
||||||
|
val message = "Can't generate ${extractionTarget.targetName}: ${config.descriptor.extractionData.codeFragmentText}"
|
||||||
|
throw BaseRefactoringProcessor.ConflictsInTestsException(listOf(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
val builderTarget = when (extractionTarget) {
|
||||||
|
ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
|
||||||
|
else -> CallableBuilder.Target.READ_ONLY_PROPERTY
|
||||||
|
}
|
||||||
|
return CallableBuilder(builderTarget).apply {
|
||||||
|
modifier(config.descriptor.visibility)
|
||||||
|
|
||||||
|
typeParams(
|
||||||
|
config.descriptor.typeParameters.map {
|
||||||
|
val typeParameter = it.originalDeclaration
|
||||||
|
val bound = typeParameter.extendsBound
|
||||||
|
typeParameter.name + (bound?.let { " : " + it.text } ?: "")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
fun KotlinType.typeAsString(): String {
|
||||||
|
return if (config.descriptor.extractionData.options.allowSpecialClassNames && isSpecial()) {
|
||||||
|
DEBUG_TYPE_REFERENCE_STRING
|
||||||
|
} else {
|
||||||
|
renderer.renderType(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.descriptor.receiverParameter?.let {
|
||||||
|
val receiverType = it.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames)
|
||||||
|
val receiverTypeAsString = receiverType.typeAsString()
|
||||||
|
val isFunctionType = KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(receiverType)
|
||||||
|
receiver(if (isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString)
|
||||||
|
}
|
||||||
|
|
||||||
|
name(config.generatorOptions.dummyName ?: config.descriptor.name)
|
||||||
|
|
||||||
|
config.descriptor.parameters.forEach { parameter ->
|
||||||
|
param(parameter.name,
|
||||||
|
parameter.getParameterType(config.descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
|
||||||
|
}
|
||||||
|
|
||||||
|
with(config.descriptor.returnType) {
|
||||||
|
if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
|
||||||
|
noReturnType()
|
||||||
|
} else {
|
||||||
|
returnType(typeAsString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeConstraints(config.descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ExtractionGeneratorConfiguration.getSignaturePreview(renderer: DescriptorRenderer) = buildSignature(this, renderer).asString()
|
||||||
|
|
||||||
|
fun ExtractionGeneratorConfiguration.getDeclarationPattern(
|
||||||
descriptorRenderer: DescriptorRenderer = if (generatorOptions.flexibleTypesAllowed)
|
descriptorRenderer: DescriptorRenderer = if (generatorOptions.flexibleTypesAllowed)
|
||||||
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
|
DescriptorRenderer.FLEXIBLE_TYPES_FOR_CODE
|
||||||
else IdeDescriptorRenderers.SOURCE_CODE
|
else IdeDescriptorRenderers.SOURCE_CODE
|
||||||
@@ -67,58 +124,20 @@ fun ExtractionGeneratorConfiguration.getDeclarationText(
|
|||||||
throw BaseRefactoringProcessor.ConflictsInTestsException(listOf("Can't generate ${extractionTarget.targetName}: ${descriptor.extractionData.codeFragmentText}"))
|
throw BaseRefactoringProcessor.ConflictsInTestsException(listOf("Can't generate ${extractionTarget.targetName}: ${descriptor.extractionData.codeFragmentText}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val builderTarget = when (extractionTarget) {
|
return buildSignature(this, descriptorRenderer).let { builder ->
|
||||||
ExtractionTarget.FUNCTION, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> CallableBuilder.Target.FUNCTION
|
builder.transform {
|
||||||
else -> CallableBuilder.Target.READ_ONLY_PROPERTY
|
for (i in sequence(indexOf('$')) { indexOf('$', it + 2) }) {
|
||||||
}
|
if (i < 0) break
|
||||||
return CallableBuilder(builderTarget).let { builder ->
|
insert(i + 1, '$')
|
||||||
builder.modifier(descriptor.visibility)
|
|
||||||
|
|
||||||
builder.typeParams(
|
|
||||||
descriptor.typeParameters.map {
|
|
||||||
val typeParameter = it.originalDeclaration
|
|
||||||
val bound = typeParameter.extendsBound
|
|
||||||
typeParameter.name + (bound?.let { " : " + it.text } ?: "")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun KotlinType.typeAsString(): String {
|
|
||||||
return if (descriptor.extractionData.options.allowSpecialClassNames && isSpecial()) DEBUG_TYPE_REFERENCE_STRING else descriptorRenderer.renderType(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptor.receiverParameter?.let {
|
|
||||||
val receiverType = it.getParameterType(descriptor.extractionData.options.allowSpecialClassNames)
|
|
||||||
val receiverTypeAsString = receiverType.typeAsString()
|
|
||||||
val isFunctionType = KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(receiverType)
|
|
||||||
builder.receiver(if (isFunctionType) "($receiverTypeAsString)" else receiverTypeAsString)
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.name(generatorOptions.dummyName ?: descriptor.name)
|
|
||||||
|
|
||||||
descriptor.parameters.forEach { parameter ->
|
|
||||||
builder.param(parameter.name,
|
|
||||||
parameter.getParameterType(descriptor.extractionData.options.allowSpecialClassNames).typeAsString())
|
|
||||||
}
|
|
||||||
|
|
||||||
with(descriptor.returnType) {
|
|
||||||
if (isDefault() || isError || extractionTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER) {
|
|
||||||
builder.noReturnType()
|
|
||||||
} else {
|
|
||||||
builder.returnType(typeAsString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.typeConstraints(descriptor.typeParameters.flatMap { it.originalConstraints }.map { it.text!! })
|
when (extractionTarget) {
|
||||||
|
ExtractionTarget.FUNCTION,
|
||||||
if (withBody) {
|
ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
|
||||||
val bodyText = descriptor.extractionData.codeFragmentText
|
ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody("$0")
|
||||||
when (extractionTarget) {
|
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer("$0")
|
||||||
ExtractionTarget.FUNCTION,
|
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody("$0")
|
||||||
ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION,
|
|
||||||
ExtractionTarget.PROPERTY_WITH_GETTER -> builder.blockBody(bodyText)
|
|
||||||
ExtractionTarget.PROPERTY_WITH_INITIALIZER -> builder.initializer(bodyText)
|
|
||||||
ExtractionTarget.LAZY_PROPERTY -> builder.lazyBody(bodyText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.asString()
|
builder.asString()
|
||||||
@@ -131,22 +150,7 @@ fun KotlinType.isSpecial(): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createNameCounterpartMap(from: KtElement, to: KtElement): Map<KtSimpleNameExpression, KtSimpleNameExpression> {
|
fun createNameCounterpartMap(from: KtElement, to: KtElement): Map<KtSimpleNameExpression, KtSimpleNameExpression> {
|
||||||
val map = HashMap<KtSimpleNameExpression, KtSimpleNameExpression>()
|
return from.collectDescendantsOfType<KtSimpleNameExpression>().zip(to.collectDescendantsOfType<KtSimpleNameExpression>()).toMap()
|
||||||
|
|
||||||
val fromOffset = from.textRange!!.startOffset
|
|
||||||
from.accept(
|
|
||||||
object : KtTreeVisitorVoid() {
|
|
||||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
|
||||||
val offset = expression.textRange!!.startOffset - fromOffset
|
|
||||||
val newExpression = to.findElementAt(offset)?.getNonStrictParentOfType<KtSimpleNameExpression>()
|
|
||||||
assert(newExpression != null) { "Couldn't find expression at $offset in '${to.text}'" }
|
|
||||||
|
|
||||||
map[expression] = newExpression!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DuplicateInfo(
|
class DuplicateInfo(
|
||||||
@@ -408,131 +412,94 @@ private fun makeCall(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var KtExpression.isJumpElementToReplace: Boolean
|
||||||
|
by NotNullableCopyableUserDataProperty(Key.create("IS_JUMP_ELEMENT_TO_REPLACE"), false)
|
||||||
|
|
||||||
|
private var KtReturnExpression.isReturnForLabelRemoval: Boolean
|
||||||
|
by NotNullableCopyableUserDataProperty(Key.create("IS_RETURN_FOR_LABEL_REMOVAL"), false)
|
||||||
|
|
||||||
fun ExtractionGeneratorConfiguration.generateDeclaration(
|
fun ExtractionGeneratorConfiguration.generateDeclaration(
|
||||||
declarationToReplace: KtNamedDeclaration? = null
|
declarationToReplace: KtNamedDeclaration? = null
|
||||||
): ExtractionResult{
|
): ExtractionResult{
|
||||||
val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile)
|
val psiFactory = KtPsiFactory(descriptor.extractionData.originalFile)
|
||||||
val nameByOffset = MultiMap<Int, KtElement>()
|
|
||||||
|
fun getReturnsForLabelRemoval() = descriptor.controlFlow.outputValues
|
||||||
|
.flatMapTo(ArrayList<KtReturnExpression>()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() }
|
||||||
|
|
||||||
fun createDeclaration(): KtNamedDeclaration {
|
fun createDeclaration(): KtNamedDeclaration {
|
||||||
|
descriptor.controlFlow.jumpOutputValue?.elementsToReplace?.forEach { it.isJumpElementToReplace = true }
|
||||||
|
getReturnsForLabelRemoval().forEach { it.isReturnForLabelRemoval = true }
|
||||||
|
|
||||||
return with(descriptor.extractionData) {
|
return with(descriptor.extractionData) {
|
||||||
if (generatorOptions.inTempFile) {
|
if (generatorOptions.inTempFile) {
|
||||||
createTemporaryDeclaration("${getDeclarationText()}\n")
|
createTemporaryDeclaration("${getDeclarationPattern()}\n")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
psiFactory.createDeclaration(getDeclarationText())
|
psiFactory.createDeclarationByPattern(
|
||||||
|
getDeclarationPattern(),
|
||||||
|
PsiChildRange(originalElements.firstOrNull(), originalElements.lastOrNull())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReturnArguments(resultExpression: KtExpression?): List<String> {
|
fun getReturnArguments(resultExpression: KtExpression?): List<KtExpression> {
|
||||||
return descriptor.controlFlow.outputValues
|
return descriptor.controlFlow.outputValues
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
when (it) {
|
when (it) {
|
||||||
is ExpressionValue -> resultExpression?.text
|
is ExpressionValue -> resultExpression
|
||||||
is Jump -> if (it.conditional) "false" else null
|
is Jump -> if (it.conditional) psiFactory.createExpression("false") else null
|
||||||
is ParameterUpdate -> it.parameter.nameForRef
|
is ParameterUpdate -> psiFactory.createExpression(it.parameter.nameForRef)
|
||||||
is Initializer -> it.initializedDeclaration.name
|
is Initializer -> psiFactory.createExpression(it.initializedDeclaration.name!!)
|
||||||
else -> throw IllegalArgumentException("Unknown output value: $it")
|
else -> throw IllegalArgumentException("Unknown output value: $it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun replaceWithReturn(
|
fun KtExpression.replaceWithReturn(replacingExpression: KtReturnExpression) {
|
||||||
originalExpression: KtExpression,
|
descriptor.controlFlow.defaultOutputValue?.let {
|
||||||
replacingExpression: KtReturnExpression,
|
val boxedExpression = replaced(replacingExpression).returnedExpression!!
|
||||||
expressionToUnifyWith: KtExpression?
|
|
||||||
) {
|
|
||||||
val currentResultExpression =
|
|
||||||
(if (originalExpression is KtReturnExpression) originalExpression.returnedExpression else originalExpression) ?: return
|
|
||||||
|
|
||||||
val newResultExpression = descriptor.controlFlow.defaultOutputValue?.let {
|
|
||||||
val boxedExpression = originalExpression.replaced(replacingExpression).returnedExpression!!
|
|
||||||
descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
|
descriptor.controlFlow.outputValueBoxer.extractExpressionByValue(boxedExpression, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress
|
|
||||||
if (newResultExpression == null) {
|
|
||||||
throw AssertionError("Can' replace '${originalExpression.text}' with '${replacingExpression.text}'")
|
|
||||||
}
|
|
||||||
|
|
||||||
val counterpartMap = createNameCounterpartMap(currentResultExpression, expressionToUnifyWith ?: newResultExpression)
|
|
||||||
counterpartMap.entries.forEach {
|
|
||||||
val (cmOriginalExpr, cmNewExpr) = it
|
|
||||||
nameByOffset.entrySet().find { cmOriginalExpr in it.value }?.let {
|
|
||||||
nameByOffset.remove(it.key, cmOriginalExpr)
|
|
||||||
nameByOffset.putValue(it.key, cmNewExpr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCounterparts<T : KtExpression>(originalExpressions: Collection<T>,
|
|
||||||
body: KtExpression,
|
|
||||||
bodyOffset: Int,
|
|
||||||
file: PsiFile): List<T> {
|
|
||||||
return originalExpressions.map { originalExpression ->
|
|
||||||
val offsetInBody = originalExpression.textRange!!.startOffset - descriptor.extractionData.originalStartOffset!!
|
|
||||||
file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType(originalExpression.javaClass)
|
|
||||||
?: throw AssertionError("Couldn't find expression at $offsetInBody in '${body.text}'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun adjustDeclarationBody(declaration: KtNamedDeclaration) {
|
fun adjustDeclarationBody(declaration: KtNamedDeclaration) {
|
||||||
val body = declaration.getGeneratedBody()
|
val body = declaration.getGeneratedBody()
|
||||||
|
|
||||||
val exprReplacementMap = MultiMap<KtElement, (ExtractableCodeDescriptor, KtElement) -> KtElement>()
|
val jumpValue = descriptor.controlFlow.jumpOutputValue
|
||||||
val originalOffsetByExpr = LinkedHashMap<KtElement, Int>()
|
if (jumpValue != null) {
|
||||||
|
val replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
|
||||||
|
body.collectDescendantsOfType<KtExpression> { it.isJumpElementToReplace }.forEach {
|
||||||
|
it.replace(replacingReturn)
|
||||||
|
it.isJumpElementToReplace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val bodyOffset = body.getBlockContentOffset()
|
body.collectDescendantsOfType<KtReturnExpression> { it.isReturnForLabelRemoval }.forEach {
|
||||||
val file = body.containingFile!!
|
it.getTargetLabel()?.delete()
|
||||||
|
it.isReturnForLabelRemoval = false
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
|
* Sort by descending position so that internals of value/type arguments in calls and qualified types are replaced
|
||||||
* before calls/types themselves
|
* before calls/types themselves
|
||||||
*/
|
*/
|
||||||
for ((offsetInBody, resolveResult) in descriptor.extractionData.refOffsetToDeclaration.entries.sortedByDescending { it.key }) {
|
val currentRefs = body
|
||||||
val expr = file.findElementAt(bodyOffset + offsetInBody)?.getNonStrictParentOfType<KtSimpleNameExpression>()
|
.collectDescendantsOfType<KtSimpleNameExpression> { it.resolveResult != null }
|
||||||
assert(expr != null) { "Couldn't find expression at $offsetInBody in '${body.text}'" }
|
.sortedByDescending { it.startOffset }
|
||||||
|
|
||||||
originalOffsetByExpr[expr!!] = offsetInBody
|
currentRefs.forEach {
|
||||||
|
val resolveResult = it.resolveResult!!
|
||||||
descriptor.replacementMap[offsetInBody].let { replacements ->
|
val currentRef = if (it.isValid) {
|
||||||
replacements.forEach {
|
it
|
||||||
exprReplacementMap.putValue(expr, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
|
body.findDescendantOfType<KtSimpleNameExpression> { it.resolveResult == resolveResult } ?: return@forEach
|
||||||
val replacingReturn: KtExpression?
|
|
||||||
val expressionsToReplaceWithReturn: List<KtElement>
|
|
||||||
|
|
||||||
val returnsForLabelRemoval = descriptor.controlFlow.outputValues
|
|
||||||
.flatMapTo(ArrayList<KtReturnExpression>()) { it.originalExpressions.filterIsInstance<KtReturnExpression>() }
|
|
||||||
|
|
||||||
val jumpValue = descriptor.controlFlow.jumpOutputValue
|
|
||||||
if (jumpValue != null) {
|
|
||||||
replacingReturn = psiFactory.createExpression(if (jumpValue.conditional) "return true" else "return")
|
|
||||||
returnsForLabelRemoval.removeAll(jumpValue.elementsToReplace)
|
|
||||||
expressionsToReplaceWithReturn = getCounterparts(jumpValue.elementsToReplace, body, bodyOffset, file)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
replacingReturn = null
|
|
||||||
expressionsToReplaceWithReturn = Collections.emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (replacingReturn != null) {
|
|
||||||
for (expr in expressionsToReplaceWithReturn) {
|
|
||||||
expr.replace(replacingReturn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCounterparts(returnsForLabelRemoval, body, bodyOffset, file).forEach { it.getTargetLabel()?.delete() }
|
|
||||||
|
|
||||||
for ((expr, originalOffset) in originalOffsetByExpr) {
|
|
||||||
if (expr.isValid) {
|
|
||||||
val replacements = exprReplacementMap[expr].mapNotNull { it?.invoke(descriptor, expr) }
|
|
||||||
nameByOffset.put(originalOffset, if (replacements.isEmpty()) arrayListOf(expr) else replacements)
|
|
||||||
}
|
}
|
||||||
|
val originalRef = resolveResult.originalRefExpr
|
||||||
|
val newRef = descriptor.replacementMap[originalRef]
|
||||||
|
.fold(currentRef as KtElement) { currentRef, replacement -> replacement(descriptor, currentRef) }
|
||||||
|
(newRef as? KtSimpleNameExpression)?.resolveResult = resolveResult
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return
|
if (generatorOptions.target == ExtractionTarget.PROPERTY_WITH_INITIALIZER) return
|
||||||
@@ -554,20 +521,19 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
|
|||||||
val lastExpression = body.statements.lastOrNull()
|
val lastExpression = body.statements.lastOrNull()
|
||||||
if (lastExpression is KtReturnExpression) return
|
if (lastExpression is KtReturnExpression) return
|
||||||
|
|
||||||
val (defaultExpression, expressionToUnifyWith) =
|
val defaultExpression =
|
||||||
if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) {
|
if (!generatorOptions.inTempFile && defaultValue != null && descriptor.controlFlow.outputValueBoxer.boxingRequired && lastExpression!!.isMultiLine()) {
|
||||||
val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES)
|
val varNameValidator = NewDeclarationNameValidator(body, lastExpression, NewDeclarationNameValidator.Target.VARIABLES)
|
||||||
val resultVal = KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first()
|
val resultVal = KotlinNameSuggester.suggestNamesByType(defaultValue.valueType, varNameValidator, null).first()
|
||||||
val newDecl = body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression) as KtProperty
|
body.addBefore(psiFactory.createDeclaration("val $resultVal = ${lastExpression.text}"), lastExpression)
|
||||||
body.addBefore(psiFactory.createNewLine(), lastExpression)
|
body.addBefore(psiFactory.createNewLine(), lastExpression)
|
||||||
psiFactory.createExpression(resultVal) to newDecl.initializer!!
|
psiFactory.createExpression(resultVal)
|
||||||
}
|
|
||||||
else {
|
|
||||||
lastExpression to null
|
|
||||||
}
|
}
|
||||||
|
else lastExpression
|
||||||
|
|
||||||
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return
|
val returnExpression = descriptor.controlFlow.outputValueBoxer.getReturnExpression(getReturnArguments(defaultExpression), psiFactory) ?: return
|
||||||
|
|
||||||
|
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||||
when(generatorOptions.target) {
|
when(generatorOptions.target) {
|
||||||
ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> {
|
ExtractionTarget.LAZY_PROPERTY, ExtractionTarget.FAKE_LAMBDALIKE_FUNCTION -> {
|
||||||
// In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
|
// In the case of lazy property absence of default value means that output values are of OutputValue.Initializer type
|
||||||
@@ -580,11 +546,8 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
defaultValue == null ->
|
defaultValue == null -> body.appendElement(returnExpression)
|
||||||
body.appendElement(returnExpression)
|
!defaultValue.callSiteReturn -> lastExpression!!.replaceWithReturn(returnExpression)
|
||||||
|
|
||||||
!defaultValue.callSiteReturn ->
|
|
||||||
replaceWithReturn(lastExpression!!, returnExpression, expressionToUnifyWith)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generatorOptions.allowExpressionBody) {
|
if (generatorOptions.allowExpressionBody) {
|
||||||
@@ -657,7 +620,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap(), nameByOffset)
|
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, Collections.emptyMap())
|
||||||
|
|
||||||
val replaceInitialOccurrence = {
|
val replaceInitialOccurrence = {
|
||||||
val arguments = descriptor.parameters.map { it.argumentText }
|
val arguments = descriptor.parameters.map { it.argumentText }
|
||||||
@@ -670,7 +633,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
|
|||||||
ShortenReferences.DEFAULT.process(declaration)
|
ShortenReferences.DEFAULT.process(declaration)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, emptyMap(), nameByOffset)
|
if (generatorOptions.inTempFile) return ExtractionResult(this, declaration, emptyMap())
|
||||||
|
|
||||||
val duplicateReplacers = HashMap<KotlinPsiRange, () -> Unit>().apply {
|
val duplicateReplacers = HashMap<KotlinPsiRange, () -> Unit>().apply {
|
||||||
if (generatorOptions.delayInitialOccurrenceReplacement) {
|
if (generatorOptions.delayInitialOccurrenceReplacement) {
|
||||||
@@ -688,5 +651,7 @@ fun ExtractionGeneratorConfiguration.generateDeclaration(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExtractionResult(this, declaration, duplicateReplacers, nameByOffset)
|
CodeStyleManager.getInstance(descriptor.extractionData.project).reformat(declaration)
|
||||||
|
|
||||||
|
return ExtractionResult(this, declaration, duplicateReplacers)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-8
@@ -17,6 +17,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
package org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine
|
||||||
|
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
|
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.SingleType
|
import org.jetbrains.kotlin.cfg.pseudocode.SingleType
|
||||||
import org.jetbrains.kotlin.cfg.pseudocode.getElementValuesRecursively
|
import org.jetbrains.kotlin.cfg.pseudocode.getElementValuesRecursively
|
||||||
@@ -51,6 +52,15 @@ import org.jetbrains.kotlin.types.KotlinType
|
|||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
internal class ParametersInfo {
|
||||||
|
var errorMessage: AnalysisResult.ErrorMessage? = null
|
||||||
|
val originalRefToParameter = MultiMap.create<KtSimpleNameExpression, MutableParameter>()
|
||||||
|
val parameters = HashSet<MutableParameter>()
|
||||||
|
val typeParameters = HashSet<TypeParameter>()
|
||||||
|
val nonDenotableTypes = HashSet<KotlinType>()
|
||||||
|
val replacementMap = MultiMap.create<KtSimpleNameExpression, Replacement>()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun ExtractionData.inferParametersInfo(
|
internal fun ExtractionData.inferParametersInfo(
|
||||||
commonParent: PsiElement,
|
commonParent: PsiElement,
|
||||||
pseudocode: Pseudocode,
|
pseudocode: Pseudocode,
|
||||||
@@ -158,7 +168,7 @@ private fun ExtractionData.extractReceiver(
|
|||||||
)) return
|
)) return
|
||||||
|
|
||||||
if (referencedClassifierDescriptor is ClassDescriptor) {
|
if (referencedClassifierDescriptor is ClassDescriptor) {
|
||||||
info.replacementMap.putValue(refInfo.offsetInBody, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
|
info.replacementMap.putValue(originalRef, FqNameReplacement(originalDescriptor.getImportableDescriptor().fqNameSafe))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -244,12 +254,12 @@ private fun ExtractionData.extractReceiver(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.replacementMap.putValue(refInfo.offsetInBody,
|
val replacement = when {
|
||||||
when {
|
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
|
||||||
isMemberExtensionFunction -> WrapInWithReplacement(parameter)
|
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
|
||||||
hasThisReceiver && extractThis -> AddPrefixReplacement(parameter)
|
else -> RenameReplacement(parameter)
|
||||||
else -> RenameReplacement(parameter)
|
}
|
||||||
})
|
info.replacementMap.putValue(originalRef, replacement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +291,6 @@ private fun suggestParameterType(
|
|||||||
?: if (receiverToExtract.exists()) receiverToExtract.type else null
|
?: if (receiverToExtract.exists()) receiverToExtract.type else null
|
||||||
|
|
||||||
receiverToExtract is ImplicitReceiver -> {
|
receiverToExtract is ImplicitReceiver -> {
|
||||||
val calleeExpression = resolvedCall!!.call.calleeExpression
|
|
||||||
val typeByDataFlowInfo = if (useSmartCastsIfPossible) {
|
val typeByDataFlowInfo = if (useSmartCastsIfPossible) {
|
||||||
val dataFlowInfo = bindingContext.getDataFlowInfo(resolvedCall!!.call.callElement)
|
val dataFlowInfo = bindingContext.getDataFlowInfo(resolvedCall!!.call.callElement)
|
||||||
val possibleTypes = dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract))
|
val possibleTypes = dataFlowInfo.getPossibleTypes(DataFlowValueFactory.createDataFlowValueForStableReceiver(receiverToExtract))
|
||||||
|
|||||||
@@ -130,9 +130,9 @@ public fun PsiElement.getUsageContext(): PsiElement {
|
|||||||
public fun PsiElement.isInJavaSourceRoot(): Boolean =
|
public fun PsiElement.isInJavaSourceRoot(): Boolean =
|
||||||
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
|
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(getContainingFile())
|
||||||
|
|
||||||
public inline fun KtFile.createTempCopy(textTransform: (String) -> String): KtFile {
|
public fun KtFile.createTempCopy(text: String? = null): KtFile {
|
||||||
val tmpFile = KtPsiFactory(this).createAnalyzableFile(getName(), textTransform(getText() ?: ""), this)
|
val tmpFile = KtPsiFactory(this).createAnalyzableFile(name, text ?: this.text ?: "", this)
|
||||||
tmpFile.setOriginalFile(this)
|
tmpFile.originalFile = this
|
||||||
tmpFile.suppressDiagnosticsInDebugMode = suppressDiagnosticsInDebugMode
|
tmpFile.suppressDiagnosticsInDebugMode = suppressDiagnosticsInDebugMode
|
||||||
return tmpFile
|
return tmpFile
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ fun foo(a: Int): Int {
|
|||||||
|
|
||||||
private fun b(a: Int): Boolean {
|
private fun b(a: Int): Boolean {
|
||||||
/*
|
/*
|
||||||
test
|
test
|
||||||
*/
|
*/
|
||||||
println(a)
|
println(a)
|
||||||
if (a > 0) return true
|
if (a > 0) return true
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user