diff --git a/CHANGELOG.md b/CHANGELOG.md index 906397fb86..aa873097c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,13 @@ * Rewrite `quick_discouraged_call` rule with SwiftSyntax. [SimplyDanny](https://github.com/SimplyDanny) +* `multiline_parameters`: add optional checks for **function call arguments**. + When `check_calls` is enabled, the rule enforces that call arguments are either + all on one line (subject to `max_number_of_single_line_parameters`) or + one per line, mirroring the behavior for function/initializer declarations. + Trailing closures are ignored by default. + [GandaLF2006](https://github.com/GandaLF2006) + ### Bug Fixes * Ensure that header matched against always end in a newline in `file_header` rule. diff --git a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/MultilineParametersConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/MultilineParametersConfiguration.swift index 22c3cd470c..25491868cf 100644 --- a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/MultilineParametersConfiguration.swift +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/MultilineParametersConfiguration.swift @@ -10,8 +10,19 @@ struct MultilineParametersConfiguration: SeverityBasedRuleConfiguration { private(set) var allowsSingleLine = true @ConfigurationElement(key: "max_number_of_single_line_parameters") private(set) var maxNumberOfSingleLineParameters: Int? + @ConfigurationElement(key: "check_calls") + private(set) var checkCalls = false func validate() throws { + if checkCalls, maxNumberOfSingleLineParameters == nil { + Issue.inconsistentConfiguration( + ruleID: Parent.identifier, + message: """ + Option '\($checkCalls.key)' has no effect when \ + '\($maxNumberOfSingleLineParameters.key)' is nil. + """ + ).print() + } guard let maxNumberOfSingleLineParameters else { return } @@ -32,5 +43,15 @@ struct MultilineParametersConfiguration: SeverityBasedRuleConfiguration { """ ).print() } + + if checkCalls, !allowsSingleLine { + Issue.inconsistentConfiguration( + ruleID: Parent.identifier, + message: """ + Option '\($checkCalls.key)' has no effect when \ + '\($allowsSingleLine.key)' is false. + """ + ).print() + } } } diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRule.swift b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRule.swift index 7ff00199d6..c50a725daf 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRule.swift @@ -7,7 +7,9 @@ struct MultilineParametersRule: Rule { static let description = RuleDescription( identifier: "multiline_parameters", name: "Multiline Parameters", - description: "Functions and methods parameters should be either on the same line, or one per line", + description: """ + Functions, initializers, and function call arguments should be either on the same line, or one per line + """, kind: .style, nonTriggeringExamples: MultilineParametersRuleExamples.nonTriggeringExamples, triggeringExamples: MultilineParametersRuleExamples.triggeringExamples @@ -28,8 +30,28 @@ private extension MultilineParametersRule { } } + override func visitPost(_ node: FunctionCallExprSyntax) { + guard node.arguments.isNotEmpty else { return } + guard configuration.checkCalls else { return } + guard node.trailingClosure == nil else { return } + + if containsViolation(for: node.arguments) { + let anchor = node.calledExpression.positionAfterSkippingLeadingTrivia + violations.append(anchor) + } + } + private func containsViolation(for signature: FunctionSignatureSyntax) -> Bool { let parameterPositions = signature.parameterClause.parameters.map(\.positionAfterSkippingLeadingTrivia) + return containsViolation(parameterPositions: parameterPositions) + } + + private func containsViolation(for arguments: LabeledExprListSyntax) -> Bool { + let argumentPositions = arguments.map(\.positionAfterSkippingLeadingTrivia) + return containsViolation(parameterPositions: argumentPositions) + } + + private func containsViolation(parameterPositions: [AbsolutePosition]) -> Bool { guard parameterPositions.isNotEmpty else { return false } diff --git a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRuleExamples.swift b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRuleExamples.swift index 250713e900..df910b59e0 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRuleExamples.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Style/MultilineParametersRuleExamples.swift @@ -213,6 +213,20 @@ internal struct MultilineParametersRuleExamples { param3: [String] ) { } """, configuration: ["max_number_of_single_line_parameters": 2]), + Example(""" + foo( + param1: "param1", + param2: false, + param3: [] + ) + """, configuration: ["max_number_of_single_line_parameters": 2]), + Example(""" + foo(param1: 1, + param2: false, + param3: []) + """, configuration: ["max_number_of_single_line_parameters": 1]), + Example("foo(param1: 1, param2: false)", + configuration: ["max_number_of_single_line_parameters": 2]), ] static let triggeringExamples: [Example] = [ @@ -360,6 +374,27 @@ internal struct MultilineParametersRuleExamples { """, configuration: ["max_number_of_single_line_parameters": 3]), Example(""" func ↓foo(param1: Int, param2: Bool, param3: [String]) { } - """, configuration: ["max_number_of_single_line_parameters": 2]), + """, configuration: [ + "max_number_of_single_line_parameters": 2, + "check_calls": true, + ]), + Example("↓foo(param1: 1, param2: false, param3: [])", + configuration: [ + "max_number_of_single_line_parameters": 2, + "check_calls": true, + ]), + Example(""" + func ↓foo(param1: Int, + param2: Bool, param3: [String]) { } + """, configuration: [ + "max_number_of_single_line_parameters": 3, + "check_calls": true, + ]), + Example(""" + ↓foo(param1: Int, param2: Bool, param3: [String]) + """, configuration: [ + "max_number_of_single_line_parameters": 2, + "check_calls": true, + ]), ] } diff --git a/Tests/IntegrationTests/default_rule_configurations.yml b/Tests/IntegrationTests/default_rule_configurations.yml index e324f818ff..61ae6cfff4 100644 --- a/Tests/IntegrationTests/default_rule_configurations.yml +++ b/Tests/IntegrationTests/default_rule_configurations.yml @@ -684,6 +684,7 @@ multiline_literal_brackets: multiline_parameters: severity: warning allows_single_line: true + check_calls: false meta: opt-in: true correctable: false