Add `test_parent_class` option to more test rules (#4262)

This commit is contained in:
Martin Redington 2022-12-01 17:56:13 +00:00 committed by GitHub
parent 92304cdd98
commit 7a8b2d1dab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 34 deletions

View File

@ -100,6 +100,10 @@
SwiftLintFramework. This only impacts the programmatic API for the
SwiftLintFramework module.
[JP Simard](https://github.com/jpsim)
* The `balanced_xctest_lifecycle`, `single_test_class`, `empty_xctest_method` and
`test_case_accessibility` rules will now be applied to subclasses of `QuickSpec`,
as well as `XCTestCase`, by default.
[Martin Redington](https://github.com/mildm8nnered)
#### Experimental
@ -341,6 +345,11 @@
* Print violations in realtime if `--progress` and `--output` are both set.
[JP Simard](https://github.com/jpsim)
* Add `test_parent_classes` option to `balanced_xctest_lifecycle`, `single_test_class`
and `empty_xctest_method` rules.
[Martin Redington](https://github.com/mildm8nnered)
[#4200](https://github.com/realm/SwiftLint/issues/4200)
* Trigger `prefer_self_in_static_references` rule on more type references like:
* Key paths (e.g. `\MyType.myVar` -> `\Self.myVar`)
* Computed properties (e.g. `var i: Int { MyType.myVar )` -> `var i: Int { Self.myVar }`)

View File

@ -75,6 +75,16 @@ extension ByteSourceRange {
}
}
extension ClassDeclSyntax {
func isXCTestCase(_ testParentClasses: Set<String>) -> Bool {
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
return false
}
let inheritedTypes = inheritanceList.compactMap { $0.typeName.as(SimpleTypeIdentifierSyntax.self)?.name.text }
return testParentClasses.intersection(inheritedTypes).isNotEmpty
}
}
extension ExprSyntax {
var asFunctionCall: FunctionCallExprSyntax? {
if let functionCall = self.as(FunctionCallExprSyntax.self) {

View File

@ -3,7 +3,7 @@ import SwiftSyntax
struct BalancedXCTestLifecycleRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
// MARK: - Properties
var configuration = SeverityConfiguration(.warning)
var configuration = BalancedXCTestLifecycleConfiguration()
static let description = RuleDescription(
identifier: "balanced_xctest_lifecycle",
@ -113,16 +113,22 @@ struct BalancedXCTestLifecycleRule: SwiftSyntaxRule, OptInRule, ConfigurationPro
// MARK: - Public
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
Visitor(viewMode: .sourceAccurate, testClasses: configuration.testParentClasses)
}
}
private extension BalancedXCTestLifecycleRule {
final class Visitor: ViolationsSyntaxVisitor {
private let testClasses: Set<String>
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
init(viewMode: SyntaxTreeViewMode, testClasses: Set<String>) {
self.testClasses = testClasses
super.init(viewMode: viewMode)
}
override func visitPost(_ node: ClassDeclSyntax) {
guard node.isXCTestCase else {
guard node.isXCTestCase(testClasses) else {
return
}
@ -149,17 +155,6 @@ private extension BalancedXCTestLifecycleRule {
}
}
private extension ClassDeclSyntax {
var isXCTestCase: Bool {
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
return false
}
return inheritanceList.contains { type in
type.typeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "XCTestCase"
}
}
}
// MARK: - Private
private enum XCTMethod {

View File

@ -1,7 +1,7 @@
import SwiftSyntax
struct EmptyXCTestMethodRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration(.warning)
var configuration = EmptyXCTestMethodConfiguration()
init() {}
@ -15,15 +15,21 @@ struct EmptyXCTestMethodRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxR
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
EmptyXCTestMethodRuleVisitor(viewMode: .sourceAccurate)
EmptyXCTestMethodRuleVisitor(testParentClasses: configuration.testParentClasses)
}
}
private final class EmptyXCTestMethodRuleVisitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
private let testParentClasses: Set<String>
init(testParentClasses: Set<String>) {
self.testParentClasses = testParentClasses
super.init(viewMode: .sourceAccurate)
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
node.isXCTestCase ? .visitChildren : .skipChildren
node.isXCTestCase(testParentClasses) ? .visitChildren : .skipChildren
}
override func visitPost(_ node: FunctionDeclSyntax) {
@ -33,17 +39,6 @@ private final class EmptyXCTestMethodRuleVisitor: ViolationsSyntaxVisitor {
}
}
private extension ClassDeclSyntax {
var isXCTestCase: Bool {
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
return false
}
return inheritanceList.contains { type in
type.typeName.as(SimpleTypeIdentifierSyntax.self)?.name.text == "XCTestCase"
}
}
}
private extension FunctionDeclSyntax {
var hasEmptyBody: Bool {
if let body = body {

View File

@ -1,12 +1,12 @@
struct TestCaseAccessibilityConfiguration: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration(.warning)
private(set) var allowedPrefixes: Set<String> = []
private(set) var testParentClasses: Set<String> = ["XCTestCase"]
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
var consoleDescription: String {
return severityConfiguration.consoleDescription +
", allowed_prefixes: [\(allowedPrefixes)]" +
", test_parent_classes: [\(testParentClasses)]"
", allowed_prefixes: \(allowedPrefixes.sorted())" +
", test_parent_classes: \(testParentClasses.sorted())"
}
mutating func apply(configuration: Any) throws {

View File

@ -0,0 +1,31 @@
public typealias BalancedXCTestLifecycleConfiguration = UnitTestRuleConfiguration
public typealias EmptyXCTestMethodConfiguration = UnitTestRuleConfiguration
public typealias SingleTestClassConfiguration = UnitTestRuleConfiguration
public struct UnitTestRuleConfiguration: SeverityBasedRuleConfiguration, Equatable {
public private(set) var severityConfiguration = SeverityConfiguration(.warning)
public private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
public var consoleDescription: String {
return severityConfiguration.consoleDescription +
", test_parent_classes: \(testParentClasses.sorted())"
}
public mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw ConfigurationError.unknownConfiguration
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let extraTestParentClasses = configuration["test_parent_classes"] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
}
public var severity: ViolationSeverity {
return severityConfiguration.severity
}
}

View File

@ -1,7 +1,7 @@
import SwiftSyntax
struct SingleTestClassRule: SourceKitFreeRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration(.warning)
var configuration = SingleTestClassConfiguration()
static let description = RuleDescription(
identifier: "single_test_class",
@ -52,7 +52,7 @@ struct SingleTestClassRule: SourceKitFreeRule, OptInRule, ConfigurationProviderR
init() {}
func validate(file: SwiftLintFile) -> [StyleViolation] {
let classes = TestClassVisitor(viewMode: .sourceAccurate)
let classes = TestClassVisitor(viewMode: .sourceAccurate, testClasses: configuration.testParentClasses)
.walk(tree: file.syntaxTree, handler: \.violations)
guard classes.count > 1 else { return [] }
@ -67,9 +67,14 @@ struct SingleTestClassRule: SourceKitFreeRule, OptInRule, ConfigurationProviderR
}
private class TestClassVisitor: ViolationsSyntaxVisitor {
private let testClasses: Set = ["QuickSpec", "XCTestCase"]
private let testClasses: Set<String>
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
init(viewMode: SyntaxTreeViewMode, testClasses: Set<String>) {
self.testClasses = testClasses
super.init(viewMode: viewMode)
}
override func visitPost(_ node: ClassDeclSyntax) {
guard node.inheritanceClause.containsInheritedType(inheritedTypes: testClasses) else {
return