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 4 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
24 changes: 24 additions & 0 deletions Source/SwiftLintFramework/Helpers/XCTestHelpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SourceKittenFramework

private let testFunctionNames: Set = [
"setUp()",
keith marked this conversation as resolved.
Show resolved Hide resolved
"setUpWithError()",
"tearDown()",
"tearDownWithError()"
]

private let testVariableNames: Set = [
"allTests"
]

enum XCTestHelpers {
static func isXCTestMember(kind: SwiftDeclarationKind, name: String) -> Bool {
if SwiftDeclarationKind.functionKinds.contains(kind) {
return name.hasPrefix("test") || testFunctionNames.contains(name)
keith marked this conversation as resolved.
Show resolved Hide resolved
} else if SwiftDeclarationKind.variableKinds.contains(kind) {
return testVariableNames.contains(name)
keith marked this conversation as resolved.
Show resolved Hide resolved
}

return false
}
}
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,8 @@ 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),
let offset = subDictionary.offset,
subDictionary.enclosedVarParameters.isEmpty,
subDictionary.substructure.isEmpty else { return nil }
Expand All @@ -44,8 +44,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
guard dictionary.declarationKind == .class else { return false }
return dictionary.inheritedTypes.contains("XCTestCase")
keith marked this conversation as resolved.
Show resolved Hide resolved
}
}

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),
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) -> Bool {
return XCTestHelpers.isXCTestMember(kind: kind, name: name)
|| configuration.methodPrefixes.contains { name.hasPrefix($0) }
keith marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
internal struct TestCaseAccessibilityRuleExamples {
static let nonTriggeringExamples = [
// Valid XCTestCase class

Example("""
let foo: String?

class FooTests: XCTestCase {
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()
}

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 {}
}

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 methodPrefixes: Set<String> = []

public var consoleDescription: String {
return severityConfiguration.consoleDescription +
", method_prefixes: [\(methodPrefixes)]"
}

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 methodPrefixes = configuration["method_prefixes"] as? [String] {
self.methodPrefixes = Set(methodPrefixes)
}
}

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