From cf545c6892a650520da84e2ff0f99d41e186e6b7 Mon Sep 17 00:00:00 2001 From: Ornithologist Coder Date: Tue, 15 Aug 2017 23:34:47 +0200 Subject: [PATCH 1/5] Add quick_single_spec opt-in rule Implements #1779 (Limit number of QuickSpecs in file to one). --- CHANGELOG.md | 5 ++ Rules.md | 45 +++++++++++++++ .../Models/MasterRuleList.swift | 1 + .../Rules/QuickSingleSpecRule.swift | 57 +++++++++++++++++++ SwiftLint.xcodeproj/project.pbxproj | 4 ++ Tests/LinuxMain.swift | 1 + .../SwiftLintFrameworkTests/RulesTests.swift | 4 ++ 7 files changed, 117 insertions(+) create mode 100644 Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e0b0b89d0..e1c61fe96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ [Ryan Booker](https://github.com/ryanbooker) [#1761](https://github.com/realm/SwiftLint/issues/1761) +* Add `quick_single_spec` opt-in rule to enforce test files + to contain a single QuickSpec classes. + [Ornithologist Coder](https://github.com/ornithocoder) + [#1779](https://github.com/realm/SwiftLint/issues/1779) + ##### Bug Fixes * Fix false positive on `force_unwrapping` rule when declaring diff --git a/Rules.md b/Rules.md index 9c89471360..2c981a82e5 100644 --- a/Rules.md +++ b/Rules.md @@ -67,6 +67,7 @@ * [Private Unit Test](#private-unit-test) * [Prohibited calls to super](#prohibited-calls-to-super) * [Protocol Property Accessors Order](#protocol-property-accessors-order) +* [Quick Single Spec](#quick-single-spec) * [Redundant Discardable Let](#redundant-discardable-let) * [Redundant Nil Coalescing](#redundant-nil-coalescing) * [Redundant Optional Initialization](#redundant-optional-initialization) @@ -7906,6 +7907,50 @@ protocol Foo { +## Quick Single Spec + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`quick_single_spec` | Disabled | No | style + +Test files should contain a single QuickSpec class. + +### Examples + +
+Non Triggering Examples + +```swift +class FooTests { } + +``` + +```swift +class FooTests: QuickSpec { } + +``` + +
+
+Triggering Examples + +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: QuickSpec { } + +``` + +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: QuickSpec { } +↓class TotoTests: QuickSpec { } + +``` + +
+ + + ## Redundant Discardable Let Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index b1ce8c90ce..0b18c05f51 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -75,6 +75,7 @@ public let masterRuleList = RuleList(rules: [ PrivateUnitTestRule.self, ProhibitedSuperRule.self, ProtocolPropertyAccessorsOrderRule.self, + QuickSingleSpecRule.self, RedundantDiscardableLetRule.self, RedundantNilCoalescingRule.self, RedundantOptionalInitializationRule.self, diff --git a/Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift b/Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift new file mode 100644 index 0000000000..bbdf22bc42 --- /dev/null +++ b/Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift @@ -0,0 +1,57 @@ +// +// QuickSpecLimitRule.swift +// SwiftLint +// +// Created by Ornithologist Coder on 8/15/17. +// Copyright © 2017 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct QuickSingleSpecRule: Rule, OptInRule, ConfigurationProviderRule { + public var configuration = SeverityConfiguration(.warning) + + public init() {} + + public static let description = RuleDescription( + identifier: "quick_single_spec", + name: "Quick Single Spec", + description: "Test files should contain a single QuickSpec class.", + kind: .style, + nonTriggeringExamples: [ + "class FooTests { }\n", + "class FooTests: QuickSpec { }\n" + ], + triggeringExamples: [ + "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n", + "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n↓class TotoTests: QuickSpec { }\n" + ] + ) + + public func validate(file: File) -> [StyleViolation] { + let specs = quickSpecs(in: file) + + guard specs.count > 1 else { return [] } + + return specs.flatMap(toViolation(in: file, configuration: configuration, numberOfSpecs: specs.count)) + } + + // MARK: - Private + + private func quickSpecs(in file: File) -> [[String: SourceKitRepresentable]] { + return file.structure.dictionary.substructure.filter { $0.inheritedTypes.contains("QuickSpec") } + } + + private func toViolation(in file: File, + configuration: SeverityConfiguration, + numberOfSpecs: Int) -> ([String: SourceKitRepresentable]) -> StyleViolation? { + return { dictionary in + guard let offset = dictionary.offset else { return nil } + return StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, byteOffset: offset), + reason: "\(numberOfSpecs) Quick Specs found in this file.") + } + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index b9e25b3a1e..553104ccb8 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 6250D32A1ED4DFEB00735129 /* MultilineParametersRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */; }; 62622F6B1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */; }; 626D02971F31CBCC0054788D /* XCTFailMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */; }; + 629C60D91F43906700B4AF92 /* QuickSingleSpecRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */; }; 62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; }; 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; }; 67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; }; @@ -377,6 +378,7 @@ 6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineParametersRule.swift; sourceTree = ""; }; 62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRule.swift; sourceTree = ""; }; 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTFailMessageRule.swift; sourceTree = ""; }; + 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSingleSpecRule.swift; sourceTree = ""; }; 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = ""; }; 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = ""; }; 62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = ""; }; @@ -996,6 +998,7 @@ B2902A0B1D66815600BFCCF7 /* PrivateUnitTestRule.swift */, 009E09271DFEE4C200B588A7 /* ProhibitedSuperRule.swift */, D47F31141EC918B600E3E1CA /* ProtocolPropertyAccessorsOrderRule.swift */, + 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */, D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */, 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */, D4B022951E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift */, @@ -1435,6 +1438,7 @@ D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */, D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */, D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */, + 629C60D91F43906700B4AF92 /* QuickSingleSpecRule.swift in Sources */, 621061BF1ED57E640082D51E /* MultilineParametersRuleExamples.swift in Sources */, D48AE2CC1DFB58C5001C6A4A /* AttributesRulesExamples.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 40c9e004fe..995489b3bf 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -388,6 +388,7 @@ extension RulesTests { ("testPrivateUnitTest", testPrivateUnitTest), ("testProhibitedSuper", testProhibitedSuper), ("testProtocolPropertyAccessorsOrder", testProtocolPropertyAccessorsOrder), + ("testQuickSingleSpec", testQuickSingleSpec), ("testRedundantDiscardableLet", testRedundantDiscardableLet), ("testRedundantNilCoalescing", testRedundantNilCoalescing), ("testRedundantOptionalInitialization", testRedundantOptionalInitialization), diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index 3c0713b3d4..ca80694a78 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -237,6 +237,10 @@ class RulesTests: XCTestCase { verifyRule(ProtocolPropertyAccessorsOrderRule.description) } + func testQuickSingleSpec() { + verifyRule(QuickSingleSpecRule.description) + } + func testRedundantDiscardableLet() { verifyRule(RedundantDiscardableLetRule.description) } From df6813f76a5f3a87d15303102e7469e2b987f7ae Mon Sep 17 00:00:00 2001 From: Ornithologist Coder Date: Wed, 16 Aug 2017 14:33:03 +0200 Subject: [PATCH 2/5] Add XCTestCase --- CHANGELOG.md | 4 +- Rules.md | 114 +++++++++++------- .../Models/MasterRuleList.swift | 2 +- ...ecRule.swift => SingleTestClassRule.swift} | 26 ++-- SwiftLint.xcodeproj/project.pbxproj | 8 +- Tests/LinuxMain.swift | 2 +- .../SwiftLintFrameworkTests/RulesTests.swift | 4 +- 7 files changed, 97 insertions(+), 63 deletions(-) rename Source/SwiftLintFramework/Rules/{QuickSingleSpecRule.swift => SingleTestClassRule.swift} (63%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c61fe96e..abb1c2af86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,8 +31,8 @@ [Ryan Booker](https://github.com/ryanbooker) [#1761](https://github.com/realm/SwiftLint/issues/1761) -* Add `quick_single_spec` opt-in rule to enforce test files - to contain a single QuickSpec classes. +* Add `single_test_class` opt-in rule to enforce test files + to contain a single QuickSpec or XCTestCase class. [Ornithologist Coder](https://github.com/ornithocoder) [#1779](https://github.com/realm/SwiftLint/issues/1779) diff --git a/Rules.md b/Rules.md index 2c981a82e5..21cd0d3e31 100644 --- a/Rules.md +++ b/Rules.md @@ -67,7 +67,6 @@ * [Private Unit Test](#private-unit-test) * [Prohibited calls to super](#prohibited-calls-to-super) * [Protocol Property Accessors Order](#protocol-property-accessors-order) -* [Quick Single Spec](#quick-single-spec) * [Redundant Discardable Let](#redundant-discardable-let) * [Redundant Nil Coalescing](#redundant-nil-coalescing) * [Redundant Optional Initialization](#redundant-optional-initialization) @@ -75,6 +74,7 @@ * [Redundant Void Return](#redundant-void-return) * [Returning Whitespace](#returning-whitespace) * [Shorthand Operator](#shorthand-operator) +* [Single Test Class](#single-test-class) * [Sorted Imports](#sorted-imports) * [Statement Position](#statement-position) * [Strict fileprivate](#strict-fileprivate) @@ -7907,50 +7907,6 @@ protocol Foo { -## Quick Single Spec - -Identifier | Enabled by default | Supports autocorrection | Kind ---- | --- | --- | --- -`quick_single_spec` | Disabled | No | style - -Test files should contain a single QuickSpec class. - -### Examples - -
-Non Triggering Examples - -```swift -class FooTests { } - -``` - -```swift -class FooTests: QuickSpec { } - -``` - -
-
-Triggering Examples - -```swift -↓class FooTests: QuickSpec { } -↓class BarTests: QuickSpec { } - -``` - -```swift -↓class FooTests: QuickSpec { } -↓class BarTests: QuickSpec { } -↓class TotoTests: QuickSpec { } - -``` - -
- - - ## Redundant Discardable Let Identifier | Enabled by default | Supports autocorrection | Kind @@ -8683,6 +8639,74 @@ n = n - i / outputLength +## Single Test Class + +Identifier | Enabled by default | Supports autocorrection | Kind +--- | --- | --- | --- +`single_test_class` | Disabled | No | style + +Test files should contain a single QuickSpec or XCTestCase class. + +### Examples + +
+Non Triggering Examples + +```swift +class FooTests { } + +``` + +```swift +class FooTests: QuickSpec { } + +``` + +```swift +class FooTests: XCTestCase { } + +``` + +
+
+Triggering Examples + +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: QuickSpec { } + +``` + +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: QuickSpec { } +↓class TotoTests: QuickSpec { } + +``` + +```swift +↓class FooTests: XCTestCase { } +↓class BarTests: XCTestCase { } + +``` + +```swift +↓class FooTests: XCTestCase { } +↓class BarTests: XCTestCase { } +↓class TotoTests: XCTestCase { } + +``` + +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: XCTestCase { } + +``` + +
+ + + ## Sorted Imports Identifier | Enabled by default | Supports autocorrection | Kind diff --git a/Source/SwiftLintFramework/Models/MasterRuleList.swift b/Source/SwiftLintFramework/Models/MasterRuleList.swift index 0b18c05f51..765621d323 100644 --- a/Source/SwiftLintFramework/Models/MasterRuleList.swift +++ b/Source/SwiftLintFramework/Models/MasterRuleList.swift @@ -75,7 +75,7 @@ public let masterRuleList = RuleList(rules: [ PrivateUnitTestRule.self, ProhibitedSuperRule.self, ProtocolPropertyAccessorsOrderRule.self, - QuickSingleSpecRule.self, + SingleTestClassRule.self, RedundantDiscardableLetRule.self, RedundantNilCoalescingRule.self, RedundantOptionalInitializationRule.self, diff --git a/Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift similarity index 63% rename from Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift rename to Source/SwiftLintFramework/Rules/SingleTestClassRule.swift index bbdf22bc42..980ccd6e99 100644 --- a/Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift +++ b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift @@ -9,23 +9,27 @@ import Foundation import SourceKittenFramework -public struct QuickSingleSpecRule: Rule, OptInRule, ConfigurationProviderRule { +public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule { public var configuration = SeverityConfiguration(.warning) public init() {} public static let description = RuleDescription( - identifier: "quick_single_spec", - name: "Quick Single Spec", - description: "Test files should contain a single QuickSpec class.", + identifier: "single_test_class", + name: "Single Test Class", + description: "Test files should contain a single QuickSpec or XCTestCase class.", kind: .style, nonTriggeringExamples: [ "class FooTests { }\n", - "class FooTests: QuickSpec { }\n" + "class FooTests: QuickSpec { }\n", + "class FooTests: XCTestCase { }\n" ], triggeringExamples: [ "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n", - "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n↓class TotoTests: QuickSpec { }\n" + "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n↓class TotoTests: QuickSpec { }\n", + "↓class FooTests: XCTestCase { }\n↓class BarTests: XCTestCase { }\n", + "↓class FooTests: XCTestCase { }\n↓class BarTests: XCTestCase { }\n↓class TotoTests: XCTestCase { }\n", + "↓class FooTests: QuickSpec { }\n↓class BarTests: XCTestCase { }\n" ] ) @@ -40,7 +44,9 @@ public struct QuickSingleSpecRule: Rule, OptInRule, ConfigurationProviderRule { // MARK: - Private private func quickSpecs(in file: File) -> [[String: SourceKitRepresentable]] { - return file.structure.dictionary.substructure.filter { $0.inheritedTypes.contains("QuickSpec") } + return file.structure.dictionary.substructure.filter { + !$0.inheritedTypes.filter { testClasses().contains($0) }.isEmpty + } } private func toViolation(in file: File, @@ -51,7 +57,11 @@ public struct QuickSingleSpecRule: Rule, OptInRule, ConfigurationProviderRule { return StyleViolation(ruleDescription: type(of: self).description, severity: configuration.severity, location: Location(file: file, byteOffset: offset), - reason: "\(numberOfSpecs) Quick Specs found in this file.") + reason: "\(numberOfSpecs) test classes found in this file.") } } + + private func testClasses() -> [String] { + return ["QuickSpec", "XCTestCase"] + } } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 553104ccb8..dc8d7a499f 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -69,7 +69,7 @@ 6250D32A1ED4DFEB00735129 /* MultilineParametersRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */; }; 62622F6B1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */; }; 626D02971F31CBCC0054788D /* XCTFailMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */; }; - 629C60D91F43906700B4AF92 /* QuickSingleSpecRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */; }; + 629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */; }; 62A498561F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */; }; 62A6E7931F3317E3003A0479 /* JoinedDefaultRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */; }; 67932E2D1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67932E2C1E54AF4B00CB0629 /* CyclomaticComplexityConfigurationTests.swift */; }; @@ -378,7 +378,7 @@ 6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineParametersRule.swift; sourceTree = ""; }; 62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRule.swift; sourceTree = ""; }; 626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTFailMessageRule.swift; sourceTree = ""; }; - 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSingleSpecRule.swift; sourceTree = ""; }; + 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTestClassRule.swift; sourceTree = ""; }; 62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = ""; }; 62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = ""; }; 62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = ""; }; @@ -998,7 +998,7 @@ B2902A0B1D66815600BFCCF7 /* PrivateUnitTestRule.swift */, 009E09271DFEE4C200B588A7 /* ProhibitedSuperRule.swift */, D47F31141EC918B600E3E1CA /* ProtocolPropertyAccessorsOrderRule.swift */, - 629C60D81F43906700B4AF92 /* QuickSingleSpecRule.swift */, + 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */, D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */, 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */, D4B022951E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift */, @@ -1438,7 +1438,7 @@ D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */, D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */, D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */, - 629C60D91F43906700B4AF92 /* QuickSingleSpecRule.swift in Sources */, + 629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */, 621061BF1ED57E640082D51E /* MultilineParametersRuleExamples.swift in Sources */, D48AE2CC1DFB58C5001C6A4A /* AttributesRulesExamples.swift in Sources */, E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 995489b3bf..44a7b6c1b5 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -388,7 +388,7 @@ extension RulesTests { ("testPrivateUnitTest", testPrivateUnitTest), ("testProhibitedSuper", testProhibitedSuper), ("testProtocolPropertyAccessorsOrder", testProtocolPropertyAccessorsOrder), - ("testQuickSingleSpec", testQuickSingleSpec), + ("testSingleTestClass", testSingleTestClass), ("testRedundantDiscardableLet", testRedundantDiscardableLet), ("testRedundantNilCoalescing", testRedundantNilCoalescing), ("testRedundantOptionalInitialization", testRedundantOptionalInitialization), diff --git a/Tests/SwiftLintFrameworkTests/RulesTests.swift b/Tests/SwiftLintFrameworkTests/RulesTests.swift index ca80694a78..025137307c 100644 --- a/Tests/SwiftLintFrameworkTests/RulesTests.swift +++ b/Tests/SwiftLintFrameworkTests/RulesTests.swift @@ -237,8 +237,8 @@ class RulesTests: XCTestCase { verifyRule(ProtocolPropertyAccessorsOrderRule.description) } - func testQuickSingleSpec() { - verifyRule(QuickSingleSpecRule.description) + func testSingleTestClass() { + verifyRule(SingleTestClassRule.description) } func testRedundantDiscardableLet() { From 641c4c2a8826788080ae02591b5b4468e3187f86 Mon Sep 17 00:00:00 2001 From: Ornithologist Coder Date: Wed, 16 Aug 2017 14:56:27 +0200 Subject: [PATCH 3/5] Add another triggering example --- Rules.md | 7 +++++++ Source/SwiftLintFramework/Rules/SingleTestClassRule.swift | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Rules.md b/Rules.md index 21cd0d3e31..f1bd088357 100644 --- a/Rules.md +++ b/Rules.md @@ -8703,6 +8703,13 @@ class FooTests: XCTestCase { } ``` +```swift +↓class FooTests: QuickSpec { } +↓class BarTests: XCTestCase { } +class TotoTests { } + +``` + diff --git a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift index 980ccd6e99..5b10fb7442 100644 --- a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift +++ b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift @@ -29,7 +29,8 @@ public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule { "↓class FooTests: QuickSpec { }\n↓class BarTests: QuickSpec { }\n↓class TotoTests: QuickSpec { }\n", "↓class FooTests: XCTestCase { }\n↓class BarTests: XCTestCase { }\n", "↓class FooTests: XCTestCase { }\n↓class BarTests: XCTestCase { }\n↓class TotoTests: XCTestCase { }\n", - "↓class FooTests: QuickSpec { }\n↓class BarTests: XCTestCase { }\n" + "↓class FooTests: QuickSpec { }\n↓class BarTests: XCTestCase { }\n", + "↓class FooTests: QuickSpec { }\n↓class BarTests: XCTestCase { }\nclass TotoTests { }\n" ] ) From d3a063e4e4838405986583509f3d4a9125237d42 Mon Sep 17 00:00:00 2001 From: Ornithologist Coder Date: Wed, 16 Aug 2017 15:46:39 +0200 Subject: [PATCH 4/5] Rename method, update header and sort files and tests --- .../SwiftLintFramework/Rules/SingleTestClassRule.swift | 10 +++++----- SwiftLint.xcodeproj/project.pbxproj | 2 +- Tests/LinuxMain.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift index 5b10fb7442..82f2efcdc8 100644 --- a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift +++ b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift @@ -1,5 +1,5 @@ // -// QuickSpecLimitRule.swift +// SingleTestClassRule.swift // SwiftLint // // Created by Ornithologist Coder on 8/15/17. @@ -35,16 +35,16 @@ public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule { ) public func validate(file: File) -> [StyleViolation] { - let specs = quickSpecs(in: file) + let classes = testClasses(in: file) - guard specs.count > 1 else { return [] } + guard classes.count > 1 else { return [] } - return specs.flatMap(toViolation(in: file, configuration: configuration, numberOfSpecs: specs.count)) + return classes.flatMap(toViolation(in: file, configuration: configuration, numberOfSpecs: classes.count)) } // MARK: - Private - private func quickSpecs(in file: File) -> [[String: SourceKitRepresentable]] { + private func testClasses(in file: File) -> [[String: SourceKitRepresentable]] { return file.structure.dictionary.substructure.filter { !$0.inheritedTypes.filter { testClasses().contains($0) }.isEmpty } diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index dc8d7a499f..376b452f86 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -998,7 +998,6 @@ B2902A0B1D66815600BFCCF7 /* PrivateUnitTestRule.swift */, 009E09271DFEE4C200B588A7 /* ProhibitedSuperRule.swift */, D47F31141EC918B600E3E1CA /* ProtocolPropertyAccessorsOrderRule.swift */, - 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */, D4C889701E385B7B00BAE88D /* RedundantDiscardableLetRule.swift */, 24B4DF0B1D6DFA370097803B /* RedundantNilCoalescingRule.swift */, D4B022951E0EF80C007E5297 /* RedundantOptionalInitializationRule.swift */, @@ -1006,6 +1005,7 @@ D4B022B11E10B613007E5297 /* RedundantVoidReturnRule.swift */, E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */, D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */, + 629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */, D286EC001E02DA190003CF72 /* SortedImportsRule.swift */, 692B60AB1BD8F2E700C7AA22 /* StatementPositionRule.swift */, D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 44a7b6c1b5..fa102f94d5 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -388,7 +388,6 @@ extension RulesTests { ("testPrivateUnitTest", testPrivateUnitTest), ("testProhibitedSuper", testProhibitedSuper), ("testProtocolPropertyAccessorsOrder", testProtocolPropertyAccessorsOrder), - ("testSingleTestClass", testSingleTestClass), ("testRedundantDiscardableLet", testRedundantDiscardableLet), ("testRedundantNilCoalescing", testRedundantNilCoalescing), ("testRedundantOptionalInitialization", testRedundantOptionalInitialization), @@ -396,6 +395,7 @@ extension RulesTests { ("testRedundantVoidReturn", testRedundantVoidReturn), ("testReturnArrowWhitespace", testReturnArrowWhitespace), ("testShorthandOperator", testShorthandOperator), + ("testSingleTestClass", testSingleTestClass), ("testSortedImports", testSortedImports), ("testStatementPosition", testStatementPosition), ("testStatementPositionUncuddled", testStatementPositionUncuddled), From 0a330f86b1cce5f2ed335cbdbf4f2cfd6dfc15d8 Mon Sep 17 00:00:00 2001 From: Ornithologist Coder Date: Wed, 16 Aug 2017 18:20:16 +0200 Subject: [PATCH 5/5] Apply code review changes --- .../Rules/SingleTestClassRule.swift | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift index 82f2efcdc8..aead75deec 100644 --- a/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift +++ b/Source/SwiftLintFramework/Rules/SingleTestClassRule.swift @@ -12,8 +12,6 @@ import SourceKittenFramework public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule { public var configuration = SeverityConfiguration(.warning) - public init() {} - public static let description = RuleDescription( identifier: "single_test_class", name: "Single Test Class", @@ -34,35 +32,33 @@ public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule { ] ) + private let testClasses = ["QuickSpec", "XCTestCase"] + + public init() {} + public func validate(file: File) -> [StyleViolation] { let classes = testClasses(in: file) guard classes.count > 1 else { return [] } - return classes.flatMap(toViolation(in: file, configuration: configuration, numberOfSpecs: classes.count)) - } - - // MARK: - Private - - private func testClasses(in file: File) -> [[String: SourceKitRepresentable]] { - return file.structure.dictionary.substructure.filter { - !$0.inheritedTypes.filter { testClasses().contains($0) }.isEmpty - } - } - - private func toViolation(in file: File, - configuration: SeverityConfiguration, - numberOfSpecs: Int) -> ([String: SourceKitRepresentable]) -> StyleViolation? { - return { dictionary in + return classes.flatMap { dictionary in guard let offset = dictionary.offset else { return nil } + return StyleViolation(ruleDescription: type(of: self).description, severity: configuration.severity, location: Location(file: file, byteOffset: offset), - reason: "\(numberOfSpecs) test classes found in this file.") + reason: "\(classes.count) test classes found in this file.") } } - private func testClasses() -> [String] { - return ["QuickSpec", "XCTestCase"] + private func testClasses(in file: File) -> [[String: SourceKitRepresentable]] { + return file.structure.dictionary.substructure.filter { dictionary in + guard + let kind = dictionary.kind, + SwiftDeclarationKind(rawValue: kind) == .class + else { return false } + + return !dictionary.inheritedTypes.filter { testClasses.contains($0) }.isEmpty + } } }