WhenWithOnlyElse: don't remove when subject variable if needed

#KT-30975 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-04-18 19:45:33 +09:00
committed by Dmitry Gridin
parent 7f1d30058a
commit e955dcfc14
44 changed files with 636 additions and 12 deletions
@@ -10,6 +10,8 @@ import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isElseIf
@@ -17,10 +19,7 @@ import org.jetbrains.kotlin.idea.intentions.branchedTransformations.unwrapBlockO
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
@@ -157,27 +156,79 @@ private fun KtExpression.enumEntry(): KtEnumEntry? {
}
fun KtExpression.replaceWithBranch(branch: KtExpression, isUsedAsExpression: Boolean, keepBraces: Boolean = false) {
val lastExpression = when {
branch !is KtBlockExpression -> replaced(branch)
val caretModel = findExistingEditor()?.caretModel
val subjectVariable = (this as? KtWhenExpression)?.subjectVariable?.let { it ->
if (it.annotationEntries.isNotEmpty()) return@let it
val initializer = it.initializer ?: return@let it
val references = ReferencesSearch.search(it, LocalSearchScope(this)).toList()
when (references.size) {
0 -> when (initializer) {
is KtSimpleNameExpression, is KtStringTemplateExpression, is KtConstantExpression -> null
else -> it
}
1 -> {
references.firstOrNull()?.element?.replace(initializer)
null
}
else -> it
}
}
val wrapSubjectVariableByRun = if (subjectVariable != null) {
val subjectVariableName = subjectVariable.nameAsName
val parentBlock = getStrictParentOfType<KtBlockExpression>()
parentBlock?.anyDescendantOfType<KtProperty> { it != subjectVariable && it.nameAsName == subjectVariableName } == true
} else {
false
}
val factory = KtPsiFactory(this)
val parent = this.parent
val replaced = when {
branch !is KtBlockExpression -> {
if (subjectVariable != null) {
if (isUsedAsExpression || wrapSubjectVariableByRun) {
replaced(KtPsiFactory(this).createExpressionByPattern("run { $0\n$1 }", subjectVariable, branch))
} else {
parent.addBefore(subjectVariable, this).also {
parent.addAfter(factory.createNewLine(), it)
replaced(branch)
}
}
} else {
replaced(branch)
}
}
isUsedAsExpression -> {
val factory = KtPsiFactory(this)
if (subjectVariable != null) {
branch.addAfter(factory.createNewLine(), branch.addBefore(subjectVariable, branch.statements.firstOrNull()))
}
replaced(factory.createExpressionByPattern("run $0", branch.text))
}
else -> {
val firstChildSibling = branch.firstChild.nextSibling
val lastChild = branch.lastChild
if (firstChildSibling != lastChild) {
val replaced = if (firstChildSibling != lastChild) {
if (keepBraces) {
parent.addAfter(branch, this)
} else {
parent.addRangeAfter(firstChildSibling, lastChild.prevSibling, this)
if (subjectVariable != null && wrapSubjectVariableByRun) {
branch.addAfter(subjectVariable, branch.lBrace)
parent.addAfter(KtPsiFactory(this).createExpression("run ${branch.text}"), this)
} else {
parent.addRangeAfter(firstChildSibling, lastChild.prevSibling, this)
subjectVariable?.let { parent.addBefore(subjectVariable, this) }
}
}
} else {
null
}
delete()
null
replaced
}
}
val caretModel = branch.findExistingEditor()?.caretModel
caretModel?.moveToOffset(lastExpression?.startOffset ?: return)
if (replaced != null) {
caretModel?.moveToOffset(replaced.startOffset)
}
}
@@ -0,0 +1,14 @@
fun test() {
when (val a = create()) {
else -> {
use(a, a)
foo()
}
}<caret>
}
fun create(): String = ""
fun use(s: String, t: String) {}
fun foo() {}
@@ -0,0 +1,11 @@
fun test() {
<caret>val a = create()
use(a, a)
foo()
}
fun create(): String = ""
fun use(s: String, t: String) {}
fun foo() {}
@@ -0,0 +1,14 @@
fun test() {
when (val a = create()) {
else -> {
use("")
foo()
}
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,11 @@
fun test() {
<caret>val a = create()
use("")
foo()
}
fun create(): String = ""
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,12 @@
fun test() {
when (val a = 42) {
else -> {
use("")
foo()
}
}<caret>
}
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,8 @@
fun test() {
<caret>use("")
foo()
}
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,14 @@
fun test() {
when (val a = create()) {
else -> {
use(a)
foo()
}
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,10 @@
fun test() {
<caret>use(create())
foo()
}
fun create(): String = ""
fun use(s: String) {}
fun foo() {}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> {
use(a, a)
}
}<caret>
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
val a = create()
use(a, a)
}
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> {
use("")
}
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
val a = create()
use("")
}
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = 42) {
else -> {
use("")
}
}<caret>
}
fun use(s: String) {}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
use("")
}
}
fun use(s: String) {}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> {
use(a)
}
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
use(create())
}
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,11 @@
fun hasAnnotation() {
when (@Bar val a = create()) {
else -> use(a)
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
annotation class Bar
@@ -0,0 +1,10 @@
fun hasAnnotation() {
<caret>@Bar val a = create()
use(a)
}
fun create(): String = ""
fun use(s: String) {}
annotation class Bar
@@ -0,0 +1,9 @@
// WITH_RUNTIME
fun test() {
<caret>when (val a = 42) {
else -> use(a, a)
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
<caret>run {
val a = 42
use(a, a)
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
fun test() {
<caret>when (val a = 42) {
else -> {
use(a, a)
use(a, a)
}
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
<caret>run {
val a = 42
use(a, a)
use(a, a)
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,9 @@
fun test() {
<caret>when (val a = 42) {
else -> use(a)
}
val a = 33
}
fun use(i: Int) {}
@@ -0,0 +1,7 @@
fun test() {
<caret>use(42)
val a = 33
}
fun use(i: Int) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = <caret>when (val a = 42) {
else -> use(a, a)
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
val a = 42
use(a, a)
}
val a = 33
}
fun use(i: Int, j: Int) {}
@@ -0,0 +1,9 @@
fun test() {
when (val a = create()) {
else -> use(a, a)
}<caret>
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,8 @@
fun test() {
<caret>val a = create()
use(a, a)
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> use(a, a)
}<caret>
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
val a = create()
use(a, a)
}
}
fun create(): String = ""
fun use(s: String, t: String) {}
@@ -0,0 +1,9 @@
fun test() {
when (val a = create()) {
else -> use("")
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,8 @@
fun test() {
<caret>val a = create()
use("")
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,7 @@
fun test() {
when (val a = 42) {
else -> use("")
}<caret>
}
fun use(s: String) {}
@@ -0,0 +1,5 @@
fun test() {
<caret>use("")
}
fun use(s: String) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> use("")
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val x = <caret>run {
val a = create()
use("")
}
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = 42) {
else -> use("")
}<caret>
}
fun use(s: String) {}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test() {
val x = <caret>use("")
}
fun use(s: String) {}
@@ -0,0 +1,9 @@
fun test() {
when (val a = create()) {
else -> use(a)
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,7 @@
fun test() {
<caret>use(create())
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test() {
val x = when (val a = create()) {
else -> use(a)
}<caret>
}
fun create(): String = ""
fun use(s: String) {}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test() {
val x = <caret>use(create())
}
fun create(): String = ""
fun use(s: String) {}
@@ -10314,6 +10314,163 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
public void testSimpleExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/simpleExpression.kt");
}
@TestMetadata("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SubjectVariable extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInSubjectVariable() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("hasAnnotation.kt")
public void testHasAnnotation() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/hasAnnotation.kt");
}
@TestMetadata("hasSameNameVariable.kt")
public void testHasSameNameVariable() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/hasSameNameVariable.kt");
}
@TestMetadata("hasSameNameVariable2.kt")
public void testHasSameNameVariable2() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/hasSameNameVariable2.kt");
}
@TestMetadata("hasSameNameVariable3.kt")
public void testHasSameNameVariable3() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/hasSameNameVariable3.kt");
}
@TestMetadata("hasSameNameVariable4.kt")
public void testHasSameNameVariable4() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/hasSameNameVariable4.kt");
}
@TestMetadata("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class BlockElse extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInBlockElse() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("multiReference.kt")
public void testMultiReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse/multiReference.kt");
}
@TestMetadata("noReference.kt")
public void testNoReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse/noReference.kt");
}
@TestMetadata("noReference2.kt")
public void testNoReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse/noReference2.kt");
}
@TestMetadata("singleReference.kt")
public void testSingleReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElse/singleReference.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class BlockElseUsedAsExpression extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInBlockElseUsedAsExpression() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("multiReference.kt")
public void testMultiReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression/multiReference.kt");
}
@TestMetadata("noReference.kt")
public void testNoReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression/noReference.kt");
}
@TestMetadata("noReference2.kt")
public void testNoReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression/noReference2.kt");
}
@TestMetadata("singleReference.kt")
public void testSingleReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/blockElseUsedAsExpression/singleReference.kt");
}
}
@TestMetadata("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class SingleElse extends AbstractLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
public void testAllFilesPresentInSingleElse() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("multiReference.kt")
public void testMultiReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/multiReference.kt");
}
@TestMetadata("multiReferenceUsedAsExpression.kt")
public void testMultiReferenceUsedAsExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/multiReferenceUsedAsExpression.kt");
}
@TestMetadata("noReference.kt")
public void testNoReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/noReference.kt");
}
@TestMetadata("noReference2.kt")
public void testNoReference2() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/noReference2.kt");
}
@TestMetadata("noReferenceUsedAsExpression.kt")
public void testNoReferenceUsedAsExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/noReferenceUsedAsExpression.kt");
}
@TestMetadata("noReferenceUsedAsExpression2.kt")
public void testNoReferenceUsedAsExpression2() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/noReferenceUsedAsExpression2.kt");
}
@TestMetadata("singleReference.kt")
public void testSingleReference() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/singleReference.kt");
}
@TestMetadata("singleReferenceUsedAsExpression.kt")
public void testSingleReferenceUsedAsExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/whenWithOnlyElse/subjectVariable/singleElse/singleReferenceUsedAsExpression.kt");
}
}
}
}
@TestMetadata("idea/testData/inspectionsLocal/wrapUnaryOperator")