Files
SwiftLint/Source/SwiftLintFramework/Rules/ValidIBInspectableRule.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

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
}
}