Support command comment modifiers

addresses #222
This commit is contained in:
JP Simard
2015-12-25 01:06:23 -05:00
parent 68960a0a92
commit 65f992d8fe
4 changed files with 100 additions and 25 deletions
+5
View File
@@ -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.
+22 -12
View File
@@ -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 <rule>`
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 <rule>`
@@ -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)
```
@@ -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()
+60 -4
View File
@@ -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)
]
}
}
}