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

Configuration Structs #391

Merged
merged 52 commits into from
Jan 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d300eec
Added ConfigurationProviderRule
scottrhoyt Jan 20, 2016
21abbd3
Added ViolationLevelConfiguration.
scottrhoyt Jan 20, 2016
ccbcb53
Convert ConfigurableRule to throwing init instead of failing.
scottrhoyt Jan 20, 2016
1a2bb1b
Add MinMaxConfiguration.
scottrhoyt Jan 20, 2016
619a345
Fix spacing.
scottrhoyt Jan 20, 2016
d2ec3fb
Added associated type for ConfigurationProviderRule.
scottrhoyt Jan 20, 2016
6522add
TypeNameRule conforms to ConfigurationProviderRule.
scottrhoyt Jan 20, 2016
b92c241
Fixed tests for throwing ConfigurableRule init.
scottrhoyt Jan 20, 2016
4ad287d
Fixed TypeNameRule to match previous definition.
scottrhoyt Jan 20, 2016
6d38573
Delete unused ViolationLevelConfiguration protocol.
scottrhoyt Jan 20, 2016
0883f56
Rename Rule Configuration structures.
scottrhoyt Jan 20, 2016
0b61c63
Extracted Rule Configuration structs to own files.
scottrhoyt Jan 20, 2016
05a65af
Removed ViolationLeveLRule in favor of ConfigurationProviderRule.
scottrhoyt Jan 20, 2016
873edb7
Provided convenience property params for RuleLevelsConfig.
scottrhoyt Jan 20, 2016
ddcde17
Add min and max length checking responsibilities to VariableNameRule.
scottrhoyt Jan 20, 2016
ee3fe42
Remove old VariableName*LengthRules.
scottrhoyt Jan 20, 2016
84aa479
Fixed description for VariableNameRule to reflect new responsibility.
scottrhoyt Jan 20, 2016
0691b79
Rename Rule Configurations.
scottrhoyt Jan 20, 2016
d2c697d
Rename dictionary keys for length config.
scottrhoyt Jan 20, 2016
810f813
Add min/max thresholds to MinMaxLengthConfig for convenience.
scottrhoyt Jan 20, 2016
d137dcd
Remove VariableNameRuleConfig in leu of adding excluded to MinMaxLeng…
scottrhoyt Jan 20, 2016
bf5a569
Renamed MinMaxLengthConfig to NameConfig to reflect combined responsi…
scottrhoyt Jan 20, 2016
20e8038
Changed min/max names to reflect that they belong to name length.
scottrhoyt Jan 20, 2016
53e3057
Extracted violationSeverity to NameConfig.
scottrhoyt Jan 20, 2016
a64f9ed
SeverityLevelConfig stores levels as Ints now.
scottrhoyt Jan 20, 2016
421aa6e
Added tests for NameConfig.
scottrhoyt Jan 20, 2016
3230c64
Replaced things in rule configurations named configuration for config…
scottrhoyt Jan 20, 2016
60546e4
Renamed violationSeverity to severity for brevity.
scottrhoyt Jan 20, 2016
1f13e16
Renamed return nil to return .None for semantics.
scottrhoyt Jan 20, 2016
cd5f457
Switched to Set for excluded for performance and semantics.
scottrhoyt Jan 21, 2016
8ff1562
Added exclusions to TypeNameRule.
scottrhoyt Jan 21, 2016
989bac7
Renamed SeverityLevelConfig to SeverityLevelsConfig.
scottrhoyt Jan 21, 2016
7af4756
Added SeverityConfig and tests.
scottrhoyt Jan 21, 2016
f722bcf
All Rules (with the exception of MissingDocs) now provide some severi…
scottrhoyt Jan 21, 2016
943ed30
Modified names for brevity.
scottrhoyt Jan 21, 2016
885b47d
Extract severity function to ConfigProviderRule extension for better …
scottrhoyt Jan 21, 2016
284d4e1
Extracted ViolationSeverity initialization via unnormalized String to…
scottrhoyt Jan 21, 2016
123d06e
Switch VariableNameRule to use a guard for early exit if name is excl…
scottrhoyt Jan 21, 2016
6132d5d
Provide default implementation for RuleConfig.isEqualTo when it is Eq…
scottrhoyt Jan 21, 2016
42a6e9f
Use guard in NameConfig.setConfig for better semantics.
scottrhoyt Jan 21, 2016
9f20fc2
Switch SeverityConfig.setConfig to guard for better semantics.
scottrhoyt Jan 21, 2016
95a7352
Remove unnecessary Swift namespace for min and max calls.
scottrhoyt Jan 23, 2016
e2265cf
Removed ViolationSeverity extension. Moved into a private function of…
scottrhoyt Jan 23, 2016
6107822
Make ConfigurationError marginally more useful by not squashing it in…
scottrhoyt Jan 23, 2016
b145bf1
Reset VariableNameRule to instantiating Location at the call site.
scottrhoyt Jan 23, 2016
87c5342
Update README.
scottrhoyt Jan 23, 2016
d1574bb
Update CHANGELOG
scottrhoyt Jan 23, 2016
53218ec
Update CONTRIBUTING
scottrhoyt Jan 23, 2016
6718de4
Fixed typo in CONTRIBUTING
scottrhoyt Jan 24, 2016
44ff8ef
Reset VariableNameRule to if check on return as opposed to guard.
scottrhoyt Jan 24, 2016
12aba5f
Rebased. Fixed new conflicts.
scottrhoyt Jan 24, 2016
75f2068
Switch to if/early return for VariableNameRule.
scottrhoyt Jan 24, 2016
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
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why introduce this if there's only one member, and it's never checked? ConfigurableRuleType.init(config:) is only ever used with try?, which type-erases the error in a sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was something I thought about for a bit. Basically what was the best way to support configuration structures failing and provide optional configuration values functionality. To use the failable initializers approach that was previously used in ConfigurableRule would have required each rule to have its own RuleConfig type because you could only declare the default values in the structure. The second approach was to split initialization with default values and configuration with optional values into init and setConfig. Since we needed a way to indicate failure in setConfig, we could either throw or return some sort of success/failure flag (e.g. Bool, Result, etc.). Throwing felt the most Swifty to me, and I needed something to throw, so that's how this came to be.

There's certainly a case to be made that if we use this approach, we should introduce some finer-grain errors, possibly merge this with YamlParsingError, and probably change Configuration.init? to Configuration.init() throws. That's all possible in this PR, but in my head I was scoping it to the rework that needs to be done on logging and errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the time being, I made this marginally more useful by not squashing the error in Configuration.rulesFromDict, though nothing is really being done with it. Let me know if you want to combine this with YamlParserError. I like that we have one less error type with that approach. But I don't like that a ConfigProviderRule would even know about something that knows about the Yaml subsystem.

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
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It can use:

extension RuleConfig where Self: Equatable {
    public func isEqualTo(ruleConfig: RuleConfig) -> Bool {
        if let ruleConfig = ruleConfig as? Self {
            return self == ruleConfig
        }
        return false
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great add.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍


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