From 94ca0dcc829db2a7618b9a4bd6a1295270907075 Mon Sep 17 00:00:00 2001 From: Norio Nomura Date: Tue, 4 Oct 2016 21:27:30 +0900 Subject: [PATCH] Re-write `ExplicitInitRule` to `ASTRule` --- .../Rules/ExplicitInitRule.swift | 98 ++++++++++++++----- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift b/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift index 86d51d0bf3..4d118187b7 100644 --- a/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift +++ b/Source/SwiftLintFramework/Rules/ExplicitInitRule.swift @@ -9,9 +9,7 @@ import Foundation import SourceKittenFramework -public struct ExplicitInitRule: ConfigurationProviderRule, CorrectableRule, OptInRule { - - private let pattern = "\\b([A-Z][A-Za-z]*)\\.init\\(" +public struct ExplicitInitRule: ASTRule, ConfigurationProviderRule, CorrectableRule, OptInRule { public var configuration = SeverityConfiguration(.Warning) @@ -22,49 +20,99 @@ public struct ExplicitInitRule: ConfigurationProviderRule, CorrectableRule, OptI name: "Explicit Init", description: "Explicitly calling .init() should be avoided.", nonTriggeringExamples: [ - "self.init(", - "self.init", - "Abc.init", - "abc.init(", - "$0.init(" + "import Foundation; class C: NSObject { override init() { super.init() }}", // super + "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }", // self + "[1].flatMap(String.init)", // pass init as closure + "[String.self].map { $0.init(1) }", // initialize from a metatype value + "[String.self].map { type in type.init(1) }", // initialize from a metatype value ], triggeringExamples: [ - "Abc.init(", - "Abc(NSURL.init(someString" + "[1].flatMap{String↓.init($0)}", + "[String.self].map { Type in Type↓.init(1) }", // starting with capital assumes as type ], corrections: [ - "Abc.init(": "Abc(", - "Abc(NSURL.init(someString": "Abc(NSURL(someString" + "[1].flatMap{String.init($0)}" : "[1].flatMap{String($0)}", ] ) - public func validateFile(file: File) -> [StyleViolation] { - return violationRangesInFile(file).flatMap { range in - return StyleViolation(ruleDescription: self.dynamicType.description, + public enum Kind: String { + case expr_call = "source.lang.swift.expr.call" + case other + public init?(rawValue: String) { + switch rawValue { + case expr_call.rawValue: self = .expr_call + default: self = .other + } + } + } + + public func validateFile( + file: File, + kind: ExplicitInitRule.Kind, + dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] { + return violationRangesInFile(file, kind: kind, dictionary: dictionary).map { + StyleViolation(ruleDescription: self.dynamicType.description, severity: configuration.severity, - location: Location(file: file, characterOffset: range.location)) + location: Location(file: file, characterOffset: $0.location)) } } - private func violationRangesInFile(file: File) -> [NSRange] { - let excludingKinds = SyntaxKind.commentAndStringKinds() + private let initializerWithType = regex("^[A-Z].*\\.init$") + + private func violationRangesInFile( + file: File, + kind: ExplicitInitRule.Kind, + dictionary: [String: SourceKitRepresentable]) -> [NSRange] { + + func isExpected(name: String) -> Bool { + let range = NSRange(location: 0, length: name.utf16.count) + return !["super.init", "self.init"].contains(name) + && initializerWithType.numberOfMatchesInString(name, options: [], range: range) != 0 + } - return file.matchPattern(pattern, excludingSyntaxKinds: excludingKinds) + let length = ".init".utf16.count + + guard kind == .expr_call, + let name = dictionary["key.name"] as? String where isExpected(name), + let nameOffset = dictionary["key.nameoffset"] as? Int64, + let nameLength = dictionary["key.namelength"] as? Int64, + let range = (file.contents as NSString) + .byteRangeToNSRange(start: Int(nameOffset + nameLength) - length, length: length) + else { return [] } + return [range] + } + + private func violationRangesInFile( + file: File, + dictionary: [String: SourceKitRepresentable]) -> [NSRange] { + let substructure = dictionary["key.substructure"] as? [SourceKitRepresentable] ?? [] + return substructure.flatMap { subItem -> [NSRange] in + guard let subDict = subItem as? [String: SourceKitRepresentable], + kindString = subDict["key.kind"] as? String, + kind = ExplicitInitRule.Kind(rawValue: kindString) else { + return [] + } + return violationRangesInFile(file, dictionary: subDict) + + violationRangesInFile(file, kind: kind, dictionary: subDict) + } + } + + private func violationRangesInFile(file: File) -> [NSRange] { + return violationRangesInFile(file, dictionary: file.structure.dictionary).sort { lh, rh in + lh.location > rh.location + } } public func correctFile(file: File) -> [Correction] { let matches = violationRangesInFile(file) guard !matches.isEmpty else { return [] } - let regularExpression = regex(pattern) let description = self.dynamicType.description var corrections = [Correction]() var contents = file.contents - for range in matches.reverse() { - contents = regularExpression.stringByReplacingMatchesInString(contents, - options: [], - range: range, - withTemplate: "$1(") + for range in matches { + contents = (contents as NSString) + .stringByReplacingCharactersInRange(range, withString: "") let location = Location(file: file, characterOffset: range.location) corrections.append(Correction(ruleDescription: description, location: location)) }