Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable/re-enable rules from within source code comments. Fixes #4. #111

Merged
merged 6 commits into from
Sep 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 45 additions & 0 deletions Source/SwiftLintFramework/Command.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Command.swift
// SwiftLint
//
// Created by JP Simard on 8/29/15.
// Copyright © 2015 Realm. All rights reserved.
//

import Foundation

public enum CommandAction: String {
case Enable = "enable"
case Disable = "disable"
}

public struct Command {
let action: CommandAction
let ruleIdentifier: String
let line: Int
let character: Int

public init(action: CommandAction, ruleIdentifier: String, line: Int, character: Int) {
self.action = action
self.ruleIdentifier = ruleIdentifier
self.line = line
self.character = character
}

public init?(string: NSString, range: NSRange) {
let scanner = NSScanner(string: string.substringWithRange(range))
scanner.scanString("swiftlint:", intoString: nil)
var actionNSString: NSString? = nil
scanner.scanUpToString(" ", intoString: &actionNSString)
guard let actionString = actionNSString as String?,
action = CommandAction(rawValue: actionString),
lineAndCharacter = string.lineAndCharacterForByteOffset(NSMaxRange(range)) else {
return nil
}
self.action = action
let ruleStart = scanner.string.startIndex.advancedBy(scanner.scanLocation + 1)
ruleIdentifier = scanner.string.substringFromIndex(ruleStart)
line = lineAndCharacter.line
character = lineAndCharacter.character
}
}
41 changes: 39 additions & 2 deletions Source/SwiftLintFramework/File+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,44 @@
import SourceKittenFramework
import SwiftXPC

typealias Line = (index: Int, content: String)
internal typealias Line = (index: Int, content: String)

