forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ImplicitGetterRule.swift
133 lines (115 loc) · 5.18 KB
/
ImplicitGetterRule.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
//
// ImplicitGetterRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 29/10/16.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
private func classScoped(_ value: String) -> String {
return "class Foo {\n \(value)\n}\n"
}
public struct ImplicitGetterRule: ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "implicit_getter",
name: "Implicit Getter",
description: "Computed read-only properties should avoid using the get keyword.",
kind: .style,
nonTriggeringExamples: [
classScoped("var foo: Int {\n get {\n return 3\n}\n set {\n _abc = newValue \n}\n}"),
classScoped("var foo: Int {\n return 20 \n} \n}"),
classScoped("static var foo: Int {\n return 20 \n} \n}"),
classScoped("static foo: Int {\n get {\n return 3\n}\n set {\n _abc = newValue \n}\n}"),
classScoped("var foo: Int"),
classScoped("var foo: Int {\n return getValueFromDisk() \n} \n}"),
classScoped("var foo: String {\n return \"get\" \n} \n}"),
"protocol Foo {\n var foo: Int { get }\n",
"protocol Foo {\n var foo: Int { get set }\n",
"class Foo {\n" +
" var foo: Int {\n" +
" struct Bar {\n" +
" var bar: Int {\n" +
" get { return 1 }\n" +
" set { _ = newValue }\n" +
" }\n" +
" }\n" +
" return Bar().bar\n" +
" }\n" +
"}\n",
"var _objCTaggedPointerBits: UInt {\n" +
" @inline(__always) get { return 0 }\n" +
"}\n",
"var next: Int? {\n" +
" mutating get {\n" +
" defer { self.count += 1 }\n" +
" return self.count\n" +
" }\n" +
"}\n"
],
triggeringExamples: [
classScoped("var foo: Int {\n ↓get {\n return 20 \n} \n} \n}"),
classScoped("var foo: Int {\n ↓get{\n return 20 \n} \n} \n}"),
classScoped("static var foo: Int {\n ↓get {\n return 20 \n} \n} \n}"),
"var foo: Int {\n ↓get {\n return 20 \n} \n} \n}",
classScoped("@objc func bar() { }\nvar foo: Int {\n ↓get {\n return 20 \n} \n} \n}")
]
)
public func validate(file: File) -> [StyleViolation] {
let pattern = "\\{[^\\{]*?\\s+get\\b"
let attributesKinds: Set<SyntaxKind> = [.attributeBuiltin, .attributeID]
let getTokens: [SyntaxToken] = file.rangesAndTokens(matching: pattern).flatMap { _, tokens in
let kinds = tokens.flatMap { SyntaxKind(rawValue: $0.type) }
guard let token = tokens.last,
SyntaxKind(rawValue: token.type) == .keyword,
attributesKinds.intersection(kinds).isEmpty else {
return nil
}
return token
}
let violatingTokens = getTokens.filter { token -> Bool in
// the last element is the deepest structure
guard let dict = variableDeclarations(forByteOffset: token.offset, structure: file.structure).last else {
return false
}
// If there's a setter, `get` is allowed
return dict.setterAccessibility == nil
}
return violatingTokens.map { token in
// Violation found!
let location = Location(file: file, byteOffset: token.offset)
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: location)
}
}
private func variableDeclarations(forByteOffset byteOffset: Int,
structure: Structure) -> [[String: SourceKitRepresentable]] {
var results = [[String: SourceKitRepresentable]]()
let allowedKinds = Set(SwiftDeclarationKind.variableKinds()).subtracting([.varParameter])
func parse(dictionary: [String: SourceKitRepresentable], parentKind: SwiftDeclarationKind?) {
// Only accepts variable declarations which contains a body and contains the
// searched byteOffset
guard let kindString = dictionary.kind,
let kind = SwiftDeclarationKind(rawValue: kindString),
let bodyOffset = dictionary.bodyOffset,
let bodyLength = dictionary.bodyLength,
case let byteRange = NSRange(location: bodyOffset, length: bodyLength),
NSLocationInRange(byteOffset, byteRange) else {
return
}
if parentKind != .protocol && allowedKinds.contains(kind) {
results.append(dictionary)
}
for dictionary in dictionary.substructure {
parse(dictionary: dictionary, parentKind: kind)
}
}
for dictionary in structure.dictionary.substructure {
parse(dictionary: dictionary, parentKind: nil)
}
return results
}
}