KT-9669: introduced JoinStatementAddSemicolonHandler; registered the handler in plugin.xml; added test coverage (#1232)
This commit is contained in:
committed by
Dmitry Jemerov
parent
d624ed4aff
commit
b24c1bf06c
@@ -118,7 +118,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
|
||||
LBRACKET // Collection literal expression
|
||||
);
|
||||
|
||||
private static final TokenSet STATEMENT_FIRST = TokenSet.orSet(
|
||||
public static final TokenSet STATEMENT_FIRST = TokenSet.orSet(
|
||||
EXPRESSION_FIRST,
|
||||
TokenSet.create(
|
||||
// declaration
|
||||
|
||||
@@ -798,6 +798,7 @@
|
||||
|
||||
<joinLinesHandler implementation="org.jetbrains.kotlin.idea.joinLines.JoinDeclarationAndAssignmentHandler"/>
|
||||
<joinLinesHandler implementation="org.jetbrains.kotlin.idea.joinLines.JoinBlockIntoSingleStatementHandler"/>
|
||||
<joinLinesHandler implementation="org.jetbrains.kotlin.idea.joinLines.JoinStatementsAddSemicolonHandler"/>
|
||||
|
||||
<targetElementEvaluator
|
||||
language="kotlin"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.joinLines
|
||||
|
||||
import com.intellij.codeInsight.editorActions.JoinLinesHandlerDelegate.CANNOT_JOIN
|
||||
import com.intellij.codeInsight.editorActions.JoinRawLinesHandlerDelegate
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.KtNodeTypes.BLOCK
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.LBRACE
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.WHITE_SPACE_OR_COMMENT_BIT_SET
|
||||
import org.jetbrains.kotlin.parsing.KotlinExpressionParsing
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
|
||||
class JoinStatementsAddSemicolonHandler : JoinRawLinesHandlerDelegate {
|
||||
|
||||
override fun tryJoinRawLines(document: Document, file: PsiFile, start: Int, end: Int): Int {
|
||||
if (file !is KtFile) return CANNOT_JOIN
|
||||
|
||||
val linebreak = file.findElementAt(start)
|
||||
?.siblings(forward = true, withItself = true)
|
||||
?.firstOrNull { it.textContains('\n') }
|
||||
?: return CANNOT_JOIN
|
||||
|
||||
val parent = linebreak.parent ?: return CANNOT_JOIN
|
||||
val element1 = linebreak.firstMaterialSiblingSameLine { prevSibling } ?: return CANNOT_JOIN
|
||||
val element2 = linebreak.firstMaterialSiblingSameLine { nextSibling } ?: return CANNOT_JOIN
|
||||
|
||||
if (parent.node.elementType != BLOCK) return CANNOT_JOIN
|
||||
if (linebreak.text.count { it == '\n' } > 1) return CANNOT_JOIN
|
||||
if (!element1.isStatement()) return CANNOT_JOIN
|
||||
if (!element2.isStatement()) return CANNOT_JOIN
|
||||
|
||||
document.replaceString(linebreak.textRange.startOffset, linebreak.textRange.endOffset, " ")
|
||||
document.insertString(element1.textRange.endOffset, ";")
|
||||
|
||||
return linebreak.textRange.startOffset + 1
|
||||
}
|
||||
|
||||
override fun tryJoinLines(document: Document, file: PsiFile, start: Int, end: Int) = CANNOT_JOIN
|
||||
|
||||
private fun PsiElement.firstMaterialSiblingSameLine(getNext: PsiElement.() -> PsiElement?): PsiElement? {
|
||||
var element = this
|
||||
do {
|
||||
element = element.getNext() ?: return null
|
||||
if (element.node.elementType !in WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
return element
|
||||
}
|
||||
while (!element.textContains('\n'))
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun PsiElement.isStatement(): Boolean {
|
||||
if (this.node.elementType == LBRACE) return false
|
||||
|
||||
var firstSubElement: PsiElement = this
|
||||
while (true) firstSubElement = firstSubElement.firstChild ?: break
|
||||
|
||||
// Emulates the `atSet(STATEMENT_FIRST)` check at [KotlinExpressionParsing.parseStatements]
|
||||
return firstSubElement.node.elementType in KotlinExpressionParsing.STATEMENT_FIRST
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<caret>class A
|
||||
class B
|
||||
@@ -0,0 +1 @@
|
||||
class A<caret> class B
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
<caret>println()
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
println();<caret> println()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
<caret>println() // A very important comment
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
println(); /* A very important comment*/<caret> println()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
<caret>println() // A very long
|
||||
// and important comment
|
||||
println()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
println() // A very long<caret> and important comment
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
/*0<caret>*/println() /*1*/ /*2*/
|
||||
println() // 4
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
/*0*/println(); /*1*/ /*2*/<caret> println() // 4
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
<caret>println()
|
||||
|
||||
println()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
println()<caret>
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo() {
|
||||
|
||||
}<caret>
|
||||
fun boo() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
|
||||
}<caret> fun boo() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
{<caret>
|
||||
println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
{<caret> println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo() {
|
||||
if(true){<caret>
|
||||
println()
|
||||
println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
if(true){<caret> println()
|
||||
println()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
val a<caret> = 1
|
||||
val b = 1
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val a = 1;<caret> val b = 1
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
val a<caret> = 1;
|
||||
val b = 1
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val a = 1;<caret> val b = 1
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun main(args: Array<String>) {
|
||||
class A<caret>
|
||||
class B
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun main(args: Array<String>) {
|
||||
class A;<caret> class B
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun main(args: Array<String>) {
|
||||
fun foo() {
|
||||
|
||||
}<caret>
|
||||
fun boo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fun main(args: Array<String>) {
|
||||
fun foo() {
|
||||
|
||||
};<caret> fun boo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
class A {
|
||||
fun a(){}<caret>
|
||||
fun b(){}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
fun a(){}<caret> fun b(){}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
<caret>while (true) println()
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
while (true) println();<caret> println()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
while (true) { println(<caret>) }
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
while (true) { println() };<caret> println()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
fun foo() {
|
||||
while (true) {
|
||||
println()
|
||||
<caret>}
|
||||
println()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
while (true) {
|
||||
println()
|
||||
};<caret> println()
|
||||
}
|
||||
+111
@@ -36,6 +36,117 @@ public class JoinLinesTestGenerated extends AbstractJoinLinesTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/joinLines"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/joinLines/addSemicolon")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddSemicolon extends AbstractJoinLinesTest {
|
||||
public void testAllFilesPresentInAddSemicolon() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/joinLines/addSemicolon"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("ClassDeclarations.kt")
|
||||
public void testClassDeclarations() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/ClassDeclarations.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionCalls.kt")
|
||||
public void testFunctionCalls() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionCalls.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionCallsAndAComment.kt")
|
||||
public void testFunctionCallsAndAComment() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionCallsAndAComment.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionCallsAndAMultilineComment.kt")
|
||||
public void testFunctionCallsAndAMultilineComment() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionCallsAndAMultilineComment.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionCallsAndSeveralComments.kt")
|
||||
public void testFunctionCallsAndSeveralComments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionCallsAndSeveralComments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionCallsWithMutlineSeparator.kt")
|
||||
public void testFunctionCallsWithMutlineSeparator() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionCallsWithMutlineSeparator.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("FunctionDeclarations.kt")
|
||||
public void testFunctionDeclarations() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/FunctionDeclarations.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("InsideBraces.kt")
|
||||
public void testInsideBraces() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/InsideBraces.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("InsideIfBlock.kt")
|
||||
public void testInsideIfBlock() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/InsideIfBlock.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("LocalAssingments.kt")
|
||||
public void testLocalAssingments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/LocalAssingments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("LocalAssingmentsWithSemicolon.kt")
|
||||
public void testLocalAssingmentsWithSemicolon() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/LocalAssingmentsWithSemicolon.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("LocalClasses.kt")
|
||||
public void testLocalClasses() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/LocalClasses.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("LocalFunctions.kt")
|
||||
public void testLocalFunctions() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/LocalFunctions.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("MemberFunctions.kt")
|
||||
public void testMemberFunctions() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/MemberFunctions.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("WhileAndACall.kt")
|
||||
public void testWhileAndACall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/WhileAndACall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("WhileBlockAndACall.kt")
|
||||
public void testWhileBlockAndACall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/WhileBlockAndACall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("WhileBlockAndACall2.kt")
|
||||
public void testWhileBlockAndACall2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/joinLines/addSemicolon/WhileBlockAndACall2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/joinLines/declarationAndAssignment")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user