Skip to content
New issue

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

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

Already on GitHub? # to your account

Configure SwiftLint via a YAML file #99

Merged
merged 3 commits into from
Aug 28, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
[submodule "Carthage/Checkouts/Result"]
path = Carthage/Checkouts/Result
url = https://github.com/antitypical/Result.git
[submodule "Carthage/Checkouts/YamlSwift"]
path = Carthage/Checkouts/YamlSwift
url = https://github.com/jpsim/YamlSwift.git
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
included:
- Source
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## Master

##### Breaking

* None.

##### Enhancements

* 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.


## 0.1.2: FabricSoftenerRule

##### Breaking
Expand Down
1 change: 1 addition & 0 deletions Cartfile.private
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
github "Carthage/Commandant" "swift-2.0"
github "jspahrsummers/xcconfigs" >= 0.8
github "jpsim/YamlSwift" "master"
1 change: 1 addition & 0 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
github "antitypical/Result" "0.6-beta.1"
github "drmohundro/SWXMLHash" "050154bfc56032f04fcca37464693a17f897200a"
github "jpsim/SwiftXPC" "d32e70f1b35cfa833be85fd40e70401f4190f5b0"
github "jpsim/YamlSwift" "ed060df018b5693a045f207088b95ce15f433065"
github "jspahrsummers/xcconfigs" "0.8.1"
github "Carthage/Commandant" "40f503c33121431dc098adb7c44d9496e3a1de2f"
github "jpsim/SourceKitten" "ace729472170a4ec2996b8f921fc66784e5ef4fe"
1 change: 1 addition & 0 deletions Carthage/Checkouts/YamlSwift
Submodule YamlSwift added at ed060d
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 @@ -36,7 +39,8 @@ as its contents. Like this:
### 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 @@ -62,8 +66,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 = validRuleIdentifiers.joinWithSeparator("\n")
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this print some error about an invalid config?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see this error below

}
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 @@ -37,7 +37,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 @@ -29,7 +29,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