Skip to content
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

Merged
merged 7 commits into from
Oct 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

#### Enhancements

* None.
* Add `test_case_accessibility` rule.
[Keith Smiley](https://github.com/keith)
[#3376](https://github.com/realm/SwiftLint/issues/3376)

#### Bug Fixes

Expand Down
14 changes: 14 additions & 0 deletions Source/SwiftLintFramework/Helpers/XCTestHelpers.swift
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

|| (kind == .functionMethodInstance && name.hasPrefix("test"))
|| ([.varStatic, .varClass].contains(kind) && testVariableNames.contains(name))
}
}
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public let masterRuleList = RuleList(rules: [
SwitchCaseAlignmentRule.self,
SwitchCaseOnNewlineRule.self,
SyntacticSugarRule.self,
TestCaseAccessibilityRule.self,
TodoRule.self,
ToggleBoolRule.self,
TrailingClosureRule.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ public struct EmptyXCTestMethodRule: Rule, OptInRule, ConfigurationProviderRule,
return dictionary.substructure.compactMap { subDictionary -> StyleViolation? in
guard
let kind = subDictionary.declarationKind,
SwiftDeclarationKind.functionKinds.contains(kind),
let name = subDictionary.name, isXCTestMethod(name),
let name = subDictionary.name,
XCTestHelpers.isXCTestMember(kind: kind, name: name,
attributes: subDictionary.enclosedSwiftAttributes),
let offset = subDictionary.offset,
subDictionary.enclosedVarParameters.isEmpty,
subDictionary.substructure.isEmpty else { return nil }
Expand All @@ -44,8 +45,4 @@ public struct EmptyXCTestMethodRule: Rule, OptInRule, ConfigurationProviderRule,
location: Location(file: file, byteOffset: offset))
}
}

private func isXCTestMethod(_ method: String) -> Bool {
return method.hasPrefix("test") || method == "setUp()" || method == "tearDown()"
}
}
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
Expand Up @@ -29,7 +29,9 @@ public struct OverridenSuperCallConfiguration: RuleConfiguration, Equatable {
"viewWillDisappear(_:)",
//XCTestCase
"setUp()",
"tearDown()"
"setUpWithError()",
"tearDown()",
"tearDownWithError()"
]

var severityConfiguration = SeverityConfiguration(.warning)
Expand Down
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)]"
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 disabled_test as an allowed prefix.

/// The untyped configuration to apply to the rule, if deviating from the default configuration.
/// The structure should match what is expected as a configuration value for the rule being tested.
///
/// For example, if the following YAML would be used to configure the rule:
///
/// ```
/// severity: warning
/// ```
///
/// Then the equivalent configuration value would be `["severity": "warning"]`.
public private(set) var configuration: Any?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT it only is for analyze tests

Copy link
Collaborator

Choose a reason for hiding this comment

