mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Multiline Brackets (#2302)
* Add new multiline_literal_brackets rule with examples * Implement rule * Add changelog entry * Fix CHANGELOG and rule name * Fix tests + Update stuff after rebasing * Add more examples & fix whitespace issue * Address feedback from @ornithocoder * Add multiline rules for arguments and parameters * Fix false positives in rule multiline_parameters_brackets * Fix false positive for trailing closures in multiline_arguments_brackets * Add nested examples to rule multiline_arguments_brackets * Fix more false positives in multiline_arguments_brackets rule * Use guard where appropriate instead of if * Update generated artifacts after rebase * Add CHANGELOG entry for all three new rules * Move changelog entries to new version * Fix changelog entries position * Move new rules to correct subfolder * Update Rules.md file contents * Fixup changelog * Refactor rules
This commit is contained in:
@@ -20,6 +20,21 @@
|
||||
[Cihat Gündüz](https://github.com/Dschee)
|
||||
[#1517](https://github.com/realm/SwiftLint/issues/1517)
|
||||
|
||||
* Add `multiline_arguments_brackets` opt-in rule to warn against multiline
|
||||
function call arguments with surrounding brackets without newline.
|
||||
[Cihat Gündüz](https://github.com/Dschee)
|
||||
[#2306](https://github.com/realm/SwiftLint/issues/2306)
|
||||
|
||||
* Add `multiline_literal_brackets` opt-in rule to warn against multiline
|
||||
literal arrays & dictionaries with surrounding brackets without newline.
|
||||
[Cihat Gündüz](https://github.com/Dschee)
|
||||
[#2306](https://github.com/realm/SwiftLint/issues/2306)
|
||||
|
||||
* Add `multiline_parameters_brackets` opt-in rule to warn against multiline
|
||||
function definition parameters with surrounding brackets without newline.
|
||||
[Cihat Gündüz](https://github.com/Dschee)
|
||||
[#2306](https://github.com/realm/SwiftLint/issues/2306)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* Fix false positive in `nimble_operator` rule.
|
||||
|
||||
@@ -77,8 +77,11 @@
|
||||
* [Missing Docs](#missing-docs)
|
||||
* [Modifier Order](#modifier-order)
|
||||
* [Multiline Arguments](#multiline-arguments)
|
||||
* [Multiline Arguments Brackets](#multiline-arguments-brackets)
|
||||
* [Multiline Function Chains](#multiline-function-chains)
|
||||
* [Multiline Literal Brackets](#multiline-literal-brackets)
|
||||
* [Multiline Parameters](#multiline-parameters)
|
||||
* [Multiline Parameters Brackets](#multiline-parameters-brackets)
|
||||
* [Multiple Closures with Trailing Closure](#multiple-closures-with-trailing-closure)
|
||||
* [Nesting](#nesting)
|
||||
* [Nimble Operator](#nimble-operator)
|
||||
@@ -11091,6 +11094,95 @@ foo(
|
||||
|
||||
|
||||
|
||||
## Multiline Arguments Brackets
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
--- | --- | --- | --- | --- | ---
|
||||
`multiline_arguments_brackets` | Disabled | No | style | No | 3.0.0
|
||||
|
||||
Multiline arguments should have their surrounding brackets in a new line.
|
||||
|
||||
### Examples
|
||||
|
||||
<details>
|
||||
<summary>Non Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
foo(param1: "Param1", param2: "Param2", param3: "Param3")
|
||||
```
|
||||
|
||||
```swift
|
||||
foo(
|
||||
param1: "Param1", param2: "Param2", param3: "Param3"
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo(
|
||||
param1: "Param1",
|
||||
param2: "Param2",
|
||||
param3: "Param3"
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
foo { param1, param2 in
|
||||
print("hello world")
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
foo(
|
||||
bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
AlertViewModel.AlertAction(title: "some title", style: .default) {
|
||||
AlertManager.shared.presentNextDebugAlert()
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
foo(↓param1: "Param1", param2: "Param2",
|
||||
param3: "Param3"
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
foo(
|
||||
param1: "Param1",
|
||||
param2: "Param2",
|
||||
param3: "Param3"↓)
|
||||
```
|
||||
|
||||
```swift
|
||||
foo(↓bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
foo(
|
||||
bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)↓)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## Multiline Function Chains
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
@@ -11207,6 +11299,118 @@ a.b {
|
||||
|
||||
|
||||
|
||||
## Multiline Literal Brackets
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
--- | --- | --- | --- | --- | ---
|
||||
`multiline_literal_brackets` | Disabled | No | style | No | 3.0.0
|
||||
|
||||
Multiline literals should have their surrounding brackets in a new line.
|
||||
|
||||
### Examples
|
||||
|
||||
<details>
|
||||
<summary>Non Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
let trio = ["harry", "ronald", "hermione"]
|
||||
let houseCup = ["gryffinder": 460, "hufflepuff": 370, "ravenclaw": 410, "slytherin": 450]
|
||||
```
|
||||
|
||||
```swift
|
||||
let trio = [
|
||||
"harry",
|
||||
"ronald",
|
||||
"hermione"
|
||||
]
|
||||
let houseCup = [
|
||||
"gryffinder": 460,
|
||||
"hufflepuff": 370,
|
||||
"ravenclaw": 410,
|
||||
"slytherin": 450
|
||||
]
|
||||
```
|
||||
|
||||
```swift
|
||||
let trio = [
|
||||
"harry", "ronald", "hermione"
|
||||
]
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450
|
||||
]
|
||||
```
|
||||
|
||||
```swift
|
||||
_ = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5, 6,
|
||||
7, 8, 9
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
let trio = [↓"harry",
|
||||
"ronald",
|
||||
"hermione"
|
||||
]
|
||||
```
|
||||
|
||||
```swift
|
||||
let houseCup = [↓"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450
|
||||
]
|
||||
```
|
||||
|
||||
```swift
|
||||
let trio = [
|
||||
"harry",
|
||||
"ronald",
|
||||
"hermione"↓]
|
||||
```
|
||||
|
||||
```swift
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450↓]
|
||||
```
|
||||
|
||||
```swift
|
||||
class Hogwarts {
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450↓]
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
_ = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5, 6,
|
||||
7, 8, 9↓]
|
||||
```
|
||||
|
||||
```swift
|
||||
_ = [↓1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9
|
||||
]
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## Multiline Parameters
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
@@ -11687,6 +11891,109 @@ class Foo {
|
||||
|
||||
|
||||
|
||||
## Multiline Parameters Brackets
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
--- | --- | --- | --- | --- | ---
|
||||
`multiline_parameters_brackets` | Disabled | No | style | No | 3.0.0
|
||||
|
||||
Multiline parameters should have their surrounding brackets in a new line.
|
||||
|
||||
### Examples
|
||||
|
||||
<details>
|
||||
<summary>Non Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
func foo(param1: String, param2: String, param3: String)
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo(
|
||||
param1: String, param2: String, param3: String
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
class SomeType {
|
||||
func foo(param1: String, param2: String, param3: String)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String, param2: String, param3: String
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo<T>(param1: T, param2: String, param3: String) -> T { /* some code */ }
|
||||
```
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>Triggering Examples</summary>
|
||||
|
||||
```swift
|
||||
func foo(↓param1: String, param2: String,
|
||||
param3: String
|
||||
)
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String↓)
|
||||
```
|
||||
|
||||
```swift
|
||||
class SomeType {
|
||||
func foo(↓param1: String, param2: String,
|
||||
param3: String
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String↓)
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
func foo<T>(↓param1: T, param2: String,
|
||||
param3: String
|
||||
) -> T
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
## Multiple Closures with Trailing Closure
|
||||
|
||||
Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
|
||||
|
||||
@@ -77,8 +77,11 @@ public let masterRuleList = RuleList(rules: [
|
||||
MarkRule.self,
|
||||
MissingDocsRule.self,
|
||||
ModifierOrderRule.self,
|
||||
MultilineArgumentsBracketsRule.self,
|
||||
MultilineArgumentsRule.self,
|
||||
MultilineFunctionChainsRule.self,
|
||||
MultilineLiteralBracketsRule.self,
|
||||
MultilineParametersBracketsRule.self,
|
||||
MultilineParametersRule.self,
|
||||
MultipleClosuresWithTrailingClosureRule.self,
|
||||
NestingRule.self,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct MultilineArgumentsBracketsRule: ASTRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "multiline_arguments_brackets",
|
||||
name: "Multiline Arguments Brackets",
|
||||
description: "Multiline arguments should have their surrounding brackets in a new line.",
|
||||
kind: .style,
|
||||
nonTriggeringExamples: [
|
||||
"""
|
||||
foo(param1: "Param1", param2: "Param2", param3: "Param3")
|
||||
""",
|
||||
"""
|
||||
foo(
|
||||
param1: "Param1", param2: "Param2", param3: "Param3"
|
||||
)
|
||||
""",
|
||||
"""
|
||||
func foo(
|
||||
param1: "Param1",
|
||||
param2: "Param2",
|
||||
param3: "Param3"
|
||||
)
|
||||
""",
|
||||
"""
|
||||
foo { param1, param2 in
|
||||
print("hello world")
|
||||
}
|
||||
""",
|
||||
"""
|
||||
foo(
|
||||
bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
AlertViewModel.AlertAction(title: "some title", style: .default) {
|
||||
AlertManager.shared.presentNextDebugAlert()
|
||||
}
|
||||
"""
|
||||
],
|
||||
triggeringExamples: [
|
||||
"""
|
||||
foo(↓param1: "Param1", param2: "Param2",
|
||||
param3: "Param3"
|
||||
)
|
||||
""",
|
||||
"""
|
||||
foo(
|
||||
param1: "Param1",
|
||||
param2: "Param2",
|
||||
param3: "Param3"↓)
|
||||
""",
|
||||
"""
|
||||
foo(↓bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
foo(
|
||||
bar(
|
||||
x: 5,
|
||||
y: 7
|
||||
)↓)
|
||||
"""
|
||||
]
|
||||
)
|
||||
|
||||
public func validate(file: File,
|
||||
kind: SwiftExpressionKind,
|
||||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
||||
guard
|
||||
kind == .call,
|
||||
let bodyOffset = dictionary.bodyOffset,
|
||||
let bodyLength = dictionary.bodyLength,
|
||||
let range = file.contents.bridge().byteRangeToNSRange(start: bodyOffset, length: bodyLength)
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
let body = file.contents.substring(from: range.location, length: range.length)
|
||||
let isMultiline = body.contains("\n")
|
||||
guard isMultiline else {
|
||||
return []
|
||||
}
|
||||
|
||||
let expectedBodyBeginRegex = regex("\\A(?:[ \\t]*\\n|[^\\n]*(?:in|\\{)\\n)")
|
||||
let expectedBodyEndRegex = regex("\\n[ \\t]*\\z")
|
||||
|
||||
var violatingByteOffsets = [Int]()
|
||||
if expectedBodyBeginRegex.firstMatch(in: body, options: [], range: body.fullNSRange) == nil {
|
||||
violatingByteOffsets.append(bodyOffset)
|
||||
}
|
||||
|
||||
if expectedBodyEndRegex.firstMatch(in: body, options: [], range: body.fullNSRange) == nil {
|
||||
violatingByteOffsets.append(bodyOffset + bodyLength)
|
||||
}
|
||||
|
||||
return violatingByteOffsets.map { byteOffset in
|
||||
StyleViolation(
|
||||
ruleDescription: type(of: self).description, severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: byteOffset)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct MultilineLiteralBracketsRule: ASTRule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "multiline_literal_brackets",
|
||||
name: "Multiline Literal Brackets",
|
||||
description: "Multiline literals should have their surrounding brackets in a new line.",
|
||||
kind: .style,
|
||||
nonTriggeringExamples: [
|
||||
"""
|
||||
let trio = ["harry", "ronald", "hermione"]
|
||||
let houseCup = ["gryffinder": 460, "hufflepuff": 370, "ravenclaw": 410, "slytherin": 450]
|
||||
""",
|
||||
"""
|
||||
let trio = [
|
||||
"harry",
|
||||
"ronald",
|
||||
"hermione"
|
||||
]
|
||||
let houseCup = [
|
||||
"gryffinder": 460,
|
||||
"hufflepuff": 370,
|
||||
"ravenclaw": 410,
|
||||
"slytherin": 450
|
||||
]
|
||||
""",
|
||||
"""
|
||||
let trio = [
|
||||
"harry", "ronald", "hermione"
|
||||
]
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450
|
||||
]
|
||||
""",
|
||||
"""
|
||||
_ = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5, 6,
|
||||
7, 8, 9
|
||||
]
|
||||
"""
|
||||
],
|
||||
triggeringExamples: [
|
||||
"""
|
||||
let trio = [↓"harry",
|
||||
"ronald",
|
||||
"hermione"
|
||||
]
|
||||
""",
|
||||
"""
|
||||
let houseCup = [↓"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450
|
||||
]
|
||||
""",
|
||||
"""
|
||||
let trio = [
|
||||
"harry",
|
||||
"ronald",
|
||||
"hermione"↓]
|
||||
""",
|
||||
"""
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450↓]
|
||||
""",
|
||||
"""
|
||||
class Hogwarts {
|
||||
let houseCup = [
|
||||
"gryffinder": 460, "hufflepuff": 370,
|
||||
"ravenclaw": 410, "slytherin": 450↓]
|
||||
}
|
||||
""",
|
||||
"""
|
||||
_ = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5, 6,
|
||||
7, 8, 9↓]
|
||||
""",
|
||||
"""
|
||||
_ = [↓1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9
|
||||
]
|
||||
"""
|
||||
]
|
||||
)
|
||||
|
||||
public func validate(file: File,
|
||||
kind: SwiftExpressionKind,
|
||||
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
|
||||
guard
|
||||
[.array, .dictionary].contains(kind),
|
||||
let bodyOffset = dictionary.bodyOffset,
|
||||
let bodyLength = dictionary.bodyLength,
|
||||
let range = file.contents.bridge().byteRangeToNSRange(start: bodyOffset, length: bodyLength)
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
let body = file.contents.substring(from: range.location, length: range.length)
|
||||
let isMultiline = body.contains("\n")
|
||||
guard isMultiline else {
|
||||
return []
|
||||
}
|
||||
|
||||
let expectedBodyBeginRegex = regex("\\A[ \\t]*\\n")
|
||||
let expectedBodyEndRegex = regex("\\n[ \\t]*\\z")
|
||||
|
||||
var violatingByteOffsets = [Int]()
|
||||
if expectedBodyBeginRegex.firstMatch(in: body, options: [], range: body.fullNSRange) == nil {
|
||||
violatingByteOffsets.append(bodyOffset)
|
||||
}
|
||||
|
||||
if expectedBodyEndRegex.firstMatch(in: body, options: [], range: body.fullNSRange) == nil {
|
||||
violatingByteOffsets.append(bodyOffset + bodyLength)
|
||||
}
|
||||
|
||||
return violatingByteOffsets.map { byteOffset in
|
||||
StyleViolation(
|
||||
ruleDescription: type(of: self).description, severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: byteOffset)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
|
||||
public struct MultilineParametersBracketsRule: OptInRule, ConfigurationProviderRule, AutomaticTestableRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "multiline_parameters_brackets",
|
||||
name: "Multiline Parameters Brackets",
|
||||
description: "Multiline parameters should have their surrounding brackets in a new line.",
|
||||
kind: .style,
|
||||
nonTriggeringExamples: [
|
||||
"""
|
||||
func foo(param1: String, param2: String, param3: String)
|
||||
""",
|
||||
"""
|
||||
func foo(
|
||||
param1: String, param2: String, param3: String
|
||||
)
|
||||
""",
|
||||
"""
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String
|
||||
)
|
||||
""",
|
||||
"""
|
||||
class SomeType {
|
||||
func foo(param1: String, param2: String, param3: String)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String, param2: String, param3: String
|
||||
)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String
|
||||
)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
func foo<T>(param1: T, param2: String, param3: String) -> T { /* some code */ }
|
||||
"""
|
||||
],
|
||||
triggeringExamples: [
|
||||
"""
|
||||
func foo(↓param1: String, param2: String,
|
||||
param3: String
|
||||
)
|
||||
""",
|
||||
"""
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String↓)
|
||||
""",
|
||||
"""
|
||||
class SomeType {
|
||||
func foo(↓param1: String, param2: String,
|
||||
param3: String
|
||||
)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
class SomeType {
|
||||
func foo(
|
||||
param1: String,
|
||||
param2: String,
|
||||
param3: String↓)
|
||||
}
|
||||
""",
|
||||
"""
|
||||
func foo<T>(↓param1: T, param2: String,
|
||||
param3: String
|
||||
) -> T
|
||||
"""
|
||||
]
|
||||
)
|
||||
|
||||
public func validate(file: File) -> [StyleViolation] {
|
||||
return violations(in: file.structure.dictionary, file: file)
|
||||
}
|
||||
|
||||
private func violations(in substructure: [String: SourceKitRepresentable], file: File) -> [StyleViolation] {
|
||||
var violations = [StyleViolation]()
|
||||
|
||||
// find violations at current level
|
||||
if let kindString = substructure.kind, let kind = SwiftDeclarationKind(rawValue: kindString),
|
||||
SwiftDeclarationKind.functionKinds.contains(kind) {
|
||||
|
||||
guard
|
||||
let nameOffset = substructure.nameOffset,
|
||||
let nameLength = substructure.nameLength,
|
||||
let functionName = file.contents.bridge().substringWithByteRange(start: nameOffset, length: nameLength)
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
let isMultiline = functionName.contains("\n")
|
||||
|
||||
let parameters = substructure.substructure.filter { $0.kind == SwiftDeclarationKind.varParameter.rawValue }
|
||||
if isMultiline && !parameters.isEmpty {
|
||||
if let openingBracketViolation = openingBracketViolation(parameters: parameters, file: file) {
|
||||
violations.append(openingBracketViolation)
|
||||
}
|
||||
|
||||
if let closingBracketViolation = closingBracketViolation(parameters: parameters, file: file) {
|
||||
violations.append(closingBracketViolation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find violations at deeper levels
|
||||
for substructure in substructure.substructure {
|
||||
violations += self.violations(in: substructure, file: file)
|
||||
}
|
||||
|
||||
return violations
|
||||
}
|
||||
|
||||
private func openingBracketViolation(parameters: [[String: SourceKitRepresentable]],
|
||||
file: File) -> StyleViolation? {
|
||||
guard
|
||||
let firstParamByteOffset = parameters.first?.offset,
|
||||
let firstParamByteLength = parameters.first?.length,
|
||||
let firstParamRange = file.contents.bridge().byteRangeToNSRange(
|
||||
start: firstParamByteOffset,
|
||||
length: firstParamByteLength
|
||||
)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let prefix = file.contents.bridge().substring(to: firstParamRange.lowerBound)
|
||||
let invalidRegex = regex("\\([ \\t]*\\z")
|
||||
|
||||
guard let invalidMatch = invalidRegex.firstMatch(in: prefix, options: [], range: prefix.fullNSRange) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return StyleViolation(
|
||||
ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, characterOffset: invalidMatch.range.location + 1)
|
||||
)
|
||||
}
|
||||
|
||||
private func closingBracketViolation(parameters: [[String: SourceKitRepresentable]],
|
||||
file: File) -> StyleViolation? {
|
||||
guard
|
||||
let lastParamByteOffset = parameters.last?.offset,
|
||||
let lastParamByteLength = parameters.last?.length,
|
||||
let lastParamRange = file.contents.bridge().byteRangeToNSRange(
|
||||
start: lastParamByteOffset,
|
||||
length: lastParamByteLength
|
||||
)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let suffix = file.contents.bridge().substring(from: lastParamRange.upperBound)
|
||||
let invalidRegex = regex("\\A[ \\t]*\\)")
|
||||
|
||||
guard let invalidMatch = invalidRegex.firstMatch(in: suffix, options: [], range: suffix.fullNSRange) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let characterOffset = lastParamRange.upperBound + invalidMatch.range.upperBound - 1
|
||||
return StyleViolation(
|
||||
ruleDescription: type(of: self).description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, characterOffset: characterOffset)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -139,10 +139,13 @@
|
||||
7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */; };
|
||||
820F451E21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */; };
|
||||
824AB64D2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */; };
|
||||
823EDC6221020D850070B7CD /* MultilineLiteralBracketsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */; };
|
||||
82144ACC20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82144ACB20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift */; };
|
||||
825F19D11EEFF19700969EF1 /* ObjectLiteralRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F19D01EEFF19700969EF1 /* ObjectLiteralRuleTests.swift */; };
|
||||
827169B31F488181003FB9AF /* ExplicitEnumRawValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827169B21F488181003FB9AF /* ExplicitEnumRawValueRule.swift */; };
|
||||
827169B51F48D712003FB9AF /* NoGroupingExtensionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827169B41F48D712003FB9AF /* NoGroupingExtensionRule.swift */; };
|
||||
82F614F22106014500D23904 /* MultilineParametersBracketsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F614F12106014500D23904 /* MultilineParametersBracketsRule.swift */; };
|
||||
82F614F42106015100D23904 /* MultilineArgumentsBracketsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F614F32106015100D23904 /* MultilineArgumentsBracketsRule.swift */; };
|
||||
83894F221B0C928A006214E1 /* RulesCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83894F211B0C928A006214E1 /* RulesCommand.swift */; };
|
||||
83D71E281B131ECE000395DE /* RuleDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83D71E261B131EB5000395DE /* RuleDescription.swift */; };
|
||||
85DA81321D6B471000951BC4 /* MarkRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 856651A61D6B395F005E6B29 /* MarkRule.swift */; };
|
||||
@@ -561,10 +564,13 @@
|
||||
7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitInitRule.swift; sourceTree = "<group>"; };
|
||||
820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineRuleTests.swift; sourceTree = "<group>"; };
|
||||
824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineConfiguration.swift; sourceTree = "<group>"; };
|
||||
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLiteralBracketsRule.swift; sourceTree = "<group>"; };
|
||||
82144ACB20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceBetweenCasesRule.swift; sourceTree = "<group>"; };
|
||||
825F19D01EEFF19700969EF1 /* ObjectLiteralRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLiteralRuleTests.swift; sourceTree = "<group>"; };
|
||||
827169B21F488181003FB9AF /* ExplicitEnumRawValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitEnumRawValueRule.swift; sourceTree = "<group>"; };
|
||||
827169B41F48D712003FB9AF /* NoGroupingExtensionRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoGroupingExtensionRule.swift; sourceTree = "<group>"; };
|
||||
82F614F12106014500D23904 /* MultilineParametersBracketsRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineParametersBracketsRule.swift; sourceTree = "<group>"; };
|
||||
82F614F32106015100D23904 /* MultilineArgumentsBracketsRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineArgumentsBracketsRule.swift; sourceTree = "<group>"; };
|
||||
83894F211B0C928A006214E1 /* RulesCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesCommand.swift; sourceTree = "<group>"; };
|
||||
83D71E261B131EB5000395DE /* RuleDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleDescription.swift; sourceTree = "<group>"; };
|
||||
856651A61D6B395F005E6B29 /* MarkRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkRule.swift; sourceTree = "<group>"; };
|
||||
@@ -1050,9 +1056,12 @@
|
||||
C946FEC91EAE5E20007DD778 /* LetVarWhitespaceRule.swift */,
|
||||
D4EA77C91F81FACC00C315FB /* LiteralExpressionEndIdentationRule.swift */,
|
||||
188B3FF1207D61040073C2D6 /* ModifierOrderRule.swift */,
|
||||
82F614F32106015100D23904 /* MultilineArgumentsBracketsRule.swift */,
|
||||
B25DCD071F7E9B5F0028A199 /* MultilineArgumentsRule.swift */,
|
||||
B25DCD091F7E9BB50028A199 /* MultilineArgumentsRuleExamples.swift */,
|
||||
3ABE19CD20B7CDE0009C2EC2 /* MultilineFunctionChainsRule.swift */,
|
||||
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */,
|
||||
82F614F12106014500D23904 /* MultilineParametersBracketsRule.swift */,
|
||||
6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */,
|
||||
621061BE1ED57E640082D51E /* MultilineParametersRuleExamples.swift */,
|
||||
BB00B4E71F5216070079869F /* MultipleClosuresWithTrailingClosureRule.swift */,
|
||||
@@ -1787,6 +1796,7 @@
|
||||
D44AD2761C0AA5350048F7B0 /* LegacyConstructorRule.swift in Sources */,
|
||||
D286EC021E02DF6F0003CF72 /* SortedImportsRule.swift in Sources */,
|
||||
D40E041C1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift in Sources */,
|
||||
82F614F42106015100D23904 /* MultilineArgumentsBracketsRule.swift in Sources */,
|
||||
E86623671F1D377900AAA3A2 /* Configuration+Parsing.swift in Sources */,
|
||||
3BCC04CD1C4F5694006073C3 /* ConfigurationError.swift in Sources */,
|
||||
D4C4A34E1DEA877200E0E04C /* FileHeaderRule.swift in Sources */,
|
||||
@@ -1839,6 +1849,7 @@
|
||||
D4B022A41E105636007E5297 /* GenericTypeNameRule.swift in Sources */,
|
||||
E86396CB1BADB519002C9E88 /* CSVReporter.swift in Sources */,
|
||||
37B3FA8B1DFD45A700AD30D2 /* Dictionary+SwiftLint.swift in Sources */,
|
||||
823EDC6221020D850070B7CD /* MultilineLiteralBracketsRule.swift in Sources */,
|
||||
D47EF4801F69E3100012C4CA /* ColonRule+FunctionCall.swift in Sources */,
|
||||
E88198561BEA94D800333A11 /* FileLengthRule.swift in Sources */,
|
||||
D47079A91DFDBED000027086 /* ClosureParameterPositionRule.swift in Sources */,
|
||||
@@ -1904,6 +1915,7 @@
|
||||
E86396C51BADAC15002C9E88 /* XcodeReporter.swift in Sources */,
|
||||
E889D8C51F1D11A200058332 /* Configuration+LintableFiles.swift in Sources */,
|
||||
094385011D5D2894009168CF /* WeakDelegateRule.swift in Sources */,
|
||||
82F614F22106014500D23904 /* MultilineParametersBracketsRule.swift in Sources */,
|
||||
3B1DF0121C5148140011BCED /* CustomRules.swift in Sources */,
|
||||
2E5761AA1C573B83003271AF /* FunctionParameterCountRule.swift in Sources */,
|
||||
E86396C91BADB2B9002C9E88 /* JSONReporter.swift in Sources */,
|
||||
|
||||
@@ -714,6 +714,12 @@ extension ModifierOrderTests {
|
||||
]
|
||||
}
|
||||
|
||||
extension MultilineArgumentsBracketsRuleTests {
|
||||
static var allTests: [(String, (MultilineArgumentsBracketsRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
]
|
||||
}
|
||||
|
||||
extension MultilineArgumentsRuleTests {
|
||||
static var allTests: [(String, (MultilineArgumentsRuleTests) -> () throws -> Void)] = [
|
||||
("testMultilineArgumentsWithDefaultConfiguration", testMultilineArgumentsWithDefaultConfiguration),
|
||||
@@ -729,6 +735,18 @@ extension MultilineFunctionChainsRuleTests {
|
||||
]
|
||||
}
|
||||
|
||||
extension MultilineLiteralBracketsRuleTests {
|
||||
static var allTests: [(String, (MultilineLiteralBracketsRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
]
|
||||
}
|
||||
|
||||
extension MultilineParametersBracketsRuleTests {
|
||||
static var allTests: [(String, (MultilineParametersBracketsRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
]
|
||||
}
|
||||
|
||||
extension MultilineParametersRuleTests {
|
||||
static var allTests: [(String, (MultilineParametersRuleTests) -> () throws -> Void)] = [
|
||||
("testWithDefaultConfiguration", testWithDefaultConfiguration)
|
||||
@@ -1400,8 +1418,11 @@ XCTMain([
|
||||
testCase(MissingDocsRuleConfigurationTests.allTests),
|
||||
testCase(MissingDocsRuleTests.allTests),
|
||||
testCase(ModifierOrderTests.allTests),
|
||||
testCase(MultilineArgumentsBracketsRuleTests.allTests),
|
||||
testCase(MultilineArgumentsRuleTests.allTests),
|
||||
testCase(MultilineFunctionChainsRuleTests.allTests),
|
||||
testCase(MultilineLiteralBracketsRuleTests.allTests),
|
||||
testCase(MultilineParametersBracketsRuleTests.allTests),
|
||||
testCase(MultilineParametersRuleTests.allTests),
|
||||
testCase(MultipleClosuresWithTrailingClosureRuleTests.allTests),
|
||||
testCase(NestingRuleTests.allTests),
|
||||
|
||||
@@ -330,12 +330,30 @@ class MissingDocsRuleTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
class MultilineArgumentsBracketsRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineArgumentsBracketsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class MultilineFunctionChainsRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineFunctionChainsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class MultilineLiteralBracketsRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineLiteralBracketsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class MultilineParametersBracketsRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineParametersBracketsRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class MultilineParametersRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(MultilineParametersRule.description)
|
||||
|
||||
Reference in New Issue
Block a user