diff --git a/CHANGELOG.md b/CHANGELOG.md index 76222f9e4..d3cae9a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ names to start with an underscore. [JP Simard](https://github.com/jpsim) +* Disable and re-enable rules from within source code comments using + `// swiftlint:disable:$IDENTIFIER` and `// swiftlint:enable:$IDENTIFIER`. + [JP Simard](https://github.com/jpsim) + [#4](https://github.com/realm/SwiftLint/issues/4) + ##### Bug Fixes * None. diff --git a/Source/SwiftLintFramework/File+SwiftLint.swift b/Source/SwiftLintFramework/File+SwiftLint.swift index 59dd517fd..556b95b4e 100644 --- a/Source/SwiftLintFramework/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/File+SwiftLint.swift @@ -9,9 +9,51 @@ import SourceKittenFramework import SwiftXPC -typealias Line = (index: Int, content: String) +public typealias Line = (index: Int, content: String) + +public typealias Region = (startLine: Int, endLine: Int, disabledRules: [String]) + +public enum CommandAction: String { + case Enable = "enable" + case Disable = "disable" +} + +public typealias Command = (CommandAction, String, Int) extension File { + public func regions() -> [Region] { + let nsStringContents = contents as NSString + let commands = matchPattern("swiftlint:(enable|disable):force_cast") + .flatMap { range, syntaxKinds -> Command? in + let scanner = NSScanner(string: nsStringContents.substringWithRange(range)) + scanner.scanString("swiftlint:", intoString: nil) + var actionString: NSString? = nil + scanner.scanUpToString(":", intoString: &actionString) + let start = range.location + if let actionString = actionString as String?, + action = CommandAction(rawValue: actionString), + lineRange = nsStringContents.lineRangeWithByteRange(start: start, length: 0) { + scanner.scanString(":", intoString: nil) + let ruleStart = scanner.string.startIndex.advancedBy(scanner.scanLocation) + let rule = scanner.string.substringFromIndex(ruleStart) + return (action, rule, lineRange.start) + } + return nil + } + let totalNumberOfLines = contents.lines().count + var regions: [Region] = [(1, commands.first?.2 ?? totalNumberOfLines, [])] + var disabledRules = Set() + let commandPairs = zip(commands, Array(commands.dropFirst().map({Optional($0)})) + [nil]) + for (command, nextCommand) in commandPairs { + switch command.0 { + case .Disable: disabledRules.insert(command.1) + case .Enable: disabledRules.remove(command.1) + } + regions.append((command.2, nextCommand?.2 ?? totalNumberOfLines, Array(disabledRules))) + } + return regions + } + public func matchPattern(pattern: String, withSyntaxKinds syntaxKinds: [SyntaxKind]) -> [NSRange] { return matchPattern(pattern).filter { _, kindsInRange in diff --git a/Source/SwiftLintFramework/Linter.swift b/Source/SwiftLintFramework/Linter.swift index fcafcc94e..a6310dc21 100644 --- a/Source/SwiftLintFramework/Linter.swift +++ b/Source/SwiftLintFramework/Linter.swift @@ -16,7 +16,20 @@ public struct Linter { private let rules: [Rule] public var styleViolations: [StyleViolation] { - return rules.flatMap { $0.validateFile(self.file) } + let regions = file.regions() + return rules.flatMap { rule in + return rule.validateFile(self.file).filter { styleViolation in + guard let line = styleViolation.location.line else { + return true + } + guard let violationRegion = regions.filter({ + $0.startLine < line && $0.endLine > line + }).first else { + return true + } + return !violationRegion.disabledRules.contains(rule.identifier) + } + } } public var ruleExamples: [RuleExample] {