Fixed intentions that used not valid PSI

Fixed ConvertToMemberIntention and ObjectLiteralToLambdaIntention that are used PSI after lazy block reparse.
This commit is contained in:
Igor Yakovlev
2019-04-01 17:24:54 +03:00
parent 1f29b42cd3
commit 1178e94f50
2 changed files with 72 additions and 36 deletions
@@ -112,10 +112,10 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention<KtObjectLiter
}
override fun applyTo(element: KtObjectLiteralExpression, editor: Editor?) {
val commentSaver = CommentSaver(element)
val (_, baseType, singleFunction) = extractData(element)!!
val commentSaver = CommentSaver(element)
val returnSaver = ReturnSaver(singleFunction)
val body = singleFunction.bodyExpression!!
@@ -157,14 +157,13 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention<KtObjectLiter
}
val replaced = element.replaced(newExpression)
commentSaver.restore(replaced, forceAdjustIndent = true/* by some reason lambda body is sometimes not properly indented */)
val callee = replaced.getCalleeExpressionIfAny()!! as KtNameReferenceExpression
val callExpression = callee.parent as KtCallExpression
val functionLiteral = callExpression.lambdaArguments.single().getLambdaExpression()!!
val returnLabel = callee.getReferencedNameAsName()
returnSaver.restore(functionLiteral, returnLabel)
commentSaver.restore(replaced, forceAdjustIndent = true/* by some reason lambda body is sometimes not properly indented */)
val parentCall = ((replaced.parent as? KtValueArgument)
?.parent as? KtValueArgumentList)
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.intentions.declarations
import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.ui.Messages
@@ -47,6 +48,8 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.utils.addIfNotNull
private val LOG = Logger.getInstance(ConvertMemberToExtensionIntention::class.java)
class ConvertMemberToExtensionIntention :
SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"),
LowPriorityAction {
@@ -81,34 +84,71 @@ class ConvertMemberToExtensionIntention :
}
runWriteAction {
val (extension, bodyToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected)
val (extension, bodyTypeToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected)
editor?.apply {
unblockDocument()
if (bodyToSelect != null) {
val range = bodyToSelect.textRange
moveCaret(range.startOffset, ScrollType.CENTER)
if (extension.isValid) {
val parent = bodyToSelect.parent
val lastSibling =
if (parent is KtBlockExpression)
parent.rBrace?.siblings(forward = false, withItself = false)?.first { it !is PsiWhiteSpace }
else
bodyToSelect.siblings(forward = true, withItself = false).lastOrNull()
val endOffset = lastSibling?.endOffset ?: range.endOffset
selectionModel.setSelection(range.startOffset, endOffset)
if (bodyTypeToSelect != GeneratedBodyType.NOTHING) {
val bodyToSelect = getBodyForSelection(extension, bodyTypeToSelect)
if (bodyToSelect != null) {
val range = bodyToSelect.textRange
moveCaret(range.startOffset, ScrollType.CENTER)
val parent = bodyToSelect.parent
val lastSibling =
if (parent is KtBlockExpression)
parent.rBrace?.siblings(forward = false, withItself = false)?.first { it !is PsiWhiteSpace }
else
bodyToSelect.siblings(forward = true, withItself = false).lastOrNull()
val endOffset = lastSibling?.endOffset ?: range.endOffset
selectionModel.setSelection(range.startOffset, endOffset)
} else {
LOG.error("Extension created with new method body for $bodyToSelect but this body was not found after document commit. Extension text: \"${extension.text}\"")
moveCaret(extension.textOffset, ScrollType.CENTER)
}
} else {
moveCaret(extension.textOffset, ScrollType.CENTER)
}
} else {
moveCaret(extension.textOffset, ScrollType.CENTER)
LOG.error("Extension invalidated during document commit. Extension text \"${extension.text}\"")
}
}
}
}
private fun getBodyForSelection(extension: KtCallableDeclaration, bodyTypeToSelect: GeneratedBodyType): KtExpression? {
fun selectBody(declaration: KtDeclarationWithBody): KtExpression? {
if (!declaration.hasBody()) return extension
return declaration.bodyExpression?.let {
(it as? KtBlockExpression)?.statements?.singleOrNull() ?: it
}
}
return when (bodyTypeToSelect) {
GeneratedBodyType.FUNCTION -> (extension as? KtFunction)?.let { selectBody(it) }
GeneratedBodyType.GETTER -> (extension as? KtProperty)?.getter?.let { selectBody(it) }
GeneratedBodyType.SETTER -> (extension as? KtProperty)?.setter?.let { selectBody(it) }
else -> null
}
}
private enum class GeneratedBodyType {
NOTHING,
FUNCTION,
SETTER,
GETTER
}
private fun processSingleDeclaration(
element: KtCallableDeclaration,
allowExpected: Boolean
): Pair<KtCallableDeclaration, KtExpression?> {
): Pair<KtCallableDeclaration, GeneratedBodyType> {
val descriptor = element.unsafeResolveToDescriptor()
val containingClass = descriptor.containingDeclaration as ClassDescriptor
@@ -161,14 +201,7 @@ class ConvertMemberToExtensionIntention :
extension.addModifier(KtTokens.EXPECT_KEYWORD)
}
var bodyToSelect: KtExpression? = null
fun selectBody(declaration: KtDeclarationWithBody) {
if (bodyToSelect == null) {
val body = declaration.bodyExpression
bodyToSelect = (body as? KtBlockExpression)?.statements?.single() ?: body
}
}
var bodyTypeToSelect = GeneratedBodyType.NOTHING
val bodyText = getFunctionBodyTextFromTemplate(
project,
@@ -183,7 +216,7 @@ class ConvertMemberToExtensionIntention :
if (!extension.hasBody() && !isEffectivelyExpected) {
//TODO: methods in PSI for setBody
extension.add(psiFactory.createBlock(bodyText))
selectBody(extension)
bodyTypeToSelect = GeneratedBodyType.FUNCTION
}
}
@@ -198,10 +231,10 @@ class ConvertMemberToExtensionIntention :
if (getter == null) {
getter = extension.addAfter(templateGetter, extension.typeReference) as KtPropertyAccessor
extension.addBefore(psiFactory.createNewLine(), getter)
selectBody(getter)
bodyTypeToSelect = GeneratedBodyType.GETTER
} else if (!getter.hasBody()) {
getter = getter.replace(templateGetter) as KtPropertyAccessor
selectBody(getter)
bodyTypeToSelect = GeneratedBodyType.GETTER
}
if (extension.isVar) {
@@ -209,10 +242,14 @@ class ConvertMemberToExtensionIntention :
if (setter == null) {
setter = extension.addAfter(templateSetter, getter) as KtPropertyAccessor
extension.addBefore(psiFactory.createNewLine(), setter)
selectBody(setter)
if (bodyTypeToSelect == GeneratedBodyType.NOTHING) {
bodyTypeToSelect = GeneratedBodyType.SETTER
}
} else if (!setter.hasBody()) {
setter = setter.replace(templateSetter) as KtPropertyAccessor
selectBody(setter)
setter.replace(templateSetter) as KtPropertyAccessor
if (bodyTypeToSelect == GeneratedBodyType.NOTHING) {
bodyTypeToSelect = GeneratedBodyType.SETTER
}
}
}
}
@@ -240,7 +277,7 @@ class ConvertMemberToExtensionIntention :
}
}
return extension to bodyToSelect
return extension to bodyTypeToSelect
}
private fun askIfExpectedIsAllowed(file: KtFile): Boolean {
@@ -258,7 +295,7 @@ class ConvertMemberToExtensionIntention :
private fun createExtensionCallableAndPrepareBodyToSelect(
element: KtCallableDeclaration,
allowExpected: Boolean = true
): Pair<KtCallableDeclaration, KtExpression?> {
): Pair<KtCallableDeclaration, GeneratedBodyType> {
val expectedDeclaration = element.liftToExpected() as? KtCallableDeclaration
if (expectedDeclaration != null) {
element.withExpectedActuals().filterIsInstance<KtCallableDeclaration>().forEach {
@@ -269,11 +306,11 @@ class ConvertMemberToExtensionIntention :
}
val classVisibility = element.containingClass()?.visibilityModifierType()
val (extension, bodyToSelect) = processSingleDeclaration(element, allowExpected)
val (extension, bodyTypeToSelect) = processSingleDeclaration(element, allowExpected)
if (classVisibility != null && extension.visibilityModifier() == null) {
extension.addModifier(classVisibility)
}
return extension to bodyToSelect
return extension to bodyTypeToSelect
}
private fun newTypeParameterList(member: KtCallableDeclaration): KtTypeParameterList? {
@@ -290,4 +327,4 @@ class ConvertMemberToExtensionIntention :
return ConvertMemberToExtensionIntention().createExtensionCallableAndPrepareBodyToSelect(element).first
}
}
}
}