185 lines
6.6 KiB
Swift
185 lines
6.6 KiB
Swift
import SwiftSyntax
|
|
|
|
struct CompilerProtocolInitRule: SwiftSyntaxRule, ConfigurationProviderRule {
|
|
var configuration = SeverityConfiguration<Self>(.warning)
|
|
|
|
static let description = RuleDescription(
|
|
identifier: "compiler_protocol_init",
|
|
name: "Compiler Protocol Init",
|
|
description: "The initializers declared in compiler protocols such as `ExpressibleByArrayLiteral` " +
|
|
"shouldn't be called directly.",
|
|
kind: .lint,
|
|
nonTriggeringExamples: [
|
|
Example("let set: Set<Int> = [1, 2]\n"),
|
|
Example("let set = Set(array)\n")
|
|
],
|
|
triggeringExamples: [
|
|
Example("let set = ↓Set(arrayLiteral: 1, 2)\n"),
|
|
Example("let set = ↓Set (arrayLiteral: 1, 2)\n"),
|
|
Example("let set = ↓Set.init(arrayLiteral: 1, 2)\n"),
|
|
Example("let set = ↓Set.init(arrayLiteral : 1, 2)\n")
|
|
]
|
|
)
|
|
|
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
Visitor(viewMode: .sourceAccurate)
|
|
}
|
|
}
|
|
|
|
private extension CompilerProtocolInitRule {
|
|
final class Visitor: ViolationsSyntaxVisitor {
|
|
override func visitPost(_ node: FunctionCallExprSyntax) {
|
|
guard node.trailingClosure == nil else {
|
|
return
|
|
}
|
|
|
|
let arguments = node.argumentList.compactMap(\.label)
|
|
guard ExpressibleByCompiler.possibleNumberOfArguments.contains(arguments.count) else {
|
|
return
|
|
}
|
|
|
|
guard let name = node.functionName, ExpressibleByCompiler.allInitNames.contains(name) else {
|
|
return
|
|
}
|
|
|
|
let argumentsNames = arguments.map(\.text)
|
|
for compilerProtocol in ExpressibleByCompiler.allProtocols {
|
|
guard compilerProtocol.initCallNames.contains(name),
|
|
compilerProtocol.match(arguments: argumentsNames) else {
|
|
continue
|
|
}
|
|
|
|
violations.append(ReasonedRuleViolation(
|
|
position: node.positionAfterSkippingLeadingTrivia,
|
|
reason: "Initializers declared in compiler protocol \(compilerProtocol.protocolName) " +
|
|
"shouldn't be called directly"
|
|
))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension FunctionCallExprSyntax {
|
|
// doing this instead of calling `.description` as it's faster
|
|
var functionName: String? {
|
|
if let expr = calledExpression.as(IdentifierExprSyntax.self) {
|
|
return expr.identifier.text
|
|
} else if let expr = calledExpression.as(MemberAccessExprSyntax.self),
|
|
let base = expr.base?.as(IdentifierExprSyntax.self) {
|
|
return base.identifier.text + "." + expr.name.text
|
|
}
|
|
|
|
// we don't care about other possible expressions as they wouldn't match the calls we're interested in
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private struct ExpressibleByCompiler {
|
|
let protocolName: String
|
|
let initCallNames: Set<String>
|
|
private let arguments: Set<[String]>
|
|
|
|
private init(protocolName: String, types: Set<String>, arguments: Set<[String]>) {
|
|
self.protocolName = protocolName
|
|
self.arguments = arguments
|
|
|
|
initCallNames = Set(types.flatMap { [$0, "\($0).init"] })
|
|
}
|
|
|
|
static let allProtocols = [byArrayLiteral, byNilLiteral, byBooleanLiteral,
|
|
byFloatLiteral, byIntegerLiteral, byUnicodeScalarLiteral,
|
|
byExtendedGraphemeClusterLiteral, byStringLiteral,
|
|
byStringInterpolation, byDictionaryLiteral]
|
|
|
|
static let possibleNumberOfArguments: Set<Int> = {
|
|
allProtocols.reduce(into: Set<Int>()) { partialResult, entry in
|
|
partialResult.insert(entry.arguments.count)
|
|
}
|
|
}()
|
|
|
|
static let allInitNames: Set<String> = {
|
|
allProtocols.reduce(into: Set<String>()) { partialResult, entry in
|
|
partialResult.formUnion(entry.initCallNames)
|
|
}
|
|
}()
|
|
|
|
func match(arguments: [String]) -> Bool {
|
|
return self.arguments.contains(arguments)
|
|
}
|
|
|
|
private static let byArrayLiteral: ExpressibleByCompiler = {
|
|
let types: Set = [
|
|
"Array",
|
|
"ArraySlice",
|
|
"ContiguousArray",
|
|
"IndexPath",
|
|
"NSArray",
|
|
"NSCountedSet",
|
|
"NSMutableArray",
|
|
"NSMutableOrderedSet",
|
|
"NSMutableSet",
|
|
"NSOrderedSet",
|
|
"NSSet",
|
|
"SBElementArray",
|
|
"Set",
|
|
"IndexSet"
|
|
]
|
|
return Self(protocolName: "ExpressibleByArrayLiteral", types: types, arguments: [["arrayLiteral"]])
|
|
}()
|
|
|
|
private static let byNilLiteral = Self(
|
|
protocolName: "ExpressibleByNilLiteral",
|
|
types: ["Optional"],
|
|
arguments: [["nilLiteral"]]
|
|
)
|
|
|
|
private static let byBooleanLiteral = Self(
|
|
protocolName: "ExpressibleByBooleanLiteral",
|
|
types: ["Bool", "NSDecimalNumber", "NSNumber", "ObjCBool"],
|
|
arguments: [["booleanLiteral"]]
|
|
)
|
|
|
|
private static let byFloatLiteral = Self(
|
|
protocolName: "ExpressibleByFloatLiteral",
|
|
types: ["Decimal", "NSDecimalNumber", "NSNumber"],
|
|
arguments: [["floatLiteral"]]
|
|
)
|
|
|
|
private static let byIntegerLiteral = Self(
|
|
protocolName: "ExpressibleByIntegerLiteral",
|
|
types: ["Decimal", "Double", "Float", "Float80", "NSDecimalNumber", "NSNumber"],
|
|
arguments: [["integerLiteral"]]
|
|
)
|
|
|
|
private static let byUnicodeScalarLiteral = Self(
|
|
protocolName: "ExpressibleByUnicodeScalarLiteral",
|
|
types: ["StaticString", "String", "UnicodeScalar"],
|
|
arguments: [["unicodeScalarLiteral"]]
|
|
)
|
|
|
|
private static let byExtendedGraphemeClusterLiteral = Self(
|
|
protocolName: "ExpressibleByExtendedGraphemeClusterLiteral",
|
|
types: ["Character", "StaticString", "String"],
|
|
arguments: [["extendedGraphemeClusterLiteral"]]
|
|
)
|
|
|
|
private static let byStringLiteral = Self(
|
|
protocolName: "ExpressibleByStringLiteral",
|
|
types: ["CSLocalizedString", "NSMutableString", "NSString", "Selector", "StaticString", "String"],
|
|
arguments: [["stringLiteral"]]
|
|
)
|
|
|
|
private static let byStringInterpolation = Self(
|
|
protocolName: "ExpressibleByStringInterpolation",
|
|
types: ["String"],
|
|
arguments: [["stringInterpolation"], ["stringInterpolationSegment"]]
|
|
)
|
|
|
|
private static let byDictionaryLiteral = Self(
|
|
protocolName: "ExpressibleByDictionaryLiteral",
|
|
types: ["Dictionary", "DictionaryLiteral", "NSDictionary", "NSMutableDictionary"],
|
|
arguments: [["dictionaryLiteral"]]
|
|
)
|
|
}
|