Skip to content

Commit

Permalink
Merge pull request #1780 from ornithocoder/quick_spec_limit
Browse files Browse the repository at this point in the history
Add single_test_class opt-in rule
  • Loading branch information
marcelofabri authored Aug 17, 2017
2 parents 2794785 + 0a330f8 commit 49475d3
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
[Ryan Booker](https://github.com/ryanbooker)
[#1761](https://github.com/realm/SwiftLint/issues/1761)

* 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)

##### Bug Fixes

* Fix false positive on `force_unwrapping` rule when declaring
Expand Down
76 changes: 76 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,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)
Expand Down Expand Up @@ -8638,6 +8639,81 @@ 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

<details>
<summary>Non Triggering Examples</summary>

```swift
class FooTests { }

```

```swift
class FooTests: QuickSpec { }

```

```swift
class FooTests: XCTestCase { }

```

</details>
<details>
<summary>Triggering Examples</summary>

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

```

```swift
class FooTests: QuickSpec { }
class BarTests: XCTestCase { }
class TotoTests { }

```

</details>



## Sorted Imports

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public let masterRuleList = RuleList(rules: [
PrivateUnitTestRule.self,
ProhibitedSuperRule.self,
ProtocolPropertyAccessorsOrderRule.self,
SingleTestClassRule.self,
RedundantDiscardableLetRule.self,
RedundantNilCoalescingRule.self,
RedundantOptionalInitializationRule.self,
Expand Down
64 changes: 64 additions & 0 deletions Source/SwiftLintFramework/Rules/SingleTestClassRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// SingleTestClassRule.swift
// SwiftLint
//
// Created by Ornithologist Coder on 8/15/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct SingleTestClassRule: Rule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)

public static let description = RuleDescription(
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: 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: 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 { }\nclass TotoTests { }\n"
]
)

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 { 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: "\(classes.count) test classes found in this file.")
}
}

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
}
}
}
4 changes: 4 additions & 0 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 /* 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 */; };
Expand Down Expand Up @@ -377,6 +378,7 @@
6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineParametersRule.swift; sourceTree = "<group>"; };
62622F6A1F2F2E3500D5D099 /* DiscouragedDirectInitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRule.swift; sourceTree = "<group>"; };
626D02961F31CBCC0054788D /* XCTFailMessageRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTFailMessageRule.swift; sourceTree = "<group>"; };
629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleTestClassRule.swift; sourceTree = "<group>"; };
62A498551F306A7700D766E4 /* DiscouragedDirectInitConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitConfiguration.swift; sourceTree = "<group>"; };
62A6E7911F3317E3003A0479 /* JoinedDefaultRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinedDefaultRule.swift; sourceTree = "<group>"; };
62AF35D71F30B183009B11EE /* DiscouragedDirectInitRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscouragedDirectInitRuleTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1003,6 +1005,7 @@
D4B022B11E10B613007E5297 /* RedundantVoidReturnRule.swift */,
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */,
D4D5A5FE1E1F3A1C00D15E0C /* ShorthandOperatorRule.swift */,
629C60D81F43906700B4AF92 /* SingleTestClassRule.swift */,
D286EC001E02DA190003CF72 /* SortedImportsRule.swift */,
692B60AB1BD8F2E700C7AA22 /* StatementPositionRule.swift */,
D42B45D81F0AF5E30086B683 /* StrictFilePrivateRule.swift */,
Expand Down Expand Up @@ -1435,6 +1438,7 @@
D47A510E1DB29EEB00A4CC21 /* SwitchCaseOnNewlineRule.swift in Sources */,
D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */,
D4DA1DF41E17511D0037413D /* CompilerProtocolInitRule.swift in Sources */,
629C60D91F43906700B4AF92 /* SingleTestClassRule.swift in Sources */,
621061BF1ED57E640082D51E /* MultilineParametersRuleExamples.swift in Sources */,
D48AE2CC1DFB58C5001C6A4A /* AttributesRulesExamples.swift in Sources */,
E88DEA6F1B09843F00A66CB0 /* Location.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ extension RulesTests {
("testRedundantVoidReturn", testRedundantVoidReturn),
("testReturnArrowWhitespace", testReturnArrowWhitespace),
("testShorthandOperator", testShorthandOperator),
("testSingleTestClass", testSingleTestClass),
("testSortedImports", testSortedImports),
("testStatementPosition", testStatementPosition),
("testStatementPositionUncuddled", testStatementPositionUncuddled),
Expand Down
4 changes: 4 additions & 0 deletions Tests/SwiftLintFrameworkTests/RulesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ class RulesTests: XCTestCase {
verifyRule(ProtocolPropertyAccessorsOrderRule.description)
}

func testSingleTestClass() {
verifyRule(SingleTestClassRule.description)
}

func testRedundantDiscardableLet() {
verifyRule(RedundantDiscardableLetRule.description)
}
Expand Down

0 comments on commit 49475d3

Please sign in to comment.