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

Configuration Structs #391

merged 52 commits into from
Jan 24, 2016

Conversation

scottrhoyt
Copy link
Contributor

Configuration Structs

This PR expands upon the work in #314 and #354 by providing struct-based configurations for the common patterns and providing default conformance to ConfigurableRule via these structures.

Key Benefits

  • All Rules now have some sort of configuration available.
    • Basic rules are now configurable in their violation severity. (SeverityConfig)
    • Rules with multiple severity levels are configurable in one or both levels. (SeverityLevelsConfig)
    • Name evaluation rules are configurable in their min_length and max_length (both warning and error) as well as with excluded names. (NameConfig)
  • Rule configurations can be partly or fully customized.
  • Performance has been increased by ~2% (vs master) by folding VariableNameMinLengthRule and VariableNameMaxLengthRule into VariableNameRule.
  • Test coverage has been increased.

Key Drawbacks

  • This introduces some additional verbosity because ConfigProviderRules need to access their configuration via config and it's properties. This can be somewhat mitigated with more convenience computed properties.

Example YAML

The following configuration is now possible.

force_cast: Warning      # supports severity config case-insensitive
todo:
  severity: error        # severity can be configure key/value as well
line_length: 120         # only warning configured
file_length:
  warning: 500
  error: 1200
function_body_length:    # warning and error via array still supported
  - 40
  - 60
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

Open Issues Addressed

Design Decisions

  • Used structs for RuleConfigs to encapsulate configuration via Dictionarys and also promote maximum reusability (e.g. NameConfig uses two SeverityLevelsConfigs).
  • Left ConfigurableRule in place as a catch-all for non-standard configs (e.g. MissingDocsRule)
  • Used RuleConfig.setConfig(config:) throws instead of a failable initializer for ingesting configuration dictionaries. This is to support the coexistence of default values and configured values so that partial configurations can be provided via the YAML.
  • Switched ConfigurableRule.init?(config:) to ConfigurableRule.init(config:) throwsto gracefully propograte these ConfigurationErrors.

TODO

  • Update README
  • Update CHANGELOG
  • Update CONTRIBUTING

/cc @realm/swiftlint

@scottrhoyt scottrhoyt changed the title Configuration Structure Configuration Structs Jan 21, 2016
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.

👍

@norio-nomura
Copy link
Collaborator

Great!

@scottrhoyt
Copy link
Contributor Author

This might be a good time to add ViolationSeverity.Note so that it is an option for severity configuration. @jpsim , if I just add the enum case, will that be enough to support it for XcodeReporter?

UPDATE

I played around with notes, and it appears that while Xcode recognizes them as a special case, it does note display them anywhere in the GUI.

@jpsim
Copy link
Collaborator

jpsim commented Jan 21, 2016

Awesome! At first glance, I like this direction, but I might not be able to review in depth for a few days. I'm certainly happy to see this address so many issues users have faced regarding configuration!

@scottrhoyt scottrhoyt mentioned this pull request Jan 21, 2016

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.

@jpsim
Copy link
Collaborator

jpsim commented Jan 23, 2016

This is pretty awesome! I'm happy with almost all of this, other than where noted, and the remaining checklist items from the original PR comment.

It shouldn't be too much work to rebase this on master for now, since it's only conflicting with #377 AFAICT, but #397 will probably cause much more rebase work, so we should probably merge this one first.

@scottrhoyt
Copy link
Contributor Author

Great. I'll make those changes today so we can get this in.

@scottrhoyt scottrhoyt force-pushed the sh-configuration-structs branch from c921a34 to 9a77974 Compare January 23, 2016 20:46
@scottrhoyt
Copy link
Contributor Author

@jpsim take a look at this. Only outstanding issue is the use ConfigurationError. Let me know what you think about README and CONTRIBUTING. Is the provided info on customizing through .swiftlint.yml and contributing a ConfigProviderRule clear enough?

@scottrhoyt
Copy link
Contributor Author

FWIW, unit test took ~4 seconds before the rebase and ~16 seconds after on my computer. Probably related to the work with not counting comments and whitespace lines, but the tests don't feel like unit tests anymore.

Update

Just seeing #401 addresses this now.

* If none of the provided `RuleConfig`s are applicable, you can create one
specifically for your rule.

See [`ForceTryRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/ForceCastRule.swift)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mentions ForceTryRule but links to ForceCastRule.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

@jpsim
Copy link
Collaborator

jpsim commented Jan 23, 2016

Only outstanding issue is the use ConfigurationError.

Actually your explanation makes sense: throws propagates errors, and better to throw an error in a domain under our control than a random NSError. 👍

Let me know what you think about README and CONTRIBUTING. Is the provided info on customizing through .swiftlint.yml and contributing a ConfigProviderRule clear enough?

Very useful and informative 👍 Thanks for adding the opt-in segment too!

Other than my last few comments, big 👍 on this!

@scottrhoyt scottrhoyt force-pushed the sh-configuration-structs branch from 4d9f367 to 12aba5f Compare January 24, 2016 01:48
@scottrhoyt
Copy link
Contributor Author

@jpsim I think we are all good here now.

@jpsim
Copy link
Collaborator

jpsim commented Jan 24, 2016

👍 💯 kudos. Can you make sure to close all the issues resolved by this?

jpsim added a commit that referenced this pull request Jan 24, 2016
@jpsim jpsim merged commit e9897c1 into master Jan 24, 2016
@jpsim jpsim deleted the sh-configuration-structs branch January 24, 2016 02:35
@scottrhoyt
Copy link
Contributor Author

No problem. The 3 that are 100% addressed have been closed. What do you want to do with the 3 that this provided a solid workaround for?

@jpsim
Copy link
Collaborator

jpsim commented Jan 24, 2016

I think this PR is the best way to address #191, despite not being exactly what the user wanted, so I'd close that one as resolved. The other 2 can remain open.

@norio-nomura
Copy link
Collaborator

Wow! 🎉

@tamarnachmany
Copy link

🍕

@derpoliuk
Copy link

Thanks for PR!

Is there anyway to set severity for all rules? I want to make sure that build fails if any SwiftLint rule is violated.

@ikesyo
Copy link
Contributor

ikesyo commented Jul 6, 2016

@derpoliuk What you want would be the --strict option of lint command.

[--strict]
    fail on warnings

@derpoliuk
Copy link

@ikesyo thanks for very quick answer!

I know about --strict option (should've addressed it in my first comment), but it doesn't provide pretty output (as discussed in #268). Though it's a working solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants