Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Style/NonOverridableClassDeclarationRule.swift
JP Simard 3eb3772022 Compile with -strict-concurrency=complete (#5320)
* Compile with `-strict-concurrency=complete`

Only in Bazel for now, because this is considered an unsafe flag in
SwiftPM which would lead to warnings for downstream consumers of
SwiftLint using SwiftPM.

Some imports of SwiftSyntax need the `@preconcurrency` annotation until
https://github.com/apple/swift-syntax/pull/2322 is available in a
release.

The following SwiftLint libraries have `-strict-concurrency=complete`
applied:

* SwiftLintCoreMacros
* SwiftLintBuiltInRules
* SwiftLintExtraRules

The following SwiftLint libraries don't have the flag applied and need
to be migrated:

* SwiftLintCore
* swiftlint (CLI target)

So really the rules and macros are now being compiled with
`-strict-concurrency=complete`, but the core infrastructure of SwiftLint
is not.

Still, given that Swift 6 will eventually make these warnings errors by
default, it's good to prevent issues from creeping in earlier rather
than later.

* Add CI job to build with strict concurrency
2023-11-01 15:20:40 +00:00

147 lines
4.8 KiB
Swift

import SwiftSyntax
@SwiftSyntaxRule
struct NonOverridableClassDeclarationRule: SwiftSyntaxCorrectableRule, OptInRule {
var configuration = NonOverridableClassDeclarationConfiguration()
static let description = RuleDescription(
identifier: "non_overridable_class_declaration",
name: "Class Declaration in Final Class",
description: """
Class methods and properties in final classes should themselves be final, just as if the declarations
are private. In both cases, they cannot be overriden. Using `final class` or `static` makes this explicit.
""",
kind: .style,
nonTriggeringExamples: [
Example("""
final class C {
final class var b: Bool { true }
final class func f() {}
}
"""),
Example("""
class C {
final class var b: Bool { true }
final class func f() {}
}
"""),
Example("""
class C {
class var b: Bool { true }
class func f() {}
}
"""),
Example("""
class C {
static var b: Bool { true }
static func f() {}
}
"""),
Example("""
final class C {
static var b: Bool { true }
static func f() {}
}
"""),
Example("""
final class C {
class D {
class var b: Bool { true }
class func f() {}
}
}
""")
],
triggeringExamples: [
Example("""
final class C {
↓class var b: Bool { true }
↓class func f() {}
}
"""),
Example("""
class C {
final class D {
↓class var b: Bool { true }
↓class func f() {}
}
}
"""),
Example("""
class C {
private ↓class var b: Bool { true }
private ↓class func f() {}
}
""")
],
corrections: [
Example("""
final class C {
class func f() {}
}
"""): Example("""
final class C {
final class func f() {}
}
"""),
Example("""
final class C {
class var b: Bool { true }
}
""", configuration: ["final_class_modifier": "static"]): Example("""
final class C {
static var b: Bool { true }
}
""")
]
)
}
private extension NonOverridableClassDeclarationRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
private var finalClassScope = Stack<Bool>()
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
finalClassScope.push(node.modifiers.contains(keyword: .final))
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
_ = finalClassScope.pop()
}
override func visitPost(_ node: FunctionDeclSyntax) {
checkViolations(for: node.modifiers, types: "methods")
}
override func visitPost(_ node: VariableDeclSyntax) {
checkViolations(for: node.modifiers, types: "properties")
}
private func checkViolations(for modifiers: DeclModifierListSyntax, types: String) {
guard !modifiers.contains(keyword: .final),
let classKeyword = modifiers.first(where: { $0.name.text == "class" }),
case let inFinalClass = finalClassScope.peek() == true,
inFinalClass || modifiers.contains(keyword: .private) else {
return
}
violations.append(ReasonedRuleViolation(
position: classKeyword.positionAfterSkippingLeadingTrivia,
reason: inFinalClass
? "Class \(types) in final classes should themselves be final"
: "Private class methods and properties should be declared final",
severity: configuration.severity
))
violationCorrections.append(
ViolationCorrection(
start: classKeyword.positionAfterSkippingLeadingTrivia,
end: classKeyword.endPositionBeforeTrailingTrivia,
replacement: configuration.finalClassModifier.rawValue
)
)
}
}
}