Skip to content

Commit

Permalink
Add quick_discouraged_call opt-in rule
Browse files Browse the repository at this point in the history
Implements realm#1781 (Method calls and object initialization inside Quick
'describe' and 'context' blocks can be harmful)
  • Loading branch information
ornithocoder committed Aug 18, 2017
1 parent 4d23af2 commit c707adb
Show file tree
Hide file tree
Showing 8 changed files with 487 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
to contain a single QuickSpec or XCTestCase class.
[Ornithologist Coder](https://github.com/ornithocoder)
[#1779](https://github.com/realm/SwiftLint/issues/1779)
* Add `quick_discouraged_call` opt-in rule to discourage calls and object
initialization inside 'describe' and 'context' block in Quick tests.
[Ornithologist Coder](https://github.com/ornithocoder)
[#1781](https://github.com/realm/SwiftLint/issues/1781)

##### Bug Fixes

Expand Down
208 changes: 208 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 Discouraged Call](#quick-discouraged-call)
* [Redundant Discardable Let](#redundant-discardable-let)
* [Redundant Nil Coalescing](#redundant-nil-coalescing)
* [Redundant Optional Initialization](#redundant-optional-initialization)
Expand Down Expand Up @@ -7907,6 +7908,213 @@ protocol Foo {



## Quick Discouraged Call

Identifier | Enabled by default | Supports autocorrection | Kind
--- | --- | --- | ---
`quick_discouraged_call` | Disabled | No | style

Discouraged call inside 'describe' and/or 'context' block.

### Examples

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

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
beforeEach {
let foo = Foo()
foo.toto()
}
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
beforeEach {
let foo = Foo()
foo.toto()
}
afterEach {
let foo = Foo()
foo.toto()
}
describe("bar") {
}
context("bar") {
}
it("bar") {
let foo = Foo()
foo.toto()
}
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
itBehavesLike("bar")
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
it("does something") {
let foo = Foo()
foo.toto()
}
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
context("foo") {
afterEach { toto.append(foo) }
}
}
}

```

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

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
let foo = Foo()
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
context("foo") {
let foo = Foo()
}
context("bar") {
let foo = Foo()
foo.bar()
it("does something") {
let foo = Foo()
foo.toto()
}
}
}
}
}

```

```swift
class TotoTests: QuickSpec {
override func spec() {
describe("foo") {
context("foo") {
context("foo") {
beforeEach {
let foo = Foo()
foo.toto()
}
it("bar") {
}
context("foo") {
let foo = Foo()
}
}
}
}
}
}

```

```swift
class TotoTests: QuickSpec
override func spec()
context("foo") {
let foo = Foo()
}
}
}

```

```swift
class TotoTests: QuickSpec
override func spec()
sharedExamples("foo") {
let foo = Foo()
}
}
}

```

```swift
class TotoTests: QuickSpec
override func spec()
describe("foo") {
foo()
}
}
}

```

```swift
class TotoTests: QuickSpec
override func spec()
context("foo") {
foo()
}
}
}

```

```swift
class TotoTests: QuickSpec
override func spec()
sharedExamples("foo") {
foo()
}
}
}

```

</details>



## Redundant Discardable Let

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,
QuickDiscouragedCallRule.self,
RedundantDiscardableLetRule.self,
RedundantNilCoalescingRule.self,
RedundantOptionalInitializationRule.self,
Expand Down
107 changes: 107 additions & 0 deletions Source/SwiftLintFramework/Rules/QuickDiscouragedCallRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// QuickInitializationRule.swift
// SwiftLint
//
// Created by Ornithologist Coder on 8/11/17.
// Copyright © 2017 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

// swiftlint:disable identifier_name

public struct QuickDiscouragedCallRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "quick_discouraged_call",
name: "Quick Discouraged Call",
description: "Discouraged call inside 'describe' and/or 'context' block.",
kind: .style,
nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
)

public func validate(file: File,
kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard
fileContainsQuickSpec(file: file),
kind == .call,
let name = dictionary.name,
let kindName = QuickCallKind(rawValue: name),
QuickCallKind.restrictiveKinds().contains(kindName)
else { return [] }

return violationOffsets(in: dictionary.enclosedArguments)
.map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0))
}
}

// MARK: - Private

private func fileContainsQuickSpec(file: File) -> Bool {
return !file.structure.dictionary.substructure.filter { $0.inheritedTypes.contains("QuickSpec") }.isEmpty
}

typealias ViolationOffset = Int

private func violationOffsets(in substructure: [[String: SourceKitRepresentable]]) -> [ViolationOffset] {
return substructure.flatMap { dictionary -> [ViolationOffset] in
return dictionary.substructure.flatMap(toViolationOffsets)
}
}

private func toViolationOffsets(dictionary: [String: SourceKitRepresentable]) -> [ViolationOffset] {
guard
let kind = dictionary.kind,
let offset = dictionary.offset
else { return [] }

if SwiftExpressionKind(rawValue: kind) == .call,
let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
return [offset]
}

guard SwiftExpressionKind(rawValue: kind) != .call else { return [] }

return dictionary.substructure.flatMap(toViolationOffset)
}

private func toViolationOffset(dictionary: [String: SourceKitRepresentable]) -> ViolationOffset? {
guard
let name = dictionary.name,
let offset = dictionary.offset,
let kind = dictionary.kind,
SwiftExpressionKind(rawValue: kind) == .call,
QuickCallKind(rawValue: name) == nil
else { return nil }

return offset
}
}

// MARK: - Private

private enum QuickCallKind: String {
case describe
case context
case sharedExamples
case itBehavesLike
case beforeEach
case beforeSuite
case afterEach
case afterSuite
case it
case pending

static func restrictiveKinds() -> [QuickCallKind] {
return [.describe, .context, .sharedExamples]
}
}
Loading

0 comments on commit c707adb

Please sign in to comment.