Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public let builtInRules: [any Rule.Type] = [
ReduceBooleanRule.self,
ReduceIntoRule.self,
RedundantDiscardableLetRule.self,
RedundantFinalActorRule.self,
RedundantNilCoalescingRule.self,
RedundantObjcAttributeRule.self,
RedundantSelfRule.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import SwiftSyntax

@SwiftSyntaxRule(correctable: true, optIn: true)
struct RedundantFinalActorRule: Rule {
var configuration = SeverityConfiguration<Self>(.warning)

static let description = RuleDescription(
identifier: "redundant_final_actor",
name: "Redundant Final on Actor",
description: "`final` is redundant on an actor declaration and its members because actors cannot be subclassed",
rationale: """
Actors in Swift currently do not support inheritance, making `final` redundant \
on both actor declarations and their members. Note that this may change in future \
Swift versions if actor inheritance is introduced.
""",
kind: .idiomatic,
nonTriggeringExamples: [
Example("actor MyActor {}"),
Example("final class MyClass {}"),
Example("""
@globalActor
actor MyGlobalActor {}
"""),
Example("""
actor MyActor {
func doWork() {}
var value: Int { 0 }
}
"""),
Example("""
class MyClass {
final func doWork() {}
}
"""),
],
triggeringExamples: [
Example("↓final actor MyActor {}"),
Example("public ↓final actor DataStore {}"),
Example("""
@globalActor
↓final actor MyGlobalActor {}
"""),
Example("""
actor MyActor {
↓final func doWork() {}
}
"""),
Example("""
actor MyActor {
↓final var value: Int { 0 }
}
"""),
],
corrections: [
Example("final actor MyActor {}"):
Example("actor MyActor {}"),
Example("public final actor DataStore {}"):
Example("public actor DataStore {}"),
Example("actor MyActor {\n final func doWork() {}\n}"):
Example("actor MyActor {\n func doWork() {}\n}"),
]
)
}

private extension RedundantFinalActorRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
private var insideActor = false

override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
insideActor = true
return .visitChildren
}

override func visitPost(_ node: ActorDeclSyntax) {
if let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) {
addViolation(for: finalModifier)
}
insideActor = false
}

override func visitPost(_ node: FunctionDeclSyntax) {
guard insideActor,
let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) else {
return
}
addViolation(for: finalModifier)
}

override func visitPost(_ node: VariableDeclSyntax) {
guard insideActor,
let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) else {
return
}
addViolation(for: finalModifier)
}

override func visitPost(_ node: SubscriptDeclSyntax) {
guard insideActor,
let finalModifier = node.modifiers.first(where: { $0.name.text == "final" }) else {
return
}
addViolation(for: finalModifier)
}

// Don't descend into nested classes where `final` is meaningful
override func visit(_: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
.skipChildren
}

private func addViolation(for finalModifier: DeclModifierSyntax) {
let start = finalModifier.positionAfterSkippingLeadingTrivia
let end = finalModifier.endPosition
violations.append(
ReasonedRuleViolation(
position: start,
correction: .init(
start: start,
end: end,
replacement: ""
)
)
)
}
}
}
4 changes: 2 additions & 2 deletions Tests/GeneratedTests/GeneratedTests_07.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ final class RedundantDiscardableLetRuleGeneratedTests: SwiftLintTestCase {
}
}

final class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase {
final class RedundantFinalActorRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantNilCoalescingRule.description)
verifyRule(RedundantFinalActorRule.description)
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_08.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class RedundantNilCoalescingRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantNilCoalescingRule.description)
}
}

final class RedundantObjcAttributeRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(RedundantObjcAttributeRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class StrictFilePrivateRuleGeneratedTests: SwiftLintTestCase {
verifyRule(StrictFilePrivateRule.description)
}
}

final class StrongIBOutletRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StrongIBOutletRule.description)
}
}
12 changes: 6 additions & 6 deletions Tests/GeneratedTests/GeneratedTests_09.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class StrongIBOutletRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(StrongIBOutletRule.description)
}
}

final class SuperfluousElseRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(SuperfluousElseRule.description)
Expand Down Expand Up @@ -150,9 +156,3 @@ final class UnneededSynthesizedInitializerRuleGeneratedTests: SwiftLintTestCase
verifyRule(UnneededSynthesizedInitializerRule.description)
}
}

final class UnneededThrowsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededThrowsRule.description)
}
}
6 changes: 6 additions & 0 deletions Tests/GeneratedTests/GeneratedTests_10.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
@testable import SwiftLintCore
import TestHelpers

final class UnneededThrowsRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnneededThrowsRule.description)
}
}

final class UnownedVariableCaptureRuleGeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule(UnownedVariableCaptureRule.description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,11 @@ redundant_discardable_let:
meta:
opt-in: false
correctable: true
redundant_final_actor:
severity: warning
meta:
opt-in: true
correctable: true
redundant_nil_coalescing:
severity: warning
meta:
Expand Down