Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@

### Experimental

* None.
* Add auto-correction and multi-violation support to the
`multiline_call_arguments` rule. Auto-correction reformats single-line
calls into multi-line form using the global `indentation` setting from
`.swiftlint.yml`. The per-rule `indentation` option has been removed in
favor of the global one. The rule detects all violations in a single
pass (previously required repeated `--fix`); safely handles nested
calls by suppressing overlapping inner corrections.
[GandaLF2006](https://github.com/GandaLF2006)

### Enhancements

Expand Down
316 changes: 236 additions & 80 deletions Source/SwiftLintBuiltInRules/Rules/Lint/MultilineCallArgumentsRule.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@
// swiftlint:disable type_body_length
struct MultilineCallArgumentsRuleExamples {
static let nonTriggeringExamples: [Example] = [
// MARK: - All configuration options shown
Example("""
foo(
param1: 1,
param2: false,
param3: []
)
""",
configuration: ["allows_single_line": false]
),
Example("""
foo(param1: 1, param2: false)
""",
configuration: ["max_number_of_single_line_parameters": 2]
),
Example("""
foo(
\tparam1: 1,
\tparam2: false
)
""",
configuration: ["allows_single_line": false]
),

// MARK: - Baseline: multi-line OK
Example("""
foo(param1: 1,
Expand Down Expand Up @@ -404,6 +428,14 @@ struct MultilineCallArgumentsRuleExamples {
)
"""),

// MARK: - Multi-line: multiple duplicate argument start lines
Example("""
foo(
a: 1, ↓b: 2,
c: 3, ↓d: 4
)
"""),

// MARK: - Enum-case constructor calls are linted like normal calls
Example("""
enum EnumCase {
Expand Down Expand Up @@ -508,21 +540,296 @@ struct MultilineCallArgumentsRuleExamples {
""",
configuration: ["max_number_of_single_line_parameters": 2]
),
// MARK: - Tab indentation (uses global indentation setting)
Example(
"foo(param1: 1, ↓param2: false)",
configuration: ["allows_single_line": false]
),
Example("""
func foo(a: Int, b: Int, c: Int) -> Int { a + b + c }
enum EnumCase: Error { case caseOne(Int, Int, Int, Int) }

func mayThrow() throws {
class Test {
func method() {
if condition {
foo(param1: 1, ↓param2: false)
}
}
}
""",
configuration: ["allows_single_line": false]
),
// MARK: - Comments between arguments (violation still detected)
Example(
"foo(param1: 1, /* comment */ ↓param2: false)",
configuration: ["allows_single_line": false]
),
// MARK: - Nested single-line calls (both violate)
Example(
"foo(bar(1, ↓2), ↓baz: 3)",
configuration: ["allows_single_line": false]
),
]

do {
try mayThrow()
} catch let EnumCase.caseOne(_, _, _, _) {
_ = foo(a: 1, b: 2, ↓c: 3)
static let corrections: [Example: Example] = [
// MARK: - Single-line corrections
Example(
"foo(param1: 1, ↓param2: false)",
configuration: ["allows_single_line": false]
): Example("""
foo(
param1: 1,
param2: false
)
"""),
Example(
"foo(param1: 1, param2: false, ↓param3: [])",
configuration: ["max_number_of_single_line_parameters": 2]
): Example("""
foo(
param1: 1,
param2: false,
param3: []
)
"""),
Example(
"foo(1, ↓2)",
configuration: ["allows_single_line": false]
): Example("""
foo(
1,
2
)
"""),
Example(
"foo(1, b: 2, ↓3)",
configuration: ["max_number_of_single_line_parameters": 2]
): Example("""
foo(
1,
b: 2,
3
)
"""),
// MARK: - Multi-line: duplicate argument start line
Example("""
foo(
a: 1, ↓b: 2,
c: 3
)
"""): Example("""
foo(
a: 1,
b: 2,
c: 3
)
"""),
// MARK: - Multi-line: four args, two on same line
Example("""
foo(
a: 1, ↓b: 2,
c: 3,
d: 4
)
"""): Example("""
foo(
a: 1,
b: 2,
c: 3,
d: 4
)
"""),
// MARK: - Multi-line: multiple duplicate argument start lines
Example("""
foo(
a: 1, ↓b: 2,
c: 3, ↓d: 4
)
"""): Example("""
foo(
a: 1,
b: 2,
c: 3,
d: 4
)
"""),
// MARK: - Multi-line: newline after comma
Example("""
foo(
a: 1,
b: 2, ↓c: 3
)
"""): Example("""
foo(
a: 1,
b: 2,
c: 3
)
"""),
// MARK: - Call with trailing closure
Example(
"foo(a: 1, ↓b: 2) { _ in }",
configuration: ["allows_single_line": false]
): Example("""
foo(
a: 1,
b: 2
) { _ in }
"""),
// MARK: - Closure as argument
Example(
"foo(a: 1, ↓b: { x in x })",
configuration: ["allows_single_line": false]
): Example("""
foo(
a: 1,
b: { x in x }
)
"""),
// MARK: - Enum case call
Example(
"Enum.foo(param1: 1, ↓param2: false)",
configuration: ["allows_single_line": false]
): Example("""
Enum.foo(
param1: 1,
param2: false
)
"""),
// MARK: - Tuple argument (stays on same line)
Example(
"foo(a: (1, 2), ↓b: 3)",
configuration: ["max_number_of_single_line_parameters": 1]
): Example("""
foo(
a: (1, 2),
b: 3
)
"""),
// MARK: - Enum-case constructor call
Example(
"EnumCase.first(one: 1, ↓two: 2)",
configuration: ["allows_single_line": false]
): Example("""
EnumCase.first(
one: 1,
two: 2
)
"""),
// MARK: - Multi-line with tuple argument
Example("""
foo(
a: (1, 2), ↓b: 3
)
""", configuration: ["max_number_of_single_line_parameters": 1]): Example("""
foo(
a: (1, 2),
b: 3
)
"""),
// MARK: - Nested indentation (4 spaces default)
Example("""
class Test {
func method() {
if condition {
foo(param1: 1, ↓param2: false)
}
}
}
""",
configuration: ["max_number_of_single_line_parameters": 2]
),
configuration: ["allows_single_line": false]
): Example("""
class Test {
func method() {
if condition {
foo(
param1: 1,
param2: false
)
}
}
}
"""),
// MARK: - Nested calls (inner call already correct, outer has violation)
Example(
"foo(bar(1), ↓baz: 3)",
configuration: ["allows_single_line": false]
): Example("""
foo(
bar(1),
baz: 3
)
"""),
// MARK: - Nested multiline (inner call already multiline, outer duplicate start line)
Example("""
foo(
bar(
1,
2
), ↓baz: 3
)
"""): Example("""
foo(
bar(
1,
2
),
baz: 3
)
"""),
// MARK: - Nested single-line calls (inner correction suppressed)
Example(
"foo(bar(1, ↓2), ↓baz: 3)",
configuration: ["allows_single_line": false],
excludeFromDocumentation: true
): Example("""
foo(
bar(1, 2),
baz: 3
)
"""),
Example(
"grandFoo(singleArgFoo(bar(1, ↓2)), ↓baz: 3)",
configuration: ["allows_single_line": false],
excludeFromDocumentation: true
): Example("""
grandFoo(
singleArgFoo(bar(1, 2)),
baz: 3
)
"""),
Example(
"foo(bar(1, ↓2, ↓3), ↓baz: 4, ↓qux: 5)",
configuration: ["max_number_of_single_line_parameters": 2],
excludeFromDocumentation: true
): Example("""
foo(
bar(1, 2, 3),
baz: 4,
qux: 5
)
"""),
// MARK: - Nested with comments (inner correction allowed)
Example(
"foo(bar(1, ↓2) /* c */, ↓baz: 3)",
configuration: ["allows_single_line": false],
excludeFromDocumentation: true
): Example("""
foo(bar(
1,
2
) /* c */, baz: 3)
"""),
// MARK: - Open paren on new line
Example("""
foo(
a: 1, ↓b: 2)
""",
configuration: ["allows_single_line": false],
excludeFromDocumentation: true
): Example("""
foo(
a: 1,
b: 2
)
"""),
]
}
// swiftlint:enable type_body_length
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import SwiftLintCore

/// Configuration for the `multiline_call_arguments` rule.
///
/// This configuration controls how function calls with multiple arguments should be formatted.
@AutoConfigParser
struct MultilineCallArgumentsConfiguration: SeverityBasedRuleConfiguration {
@ConfigurationElement(key: "severity")
Expand Down
4 changes: 4 additions & 0 deletions Source/SwiftLintCore/Models/CurrentRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ public enum CurrentRule {
/// Allows specific SourceKit requests to be made outside of rule execution context.
/// This should only be used for essential operations like getting the Swift version.
@TaskLocal public static var allowSourceKitRequestWithoutRule = false

/// The global indentation style from the top-level configuration, made available to rules
/// without modifying function signatures throughout the codebase.
@TaskLocal public static var indentation: IndentationStyle?
}
Loading