KT-9669: introduced JoinStatementAddSemicolonHandler; registered the handler in plugin.xml; added test coverage (#1232)

This commit is contained in:
Daniil Vodopian
2017-08-07 17:20:40 +03:00
committed by Dmitry Jemerov
parent d624ed4aff
commit b24c1bf06c
38 changed files with 333 additions and 1 deletions
@@ -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
+1
View File
@@ -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
+4
View File
@@ -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()
}
@@ -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()
}
@@ -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() {
}
+5
View File
@@ -0,0 +1,5 @@
fun foo() {
{<caret>
println()
}
}
@@ -0,0 +1,4 @@
fun foo() {
{<caret> println()
}
}
+6
View File
@@ -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
}
+4
View File
@@ -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(){}
}
+4
View File
@@ -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()
}
@@ -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)