forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ValidIBInspectableRule.swift
129 lines (113 loc) · 4.8 KB
/
ValidIBInspectableRule.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
//
// ValidIBInspectableRule.swift
// SwiftLint
//
// Created by Marcelo Fabri on 10/20/16.
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
import SourceKittenFramework
public struct ValidIBInspectableRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
private static let supportedTypes = ValidIBInspectableRule.createSupportedTypes()
public init() {}
public static let description = RuleDescription(
identifier: "valid_ibinspectable",
name: "Valid IBInspectable",
description: "@IBInspectable should be applied to variables only, have its type explicit " +
"and be of a supported type",
nonTriggeringExamples: [
"class Foo {\n @IBInspectable private var x: Int\n}\n",
"class Foo {\n @IBInspectable private var x: String?\n}\n",
"class Foo {\n @IBInspectable private var x: String!\n}\n",
"class Foo {\n @IBInspectable private var count: Int = 0\n}\n",
"class Foo {\n private var notInspectable = 0\n}\n",
"class Foo {\n private let notInspectable: Int\n}\n",
"class Foo {\n private let notInspectable: UInt8\n}\n"
],
triggeringExamples: [
"class Foo {\n @IBInspectable private ↓let count: Int\n}\n",
"class Foo {\n @IBInspectable private ↓var insets: UIEdgeInsets\n}\n",
"class Foo {\n @IBInspectable private ↓var count = 0\n}\n",
"class Foo {\n @IBInspectable private ↓var count: Int?\n}\n",
"class Foo {\n @IBInspectable private ↓var count: Int!\n}\n",
"class Foo {\n @IBInspectable private ↓var x: ImplicitlyUnwrappedOptional<Int>\n}\n",
"class Foo {\n @IBInspectable private ↓var count: Optional<Int>\n}\n",
"class Foo {\n @IBInspectable private ↓var x: Optional<String>\n}\n",
"class Foo {\n @IBInspectable private ↓var x: ImplicitlyUnwrappedOptional<String>\n}\n"
]
)
public func validate(file: File, kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .varInstance else {
return []
}
// Check if IBInspectable
let isIBInspectable = dictionary.enclosedSwiftAttributes.contains(
"source.decl.attribute.ibinspectable")
guard isIBInspectable else {
return []
}
let shouldMakeViolation: Bool
if dictionary.setterAccessibility == nil {
// if key.setter_accessibility is nil, it's a `let` declaration
shouldMakeViolation = true
} else if let type = dictionary.typeName,
ValidIBInspectableRule.supportedTypes.contains(type) {
shouldMakeViolation = false
} else {
// Variable should have explicit type or IB won't recognize it
// Variable should be of one of the supported types
shouldMakeViolation = true
}
guard shouldMakeViolation else {
return []
}
let location: Location
if let offset = dictionary.offset {
location = Location(file: file, byteOffset: offset)
} else {
location = Location(file: file.path)
}
return [
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: location)
]
}
private static func createSupportedTypes() -> [String] {
// "You can add the IBInspectable attribute to any property in a class declaration,
// class extension, or category of type: boolean, integer or floating point number, string,
// localized string, rectangle, point, size, color, range, and nil."
//
// from http://help.apple.com/xcode/mac/8.0/#/devf60c1c514
let referenceTypes = [
"String",
"NSString",
"UIColor",
"NSColor",
"UIImage",
"NSImage"
]
let types = [
"CGFloat",
"Float",
"Double",
"Bool",
"CGPoint",
"NSPoint",
"CGSize",
"NSSize",
"CGRect",
"NSRect"
]
let intTypes = ["", "8", "16", "32", "64"].flatMap { size in
["U", ""].flatMap { (sign: String) -> String in
"\(sign)Int\(size)"
}
}
let expandToIncludeOptionals: (String) -> [String] = { [$0, $0 + "!", $0 + "?"] }
// It seems that only reference types can be used as ImplicitlyUnwrappedOptional or Optional
return referenceTypes.flatMap(expandToIncludeOptionals) + types + intTypes
}
}