Skip to content

Commit b9f3843

Browse files
authored
Make no_empty_block rule opt-in and add configurations (#5685)
1 parent d46bfcd commit b9f3843

File tree

5 files changed

+186
-48
lines changed

5 files changed

+186
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
[Martin Redington](https://github.com/mildm8nnered)
3737
[#5552](https://github.com/realm/SwiftLint/issues/5552)
3838

39-
* Add `no_empty_block` default rule to validate that code blocks are not empty.
39+
* Add `no_empty_block` opt-in rule to validate that code blocks are not empty.
4040
They should at least contain a comment.
4141
[Ueeek](https://github.com/Ueeek)
4242
[#5615](https://github.com/realm/SwiftLint/issues/5615)
Lines changed: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import SwiftLintCore
12
import SwiftSyntax
23

34
@SwiftSyntaxRule
4-
struct NoEmptyBlockRule: Rule {
5-
var configuration = SeverityConfiguration<Self>(.warning)
5+
struct NoEmptyBlockRule: OptInRule {
6+
var configuration = NoEmptyBlockConfiguration()
67

78
static let description = RuleDescription(
89
identifier: "no_empty_block",
@@ -11,85 +12,122 @@ struct NoEmptyBlockRule: Rule {
1112
kind: .idiomatic,
1213
nonTriggeringExamples: [
1314
Example("""
15+
func f() {
16+
/* do something */
17+
}
18+
1419
var flag = true {
1520
willSet { /* do something */ }
1621
}
1722
"""),
23+
1824
Example("""
19-
do {
20-
/* do something */
21-
} catch {
22-
/* do something */
25+
class Apple {
26+
init() { /* do something */ }
27+
28+
deinit { /* do something */ }
2329
}
2430
"""),
31+
2532
Example("""
26-
defer {
33+
for _ in 0..<10 { /* do something */ }
34+
35+
do {
36+
/* do something */
37+
} catch {
2738
/* do something */
2839
}
29-
"""),
30-
Example("""
31-
deinit { /* do something */ }
32-
"""),
33-
Example("""
34-
for _ in 0..<10 { /* do something */ }
35-
"""),
36-
Example("""
40+
3741
func f() {
38-
/* do something */
42+
defer {
43+
/* do something */
44+
}
45+
print("other code")
3946
}
40-
"""),
41-
Example("""
47+
4248
if flag {
4349
/* do something */
4450
} else {
4551
/* do something */
4652
}
53+
54+
repeat { /* do something */ } while (flag)
55+
56+
while i < 10 { /* do something */ }
4757
"""),
58+
4859
Example("""
49-
init() { /* do something */ }
50-
"""),
60+
func f() {}
61+
62+
var flag = true {
63+
willSet {}
64+
}
65+
""", configuration: ["disabled_block_types": ["function_bodies"]]),
66+
5167
Example("""
52-
repeat { /* do something */ } while (flag)
53-
"""),
68+
class Apple {
69+
init() {}
70+
71+
deinit {}
72+
}
73+
""", configuration: ["disabled_block_types": ["initializer_bodies"]]),
74+
5475
Example("""
55-
while i < 10 { /* do something */ }
56-
"""),
76+
for _ in 0..<10 {}
77+
78+
do {
79+
} catch {
80+
}
81+
82+
func f() {
83+
defer {}
84+
print("other code")
85+
}
86+
87+
if flag {
88+
} else {
89+
}
90+
91+
repeat {} while (flag)
92+
93+
while i < 10 {}
94+
""", configuration: ["disabled_block_types": ["statement_blocks"]]),
5795
],
5896
triggeringExamples: [
5997
Example("""
98+
func f() ↓{}
99+
60100
var flag = true {
61101
willSet ↓{}
62102
}
63103
"""),
104+
64105
Example("""
65-
do ↓{
66-
} catch ↓{
106+
class Apple {
107+
init() ↓{}
108+
109+
deinit ↓{}
67110
}
68111
"""),
69-
Example("""
70-
defer ↓{}
71-
"""),
72-
Example("""
73-
deinit ↓{}
74-
"""),
112+
75113
Example("""
76114
for _ in 0..<10 ↓{}
77-
"""),
78-
Example("""
79-
func f() ↓{}
80-
"""),
81-
Example("""
115+
116+
do ↓{
117+
} catch ↓{
118+
}
119+
120+
func f() {
121+
defer ↓{}
122+
print("other code")
123+
}
124+
82125
if flag ↓{
83126
} else ↓{
84127
}
85-
"""),
86-
Example("""
87-
init() ↓{}
88-
"""),
89-
Example("""
128+
90129
repeat ↓{} while (flag)
91-
"""),
92-
Example("""
130+
93131
while i < 10 ↓{}
94132
"""),
95133
]
@@ -99,16 +137,36 @@ struct NoEmptyBlockRule: Rule {
99137
private extension NoEmptyBlockRule {
100138
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
101139
override func visitPost(_ node: CodeBlockSyntax) {
102-
// No need to show a warning since Empty Block of `guard` is compile error.
103-
guard node.parent?.kind != .guardStmt else { return }
140+
if let codeBlockType = node.codeBlockType, configuration.enabledBlockTypes.contains(codeBlockType) {
141+
validate(node: node)
142+
}
143+
}
104144

145+
func validate(node: CodeBlockSyntax) {
105146
guard node.statements.isEmpty,
106147
!node.leftBrace.trailingTrivia.containsComments,
107148
!node.rightBrace.leadingTrivia.containsComments else {
108149
return
109150
}
110-
111151
violations.append(node.leftBrace.positionAfterSkippingLeadingTrivia)
112152
}
113153
}
114154
}
155+
156+
private extension CodeBlockSyntax {
157+
var codeBlockType: NoEmptyBlockConfiguration.CodeBlockType? {
158+
switch parent?.kind {
159+
case .functionDecl, .accessorDecl:
160+
.functionBodies
161+
case .initializerDecl, .deinitializerDecl:
162+
.initializerBodies
163+
case .forStmt, .doStmt, .whileStmt, .repeatStmt, .ifExpr, .catchClause, .deferStmt:
164+
.statementBlocks
165+
case .guardStmt:
166+
// No need to handle this case since Empty Block of `guard` is compile error.
167+
nil
168+
default:
169+
nil
170+
}
171+
}
172+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import SwiftLintCore
2+
3+
@AutoApply
4+
struct NoEmptyBlockConfiguration: SeverityBasedRuleConfiguration {
5+
typealias Parent = NoEmptyBlockRule
6+
7+
@MakeAcceptableByConfigurationElement
8+
enum CodeBlockType: String, CaseIterable {
9+
case functionBodies = "function_bodies"
10+
case initializerBodies = "initializer_bodies"
11+
case statementBlocks = "statement_blocks"
12+
13+
static let all = Set(allCases)
14+
}
15+
16+
@ConfigurationElement(key: "severity")
17+
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
18+
19+
@ConfigurationElement(key: "disabled_block_types")
20+
private(set) var disabledBlockTypes: [CodeBlockType] = []
21+
22+
var enabledBlockTypes: Set<CodeBlockType> {
23+
CodeBlockType.all.subtracting(disabledBlockTypes)
24+
}
25+
}

Tests/IntegrationTests/default_rule_configurations.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ nimble_operator:
336336
severity: warning
337337
no_empty_block:
338338
severity: warning
339+
disabled_block_types: []
339340
no_extension_access_modifier:
340341
severity: error
341342
no_fallthrough_only:
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@testable import SwiftLintBuiltInRules
2+
@testable import SwiftLintCore
3+
import XCTest
4+
5+
final class NoEmptyBlockConfigurationTests: SwiftLintTestCase {
6+
func testDefaultConfiguration() {
7+
let config = NoEmptyBlockConfiguration()
8+
XCTAssertEqual(config.severityConfiguration.severity, .warning)
9+
XCTAssertEqual(config.enabledBlockTypes, NoEmptyBlockConfiguration.CodeBlockType.all)
10+
}
11+
12+
func testApplyingCustomConfiguration() throws {
13+
var config = NoEmptyBlockConfiguration()
14+
try config.apply(
15+
configuration: [
16+
"severity": "error",
17+
"disabled_block_types": ["function_bodies"],
18+
] as [String: any Sendable]
19+
)
20+
XCTAssertEqual(config.severityConfiguration.severity, .error)
21+
XCTAssertEqual(config.enabledBlockTypes, Set([.initializerBodies, .statementBlocks]))
22+
}
23+
24+
func testInvalidKeyInCustomConfiguration() {
25+
var config = NoEmptyBlockConfiguration()
26+
XCTAssertEqual(
27+
try Issue.captureConsole { try config.apply(configuration: ["invalidKey": "error"]) },
28+
"warning: Configuration for 'no_empty_block' rule contains the invalid key(s) 'invalidKey'."
29+
)
30+
}
31+
32+
func testInvalidTypeOfCustomConfiguration() {
33+
var config = NoEmptyBlockConfiguration()
34+
checkError(Issue.invalidConfiguration(ruleID: NoEmptyBlockRule.description.identifier)) {
35+
try config.apply(configuration: "invalidKey")
36+
}
37+
}
38+
39+
func testInvalidTypeOfValueInCustomConfiguration() {
40+
var config = NoEmptyBlockConfiguration()
41+
checkError(Issue.invalidConfiguration(ruleID: NoEmptyBlockRule.description.identifier)) {
42+
try config.apply(configuration: ["severity": "foo"])
43+
}
44+
}
45+
46+
func testConsoleDescription() throws {
47+
var config = NoEmptyBlockConfiguration()
48+
try config.apply(configuration: ["disabled_block_types": ["initializer_bodies", "statement_blocks"]])
49+
XCTAssertEqual(
50+
RuleConfigurationDescription.from(configuration: config).oneLiner(),
51+
"severity: warning; disabled_block_types: [initializer_bodies, statement_blocks]"
52+
)
53+
}
54+
}

0 commit comments

Comments
 (0)