Skip to content

Commit

Permalink
Add quick_single_spec opt-in rule
Browse files Browse the repository at this point in the history
Implements realm#1779 (Limit number of QuickSpecs in file to one).
  • Loading branch information
ornithocoder committed Aug 16, 2017
1 parent 1a50451 commit 8d478f7
Show file tree
Hide file tree
Showing 7 changed files with 137 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 `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
Expand Down
65 changes: 65 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

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

```swift
class FooTests { }

```

```swift
class FooTests: QuickSpec { }

```

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

```swift
↓class FooTests: QuickSpec { }
↓class BarTests: QuickSpec { }

```

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

```

</details>



## Redundant Discardable Let

Identifier | Enabled by default | Supports autocorrection | Kind
Expand Down Expand Up @@ -8223,6 +8268,26 @@ let foo: Int -> ()

```

```swift
func foo() -> ()?

```

```swift
func foo() -> ()!

```

```swift
func foo() -> Void?

```

```swift
func foo() -> Void!

```

</details>
<details>
<summary>Triggering Examples</summary>
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,
QuickSingleSpecRule.self,
RedundantDiscardableLetRule.self,
RedundantNilCoalescingRule.self,
RedundantOptionalInitializationRule.self,
Expand Down
57 changes: 57 additions & 0 deletions Source/SwiftLintFramework/Rules/QuickSingleSpecRule.swift
Original file line number Diff line number Diff line change
@@ -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.")
}
}
}
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 /* 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 */; };
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 /* QuickSingleSpecRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickSingleSpecRule.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 @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ extension RulesTests {
("testPrivateUnitTest", testPrivateUnitTest),
("testProhibitedSuper", testProhibitedSuper),
("testProtocolPropertyAccessorsOrder", testProtocolPropertyAccessorsOrder),
("testQuickSingleSpec", testQuickSingleSpec),
("testRedundantDiscardableLet", testRedundantDiscardableLet),
("testRedundantNilCoalescing", testRedundantNilCoalescing),
("testRedundantOptionalInitialization", testRedundantOptionalInitialization),
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 testQuickSingleSpec() {
verifyRule(QuickSingleSpecRule.description)
}

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

0 comments on commit 8d478f7

Please sign in to comment.