mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
Add new multiline_call_arguments rule (#6223)
Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
This commit is contained in:
@@ -32,6 +32,7 @@ disabled_rules:
|
||||
- missing_docs
|
||||
- multiline_arguments
|
||||
- multiline_arguments_brackets
|
||||
- multiline_call_arguments
|
||||
- multiline_function_chains
|
||||
- multiline_parameters_brackets
|
||||
- no_extension_access_modifier
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
* Add new `unneeded_escaping` rule that detects closure parameters marked with
|
||||
`@escaping` that are never stored or captured escapingly.
|
||||
[SimplyDanny](https://github.com/SimplyDanny)
|
||||
|
||||
* Add `multiline_call_arguments` opt-in rule to enforce consistent multiline
|
||||
formatting for function and method call arguments.
|
||||
[GandaLF2006](https://github.com/GandaLF2006)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ public let builtInRules: [any Rule.Type] = [
|
||||
ModifierOrderRule.self,
|
||||
MultilineArgumentsBracketsRule.self,
|
||||
MultilineArgumentsRule.self,
|
||||
MultilineCallArgumentsRule.self,
|
||||
MultilineFunctionChainsRule.self,
|
||||
MultilineLiteralBracketsRule.self,
|
||||
MultilineParametersBracketsRule.self,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import SwiftLintCore
|
||||
import SwiftSyntax
|
||||
|
||||
@SwiftSyntaxRule(optIn: true)
|
||||
struct MultilineCallArgumentsRule: Rule {
|
||||
var configuration = MultilineCallArgumentsConfiguration()
|
||||
|
||||
static let description = RuleDescription(
|
||||
identifier: "multiline_call_arguments",
|
||||
name: "Multiline Call Arguments",
|
||||
description: "Call should have each parameter on a separate line",
|
||||
kind: .style,
|
||||
nonTriggeringExamples: [
|
||||
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]),
|
||||
Example(
|
||||
"Enum.foo(param1: 1, param2: false)",
|
||||
configuration: ["max_number_of_single_line_parameters": 2]),
|
||||
Example("foo(param1: 1)", configuration: ["allows_single_line": false]),
|
||||
Example("Enum.foo(param1: 1)", configuration: ["allows_single_line": false]),
|
||||
Example(
|
||||
"Enum.foo(param1: 1, param2: 2, param3: 3)",
|
||||
configuration: ["allows_single_line": true]),
|
||||
Example("""
|
||||
foo(
|
||||
param1: 1,
|
||||
param2: 2,
|
||||
param3: 3
|
||||
)
|
||||
""",
|
||||
configuration: ["allows_single_line": false]),
|
||||
],
|
||||
triggeringExamples: [
|
||||
Example(
|
||||
"↓foo(param1: 1, param2: false, param3: [])",
|
||||
configuration: ["max_number_of_single_line_parameters": 2]),
|
||||
Example(
|
||||
"↓Enum.foo(param1: 1, 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": 3]),
|
||||
Example("""
|
||||
↓Enum.foo(param1: 1, param2: false,
|
||||
param3: [])
|
||||
""",
|
||||
configuration: ["max_number_of_single_line_parameters": 3]),
|
||||
Example("↓foo(param1: 1, param2: false)", configuration: ["allows_single_line": false]),
|
||||
Example("↓Enum.foo(param1: 1, param2: false)", configuration: ["allows_single_line": false]),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
private extension MultilineCallArgumentsRule {
|
||||
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
|
||||
override func visitPost(_ node: FunctionCallExprSyntax) {
|
||||
if containsViolation(parameterPositions: node.arguments.map(\.positionAfterSkippingLeadingTrivia)) {
|
||||
violations.append(node.calledExpression.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
}
|
||||
|
||||
private func containsViolation(parameterPositions: [AbsolutePosition]) -> Bool {
|
||||
var numberOfParameters = 0
|
||||
var linesWithParameters: Set<Int> = []
|
||||
var hasMultipleParametersOnSameLine = false
|
||||
|
||||
for position in parameterPositions {
|
||||
let line = locationConverter.location(for: position).line
|
||||
|
||||
if !linesWithParameters.insert(line).inserted {
|
||||
hasMultipleParametersOnSameLine = true
|
||||
}
|
||||
|
||||
numberOfParameters += 1
|
||||
}
|
||||
|
||||
if linesWithParameters.count == 1 {
|
||||
guard configuration.allowsSingleLine else {
|
||||
return numberOfParameters > 1
|
||||
}
|
||||
|
||||
if let maxNumberOfSingleLineParameters = configuration.maxNumberOfSingleLineParameters {
|
||||
return numberOfParameters > maxNumberOfSingleLineParameters
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return hasMultipleParametersOnSameLine
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import SwiftLintCore
|
||||
|
||||
@AutoConfigParser
|
||||
struct MultilineCallArgumentsConfiguration: SeverityBasedRuleConfiguration {
|
||||
typealias Parent = MultilineCallArgumentsRule
|
||||
|
||||
@ConfigurationElement(key: "severity")
|
||||
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
|
||||
@ConfigurationElement(key: "allows_single_line")
|
||||
private(set) var allowsSingleLine = true
|
||||
@ConfigurationElement(key: "max_number_of_single_line_parameters")
|
||||
private(set) var maxNumberOfSingleLineParameters: Int?
|
||||
|
||||
func validate() throws(Issue) {
|
||||
guard let maxNumberOfSingleLineParameters else {
|
||||
return
|
||||
}
|
||||
guard maxNumberOfSingleLineParameters >= 1 else {
|
||||
throw Issue.inconsistentConfiguration(
|
||||
ruleID: Parent.identifier,
|
||||
message: "Option '\($maxNumberOfSingleLineParameters.key)' should be >= 1."
|
||||
)
|
||||
}
|
||||
|
||||
if maxNumberOfSingleLineParameters > 1, !allowsSingleLine {
|
||||
throw Issue.inconsistentConfiguration(
|
||||
ruleID: Parent.identifier,
|
||||
message: """
|
||||
Option '\($maxNumberOfSingleLineParameters.key)' has no effect when \
|
||||
'\($allowsSingleLine.key)' is false.
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,12 @@ final class MultilineArgumentsRuleGeneratedTests: SwiftLintTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
final class MultilineCallArgumentsRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineCallArgumentsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class MultilineFunctionChainsRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineFunctionChainsRule.description)
|
||||
@@ -150,9 +156,3 @@ final class NSNumberInitAsFunctionReferenceRuleGeneratedTests: SwiftLintTestCase
|
||||
verifyRule(NSNumberInitAsFunctionReferenceRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class NSObjectPreferIsEqualRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(NSObjectPreferIsEqualRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
@testable import SwiftLintCore
|
||||
import TestHelpers
|
||||
|
||||
final class NSObjectPreferIsEqualRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(NSObjectPreferIsEqualRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class NestingRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(NestingRule.description)
|
||||
@@ -150,9 +156,3 @@ final class PreferAssetSymbolsRuleGeneratedTests: SwiftLintTestCase {
|
||||
verifyRule(PreferAssetSymbolsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class PreferConditionListRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(PreferConditionListRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
@testable import SwiftLintCore
|
||||
import TestHelpers
|
||||
|
||||
final class PreferConditionListRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(PreferConditionListRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class PreferKeyPathRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(PreferKeyPathRule.description)
|
||||
@@ -150,9 +156,3 @@ final class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase {
|
||||
verifyRule(RedundantNilCoalescingRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class RedundantObjcAttributeRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(RedundantObjcAttributeRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
@testable import SwiftLintCore
|
||||
import TestHelpers
|
||||
|
||||
final class RedundantObjcAttributeRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(RedundantObjcAttributeRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class RedundantSelfInClosureRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(RedundantSelfInClosureRule.description)
|
||||
@@ -150,9 +156,3 @@ final class StrongIBOutletRuleGeneratedTests: SwiftLintTestCase {
|
||||
verifyRule(StrongIBOutletRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class SuperfluousElseRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(SuperfluousElseRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
@testable import SwiftLintCore
|
||||
import TestHelpers
|
||||
|
||||
final class SuperfluousElseRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(SuperfluousElseRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class SwitchCaseAlignmentRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(SwitchCaseAlignmentRule.description)
|
||||
@@ -150,9 +156,3 @@ final class UnneededThrowsRuleGeneratedTests: SwiftLintTestCase {
|
||||
verifyRule(UnneededThrowsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class UnownedVariableCaptureRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(UnownedVariableCaptureRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
@testable import SwiftLintCore
|
||||
import TestHelpers
|
||||
|
||||
final class UnownedVariableCaptureRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(UnownedVariableCaptureRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
final class UntypedErrorInCatchRuleGeneratedTests: SwiftLintTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(UntypedErrorInCatchRule.description)
|
||||
|
||||
@@ -678,6 +678,12 @@ multiline_arguments_brackets:
|
||||
meta:
|
||||
opt-in: true
|
||||
correctable: false
|
||||
multiline_call_arguments:
|
||||
severity: warning
|
||||
allows_single_line: true
|
||||
meta:
|
||||
opt-in: true
|
||||
correctable: false
|
||||
multiline_function_chains:
|
||||
severity: warning
|
||||
meta:
|
||||
|
||||
Reference in New Issue
Block a user