Inline Property also supported for properties with getter
This commit is contained in:
+4
-53
@@ -29,24 +29,15 @@ import com.intellij.refactoring.util.CommonRefactoringUtil
|
|||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
|
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
|
||||||
import org.jetbrains.kotlin.idea.core.copied
|
|
||||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
|
||||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
class KotlinInlineFunctionHandler: InlineActionHandler() {
|
class KotlinInlineFunctionHandler: InlineActionHandler() {
|
||||||
@@ -58,49 +49,6 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
|
|||||||
override fun inlineElement(project: Project, editor: Editor?, element: PsiElement) {
|
override fun inlineElement(project: Project, editor: Editor?, element: PsiElement) {
|
||||||
element as KtNamedFunction
|
element as KtNamedFunction
|
||||||
|
|
||||||
val descriptor = element.resolveToDescriptor() as SimpleFunctionDescriptor
|
|
||||||
val bodyExpression = element.bodyExpression!!
|
|
||||||
val bodyCopy = bodyExpression.copied()
|
|
||||||
|
|
||||||
val expectedType = if (!element.hasBlockBody() && element.hasDeclaredReturnType())
|
|
||||||
descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE
|
|
||||||
else
|
|
||||||
TypeUtils.NO_EXPECTED_TYPE
|
|
||||||
|
|
||||||
fun analyzeBodyCopy(): BindingContext {
|
|
||||||
return bodyCopy.analyzeInContext(bodyExpression.getResolutionScope(),
|
|
||||||
contextExpression = bodyExpression,
|
|
||||||
expectedType = expectedType)
|
|
||||||
}
|
|
||||||
|
|
||||||
val replacementBuilder = CodeToInlineBuilder(descriptor, element.getResolutionFacade())
|
|
||||||
val replacement = if (element.hasBlockBody()) {
|
|
||||||
bodyCopy as KtBlockExpression
|
|
||||||
val statements = bodyCopy.statements
|
|
||||||
|
|
||||||
val returnStatements = bodyCopy.collectDescendantsOfType<KtReturnExpression> {
|
|
||||||
it.getLabelName().let { it == null || it == element.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
val lastReturn = statements.lastOrNull() as? KtReturnExpression
|
|
||||||
if (returnStatements.any { it != lastReturn }) {
|
|
||||||
val message = RefactoringBundle.getCannotRefactorMessage(
|
|
||||||
if (returnStatements.size > 1)
|
|
||||||
"Inline Function is not supported for functions with multiple return statements."
|
|
||||||
else
|
|
||||||
"Inline Function is not supported for functions with return statements not at the end of the body."
|
|
||||||
)
|
|
||||||
CommonRefactoringUtil.showErrorHint(project, editor, message, "Inline Function", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
replacementBuilder.prepareCodeToInline(lastReturn?.returnedExpression,
|
|
||||||
statements.dropLast(returnStatements.size), ::analyzeBodyCopy)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
replacementBuilder.prepareCodeToInline(bodyCopy, emptyList(), ::analyzeBodyCopy)
|
|
||||||
}
|
|
||||||
|
|
||||||
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) }
|
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) }
|
||||||
val nameReference = when (reference) {
|
val nameReference = when (reference) {
|
||||||
is KtSimpleNameReference -> reference
|
is KtSimpleNameReference -> reference
|
||||||
@@ -114,7 +62,10 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
val descriptor = element.resolveToDescriptor() as SimpleFunctionDescriptor
|
||||||
|
val codeToInline = buildCodeToInline(element, descriptor.returnType, editor) ?: return
|
||||||
|
|
||||||
|
val replacementStrategy = CallableUsageReplacementStrategy(codeToInline)
|
||||||
|
|
||||||
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy,
|
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy,
|
||||||
allowInlineThisOnly = recursive)
|
allowInlineThisOnly = recursive)
|
||||||
|
|||||||
@@ -29,12 +29,14 @@ import com.intellij.refactoring.HelpID
|
|||||||
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.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
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.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
||||||
|
import org.jetbrains.kotlin.idea.codeInliner.CodeToInline
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
||||||
import org.jetbrains.kotlin.idea.core.copied
|
import org.jetbrains.kotlin.idea.core.copied
|
||||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||||
@@ -58,18 +60,70 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
override fun isEnabledForLanguage(l: Language) = l == KotlinLanguage.INSTANCE
|
override fun isEnabledForLanguage(l: Language) = l == KotlinLanguage.INSTANCE
|
||||||
|
|
||||||
override fun canInlineElement(element: PsiElement): Boolean {
|
override fun canInlineElement(element: PsiElement): Boolean {
|
||||||
if (element !is KtProperty) return false
|
return element is KtProperty && element.name != null
|
||||||
return element.getter == null && element.receiverTypeReference == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 name = declaration.name!!
|
||||||
|
|
||||||
val file = declaration.containingKtFile
|
val file = declaration.containingKtFile
|
||||||
val name = declaration.name ?: return
|
|
||||||
if (file.isCompiled) {
|
if (file.isCompiled) {
|
||||||
return showErrorHint(project, editor, "Cannot inline '$name' from a decompiled file")
|
return showErrorHint(project, editor, "Cannot inline '$name' from a decompiled file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getter = declaration.getter?.takeIf { it.hasBody() }
|
||||||
|
val setter = declaration.setter?.takeIf { it.hasBody() }
|
||||||
|
|
||||||
|
if (getter != null || setter != null) {
|
||||||
|
if (declaration.initializer != null) {
|
||||||
|
return showErrorHint(project, editor, "Cannot inline property with accessor(s) and backing field")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setter != null) {
|
||||||
|
return showErrorHint(project, editor, "Inline property not supported for properties with setter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (referenceExpressions, foreignUsages) = findUsages(declaration)
|
||||||
|
|
||||||
|
if (referenceExpressions.isEmpty()) {
|
||||||
|
val kind = if (declaration.isLocal) "Variable" else "Property"
|
||||||
|
return showErrorHint(project, editor, "$kind '$name' is never used") //TODO: foreign usages!
|
||||||
|
}
|
||||||
|
|
||||||
|
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
||||||
|
val isHighlighting = referencesInOriginalFile.isNotEmpty()
|
||||||
|
highlightElements(project, editor, referencesInOriginalFile)
|
||||||
|
|
||||||
|
val codeToInline: CodeToInline
|
||||||
|
val assignment: KtBinaryExpression?
|
||||||
|
if (getter == null) {
|
||||||
|
val initialization = extractInitialization(declaration, referenceExpressions, project, editor) ?: return
|
||||||
|
codeToInline = buildCodeToInline(declaration, initialization.value)
|
||||||
|
assignment = initialization.assignment
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val descriptor = declaration.resolveToDescriptor() as PropertyDescriptor
|
||||||
|
codeToInline = buildCodeToInline(getter, descriptor.type, editor) ?: return
|
||||||
|
assignment = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foreignUsages.isNotEmpty()) {
|
||||||
|
val conflicts = MultiMap<PsiElement, String>().apply {
|
||||||
|
putValue(null, "Property '$name' has non-Kotlin usages. They won't be processed by the Inline refactoring.")
|
||||||
|
foreignUsages.forEach { putValue(it, it.text) }
|
||||||
|
}
|
||||||
|
project.checkConflictsInteractively(conflicts) { performRefactoring(declaration, codeToInline, editor, assignment, isHighlighting) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
performRefactoring(declaration, codeToInline, editor, assignment, isHighlighting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Usages(val referenceExpressions: Collection<KtExpression>, val foreignUsages: Collection<PsiElement>)
|
||||||
|
|
||||||
|
private fun findUsages(declaration: KtProperty): Usages {
|
||||||
val references = ReferencesSearch.search(declaration)
|
val references = ReferencesSearch.search(declaration)
|
||||||
val referenceExpressions = mutableListOf<KtExpression>()
|
val referenceExpressions = mutableListOf<KtExpression>()
|
||||||
val foreignUsages = mutableListOf<PsiElement>()
|
val foreignUsages = mutableListOf<PsiElement>()
|
||||||
@@ -81,59 +135,41 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
}
|
}
|
||||||
referenceExpressions.addIfNotNull((refElement as? KtExpression)?.getQualifiedExpressionForSelectorOrThis())
|
referenceExpressions.addIfNotNull((refElement as? KtExpression)?.getQualifiedExpressionForSelectorOrThis())
|
||||||
}
|
}
|
||||||
|
return Usages(referenceExpressions, foreignUsages)
|
||||||
|
}
|
||||||
|
|
||||||
if (referenceExpressions.isEmpty()) {
|
private data class Initialization(val value: KtExpression, val assignment: KtBinaryExpression?)
|
||||||
val kind = if (declaration.isLocal) "Variable" else "Property"
|
|
||||||
return showErrorHint(project, editor, "$kind '$name' is never used")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private fun extractInitialization(
|
||||||
|
declaration: KtProperty,
|
||||||
|
referenceExpressions: Collection<KtExpression>,
|
||||||
|
project: Project,
|
||||||
|
editor: Editor?
|
||||||
|
): Initialization? {
|
||||||
val writeUsages = referenceExpressions.filter { it.readWriteAccess(useResolveForReadWrite = true) != ReferenceAccess.READ }
|
val writeUsages = referenceExpressions.filter { it.readWriteAccess(useResolveForReadWrite = true) != ReferenceAccess.READ }
|
||||||
|
|
||||||
val initializerInDeclaration = declaration.initializer
|
val initializerInDeclaration = declaration.initializer
|
||||||
val initializer: KtExpression
|
|
||||||
val assignment: KtBinaryExpression?
|
|
||||||
if (initializerInDeclaration != null) {
|
if (initializerInDeclaration != null) {
|
||||||
if (!writeUsages.isEmpty()) {
|
if (!writeUsages.isEmpty()) {
|
||||||
return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
reportAmbiguousAssignment(project, editor, declaration.name!!, writeUsages)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
initializer = initializerInDeclaration
|
return Initialization(initializerInDeclaration, assignment = null)
|
||||||
assignment = null
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assignment = writeUsages.singleOrNull()
|
val assignment = writeUsages.singleOrNull()
|
||||||
?.getAssignmentByLHS()
|
?.getAssignmentByLHS()
|
||||||
?.takeIf { it.operationToken == KtTokens.EQ }
|
?.takeIf { it.operationToken == KtTokens.EQ }
|
||||||
initializer = assignment?.right
|
val initializer = assignment?.right
|
||||||
?: return reportAmbiguousAssignment(project, editor, name, writeUsages)
|
if (initializer == null) {
|
||||||
}
|
reportAmbiguousAssignment(project, editor, declaration.name!!, writeUsages)
|
||||||
|
return null
|
||||||
val referencesInOriginalFile = referenceExpressions.filter { it.containingFile == file }
|
|
||||||
val isHighlighting = referencesInOriginalFile.isNotEmpty()
|
|
||||||
highlightElements(project, editor, referencesInOriginalFile)
|
|
||||||
|
|
||||||
if (referencesInOriginalFile.size != referenceExpressions.size) {
|
|
||||||
preProcessInternalUsages(initializer, referenceExpressions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foreignUsages.isNotEmpty()) {
|
|
||||||
val conflicts = MultiMap<PsiElement, String>().apply {
|
|
||||||
putValue(null, "Property '$name' has non-Kotlin usages. They won't be processed by the Inline refactoring.")
|
|
||||||
foreignUsages.forEach { putValue(it, it.text) }
|
|
||||||
}
|
}
|
||||||
project.checkConflictsInteractively(conflicts) { performRefactoring(declaration, initializer, editor, assignment, isHighlighting) }
|
return Initialization(initializer, assignment)
|
||||||
}
|
|
||||||
else {
|
|
||||||
performRefactoring(declaration, initializer, editor, assignment, isHighlighting)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun performRefactoring(
|
private fun buildCodeToInline(declaration: KtProperty, initializer: KtExpression): CodeToInline {
|
||||||
declaration: KtProperty,
|
|
||||||
initializer: KtExpression,
|
|
||||||
editor: Editor?,
|
|
||||||
assignment: KtBinaryExpression?,
|
|
||||||
isHighlighting: Boolean
|
|
||||||
) {
|
|
||||||
val descriptor = declaration.resolveToDescriptor() as VariableDescriptor
|
val descriptor = declaration.resolveToDescriptor() as VariableDescriptor
|
||||||
val expectedType = if (declaration.typeReference != null)
|
val expectedType = if (declaration.typeReference != null)
|
||||||
descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE
|
descriptor.returnType ?: TypeUtils.NO_EXPECTED_TYPE
|
||||||
@@ -147,11 +183,21 @@ class KotlinInlineValHandler : InlineActionHandler() {
|
|||||||
expectedType = expectedType)
|
expectedType = expectedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) } as? KtSimpleNameReference
|
val codeToInlineBuilder = CodeToInlineBuilder(descriptor, declaration.getResolutionFacade())
|
||||||
val replacementBuilder = CodeToInlineBuilder(descriptor, declaration.getResolutionFacade())
|
return codeToInlineBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
||||||
val replacement = replacementBuilder.prepareCodeToInline(initializerCopy, emptyList(), ::analyzeInitializerCopy)
|
}
|
||||||
|
|
||||||
|
private fun performRefactoring(
|
||||||
|
declaration: KtProperty,
|
||||||
|
replacement: CodeToInline,
|
||||||
|
editor: Editor?,
|
||||||
|
assignment: KtBinaryExpression?,
|
||||||
|
isHighlighting: Boolean
|
||||||
|
) {
|
||||||
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
||||||
|
|
||||||
|
val reference = editor?.let { TargetElementUtil.findReference(it, it.caretModel.offset) } as? KtSimpleNameReference
|
||||||
|
|
||||||
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignment)
|
val dialog = KotlinInlineValDialog(declaration, reference, replacementStrategy, assignment)
|
||||||
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
|||||||
@@ -25,16 +25,29 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
import com.intellij.refactoring.util.RefactoringMessageDialog
|
import com.intellij.refactoring.util.RefactoringMessageDialog
|
||||||
import com.intellij.usageView.UsageInfo
|
import com.intellij.usageView.UsageInfo
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.codeInliner.CodeToInline
|
||||||
|
import org.jetbrains.kotlin.idea.codeInliner.CodeToInlineBuilder
|
||||||
|
import org.jetbrains.kotlin.idea.core.copied
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.ContainerChangeInfo
|
import org.jetbrains.kotlin.idea.refactoring.move.ContainerChangeInfo
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.ContainerInfo
|
import org.jetbrains.kotlin.idea.refactoring.move.ContainerInfo
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.processInternalReferencesToUpdateOnPackageNameChange
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages
|
import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.move.processInternalReferencesToUpdateOnPackageNameChange
|
||||||
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun highlightElements(project: Project, editor: Editor?, elements: List<PsiElement>) {
|
fun highlightElements(project: Project, editor: Editor?, elements: List<PsiElement>) {
|
||||||
@@ -76,7 +89,7 @@ fun showDialog(
|
|||||||
internal var KtSimpleNameExpression.internalUsageInfos: MutableMap<FqName, (KtSimpleNameExpression) -> UsageInfo?>?
|
internal var KtSimpleNameExpression.internalUsageInfos: MutableMap<FqName, (KtSimpleNameExpression) -> UsageInfo?>?
|
||||||
by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFOS"))
|
by CopyableUserDataProperty(Key.create("INTERNAL_USAGE_INFOS"))
|
||||||
|
|
||||||
internal fun preProcessInternalUsages(element: KtElement, usages: List<KtElement>) {
|
internal fun preProcessInternalUsages(element: KtElement, usages: Collection<KtElement>) {
|
||||||
val mainFile = element.containingKtFile
|
val mainFile = element.containingKtFile
|
||||||
val targetPackages = usages.mapNotNullTo(LinkedHashSet()) { it.containingKtFile.packageFqName }
|
val targetPackages = usages.mapNotNullTo(LinkedHashSet()) { it.containingKtFile.packageFqName }
|
||||||
for (targetPackage in targetPackages) {
|
for (targetPackage in targetPackages) {
|
||||||
@@ -99,4 +112,49 @@ internal fun <E : KtElement> postProcessInternalReferences(inlinedElement: E): E
|
|||||||
expressionsToProcess.forEach { it.internalUsageInfos = null }
|
expressionsToProcess.forEach { it.internalUsageInfos = null }
|
||||||
postProcessMoveUsages(internalUsages)
|
postProcessMoveUsages(internalUsages)
|
||||||
return pointer.element
|
return pointer.element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun buildCodeToInline(declaration: KtDeclarationWithBody, returnType: KotlinType?, editor: Editor?): CodeToInline? {
|
||||||
|
val bodyExpression = declaration.bodyExpression!!
|
||||||
|
val bodyCopy = bodyExpression.copied()
|
||||||
|
|
||||||
|
val expectedType = if (!declaration.hasBlockBody() && declaration.hasDeclaredReturnType())
|
||||||
|
returnType ?: TypeUtils.NO_EXPECTED_TYPE
|
||||||
|
else
|
||||||
|
TypeUtils.NO_EXPECTED_TYPE
|
||||||
|
|
||||||
|
fun analyzeBodyCopy(): BindingContext {
|
||||||
|
return bodyCopy.analyzeInContext(bodyExpression.getResolutionScope(),
|
||||||
|
contextExpression = bodyExpression,
|
||||||
|
expectedType = expectedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val descriptor = declaration.resolveToDescriptor()
|
||||||
|
val builder = CodeToInlineBuilder(descriptor as CallableDescriptor, declaration.getResolutionFacade())
|
||||||
|
if (declaration.hasBlockBody()) {
|
||||||
|
bodyCopy as KtBlockExpression
|
||||||
|
val statements = bodyCopy.statements
|
||||||
|
|
||||||
|
val returnStatements = bodyCopy.collectDescendantsOfType<KtReturnExpression> {
|
||||||
|
it.getLabelName().let { it == null || it == declaration.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastReturn = statements.lastOrNull() as? KtReturnExpression
|
||||||
|
if (returnStatements.any { it != lastReturn }) {
|
||||||
|
val message = RefactoringBundle.getCannotRefactorMessage(
|
||||||
|
if (returnStatements.size > 1)
|
||||||
|
"Inline Function is not supported for functions with multiple return statements."
|
||||||
|
else
|
||||||
|
"Inline Function is not supported for functions with return statements not at the end of the body."
|
||||||
|
)
|
||||||
|
CommonRefactoringUtil.showErrorHint(declaration.project, editor, message, "Inline Function", null)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.prepareCodeToInline(lastReturn?.returnedExpression,
|
||||||
|
statements.dropLast(returnStatements.size), ::analyzeBodyCopy)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return builder.prepareCodeToInline(bodyCopy, emptyList(), ::analyzeBodyCopy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
val Int.C = 239
|
|
||||||
|
|
||||||
// not implemented yet
|
|
||||||
|
|
||||||
fun f() {
|
|
||||||
println(5.<caret>C)
|
|
||||||
}
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
val C: Int
|
|
||||||
get() = 239
|
|
||||||
|
|
||||||
// not implemented yet
|
|
||||||
|
|
||||||
fun f() {
|
|
||||||
println(<caret>C)
|
|
||||||
}
|
|
||||||
Vendored
-9
@@ -1,9 +0,0 @@
|
|||||||
val C = 239
|
|
||||||
get() = $C + 1
|
|
||||||
|
|
||||||
|
|
||||||
// not implemented yet
|
|
||||||
|
|
||||||
fun f() {
|
|
||||||
println(<caret>C)
|
|
||||||
}
|
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
val <caret>property: Int
|
||||||
|
get {
|
||||||
|
println("access!")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
println(property)
|
||||||
|
}
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo() {
|
||||||
|
println("access!")
|
||||||
|
println(1)
|
||||||
|
}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val <caret>property: Int
|
||||||
|
get() = Random().nextInt()
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
println(property)
|
||||||
|
}
|
||||||
idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors/ExpressionBody.kt.after
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
println(Random().nextInt())
|
||||||
|
}
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
val String.<caret>property: Int
|
||||||
|
get() = length * 2
|
||||||
|
|
||||||
|
fun String.foo() {
|
||||||
|
println("a".property)
|
||||||
|
println(property)
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun String.foo() {
|
||||||
|
println("a".length * 2)
|
||||||
|
println(length * 2)
|
||||||
|
}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
// ERROR: Cannot inline property with accessor(s) and backing field
|
||||||
|
|
||||||
|
val C = 239
|
||||||
|
get() = field + 1
|
||||||
|
|
||||||
|
fun f() {
|
||||||
|
println(<caret>C)
|
||||||
|
}
|
||||||
@@ -65,9 +65,9 @@ abstract class AbstractInlineTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e: CommonRefactoringUtil.RefactoringErrorHintException) {
|
catch (e: CommonRefactoringUtil.RefactoringErrorHintException) {
|
||||||
TestCase.assertFalse(afterFileExists)
|
TestCase.assertFalse("Refactoring not available: ${e.message}", afterFileExists)
|
||||||
TestCase.assertEquals(1, expectedErrors.size)
|
TestCase.assertEquals("Expected errors", 1, expectedErrors.size)
|
||||||
TestCase.assertEquals(expectedErrors[0].replace("\\n", "\n"), e.message)
|
TestCase.assertEquals("Error message", expectedErrors[0].replace("\\n", "\n"), e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -994,12 +994,6 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("ExtensionProperty.kt")
|
|
||||||
public void testExtensionProperty() throws Exception {
|
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/ExtensionProperty.kt");
|
|
||||||
doTest(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestMetadata("InstanceProperty.kt")
|
@TestMetadata("InstanceProperty.kt")
|
||||||
public void testInstanceProperty() throws Exception {
|
public void testInstanceProperty() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/InstanceProperty.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/InstanceProperty.kt");
|
||||||
@@ -1048,16 +1042,37 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("WithGetter.kt")
|
@TestMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors")
|
||||||
public void testWithGetter() throws Exception {
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/WithGetter.kt");
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
doTest(fileName);
|
public static class Accessors extends AbstractInlineTest {
|
||||||
}
|
public void testAllFilesPresentInAccessors() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors"), Pattern.compile("^(\\w+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("WithInitializerAndGetter.kt")
|
@TestMetadata("BlockBody.kt")
|
||||||
public void testWithInitializerAndGetter() throws Exception {
|
public void testBlockBody() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/WithInitializerAndGetter.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors/BlockBody.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ExpressionBody.kt")
|
||||||
|
public void testExpressionBody() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors/ExpressionBody.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ExtensionProperty.kt")
|
||||||
|
public void testExtensionProperty() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors/ExtensionProperty.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("WithInitializer.kt")
|
||||||
|
public void testWithInitializer() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/inlineVariableOrProperty/property/accessors/WithInitializer.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user