forked from realm/SwiftLint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ValidIBInspectableRule.swift
131 lines (114 loc) · 4.83 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
130
131
//
// 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",
kind: .lint,
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: [String] = ["", "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
}
}