mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
122 lines
5.9 KiB
Swift
122 lines
5.9 KiB
Swift
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, String>, Int)?"),
|
|
Example("func getGenericTypeAndInt() -> (Type<Int, String, Float>, 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, String>, Int)?"),
|
|
Example("func getGenericTypeAndInt() async -> (Type<Int, String, Float>, 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, ↓(String, String, String)>, 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, ↓(String, String, String)>, Int)?"),
|
|
]
|
|
)
|
|
}
|
|
|
|
private extension LargeTupleRule {
|
|
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
|
|
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
|
|
}
|
|
}
|