-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
tuple_pattern
opt-in rule (#3086)
* Add `tuple_pattern` opt-in rule Fixes #2203 * Remove unused import
- Loading branch information
1 parent
bdede7b
commit f95768d
Showing
6 changed files
with
113 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
Source/SwiftLintFramework/Rules/Idiomatic/TuplePatternRule.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import SourceKittenFramework | ||
#if canImport(SwiftSyntax) | ||
import SwiftSyntax | ||
#endif | ||
|
||
public struct TuplePatternRule: ConfigurationProviderRule, SyntaxRule, AutomaticTestableRule { | ||
public var configuration = SeverityConfiguration(.warning) | ||
|
||
public init() {} | ||
|
||
public static let description = RuleDescription( | ||
identifier: "tuple_pattern", | ||
name: "Tuple Pattern", | ||
description: "Assigning variables through a tuple pattern is only permitted if the left-hand side of the " + | ||
"assignment is unlabeled.", | ||
kind: .idiomatic, | ||
minSwiftVersion: .fiveDotOne, | ||
nonTriggeringExamples: [ | ||
Example("let (a, b) = (y: 4, x: 5.0)"), | ||
Example("let (a, b) = (4, 5.0)"), | ||
Example("let (a, b) = (a: 4, b: 5.0)"), | ||
Example("let (a, b) = tuple") | ||
], | ||
triggeringExamples: [ | ||
Example("let ↓(x: a, y: b) = (y: 4, x: 5.0)"), | ||
Example("let ↓(x: Int, y: Double) = (y: 4, x: 5.0)"), | ||
Example("let ↓(x: Int, y: Double) = (y: 4, x: 5.0)") | ||
] | ||
) | ||
|
||
public func validate(file: SwiftLintFile) -> [StyleViolation] { | ||
#if canImport(SwiftSyntax) | ||
return validate(file: file, visitor: PatternBindingVisitor()) | ||
#else | ||
return [] | ||
#endif | ||
} | ||
} | ||
|
||
#if canImport(SwiftSyntax) | ||
private class PatternBindingVisitor: SyntaxRuleVisitor { | ||
private var positions = [AbsolutePosition]() | ||
|
||
func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { | ||
if let tuplePattern = node.pattern as? TuplePatternSyntax, | ||
case let leftSideLabels = tuplePattern.labels, | ||
!leftSideLabels.compactMap({ $0 }).isEmpty, | ||
let rightSideLabels = node.initializer?.tupleElementList?.labels, | ||
leftSideLabels != rightSideLabels { | ||
positions.append(node.positionAfterSkippingLeadingTrivia) | ||
} | ||
return .visitChildren | ||
} | ||
|
||
func violations(for rule: TuplePatternRule, in file: SwiftLintFile) -> [StyleViolation] { | ||
return positions.map { position in | ||
StyleViolation(ruleDescription: type(of: rule).description, | ||
severity: rule.configuration.severity, | ||
location: Location(file: file, byteOffset: ByteCount(position.utf8Offset))) | ||
} | ||
} | ||
} | ||
|
||
private extension TuplePatternSyntax { | ||
var labels: [String?] { | ||
return elements.map { element in | ||
element.labelName?.withoutTrivia().text | ||
} | ||
} | ||
} | ||
|
||
private extension InitializerClauseSyntax { | ||
var tupleElementList: TupleElementListSyntax? { | ||
if let expr = value as? TupleExprSyntax { | ||
return expr.elementList | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
private extension TupleElementListSyntax { | ||
var labels: [String?] { | ||
return map { element in | ||
element.label?.withoutTrivia().text | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters