Skip to content

Commit 2d01a01

Browse files
Add redundant_final_actor rule
Actors in Swift cannot be subclassed (SE-0306), making the `final` modifier redundant on actor declarations. This new opt-in rule detects and auto-corrects `final actor` to just `actor`. Examples: - `final actor MyActor {}` → `actor MyActor {}` - `public final actor DataStore {}` → `public actor DataStore {}` Closes #6407
1 parent cc3b22c commit 2d01a01

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public let builtInRules: [any Rule.Type] = [
176176
ReduceBooleanRule.self,
177177
ReduceIntoRule.self,
178178
RedundantDiscardableLetRule.self,
179+
RedundantFinalActorRule.self,
179180
RedundantNilCoalescingRule.self,
180181
RedundantObjcAttributeRule.self,
181182
RedundantSelfRule.self,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import SwiftSyntax
2+
3+
@SwiftSyntaxRule(explicitRewriter: true)
4+
struct RedundantFinalActorRule: OptInRule {
5+
var configuration = SeverityConfiguration<Self>(.warning)
6+
7+
static let description = RuleDescription(
8+
identifier: "redundant_final_actor",
9+
name: "Redundant Final on Actor",
10+
description: "`final` is redundant on an actor declaration because actors cannot be subclassed",
11+
kind: .idiomatic,
12+
nonTriggeringExamples: [
13+
Example("actor MyActor {}"),
14+
Example("final class MyClass {}"),
15+
Example("""
16+
@globalActor
17+
actor MyGlobalActor {}
18+
"""),
19+
],
20+
triggeringExamples: [
21+
Example("↓final actor MyActor {}"),
22+
Example("public ↓final actor DataStore {}"),
23+
Example("""
24+
@globalActor
25+
↓final actor MyGlobalActor {}
26+
"""),
27+
],
28+
corrections: [
29+
Example("final actor MyActor {}"):
30+
Example("actor MyActor {}"),
31+
Example("public final actor DataStore {}"):
32+
Example("public actor DataStore {}"),
33+
]
34+
)
35+
}
36+
37+
private extension RedundantFinalActorRule {
38+
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
39+
override func visitPost(_ node: ActorDeclSyntax) {
40+
if let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) {
41+
violations.append(finalModifier.positionAfterSkippingLeadingTrivia)
42+
}
43+
}
44+
}
45+
46+
final class Rewriter: ViolationsSyntaxRewriter<ConfigurationType> {
47+
override func visit(_ node: ActorDeclSyntax) -> DeclSyntax {
48+
guard let finalIndex = node.modifiers.firstIndex(where: { $0.name.text == "final" }) else {
49+
return super.visit(node)
50+
}
51+
numberOfCorrections += 1
52+
var modifiers = node.modifiers
53+
modifiers.remove(at: finalIndex)
54+
// If no modifiers remain, preserve the leading trivia on the actor keyword
55+
var result = node.with(\.modifiers, modifiers)
56+
if modifiers.isEmpty {
57+
let leadingTrivia = node.modifiers[finalIndex].leadingTrivia
58+
result = result.with(\.actorKeyword.leadingTrivia, leadingTrivia)
59+
}
60+
return super.visit(result)
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)