forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathShorthandOperatorRule.swift
More file actions
108 lines (88 loc) · 3.89 KB
/
ShorthandOperatorRule.swift
File metadata and controls
108 lines (88 loc) · 3.89 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
//
// ShorthandOperatorRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 01/06/17.
// Copyright © 2017 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct ShorthandOperatorRule: ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.error)
public init() {}
public static let description = RuleDescription(
identifier: "shorthand_operator",
name: "Shorthand Operator",
description: "Prefer shorthand operators (+=, -=, *=, /=) over doing the operation and assigning.",
nonTriggeringExamples: allOperators.flatMap { operation in
[
"foo \(operation)= 1",
"foo \(operation)= variable",
"foo \(operation)= bar.method()",
"self.foo = foo \(operation) 1",
"foo = self.foo \(operation) 1",
"page = ceilf(currentOffset \(operation) pageWidth)",
"foo = aMethod(foo \(operation) bar)",
"foo = aMethod(bar \(operation) foo)"
]
} + [
"var helloWorld = \"world!\"\n helloWorld = \"Hello, \" + helloWorld",
"angle = someCheck ? angle : -angle"
],
triggeringExamples: allOperators.flatMap { operation in
[
"↓foo = foo \(operation) 1\n",
"↓foo = foo \(operation) aVariable\n",
"↓foo = foo \(operation) bar.method()\n",
"↓foo.aProperty = foo.aProperty \(operation) 1\n",
"↓self.aProperty = self.aProperty \(operation) 1\n"
]
}
)
private static let allOperators = ["-", "/", "+", "*"]
private static let pattern: String = {
let escaped = { (operators: [String]) -> String in
return "[\(operators.map { "\\\($0)" }.joined())]"
}
let escapedOperators = escaped(allOperators)
let operand = "[\\w\\d\\.]+?"
let spaces = "[^\\S\\r\\n]*?"
let pattern = "^\(spaces)(\(operand))\(spaces)=\(spaces)(\\1)\(spaces)\(escapedOperators)"
return pattern
}()
private static let violationRegex = regex(pattern, options: [.anchorsMatchLines])
public func validate(file: File) -> [StyleViolation] {
let contents = file.contents.bridge()
let range = NSRange(location: 0, length: contents.length)
let matches = ShorthandOperatorRule.violationRegex.matches(in: file.contents, options: [], range: range)
return matches.flatMap { match -> StyleViolation? in
// byteRanges will have the ranges of captured groups
let byteRanges: [NSRange?] = (1..<match.numberOfRanges).map { rangeIdx in
let range = match.rangeAt(rangeIdx)
guard range.location != NSNotFound else {
return nil
}
return contents.NSRangeToByteRange(start: range.location, length: range.length)
}
guard let byteRange = byteRanges[0] else {
return nil
}
let kindsInCaptureGroups = byteRanges.map { range -> [SyntaxKind] in
range.flatMap {
let tokens = file.syntaxMap.tokens(inByteRange: $0)
return tokens.flatMap { SyntaxKind(rawValue: $0.type) }
} ?? []
}
guard kindsAreValid(kindsInCaptureGroups[0]) &&
kindsAreValid(kindsInCaptureGroups[1]) else {
return nil
}
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: byteRange.location))
}
}
private func kindsAreValid(_ kinds: [SyntaxKind]) -> Bool {
return Set(kinds).isSubset(of: [.identifier, .keyword])
}
}