Files
SwiftLint/Source/SwiftLintFramework/Rules/ImplicitGetterRule.swift
T
JP Simard b83e0991b9 Remove all file headers
The MIT license doesn't require that all files be prepended with this
licensing or copyright information. Realm confirmed that they're ok with this
change. This will enable some companies to contribute to SwiftLint and the
date & authorship information will remain accessible via git source control.
2018-05-04 13:42:02 -07:00

255 lines
7.8 KiB
Swift

import Foundation
import SourceKittenFramework
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 and subscripts should avoid using the get keyword.",
kind: .style,
nonTriggeringExamples: ImplicitGetterRule.nonTriggeringExamples,
triggeringExamples: ImplicitGetterRule.triggeringExamples
)
private static var nonTriggeringExamples: [String] {
let commonExamples = [
"""
class Foo {
var foo: Int {
get { return 3 }
set { _abc = newValue }
}
}
""",
"""
class Foo {
var foo: Int {
return 20
}
}
""",
"""
class Foo {
static var foo: Int {
return 20
}
}
""",
"""
class Foo {
static var foo: Int {
get { return 3 }
set { _abc = newValue }
}
}
""",
"class Foo {\n var foo: Int\n}",
"""
class Foo {
var foo: Int {
return getValueFromDisk()
}
}
""",
"""
class Foo {
var foo: String {
return "get"
}
}
""",
"protocol Foo {\n var foo: Int { get }\n",
"protocol Foo {\n var foo: Int { get set }\n",
"""
class Foo {
var foo: Int {
struct Bar {
var bar: Int {
get { return 1 }
set { _ = newValue }
}
}
return Bar().bar
}
}
""",
"""
var _objCTaggedPointerBits: UInt {
@inline(__always) get { return 0 }
}
""",
"""
var next: Int? {
mutating get {
defer { self.count += 1 }
return self.count
}
}
"""
]
guard SwiftVersion.current >= SwiftVersion.fourDotOne else {
return commonExamples
}
return commonExamples + [
"""
class Foo {
subscript(i: Int) -> Int {
return 20
}
}
""",
"""
class Foo {
subscript(i: Int) -> Int {
get { return 3 }
set { _abc = newValue }
}
}
""",
"protocol Foo {\n subscript(i: Int) -> Int { get }\n}",
"protocol Foo {\n subscript(i: Int) -> Int { get set }\n}"
]
}
private static var triggeringExamples: [String] {
let commonExamples = [
"""
class Foo {
var foo: Int {
↓get {
return 20
}
}
}
""",
"""
class Foo {
var foo: Int {
↓get{ return 20 }
}
}
""",
"""
class Foo {
static var foo: Int {
↓get {
return 20
}
}
}
""",
"var foo: Int {\n ↓get { return 20 }\n}",
"""
class Foo {
@objc func bar() {}
var foo: Int {
↓get {
return 20
}
}
}
"""
]
guard SwiftVersion.current >= SwiftVersion.fourDotOne else {
return commonExamples
}
return commonExamples + [
"""
class Foo {
subscript(i: Int) -> Int {
↓get {
return 20
}
}
}
"""
]
}
public func validate(file: File) -> [StyleViolation] {
let pattern = "\\{[^\\{]*?\\s+get\\b"
let attributesKinds: Set<SyntaxKind> = [.attributeBuiltin, .attributeID]
let getTokens: [SyntaxToken] = file.rangesAndTokens(matching: pattern).compactMap { _, tokens in
let kinds = tokens.compactMap { SyntaxKind(rawValue: $0.type) }
guard let token = tokens.last,
SyntaxKind(rawValue: token.type) == .keyword,
attributesKinds.isDisjoint(with: kinds) else {
return nil
}
return token
}
let violatingLocations = getTokens.compactMap { token -> (Int, SwiftDeclarationKind?)? in
// the last element is the deepest structure
guard let dict = declarations(forByteOffset: token.offset, structure: file.structure).last else {
return nil
}
// If there's a setter, `get` is allowed
guard dict.setterAccessibility == nil else {
return nil
}
let kind = dict.kind.flatMap(SwiftDeclarationKind.init(rawValue:))
return (token.offset, kind)
}
return violatingLocations.map { offset, kind in
let reason = kind.map { kind -> String in
let kindString = kind == .functionSubscript ? "subscripts" : "properties"
return "Computed read-only \(kindString) should avoid using the get keyword."
}
return StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset),
reason: reason)
}
}
private func declarations(forByteOffset byteOffset: Int,
structure: Structure) -> [[String: SourceKitRepresentable]] {
var results = [[String: SourceKitRepresentable]]()
let allowedKinds = SwiftDeclarationKind.variableKinds.subtracting([.varParameter])
.union([.functionSubscript])
func parse(dictionary: [String: SourceKitRepresentable], parentKind: SwiftDeclarationKind?) {
// Only accepts 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
}
}