Files
SwiftLint/Source/SwiftLintFramework/Rules/MarkRule.swift
T
Eric Horacek 139df3d612 Improve mark rule autocorrection
Adds a few additional cases for the MARK rule to autocorrect.

In our project, this made it so that the all MARK rule failures were corrected automatically. Before this change, we had over 30 failures that could not be autocorrected.
2018-05-16 11:08:24 -07:00

205 lines
8.5 KiB
Swift

import Foundation
import SourceKittenFramework
private let nonSpace = "[^ ]"
private let twoOrMoreSpace = " {2,}"
private let mark = "MARK:"
private let nonSpaceOrTwoOrMoreSpace = "(?:\(nonSpace)|\(twoOrMoreSpace))"
private let nonSpaceOrTwoOrMoreSpaceOrNewline = "(?:[^ \n]|\(twoOrMoreSpace))"
public struct MarkRule: CorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
identifier: "mark",
name: "Mark",
description: "MARK comment should be in valid format. e.g. '// MARK: ...' or '// MARK: - ...'",
kind: .lint,
nonTriggeringExamples: [
"// MARK: good\n",
"// MARK: - good\n",
"// MARK: -\n",
"// BOOKMARK",
"//BOOKMARK",
"// BOOKMARKS"
],
triggeringExamples: [
"↓//MARK: bad",
"↓// MARK:bad",
"↓//MARK:bad",
"↓// MARK: bad",
"↓// MARK: bad",
"↓// MARK: -bad",
"↓// MARK:- bad",
"↓// MARK:-bad",
"↓//MARK: - bad",
"↓//MARK:- bad",
"↓//MARK: -bad",
"↓//MARK:-bad",
"↓//Mark: bad",
"↓// Mark: bad",
"↓// MARK bad",
"↓//MARK bad",
"↓// MARK - bad",
"↓//MARK : bad",
"↓// MARKL:",
"↓// MARKR ",
"↓// MARKK -",
issue1029Example
],
corrections: [
"↓//MARK: comment": "// MARK: comment",
"↓// MARK: comment": "// MARK: comment",
"↓// MARK:comment": "// MARK: comment",
"↓// MARK: comment": "// MARK: comment",
"↓//MARK: - comment": "// MARK: - comment",
"↓// MARK:- comment": "// MARK: - comment",
"↓// MARK: -comment": "// MARK: - comment",
"↓// MARK: - comment": "// MARK: - comment",
"↓// Mark: comment": "// MARK: comment",
"↓// Mark: - comment": "// MARK: - comment",
"↓// MARK - comment": "// MARK: - comment",
"↓// MARK : comment": "// MARK: comment",
"↓// MARKL:": "// MARK:",
"↓// MARKL: -": "// MARK: -",
"↓// MARKK ": "// MARK: ",
"↓// MARKK -": "// MARK: -",
issue1029Example: issue1029Correction
]
)
private let spaceStartPattern = "(?:\(nonSpaceOrTwoOrMoreSpace)\(mark))"
private let endNonSpacePattern = "(?:\(mark)\(nonSpace))"
private let endTwoOrMoreSpacePattern = "(?:\(mark)\(twoOrMoreSpace))"
private let invalidEndSpacesPattern = "(?:\(mark)\(nonSpaceOrTwoOrMoreSpace))"
private let twoOrMoreSpacesAfterHyphenPattern = "(?:\(mark) -\(twoOrMoreSpace))"
private let nonSpaceOrNewlineAfterHyphenPattern = "(?:\(mark) -[^ \n])"
private let invalidSpacesAfterHyphenPattern = "(?:\(mark) -\(nonSpaceOrTwoOrMoreSpaceOrNewline))"
private let invalidLowercasePattern = "(?:// ?[Mm]ark:)"
private let missingColonPattern = "(?:// ?MARK[^:])"
// The below patterns more specifically describe some of the above pattern's failure cases for correction.
private let oneOrMoreSpacesBeforeColonPattern = "(?:// ?MARK +:)"
private let nonWhitespaceBeforeColonPattern = "(?:// ?MARK\\S+:)"
private let nonWhitespaceNorColonBeforeSpacesPattern = "(?:// ?MARK[^\\s:]* +)"
private var pattern: String {
return [
spaceStartPattern,
invalidEndSpacesPattern,
invalidSpacesAfterHyphenPattern,
invalidLowercasePattern,
missingColonPattern
].joined(separator: "|")
}
public func validate(file: File) -> [StyleViolation] {
return violationRanges(in: file, matching: pattern).map {
StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
}
public func correct(file: File) -> [Correction] {
var result = [Correction]()
result.append(contentsOf: correct(file: file,
pattern: spaceStartPattern,
replaceString: "// MARK:"))
result.append(contentsOf: correct(file: file,
pattern: endNonSpacePattern,
replaceString: "// MARK: ",
keepLastChar: true))
result.append(contentsOf: correct(file: file,
pattern: endTwoOrMoreSpacePattern,
replaceString: "// MARK: "))
result.append(contentsOf: correct(file: file,
pattern: twoOrMoreSpacesAfterHyphenPattern,
replaceString: "// MARK: - "))
result.append(contentsOf: correct(file: file,
pattern: nonSpaceOrNewlineAfterHyphenPattern,
replaceString: "// MARK: - ",
keepLastChar: true))
result.append(contentsOf: correct(file: file,
pattern: oneOrMoreSpacesBeforeColonPattern,
replaceString: "// MARK:",
keepLastChar: false))
result.append(contentsOf: correct(file: file,
pattern: nonWhitespaceBeforeColonPattern,
replaceString: "// MARK:",
keepLastChar: false))
result.append(contentsOf: correct(file: file,
pattern: nonWhitespaceNorColonBeforeSpacesPattern,
replaceString: "// MARK: ",
keepLastChar: false))
result.append(contentsOf: correct(file: file,
pattern: invalidLowercasePattern,
replaceString: "// MARK:"))
return result.unique
}
private func correct(file: File,
pattern: String,
replaceString: String,
keepLastChar: Bool = false) -> [Correction] {
let violations = violationRanges(in: file, matching: pattern)
let matches = file.ruleEnabled(violatingRanges: violations, for: self)
if matches.isEmpty { return [] }
var nsstring = file.contents.bridge()
let description = type(of: self).description
var corrections = [Correction]()
for var range in matches.reversed() {
if keepLastChar {
range.length -= 1
}
let location = Location(file: file, characterOffset: range.location)
nsstring = nsstring.replacingCharacters(in: range, with: replaceString).bridge()
corrections.append(Correction(ruleDescription: description, location: location))
}
file.write(nsstring.bridge())
return corrections
}
private func violationRanges(in file: File, matching pattern: String) -> [NSRange] {
let nsstring = file.contents.bridge()
return file.rangesAndTokens(matching: pattern).filter { _, syntaxTokens in
return !syntaxTokens.isEmpty && SyntaxKind(rawValue: syntaxTokens[0].type) == .comment
}.compactMap { range, syntaxTokens in
let identifierRange = nsstring
.byteRangeToNSRange(start: syntaxTokens[0].offset, length: 0)
return identifierRange.map { NSUnionRange($0, range) }
}
}
}
private let issue1029Example = "↓//MARK:- Top-Level bad mark\n" +
"↓//MARK:- Another bad mark\n" +
"struct MarkTest {}\n" +
"↓// MARK:- Bad mark\n" +
"extension MarkTest {}\n"
private let issue1029Correction = "// MARK: - Top-Level bad mark\n" +
"// MARK: - Another bad mark\n" +
"struct MarkTest {}\n" +
"// MARK: - Bad mark\n" +
"extension MarkTest {}\n"