The 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
}
}
16 changes: 16 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,14 @@
BC87573B2195CF2A00CA7A74 /* ModifierOrderRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC8757392195CDD500CA7A74 /* ModifierOrderRuleExamples.swift */; };
BCB68283216213130078E4C3 /* CompilerProtocolInitRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB68282216213130078E4C3 /* CompilerProtocolInitRuleTests.swift */; };
BFF028AE1CBCF8A500B38A9D /* TrailingWhitespaceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */; };
C225E846252E8B2200EDE3E7 /* TestCaseAccessibilityRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225E844252E8B1B00EDE3E7 /* TestCaseAccessibilityRule.swift */; };
C225E847252E8B2200EDE3E7 /* TestCaseAccessibilityRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225E845252E8B1E00EDE3E7 /* TestCaseAccessibilityRuleExamples.swift */; };
C225E849252E8B3B00EDE3E7 /* XCTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C225E848252E8B2F00EDE3E7 /* XCTestHelpers.swift */; };
C25EBBDF2107884200E27603 /* PrefixedTopLevelConstantRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25EBBDD210787B200E27603 /* PrefixedTopLevelConstantRuleTests.swift */; };
C25EBBE221078D5F00E27603 /* GlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25EBBE021078D5B00E27603 /* GlobTests.swift */; };
C25EBBE521078DCE00E27603 /* Glob.swift in Sources */ = {isa = PBXBuildFile; fileRef = C25EBBE321078DC700E27603 /* Glob.swift */; };
C26330382073DAC500D7B4FD /* LowerACLThanParentRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */; };
C26522A2252E93AC00BF9C0C /* TestCaseAccessibilityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26522A1252E93AC00BF9C0C /* TestCaseAccessibilityConfiguration.swift */; };
C28B2B3D2106DF730009A0FE /* PrefixedConstantRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28B2B3B2106DF210009A0FE /* PrefixedConstantRuleConfiguration.swift */; };
C2A8D076243C0D0300642BC9 /* IBInspectableInExtensionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2A8D075243C0D0300642BC9 /* IBInspectableInExtensionRule.swift */; };
C2B3C1612106F78C00088928 /* ConfigurationAliasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */; };
Expand Down Expand Up @@ -771,10 +775,14 @@
BC8757392195CDD500CA7A74 /* ModifierOrderRuleExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierOrderRuleExamples.swift; sourceTree = "<group>"; };
BCB68282216213130078E4C3 /* CompilerProtocolInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompilerProtocolInitRuleTests.swift; sourceTree = "<group>"; };
BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingWhitespaceConfiguration.swift; sourceTree = "<group>"; };
C225E844252E8B1B00EDE3E7 /* TestCaseAccessibilityRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCaseAccessibilityRule.swift; sourceTree = "<group>"; };
C225E845252E8B1E00EDE3E7 /* TestCaseAccessibilityRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCaseAccessibilityRuleExamples.swift; sourceTree = "<group>"; };
C225E848252E8B2F00EDE3E7 /* XCTestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTestHelpers.swift; sourceTree = "<group>"; };
C25EBBDD210787B200E27603 /* PrefixedTopLevelConstantRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefixedTopLevelConstantRuleTests.swift; sourceTree = "<group>"; };
C25EBBE021078D5B00E27603 /* GlobTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobTests.swift; sourceTree = "<group>"; };
C25EBBE321078DC700E27603 /* Glob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glob.swift; sourceTree = "<group>"; };
C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LowerACLThanParentRule.swift; sourceTree = "<group>"; };
C26522A1252E93AC00BF9C0C /* TestCaseAccessibilityConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCaseAccessibilityConfiguration.swift; sourceTree = "<group>"; };
C28B2B3B2106DF210009A0FE /* PrefixedConstantRuleConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefixedConstantRuleConfiguration.swift; sourceTree = "<group>"; };
C2A8D075243C0D0300642BC9 /* IBInspectableInExtensionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IBInspectableInExtensionRule.swift; sourceTree = "<group>"; };
C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationAliasesTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1128,6 +1136,7 @@
3BCC04CF1C4F56D3006073C3 /* SeverityLevelsConfiguration.swift */,
725094881D0855760039B353 /* StatementModeConfiguration.swift */,
787CDE38208E7D41005F3D2F /* SwitchCaseAlignmentConfiguration.swift */,
C26522A1252E93AC00BF9C0C /* TestCaseAccessibilityConfiguration.swift */,
D450D1DA21F1992E00E60010 /* TrailingClosureConfiguration.swift */,
D40F83871DE9179200524C62 /* TrailingCommaConfiguration.swift */,
BF48D2D61CBCCA5F0080BDAE /* TrailingWhitespaceConfiguration.swift */,
Expand All @@ -1146,6 +1155,7 @@
C25EBBE321078DC700E27603 /* Glob.swift */,
D4AB0EA11F8993DD00CEC380 /* NamespaceCollector.swift */,
4DCB8E7D1CBE43640070FCF0 /* RegexHelpers.swift */,
C225E848252E8B2F00EDE3E7 /* XCTestHelpers.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -1243,6 +1253,8 @@
B89F3BC91FD5ED9000931E59 /* RequiredEnumCaseRule.swift */,
D450D1D021EC4A6900E60010 /* StrongIBOutletRule.swift */,
D40E041B1F46E3B30043BC4E /* SuperfluousDisableCommandRule.swift */,
C225E844252E8B1B00EDE3E7 /* TestCaseAccessibilityRule.swift */,
C225E845252E8B1E00EDE3E7 /* TestCaseAccessibilityRuleExamples.swift */,
E88DEA811B0990A700A66CB0 /* TodoRule.swift */,
D47421F324E14760009AE788 /* UnneededNotificationCenterRemovalRule.swift */,
7565E5F02262BA0900B0597C /* UnusedCaptureListRule.swift */,
Expand Down Expand Up @@ -2080,6 +2092,7 @@
D4F10614229A2F5E00FDE319 /* NoFallthroughOnlyRuleExamples.swift in Sources */,
3ABE19CF20B7CE32009C2EC2 /* MultilineFunctionChainsRule.swift in Sources */,
82FE253F20F604AD00295958 /* VerticalWhitespaceOpeningBracesRule.swift in Sources */,
C26522A2252E93AC00BF9C0C /* TestCaseAccessibilityConfiguration.swift in Sources */,
827169B51F48D712003FB9AF /* NoGroupingExtensionRule.swift in Sources */,
D41B57781ED8CEE0007B0470 /* ExtensionAccessModifierRule.swift in Sources */,
E881985C1BEA978500333A11 /* TrailingNewlineRule.swift in Sources */,
Expand Down Expand Up @@ -2137,6 +2150,7 @@
623675B01F960C5C009BE6F3 /* QuickDiscouragedPendingTestRule.swift in Sources */,
8F4E30D52519092800EED8CB /* UnusedDeclarationRuleExamples.swift in Sources */,
287F8B642230843000BDC504 /* NSLocalizedStringRequireBundleRule.swift in Sources */,
C225E847252E8B2200EDE3E7 /* TestCaseAccessibilityRuleExamples.swift in Sources */,
D47079AD1DFE2FA700027086 /* EmptyParametersRule.swift in Sources */,
E87E4A091BFB9CAE00FCFE46 /* SyntaxKind+SwiftLint.swift in Sources */,
3B0B14541C505D6300BE82F7 /* SeverityConfiguration.swift in Sources */,
Expand Down Expand Up @@ -2191,6 +2205,7 @@
E889D8C71F1D357B00058332 /* Configuration+Merging.swift in Sources */,
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */,
D4EA77C81F817FD200C315FB /* UnneededBreakInSwitchRule.swift in Sources */,
C225E846252E8B2200EDE3E7 /* TestCaseAccessibilityRule.swift in Sources */,
D4D383852145F550000235BD /* StaticOperatorRule.swift in Sources */,
CC6D285B2292F0600052B682 /* IndentationWidthConfiguration.swift in Sources */,
006204DC1E1E492F00FFFBE1 /* VerticalWhitespaceConfiguration.swift in Sources */,
Expand Down Expand Up @@ -2409,6 +2424,7 @@
A1A6F3F21EE319ED00A9F9E2 /* ObjectLiteralConfiguration.swift in Sources */,
D489B548231233A40090BAA0 /* ContainsOverFilterCountRule.swift in Sources */,
D4B0228E1E0CC608007E5297 /* ClassDelegateProtocolRule.swift in Sources */,
C225E849252E8B3B00EDE3E7 /* XCTestHelpers.swift in Sources */,
D4369C572430830000505BB9 /* ComputedAccessorsOrderRuleExamples.swift in Sources */,
E881985F1BEA987C00333A11 /* TypeNameRule.swift in Sources */,
D40AD08A1E032F9700F48C30 /* UnusedClosureParameterRule.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,12 @@ extension SyntacticSugarRuleTests {
]
}

extension TestCaseAccessibilityRuleTests {
static var allTests: [(String, (TestCaseAccessibilityRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}

extension TodoRuleTests {
static var allTests: [(String, (TodoRuleTests) -> () throws -> Void)] = [
("testTodo", testTodo),
Expand Down Expand Up @@ -1900,6 +1906,7 @@ XCTMain([
testCase(SwitchCaseAlignmentRuleTests.allTests),
testCase(SwitchCaseOnNewlineRuleTests.allTests),
testCase(SyntacticSugarRuleTests.allTests),
testCase(TestCaseAccessibilityRuleTests.allTests),
testCase(TodoRuleTests.allTests),
testCase(ToggleBoolRuleTests.allTests),
testCase(TrailingClosureConfigurationTests.allTests),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,12 @@ class SyntacticSugarRuleTests: XCTestCase {
}
}

class TestCaseAccessibilityRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(TestCaseAccessibilityRule.description)
}
}

class ToggleBoolRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(ToggleBoolRule.description)
Expand Down