mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
da3e1a793b
* Fix false positives in valid_ibinspectable rule when using Swift 5.2
when defining inspectable properties in class extensions with computed
properties.
The following was triggering:
```swift
extension Foo {
@IBInspectable var color: UIColor {
set {
self.bar.textColor = newValue
}
get {
return self.bar.textColor
}
}
}
```
Fix by checking to see if an instance property has `set` keywords in its
body when running with Swift 5.2 or later.
* fixup! Fix false positives in valid_ibinspectable rule when using Swift 5.2
221 lines
6.5 KiB
Swift
221 lines
6.5 KiB
Swift
import Foundation
|
|
import SourceKittenFramework
|
|
|
|
public struct ValidIBInspectableRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
|
|
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: [
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private var x: Int
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private var x: String?
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private var x: String!
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private var count: Int = 0
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
private var notInspectable = 0
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
private let notInspectable: Int
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
private let notInspectable: UInt8
|
|
}
|
|
"""),
|
|
Example("""
|
|
extension Foo {
|
|
@IBInspectable var color: UIColor {
|
|
set {
|
|
self.bar.textColor = newValue
|
|
}
|
|
|
|
get {
|
|
return self.bar.textColor
|
|
}
|
|
}
|
|
}
|
|
""")
|
|
],
|
|
triggeringExamples: [
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓let count: Int
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var insets: UIEdgeInsets
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var count = 0
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var count: Int?
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var count: Int!
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var x: ImplicitlyUnwrappedOptional<Int>
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var count: Optional<Int>
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var x: Optional<String>
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var x: ImplicitlyUnwrappedOptional<String>
|
|
}
|
|
""")
|
|
]
|
|
)
|
|
|
|
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
|
|
dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
|
guard kind == .varInstance else {
|
|
return []
|
|
}
|
|
|
|
// Check if IBInspectable
|
|
let isIBInspectable = dictionary.enclosedSwiftAttributes.contains(.ibinspectable)
|
|
guard isIBInspectable else {
|
|
return []
|
|
}
|
|
|
|
let shouldMakeViolation: Bool
|
|
if !file.isMutableProperty(dictionary) {
|
|
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
|
|
}
|
|
}
|
|
|
|
private extension SwiftLintFile {
|
|
func isMutableProperty(_ dictionary: SourceKittenDictionary) -> Bool {
|
|
if dictionary.setterAccessibility != nil {
|
|
return true
|
|
}
|
|
|
|
if SwiftVersion.current >= .fiveDotTwo,
|
|
let range = dictionary.byteRange.map(stringView.byteRangeToNSRange) {
|
|
return hasSetToken(in: range)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private func hasSetToken(in range: NSRange?) -> Bool {
|
|
return rangesAndTokens(matching: "\\bset\\b", range: range).contains { _, tokens in
|
|
return tokens.count == 1 && tokens[0].kind == .keyword
|
|
}
|
|
}
|
|
}
|