Add auto-correction, indentation config, and multi-violation support to multiline_call_arguments#6745
Conversation
b183a6a to
78a2da1
Compare
|
@SimplyDanny Let's add correctable for The CI failure on oss_scan is not related to this PR — it's a danger bot failing to post a comment due to the repository's interaction limits (422 - Interactions on this repository have been restricted to prior contributors only) |
| /// | ||
| /// - `tab`: Use tab character for indentation | ||
| /// - `spaces(count:)`: Use the specified number of spaces | ||
| enum IndentationStyle: Hashable, Sendable { |
There was a problem hiding this comment.
There is already a IndentationStyle that can be configured in configuration files and is supposed to apply globally. Can we use it in this rule as well?
There was a problem hiding this comment.
Good point — done. The rule-local IndentationStyle has been replaced by the
shared IndentationStyle type from SwiftLintCore, which is the same type
used by the global indentation configuration. This required two changes:
- Moving the type to
SwiftLintCore(the lowest common dependency) and adding
AcceptableByConfigurationElementconformance +indentationStringproperty
so both the rule andConfiguration.indentationcan use it. - Aligning the string value to
"tabs"(matching the global config format)
instead of the previous"tab".
f16bd4c to
8b5b043
Compare
Generated by 🚫 Danger |
3c09c9b to
c2b5aa8
Compare
9510ef7 to
0cdc2a7
Compare
7b65161 to
9a91acd
Compare
|
@SimplyDanny It would be great if you could review the PR :) |
9a91acd to
0bd454f
Compare
| /// Indentation style for corrected lines. | ||
| /// Can be an integer (number of spaces) or the string "tabs". | ||
| @ConfigurationElement(key: "indentation") | ||
| private(set) var indentationStyle: IndentationStyle = .spaces(count: 4) |
There was a problem hiding this comment.
Can the global option not be reused?
There was a problem hiding this comment.
I looked into this, and unfortunately the global indentation is not currently accessible from rules. Here's what I found:
Linterstores the fullConfiguration(includingindentation), but when invoking rules it only passesSwiftLintFile+ per-rule config tovalidate(file:using:compilerArguments:).- Neither
SwiftLintFile,ViolationsSyntaxVisitor, norRuleStoragehold a reference to the globalConfiguration. - The global
configuration.indentationis consumed only by --format inLintOrAnalyzeCommand.swift— a separate code path that reformats file text directly without invoking any rules.
Is there an existing mechanism for rules to access the global Configuration.indentation that I'm overlooking? If so, could you point me in the right direction?
If not, reusing it would require threading the global indentation through the rule execution path — e.g. by adding it to SwiftLintFile or ViolationsSyntaxVisitor. This is a framework-wide change that affects all rules. I'd suggest doing this as a separate PR (ideally before merging this one) — once the global indentation is available to rules, the per-rule option can be removed in favor of it. For now, the rule reuses the shared IndentationStyle type from SwiftLintCore (the same type the global config uses), so it's ready to switch over when the plumbing is in place
There was a problem hiding this comment.
I looked into this further and found a way to make it work without a separate PR.
The global indentation was indeed not accessible from rules — Linter stores the full Configuration, but validate(file:using:compilerArguments:) only receives SwiftLintFile + per-rule config. However, SwiftLint already has CurrentRule with @TaskLocal properties for exactly this kind of cross-cutting concern (used for identifier and allowSourceKitRequestWithoutRule).
I added @TaskLocal public static var indentation: IndentationStyle? to CurrentRule and set it at the three rule-execution chokepoints in Linter.swift (lint, collect, correct) from Configuration.indentation. The rule reads it via CurrentRule.indentation, falling back to IndentationStyle.default when not set (e.g., in unit tests outside a Linter context).
The per-rule indentation option has been removed — the rule now uses the global indentation setting from .swiftlint.yml. End-to-end tests confirm the full pipeline: Configuration(indentation: .tabs) → Linter → correct → tab-indented output.
| /// ```yaml | ||
| /// multiline_call_arguments: | ||
| /// indentation: "tabs" | ||
| /// ``` |
There was a problem hiding this comment.
I don't think we need the examples here.
| } | ||
| } | ||
| } | ||
| typealias IndentationStyle = SwiftLintCore.IndentationStyle |
There was a problem hiding this comment.
Do we need this alias? I think the file can just be removed.
| @SwiftSyntaxRule(optIn: true) | ||
| @SwiftSyntaxRule(correctable: true, optIn: true) | ||
| struct MultilineCallArgumentsRule: Rule { | ||
| /// Configuration for the multiline_call_arguments rule. |
There was a problem hiding this comment.
We don't the docs here ...
| /// Configuration for the multiline_call_arguments rule. | ||
| var configuration = MultilineCallArgumentsConfiguration() | ||
|
|
||
| /// Reasons for violations reported by this rule. |
| static let nonTriggeringExamples: [Example] = [ | ||
| // MARK: - All configuration options shown | ||
| Example(""" | ||
| # Configuration: allows_single_line: false, indentation: 4 |
There was a problem hiding this comment.
Can be omitted. Configuration will be part of the example in the rendered docs anyway.
| """, | ||
| configuration: ["allows_single_line": false, "indentation": "tabs"] | ||
| ), | ||
| // MARK: - Comments between arguments (violation detected, no auto-correction) |
There was a problem hiding this comment.
Corrections are not checked here, are they? The example (plus the one below) might be useless then.
| MultilineCallArgumentsRule.Reason.eachArgumentMustStartOnOwnLine | ||
| ) | ||
| let reason = MultilineCallArgumentsRule.Reason.eachArgumentMustStartOnOwnLine | ||
| let violations1 = try validate("foo(\n a: 1, b: 2,\n c: 3\n)") |
There was a problem hiding this comment.
I like the block style for multiline examples more, actually.
| // MARK: - Auto-correction with comments | ||
|
|
||
| extension MultilineCallArgumentsRuleTests { | ||
| func testCorrection_withCommentBetweenArguments_doesNotAutoCorrect() throws { |
There was a problem hiding this comment.
Most of these examples could be part of the (documented) example's list. You may use excludeFromDocumentation: true if you'd just like to have them for testing purposes.
There was a problem hiding this comment.
Moved the redundant correction cases (single-line correction, custom indent, multiline, nested suppression, open paren on new line, etc.) into MultilineCallArgumentsRuleExamples.corrections with excludeFromDocumentation: true
|
|
||
| extension MultilineCallArgumentsRuleTests { | ||
| func testCorrection_withCRLFLineEndings_correctsIndentation() throws { | ||
| let contents = "foo(\n a: 1, b: 2,\n c: 3\n)".replacingOccurrences(of: "\n", with: "\r\n") |
There was a problem hiding this comment.
Either use a string block or put \r\n directly.
There was a problem hiding this comment.
Replaced replacingOccurrences(of: "\n", with: "\r\n") with a direct \r\n string literal.
ebc98ef to
f694a41
Compare
…to multiline_call_arguments
Thread the global into rules via a new on , set at the three rule-execution chokepoints in (lint, collect, correct). Remove the per-rule option from in favor of the global setting.
8b03add to
1d6eaeb
Compare
Summary
Enhances the
multiline_call_argumentsopt-in rule with auto-correction, a newindentationconfiguration option, and multi-violation detection in a single pass.Auto-correction (
--fix))moves to its own line at the call's base indent\n+ indent in the appropriate range--fixpassindentationconfigurationindentation(integer ≥ 1 for spaces, or the string"tab"; default:4)0and negative values are invalid and cause a configuration errorMulti-violation support
duplicateArgumentStartLineViolationandnewlineAfterCommaViolationreturned at the first match, requiring repeated--fixpasses for calls with multiple same-line argument pairsreasonedViolationsreturns[ReasonedRuleViolation])Documentation
RuleDescription.descriptioninto concisedescription(what) + structuredrationale(why/how) with markdown sections