forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCommand.swift
172 lines (159 loc) · 7.06 KB
/
Command.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import Foundation
/// A SwiftLint-interpretable command to modify SwiftLint's behavior embedded as comments in source code.
public struct Command: Equatable {
/// The action (verb) that SwiftLint should perform when interpreting this command.
public enum Action: String {
/// The rule(s) associated with this command should be enabled by the SwiftLint engine.
case enable
/// The rule(s) associated with this command should be disabled by the SwiftLint engine.
case disable
/// The action string was invalid.
case invalid
/// - returns: The inverse action that can cancel out the current action, restoring the SwifttLint engine's
/// state prior to the current action.
package func inverse() -> Self {
switch self {
case .enable: return .disable
case .disable: return .enable
case .invalid: return .invalid
}
}
}
/// The modifier for a command, used to modify its scope.
public enum Modifier: String {
/// The command should only apply to the line preceding its definition.
case previous
/// The command should only apply to the same line as its definition.
case this
/// The command should only apply to the line following its definition.
case next
/// The modifier string was invalid.
case invalid
}
/// Text after this delimiter is not considered part of the rule.
/// The purpose of this delimiter is to allow SwiftLint
/// commands to be documented in source code.
///
/// swiftlint:disable:next force_try - Explanation here
private static let commentDelimiter = " - "
var isValid: Bool {
action != .invalid && modifier != .invalid && !ruleIdentifiers.isEmpty
}
/// The action (verb) that SwiftLint should perform when interpreting this command.
public let action: Action
/// The identifiers for the rules associated with this command.
public let ruleIdentifiers: Set<RuleIdentifier>
/// The line in the source file where this command is defined.
public let line: Int
/// The range of the command in the line (0-based).
public let range: Range<Int>?
/// This command's modifier, if any.
public let modifier: Modifier?
/// The comment following this command's `-` delimiter, if any.
internal let trailingComment: String?
/// Creates a command based on the specified parameters.
///
/// - parameter action: This command's action.
/// - parameter ruleIdentifiers: The identifiers for the rules associated with this command.
/// - parameter line: The line in the source file where this command is defined.
/// - parameter range: The range of the command in the line (0-based).
/// - parameter modifier: This command's modifier, if any.
/// - parameter trailingComment: The comment following this command's `-` delimiter, if any.
public init(action: Action,
ruleIdentifiers: Set<RuleIdentifier> = [],
line: Int = 0,
range: Range<Int>? = nil,
modifier: Modifier? = nil,
trailingComment: String? = nil) {
self.action = action
self.ruleIdentifiers = ruleIdentifiers
self.line = line
self.range = range
self.modifier = modifier
self.trailingComment = trailingComment
}
/// Creates a command based on the specified parameters.
///
/// - parameter commandString: The whole command string as found in the code.
/// - parameter line: The line in the source file where this command is defined.
/// - parameter range: The range of the command in the line (0-based).
public init(commandString: String, line: Int, range: Range<Int>) {
let scanner = Scanner(string: commandString)
_ = scanner.scanString("swiftlint:")
// (enable|disable)(:previous|:this|:next)
guard let actionAndModifierString = scanner.scanUpToString(" ") else {
self.init(action: .invalid, line: line, range: range)
return
}
let actionAndModifierScanner = Scanner(string: actionAndModifierString)
guard let actionString = actionAndModifierScanner.scanUpToString(":"),
let action = Action(rawValue: actionString)
else {
self.init(action: .invalid, line: line, range: range)
return
}
let rawRuleTexts = scanner.scanUpToString(Self.commentDelimiter) ?? ""
var trailingComment: String?
if scanner.isAtEnd {
trailingComment = nil
} else {
// Store any text after the comment delimiter as the trailingComment.
// The addition to currentIndex is to move past the delimiter
trailingComment = String(
scanner
.string[scanner.currentIndex...]
.dropFirst(Self.commentDelimiter.count)
)
}
let ruleTexts = rawRuleTexts.components(separatedBy: .whitespacesAndNewlines).filter {
let component = $0.trimmingCharacters(in: .whitespaces)
return component.isNotEmpty && component != "*/"
}
let ruleIdentifiers = Set(ruleTexts.map(RuleIdentifier.init(_:)))
// Modifier
let hasModifier = actionAndModifierScanner.scanString(":") != nil
let modifier: Modifier?
if hasModifier {
let modifierString = String(actionAndModifierScanner.string[actionAndModifierScanner.currentIndex...])
modifier = Modifier(rawValue: modifierString) ?? .invalid
} else {
modifier = nil
}
self.init(
action: action,
ruleIdentifiers: ruleIdentifiers,
line: line,
range: range,
modifier: modifier,
trailingComment: trailingComment
)
}
/// Expands the current command into its fully descriptive form without any modifiers.
/// If the command doesn't have a modifier, it is returned as-is.
///
/// - returns: The expanded commands.
package func expand() -> [Self] {
guard let modifier else {
return [self]
}
switch modifier {
case .previous:
return [
Self(action: action, ruleIdentifiers: ruleIdentifiers, line: line - 1),
Self(action: action.inverse(), ruleIdentifiers: ruleIdentifiers, line: line - 1, range: 0..<Int.max),
]
case .this:
return [
Self(action: action, ruleIdentifiers: ruleIdentifiers, line: line),
Self(action: action.inverse(), ruleIdentifiers: ruleIdentifiers, line: line, range: 0..<Int.max),
]
case .next:
return [
Self(action: action, ruleIdentifiers: ruleIdentifiers, line: line + 1),
Self(action: action.inverse(), ruleIdentifiers: ruleIdentifiers, line: line + 1, range: 0..<Int.max),
]
case .invalid:
return []
}
}
}