Rewrite pattern_matching_keywords with SwiftSyntax (#4490)

This commit is contained in:
Marcelo Fabri
2022-11-05 23:16:59 -07:00
committed by GitHub
parent 22522254df
commit fc0c0c7f5b
2 changed files with 64 additions and 33 deletions
+1
View File
@@ -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
}
}