-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Dictionary Parameterization of rules #314
Conversation
Also, if we go this route, |
Thanks for the PR, it will take some time to review 😬 |
No problem. Just post any questions you have about implementation details or design decisions, and I'll be happy to discuss. |
Hey @jpsim. I was wondering if you had any thoughts on this yet? I just don't want to let it go too long because of merge conflicts. If you are ready to take another look, I can clean up whatever conflicts have arisen. |
return nil | ||
} | ||
|
||
var flatArray: [AnyObject]? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One liner: var flatArray: [AnyObject]? { return array?.map { $0.flatValue } }
@scottrhoyt I really like this. It makes a lot more sense than our current approach. I like the way you built it too. I've commented a lot of nitpicky stuff as line notes, but taking a step back and looking at the big picture, there's just one thing that jumps out at me for now: can we consolidate the Otherwise, once you've had a chance to rebase against |
Thanks, I'm glad you like it. I think it's a good direction as well. I fixed most of the suggestions and asked for follow up on a couple. Yes, there is now no need for Once I get that clarification on the couple of outstanding edits, I will rebase against |
} | ||
return [] | ||
} ?? [] | ||
} | ||
|
||
public func isEqualTo(rule: ConfigurableRule) -> Bool { | ||
if let rule = rule as? VariableNameMinLengthRule { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good place for a guard
here
Yup, let's remove |
Most of this is cleaned up now. My thinking on removing |
Also, with regards to the rebase, I must confess I haven't rebased much, but I'm familiar with the concept and the intended results of cleaning up this commit history. However, I was pretty sure rebasing after pushing to a remote was a no no. Is that not the case? Or should we use a work around like creating a fresh branch to do the rebase and then pushing to github with that, creating a new PR from there, and deleting this branch? |
I don't want to take up more of your time, so how about you rebase this to address the merge conflicts and I can do a final review pass as-is before we embark on deprecating
Some projects discourage rebasing because it does in a sense rewrite history, but I value legible commit history in SwiftLint enough to prefer rebasing over verbose merge noise. To rebase, first make sure that your local |
Great. I'll get that done later today. Something to put in the backlog after this merge will be a new system for documenting rules: so that we can provide documentation via web and command line for the new expanded configuration options. I suppose that will be contingent on what the standards are for creating |
This is one downside to |
@scottrhoyt let me know if you're too busy to get to this, I don't mind taking care of the rebase. |
…fault implementation for conformance.
0d9c03a
to
55fa348
Compare
|
||
public func isEqualTo(rule: ConfigurableRule) -> Bool { | ||
if let rule = rule as? Self { | ||
return self.parameters == rule.parameters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can omit self
here probably?
@jpsim Incorporated latest feedback and updated CHANGELOG and CONTRIBUTING. Let me know if there is anything else. |
This looks ok to me, but it's a large change, so I'd love to get some feedback from @realm/swiftlint thoughts? |
I don't like that rule configuration parsing is mixed with the validation and correcting of rules. Maybe splitting these concerns would make sense down the line. However it's a good step to making the rules better configurable! 👍 |
Cool, this looks like it's good to merge to me. @nicolai86 feel free to decouple that in a PR, or even just file an issue detailing what you think should be done so it doesn't slip through the cracks. |
Dictionary Parameterization of rules
Great job @scottrhoyt! I know this was a lot of work, and especially dealing with all my nitpicks, but it turned out great 🎉 |
@nicolai86 I completely agree with you on that and would love to see some more separation of responsibility as well as perhaps bringing back some type safety to configurations. I think this can be part of the work of standardizing how rules can be configured, how to handle documenting rule configurations, and providing the boilerplate for common rule configs like On the flip side, I do think this is a necessary first step to open the door to more advanced configurations. I also think it is a step forward, single responsibility-wise, from the relatively monolithic @jpsim no problem! Thanks for your feedback and allowing me to contribute in a big way to a great project. |
This is great. 👍 Thanks for all the work @scottrhoyt |
No problem!
|
Great! 🎉 |
Dictionary Parameterization
Closes #303
Here is my idea on how to implement dictionary parameterization for rules. Sorry it is such a beast of a change (and hard to review), but it was necessary IMO to accomplish the task. There are some pretty big changes in here, so I understand if we need to refine this further or even reject this course.
Merge Notes
I merged my recursive configuration branch into this branch before it was integrated into master. In addition to that I have merged master several times to keep up with the other changes. Somehow this resulted in the recursive config commits being duplicated, though the diff's still look correct. This could be a result of the combination of how I merged and the cherry picking that was done to do the final integration.
Overview
This was my thought process as I developed this:
With the increased complexity in rule configuration, it didn't make sense to have
Configuration
store the knowledge on how to configure each rule anymore. This should be encapsulated in theRule
itself.A new protocol,
ConfigurableRule
, was created to facilitate this.When encapsulating the configuration in
ConfigurableRule
, I didn't want to create a dependency onYaml
, I chose instead forConfigurableRule
to have a failable initializer taking anAnyObject
instead.AnyObject
was chosen over the alternative,[String: AnyObject]
, to support the trivial config cases (e.g.line_length: 100
).I wrote
Yaml
extensions to flatten theYaml.Dictionary
s into[String: AnyObject]
sI then refactored
Configuration
to extract Yaml parsing toYamlParser
I also didn't think it made sense anymore to have
Configuration
store the master rule list, so I extracted this to the globalmasterRuleList
using a rather trivialRuleList
container I created.I wrote protocol extensions for
ParameterizedRule
to make all legacy parameterized rules automatically conform toConfigurableRule
Configuration
now constructs it's rule array by iterating through aRuleList
(defaulting tomasterRuleList
). If a rule is aConfigurableRule
and a configuration exists for this rule, the rule will be instantiated by passing it's configuration toinit?(config:)
. If this fails, the rule is not aConfigurableRule
, or a configuration does not exist, theRule
is instantiated by calling it's defaultinit
.I added the
init()
requirement to theRule
protocol to support the initialization ofRule
s with their default values.I added the trivial case
init() { }
to all non-ConfigurableRule
s to support this.I wrote unit tests for most of the above.
I adapted
VariableNameMinLengthRule
as an example of how to create aConfigurableRule
. The new implementation supports the same syntax as before, but now also supports configuration viawarning
anderror
parameters as well as a newexcluded
array parameter to exclude certain variable names from the check (e.g.id
). Closes Special case id/exceptions for NameFormatViolation #231.Thoughts
AnyObject
parameter for configuration. I feel like it's fighting the strong type system a bit. But on the other hand, it seems like a necessary evil to support the use cases we would like. Also, this follows in the well-tread footsteps of serializing objects, and it makes it relatively easy to support any serialization technique for configurations (e.g. json, xml, or plist). The plist option is particularly nice because it would support a tighter integration of SwiftLint into Xcode.Rules
in a[Rule]
forConfiguration
and I needed to implement equality testing (isEqualTo
andEquatable
) forConfiguration
to support unit tests, I needed a way of dealing with twoConfigurableRule
s being compared while being downcasted toRule
s. This result in a somewhat ugly change to the implementation ofRule.isEqualTo
that tests for upcasting toConfigurableRule
s. This feels like fighting the type system, but I couldn't come up with a way around it. However, this is now a potential trap if more rule protocols are added.[String]
s that results in two empty[String]
s not being equal. I had to therefore test for equality in a different way inVariableNameMinLengthRule
.ConfigurableRule
creation. We can also further restrict configuration to remove theAnyObject
requirement or create a new type to facilitate this.ConfigurableRule
s.Yaml
flattening as extensions, but it might make more sense to put those inYamlParser
.