130 lines
4.8 KiB
Swift
130 lines
4.8 KiB
Swift
//
|
|
// 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: [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
|
|
}
|
|
}
|