Refactoring: inline val / fun now use the common inliner
This prevents their inconsistent work in some situations NB: breaks three tests if used alone
This commit is contained in:
+22
-11
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.refactoring.inline
|
|||||||
|
|
||||||
import com.intellij.lang.findUsages.DescriptiveNameUtil
|
import com.intellij.lang.findUsages.DescriptiveNameUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||||
@@ -31,25 +32,34 @@ import org.jetbrains.kotlin.idea.codeInliner.replaceUsages
|
|||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
|
|
||||||
class KotlinInlineFunctionProcessor(
|
class KotlinInlineCallableProcessor(
|
||||||
project: Project,
|
project: Project,
|
||||||
private val replacementStrategy: CallableUsageReplacementStrategy,
|
private val replacementStrategy: CallableUsageReplacementStrategy,
|
||||||
private val function: KtNamedFunction,
|
private val declaration: KtCallableDeclaration,
|
||||||
private val reference: KtSimpleNameReference?,
|
private val reference: KtSimpleNameReference?,
|
||||||
private val inlineThisOnly: Boolean,
|
private val inlineThisOnly: Boolean,
|
||||||
private val deleteAfter: Boolean
|
private val deleteAfter: Boolean,
|
||||||
|
private val assignments: Set<PsiElement> = emptySet()
|
||||||
) : BaseRefactoringProcessor(project) {
|
) : BaseRefactoringProcessor(project) {
|
||||||
|
|
||||||
private val commandName = "Inlining function ${DescriptiveNameUtil.getDescriptiveName(function)}"
|
private val kind = when (declaration) {
|
||||||
|
is KtNamedFunction -> "function"
|
||||||
|
is KtProperty -> if (declaration.isLocal) "local variable" else "property"
|
||||||
|
else -> "declaration"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commandName = "Inlining $kind ${DescriptiveNameUtil.getDescriptiveName(declaration)}"
|
||||||
|
|
||||||
override fun findUsages(): Array<UsageInfo> {
|
override fun findUsages(): Array<UsageInfo> {
|
||||||
if (inlineThisOnly && reference != null) return arrayOf(UsageInfo(reference))
|
if (inlineThisOnly && reference != null) return arrayOf(UsageInfo(reference))
|
||||||
val usages = runReadAction {
|
val usages = runReadAction {
|
||||||
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(myProject), myProject)
|
val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(myProject), myProject)
|
||||||
ReferencesSearch.search(function, searchScope)
|
ReferencesSearch.search(declaration, searchScope)
|
||||||
}
|
}
|
||||||
return usages.map(::UsageInfo).toTypedArray()
|
return usages.map(::UsageInfo).toTypedArray()
|
||||||
}
|
}
|
||||||
@@ -58,20 +68,21 @@ class KotlinInlineFunctionProcessor(
|
|||||||
val simpleNameUsages = usages.mapNotNull { it.element as? KtSimpleNameExpression }
|
val simpleNameUsages = usages.mapNotNull { it.element as? KtSimpleNameExpression }
|
||||||
replacementStrategy.replaceUsages(
|
replacementStrategy.replaceUsages(
|
||||||
simpleNameUsages,
|
simpleNameUsages,
|
||||||
function,
|
declaration,
|
||||||
myProject,
|
myProject,
|
||||||
commandName,
|
commandName,
|
||||||
{
|
{
|
||||||
if (deleteAfter) {
|
if (deleteAfter) {
|
||||||
if (usages.size == simpleNameUsages.size) {
|
if (usages.size == simpleNameUsages.size) {
|
||||||
function.delete()
|
declaration.delete()
|
||||||
|
assignments.forEach(PsiElement::delete)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CommonRefactoringUtil.showErrorHint(
|
CommonRefactoringUtil.showErrorHint(
|
||||||
function.project,
|
declaration.project,
|
||||||
null,
|
null,
|
||||||
"Cannot inline ${usages.size - simpleNameUsages.size}/${usages.size} usages",
|
"Cannot inline ${usages.size - simpleNameUsages.size}/${usages.size} usages",
|
||||||
"Inline Function",
|
"Inline $kind",
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -90,9 +101,9 @@ class KotlinInlineFunctionProcessor(
|
|||||||
override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) =
|
override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) =
|
||||||
RefactoringBundle.message("invocations.to.be.inlined", UsageViewBundle.getReferencesString(usagesCount, filesCount))
|
RefactoringBundle.message("invocations.to.be.inlined", UsageViewBundle.getReferencesString(usagesCount, filesCount))
|
||||||
|
|
||||||
override fun getElements() = arrayOf(function)
|
override fun getElements() = arrayOf(declaration)
|
||||||
|
|
||||||
override fun getProcessedElementsHeader() = "Function to inline"
|
override fun getProcessedElementsHeader() = "${kind.capitalize()} to inline"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,6 @@ import com.intellij.openapi.help.HelpManager
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.refactoring.HelpID
|
import com.intellij.refactoring.HelpID
|
||||||
import com.intellij.refactoring.JavaRefactoringSettings
|
import com.intellij.refactoring.JavaRefactoringSettings
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
|
||||||
import com.intellij.refactoring.inline.InlineOptionsDialog
|
import com.intellij.refactoring.inline.InlineOptionsDialog
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference
|
||||||
@@ -71,7 +70,7 @@ class KotlinInlineFunctionDialog(
|
|||||||
|
|
||||||
public override fun doAction() {
|
public override fun doAction() {
|
||||||
invokeRefactoring(
|
invokeRefactoring(
|
||||||
KotlinInlineFunctionProcessor(project, replacementStrategy, function, reference,
|
KotlinInlineCallableProcessor(project, replacementStrategy, function, reference,
|
||||||
inlineThisOnly = isInlineThisOnly || allowInlineThisOnly,
|
inlineThisOnly = isInlineThisOnly || allowInlineThisOnly,
|
||||||
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration && !allowInlineThisOnly)
|
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration && !allowInlineThisOnly)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,48 +17,64 @@
|
|||||||
package org.jetbrains.kotlin.idea.refactoring.inline
|
package org.jetbrains.kotlin.idea.refactoring.inline
|
||||||
|
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.PsiReference
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.refactoring.JavaRefactoringSettings
|
import com.intellij.refactoring.JavaRefactoringSettings
|
||||||
import com.intellij.refactoring.inline.AbstractInlineLocalDialog
|
import com.intellij.refactoring.inline.InlineOptionsDialog
|
||||||
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
|
||||||
class KotlinInlineValDialog(
|
class KotlinInlineValDialog(
|
||||||
element: KtProperty,
|
private val property: KtProperty,
|
||||||
ref: PsiReference?,
|
private val reference: KtSimpleNameReference?,
|
||||||
private val occurrenceCount: Int
|
private val replacementStrategy: CallableUsageReplacementStrategy,
|
||||||
) : AbstractInlineLocalDialog(element.project, element, ref, occurrenceCount) {
|
private val assignments: Set<PsiElement>
|
||||||
private val property: KtProperty get() = myElement as KtProperty
|
) : InlineOptionsDialog(property.project, true, property) {
|
||||||
|
|
||||||
|
private var occurrenceCount = initOccurrencesNumber(property)
|
||||||
|
|
||||||
private val kind = if (property.isLocal) "local variable" else "property"
|
private val kind = if (property.isLocal) "local variable" else "property"
|
||||||
|
|
||||||
private val refactoringName = "Inline ${StringUtil.capitalizeWords(kind, true)}"
|
private val refactoringName = "Inline ${StringUtil.capitalizeWords(kind, true)}"
|
||||||
|
|
||||||
init {
|
init {
|
||||||
myInvokedOnReference = ref != null
|
myInvokedOnReference = reference != null
|
||||||
title = refactoringName
|
title = refactoringName
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allowInlineAll() = true
|
||||||
|
|
||||||
override fun getBorderTitle() = refactoringName
|
override fun getBorderTitle() = refactoringName
|
||||||
|
|
||||||
override fun getNameLabelText() = "${kind.capitalize()} ${property.name}"
|
override fun getNameLabelText() = "${kind.capitalize()} ${property.name}"
|
||||||
|
|
||||||
override fun getInlineAllText(): String? {
|
private val occurrencesString get() = if (occurrenceCount >= 0) {
|
||||||
val occurrencesString = if (occurrenceCount >= 0) {
|
" (" + occurrenceCount + " occurrence" + (if (occurrenceCount == 1) ")" else "s)")
|
||||||
" (" + occurrenceCount + " occurrence" + (if (occurrenceCount == 1) ")" else "s)")
|
} else ""
|
||||||
} else ""
|
|
||||||
return "Inline all references and remove the $kind" + occurrencesString
|
override fun getInlineAllText() =
|
||||||
}
|
"Inline all references and remove the $kind" + occurrencesString
|
||||||
|
|
||||||
|
override fun getKeepTheDeclarationText(): String? =
|
||||||
|
if (property.isWritable) "Inline all references and keep the $kind" + occurrencesString
|
||||||
|
else super.getKeepTheDeclarationText()
|
||||||
|
|
||||||
override fun isInlineThis() = JavaRefactoringSettings.getInstance().INLINE_LOCAL_THIS
|
override fun isInlineThis() = JavaRefactoringSettings.getInstance().INLINE_LOCAL_THIS
|
||||||
|
|
||||||
override fun getInlineThisText() = "Inline this occurrence and leave the $kind"
|
override fun getInlineThisText() = "Inline this occurrence and leave the $kind"
|
||||||
|
|
||||||
override fun doAction() {
|
public override fun doAction() {
|
||||||
|
invokeRefactoring(
|
||||||
|
KotlinInlineCallableProcessor(project, replacementStrategy, property, reference,
|
||||||
|
inlineThisOnly = isInlineThisOnly,
|
||||||
|
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration,
|
||||||
|
assignments = assignments)
|
||||||
|
)
|
||||||
|
|
||||||
val settings = JavaRefactoringSettings.getInstance()
|
val settings = JavaRefactoringSettings.getInstance()
|
||||||
if (myRbInlineThisOnly.isEnabled && myRbInlineAll.isEnabled) {
|
if (myRbInlineThisOnly.isEnabled && myRbInlineAll.isEnabled) {
|
||||||
settings.INLINE_LOCAL_THIS = isInlineThisOnly
|
settings.INLINE_LOCAL_THIS = isInlineThisOnly
|
||||||
}
|
}
|
||||||
close(OK_EXIT_CODE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,51 +16,39 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.inline
|
package org.jetbrains.kotlin.idea.refactoring.inline
|
||||||
|
|
||||||
import com.google.common.collect.Sets
|
import com.intellij.codeInsight.TargetElementUtil
|
||||||
import com.intellij.lang.Language
|
import com.intellij.lang.Language
|
||||||
import com.intellij.lang.refactoring.InlineActionHandler
|
import com.intellij.lang.refactoring.InlineActionHandler
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.wm.WindowManager
|
import com.intellij.openapi.wm.WindowManager
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiReference
|
|
||||||
import com.intellij.psi.PsiWhiteSpace
|
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.HelpID
|
import com.intellij.refactoring.HelpID
|
||||||
import com.intellij.refactoring.JavaRefactoringSettings
|
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.core.replaced
|
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
||||||
import org.jetbrains.kotlin.idea.refactoring.addTypeArgumentsIfNeeded
|
import org.jetbrains.kotlin.idea.core.copied
|
||||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getQualifiedTypeArgumentList
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.getAssignmentByLHS
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.types.ErrorUtils
|
|
||||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import org.jetbrains.kotlin.utils.sure
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class KotlinInlineValHandler : InlineActionHandler() {
|
class KotlinInlineValHandler : InlineActionHandler() {
|
||||||
enum class InlineMode {
|
|
||||||
ALL, PRIMARY, NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isEnabledForLanguage(l: Language) = l == KotlinLanguage.INSTANCE
|
override fun isEnabledForLanguage(l: Language) = l == KotlinLanguage.INSTANCE
|
||||||
|
|
||||||
@@ -69,46 +57,14 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
return element.getter == null && element.receiverTypeReference == null
|
return element.getter == null && element.receiverTypeReference == null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doReplace(expression: KtExpression, replacement: KtExpression): List<KtExpression> {
|
|
||||||
val parent = expression.parent
|
|
||||||
|
|
||||||
if (parent is KtStringTemplateEntryWithExpression &&
|
|
||||||
replacement is KtStringTemplateExpression &&
|
|
||||||
// Do not mix raw and non-raw templates
|
|
||||||
parent.parent.firstChild.text == replacement.firstChild.text) {
|
|
||||||
val entriesToAdd = replacement.entries
|
|
||||||
val templateExpression = parent.parent as KtStringTemplateExpression
|
|
||||||
val inlinedExpressions = if (entriesToAdd.isNotEmpty()) {
|
|
||||||
val firstAddedEntry = templateExpression.addRangeBefore(entriesToAdd.first(), entriesToAdd.last(), parent)
|
|
||||||
val lastNewEntry = parent.prevSibling
|
|
||||||
val nextElement = parent.nextSibling
|
|
||||||
if (lastNewEntry is KtSimpleNameStringTemplateEntry &&
|
|
||||||
lastNewEntry.expression != null &&
|
|
||||||
!canPlaceAfterSimpleNameEntry(nextElement)) {
|
|
||||||
lastNewEntry.replace(KtPsiFactory(expression).createBlockStringTemplateEntry(lastNewEntry.expression!!))
|
|
||||||
}
|
|
||||||
firstAddedEntry.siblings()
|
|
||||||
.take(entriesToAdd.size)
|
|
||||||
.mapNotNull { (it as? KtStringTemplateEntryWithExpression)?.expression }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
else emptyList()
|
|
||||||
|
|
||||||
parent.delete()
|
|
||||||
return inlinedExpressions
|
|
||||||
}
|
|
||||||
|
|
||||||
return listOf(expression.replaced(replacement))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inlineElement(project: Project, editor: Editor?, element: PsiElement) {
|
override fun inlineElement(project: Project, editor: Editor?, element: PsiElement) {
|
||||||
val declaration = element as KtProperty
|
val declaration = element as KtProperty
|
||||||
val file = declaration.containingKtFile
|
val file = declaration.containingKtFile
|
||||||
val name = declaration.name ?: return
|
val name = declaration.name ?: return
|
||||||
|
|
||||||
val references = ReferencesSearch.search(declaration)
|
val references = ReferencesSearch.search(declaration)
|
||||||
val referenceExpressions = ArrayList<KtExpression>()
|
val referenceExpressions = mutableListOf<KtExpression>()
|
||||||
val foreignUsages = ArrayList<PsiElement>()
|
val foreignUsages = mutableListOf<PsiElement>()
|
||||||
for (ref in references) {
|
for (ref in references) {
|
||||||
val refElement = ref.element ?: continue
|
val refElement = ref.element ?: continue
|
||||||
if (refElement !is KtElement) {
|
if (refElement !is KtElement) {
|
||||||
@@ -123,7 +79,7 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
return showErrorHint(project, editor, "$kind '$name' is never used")
|
return showErrorHint(project, editor, "$kind '$name' is never used")
|
||||||
}
|
}
|
||||||
|
|
||||||
val assignments = Sets.newHashSet<PsiElement>()
|
val assignments = hashSetOf<PsiElement>()
|
||||||
referenceExpressions.forEach { expression ->
|
referenceExpressions.forEach { expression ->
|
||||||
val parent = expression.parent
|
val parent = expression.parent
|
||||||
|
|
||||||
@@ -147,9 +103,6 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
?: return reportAmbiguousAssignment(project, editor, name, assignments)
|
?: return reportAmbiguousAssignment(project, editor, name, assignments)
|
||||||
}
|
}
|
||||||
|
|
||||||
val typeArgumentsForCall = getQualifiedTypeArgumentList(initializer)
|
|
||||||
val parametersForFunctionLiteral = getParametersForFunctionLiteral(initializer)
|
|
||||||
|
|
||||||
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
||||||
val isHighlighting = referencesInOriginalFile.isNotEmpty()
|
val isHighlighting = referencesInOriginalFile.isNotEmpty()
|
||||||
highlightElements(project, editor, referencesInOriginalFile)
|
highlightElements(project, editor, referencesInOriginalFile)
|
||||||
@@ -158,61 +111,36 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
preProcessInternalUsages(initializer, referenceExpressions)
|
preProcessInternalUsages(initializer, referenceExpressions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performRefactoring() {
|
val descriptor = element.resolveToDescriptor() as VariableDescriptor
|
||||||
val primaryExpression = if (editor != null) {
|
val expectedType = if (element.typeReference != null)
|
||||||
val offset = editor.caretModel.offset
|
descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE
|
||||||
referenceExpressions.firstOrNull { it.textRange.contains(offset) }
|
else
|
||||||
}
|
TypeUtils.NO_EXPECTED_TYPE
|
||||||
else null
|
|
||||||
val primaryRef = primaryExpression?.mainReference
|
|
||||||
|
|
||||||
val inlineMode = showDialog(declaration, primaryRef, referenceExpressions.size)
|
val initializerCopy = initializer.copied()
|
||||||
if (inlineMode == InlineMode.NONE) {
|
fun analyzeInitializerCopy(): BindingContext {
|
||||||
if (isHighlighting) {
|
return initializerCopy.analyzeInContext(initializer.getResolutionScope(),
|
||||||
|
contextExpression = initializer,
|
||||||
|
expectedType = expectedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun performRefactoring() {
|
||||||
|
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) } as? KtSimpleNameReference
|
||||||
|
val replacementBuilder = CodeToInlineBuilder(descriptor, element.getResolutionFacade())
|
||||||
|
val replacement = replacementBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
||||||
|
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
||||||
|
|
||||||
|
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignments)
|
||||||
|
|
||||||
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
dialog.show()
|
||||||
|
if (!dialog.isOK && isHighlighting) {
|
||||||
val statusBar = WindowManager.getInstance().getStatusBar(project)
|
val statusBar = WindowManager.getInstance().getStatusBar(project)
|
||||||
statusBar?.info = RefactoringBundle.message("press.escape.to.remove.the.highlighting")
|
statusBar?.info = RefactoringBundle.message("press.escape.to.remove.the.highlighting")
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
val chosenExpressions = if (inlineMode == InlineMode.ALL) referenceExpressions else listOf(primaryExpression)
|
dialog.doAction()
|
||||||
|
|
||||||
project.executeWriteCommand(RefactoringBundle.message("inline.command", name)) {
|
|
||||||
val inlinedExpressions = chosenExpressions
|
|
||||||
.flatMap { referenceExpression ->
|
|
||||||
if (assignments.contains(referenceExpression!!.parent)) return@flatMap emptyList<KtExpression>()
|
|
||||||
|
|
||||||
val importDirective = referenceExpression.getStrictParentOfType<KtImportDirective>()
|
|
||||||
if (importDirective != null) {
|
|
||||||
val reference = referenceExpression.getQualifiedElementSelector()?.mainReference
|
|
||||||
if (reference != null && reference.multiResolve(false).size <= 1) {
|
|
||||||
importDirective.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
return@flatMap emptyList<KtExpression>()
|
|
||||||
}
|
|
||||||
|
|
||||||
doReplace(referenceExpression, initializer)
|
|
||||||
}
|
|
||||||
.mapNotNull { postProcessInternalReferences(it) }
|
|
||||||
|
|
||||||
if (inlineMode == InlineMode.ALL) {
|
|
||||||
assignments.forEach { it.delete() }
|
|
||||||
declaration.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inlinedExpressions.isNotEmpty()) {
|
|
||||||
if (typeArgumentsForCall != null) {
|
|
||||||
inlinedExpressions.forEach { addTypeArgumentsIfNeeded(it, typeArgumentsForCall) }
|
|
||||||
}
|
|
||||||
|
|
||||||
parametersForFunctionLiteral?.let { addFunctionLiteralParameterTypes(it, inlinedExpressions) }
|
|
||||||
|
|
||||||
if (isHighlighting) {
|
|
||||||
highlightElements(project, editor, inlinedExpressions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
performDelayedRefactoringRequests(project)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,80 +166,4 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
CommonRefactoringUtil.showErrorHint(project, editor, message, RefactoringBundle.message("inline.variable.title"), HelpID.INLINE_VARIABLE)
|
CommonRefactoringUtil.showErrorHint(project, editor, message, RefactoringBundle.message("inline.variable.title"), HelpID.INLINE_VARIABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDialog(property: KtProperty, ref: PsiReference?, occurrenceCount: Int): InlineMode {
|
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) return InlineMode.ALL
|
|
||||||
if ((ref == null || occurrenceCount <= 1) && !EditorSettingsExternalizable.getInstance().isShowInlineLocalDialog) return InlineMode.ALL
|
|
||||||
|
|
||||||
val dialog = KotlinInlineValDialog(property, ref, occurrenceCount)
|
|
||||||
if (!dialog.showAndGet()) return InlineMode.NONE
|
|
||||||
return if (JavaRefactoringSettings.getInstance().INLINE_LOCAL_THIS) InlineMode.PRIMARY else InlineMode.ALL
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getParametersForFunctionLiteral(initializer: KtExpression): String? {
|
|
||||||
val functionLiteralExpression = initializer.unpackFunctionLiteral(true) ?: return null
|
|
||||||
val context = initializer.analyze(BodyResolveMode.PARTIAL)
|
|
||||||
val lambdaDescriptor = context.get(BindingContext.FUNCTION, functionLiteralExpression.functionLiteral)
|
|
||||||
if (lambdaDescriptor == null || ErrorUtils.containsErrorType(lambdaDescriptor)) return null
|
|
||||||
return lambdaDescriptor.valueParameters.joinToString {
|
|
||||||
it.name.asString() + ": " + IdeDescriptorRenderers.SOURCE_CODE.renderType(it.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFunctionLiteralParameterTypes(parameters: String, inlinedExpressions: List<KtExpression>) {
|
|
||||||
val containingFile = inlinedExpressions.first().containingKtFile
|
|
||||||
val resolutionFacade = containingFile.getResolutionFacade()
|
|
||||||
|
|
||||||
val functionsToAddParameters = inlinedExpressions.mapNotNull {
|
|
||||||
val lambdaExpr = it.unpackFunctionLiteral(true).sure { "can't find function literal expression for " + it.text }
|
|
||||||
if (needToAddParameterTypes(lambdaExpr, resolutionFacade)) lambdaExpr else null
|
|
||||||
}
|
|
||||||
|
|
||||||
val psiFactory = KtPsiFactory(containingFile)
|
|
||||||
for (lambdaExpr in functionsToAddParameters) {
|
|
||||||
val lambda = lambdaExpr.functionLiteral
|
|
||||||
|
|
||||||
val currentParameterList = lambda.valueParameterList
|
|
||||||
val newParameterList = psiFactory.createParameterList("($parameters)")
|
|
||||||
if (currentParameterList != null) {
|
|
||||||
currentParameterList.replace(newParameterList)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO: Ugly code, need refactoring
|
|
||||||
val openBraceElement = lambda.lBrace
|
|
||||||
|
|
||||||
val nextSibling = openBraceElement.nextSibling
|
|
||||||
val whitespaceToAdd = if (nextSibling is PsiWhiteSpace && nextSibling.text.contains("\n"))
|
|
||||||
nextSibling.copy()
|
|
||||||
else
|
|
||||||
null
|
|
||||||
|
|
||||||
val whitespaceAndArrow = psiFactory.createWhitespaceAndArrow()
|
|
||||||
lambda.addRangeAfter(whitespaceAndArrow.first, whitespaceAndArrow.second, openBraceElement)
|
|
||||||
|
|
||||||
lambda.addAfter(newParameterList, openBraceElement)
|
|
||||||
if (whitespaceToAdd != null) {
|
|
||||||
lambda.addAfter(whitespaceToAdd, openBraceElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ShortenReferences.DEFAULT.process(lambdaExpr.valueParameters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun needToAddParameterTypes(
|
|
||||||
lambdaExpression: KtLambdaExpression,
|
|
||||||
resolutionFacade: ResolutionFacade
|
|
||||||
): Boolean {
|
|
||||||
val functionLiteral = lambdaExpression.functionLiteral
|
|
||||||
val context = resolutionFacade.analyze(lambdaExpression, BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
|
|
||||||
return context.diagnostics.any { diagnostic ->
|
|
||||||
val factory = diagnostic.factory
|
|
||||||
val element = diagnostic.psiElement
|
|
||||||
val hasCantInferParameter = factory == Errors.CANNOT_INFER_PARAMETER_TYPE &&
|
|
||||||
element.parent.parent == functionLiteral
|
|
||||||
val hasUnresolvedItOrThis = factory == Errors.UNRESOLVED_REFERENCE &&
|
|
||||||
element.text == "it" &&
|
|
||||||
element.getStrictParentOfType<KtFunctionLiteral>() == functionLiteral
|
|
||||||
hasCantInferParameter || hasUnresolvedItOrThis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
val ff = {(it: Int) -> it }
|
val ff = { it: Int -> it }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
val ff = {
|
val ff = {
|
||||||
(it: Int) ->
|
it: Int ->
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
val ff = (({(x: Int) -> x }))
|
val ff = (({ x: Int -> x }))
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
fun foo() {
|
fun foo() {
|
||||||
val ff = {(x: Int) -> x }
|
val ff = { x: Int -> x }
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -6,5 +6,6 @@ class Class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun f() {
|
fun f() {
|
||||||
|
Class()
|
||||||
println(239)
|
println(239)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// ERROR: Cannot perform refactoring.\nVariable length has no initializer
|
||||||
|
|
||||||
|
fun foo(s: String) {
|
||||||
|
val l = s.<caret>length
|
||||||
|
}
|
||||||
@@ -982,6 +982,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("Library.kt")
|
||||||
|
public void testLibrary() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/Library.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("multiplePackages.kt")
|
@TestMetadata("multiplePackages.kt")
|
||||||
public void testMultiplePackages() throws Exception {
|
public void testMultiplePackages() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/multiplePackages.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/multiplePackages.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user