Skip to content

Commit bd662b7

Browse files
Catch guard else spacing in statement_position rule (#6543)
1 parent 7458be2 commit bd662b7

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454

5555
### Bug Fixes
5656

57+
* Detect and autocorrect missing whitespace before `else` in `guard`
58+
statements for the `statement_position` rule.
59+
[theamodhshetty](https://github.com/theamodhshetty)
60+
[#6153](https://github.com/realm/SwiftLint/issues/6153)
61+
5762
* Add an `ignore_attributes` option to `implicit_optional_initialization` so
5863
wrappers/attributes that require explicit `= nil` can be excluded from
5964
style checks for both `style: always` and `style: never`.

Source/SwiftLintBuiltInRules/Rules/Style/StatementPositionRule.swift

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct StatementPositionRule: CorrectableRule {
1414
Example("} else if {"),
1515
Example("} else {"),
1616
Example("} catch {"),
17+
Example("guard foo() else { return }"),
1718
Example("\"}else{\""),
1819
Example("struct A { let catchphrase: Int }\nlet a = A(\n catchphrase: 0\n)"),
1920
Example("struct A { let `catch`: Int }\nlet a = A(\n `catch`: 0\n)"),
@@ -23,11 +24,13 @@ struct StatementPositionRule: CorrectableRule {
2324
Example("↓} else {"),
2425
Example("↓}\ncatch {"),
2526
Example("↓}\n\t catch {"),
27+
Example("guard foo()↓else { return }"),
2628
],
2729
corrections: [
2830
Example("↓}\n else {"): Example("} else {"),
2931
Example("↓}\n else if {"): Example("} else if {"),
3032
Example("↓}\n catch {"): Example("} catch {"),
33+
Example("guard foo()↓else { return }"): Example("guard foo() else { return }"),
3134
]
3235
)
3336

@@ -87,34 +90,75 @@ private extension StatementPositionRule {
8790
// followed by 'else' or 'catch' literals
8891
static let defaultPattern = "\\}(?:[\\s\\n\\r]{2,}|[\\n\\t\\r]+)?\\b(else|catch)\\b"
8992

93+
// match a guard statement where `else` is glued to the condition without whitespace
94+
static let defaultGuardPattern = "(\\bguard\\b[^\\n]*\\S)(else\\b)"
95+
96+
static let defaultGuardRegex = regex(defaultGuardPattern)
97+
9098
func defaultValidate(file: SwiftLintFile) -> [StyleViolation] {
91-
defaultViolationRanges(in: file, matching: Self.defaultPattern).compactMap { range in
99+
defaultViolationRanges(in: file).compactMap { range in
92100
StyleViolation(ruleDescription: Self.description,
93101
severity: configuration.severity,
94102
location: Location(file: file, characterOffset: range.location))
95103
}
96104
}
97105

98-
func defaultViolationRanges(in file: SwiftLintFile, matching pattern: String) -> [NSRange] {
99-
file.match(pattern: pattern).filter { _, syntaxKinds in
106+
func defaultViolationRanges(in file: SwiftLintFile) -> [NSRange] {
107+
defaultBraceViolationRanges(in: file) + defaultGuardViolationRanges(in: file)
108+
}
109+
110+
func defaultBraceViolationRanges(in file: SwiftLintFile) -> [NSRange] {
111+
file.match(pattern: Self.defaultPattern).filter { _, syntaxKinds in
100112
syntaxKinds.starts(with: [.keyword])
101113
}.compactMap(\.0)
102114
}
103115

116+
func defaultGuardViolationRanges(in file: SwiftLintFile) -> [NSRange] {
117+
defaultGuardMatches(in: file).map { $0.range(at: 2) }
118+
}
119+
120+
func defaultGuardCorrectionRanges(in file: SwiftLintFile) -> [NSRange] {
121+
defaultGuardMatches(in: file).map(\.range)
122+
}
123+
124+
func defaultGuardMatches(in file: SwiftLintFile) -> [NSTextCheckingResult] {
125+
let contents = file.stringView
126+
let syntaxMap = file.syntaxMap
127+
128+
return Self.defaultGuardRegex.matches(in: file).filter { match in
129+
guard let elseRange = contents.NSRangeToByteRange(
130+
start: match.range(at: 2).location,
131+
length: match.range(at: 2).length
132+
) else {
133+
return false
134+
}
135+
136+
return syntaxMap.kinds(inByteRange: elseRange) == [.keyword]
137+
}
138+
}
139+
104140
func defaultCorrect(file: SwiftLintFile) -> Int {
105-
let violations = defaultViolationRanges(in: file, matching: Self.defaultPattern)
106-
let matches = file.ruleEnabled(violatingRanges: violations, for: self)
107-
if matches.isEmpty {
141+
let braceViolations = defaultBraceViolationRanges(in: file)
142+
let guardViolations = defaultGuardCorrectionRanges(in: file)
143+
let enabledBraceViolations = file.ruleEnabled(violatingRanges: braceViolations, for: self)
144+
let enabledGuardViolations = file.ruleEnabled(violatingRanges: guardViolations, for: self)
145+
if enabledBraceViolations.isEmpty, enabledGuardViolations.isEmpty {
108146
return 0
109147
}
110-
let regularExpression = regex(Self.defaultPattern)
148+
111149
var contents = file.contents
112-
for range in matches.reversed() {
113-
contents = regularExpression.stringByReplacingMatches(in: contents, options: [], range: range,
114-
withTemplate: "} $1")
150+
let braceRegex = regex(Self.defaultPattern)
151+
for range in enabledBraceViolations.reversed() {
152+
contents = braceRegex.stringByReplacingMatches(in: contents, options: [], range: range,
153+
withTemplate: "} $1")
154+
}
155+
156+
for range in enabledGuardViolations.reversed() {
157+
contents = Self.defaultGuardRegex.stringByReplacingMatches(in: contents, options: [], range: range,
158+
withTemplate: "$1 $2")
115159
}
116160
file.write(contents)
117-
return matches.count
161+
return enabledBraceViolations.count + enabledGuardViolations.count
118162
}
119163
}
120164

0 commit comments

Comments
 (0)