import SwiftSyntax @SwiftSyntaxRule struct LargeTupleRule: Rule { var configuration = LargeTupleConfiguration() static let description = RuleDescription( identifier: "large_tuple", name: "Large Tuple", description: "Tuples shouldn't have too many members. Create a custom type instead.", kind: .metrics, nonTriggeringExamples: [ Example("let foo: (Int, Int)"), Example("let foo: (start: Int, end: Int)"), Example("let foo: (Int, (Int, String))"), Example("func foo() -> (Int, Int)"), Example("func foo() -> (Int, Int) {}"), Example("func foo(bar: String) -> (Int, Int)"), Example("func foo(bar: String) -> (Int, Int) {}"), Example("func foo() throws -> (Int, Int)"), Example("func foo() throws -> (Int, Int) {}"), Example("let foo: (Int, Int, Int) -> Void"), Example("let foo: (Int, Int, Int) throws -> Void"), Example("func foo(bar: (Int, String, Float) -> Void)"), Example("func foo(bar: (Int, String, Float) throws -> Void)"), Example("var completionHandler: ((_ data: Data?, _ resp: URLResponse?, _ e: NSError?) -> Void)!"), Example("func getDictionaryAndInt() -> (Dictionary, Int)?"), Example("func getGenericTypeAndInt() -> (Type, Int)?"), Example("func foo() async -> (Int, Int)"), Example("func foo() async -> (Int, Int) {}"), Example("func foo(bar: String) async -> (Int, Int)"), Example("func foo(bar: String) async -> (Int, Int) {}"), Example("func foo() async throws -> (Int, Int)"), Example("func foo() async throws -> (Int, Int) {}"), Example("let foo: (Int, Int, Int) async -> Void"), Example("let foo: (Int, Int, Int) async throws -> Void"), Example("func foo(bar: (Int, String, Float) async -> Void)"), Example("func foo(bar: (Int, String, Float) async throws -> Void)"), Example("func getDictionaryAndInt() async -> (Dictionary, Int)?"), Example("func getGenericTypeAndInt() async -> (Type, Int)?"), Example( "func foo() -> Regex<(Substring, foo: Substring, bar: Substring)>.Match? { nil }", configuration: ["ignore_regex": true] ), Example( "let regex: Regex<(Substring, Substring, Substring, Substring)>? = nil", configuration: ["ignore_regex": true] ), Example( "var regex: Regex<(Substring, Substring, Substring, Substring)?>.Match? { nil }", configuration: ["ignore_regex": true] ), ], triggeringExamples: [ Example("let foo: ↓(Int, Int, Int)"), Example("let foo: ↓(start: Int, end: Int, value: String)"), Example("let foo: (Int, ↓(Int, Int, Int))"), Example("func foo(bar: ↓(Int, Int, Int))"), Example("func foo() -> ↓(Int, Int, Int)"), Example("func foo() -> ↓(Int, Int, Int) {}"), Example("func foo(bar: String) -> ↓(Int, Int, Int)"), Example("func foo(bar: String) -> ↓(Int, Int, Int) {}"), Example("func foo() throws -> ↓(Int, Int, Int)"), Example("func foo() throws -> ↓(Int, Int, Int) {}"), Example("func foo() throws -> ↓(Int, ↓(String, String, String), Int) {}"), Example("func getDictionaryAndInt() -> (Dictionary, Int)?"), Example("func foo(bar: ↓(Int, Int, Int)) async"), Example("func foo() async -> ↓(Int, Int, Int)"), Example("func foo() async -> ↓(Int, Int, Int) {}"), Example("func foo(bar: String) async -> ↓(Int, Int, Int)"), Example("func foo(bar: String) async -> ↓(Int, Int, Int) {}"), Example("func foo() async throws -> ↓(Int, Int, Int)"), Example( "func foo() async throws -> ↓(Int, Int, Int) {}", configuration: ["ignore_regex": false] ), Example("func foo() async throws -> ↓(Int, ↓(String, String, String), Int) {}"), Example("func getDictionaryAndInt() async -> (Dictionary, Int)?"), ] ) } private extension LargeTupleRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: TupleTypeSyntax) { if configuration.ignoreRegex, node.isInsideRegexType { return } let memberCount = node.elements.count for parameter in configuration.severityConfiguration.params where memberCount > parameter.value { violations.append(.init( position: node.positionAfterSkippingLeadingTrivia, reason: "Tuples should have at most \(configuration.severityConfiguration.warning) members", severity: parameter.severity )) return } } } } private extension TupleTypeSyntax { var isInsideRegexType: Bool { var current: Syntax? = Syntax(self) // Skip OptionalType wrapper if present (for Regex<(A, B)?>) if current?.parent?.is(OptionalTypeSyntax.self) == true { current = current?.parent } guard let genericArgument = current?.parent?.as(GenericArgumentSyntax.self), let genericArgumentList = genericArgument.parent?.as(GenericArgumentListSyntax.self), let genericArgumentClause = genericArgumentList.parent?.as(GenericArgumentClauseSyntax.self), let identifierType = genericArgumentClause.parent?.as(IdentifierTypeSyntax.self), identifierType.name.text == "Regex" else { return false } return true } }