Compare commits

...

2 Commits

Author SHA1 Message Date
Marcelo Fabri 5e82f7999b Remove import 2022-09-04 05:40:22 -07:00
Marcelo Fabri 200496710d Rewrite void_return rule using SwiftSyntax 2022-09-04 04:09:28 -07:00
1 changed files with 77 additions and 23 deletions

View File

@ -1,7 +1,6 @@
import Foundation
import SourceKittenFramework
import SwiftSyntax
public struct VoidReturnRule: ConfigurationProviderRule, SubstitutionCorrectableRule {
public struct VoidReturnRule: ConfigurationProviderRule, SwiftSyntaxCorrectableRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
@ -41,28 +40,83 @@ public struct VoidReturnRule: ConfigurationProviderRule, SubstitutionCorrectable
]
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
return violationRanges(in: file).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
public func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor? {
VoidReturnRuleVisitor()
}
public func violationRanges(in file: SwiftLintFile) -> [NSRange] {
let kinds = SyntaxKind.commentAndStringKinds
let parensPattern = "\\(\\s*(?:Void)?\\s*\\)"
let pattern = "->\\s*\(parensPattern)\\s*(?!->)"
let excludingPattern = "(\(pattern))\\s*(throws\\s+)?->"
return file.match(pattern: pattern, excludingSyntaxKinds: kinds, excludingPattern: excludingPattern,
exclusionMapping: { $0.range(at: 1) }).compactMap {
let parensRegex = regex(parensPattern)
return parensRegex.firstMatch(in: file.contents, options: [], range: $0)?.range
public func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
file.locationConverter.map { locationConverter in
VoidReturnRuleRewriter(
locationConverter: locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
return (violationRange, "Void")
}
}
// MARK: - VoidReturnRuleVisitor
private final class VoidReturnRuleVisitor: SyntaxVisitor, ViolationsSyntaxVisitor {
private(set) var violationPositions: [AbsolutePosition] = []
override func visitPost(_ node: FunctionTypeSyntax) {
guard let tuple = node.returnType.as(TupleTypeSyntax.self),
tuple.shouldReturnVoid else {
return
}
violationPositions.append(tuple.positionAfterSkippingLeadingTrivia)
}
}
private extension TupleTypeSyntax {
var shouldReturnVoid: Bool {
if elements.isEmpty {
return true
}
if elements.count == 1,
let identifier = elements.first?.type.as(SimpleTypeIdentifierSyntax.self),
identifier.name.text == "Void" {
return true
}
return false
}
}
// MARK: - VoidReturnRuleRewriter
private final class VoidReturnRuleRewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionTypeSyntax) -> TypeSyntax {
guard let tuple = node.returnType.as(TupleTypeSyntax.self),
tuple.shouldReturnVoid else {
return super.visit(node)
}
let isInDisabledRegion = disabledRegions.contains { region in
region.contains(node.positionAfterSkippingLeadingTrivia, locationConverter: locationConverter)
}
guard !isInDisabledRegion else {
return super.visit(node)
}
correctionPositions.append(tuple.positionAfterSkippingLeadingTrivia)
var returnType = SyntaxFactory.makeTypeIdentifier("Void")
returnType.leadingTrivia = tuple.leadingTrivia
returnType.trailingTrivia = tuple.trailingTrivia
return super.visit(node.withReturnType(returnType))
}
}