Skip to content

Commit

Permalink
Configure SwiftLint via a YAML file. Fixes #1 and #3.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpsim committed Aug 24, 2015
1 parent 37af16b commit 551a57f
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
included:
- Source
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
* Improve performance of `TrailingNewlineRule`.
[Keith Smiley](https://github.com/keith)

* Configure SwiftLint via a YAML file:
Supports `disabled_rules`, `included` and `excluded`.
Pass a configuration file path to `--config`, defaults to `.swiftlint.yml`.
[JP Simard](https://github.com/jpsim)
[#3](https://github.com/realm/SwiftLint/issues/3)

##### Bug Fixes

* None.
Expand Down
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ Using [Homebrew](http://brew.sh/)
brew install swiftlint
```

You can also install SwiftLint by downloading `SwiftLint.pkg` from the [latest GitHub release](https://github.com/realm/SwiftLint/releases/latest) and running it.
You can also install SwiftLint by downloading `SwiftLint.pkg` from the
[latest GitHub release](https://github.com/realm/SwiftLint/releases/latest) and
running it.

You can also build from source by cloning this project and running `make install`.
You can also build from source by cloning this project and running
`make install`.

## Usage

Expand All @@ -35,7 +38,8 @@ as its contents.
### Atom

To integrate SwiftLint with [Atom](https://atom.io/) install the
[`linter-swiftlint`](https://atom.io/packages/linter-swiftlint) package from APM.
[`linter-swiftlint`](https://atom.io/packages/linter-swiftlint) package from
APM.

### Command Line

Expand All @@ -61,8 +65,23 @@ encouraged.
The rules that *are* currently implemented are mostly there as a starting point
and are subject to change.

See the [Source/SwiftLintFramework/Rules](Source/SwiftLintFramework/Rules) directory to see the currently
implemented rules.
See the [Source/SwiftLintFramework/Rules](Source/SwiftLintFramework/Rules)
directory to see the currently implemented rules.

### Configuration

Configure SwiftLint by adding a `.swiftlint.yml` file from the directory you'll
run SwiftLint from. The following parameters can be configured:

```yaml
disabled_rules: # rule identifiers to exclude from running
- todo
included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`.
- Source
excluded: # paths to ignore during linting. overridden by `included`.
- Carthage
- Pods
```
## License
Expand Down
103 changes: 103 additions & 0 deletions Source/SwiftLintFramework/Configuration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// Configuration.swift
// SwiftLint
//
// Created by JP Simard on 2015-08-23.
// Copyright (c) 2015 Realm. All rights reserved.
//

import Yaml

extension Yaml {
var arrayOfStrings: [Swift.String]? {
return array?.flatMap { $0.string }
}
}

public struct Configuration {
public let disabledRules: [String] // disabled_rules
public let included: [String] // included
public let excluded: [String] // excluded

public var rules: [Rule] {
return allRules.filter { !disabledRules.contains($0.identifier) }
}

public init?(disabledRules: [String] = [], included: [String] = [], excluded: [String] = []) {
self.disabledRules = disabledRules
self.included = included
self.excluded = excluded

// Validate that all rule identifiers map to a defined rule

let validRuleIdentifiers = allRules.map { $0.identifier }

let ruleSet = Set(disabledRules)
let invalidRules = ruleSet.filter({ !validRuleIdentifiers.contains($0) })
if invalidRules.count > 0 {
for invalidRule in invalidRules {
fputs("config error: '\(invalidRule)' is not a valid rule identifier\n", stderr)
let listOfValidRuleIdentifiers = "\n".join(validRuleIdentifiers)
fputs("Valid rule identifiers:\n\(listOfValidRuleIdentifiers)\n", stderr)
}
return nil
}

// Validate that rule identifiers aren't listed multiple times

if ruleSet.count != disabledRules.count {
let duplicateRules = disabledRules.reduce([String: Int]()) { (var accu, element) in
accu[element] = accu[element]?.successor() ?? 1
return accu
}.filter {
$0.1 > 1
}
for duplicateRule in duplicateRules {
fputs("config error: '\(duplicateRule.0)' is listed \(duplicateRule.1) times\n",
stderr)
}
return nil
}
}

public init?(yaml: String) {
guard let yamlConfig = Yaml.load(yaml).value else {
return nil
}
self.init(
disabledRules: yamlConfig["disabled_rules"].arrayOfStrings ?? [],
included: yamlConfig["included"].arrayOfStrings ?? [],
excluded: yamlConfig["excluded"].arrayOfStrings ?? []
)
}

public init(path: String = ".swiftlint.yml", optional: Bool = true) {
let fullPath = (path as NSString).absolutePathRepresentation()
let failIfRequired = {
if !optional { fatalError("Could not read configuration file at path '\(fullPath)'") }
}
if path.isEmpty {
failIfRequired()
self.init()!
} else {
if !NSFileManager.defaultManager().fileExistsAtPath(fullPath) {
failIfRequired()
self.init()!
return
}
do {
let yamlContents = try NSString(contentsOfFile: fullPath,
encoding: NSUTF8StringEncoding) as String
if let _ = Configuration(yaml: yamlContents) {
print("Loading configuration from '\(path)'")
self.init(yaml: yamlContents)!
} else {
self.init()!
}
} catch {
failIfRequired()
self.init()!
}
}
}
}
22 changes: 3 additions & 19 deletions Source/SwiftLintFramework/Linter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,7 @@ import SourceKittenFramework
public struct Linter {
private let file: File

private let rules: [Rule] = [
LineLengthRule(),
LeadingWhitespaceRule(),
TrailingWhitespaceRule(),
ReturnArrowWhitespaceRule(),
TrailingNewlineRule(),
OperatorFunctionWhitespaceRule(),
ForceCastRule(),
FileLengthRule(),
TodoRule(),
ColonRule(),
TypeNameRule(),
VariableNameRule(),
TypeBodyLengthRule(),
FunctionBodyLengthRule(),
NestingRule(),
ControlStatementRule()
]
private let rules: [Rule]

public var styleViolations: [StyleViolation] {
return rules.flatMap { $0.validateFile(self.file) }
Expand All @@ -45,7 +28,8 @@ public struct Linter {

:param: file File to lint.
*/
public init(file: File) {
public init(file: File, configuration: Configuration = Configuration()!) {
self.file = file
rules = configuration.rules
}
}
19 changes: 19 additions & 0 deletions Source/SwiftLintFramework/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,22 @@ public protocol ParameterizedRule: Rule {
typealias ParameterType
var parameters: [RuleParameter<ParameterType>] { get }
}

public let allRules: [Rule] = [
LineLengthRule(),
LeadingWhitespaceRule(),
TrailingWhitespaceRule(),
ReturnArrowWhitespaceRule(),
TrailingNewlineRule(),
OperatorFunctionWhitespaceRule(),
ForceCastRule(),
FileLengthRule(),
TodoRule(),
ColonRule(),
TypeNameRule(),
VariableNameRule(),
TypeBodyLengthRule(),
FunctionBodyLengthRule(),
NestingRule(),
ControlStatementRule()
]
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/LineLengthRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct LineLengthRule: ParameterizedRule {

public func validateFile(file: File) -> [StyleViolation] {
return file.contents.lines().flatMap { line in
for parameter in self.parameters.reverse() {
for parameter in parameters.reverse() {
if line.content.characters.count > parameter.value {
return StyleViolation(type: .Length,
location: Location(file: file.path, line: line.index),
Expand Down
2 changes: 1 addition & 1 deletion Source/SwiftLintFramework/Rules/NestingRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct NestingRule: ASTRule {
public func validateFile(file: File,
kind: SwiftDeclarationKind,
dictionary: XPCDictionary) -> [StyleViolation] {
return self.validateFile(file, kind: kind, dictionary: dictionary, level: 0)
return validateFile(file, kind: kind, dictionary: dictionary, level: 0)
}

func validateFile(file: File,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct OperatorFunctionWhitespaceRule: Rule {
return StyleViolation(type: .OperatorFunctionWhitespace,
location: Location(file: file, offset: range.location),
severity: .Medium,
reason: self.example.ruleDescription)
reason: example.ruleDescription)
}
}

Expand Down
11 changes: 5 additions & 6 deletions Source/SwiftLintFramework/String+SwiftLint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension String {
}

func countOfTailingCharactersInSet(characterSet: NSCharacterSet) -> Int {
return String(self.characters.reverse()).countOfLeadingCharactersInSet(characterSet)
return String(characters.reverse()).countOfLeadingCharactersInSet(characterSet)
}

public var chomped: String {
Expand All @@ -38,13 +38,12 @@ extension NSString {
var numberOfLines = 0, index = 0, lineRangeStart = 0, previousIndex = 0
while index < length {
numberOfLines++
if index <= range.location {
lineRangeStart = numberOfLines
previousIndex = index
index = NSMaxRange(self.lineRangeForRange(NSRange(location: index, length: 1)))
} else {
if index > range.location {
break
}
lineRangeStart = numberOfLines
previousIndex = index
index = NSMaxRange(lineRangeForRange(NSRange(location: index, length: 1)))
}
return (lineRangeStart, range.location - previousIndex + 1)
}
Expand Down
50 changes: 50 additions & 0 deletions Source/SwiftLintFrameworkTests/ConfigurationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// ConfigurationTests.swift
// SwiftLint
//
// Created by JP Simard on 8/23/15.
// Copyright © 2015 Realm. All rights reserved.
//

import SwiftLintFramework
import XCTest

class ConfigurationTests: XCTestCase {
func testInit() {
XCTAssert(Configuration(yaml: "") != nil,
"initializing Configuration with empty YAML string should succeed")
XCTAssert(Configuration(yaml: "a: 1\nb: 2") != nil,
"initializing Configuration with valid YAML string should succeed")
XCTAssert(Configuration(yaml: "|\na") == nil,
"initializing Configuration with invalid YAML string should fail")
}

func testEmptyConfiguration() {
guard let config = Configuration(yaml: "") else {
XCTFail("empty YAML string should yield non-nil Configuration")
return
}
XCTAssertEqual(config.disabledRules, [])
XCTAssertEqual(config.included, [])
XCTAssertEqual(config.excluded, [])
}

func testDisabledRules() {
XCTAssert(Configuration(yaml: "disabled_rules:\n - a") == nil,
"initializing Configuration with invalid rules in YAML string should fail")
let disabledConfig = Configuration(yaml: "disabled_rules:\n - nesting\n - todo")!
XCTAssertEqual(disabledConfig.disabledRules,
["nesting", "todo"],
"initializing Configuration with valid rules in YAML string should succeed")
let expectedIdentifiers = allRules
.map({ $0.identifier })
.filter({ !["nesting", "todo"].contains($0) })
let configuredIdentifiers = disabledConfig.rules.map({ $0.identifier })
XCTAssertEqual(expectedIdentifiers, configuredIdentifiers)

// Duplicate
let duplicateConfig = Configuration( yaml: "disabled_rules:\n - todo\n - todo")
XCTAssert(duplicateConfig == nil, "initializing Configuration with duplicate rules in " +
" YAML string should fail")
}
}
21 changes: 0 additions & 21 deletions Source/swiftlint/Components.plist
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,6 @@
<false/>
<key>BundleOverwriteAction</key>
<string>upgrade</string>
<key>ChildBundles</key>
<array>
<dict>
<key>BundleOverwriteAction</key>
<string></string>
<key>RootRelativeBundlePath</key>
<string>Library/Frameworks/SwiftLintFramework.framework/Versions/A/Frameworks/SourceKittenFramework.framework</string>
</dict>
<dict>
<key>BundleOverwriteAction</key>
<string></string>
<key>RootRelativeBundlePath</key>
<string>Library/Frameworks/SwiftLintFramework.framework/Versions/A/Frameworks/LlamaKit.framework</string>
</dict>
<dict>
<key>BundleOverwriteAction</key>
<string></string>
<key>RootRelativeBundlePath</key>
<string>Library/Frameworks/SwiftLintFramework.framework/Versions/A/Frameworks/Commandant.framework</string>
</dict>
</array>
<key>RootRelativeBundlePath</key>
<string>Library/Frameworks/SwiftLintFramework.framework</string>
</dict>
Expand Down
Loading

0 comments on commit 551a57f

Please sign in to comment.