extension File {
public func regions() -> [Region] {
let nsStringContents = contents as NSString
let commands = matchPattern("swiftlint:(enable|disable)\\ [^\\s]+",
withSyntaxKinds: [.Comment]).flatMap { Command(string: nsStringContents, range: $0) }
let lines = contents.lines()
let totalNumberOfLines = lines.count
let numberOfCharactersInLastLine = lines.last?.content.characters.count
let firstRegion = Region(start:
Location(file: path, line: 1, character: 0),
end: Location(file: path,
line: commands.first?.line ?? totalNumberOfLines,
character: commands.first?.character ?? numberOfCharactersInLastLine),
disabledRuleIdentifiers: [])
var regions = [firstRegion]
var disabledRules = Set<String>()
let commandPairs = zip(commands, Array(commands.dropFirst().map({Optional($0)})) + [nil])
for (command, nextCommand) in commandPairs {
switch command.action {
case .Disable: disabledRules.insert(command.ruleIdentifier)
case .Enable: disabledRules.remove(command.ruleIdentifier)
}
regions.append(
Region(
start: Location(file: path,
line: command.line,
character: command.character),
end: Location(file: path,
line: nextCommand?.line ?? totalNumberOfLines,
character: nextCommand?.character ?? numberOfCharactersInLastLine),
disabledRuleIdentifiers: disabledRules)
)
}
return regions
}

public func matchPattern(pattern: String,
withSyntaxKinds syntaxKinds: [SyntaxKind]) -> [NSRange] {
return matchPattern(pattern).filter { _, kindsInRange in
Expand All @@ -27,7 +62,9 @@ extension File {
let matches = regex.matchesInString(contents, options: [], range: range)
return matches.map { match in
let tokensInRange = syntax.tokens.filter {
NSLocationInRange($0.offset, match.range)
NSLocationInRange($0.offset, match.range) ||
NSLocationInRange(match.range.location,
NSRange(location: $0.offset, length: $0.length))
}
let kindsInRange = tokensInRange.flatMap {
SyntaxKind(rawValue: $0.type)
Expand Down
12 changes: 11 additions & 1 deletion Source/SwiftLintFramework/Linter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,17 @@ 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 violationRegion = regions.filter({
$0.start <= styleViolation.location && $0.end >= styleViolation.location
}).first else {
return true
}
return !violationRegion.disabledRuleIdentifiers.contains(rule.identifier)
}
}
}

public var ruleExamples: [RuleExample] {
Expand Down
22 changes: 12 additions & 10 deletions Source/SwiftLintFramework/Location.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import SourceKittenFramework

public struct Location: CustomStringConvertible, Equatable {
public struct Location: CustomStringConvertible, Comparable {
public let file: String?
public let line: Int?
public let character: Int?
Expand Down Expand Up @@ -38,18 +38,20 @@ public struct Location: CustomStringConvertible, Equatable {
}
}

// MARK: Equatable
// MARK: Comparable

/**
Returns true if `lhs` Location is equal to `rhs` Location.

:param: lhs Location to compare to `rhs`.
:param: rhs Location to compare to `lhs`.

:returns: True if `lhs` Location is equal to `rhs` Location.
*/
public func == (lhs: Location, rhs: Location) -> Bool {
return lhs.file == rhs.file &&
lhs.line == rhs.line &&
lhs.character == rhs.character
}

public func < (lhs: Location, rhs: Location) -> Bool {
if lhs.file != rhs.file {
return lhs.file < rhs.file
}
if lhs.line != rhs.line {
return lhs.line < rhs.line
}
return lhs.character < rhs.character
}
22 changes: 22 additions & 0 deletions Source/SwiftLintFramework/Region.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Region.swift
// SwiftLint
//
// Created by JP Simard on 8/29/15.
// Copyright © 2015 Realm. All rights reserved.
//

import Foundation
import SourceKittenFramework

public struct Region {
let start: Location
let end: Location
let disabledRuleIdentifiers: Set<String>

public init(start: Location, end: Location, disabledRuleIdentifiers: Set<String>) {
self.start = start
self.end = end
self.disabledRuleIdentifiers = disabledRuleIdentifiers
}
}
8 changes: 4 additions & 4 deletions Source/SwiftLintFrameworkTests/ASTRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,18 @@ class ASTRuleTests: XCTestCase {
}

func testTypeNamesVerifyRule() {
verifyRule(TypeNameRule().example, type: .NameFormat)
verifyRule(TypeNameRule(), type: .NameFormat)
}

func testVariableNamesVerifyRule() {
verifyRule(VariableNameRule().example, type: .NameFormat)
verifyRule(VariableNameRule(), type: .NameFormat)
}

func testNesting() {
verifyRule(NestingRule().example, type: .Nesting, commentDoesntViolate: false)
verifyRule(NestingRule(), type: .Nesting, commentDoesntViolate: false)
}

func testControlStatements() {
verifyRule(ControlStatementRule().example, type: .ControlStatement)
verifyRule(ControlStatementRule(), type: .ControlStatement)
}
}
14 changes: 7 additions & 7 deletions Source/SwiftLintFrameworkTests/StringRuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,35 +62,35 @@ class StringRuleTests: XCTestCase {
}

func testFileShouldntStartWithWhitespace() {
verifyRule(LeadingWhitespaceRule().example,
verifyRule(LeadingWhitespaceRule(),
type: .LeadingWhitespace,
commentDoesntViolate: false)
}

func testLinesShouldntContainTrailingWhitespace() {
verifyRule(TrailingWhitespaceRule().example,
verifyRule(TrailingWhitespaceRule(),
type: .TrailingWhitespace,
commentDoesntViolate: false)
}

func testLinesShouldContainReturnArrowWhitespace() {
verifyRule(ReturnArrowWhitespaceRule().example,
verifyRule(ReturnArrowWhitespaceRule(),
type: .ReturnArrowWhitespace)
}

func testForceCasting() {
verifyRule(ForceCastRule().example, type: .ForceCast)
verifyRule(ForceCastRule(), type: .ForceCast)
}

func testOperatorFunctionWhitespace() {
verifyRule(OperatorFunctionWhitespaceRule().example, type: .OperatorFunctionWhitespace)
verifyRule(OperatorFunctionWhitespaceRule(), type: .OperatorFunctionWhitespace)
}

func testTodoOrFIXME() {
verifyRule(TodoRule().example, type: .TODO)
verifyRule(TodoRule(), type: .TODO, commentDoesntViolate: false)
}

func testColon() {
verifyRule(ColonRule().example, type: .Colon)
verifyRule(ColonRule(), type: .Colon)
}
}
14 changes: 9 additions & 5 deletions Source/SwiftLintFrameworkTests/TestHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ private func violations(string: String, _ type: StyleViolationType) -> [StyleVio
}

extension XCTestCase {
func verifyRule(rule: RuleExample,
func verifyRule(rule: Rule,
type: StyleViolationType,
commentDoesntViolate: Bool = true) {
XCTAssertEqual(rule.nonTriggeringExamples.flatMap({violations($0, type)}), [])
XCTAssertEqual(rule.triggeringExamples.flatMap({violations($0, type).map({$0.type})}),
Array(count: rule.triggeringExamples.count, repeatedValue: type))
let example = rule.example
XCTAssertEqual(example.nonTriggeringExamples.flatMap({violations($0, type)}), [])
XCTAssertEqual(example.triggeringExamples.flatMap({violations($0, type).map({$0.type})}),
Array(count: example.triggeringExamples.count, repeatedValue: type))

if commentDoesntViolate {
XCTAssertEqual(rule.triggeringExamples.flatMap({violations("// " + $0, type)}), [])
XCTAssertEqual(example.triggeringExamples.flatMap({violations("// " + $0, type)}), [])
}

let command = "// swiftlint:disable \(rule.identifier)\n"
XCTAssertEqual(example.triggeringExamples.flatMap({violations(command + $0, type)}), [])
}
}
Loading