Extract Function: Forbid inappropriate declarations inside extractable fragment

#KT-20467 Fixed
 #KT-20469 Fixed
This commit is contained in:
Alexey Sedunov
2018-06-07 14:57:49 +03:00
parent f7cda61b08
commit 16dbc6fb4d
13 changed files with 43 additions and 5 deletions
@@ -28,9 +28,9 @@ import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ui.KotlinExtractFunctionDialog
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling
import org.jetbrains.kotlin.idea.refactoring.introduce.validateExpressionElements
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.*
class ExtractKotlinFunctionHandler(
private val allContainersEnabled: Boolean = false,
@@ -69,6 +69,7 @@ class ExtractKotlinFunctionHandler(
file,
"Select target code block",
listOf(CodeInsightUtils.ElementKind.EXPRESSION),
::validateExpressionElements,
{ elements, parent -> parent.getExtractionContainers(elements.size == 1, allContainersEnabled) },
continuation
)
@@ -187,6 +187,7 @@ fun selectNewParameterContext(
file = file,
title = "Introduce parameter to declaration",
elementKinds = listOf(CodeInsightUtils.ElementKind.EXPRESSION),
elementValidator = ::validateExpressionElements,
getContainers = { _, parent ->
val parents = parent.parents
val stopAt = (parent.parents.zip(parent.parents.drop(1)))
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetSibling
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHint
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey
import org.jetbrains.kotlin.idea.refactoring.introduce.validateExpressionElements
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtClassBody
@@ -73,6 +74,7 @@ class KotlinIntroducePropertyHandler(
file,
"Select target code block",
listOf(CodeInsightUtils.ElementKind.EXPRESSION),
::validateExpressionElements,
{ _, parent ->
parent.getExtractionContainers(strict = true, includeAll = true).filter { it is KtClassBody || (it is KtFile && !it.isScript()) }
},
@@ -55,6 +55,7 @@ open class KotlinIntroduceTypeAliasHandler : RefactoringActionHandler {
file,
"Select target code block",
listOf(TYPE_ELEMENT, TYPE_CONSTRUCTOR),
{ null },
{ _, parent -> listOf(parent.containingFile) },
continuation
)
@@ -74,6 +74,7 @@ object KotlinIntroduceTypeParameterHandler : RefactoringActionHandler {
file,
"Introduce type parameter to declaration",
listOf(CodeInsightUtils.ElementKind.TYPE_ELEMENT),
{ null },
{ _, parent -> getPossibleTypeParameterContainers(parent) },
continuation
)
@@ -47,6 +47,7 @@ fun selectElementsWithTargetSibling(
file: KtFile,
title: String,
elementKinds: Collection<CodeInsightUtils.ElementKind>,
elementValidator: (List<PsiElement>) -> String?,
getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>,
continuation: (elements: List<PsiElement>, targetSibling: PsiElement) -> Unit
) {
@@ -69,7 +70,7 @@ fun selectElementsWithTargetSibling(
continuation(elements, outermostParent)
}
selectElementsWithTargetParent(operationName, editor, file, title, elementKinds, getContainers, ::onSelectionComplete)
selectElementsWithTargetParent(operationName, editor, file, title, elementKinds, elementValidator, getContainers, ::onSelectionComplete)
}
fun selectElementsWithTargetParent(
@@ -78,6 +79,7 @@ fun selectElementsWithTargetParent(
file: KtFile,
title: String,
elementKinds: Collection<CodeInsightUtils.ElementKind>,
elementValidator: (List<PsiElement>) -> String?,
getContainers: (elements: List<PsiElement>, commonParent: PsiElement) -> List<PsiElement>,
continuation: (elements: List<PsiElement>, targetParent: PsiElement) -> Unit
) {
@@ -86,6 +88,11 @@ fun selectElementsWithTargetParent(
}
fun selectTargetContainer(elements: List<PsiElement>) {
elementValidator(elements)?.let {
showErrorHint(file.project, editor, it, operationName)
return
}
val physicalElements = elements.map { it.substringContextOrThis }
val parent = PsiTreeUtil.findCommonParent(physicalElements)
?: throw AssertionError("Should have at least one parent: ${physicalElements.joinToString("\n")}")
@@ -236,4 +243,11 @@ fun <T : KtDeclaration> insertDeclaration(declaration: T, targetSibling: PsiElem
return (targetContainer.addBefore(declaration, anchor) as T).apply {
targetContainer.addBefore(KtPsiFactory(declaration).createWhiteSpace("\n\n"), anchor)
}
}
internal fun validateExpressionElements(elements: List<PsiElement>): String? {
if (elements.any { it is KtConstructor<*> || it is KtParameter || it is KtTypeAlias || it is KtPropertyAccessor }) {
return "Refactoring is not applicable to this code fragment"
}
return null
}
@@ -0,0 +1,4 @@
// SIBLING:
class Foo {
<selection>constructor(bar: Int)</selection>
}
@@ -0,0 +1 @@
Refactoring is not applicable to this code fragment
@@ -0,0 +1,2 @@
// SIBLING:
class Foo(<selection>bar: String = "baz"</selection>)
@@ -0,0 +1 @@
Refactoring is not applicable to this code fragment
@@ -1 +1 @@
Cannot refactor in this place
Cannot refactor in this place
@@ -1 +1 @@
Cannot refactor in this place
Cannot refactor in this place
@@ -986,6 +986,16 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
runTest("idea/testData/refactoring/extractFunction/basic/refInReturn.kt");
}
@TestMetadata("selectedConstructor.kt")
public void testSelectedConstructor() throws Exception {
runTest("idea/testData/refactoring/extractFunction/basic/selectedConstructor.kt");
}
@TestMetadata("selectedParameter.kt")
public void testSelectedParameter() throws Exception {
runTest("idea/testData/refactoring/extractFunction/basic/selectedParameter.kt");
}
@TestMetadata("suspendCall.kt")
public void testSuspendCall() throws Exception {
runTest("idea/testData/refactoring/extractFunction/basic/suspendCall.kt");