forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuickDiscouragedCallRule.swift
More file actions
135 lines (114 loc) · 4.6 KB
/
QuickDiscouragedCallRule.swift
File metadata and controls
135 lines (114 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//
// QuickDiscouragedCallRule.swift
// SwiftLint
//
// Created by Ornithologist Coder on 8/11/17.
// Copyright © 2017 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct QuickDiscouragedCallRule: 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: .lint,
nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
)
public func validate(file: File) -> [StyleViolation] {
let testClasses = file.structure.dictionary.substructure.filter {
return $0.inheritedTypes.contains("QuickSpec") &&
$0.kind.flatMap(SwiftDeclarationKind.init) == .class
}
let specDeclarations = testClasses.flatMap { classDict in
return classDict.substructure.filter {
return $0.name == "spec()" && $0.enclosedVarParameters.isEmpty &&
$0.kind.flatMap(SwiftDeclarationKind.init) == .functionMethodInstance &&
$0.enclosedSwiftAttributes.contains("source.decl.attribute.override")
}
}
return specDeclarations.flatMap {
validate(file: file, dictionary: $0)
}
}
private func validate(file: File, dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
return dictionary.substructure.flatMap { subDict -> [StyleViolation] in
var violations = validate(file: file, dictionary: subDict)
if let kindString = subDict.kind,
let kind = SwiftExpressionKind(rawValue: kindString) {
violations += validate(file: file, kind: kind, dictionary: subDict)
}
return violations
}
}
private func validate(file: File,
kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
// is it a call to a restricted method?
guard
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),
reason: "Discouraged call inside a '\(name)' block.")
}
}
private func violationOffsets(in substructure: [[String: SourceKitRepresentable]]) -> [Int] {
return substructure.flatMap { dictionary -> [Int] in
return dictionary.substructure.flatMap(toViolationOffsets)
}
}
private func toViolationOffsets(dictionary: [String: SourceKitRepresentable]) -> [Int] {
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]) -> Int? {
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
}
}
private enum QuickCallKind: String {
case describe
case context
case sharedExamples
case itBehavesLike
case beforeEach
case beforeSuite
case afterEach
case afterSuite
case it // swiftlint:disable:this identifier_name
case pending
case xdescribe
case xcontext
case xit
case xitBehavesLike
case fdescribe
case fcontext
case fit
case fitBehavesLike
static let restrictiveKinds: Set<QuickCallKind> = [
.describe, .fdescribe, .xdescribe, .context, .fcontext, .xcontext, .sharedExamples
]
}