mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
* Compile with `-strict-concurrency=complete` Only in Bazel for now, because this is considered an unsafe flag in SwiftPM which would lead to warnings for downstream consumers of SwiftLint using SwiftPM. Some imports of SwiftSyntax need the `@preconcurrency` annotation until https://github.com/apple/swift-syntax/pull/2322 is available in a release. The following SwiftLint libraries have `-strict-concurrency=complete` applied: * SwiftLintCoreMacros * SwiftLintBuiltInRules * SwiftLintExtraRules The following SwiftLint libraries don't have the flag applied and need to be migrated: * SwiftLintCore * swiftlint (CLI target) So really the rules and macros are now being compiled with `-strict-concurrency=complete`, but the core infrastructure of SwiftLint is not. Still, given that Swift 6 will eventually make these warnings errors by default, it's good to prevent issues from creeping in earlier rather than later. * Add CI job to build with strict concurrency
212 lines
5.9 KiB
Swift
212 lines
5.9 KiB
Swift
import SwiftSyntax
|
|
|
|
@SwiftSyntaxRule
|
|
struct ValidIBInspectableRule: Rule {
|
|
var configuration = SeverityConfiguration<Self>(.warning)
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable var borderColor: UIColor? = nil {
|
|
didSet {
|
|
updateAppearance()
|
|
}
|
|
}
|
|
}
|
|
""")
|
|
],
|
|
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 count: Optional<Int>
|
|
}
|
|
"""),
|
|
Example("""
|
|
class Foo {
|
|
@IBInspectable private ↓var x: Optional<String>
|
|
}
|
|
""")
|
|
]
|
|
)
|
|
|
|
fileprivate static let supportedTypes: Set<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 Set(referenceTypes.flatMap(expandToIncludeOptionals) + types + intTypes)
|
|
}()
|
|
}
|
|
|
|
private extension ValidIBInspectableRule {
|
|
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
|
|
override var skippableDeclarations: [any DeclSyntaxProtocol.Type] { [FunctionDeclSyntax.self] }
|
|
|
|
override func visitPost(_ node: VariableDeclSyntax) {
|
|
if node.isInstanceVariable, node.isIBInspectable, node.hasViolation {
|
|
violations.append(node.bindingSpecifier.positionAfterSkippingLeadingTrivia)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension VariableDeclSyntax {
|
|
var isIBInspectable: Bool {
|
|
attributes.contains(attributeNamed: "IBInspectable")
|
|
}
|
|
|
|
var hasViolation: Bool {
|
|
isReadOnlyProperty || !isSupportedType
|
|
}
|
|
|
|
var isReadOnlyProperty: Bool {
|
|
if bindingSpecifier.tokenKind == .keyword(.let) {
|
|
return true
|
|
}
|
|
|
|
let computedProperty = bindings.contains { binding in
|
|
binding.accessorBlock != nil
|
|
}
|
|
|
|
if !computedProperty {
|
|
return false
|
|
}
|
|
|
|
return bindings.allSatisfy { binding in
|
|
guard let accessorBlock = binding.accessorBlock?.as(AccessorBlockSyntax.self) else {
|
|
return true
|
|
}
|
|
|
|
// if it has a `get`, it needs to have a `set`, otherwise it's readonly
|
|
if accessorBlock.getAccessor != nil {
|
|
return accessorBlock.setAccessor == nil
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
var isSupportedType: Bool {
|
|
bindings.allSatisfy { binding in
|
|
guard let type = binding.typeAnnotation else {
|
|
return false
|
|
}
|
|
|
|
return ValidIBInspectableRule.supportedTypes.contains(type.type.trimmedDescription)
|
|
}
|
|
}
|
|
}
|