forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ObjectLiteralRule.swift
113 lines (97 loc) · 4.43 KB
/
ObjectLiteralRule.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
//
// ObjectLiteralRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 12/25/16.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct ObjectLiteralRule: ASTRule, ConfigurationProviderRule, OptInRule {
public var configuration = ObjectLiteralConfiguration()
public init() {}
public static let description = RuleDescription(
identifier: "object_literal",
name: "Object Literal",
description: "Prefer object literals over image and color inits.",
kind: .idiomatic,
nonTriggeringExamples: [
"let image = #imageLiteral(resourceName: \"image.jpg\")",
"let color = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)",
"let image = UIImage(named: aVariable)",
"let image = UIImage(named: \"interpolated \\(variable)\")",
"let color = UIColor(red: value, green: value, blue: value, alpha: 1)",
"let image = NSImage(named: aVariable)",
"let image = NSImage(named: \"interpolated \\(variable)\")",
"let color = NSColor(red: value, green: value, blue: value, alpha: 1)"
],
triggeringExamples: ["", ".init"].flatMap { (method: String) -> [String] in
["UI", "NS"].flatMap { (prefix: String) -> [String] in
[
"let image = ↓\(prefix)Image\(method)(named: \"foo\")",
"let color = ↓\(prefix)Color\(method)(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)",
"let color = ↓\(prefix)Color\(method)(red: 100 / 255.0, green: 50 / 255.0, blue: 0, alpha: 1)",
"let color = ↓\(prefix)Color\(method)(white: 0.5, alpha: 1)"
]
}
}
)
public func validate(file: File, kind: SwiftExpressionKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .call,
let offset = dictionary.offset,
(configuration.imageLiteral && isImageNamedInit(dictionary: dictionary, file: file)) ||
(configuration.colorLiteral && isColorInit(dictionary: dictionary, file: file)) else {
return []
}
return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severityConfiguration.severity,
location: Location(file: file, byteOffset: offset))
]
}
private func isImageNamedInit(dictionary: [String: SourceKitRepresentable], file: File) -> Bool {
guard let name = dictionary.name,
inits(forClasses: ["UIImage", "NSImage"]).contains(name),
case let arguments = dictionary.enclosedArguments,
arguments.flatMap({ $0.name }) == ["named"],
let argument = arguments.first,
case let kinds = kinds(forArgument: argument, file: file),
kinds == [.string] else {
return false
}
return true
}
private func isColorInit(dictionary: [String: SourceKitRepresentable], file: File) -> Bool {
guard let name = dictionary.name,
inits(forClasses: ["UIColor", "NSColor"]).contains(name),
case let arguments = dictionary.enclosedArguments,
case let argumentsNames = arguments.flatMap({ $0.name }),
argumentsNames == ["red", "green", "blue", "alpha"] || argumentsNames == ["white", "alpha"],
validateColorKinds(arguments: arguments, file: file) else {
return false
}
return true
}
private func inits(forClasses names: [String]) -> [String] {
return names.flatMap { name in
[
name,
name + ".init"
]
}
}
private func validateColorKinds(arguments: [[String: SourceKitRepresentable]], file: File) -> Bool {
for dictionary in arguments where kinds(forArgument: dictionary, file: file) != [.number] {
return false
}
return true
}
private func kinds(forArgument argument: [String: SourceKitRepresentable], file: File) -> Set<SyntaxKind> {
guard let offset = argument.bodyOffset, let length = argument.bodyLength else {
return []
}
let range = NSRange(location: offset, length: length)
return Set(file.syntaxMap.kinds(inByteRange: range))
}
}