Files
SwiftLint/Source/SwiftLintBuiltInRules/Rules/Lint/ValidIBInspectableRule.swift
JP Simard 3eb3772022 Compile with -strict-concurrency=complete (#5320)
* 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
2023-11-01 15:20:40 +00:00

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