diff --git a/CHANGELOG.md b/CHANGELOG.md index 8583a1afb..85f825766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,8 +82,10 @@ [Aaron McTavish](https://github.com/aamctustwo) [#970](https://github.com/realm/SwiftLint/issues/970) -* Add `void_return` rule to validate usage of `-> Void` over `-> ()`. +* Add correctable `void_return` rule to validate usage of `-> Void` + over `-> ()`. [Marcelo Fabri](https://github.com/marcelofabri) + [JP Simard](https://github.com/jpsim) [#964](https://github.com/realm/SwiftLint/issues/964) * Add `empty_parameters` rule to validate usage of `() -> ` over `Void -> `. diff --git a/Source/SwiftLintFramework/Rules/VoidReturnRule.swift b/Source/SwiftLintFramework/Rules/VoidReturnRule.swift index 701a71471..22897cb85 100644 --- a/Source/SwiftLintFramework/Rules/VoidReturnRule.swift +++ b/Source/SwiftLintFramework/Rules/VoidReturnRule.swift @@ -9,7 +9,7 @@ import Foundation import SourceKittenFramework -public struct VoidReturnRule: Rule, ConfigurationProviderRule { +public struct VoidReturnRule: ConfigurationProviderRule, CorrectableRule { public var configuration = SeverityConfiguration(.warning) public init() {} @@ -31,26 +31,60 @@ public struct VoidReturnRule: Rule, ConfigurationProviderRule { "func foo(completion: () -> ↓())\n", "func foo(completion: () -> ↓( ))\n", "let foo: (ConfigurationTests) -> () throws -> ↓())\n" + ], + corrections: [ + "let abc: () -> () = {}\n": "let abc: () -> Void = {}\n", + "func foo(completion: () -> ())\n": "func foo(completion: () -> Void)\n", + "func foo(completion: () -> ( ))\n": "func foo(completion: () -> Void)\n", + "let foo: (ConfigurationTests) -> () throws -> ())\n": + "let foo: (ConfigurationTests) -> () throws -> Void)\n" ] ) public func validateFile(_ file: File) -> [StyleViolation] { + return violationRanges(file: file).map { + StyleViolation(ruleDescription: type(of: self).description, + severity: configuration.severity, + location: Location(file: file, characterOffset: $0.location)) + } + } + + private func violationRanges(file: File) -> [NSRange] { let kinds = SyntaxKind.commentAndStringKinds() - let pattern = "->\\s*\\(\\s*\\)\\s*(?!->)" + let parensPattern = "\\(\\s*\\)" + let pattern = "->\\s*\(parensPattern)\\s*(?!->)" let excludingPattern = "(\(pattern))\\s*(throws\\s+)?->" return file.matchPattern(pattern, excludingSyntaxKinds: kinds, excludingPattern: excludingPattern) { $0.rangeAt(1) }.flatMap { + let parensRegex = NSRegularExpression.forcePattern(parensPattern) + return parensRegex.firstMatch(in: file.contents, options: [], range: $0)?.range + } + } - let range = file.contents.bridge().substring(with: $0).bridge().range(of: "(") - guard range.location != NSNotFound else { - return nil + public func correctFile(_ file: File) -> [Correction] { + let violatingRanges = file.ruleEnabledViolatingRanges(violationRanges(file: file), + forRule: self) + return writeToFile(file, violatingRanges: violatingRanges) + } + + private func writeToFile(_ file: File, violatingRanges: [NSRange]) -> [Correction] { + var correctedContents = file.contents + var adjustedLocations = [Int]() + + for violatingRange in violatingRanges.reversed() { + if let indexRange = correctedContents.nsrangeToIndexRange(violatingRange) { + correctedContents = correctedContents + .replacingCharacters(in: indexRange, with: "Void") + adjustedLocations.insert(violatingRange.location, at: 0) } + } - let offset = range.location + $0.location - return StyleViolation(ruleDescription: type(of: self).description, - severity: configuration.severity, - location: Location(file: file, characterOffset: offset)) + file.write(correctedContents) + + return adjustedLocations.map { + Correction(ruleDescription: type(of: self).description, + location: Location(file: file, characterOffset: $0)) } } }