import SwiftSyntax @SwiftSyntaxRule struct CompilerProtocolInitRule: Rule { var configuration = SeverityConfiguration(.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 = [1, 2]"), Example("let set = Set(array)"), ], triggeringExamples: [ Example("let set = ↓Set(arrayLiteral: 1, 2)"), Example("let set = ↓Set (arrayLiteral: 1, 2)"), Example("let set = ↓Set.init(arrayLiteral: 1, 2)"), Example("let set = ↓Set.init(arrayLiteral : 1, 2)"), ] ) } private extension CompilerProtocolInitRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: FunctionCallExprSyntax) { guard node.trailingClosure == nil else { return } let arguments = node.arguments.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(DeclReferenceExprSyntax.self) { return expr.baseName.text } if let expr = calledExpression.as(MemberAccessExprSyntax.self), let base = expr.base?.as(DeclReferenceExprSyntax.self) { return base.baseName.text + "." + expr.declName.baseName.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 private let arguments: Set<[String]> private init(protocolName: String, types: Set, 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 = { allProtocols.reduce(into: Set()) { partialResult, entry in partialResult.insert(entry.arguments.count) } }() static let allInitNames: Set = { allProtocols.reduce(into: Set()) { partialResult, entry in partialResult.formUnion(entry.initCallNames) } }() func match(arguments: [String]) -> Bool { 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"]] ) }