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?) { override fun applyTo(element: KtObjectLiteralExpression, editor: Editor?) {
val commentSaver = CommentSaver(element)
val (_, baseType, singleFunction) = extractData(element)!! val (_, baseType, singleFunction) = extractData(element)!!
val commentSaver = CommentSaver(element)
val returnSaver = ReturnSaver(singleFunction) val returnSaver = ReturnSaver(singleFunction)
val body = singleFunction.bodyExpression!! val body = singleFunction.bodyExpression!!
@@ -157,14 +157,13 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention<KtObjectLiter
} }
val replaced = element.replaced(newExpression) 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 callee = replaced.getCalleeExpressionIfAny()!! as KtNameReferenceExpression
val callExpression = callee.parent as KtCallExpression val callExpression = callee.parent as KtCallExpression
val functionLiteral = callExpression.lambdaArguments.single().getLambdaExpression()!! val functionLiteral = callExpression.lambdaArguments.single().getLambdaExpression()!!
val returnLabel = callee.getReferencedNameAsName() val returnLabel = callee.getReferencedNameAsName()
returnSaver.restore(functionLiteral, returnLabel) 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) val parentCall = ((replaced.parent as? KtValueArgument)
?.parent as? KtValueArgumentList) ?.parent as? KtValueArgumentList)
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.intentions.declarations
import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.ui.Messages 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.psi.psiUtil.*
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
private val LOG = Logger.getInstance(ConvertMemberToExtensionIntention::class.java)
class ConvertMemberToExtensionIntention : class ConvertMemberToExtensionIntention :
SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"), SelfTargetingRangeIntention<KtCallableDeclaration>(KtCallableDeclaration::class.java, "Convert member to extension"),
LowPriorityAction { LowPriorityAction {
@@ -81,34 +84,71 @@ class ConvertMemberToExtensionIntention :
} }
runWriteAction { runWriteAction {
val (extension, bodyToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected) val (extension, bodyTypeToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected)
editor?.apply { editor?.apply {
unblockDocument() unblockDocument()
if (bodyToSelect != null) { if (extension.isValid) {
val range = bodyToSelect.textRange
moveCaret(range.startOffset, ScrollType.CENTER)
val parent = bodyToSelect.parent if (bodyTypeToSelect != GeneratedBodyType.NOTHING) {
val lastSibling = val bodyToSelect = getBodyForSelection(extension, bodyTypeToSelect)
if (parent is KtBlockExpression)
parent.rBrace?.siblings(forward = false, withItself = false)?.first { it !is PsiWhiteSpace } if (bodyToSelect != null) {
else val range = bodyToSelect.textRange
bodyToSelect.siblings(forward = true, withItself = false).lastOrNull() moveCaret(range.startOffset, ScrollType.CENTER)
val endOffset = lastSibling?.endOffset ?: range.endOffset
selectionModel.setSelection(range.startOffset, endOffset) 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 { } 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( private fun processSingleDeclaration(
element: KtCallableDeclaration, element: KtCallableDeclaration,
allowExpected: Boolean allowExpected: Boolean
): Pair<KtCallableDeclaration, KtExpression?> { ): Pair<KtCallableDeclaration, GeneratedBodyType> {
val descriptor = element.unsafeResolveToDescriptor() val descriptor = element.unsafeResolveToDescriptor()
val containingClass = descriptor.containingDeclaration as ClassDescriptor val containingClass = descriptor.containingDeclaration as ClassDescriptor
@@ -161,14 +201,7 @@ class ConvertMemberToExtensionIntention :
extension.addModifier(KtTokens.EXPECT_KEYWORD) extension.addModifier(KtTokens.EXPECT_KEYWORD)
} }
var bodyToSelect: KtExpression? = null var bodyTypeToSelect = GeneratedBodyType.NOTHING
fun selectBody(declaration: KtDeclarationWithBody) {
if (bodyToSelect == null) {
val body = declaration.bodyExpression
bodyToSelect = (body as? KtBlockExpression)?.statements?.single() ?: body
}
}
val bodyText = getFunctionBodyTextFromTemplate( val bodyText = getFunctionBodyTextFromTemplate(
project, project,
@@ -183,7 +216,7 @@ class ConvertMemberToExtensionIntention :
if (!extension.hasBody() && !isEffectivelyExpected) { if (!extension.hasBody() && !isEffectivelyExpected) {
//TODO: methods in PSI for setBody //TODO: methods in PSI for setBody
extension.add(psiFactory.createBlock(bodyText)) extension.add(psiFactory.createBlock(bodyText))
selectBody(extension) bodyTypeToSelect = GeneratedBodyType.FUNCTION
} }
} }
@@ -198,10 +231,10 @@ class ConvertMemberToExtensionIntention :
if (getter == null) { if (getter == null) {
getter = extension.addAfter(templateGetter, extension.typeReference) as KtPropertyAccessor getter = extension.addAfter(templateGetter, extension.typeReference) as KtPropertyAccessor
extension.addBefore(psiFactory.createNewLine(), getter) extension.addBefore(psiFactory.createNewLine(), getter)
selectBody(getter) bodyTypeToSelect = GeneratedBodyType.GETTER
} else if (!getter.hasBody()) { } else if (!getter.hasBody()) {
getter = getter.replace(templateGetter) as KtPropertyAccessor getter = getter.replace(templateGetter) as KtPropertyAccessor
selectBody(getter) bodyTypeToSelect = GeneratedBodyType.GETTER
} }
if (extension.isVar) { if (extension.isVar) {
@@ -209,10 +242,14 @@ class ConvertMemberToExtensionIntention :
if (setter == null) { if (setter == null) {
setter = extension.addAfter(templateSetter, getter) as KtPropertyAccessor setter = extension.addAfter(templateSetter, getter) as KtPropertyAccessor
extension.addBefore(psiFactory.createNewLine(), setter) extension.addBefore(psiFactory.createNewLine(), setter)
selectBody(setter) if (bodyTypeToSelect == GeneratedBodyType.NOTHING) {
bodyTypeToSelect = GeneratedBodyType.SETTER
}
} else if (!setter.hasBody()) { } else if (!setter.hasBody()) {
setter = setter.replace(templateSetter) as KtPropertyAccessor setter.replace(templateSetter) as KtPropertyAccessor
selectBody(setter) 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 { private fun askIfExpectedIsAllowed(file: KtFile): Boolean {
@@ -258,7 +295,7 @@ class ConvertMemberToExtensionIntention :
private fun createExtensionCallableAndPrepareBodyToSelect( private fun createExtensionCallableAndPrepareBodyToSelect(
element: KtCallableDeclaration, element: KtCallableDeclaration,
allowExpected: Boolean = true allowExpected: Boolean = true
): Pair<KtCallableDeclaration, KtExpression?> { ): Pair<KtCallableDeclaration, GeneratedBodyType> {
val expectedDeclaration = element.liftToExpected() as? KtCallableDeclaration val expectedDeclaration = element.liftToExpected() as? KtCallableDeclaration
if (expectedDeclaration != null) { if (expectedDeclaration != null) {
element.withExpectedActuals().filterIsInstance<KtCallableDeclaration>().forEach { element.withExpectedActuals().filterIsInstance<KtCallableDeclaration>().forEach {
@@ -269,11 +306,11 @@ class ConvertMemberToExtensionIntention :
} }
val classVisibility = element.containingClass()?.visibilityModifierType() val classVisibility = element.containingClass()?.visibilityModifierType()
val (extension, bodyToSelect) = processSingleDeclaration(element, allowExpected) val (extension, bodyTypeToSelect) = processSingleDeclaration(element, allowExpected)
if (classVisibility != null && extension.visibilityModifier() == null) { if (classVisibility != null && extension.visibilityModifier() == null) {
extension.addModifier(classVisibility) extension.addModifier(classVisibility)
} }
return extension to bodyToSelect return extension to bodyTypeToSelect
} }
private fun newTypeParameterList(member: KtCallableDeclaration): KtTypeParameterList? { private fun newTypeParameterList(member: KtCallableDeclaration): KtTypeParameterList? {
@@ -290,4 +327,4 @@ class ConvertMemberToExtensionIntention :
return ConvertMemberToExtensionIntention().createExtensionCallableAndPrepareBodyToSelect(element).first return ConvertMemberToExtensionIntention().createExtensionCallableAndPrepareBodyToSelect(element).first
} }
} }
} }