diff --git a/CHANGELOG.md b/CHANGELOG.md index 858b7f8454..fbc55ce21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ [JP Simard](https://github.com/jpsim) [#277](https://github.com/realm/SwiftLint/issues/277) +* Support command comment modifiers (`previous`, `this` & `next`) to limit the + command's scope to a single line. + [JP Simard](https://github.com/jpsim) + [#222](https://github.com/realm/SwiftLint/issues/222) + ##### Bug Fixes * Fix multibyte handling in many rules. diff --git a/README.md b/README.md index 392e64cc18..e578482d88 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,13 @@ directory to see the currently implemented rules. ### Disable a rule in code -Rules can be disabled with a comment inside a source file with the following format: +Rules can be disabled with a comment inside a source file with the following +format: `// swiftlint:disable ` -The rule will be disabled until the end of the file or until the linter sees a matching enable comment: +The rule will be disabled until the end of the file or until the linter sees a +matching enable comment: `// swiftlint:enable ` @@ -93,10 +95,26 @@ For example: // swiftlint:disable colon let noWarning :String = "" // No warning about colons immediately after variable names! // swiftlint:enable colon -let yesWarning :String = "" // Warning generated about colons immediately after variable names +let hasWarning :String = "" // Warning generated about colons immediately after variable names ``` -Run `swiftlint rules` to print a list of all available rules and their identifiers. +It's also possible to modify a disable or enable command by appending +`:previous`, `:this` or `:next` for only applying the command to the previous, +this (current) or next line respectively. + +For example: + +```swift +// swiftlint:disable:next force_cast +let noWarning = NSNumber() as! Int +let hasWarning = NSNumber() as! Int +let noWarning2 = NSNumber() as! Int // swiftlint:disable:this force_cast +let noWarning3 = NSNumber() as! Int +// swiftlint:disable:previous force_cast +``` + +Run `swiftlint rules` to print a list of all available rules and their +identifiers. ### Configuration @@ -125,14 +143,6 @@ line_length: 110 type_body_length: - 300 # warning - 400 # error -# parameterized rules are first parameterized as a warning level, then error level. -variable_name_max_length: - - 40 # warning - - 60 # error -# parameterized rules are first parameterized as a warning level, then error level. -variable_name_min_length: - - 3 # warning - - 2 # error reporter: "csv" # reporter type (xcode, json, csv, checkstyle) ``` diff --git a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift index 730f34a733..a140b8f1a2 100644 --- a/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/File+SwiftLint.swift @@ -11,18 +11,22 @@ import SourceKittenFramework import SwiftXPC internal func regex(pattern: String) -> NSRegularExpression { - // all patterns used for regular expressions in SwiftLint are string literals which have - // been confirmed to work, so it's ok to force-try here. - // swiftlint:disable force_try + // all patterns used for regular expressions in SwiftLint are string literals which have been + // confirmed to work, so it's ok to force-try here. + + // swiftlint:disable:next force_try return try! NSRegularExpression(pattern: pattern, options: [.AnchorsMatchLines]) - // swiftlint:enable force_try } extension File { public func regions() -> [Region] { let contents = self.contents as NSString - let commands = matchPattern("swiftlint:(enable|disable)\\ [^\\s]+", - withSyntaxKinds: [.Comment]).flatMap { Command(string: contents, range: $0) } + let commands = matchPattern("swiftlint:(enable|disable)(:previous|:this|:next)?\\ [^\\s]+", + withSyntaxKinds: [.Comment]).flatMap { range in + return Command(string: contents, range: range) + }.flatMap { command in + return command.expand() + } let totalNumberOfLines = lines.count let numberOfCharactersInLastLine = lines.last?.content.characters.count var regions = [Region]() @@ -115,12 +119,12 @@ extension File { } public func write(string: String) { - guard let stringData = string.dataUsingEncoding(NSUTF8StringEncoding) else { - fatalError("can't encode '\(string)' with UTF8") - } guard let path = path else { fatalError("file needs a path to call write(_:)") } + guard let stringData = string.dataUsingEncoding(NSUTF8StringEncoding) else { + fatalError("can't encode '\(string)' with UTF8") + } stringData.writeToFile(path, atomically: true) contents = string lines = contents.lines() diff --git a/Source/SwiftLintFramework/Models/Command.swift b/Source/SwiftLintFramework/Models/Command.swift index 889ec13b52..3e64fcdf71 100644 --- a/Source/SwiftLintFramework/Models/Command.swift +++ b/Source/SwiftLintFramework/Models/Command.swift @@ -11,6 +11,19 @@ import Foundation public enum CommandAction: String { case Enable = "enable" case Disable = "disable" + + private func inverse() -> CommandAction { + switch self { + case .Enable: return .Disable + case .Disable: return .Enable + } + } +} + +public enum CommandModifier: String { + case Previous = "previous" + case This = "this" + case Next = "next" } public struct Command { @@ -18,28 +31,71 @@ public struct Command { let ruleIdentifier: String let line: Int let character: Int + let modifier: CommandModifier? - public init(action: CommandAction, ruleIdentifier: String, line: Int, character: Int) { + public init(action: CommandAction, ruleIdentifier: String, line: Int = 0, character: Int = 0, + modifier: CommandModifier? = nil) { self.action = action self.ruleIdentifier = ruleIdentifier self.line = line self.character = character + self.modifier = modifier } public init?(string: NSString, range: NSRange) { let scanner = NSScanner(string: string.substringWithRange(range)) scanner.scanString("swiftlint:", intoString: nil) + var optionalActionAndModifierNSString: NSString? = nil + scanner.scanUpToString(" ", intoString: &optionalActionAndModifierNSString) + guard let actionAndModifierString = optionalActionAndModifierNSString as String? else { + return nil + } + let actionAndModifierScanner = NSScanner(string: actionAndModifierString) var actionNSString: NSString? = nil - scanner.scanUpToString(" ", intoString: &actionNSString) + actionAndModifierScanner.scanUpToString(":", + intoString: &actionNSString) guard let actionString = actionNSString as String?, action = CommandAction(rawValue: actionString), lineAndCharacter = string.lineAndCharacterForCharacterOffset(NSMaxRange(range)) else { return nil } self.action = action - let ruleStart = scanner.string.startIndex.advancedBy(scanner.scanLocation + 1) - ruleIdentifier = scanner.string.substringFromIndex(ruleStart) + ruleIdentifier = (scanner.string as NSString).substringFromIndex(scanner.scanLocation + 1) line = lineAndCharacter.line character = lineAndCharacter.character + + let hasModifier = actionAndModifierScanner.scanString(":", intoString: nil) + + // Modifier + if hasModifier { + let modifierString = (actionAndModifierScanner.string as NSString) + .substringFromIndex(actionAndModifierScanner.scanLocation) + modifier = CommandModifier(rawValue: modifierString) + } else { + modifier = nil + } + } + + internal func expand() -> [Command] { + guard let modifier = modifier else { + return [self] + } + switch modifier { + case .Previous: + return [ + Command(action: action, ruleIdentifier: ruleIdentifier, line: line - 1), + Command(action: action.inverse(), ruleIdentifier: ruleIdentifier, line: line) + ] + case .This: + return [ + Command(action: action, ruleIdentifier: ruleIdentifier, line: line), + Command(action: action.inverse(), ruleIdentifier: ruleIdentifier, line: line + 1) + ] + case .Next: + return [ + Command(action: action, ruleIdentifier: ruleIdentifier, line: line + 1), + Command(action: action.inverse(), ruleIdentifier: ruleIdentifier, line: line + 2) + ] + } } }