mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
Rewrite pattern_matching_keywords with SwiftSyntax (#4490)
This commit is contained in:
@@ -143,6 +143,7 @@
|
||||
- `orphaned_doc_comment`
|
||||
- `overridden_super_call`
|
||||
- `override_in_extension`
|
||||
- `pattern_matching_keywords`
|
||||
- `prefer_nimble`
|
||||
- `prefer_self_in_static_references`
|
||||
- `prefer_self_type_over_type_of_self`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
import SourceKittenFramework
|
||||
import SwiftSyntax
|
||||
|
||||
public struct PatternMatchingKeywordsRule: ASTRule, ConfigurationProviderRule, OptInRule {
|
||||
public struct PatternMatchingKeywordsRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
|
||||
public var configuration = SeverityConfiguration(.warning)
|
||||
|
||||
public init() {}
|
||||
@@ -26,6 +25,8 @@ public struct PatternMatchingKeywordsRule: ASTRule, ConfigurationProviderRule, O
|
||||
].map(wrapInSwitch),
|
||||
triggeringExamples: [
|
||||
Example("case (↓let x, ↓let y)"),
|
||||
Example("case (↓let x, ↓let y, .foo)"),
|
||||
Example("case (↓let x, ↓let y, _)"),
|
||||
Example("case .foo(↓let x, ↓let y)"),
|
||||
Example("case (.yamlParsing(↓let x), .yamlParsing(↓let y))"),
|
||||
Example("case (↓var x, ↓var y)"),
|
||||
@@ -34,38 +35,67 @@ public struct PatternMatchingKeywordsRule: ASTRule, ConfigurationProviderRule, O
|
||||
].map(wrapInSwitch)
|
||||
)
|
||||
|
||||
public func validate(file: SwiftLintFile, kind: StatementKind,
|
||||
dictionary: SourceKittenDictionary) -> [StyleViolation] {
|
||||
guard kind == .case else {
|
||||
return []
|
||||
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
||||
Visitor(viewMode: .sourceAccurate)
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatternMatchingKeywordsRule {
|
||||
final class Visitor: ViolationsSyntaxVisitor {
|
||||
override func visitPost(_ node: CaseItemSyntax) {
|
||||
let localViolations = TupleVisitor(viewMode: .sourceAccurate)
|
||||
.walk(tree: node.pattern, handler: \.violations)
|
||||
violations.append(contentsOf: localViolations)
|
||||
}
|
||||
}
|
||||
|
||||
final class TupleVisitor: ViolationsSyntaxVisitor {
|
||||
override func visitPost(_ node: TupleExprElementListSyntax) {
|
||||
let list = node.flatteningEnumPatterns()
|
||||
.compactMap { elem in
|
||||
elem.expression.asValueBindingPattern()
|
||||
}
|
||||
|
||||
guard list.count > 1,
|
||||
let firstLetOrVar = list.first?.letOrVarKeyword.tokenKind else {
|
||||
return
|
||||
}
|
||||
|
||||
let hasViolation = list.allSatisfy { elem in
|
||||
elem.letOrVarKeyword.tokenKind == firstLetOrVar
|
||||
}
|
||||
|
||||
guard hasViolation else {
|
||||
return
|
||||
}
|
||||
|
||||
violations.append(contentsOf: list.compactMap { elem in
|
||||
return elem.letOrVarKeyword.positionAfterSkippingLeadingTrivia
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension TupleExprElementListSyntax {
|
||||
func flatteningEnumPatterns() -> [TupleExprElementSyntax] {
|
||||
flatMap { elem in
|
||||
guard let pattern = elem.expression.as(FunctionCallExprSyntax.self),
|
||||
pattern.calledExpression.is(MemberAccessExprSyntax.self) else {
|
||||
return [elem]
|
||||
}
|
||||
|
||||
return Array(pattern.argumentList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ExprSyntax {
|
||||
func asValueBindingPattern() -> ValueBindingPatternSyntax? {
|
||||
if let pattern = self.as(UnresolvedPatternExprSyntax.self) {
|
||||
return pattern.pattern.as(ValueBindingPatternSyntax.self)
|
||||
}
|
||||
|
||||
let contents = file.stringView
|
||||
return dictionary.elements.flatMap { subDictionary -> [StyleViolation] in
|
||||
guard subDictionary.kind == "source.lang.swift.structure.elem.pattern",
|
||||
let caseByteRange = subDictionary.byteRange,
|
||||
let caseRange = contents.byteRangeToNSRange(caseByteRange)
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
let letMatches = file.match(pattern: "\\blet\\b", with: [.keyword], range: caseRange)
|
||||
let varMatches = file.match(pattern: "\\bvar\\b", with: [.keyword], range: caseRange)
|
||||
|
||||
if letMatches.isNotEmpty && varMatches.isNotEmpty {
|
||||
return []
|
||||
}
|
||||
|
||||
guard letMatches.count > 1 || varMatches.count > 1 else {
|
||||
return []
|
||||
}
|
||||
|
||||
return (letMatches + varMatches).map {
|
||||
StyleViolation(ruleDescription: Self.description,
|
||||
severity: configuration.severity,
|
||||
location: Location(file: file, characterOffset: $0.location))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user