forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathRedundantStringEnumValueRule.swift
150 lines (132 loc) · 4.9 KB
/
RedundantStringEnumValueRule.swift
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import SourceKittenFramework
private func children(of dict: SourceKittenDictionary,
matching kind: SwiftDeclarationKind) -> [SourceKittenDictionary] {
return dict.substructure.compactMap { subDict in
if subDict.declarationKind == kind {
return subDict
}
return nil
}
}
public struct RedundantStringEnumValueRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "redundant_string_enum_value",
name: "Redundant String Enum Value",
description: "String enum values can be omitted when they are equal to the enumcase name.",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
enum Numbers: String {
case one
case two
}
"""),
Example("""
enum Numbers: Int {
case one = 1
case two = 2
}
"""),
Example("""
enum Numbers: String {
case one = "ONE"
case two = "TWO"
}
"""),
Example("""
enum Numbers: String {
case one = "ONE"
case two = "two"
}
"""),
Example("""
enum Numbers: String {
case one, two
}
""")
],
triggeringExamples: [
Example("""
enum Numbers: String {
case one = ↓"one"
case two = ↓"two"
}
"""),
Example("""
enum Numbers: String {
case one = ↓"one", two = ↓"two"
}
"""),
Example("""
enum Numbers: String {
case one, two = ↓"two"
}
""")
]
)
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard kind == .enum else {
return []
}
// Check if it's a String enum
guard dictionary.inheritedTypes.contains("String") else {
return []
}
let violations = violatingOffsetsForEnum(dictionary: dictionary, file: file)
return violations.map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0))
}
}
private func violatingOffsetsForEnum(dictionary: SourceKittenDictionary, file: SwiftLintFile) -> [ByteCount] {
var caseCount = 0
var violations = [ByteCount]()
for enumCase in children(of: dictionary, matching: .enumcase) {
caseCount += enumElementsCount(dictionary: enumCase)
violations += violatingOffsetsForEnumCase(dictionary: enumCase, file: file)
}
guard violations.count == caseCount else {
return []
}
return violations
}
private func enumElementsCount(dictionary: SourceKittenDictionary) -> Int {
return children(of: dictionary, matching: .enumelement).filter({ element in
return filterEnumInits(dictionary: element).isNotEmpty
}).count
}
private func violatingOffsetsForEnumCase(dictionary: SourceKittenDictionary, file: SwiftLintFile) -> [ByteCount] {
return children(of: dictionary, matching: .enumelement).flatMap { element -> [ByteCount] in
guard let name = element.name else {
return []
}
return violatingOffsetsForEnumElement(dictionary: element, name: name, file: file)
}
}
private func violatingOffsetsForEnumElement(dictionary: SourceKittenDictionary, name: String,
file: SwiftLintFile) -> [ByteCount] {
let enumInits = filterEnumInits(dictionary: dictionary)
return enumInits.compactMap { dictionary -> ByteCount? in
guard let offset = dictionary.offset,
let length = dictionary.length else {
return nil
}
// the string would be quoted if offset and length were used directly
let rangeWithoutQuotes = ByteRange(location: offset + 1, length: length - 2)
let enumCaseName = file.stringView.substringWithByteRange(rangeWithoutQuotes) ?? ""
guard enumCaseName == name else {
return nil
}
return offset
}
}
private func filterEnumInits(dictionary: SourceKittenDictionary) -> [SourceKittenDictionary] {
return dictionary.elements.filter {
$0.kind == "source.lang.swift.structure.elem.init_expr"
}
}
}