Skip to content

Commit

Permalink
Support command comment modifiers
Browse files Browse the repository at this point in the history
addresses #222
  • Loading branch information
jpsim committed Dec 28, 2015
1 parent 68960a0 commit 65f992d
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 25 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>`

Expand All @@ -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

Expand Down Expand Up @@ -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)
```
Expand Down
22 changes: 13 additions & 9 deletions Source/SwiftLintFramework/Extensions/File+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand Down Expand Up @@ -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()
Expand Down
64 changes: 60 additions & 4 deletions Source/SwiftLintFramework/Models/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,91 @@ 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 {
let action: CommandAction
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)
]
}
}
}

0 comments on commit 65f992d

Please sign in to comment.