-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TestCaseAccessibilityRule #3376
Changes from all commits
dc6e09d
d789185
16ee926
82c057f
87dbfa2
9c0da88
33e8b93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import SourceKittenFramework | ||
|
||
private let testVariableNames: Set = [ | ||
"allTests" | ||
] | ||
|
||
enum XCTestHelpers { | ||
static func isXCTestMember(kind: SwiftDeclarationKind, name: String, | ||
attributes: [SwiftDeclarationAttributeKind]) -> Bool { | ||
return attributes.contains(.override) | ||
|| (kind == .functionMethodInstance && name.hasPrefix("test")) | ||
|| ([.varStatic, .varClass].contains(kind) && testVariableNames.contains(name)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import SourceKittenFramework | ||
|
||
public struct TestCaseAccessibilityRule: Rule, OptInRule, ConfigurationProviderRule, AutomaticTestableRule { | ||
public var configuration = TestCaseAccessibilityConfiguration() | ||
|
||
public init() {} | ||
|
||
public static let description = RuleDescription( | ||
identifier: "test_case_accessibility", | ||
name: "Test case accessibility", | ||
description: "Test cases should only contain private non-test members.", | ||
kind: .lint, | ||
nonTriggeringExamples: TestCaseAccessibilityRuleExamples.nonTriggeringExamples, | ||
triggeringExamples: TestCaseAccessibilityRuleExamples.triggeringExamples | ||
) | ||
|
||
public func validate(file: SwiftLintFile) -> [StyleViolation] { | ||
return testClasses(in: file).flatMap { violations(in: file, for: $0) } | ||
} | ||
|
||
// MARK: - Private | ||
|
||
private func testClasses(in file: SwiftLintFile) -> [SourceKittenDictionary] { | ||
let dict = file.structureDictionary | ||
return dict.substructure.filter { dictionary in | ||
dictionary.declarationKind == .class && dictionary.inheritedTypes.contains("XCTestCase") | ||
} | ||
} | ||
|
||
private func violations(in file: SwiftLintFile, | ||
for dictionary: SourceKittenDictionary) -> [StyleViolation] { | ||
return dictionary.substructure.compactMap { subDictionary -> StyleViolation? in | ||
guard | ||
let kind = subDictionary.declarationKind, | ||
kind != .varLocal, | ||
let name = subDictionary.name, | ||
!isXCTestMember(kind: kind, name: name, attributes: subDictionary.enclosedSwiftAttributes), | ||
let offset = subDictionary.offset, | ||
subDictionary.accessibility?.isPrivate != true else { return nil } | ||
|
||
return StyleViolation(ruleDescription: Self.description, | ||
severity: configuration.severity, | ||
location: Location(file: file, byteOffset: offset)) | ||
} | ||
} | ||
|
||
private func isXCTestMember(kind: SwiftDeclarationKind, name: String, | ||
attributes: [SwiftDeclarationAttributeKind]) -> Bool { | ||
return XCTestHelpers.isXCTestMember(kind: kind, name: name, attributes: attributes) | ||
|| configuration.allowedPrefixes.contains(where: name.hasPrefix) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
internal struct TestCaseAccessibilityRuleExamples { | ||
static let nonTriggeringExamples = [ | ||
// Valid XCTestCase class | ||
|
||
Example(""" | ||
let foo: String? | ||
|
||
class FooTests: XCTestCase { | ||
static let allTests: [String] = [] | ||
|
||
private let foo: String { | ||
let nestedMember = "hi" | ||
return nestedMember | ||
} | ||
|
||
override static func setUp() { | ||
super.setUp() | ||
} | ||
|
||
override func setUp() { | ||
super.setUp() | ||
} | ||
|
||
override func setUpWithError() throws { | ||
try super.setUpWithError() | ||
} | ||
|
||
override static func tearDown() { | ||
super.tearDown() | ||
} | ||
|
||
override func tearDown() { | ||
super.tearDown() | ||
} | ||
|
||
override func tearDownWithError() { | ||
try super.tearDownWithError() | ||
} | ||
|
||
override func someFutureXCTestFunction() { | ||
super.someFutureXCTestFunction() | ||
} | ||
|
||
func testFoo() { | ||
XCTAssertTrue(true) | ||
} | ||
} | ||
"""), | ||
|
||
// Not an XCTestCase class | ||
|
||
Example(""" | ||
class Foobar { | ||
func setUp() {} | ||
|
||
func tearDown() {} | ||
|
||
func testFoo() {} | ||
} | ||
""") | ||
] | ||
|
||
static let triggeringExamples = [ | ||
Example(""" | ||
class FooTests: XCTestCase { | ||
↓var foo: String? | ||
↓let bar: String? | ||
|
||
↓static func foo() {} | ||
|
||
↓func setUp(withParam: String) {} | ||
|
||
↓func foobar() {} | ||
|
||
↓func not_testBar() {} | ||
|
||
↓enum Nested {} | ||
|
||
↓static func testFoo() {} | ||
|
||
↓static func allTests() {} | ||
} | ||
|
||
final class BarTests: XCTestCase { | ||
↓class Nested {} | ||
} | ||
""") | ||
] | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,27 @@ | ||||||||||||||||||||||||
public struct TestCaseAccessibilityConfiguration: RuleConfiguration, Equatable { | ||||||||||||||||||||||||
public private(set) var severityConfiguration = SeverityConfiguration(.warning) | ||||||||||||||||||||||||
public private(set) var allowedPrefixes: Set<String> = [] | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
public var consoleDescription: String { | ||||||||||||||||||||||||
return severityConfiguration.consoleDescription + | ||||||||||||||||||||||||
", allowed_prefixes: [\(allowedPrefixes)]" | ||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Examples can now specify configurations, so it'd be nice to test something like SwiftLint/Source/SwiftLintFramework/Models/Example.swift Lines 6 to 16 in da408b5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there an example of that working? I tried that and I don't believe it's passed all the way through the tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAICT it only is for analyze tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I think you're right. Let's hold off for now, but it should be easy to make normal lint tests apply this configuration value. |
||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
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 allowedPrefixes = configuration["allowed_prefixes"] as? [String] { | ||||||||||||||||||||||||
self.allowedPrefixes = Set(allowedPrefixes) | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
public var severity: ViolationSeverity { | ||||||||||||||||||||||||
return severityConfiguration.severity | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.