Compare commits
1 Commits
main
...
marcelo/te
Author | SHA1 | Date |
---|---|---|
![]() |
0e94f03b0b |
|
@ -178,6 +178,7 @@ public let primaryRuleList = RuleList(rules: [
|
|||
SwitchCaseOnNewlineRule.self,
|
||||
SyntacticSugarRule.self,
|
||||
TestCaseAccessibilityRule.self,
|
||||
TestableImportRule.self,
|
||||
TodoRule.self,
|
||||
ToggleBoolRule.self,
|
||||
TrailingClosureRule.self,
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import SourceKittenFramework
|
||||
import SwiftSyntax
|
||||
|
||||
public struct TestableImportRule: ConfigurationProviderRule, OptInRule, AutomaticTestableRule {
|
||||
public var configuration = SeverityConfiguration(.error)
|
||||
|
||||
public init() {}
|
||||
|
||||
public static let description = RuleDescription(
|
||||
identifier: "testable_import",
|
||||
name: "Testable Import",
|
||||
description: "@testable import should only be used in test files",
|
||||
kind: .lint,
|
||||
nonTriggeringExamples: [
|
||||
Example("import Foo"),
|
||||
Example("""
|
||||
@testable import Foo
|
||||
import XCTest
|
||||
"""),
|
||||
Example("""
|
||||
@testable import Foo
|
||||
import class XCTest.XCTestCase
|
||||
"""),
|
||||
Example("""
|
||||
@testable import Foo
|
||||
import TestUtils
|
||||
|
||||
class FooTests: XCTestCase {}
|
||||
"""),
|
||||
],
|
||||
triggeringExamples: [
|
||||
Example("↓@testable import Foo"),
|
||||
]
|
||||
)
|
||||
|
||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
||||
guard let tree = file.syntaxTree else { return [] }
|
||||
|
||||
let visitor = TestableImportRuleVisitor()
|
||||
visitor.walk(tree)
|
||||
|
||||
if visitor.isTestFile {
|
||||
return []
|
||||
}
|
||||
|
||||
return visitor.positions.map { position in
|
||||
StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, byteOffset: ByteCount(position)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class TestableImportRuleVisitor: SyntaxVisitor {
|
||||
private(set) var positions: [AbsolutePosition] = []
|
||||
private(set) var isTestFile = false
|
||||
|
||||
override func visitPost(_ node: ImportDeclSyntax) {
|
||||
if node.isTestableImport {
|
||||
positions.append(node.positionAfterSkippingLeadingTrivia)
|
||||
}
|
||||
|
||||
let testImports: Set = ["XCTest", "Quick", "Nimble"]
|
||||
let components = node.path.withoutTrivia().map(\.name.text)
|
||||
if !testImports.isDisjoint(with: components) {
|
||||
isTestFile = true
|
||||
}
|
||||
}
|
||||
|
||||
override func visitPost(_ node: ClassDeclSyntax) {
|
||||
let inheritedTypes = node.inheritanceClause?.inheritedTypeCollection.map {
|
||||
$0.withoutTrivia().typeName.description
|
||||
} ?? []
|
||||
let testClasses: Set = ["XCTestCase", "QuickSpec"]
|
||||
if !testClasses.isDisjoint(with: inheritedTypes) {
|
||||
isTestFile = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ImportDeclSyntax {
|
||||
var isTestableImport: Bool {
|
||||
guard let attributes = self.attributes else {
|
||||
return false
|
||||
}
|
||||
|
||||
return attributes.contains { syntax in
|
||||
syntax.as(AttributeSyntax.self)?.attributeName.tokenKind == .identifier("testable")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -785,6 +785,12 @@ class TestCaseAccessibilityRuleTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
class TestableImportRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(TestableImportRule.description)
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleBoolRuleTests: XCTestCase {
|
||||
func testWithDefaultConfiguration() {
|
||||
verifyRule(ToggleBoolRule.description)
|
||||
|
|
Loading…
Reference in New Issue