diff --git a/CHANGELOG.md b/CHANGELOG.md index e3a3166d0e..a670c9ea4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ At this point in time, it is not yet decided what the next steps will be. Ktlint * Extract rule `no-single-line-block-comment` from `comment-wrapping` rule. The `no-single-line-block-comment` rule is added as experimental rule to the `ktlint_official` code style, but it can be enabled explicitly for the other code styles as well. ([#1980](https://github.com/pinterest/ktlint/issues/1980)) * Clean-up unwanted logging dependencies ([#1998](https://github.com/pinterest/ktlint/issues/1998)) * Fix directory traversal for patterns referring to paths outside of current working directory or any of it child directories ([#2002](https://github.com/pinterest/ktlint/issues/2002)) +* Prevent multiple expressions on same line separated by semicolon ([#1078](https://github.com/pinterest/ktlint/issues/1078)) ### Changed diff --git a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt index cb9cb5d7ee..8d26d44631 100644 --- a/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt +++ b/ktlint-cli/src/main/kotlin/com/pinterest/ktlint/cli/internal/ReporterAggregator.kt @@ -140,7 +140,8 @@ internal class ReporterAggregator( val stream = when { reporterConfiguration.output != null -> { - File(reporterConfiguration.output).parentFile?.mkdirsOrFail(); PrintStream(reporterConfiguration.output, "UTF-8") + File(reporterConfiguration.output).parentFile?.mkdirsOrFail() + PrintStream(reporterConfiguration.output, "UTF-8") } stdin -> { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt index 6e8b520d27..25fd1092e9 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/MaxLineLengthRule.kt @@ -177,7 +177,10 @@ internal class RangeTree(seq: List = emptyList()) { init { if (arr.isNotEmpty()) { - arr.reduce { p, n -> require(p <= n) { "Input must be sorted" }; n } + arr.reduce { p, n -> + require(p <= n) { "Input must be sorted" } + n + } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt index 1405c1d97e..8c399150ae 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt @@ -4,6 +4,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.SEMICOLON +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf @@ -24,7 +25,17 @@ import org.jetbrains.kotlin.psi.KtIfExpression import org.jetbrains.kotlin.psi.KtLoopExpression import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -public class NoSemicolonsRule : StandardRule("no-semi") { +public class NoSemicolonsRule : + StandardRule( + id = "no-semi", + visitorModifiers = + setOf( + RunAfterRule( + ruleId = WRAPPING_RULE_ID, + mode = RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED, + ), + ), + ) { override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt index ff1d22024d..244ea97667 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt @@ -11,6 +11,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.CONDITION import com.pinterest.ktlint.rule.engine.core.api.ElementType.DESTRUCTURING_DECLARATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_LITERAL @@ -26,6 +27,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_LITERAL import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACKET import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR +import com.pinterest.ktlint.rule.engine.core.api.ElementType.SEMICOLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.rule.engine.core.api.ElementType.SUPER_TYPE_CALL_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.SUPER_TYPE_ENTRY @@ -64,6 +66,7 @@ import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.nextSibling +import com.pinterest.ktlint.rule.engine.core.api.noNewLineInClosedRange import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.rule.engine.core.api.prevSibling @@ -137,6 +140,7 @@ public class WrappingRule : ARROW -> rearrangeArrow(node, autoCorrect, emit) WHITE_SPACE -> line += node.text.count { it == '\n' } CLOSING_QUOTE -> rearrangeClosingQuote(node, autoCorrect, emit) + SEMICOLON -> insertNewLineAfterSemi(node, autoCorrect, emit) } } @@ -529,6 +533,23 @@ public class WrappingRule : } } + private fun insertNewLineAfterSemi( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + val previousCodeLeaf = node.prevCodeLeaf()?.lastChildLeafOrSelf() ?: return + val nextCodeLeaf = node.nextCodeLeaf()?.firstChildLeafOrSelf() ?: return + if (previousCodeLeaf.treeParent.elementType == ENUM_ENTRY && nextCodeLeaf.elementType == RBRACE) { + // Allow + // enum class INDEX2 { ONE, TWO, THREE; } + return + } + if (noNewLineInClosedRange(previousCodeLeaf, nextCodeLeaf)) { + requireNewlineAfterLeaf(node, autoCorrect, emit, indent = previousCodeLeaf.indent()) + } + } + private fun requireNewlineBeforeLeaf( node: ASTNode, autoCorrect: Boolean, diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt index 3194095698..b1915d06b7 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt @@ -1871,6 +1871,658 @@ internal class WrappingRuleTest { .hasLintViolation(2, 10, "A newline was expected before 'Bar'") .isFormattedAs(formattedCode) } + + @Nested + inner class `Issue 1078 - Given multiple expression seperated with semi in a single line` { + @Nested + inner class `Given multiple variables` { + @Test + fun `Given two variables`() { + val code = + """ + fun foo() { + val bar1 = 3; val bar2 = 2 + val fooBar1: String = ""; val fooBar2: () -> Unit = { } + } + """.trimIndent() + val formattedCode = + """ + fun foo() { + val bar1 = 3 + val bar2 = 2 + val fooBar1: String = "" + val fooBar2: () -> Unit = { } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(2, 18, "Missing newline after \";\""), + LintViolation(3, 30, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given two variables run without NoSemicolonsRule`() { + val code = + """ + fun foo() { + val bar1 = 3; val bar2 = 2 + val fooBar1: String = ""; val fooBar2: () -> Unit = { } + } + """.trimIndent() + val formattedCode = + """ + fun foo() { + val bar1 = 3; + val bar2 = 2 + val fooBar1: String = ""; + val fooBar2: () -> Unit = { } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .hasLintViolations( + LintViolation(2, 18, "Missing newline after \";\""), + LintViolation(3, 30, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given more than two variables`() { + val code = + """ + fun foo() { + val bar1 = 3; val bar2 = 2; val bar3 = 3; val bar4: () -> Unit = { }; val bar4: String = ""; + } + """.trimIndent() + val formattedCode = + """ + fun foo() { + val bar1 = 3 + val bar2 = 2 + val bar3 = 3 + val bar4: () -> Unit = { } + val bar4: String = "" + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(2, 18, "Missing newline after \";\""), + LintViolation(2, 32, "Missing newline after \";\""), + LintViolation(2, 46, "Missing newline after \";\""), + LintViolation(2, 75, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given variables with comments`() { + val code = + """ + fun foo() { + val bar1 = 3; val bar2 = 2; // this is end comment + val bar1 = 3; /* block comment */ val bar2 = 2; + } + """.trimIndent() + val formattedCode = + """ + fun foo() { + val bar1 = 3 + val bar2 = 2; // this is end comment + val bar1 = 3 + /* block comment */ val bar2 = 2 + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(2, 18, "Missing newline after \";\""), + LintViolation(3, 18, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + } + + @Nested + inner class `Given multiple classes, functions and init blocks` { + @Test + fun `Given multiple function declaration`() { + val code = + """ + public fun foo1() { + // no-op + }; public fun foo2() { + // no-op + }; fun foo3() = 0 + + public fun foo4() = 1; public fun foo5() { + // no-op + } + """.trimIndent() + val formattedCode = + """ + public fun foo1() { + // no-op + } + public fun foo2() { + // no-op + } + fun foo3() = 0 + + public fun foo4() = 1 + public fun foo5() { + // no-op + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(3, 3, "Missing newline after \";\""), + LintViolation(5, 3, "Missing newline after \";\""), + LintViolation(7, 23, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given multiple function declaration with comments`() { + val code = + """ + public fun foo1() { + // no-op + }; /* block comment */ public fun foo2() { + // no-op + }; fun foo3() = 0 // single line comment + """.trimIndent() + val formattedCode = + """ + public fun foo1() { + // no-op + } + /* block comment */ public fun foo2() { + // no-op + } + fun foo3() = 0 // single line comment + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(3, 3, "Missing newline after \";\""), + LintViolation(5, 3, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given multiple function invocations`() { + val code = + """ + class Bar { + public fun foo1() = 0 + fun foo2() = 0 + fun foo3(lambda: () -> Unit) = 0 + + init { + foo1(); foo3 { }; foo2() + } + } + """.trimIndent() + val formattedCode = + """ + class Bar { + public fun foo1() = 0 + fun foo2() = 0 + fun foo3(lambda: () -> Unit) = 0 + + init { + foo1() + foo3 { } + foo2() + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(7, 16, "Missing newline after \";\""), + LintViolation(7, 27, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline class declaration`() { + val code = + """ + public class FooBar1 { + + }; public class FooBar2 { + + } + + public class FooBar3; public class FooBar4 + """.trimIndent() + val formattedCode = + """ + public class FooBar1 { + + } + public class FooBar2 { + + } + + public class FooBar3 + public class FooBar4 + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(3, 3, "Missing newline after \";\""), + LintViolation(7, 22, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline class declaration with comments`() { + val code = + """ + public class FooBar1 { + + }; /* block comment */ public class FooBar2 { + + }; public class FooBar2 { + + } // single line comment + """.trimIndent() + val formattedCode = + """ + public class FooBar1 { + + } + /* block comment */ public class FooBar2 { + + } + public class FooBar2 { + + } // single line comment + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(3, 3, "Missing newline after \";\""), + LintViolation(5, 3, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiple init block`() { + val code = + """ + public class Foo { + init { + + };init { + + } + } + """.trimIndent() + val formattedCode = + """ + public class Foo { + init { + + } + init { + + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolation(4, 7, "Missing newline after \";\"") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiple init block and variables with nested violations`() { + val code = + """ + public class Foo { + init { + val bar1 = 0; val bar2 = 0; + };init { + val bar3 = 0; val bar4 = 0; + } + } + """.trimIndent() + val formattedCode = + """ + public class Foo { + init { + val bar1 = 0 + val bar2 = 0 + } + init { + val bar3 = 0 + val bar4 = 0 + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(3, 22, "Missing newline after \";\""), + LintViolation(4, 7, "Missing newline after \";\""), + LintViolation(5, 22, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + } + + @Nested + inner class `Given flow control statements` { + @Test + fun `Given a multiple for statements`() { + val code = + """ + fun test() { + for (i in 0..10) { + println(i) + }; for (i in 0..100) { + println(i) + }; for (i in 0..1000) { + println(i) + } + } + """.trimIndent() + val formattedCode = + """ + fun test() { + for (i in 0..10) { + println(i) + } + for (i in 0..100) { + println(i) + } + for (i in 0..1000) { + println(i) + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(4, 7, "Missing newline after \";\""), + LintViolation(6, 7, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline while statements`() { + val code = + """ + fun test() { + while (System.currentTimeMillis() % 2 == 0L) { + println(System.currentTimeMillis()) + }; while (Random(System.currentTimeMillis()).nextBoolean()) { + println(System.currentTimeMillis()) + } + } + """.trimIndent() + val formattedCode = + """ + fun test() { + while (System.currentTimeMillis() % 2 == 0L) { + println(System.currentTimeMillis()) + } + while (Random(System.currentTimeMillis()).nextBoolean()) { + println(System.currentTimeMillis()) + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolation(4, 7, "Missing newline after \";\"") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline do-while statements`() { + val code = + """ + fun test() { + while (System.currentTimeMillis() % 2 == 0L) { + println(System.currentTimeMillis()) + }; do { + println(System.currentTimeMillis()) + } while (System.currentTimeMillis() % 2 == 0L); do { + println(System.currentTimeMillis()) + } while (System.currentTimeMillis() % 2 == 0L) + } + """.trimIndent() + val formattedCode = + """ + fun test() { + while (System.currentTimeMillis() % 2 == 0L) { + println(System.currentTimeMillis()) + } + do { + println(System.currentTimeMillis()) + } while (System.currentTimeMillis() % 2 == 0L) + do { + println(System.currentTimeMillis()) + } while (System.currentTimeMillis() % 2 == 0L) + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(4, 7, "Missing newline after \";\""), + LintViolation(6, 52, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline semi separated control flow with no body`() { + val code = + """ + fun test() { + for (i in 0..10); for (i in 0..100);while (System.currentTimeMillis() % 2 == 0L); while (Random(System.currentTimeMillis()).nextBoolean()); + } + """.trimIndent() + val formattedCode = + """ + fun test() { + for (i in 0..10); + for (i in 0..100); + while (System.currentTimeMillis() % 2 == 0L); + while (Random(System.currentTimeMillis()).nextBoolean()); + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(2, 22, "Missing newline after \";\""), + LintViolation(2, 41, "Missing newline after \";\""), + LintViolation(2, 86, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + } + + @Test + fun `Given a multiline semi separated import statement then wrap each expression to a new line`() { + val code = + """ + import java.util.ArrayList; import java.util.HashMap + """.trimIndent() + val formattedCode = + """ + import java.util.ArrayList + import java.util.HashMap + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolation(1, 28, "Missing newline after \";\"") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a multiline semi separated with variables, flow controls and method calls`() { + val code = + """ + fun test() { + val a = 0; val b = 0; fun bar() { + // no-op + }; for(i in 0..10) { + println(i); println(i); a++; println(a) + } + } + """.trimIndent() + val formattedCode = + """ + fun test() { + val a = 0 + val b = 0 + fun bar() { + // no-op + } + for(i in 0..10) { + println(i) + println(i) + a++ + println(a) + } + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolations( + LintViolation(2, 15, "Missing newline after \";\""), + LintViolation(2, 26, "Missing newline after \";\""), + LintViolation(4, 7, "Missing newline after \";\""), + LintViolation(5, 20, "Missing newline after \";\""), + LintViolation(5, 32, "Missing newline after \";\""), + LintViolation(5, 37, "Missing newline after \";\""), + ).isFormattedAs(formattedCode) + } + + @Nested + inner class `Given enum class` { + @Test + fun `Given a enum without ending semi`() { + val code = + """ + enum class FOO1 { ONE, TWO, THREE } + enum class FOO2 { + ONE, + TWO, + THREE + } + """.trimIndent() + wrappingRuleAssertThat(code) + .hasNoLintViolations() + } + + @Test + fun `Given a enum with ending semi`() { + val code = + """ + enum class FOO1 { ONE, TWO, THREE; } + enum class FOO2 { + ONE, TWO, THREE; + } + enum class FOO3 { + ONE, + TWO, + THREE; + } + enum class FOO4 { + ONE, + TWO, + THREE, + ; + } + enum class FOO5 { + ONE, + TWO, + THREE, + ; + fun foo() = "" + } + """.trimIndent() + wrappingRuleAssertThat(code) + .hasNoLintViolations() + } + + @Test + fun `Given a enum with ending semi with comment`() { + val code = + """ + enum class FOO1 { ONE, TWO, THREE; /* with comment */ } + enum class FOO2 { + ONE, + TWO, + THREE, // single line comment + ; // last single line comment + } + """.trimIndent() + wrappingRuleAssertThat(code) + .hasNoLintViolations() + } + + @Test + fun `Given enum class with methods`() { + val code = + """ + enum class FOO { + A, B, C; fun test() = 0 + } + """.trimIndent() + val formattedCode = + """ + enum class FOO { + A, B, C; + fun test() = 0 + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolation(2, 13, "Missing newline after \";\"") + .isFormattedAs(formattedCode) + } + } + + @Nested + inner class `Given companion or object class` { + @Test + fun `Given a companion object with semicolon and variable`() { + val code = + """ + class Foo() { + companion object; private var toto: Boolean = false + } + """.trimIndent() + val formattedCode = + """ + class Foo() { + companion object; + private var toto: Boolean = false + } + """.trimIndent() + wrappingRuleAssertThat(code) + .addAdditionalRuleProvider { NoSemicolonsRule() } + .hasLintViolation(2, 22, "Missing newline after \";\"") + .isFormattedAs(formattedCode) + } + + @Test + fun `Given a companion object with semicolon and comment has not violation`() { + val code = + """ + class Foo() { + companion object; // single-line comment + } + """.trimIndent() + wrappingRuleAssertThat(code) + .hasNoLintViolations() + } + } + } } // Replace the "$." placeholder with an actual "$" so that string "$.{expression}" is transformed to a String template