Files
Danny Mösch 47335d7f95 Remove tracking of correction positions (#5950)
Report number of corrections per file instead.
2025-04-05 06:04:37 -04:00

71 lines
2.8 KiB
Swift

import SwiftLintCore
import SwiftSyntax
@SwiftSyntaxRule(explicitRewriter: true, optIn: true)
struct FinalTestCaseRule: Rule {
var configuration = FinalTestCaseConfiguration()
static let description = RuleDescription(
identifier: "final_test_case",
name: "Final Test Case",
description: "Test cases should be final",
kind: .performance,
nonTriggeringExamples: [
Example("final class Test: XCTestCase {}"),
Example("open class Test: XCTestCase {}"),
Example("public final class Test: QuickSpec {}"),
Example("class Test: MyTestCase {}"),
Example("struct Test: MyTestCase {}", configuration: ["test_parent_classes": "MyTestCase"]),
],
triggeringExamples: [
Example("class ↓Test: XCTestCase {}"),
Example("public class ↓Test: QuickSpec {}"),
Example("class ↓Test: MyTestCase {}", configuration: ["test_parent_classes": "MyTestCase"]),
],
corrections: [
Example("class ↓Test: XCTestCase {}"):
Example("final class Test: XCTestCase {}"),
Example("internal class ↓Test: XCTestCase {}"):
Example("internal final class Test: XCTestCase {}"),
]
)
}
private extension FinalTestCaseRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: ClassDeclSyntax) {
if node.isNonFinalTestClass(parentClasses: configuration.testParentClasses) {
violations.append(node.name.positionAfterSkippingLeadingTrivia)
}
}
}
final class Rewriter: ViolationsSyntaxRewriter<ConfigurationType> {
override func visit(_ node: ClassDeclSyntax) -> DeclSyntax {
var newNode = node
if node.isNonFinalTestClass(parentClasses: configuration.testParentClasses) {
numberOfCorrections += 1
let finalModifier = DeclModifierSyntax(name: .keyword(.final))
newNode =
if node.modifiers.isEmpty {
node
.with(\.modifiers, [finalModifier.with(\.leadingTrivia, node.classKeyword.leadingTrivia)])
.with(\.classKeyword.leadingTrivia, .space)
} else {
node
.with(\.modifiers, node.modifiers + [finalModifier.with(\.trailingTrivia, .space)])
}
}
return super.visit(newNode)
}
}
}
private extension ClassDeclSyntax {
func isNonFinalTestClass(parentClasses: Set<String>) -> Bool {
inheritanceClause.containsInheritedType(inheritedTypes: parentClasses)
&& !modifiers.contains(keyword: .open)
&& !modifiers.contains(keyword: .final)
}
}