mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
b83e0991b9
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.
123 lines
4.6 KiB
Swift
123 lines
4.6 KiB
Swift
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(.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", ""].map { (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
|
|
}
|
|
}
|