Skip to content

Commit

Permalink
Merge pull request #391 from realm/sh-configuration-structs
Browse files Browse the repository at this point in the history
Configuration Structs
  • Loading branch information
jpsim committed Jan 24, 2016
2 parents fcc61ed + 75f2068 commit e9897c1
Show file tree
Hide file tree
Showing 46 changed files with 591 additions and 175 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
`[String: SourceKitRepresentable]`.
[JP Simard](https://github.com/jpsim)

* `VariableNameMinLengthRule` and `VariableNameMaxLengthRule` have been
removed. `VariableNameRule` now has this functionality.
[Scott Hoyt](https://github.com/scottrhoyt)

* `ViolationLevelRule` has been removed. This functionality is now provided
by `ConfigProviderRule` and `SeverityLevelsConfig`.
[Scott Hoyt](https://github.com/scottrhoyt)

##### Enhancements

* TypeBodyLengthRule now does not count comment or whitespace lines.
Expand All @@ -16,6 +24,20 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#258](https://github.com/realm/SwiftLint/issues/258)

* All `Rule`s are now configurable in at least their severity: `SeverityConfig`
[Scott Hoyt](https://github.com/scottrhoyt)
[#371](https://github.com/realm/SwiftLint/issues/371)
[#130](https://github.com/realm/SwiftLint/issues/130)
[#268](https://github.com/realm/SwiftLint/issues/268)

* `TypeNameRule` and `VariableNameRule` conform to `ConfigProviderRule` using
`NameConfig` to support `min_length`, `max_length`, and `excluded` names.
[Scott Hoyt](https://github.com/scottrhoyt)
[#388](https://github.com/realm/SwiftLint/issues/388)
[#259](https://github.com/realm/SwiftLint/issues/259)
[#191](https://github.com/realm/SwiftLint/issues/191)


##### Bug Fixes

* Fix crash caused by infinite recursion when using nested config files.
Expand Down
63 changes: 41 additions & 22 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,56 @@ those test cases in the unit tests directly. This makes it easier to understand
what rules do by reading their source, and simplifies adding more test cases
over time.

### `ConfigurableRule`
### `ConfigProviderRule`

If your rule supports user-configurable options via `.swiftlint.yml`, you can
accomplish this by conforming to `ConfigurableRule`:

* `init?(config: AnyObject)` will be passed the result of parsing the value
from `.swiftlint.yml` associated with your rule's `identifier` as a key (if
present).
* `config` may be of any type supported by YAML (e.g. `Int`, `String`, `Array`,
`Dictionary`, etc.).
* This initializer must fail if it does not understand the configuration, or
it cannot be fully initialized with the configuration.
* If this initializer fails, your rule will be initialized with its default
values by calling `init()`.

See [VariableNameMinLengthRule](https://github.com/realm/SwiftLint/blob/647371517e57de3499a77781e45f181605b21045/Source/SwiftLintFramework/Rules/VariableNameMinLengthRule.swift)
for an example that supports the following configurations:
accomplish this by conforming to `ConfigProviderRule`. You must provide a
configuration object via the `config` property:

* The object provided must conform to `RuleConfig`.
* There are several provided `RuleConfig`s that cover the common patterns like
configuring violation severity, violation severity levels, and evaluating
names.
* If none of the provided `RuleConfig`s are applicable, you can create one
specifically for your rule.

See [`ForceCastRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/ForceCastRule.swift)
for a rule that allows severity configuration,
[`FileLengthRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/FileLengthRule.swift)
for a rule that has multiple severity levels,
[`VariableNameRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/VariableNameRule.swift)
for a rule that allows name evaluation configuration:

``` yaml
variable_name_min_length: 3
force_cast: warning

variable_name_min_length:
- 3
- 2
file_length:
warning: 800
error: 1200

variable_name_min_length:
warning: 3
error: 2
variable_name:
min_length:
warning: 3
error: 2
max_length: 20
excluded: id
```
If your rule is configurable, but does not fit the pattern of
`ConfigProviderRule`, you can conform directly to `ConfigurableRule`:

* `init(config: AnyObject) throws` will be passed the result of parsing the
value from `.swiftlint.yml` associated with your rule's `identifier` as a key
(if present).
* `config` may be of any type supported by YAML (e.g. `Int`, `String`, `Array`,
`Dictionary`, etc.).
* This initializer must throw if it does not understand the configuration, or
it cannot be fully initialized with the configuration and default values.
* By convention, a failing initializer throws
`ConfigurationError.UnknownConfiguration`
* If this initializer fails, your rule will be initialized with its default
values by calling `init()`.

## Tracking changes

All changes should be made via pull requests on GitHub.
Expand Down
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ disabled_rules: # rule identifiers to exclude from running
- colon
- comma
- control_statement
- force_cast
- ...
enabled_rules: # some rules are only opt-in
- empty_count
- missing_docs
# Find all the available rules by running:
# swiftlint rules
included: # paths to include during linting. `--path` is ignored if present.
Expand All @@ -150,12 +151,38 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Pods
- Source/ExcludedFolder
- Source/ExcludedFile.swift
# parameterized rules can be customized from this configuration file

# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 110
# parameterized rules are first parameterized as a warning level, then error level.
# they can set both implicitly with an array
type_body_length:
- 300 # warning
- 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
min_length: 4 # only warning
max_length: # warning and error
warning: 40
error: 50
excluded: iPhone # excluded via string
variable_name:
min_length: # only min_length
error: 4 # only error
excluded: # excluded via string array
- id
- URL
- GlobalAPIKey
reporter: "csv" # reporter type (xcode, json, csv, checkstyle)
```
Expand Down
5 changes: 3 additions & 2 deletions Source/SwiftLintFramework/Models/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ public struct Configuration: Equatable {
let identifier = rule.description.identifier
if let ConfigurableRuleType = rule as? ConfigurableRule.Type,
ruleConfig = dict?[identifier] {
if let configuredRule = ConfigurableRuleType.init(config: ruleConfig) {
do {
let configuredRule = try ConfigurableRuleType.init(config: ruleConfig)
rules.append(configuredRule)
} else {
} catch {
queuedPrintError("Invalid config for '\(identifier)'. Falling back to default.")
rules.append(rule.init())
}
Expand Down
13 changes: 13 additions & 0 deletions Source/SwiftLintFramework/Models/ConfigurationError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ConfigurationError.swift
// SwiftLint
//
// Created by Scott Hoyt on 1/19/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation

public enum ConfigurationError: ErrorType {
case UnknownConfiguration
}
2 changes: 0 additions & 2 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,4 @@ public let masterRuleList = RuleList( rules: ClosingBraceRule.self,
TypeBodyLengthRule.self,
TypeNameRule.self,
ValidDocsRule.self,
VariableNameMaxLengthRule.self,
VariableNameMinLengthRule.self,
VariableNameRule.self)
33 changes: 9 additions & 24 deletions Source/SwiftLintFramework/Protocols/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,45 +28,30 @@ extension Rule {
}

public protocol ConfigurableRule: Rule {
init?(config: AnyObject)
init(config: AnyObject) throws
func isEqualTo(rule: ConfigurableRule) -> Bool
}

public protocol ViolationLevelRule: ConfigurableRule {
var warning: RuleParameter<Int> { get set }
var error: RuleParameter<Int> { get set }
public protocol ConfigProviderRule: ConfigurableRule {
typealias ConfigType: RuleConfig
var config: ConfigType { get set }
}

public protocol CorrectableRule: Rule {
func correctFile(file: File) -> [Correction]
}

// MARK: - ViolationLevelRule conformance to ConfigurableRule
// MARK: - ConfigProviderRule conformance to Configurable

public extension ViolationLevelRule {
public init?(config: AnyObject) {
public extension ConfigProviderRule {
public init(config: AnyObject) throws {
self.init()
if let config = [Int].arrayOf(config) where !config.isEmpty {
warning = RuleParameter(severity: .Warning, value: config[0])
if config.count > 1 {
error = RuleParameter(severity: .Error, value: config[1])
}
} else if let config = config as? [String: AnyObject] {
if let warningNumber = config["warning"] as? Int {
warning = RuleParameter(severity: .Warning, value: warningNumber)
}
if let errorNumber = config["error"] as? Int {
error = RuleParameter(severity: .Error, value: errorNumber)
}
} else {
return nil
}
try self.config.setConfig(config)
}

public func isEqualTo(rule: ConfigurableRule) -> Bool {
if let rule = rule as? Self {
return warning == rule.warning &&
error == rule.error
return config.isEqualTo(rule.config)
}
return false
}
Expand Down
23 changes: 23 additions & 0 deletions Source/SwiftLintFramework/Protocols/RuleConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// RuleConfig.swift
// SwiftLint
//
// Created by Scott Hoyt on 1/19/16.
// Copyright © 2016 Realm. All rights reserved.
//

import Foundation

public protocol RuleConfig {
mutating func setConfig(config: AnyObject) throws
func isEqualTo(ruleConfig: RuleConfig) -> Bool
}

extension RuleConfig where Self: Equatable {
public func isEqualTo(ruleConfig: RuleConfig) -> Bool {
if let ruleConfig = ruleConfig as? Self {
return self == ruleConfig
}
return false
}
}
7 changes: 5 additions & 2 deletions Source/SwiftLintFramework/Rules/ClosingBraceRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ extension File {
}
}

public struct ClosingBraceRule: CorrectableRule {
public struct ClosingBraceRule: CorrectableRule, ConfigProviderRule {

public var config = SeverityConfig(.Warning)

public init() {}

Expand All @@ -43,7 +45,8 @@ public struct ClosingBraceRule: CorrectableRule {
public func validateFile(file: File) -> [StyleViolation] {
return file.violatingClosingBraceRanges().map {
StyleViolation(ruleDescription: self.dynamicType.description,
location: Location(file: file, characterOffset: $0.location))
severity: config.severity,
location: Location(file: file, characterOffset: $0.location))
}
}

Expand Down
7 changes: 5 additions & 2 deletions Source/SwiftLintFramework/Rules/ColonRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import Foundation
import SourceKittenFramework

public struct ColonRule: CorrectableRule {
public struct ColonRule: CorrectableRule, ConfigProviderRule {

public var config = SeverityConfig(.Warning)

public init() {}

Expand Down Expand Up @@ -85,7 +87,8 @@ public struct ColonRule: CorrectableRule {

return violationRangesInFile(file, withPattern: pattern).flatMap { range in
return StyleViolation(ruleDescription: self.dynamicType.description,
location: Location(file: file, characterOffset: range.location))
severity: config.severity,
location: Location(file: file, characterOffset: range.location))
}
}

Expand Down
7 changes: 5 additions & 2 deletions Source/SwiftLintFramework/Rules/CommaRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import Foundation
import SourceKittenFramework

public struct CommaRule: CorrectableRule {
public struct CommaRule: CorrectableRule, ConfigProviderRule {

public var config = SeverityConfig(.Warning)

public init() {}

Expand Down Expand Up @@ -41,7 +43,8 @@ public struct CommaRule: CorrectableRule {

return file.matchPattern(pattern, excludingSyntaxKinds: excludingKinds).map {
StyleViolation(ruleDescription: self.dynamicType.description,
location: Location(file: file, characterOffset: $0.location))
severity: config.severity,
location: Location(file: file, characterOffset: $0.location))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import Foundation
import SourceKittenFramework

public struct ConditionalBindingCascadeRule: Rule {
public struct ConditionalBindingCascadeRule: ConfigProviderRule {

public var config = SeverityConfig(.Warning)

public init() {}

Expand Down Expand Up @@ -43,7 +45,8 @@ public struct ConditionalBindingCascadeRule: Rule {
!(file.contents as NSString).substringWithRange($0).containsString("where")
}.map {
StyleViolation(ruleDescription: self.dynamicType.description,
location: Location(file: file, characterOffset: $0.location))
severity: config.severity,
location: Location(file: file, characterOffset: $0.location))
}
}
}
5 changes: 4 additions & 1 deletion Source/SwiftLintFramework/Rules/ControlStatementRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import SourceKittenFramework

public struct ControlStatementRule: Rule {
public struct ControlStatementRule: ConfigProviderRule {

public var config = SeverityConfig(.Warning)

public init() {}

Expand Down Expand Up @@ -65,6 +67,7 @@ public struct ControlStatementRule: Rule {
return nil
}
return StyleViolation(ruleDescription: self.dynamicType.description,
severity: self.config.severity,
location: Location(file: file, characterOffset: match.location))
}
}
Expand Down
Loading

0 comments on commit e9897c1

Please sign in to comment.