KT-31553 Complete Statement: Wrong auto-insertion of closing curly brace for a code block (#2378)

* Complete statement: wrap property initializer by run/when block

#KT-31553 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-09-25 23:04:03 +09:00
committed by GitHub
parent 14bdcb1e26
commit 37ba9eccc4
13 changed files with 313 additions and 0 deletions
@@ -82,6 +82,7 @@ import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractCommonDecompiled
import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractCommonDecompiledTextTest
import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractJsDecompiledTextFromJsMetadataTest
import org.jetbrains.kotlin.idea.decompiler.textBuilder.AbstractJvmDecompiledTextTest
import org.jetbrains.kotlin.idea.editor.AbstractEnterAfterUnmatchedBraceHandlerTest
import org.jetbrains.kotlin.idea.editor.AbstractMultiLineStringIndentTest
import org.jetbrains.kotlin.idea.editor.backspaceHandler.AbstractBackspaceHandlerTest
import org.jetbrains.kotlin.idea.editor.quickDoc.AbstractQuickDocProviderTest
@@ -547,6 +548,10 @@ fun main(args: Array<String>) {
model("editor/backspaceHandler")
}
testClass<AbstractEnterAfterUnmatchedBraceHandlerTest> {
model("editor/enterHandler/afterUnmatchedBrace")
}
testClass<AbstractMultiLineStringIndentTest> {
model("editor/enterHandler/multilineString")
}
+2
View File
@@ -79,6 +79,8 @@
id="KotlinEnterHandler" order="before EnterBetweenBracesHandler"/>
<enterHandlerDelegate implementation="org.jetbrains.kotlin.idea.editor.KotlinMultilineStringEnterHandler"
id="KotlinMultilineStringEnterHandler" order="before EnterBetweenBracesHandler"/>
<enterHandlerDelegate implementation="org.jetbrains.kotlin.idea.editor.KotlinEnterAfterUnmatchedBraceHandler"
id="KotlinEnterAfterUnmatchedBraceHandler" order="before afterUnmatchedBrace"/>
<enterBetweenBracesDelegate language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.editor.EnterBetweenBracesAndBracketsNoCommitDelegate"/>
<lang.smartEnterProcessor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.editor.KotlinSmartEnterHandler"/>
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.editor
import com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
class KotlinEnterAfterUnmatchedBraceHandler : EnterAfterUnmatchedBraceHandler() {
override fun preprocessEnter(
file: PsiFile,
editor: Editor,
caretOffsetRef: Ref<Int>,
caretAdvance: Ref<Int>,
dataContext: DataContext,
originalHandler: EditorActionHandler?
): EnterHandlerDelegate.Result {
val caretOffset = caretOffsetRef.get() - 1
val element = file.findElementAt(caretOffset)
if (element?.node?.elementType == KtTokens.LBRACE) {
return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler)
}
if (element !is PsiWhiteSpace) {
return EnterHandlerDelegate.Result.Continue
}
val prevElement = CodeInsightUtils.getElementAtOffsetIgnoreWhitespaceAfter(file, caretOffset)
if (prevElement != null && prevElement.node.elementType == KtTokens.LBRACE) {
return super.preprocessEnter(file, editor, Ref(prevElement.startOffset + 1), caretAdvance, dataContext, originalHandler)
}
return EnterHandlerDelegate.Result.Continue
}
override fun getRBraceOffset(file: PsiFile, editor: Editor, caretOffset: Int): Int {
val element = file.findElementAt(caretOffset - 1)
val endOffset = when (val parent = element?.parent) {
is KtFunctionLiteral -> {
val call = parent.getStrictParentOfType<KtCallExpression>()
if (call?.isDeclarationInitializer() == true) {
(parent.parent as? KtLambdaExpression)?.bodyExpression?.statements?.firstOrNull()?.endOffset
} else {
null
}
}
is KtWhenExpression -> {
if (parent.isDeclarationInitializer()) {
(parent.entries.firstOrNull()?.conditions?.firstOrNull() as? KtWhenConditionWithExpression)?.endOffset
} else {
null
}
}
else -> null
}
return endOffset ?: super.getRBraceOffset(file, editor, caretOffset)
}
private fun KtExpression.isDeclarationInitializer(): Boolean {
return (parent as? KtDeclarationWithInitializer)?.initializer == this
}
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
fun test(): Int = bar { <caret>foo()
fun foo() = 42
fun bar(f: () -> Int) = f()
//-----
// WITH_RUNTIME
fun test(): Int = bar {
<caret>foo()
}
fun foo() = 42
fun bar(f: () -> Int) = f()
@@ -0,0 +1,11 @@
// WITH_RUNTIME
fun test() {
val test = run { <caret>1
}
//-----
// WITH_RUNTIME
fun test() {
val test = run {
<caret>1
}
}
@@ -0,0 +1,19 @@
// WITH_RUNTIME
class Test {
val test = run {<caret>foo()
fun foo(): Int {
return 42
}
}
//-----
// WITH_RUNTIME
class Test {
val test = run {
<caret>foo()
}
fun foo(): Int {
return 42
}
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
val test = run {<caret>foo()
fun foo() = 42
//-----
// WITH_RUNTIME
val test = run {
<caret>foo()
}
fun foo() = 42
@@ -0,0 +1,14 @@
// WITH_RUNTIME
fun test(): Int = bar { f<caret>oo()
fun foo() = 42
fun bar(f: () -> Int) = f()
//-----
// WITH_RUNTIME
fun test(): Int = bar { f
<caret>oo()
fun foo() = 42
fun bar(f: () -> Int) = f()
@@ -0,0 +1,9 @@
fun test() {
val test = when { <caret>1
}
//-----
fun test() {
val test = when {
<caret>1
}
}
@@ -0,0 +1,17 @@
class Test {
val test = when {<caret>foo()
fun foo(): Int {
return 42
}
}
//-----
class Test {
val test = when {
<caret>foo()
}
fun foo(): Int {
return 42
}
}
@@ -0,0 +1,9 @@
val test = when {<caret>foo()
fun foo() = 42
//-----
val test = when {
<caret>foo()
}
fun foo() = 42
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.editor
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.EditorTestUtil
import com.intellij.testFramework.LightProjectDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.utils.rethrow
import java.io.File
import java.io.IOException
abstract class AbstractEnterAfterUnmatchedBraceHandlerTest : KotlinLightCodeInsightFixtureTestCase() {
companion object {
private const val FILE_SEPARATOR = "//-----"
}
protected fun doTest(path: String) {
val multiFileText = FileUtil.loadFile(File(path), true)
val beforeFile = multiFileText.substringBefore(FILE_SEPARATOR).trim()
val afterFile = multiFileText.substringAfter(FILE_SEPARATOR).trim()
myFixture.setCaresAboutInjection(false)
myFixture.configureByText(KotlinFileType.INSTANCE, beforeFile)
myFixture.type('\n')
val caretModel = myFixture.editor.caretModel
val offset = caretModel.offset
val actualTextWithCaret = StringBuilder(myFixture.editor.document.text).insert(offset, EditorTestUtil.CARET_TAG).toString()
if (afterFile != actualTextWithCaret) {
KotlinTestUtils.assertEqualsToFile(File(path), "$beforeFile\n$FILE_SEPARATOR\n$actualTextWithCaret")
}
}
override fun getProjectDescriptor(): LightProjectDescriptor {
if (isAllFilesPresentInTest()) return KotlinLightProjectDescriptor.INSTANCE
return try {
val fileText = FileUtil.loadFile(File(testDataPath, fileName()), true)
if (InTextDirectivesUtils.isDirectiveDefined(fileText, "WITH_RUNTIME")) {
KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
} else {
JAVA_LATEST
}
} catch (e: IOException) {
throw rethrow(e)
}
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.editor;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/editor/enterHandler/afterUnmatchedBrace")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class EnterAfterUnmatchedBraceHandlerTestGenerated extends AbstractEnterAfterUnmatchedBraceHandlerTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInAfterUnmatchedBrace() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/editor/enterHandler/afterUnmatchedBrace"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("lambdaArgumentBeforeFunctionInitializer.kt")
public void testLambdaArgumentBeforeFunctionInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/lambdaArgumentBeforeFunctionInitializer.kt");
}
@TestMetadata("lambdaArgumentBeforeLocalPropertyInitializer.kt")
public void testLambdaArgumentBeforeLocalPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/lambdaArgumentBeforeLocalPropertyInitializer.kt");
}
@TestMetadata("lambdaArgumentBeforeMemberPropertyInitializer.kt")
public void testLambdaArgumentBeforeMemberPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/lambdaArgumentBeforeMemberPropertyInitializer.kt");
}
@TestMetadata("lambdaArgumentBeforeTopLevelPropertyInitializer.kt")
public void testLambdaArgumentBeforeTopLevelPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/lambdaArgumentBeforeTopLevelPropertyInitializer.kt");
}
@TestMetadata("notApplicableOnInitializer.kt")
public void testNotApplicableOnInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/notApplicableOnInitializer.kt");
}
@TestMetadata("whenBeforeLocalPropertyInitializer.kt")
public void testWhenBeforeLocalPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/whenBeforeLocalPropertyInitializer.kt");
}
@TestMetadata("whenBeforeMemberPropertyInitializer.kt")
public void testWhenBeforeMemberPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/whenBeforeMemberPropertyInitializer.kt");
}
@TestMetadata("whenBeforeTopLevelPropertyInitializer.kt")
public void testWhenBeforeTopLevelPropertyInitializer() throws Exception {
runTest("idea/testData/editor/enterHandler/afterUnmatchedBrace/whenBeforeTopLevelPropertyInitializer.kt");
}
}