diff --git a/CHANGELOG.md b/CHANGELOG.md index 9263f48408..f775958000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Experimental SpacingAroundAngleBracketsRule ([#769](https://github.com/pinterest/ktlint/pull/769)) - Checksum generation for executable Jar ([#695](https://github.com/pinterest/ktlint/issues/695)) - Enable Gradle dependency verification +- `parameter-list-wrapping` rule now also considers function arguments while wrapping ([#620](https://github.com/pinterest/ktlint/issues/620)) ### Fixed - Safe-called wrapped trailing lambdas indented correctly ([#776](https://github.com/pinterest/ktlint/issues/776)) diff --git a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt index 33cbb6a793..e9eed3868f 100644 --- a/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt +++ b/ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/KtLint.kt @@ -96,7 +96,8 @@ object KtLint { compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) val project = KotlinCoreEnvironment.createForProduction( Disposable {}, - compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES + compilerConfiguration, + EnvironmentConfigFiles.JVM_CONFIG_FILES ).project // everything below (up to PsiFileFactory.getInstance(...)) is to get AST mutations (`ktlint -F ...`) working // otherwise it's not needed @@ -113,7 +114,8 @@ object KtLint { // (check constructor signature and compare it to the source) // (org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.3) val constructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization( - aspect, Any::class.java.getDeclaredConstructor(*arrayOfNulls>(0)) + aspect, + Any::class.java.getDeclaredConstructor(*arrayOfNulls>(0)) ) return constructor.newInstance(*emptyArray()) as T } diff --git a/ktlint-reporter-checkstyle/src/test/kotlin/com/pinterest/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt b/ktlint-reporter-checkstyle/src/test/kotlin/com/pinterest/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt index 06ba37ea0e..a4d0988adf 100644 --- a/ktlint-reporter-checkstyle/src/test/kotlin/com/pinterest/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt +++ b/ktlint-reporter-checkstyle/src/test/kotlin/com/pinterest/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt @@ -15,7 +15,9 @@ class CheckStyleReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "<\"&'>" ), false @@ -23,7 +25,9 @@ class CheckStyleReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 2, 1, "rule-2", + 2, + 1, + "rule-2", "And if you see my friend" ), true @@ -32,7 +36,9 @@ class CheckStyleReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 1, 10, "rule-1", + 1, + 10, + "rule-1", "I thought I would again" ), false @@ -40,7 +46,9 @@ class CheckStyleReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 2, 20, "rule-2", + 2, + 20, + "rule-2", "A single thin straight line" ), false @@ -49,14 +57,16 @@ class CheckStyleReporterTest { reporter.onLintError( "/all-corrected.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "I thought we had more time" ), true ) reporter.afterAll() assertThat(String(out.toByteArray())).isEqualTo( -""" + """ diff --git a/ktlint-reporter-json/src/test/kotlin/com/pinterest/ktlint/reporter/json/JsonReporterTest.kt b/ktlint-reporter-json/src/test/kotlin/com/pinterest/ktlint/reporter/json/JsonReporterTest.kt index fb29129d0f..f82aadb712 100644 --- a/ktlint-reporter-json/src/test/kotlin/com/pinterest/ktlint/reporter/json/JsonReporterTest.kt +++ b/ktlint-reporter-json/src/test/kotlin/com/pinterest/ktlint/reporter/json/JsonReporterTest.kt @@ -15,7 +15,9 @@ class JsonReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "<\"&'>" ), false @@ -23,7 +25,9 @@ class JsonReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 2, 1, "rule-2", + 2, + 1, + "rule-2", "And if you see my friend" ), true @@ -32,7 +36,9 @@ class JsonReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 1, 10, "rule-1", + 1, + 10, + "rule-1", "I thought I would again" ), false @@ -40,7 +46,9 @@ class JsonReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 2, 20, "rule-2", + 2, + 20, + "rule-2", "A single thin straight line" ), false @@ -49,7 +57,9 @@ class JsonReporterTest { reporter.onLintError( "/all-corrected.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "I thought we had more time" ), true diff --git a/ktlint-reporter-plain/src/test/kotlin/com/pinterest/ktlint/reporter/plain/PlainReporterTest.kt b/ktlint-reporter-plain/src/test/kotlin/com/pinterest/ktlint/reporter/plain/PlainReporterTest.kt index 80aca441da..0c2bbdbc74 100644 --- a/ktlint-reporter-plain/src/test/kotlin/com/pinterest/ktlint/reporter/plain/PlainReporterTest.kt +++ b/ktlint-reporter-plain/src/test/kotlin/com/pinterest/ktlint/reporter/plain/PlainReporterTest.kt @@ -19,7 +19,9 @@ class PlainReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "<\"&'>" ), false @@ -27,7 +29,9 @@ class PlainReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 2, 1, "rule-2", + 2, + 1, + "rule-2", "And if you see my friend" ), true @@ -36,7 +40,9 @@ class PlainReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 1, 10, "rule-1", + 1, + 10, + "rule-1", "I thought I would again" ), false @@ -44,7 +50,9 @@ class PlainReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 2, 20, "rule-2", + 2, + 20, + "rule-2", "A single thin straight line" ), false @@ -53,7 +61,9 @@ class PlainReporterTest { reporter.onLintError( "/all-corrected.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "I thought we had more time" ), true @@ -79,7 +89,9 @@ class PlainReporterTest { reporter.onLintError( File.separator + "one-fixed-and-one-not.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "<\"&'>" ), false @@ -107,7 +119,9 @@ class PlainReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "<\"&'>" ), false @@ -115,7 +129,9 @@ class PlainReporterTest { reporter.onLintError( "/one-fixed-and-one-not.kt", LintError( - 2, 1, "rule-2", + 2, + 1, + "rule-2", "And if you see my friend" ), true @@ -124,7 +140,9 @@ class PlainReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 1, 10, "rule-1", + 1, + 10, + "rule-1", "I thought I would again" ), false @@ -132,7 +150,9 @@ class PlainReporterTest { reporter.onLintError( "/two-not-fixed.kt", LintError( - 2, 20, "rule-2", + 2, + 20, + "rule-2", "A single thin straight line" ), false @@ -141,7 +161,9 @@ class PlainReporterTest { reporter.onLintError( "/all-corrected.kt", LintError( - 1, 1, "rule-1", + 1, + 1, + "rule-1", "I thought we had more time" ), true diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/EnumEntryNameCaseRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/EnumEntryNameCaseRule.kt index 33197dbb13..945f9d829b 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/EnumEntryNameCaseRule.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/EnumEntryNameCaseRule.kt @@ -27,7 +27,8 @@ class EnumEntryNameCaseRule : Rule("enum-entry-name-case") { if (!name.startsWithUpperCase()) { emit( node.startOffset, - ERROR_MESSAGE, false + ERROR_MESSAGE, + false ) if (autoCorrect) correct(enumEntry, name) @@ -37,7 +38,8 @@ class EnumEntryNameCaseRule : Rule("enum-entry-name-case") { else if (name.contains("_") && name.containsLowerCase()) { emit( node.startOffset, - ERROR_MESSAGE, false + ERROR_MESSAGE, + false ) if (autoCorrect) correct(enumEntry, name) diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NoEmptyFirstLineInMethodBlockRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NoEmptyFirstLineInMethodBlockRule.kt index b0d5d416d8..345980399f 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NoEmptyFirstLineInMethodBlockRule.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/NoEmptyFirstLineInMethodBlockRule.kt @@ -25,7 +25,8 @@ class NoEmptyFirstLineInMethodBlockRule : Rule("no-empty-first-line-in-method-bl if (split.size > 2) { emit( node.startOffset + 1, - "First line in a method block should not be empty", true + "First line in a method block should not be empty", + true ) if (autoCorrect) { (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}") diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithAnnotationsRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithAnnotationsRule.kt index 52308b441b..8509eacc74 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithAnnotationsRule.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithAnnotationsRule.kt @@ -31,7 +31,8 @@ class SpacingBetweenDeclarationsWithAnnotationsRule : Rule("spacing-between-decl if (node.psi.parent.prevSibling is PsiWhiteSpace && node.psi.parent.prevSibling.text == "\n") { emit( node.startOffset, - "Declarations and declarations with annotations should have an empty space between.", true + "Declarations and declarations with annotations should have an empty space between.", + true ) if (autoCorrect) { (node.psi.parent.prevSibling.node as LeafPsiElement).rawReplaceWithText("\n\n") diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithCommentsRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithCommentsRule.kt index 1cdbef677c..c3f2056473 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithCommentsRule.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenDeclarationsWithCommentsRule.kt @@ -28,7 +28,8 @@ class SpacingBetweenDeclarationsWithCommentsRule : Rule("spacing-between-declara if (node.parent.prevSibling is PsiWhiteSpace && node.parent.prevSibling.text == "\n") { emit( node.startOffset, - "Declarations and declarations with comments should have an empty space between.", true + "Declarations and declarations with comments should have an empty space between.", + true ) if (autoCorrect) { (node.parent.prevSibling.node as LeafPsiElement).rawReplaceWithText("\n\n") diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationRuleTest.kt index f654b50d4b..3954d855d8 100644 --- a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationRuleTest.kt +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationRuleTest.kt @@ -111,7 +111,9 @@ class AnnotationRuleTest { ) ).containsExactly( LintError( - 2, 5, "annotation", + 2, + 5, + "annotation", AnnotationRule.multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage ) ) @@ -184,7 +186,10 @@ class AnnotationRuleTest { ) ).containsExactly( LintError( - 2, 28, "annotation", "Missing spacing after @SomeAnnotation(\"value\")" + 2, + 28, + "annotation", + "Missing spacing after @SomeAnnotation(\"value\")" ) ) } @@ -204,7 +209,9 @@ class AnnotationRuleTest { ) ).containsExactly( LintError( - 2, 5, "annotation", + 2, + 5, + "annotation", AnnotationRule.annotationsWithParametersAreNotOnSeparateLinesErrorMessage ) ) @@ -250,11 +257,15 @@ class AnnotationRuleTest { ) ).containsExactly( LintError( - 1, 1, "annotation", + 1, + 1, + "annotation", AnnotationRule.multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage ), LintError( - 1, 1, "annotation", + 1, + 1, + "annotation", AnnotationRule.annotationsWithParametersAreNotOnSeparateLinesErrorMessage ) ) @@ -328,11 +339,15 @@ class AnnotationRuleTest { ) ).containsExactly( LintError( - 2, 5, "annotation", + 2, + 5, + "annotation", AnnotationRule.multipleAnnotationsOnSameLineAsAnnotatedConstructErrorMessage ), LintError( - 2, 5, "annotation", + 2, + 5, + "annotation", AnnotationRule.annotationsWithParametersAreNotOnSeparateLinesErrorMessage ) ) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt index 30ff0a2301..d1e118b9c8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt @@ -22,7 +22,8 @@ class NoBlankLineBeforeRbraceRule : Rule("no-blank-line-before-rbrace") { if (split.size > 2) { emit( node.startOffset + split[0].length + split[1].length + 1, - "Unexpected blank line(s) before \"}\"", true + "Unexpected blank line(s) before \"}\"", + true ) if (autoCorrect) { (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}") diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRule.kt index 01071a81d2..bd4a74c13b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRule.kt @@ -2,9 +2,13 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.COLLECTION_LITERAL_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL +import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.RPAR +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE @@ -12,6 +16,7 @@ import com.pinterest.ktlint.core.ast.children import com.pinterest.ktlint.core.ast.isRoot import com.pinterest.ktlint.core.ast.prevLeaf import com.pinterest.ktlint.core.ast.visit +import kotlin.math.max import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement @@ -37,7 +42,10 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { if (indentSize <= 0) { return } - if (node.elementType == VALUE_PARAMETER_LIST && + val isParameterOrArgumentList = node.elementType == VALUE_PARAMETER_LIST || + // skip if number of arguments is big (we assume it with a magic number of 8) + (node.elementType == VALUE_ARGUMENT_LIST && node.children().filter { it.elementType == VALUE_ARGUMENT }.toList().size <= 8) + if (isParameterOrArgumentList && // skip when there are no parameters node.firstChildNode?.treeNext?.elementType != RPAR && // skip lambda parameters @@ -48,7 +56,7 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { // - maxLineLength exceeded (and separating parameters with \n would actually help) // in addition, "(" and ")" must be on separates line if any of the parameters are (otherwise on the same) val putParametersOnSeparateLines = - node.textContains('\n') || + node.textContainsIgnoringLambda('\n') || // max_line_length exceeded maxLineLength > -1 && (node.column - 1 + node.textLength) > maxLineLength if (putParametersOnSeparateLines) { @@ -70,10 +78,12 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { } } VALUE_PARAMETER, + VALUE_ARGUMENT, RPAR -> { var paramInnerIndentAdjustment = 0 val prevLeaf = child.prevLeaf() - val intendedIndent = if (child.elementType == VALUE_PARAMETER) { + val isParameterOrArgument = child.elementType == VALUE_PARAMETER || child.elementType == VALUE_ARGUMENT + val intendedIndent = if (isParameterOrArgument) { paramIndent } else { indent @@ -108,17 +118,30 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { } } if (paramInnerIndentAdjustment != 0 && - child.elementType == VALUE_PARAMETER + isParameterOrArgument ) { child.visit { n -> if (n.elementType == WHITE_SPACE && n.textContains('\n')) { + val isInCollectionOrFunctionLiteral = + n.treeParent?.elementType == COLLECTION_LITERAL_EXPRESSION || n.treeParent?.elementType == FUNCTION_LITERAL + + // If we're inside a collection literal, let's recalculate the adjustment + // because the items inside the collection should not be subject to the same + // indentation as the brackets. + val adjustment = if (isInCollectionOrFunctionLiteral) { + val expectedPosition = intendedIndent.length + indentSize + expectedPosition - child.column + } else { + paramInnerIndentAdjustment + } + val split = n.text.split("\n") (n as LeafElement).rawReplaceWithText( split.joinToString("\n") { - if (paramInnerIndentAdjustment > 0) { - it + " ".repeat(paramInnerIndentAdjustment) - } else { - it.substring(0, Math.max(it.length + paramInnerIndentAdjustment, 0)) + when { + it.isEmpty() -> it + adjustment > 0 -> it + " ".repeat(adjustment) + else -> it.substring(0, max(it.length + adjustment, 0)) } } ) @@ -152,6 +175,8 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { LPAR -> """Unnecessary newline before "("""" VALUE_PARAMETER -> "Parameter should be on a separate line (unless all parameters can fit a single line)" + VALUE_ARGUMENT -> + "Argument should be on a separate line (unless all arguments can fit a single line)" RPAR -> """Missing newline before ")"""" else -> throw UnsupportedOperationException() } @@ -166,4 +191,11 @@ class ParameterListWrappingRule : Rule("parameter-list-wrapping") { } return "" } + + private fun ASTNode.textContainsIgnoringLambda(char: Char): Boolean { + return children() + .flatMap { if (it.elementType == VALUE_ARGUMENT) it.children() else sequenceOf(it) } + .filter { it.elementType != LAMBDA_EXPRESSION } + .any { it.textContains(char) } + } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt index 6ce5504be2..747918fda3 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt @@ -163,6 +163,111 @@ class ParameterListWrappingRuleTest { ) } + @Test + fun testLintFunctionParameterInconsistency() { + assertThat( + ParameterListWrappingRule().lint( + """ + fun f( + a: Any, + b: Any, c: Any + ) + """.trimIndent() + ) + ).isEqualTo( + listOf( + LintError(3, 13, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)") + ) + ) + } + + @Test + fun testLintArgumentInconsistency() { + assertThat( + ParameterListWrappingRule().lint( + """ + val x = f( + a, + b, c + ) + """.trimIndent() + ) + ).isEqualTo( + listOf( + LintError(3, 8, "parameter-list-wrapping", "Argument should be on a separate line (unless all arguments can fit a single line)") + ) + ) + } + + @Test + fun testFormatArgumentInconsistency() { + assertThat( + ParameterListWrappingRule().format( + """ + val x = f( + a, + b, c + ) + """.trimIndent() + ) + ).isEqualTo( + """ + val x = f( + a, + b, + c + ) + """.trimIndent() + ) + } + + @Test + fun testFormatArgumentsWithNestedCalls() { + assertThat( + ParameterListWrappingRule().format( + """ + val x = test( + one("a", "b", + "c"), + "Two", "Three", "Four" + ) + """.trimIndent() + ) + ).isEqualTo( + """ + val x = test( + one( + "a", + "b", + "c" + ), + "Two", + "Three", + "Four" + ) + """.trimIndent() + ) + } + + @Test + fun testLintArgumentListWhenMaxLineLengthExceeded() { + assertThat( + ParameterListWrappingRule().lint( + """ + val x = f(a, b, c) + """.trimIndent(), + userData = mapOf("max_line_length" to "10") + ) + ).isEqualTo( + listOf( + LintError(1, 11, "parameter-list-wrapping", "Argument should be on a separate line (unless all arguments can fit a single line)"), + LintError(1, 14, "parameter-list-wrapping", "Argument should be on a separate line (unless all arguments can fit a single line)"), + LintError(1, 17, "parameter-list-wrapping", "Argument should be on a separate line (unless all arguments can fit a single line)"), + LintError(1, 18, "parameter-list-wrapping", """Missing newline before ")"""") + ) + ) + } + @Test fun testLintFunctionParameterListWhenMaxLineLengthExceeded() { assertThat( @@ -244,6 +349,130 @@ class ParameterListWrappingRuleTest { ).isEmpty() } + @Test + fun testLambdaArgumentsAreIgnored() { + assertThat( + ParameterListWrappingRule().lint( + """ + abstract class A(init: String.() -> Int) + class B : A({ + toInt() + }) + + fun test(a: Any, b: (Any) -> Any) { + test(a = "1", b = { + it.toString() + }) + } + """.trimIndent() + ) + ).isEmpty() + } + + @Test + fun testFormatWithLambdaArguments() { + assertThat( + ParameterListWrappingRule().format( + """ + abstract class A(init: String.() -> Int) + class B : A({ + toInt() + }) + + fun test(a: Any, b: (Any) -> Any) { + test( + a = "1", b = { + it.toString() + }) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test(a = "1", b = { + it.toString() + }, c = 123) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test(a = "1", b = { + it.toString() + }, + c = 123) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test("1", + { val x = it.toString(); x }, 123) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test( + "1", + { + f(1, + { "stuff" }, 3) + }, + 123 + ) + } + """.trimIndent() + ) + ).isEqualTo( + """ + abstract class A(init: String.() -> Int) + class B : A({ + toInt() + }) + + fun test(a: Any, b: (Any) -> Any) { + test( + a = "1", + b = { + it.toString() + } + ) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test(a = "1", b = { + it.toString() + }, c = 123) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test( + a = "1", + b = { + it.toString() + }, + c = 123 + ) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test( + "1", + { val x = it.toString(); x }, + 123 + ) + } + + fun test(a: Any, b: (Any) -> Any, c: Any) { + test( + "1", + { + f( + 1, + { "stuff" }, + 3 + ) + }, + 123 + ) + } + """.trimIndent() + ) + } + @Test fun testFormatPreservesIndent() { assertThat( @@ -272,17 +501,14 @@ class ParameterListWrappingRuleTest { } @Test - fun testFormatPreservesIndentWithAnnotations() { + fun testFormatPreservesIndentWithAnnotationsOnSingleLine() { assertThat( ParameterListWrappingRule().format( """ class A { fun f(@Annotation a: Any, - @Annotation([ - "v1", - "v2" - ]) + @Annotation(["v1", "v2"]) b: Any, c: Any = false, @@ -297,10 +523,50 @@ class ParameterListWrappingRuleTest { fun f( @Annotation a: Any, - @Annotation([ - "v1", - "v2" - ]) + @Annotation(["v1", "v2"]) + b: Any, + c: Any = + false, + @Annotation d: Any + ) { + } + } + """.trimIndent() + ) + } + + @Test + fun testFormatPreservesIndentWithAnnotationsOnMultiLine() { + assertThat( + ParameterListWrappingRule().format( + """ + class A { + fun f(@Annotation + a: Any, + @Annotation([ + "v1", + "v2" + ]) + b: Any, + c: Any = + false, + @Annotation d: Any) { + } + } + """.trimIndent() + ) + ).isEqualTo( + """ + class A { + fun f( + @Annotation + a: Any, + @Annotation( + [ + "v1", + "v2" + ] + ) b: Any, c: Any = false, @@ -523,4 +789,18 @@ class ParameterListWrappingRuleTest { """.trimIndent() ) } + + @Test + fun testLintVarargIsIgnored() { + assertThat( + ParameterListWrappingRule().lint( + """ + private val tokenSet = TokenSet.create( + MUL, PLUS, MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ, + EXCLEQ, ANDAND, OROR, ELVIS, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, ARROW + ) + """.trimIndent() + ) + ).isEmpty() + } } diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt index a51429cfb9..e6d53fc861 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/Main.kt @@ -437,7 +437,9 @@ class KtlintCommandLine { when (e) { is ParseException -> LintError( - e.line, e.col, "", + e.line, + e.col, + "", "Not a valid Kotlin file (${e.message?.toLowerCase()})" ) is RuleExecutionException -> { @@ -446,7 +448,9 @@ class KtlintCommandLine { e.printStackTrace(System.err) } LintError( - e.line, e.col, "", + e.line, + e.col, + "", "Internal Error (${e.ruleId}). " + "Please create a ticket at https://github.com/pinterest/ktlint/issues " + "(if possible, provide the source code that triggered an error)" diff --git a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/IntellijIDEAIntegration.kt b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/IntellijIDEAIntegration.kt index f1f5ceca43..6c7968fed8 100644 --- a/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/IntellijIDEAIntegration.kt +++ b/ktlint/src/main/kotlin/com/pinterest/ktlint/internal/IntellijIDEAIntegration.kt @@ -82,13 +82,15 @@ object IntellijIDEAIntegration { Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*") .iterate( Paths.get(home, "Library", "Preferences"), - Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY + Glob.IterationOption.SKIP_CHILDREN, + Glob.IterationOption.DIRECTORY ).asSequence() + // linux/windows Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config") .iterate( Paths.get(home), - Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY + Glob.IterationOption.SKIP_CHILDREN, + Glob.IterationOption.DIRECTORY ).asSequence() ( paths.flatMap { dir -> @@ -177,7 +179,8 @@ object IntellijIDEAIntegration { val xpath = XPathFactory.newInstance().newXPath() var cis = xpath.evaluate( "//component[@name='CodeInsightSettings']", - doc, XPathConstants.NODE + doc, + XPathConstants.NODE ) as Element? if (cis == null) { cis = doc.createElement("component") @@ -186,7 +189,8 @@ object IntellijIDEAIntegration { } var oiotf = xpath.evaluate( "//option[@name='OPTIMIZE_IMPORTS_ON_THE_FLY']", - cis, XPathConstants.NODE + cis, + XPathConstants.NODE ) as Element? if (oiotf == null) { oiotf = doc.createElement("option") @@ -215,7 +219,8 @@ object IntellijIDEAIntegration { val xpath = XPathFactory.newInstance().newXPath() var cis = xpath.evaluate( "//component[@name='CodeInsightWorkspaceSettings']", - doc, XPathConstants.NODE + doc, + XPathConstants.NODE ) as Element? if (cis == null) { cis = doc.createElement("component") @@ -224,7 +229,8 @@ object IntellijIDEAIntegration { } var oiotf = xpath.evaluate( "//option[@name='optimizeImportsOnTheFly']", - cis, XPathConstants.NODE + cis, + XPathConstants.NODE ) as Element? if (oiotf == null) { oiotf = doc.createElement("option")