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

Feature/custom linters #8

Merged
merged 51 commits into from
Aug 24, 2017
Merged

Conversation

johngluckmdsol
Copy link
Contributor

@johngluckmdsol johngluckmdsol commented Jun 19, 2017

@nagarwal-mdsol, @mnohai-mdsol, @fbotero, @tkessler-mdsol
Based on our proposed issue, here is our PR. In general, we attempted to preserve the current state of the project. In general, we based our changes on the rubocop artchitecture as we feel it is the standard for ruby-based linters.

Here are the major changes

  1. Added a Configuration class to load and organize multiple config files
  2. Added method in linter parent to dynamically determine which linters to include (this is currently all child Linter classes)
  3. There is now a default.yml file which, by default, sets the linter to enabled unless that not is possible because the linter requires additional properties to be set to be valid
  4. Additional linter properties besides 'Enabled' are now supported. This is done the through the interface but we didn't attempt to support this through the command line.
  5. The project now supports a local configuration file, .gherkin_lint.yml which will override any specifed defaults.
  6. For now, we added the TagConstraint Module and the RequiredTagsStartsWith implementation into the base application. We can pull it out and load it externally as we proposed but it seemed like this might come in handy.
  7. We no longer use command-line expansion through ARGV to get the file list because this doesn't work in all cases (/**/* vs dir/*). This would be a bug fix

That said, since we bugfixed the cli but all other changes should be backward compatible, we bumped to 0.7.0., which I believe is in accordance with semantic versioning, even though the usage has changed (in that command line arg now requires -f '<path to file/s>'

Open Issues

  • Linters which are not enabled by default cannot be configured through the command line. It probably makes sense to disable command-line configuration in favor of the local file but I didn't want to make this decision
  • Only one local configuration is supported because only a single name is supported. If there are two files name .gherkin_lint.yml, they collide. We should probably support a command_line option to point at other local configuration/s.
  • Potentially, 'Enabled' should probably be made a property of all linters and we should treat it the same way we treat new child members, by sending it to the linter and running the strategy.

I'll file any applicable issues after the code is merged. Additionally, I'd like to make a change to the way the feature files are tested and now that we have local config file, I believe we can actually support running bin/gherkin_lint directly instead of writing a new executable for each test

Copy link

@mjnohai mjnohai left a comment

Choose a reason for hiding this comment

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

minor changes

README.md Outdated
@@ -45,6 +45,7 @@ This will mount the current directory within the Gherkin Lint Docker Container a
- [missing scenario name](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_scenario_name.feature)
- [missing test action](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_test_action.feature)
- [missing verification](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_verification.feature)
- [required_tag](https://github.com/funkwerk/gherkin_lint/blob/master/features/required_tag.feature) - disabled by default
Copy link

Choose a reason for hiding this comment

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

should be labeled [required tags starts with]


def disable_linters
<<-content
AvoidOutlineForSingleExample:
Copy link

Choose a reason for hiding this comment

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

hmm, not too fond of this method. Is there a smarted way to disable them?

Copy link
Contributor Author

@johngluckmdsol johngluckmdsol Jun 21, 2017

Choose a reason for hiding this comment

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

Ignore previous response. I'm not sure that there is a better way but I'm open to suggestions. The way the cukes currently work is that they require all linters but the linter-under-test to be disabled. We didn't want to muck with the cukes because they use aruba, which we were unfamiliar with and it was not immediately obvious how to do deal with thm. Since we were under a time-constraint, we opted to take the path of least resistance.

The most immediate way I can see to do this is create a new test-only method in gherkin_lint, disable_all, in the same way that there was an enable_all previously. We preferred thie current approach because the disable_all would require a little more precedence work and, again, we were working with a time-constraint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the disable_all method based on @lindt's comment re: the specific test. I can go ahead and add this to all the feature files. @lindt, what do you think?

@dpoetzsch
Copy link

Love your project! But I really need this PR because I have to disable some rules for my project. Would be great if this one would get released!

Copy link
Member

@lindt lindt left a comment

Choose a reason for hiding this comment

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

thanks for your huge amount of work! sorry for my late Reply, was quite busy the last days. PR still needs a little polishment, I think. Promise you to answer you timely.

@@ -45,6 +45,7 @@ This will mount the current directory within the Gherkin Lint Docker Container a
- [missing scenario name](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_scenario_name.feature)
- [missing test action](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_test_action.feature)
- [missing verification](https://github.com/funkwerk/gherkin_lint/blob/master/features/missing_verification.feature)
- [required tag starts with](https://github.com/funkwerk/gherkin_lint/blob/master/features/required_tag_starts_with.feature) - disabled by default
Copy link
Member

Choose a reason for hiding this comment

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

could this be more specific? I for example have an requirement that I want my tags match a regexp pattern. just allow a few, the others Tags should be valid ticket numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We thought about this and decided that there are several kinds of linters that could be implemented (StartsWith, ExactMatch, Regex, Includes) and rather than implement all of them, we implemented what was required for our project along with a Mixin that could be included to create all the others. But rather than try to have this one do everything, we decided to go with the Single Responsibility prinicple.

Copy link
Member

Choose a reason for hiding this comment

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

ok

@@ -77,3 +78,6 @@ Install it with:
`sudo gem install gherkin_lint`

After that `gherkin_lint` executable is available.

## Configuration
If you have a custom configuration you'd like to run on a regular basis instead of passing enable and disable flags through the CLI on every run, you can configure a ```.gherkin_lint.yml``` file that will be loaded on execution. The format and available linters are in [```config/default.yml```](config/default.yml)
Copy link
Member

Choose a reason for hiding this comment

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

would like if there is a gherkin_lint --auto-gen-config similar to rubocop. Nobody knows where gherkin_lint is installed and where the config/default.yml is located. It is also difficult to Keep the config/default.yml up to date, when addind a new Feature. An generated config may handle this better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We took a look at this in rubocop and there is an lot of code that makes this work. We figured that this would be an acceptable shortcut for now. You don't have to have .gherkin_lint and the only people who need to know where the default is are gherkin_lint developers. I agree that there needs to be a better way to add new linters, but we didn't want to take that architectural liberty.

That said, I'll see if there is a quick and dirty way to solve this

Copy link
Contributor Author

@johngluckmdsol johngluckmdsol Jul 10, 2017

Choose a reason for hiding this comment

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

So would adding a CLI option to specify a config file (defaulting to .gherkin_lint.yml) work for you? Auto-gen-config as implemented in rubocop would require quite a few more changes.

Copy link
Member

@lindt lindt Jul 13, 2017

Choose a reason for hiding this comment

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

isn't checking if there is a .gherkin_lint.yml file enough? Then wouldn't need to CLI option. If there is no .gherkin_lint.yml-file, then the default linters are enabled with the default configuration.

Is it okay that if there is a .gherkin_lint.yml and for testing I want to disable a check, that the CLI options override the .gherkin_lint.yml settings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wrote a feature file to demonstrate that this is working as you specified. Is this okay? We are already doing what you asked, I think. I just wrote a test to prove it.

InvalidStepFlow:
Enabled: true
RequiredTagsStartsWith:
Enabled: false
Copy link
Member

Choose a reason for hiding this comment

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

if it is a regexp, it could be enabled everytime. so just set it to match everything as Default.

Copy link
Contributor Author

@johngluckmdsol johngluckmdsol Jul 10, 2017

Choose a reason for hiding this comment

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

See above comment. The idea is that there are multiple types of TagConstraint linters so it didn't make sense to always have an enabled default since, at some point, those linters would easily conflict with each other.

@@ -53,5 +54,5 @@ Feature: Background Requires Scenario
When I run `ruby lint.rb`
Then it should pass with exactly:
"""

There are no issues
Copy link
Member

Choose a reason for hiding this comment

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

this makes Tests fragile. everytime changing this Output, would Need to Change every test.
could we add a --verbose option, which issues such a message or a --quiet which suppresses such a message. Me for example do not want to read it. I trust the return code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll see what I can do. Good suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see you already have a --verbose option that defaults to true. What rubocop does is treats the report output as separate by writing it to a file. I'm happy to do something like that but I want your feedback on how to move forward. I agree that this is fragile but it's also pretty easy to change so let me know what you want to do here.

For us, this is less about trusting the return code. We want positive confirmation that gherkin_lint ran and everything was cool (or not)

Copy link
Member

Choose a reason for hiding this comment

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

yes, tie it to verbose would be a good idea.

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

Background: Prepare Testee
Given a file named ".gherkin_lint.yml" with:
"""
---
Copy link
Member

Choose a reason for hiding this comment

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

this configuration file here does not test the required tags start with Feature. The configuration Thing should be tested separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Configuration is tested in rspec. This is setup for the integration test for the new linter and this was the only way we saw to support your 'disable all' under the new structure without significant refactoring. I'm open to other suggestions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I submitted a change to this. Take a look

include TagConstraint

def match_pattern?(target)
match = false
Copy link
Member

Choose a reason for hiding this comment

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

try to avoid this state variable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean the match var? Can you tell we are both Java programmers? I can try to make this more Rubyesque

@@ -0,0 +1,32 @@
module GherkinLint
# Mixin to lint for tags that have certain string contraints
module TagConstraint
Copy link
Member

Choose a reason for hiding this comment

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

you require that every Scenario/Feature has a Ticket-Reference. I do not require that for my Projects, but I would require, that just a certain pattern for Tags is used. Like @Skip or @mcc-[\d]+. Could we express this as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We couldn't think of a way to do this. That's why we set this linter to not be required, because we couldn't determine a sensible default. So in the future, any additional implementations of the TagConstraint should be defaulted to disabled in the yml file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reread this and I think what you are asking for is an implementation of RequiredTagMatchesRegexp (@mcc[\d]). At this point, I'm not prepared to implement because I want to to limit the scope of this PR, but I will gladly attempt an implementation once this is merged. Not sure how the Skip would work? What are the requirements. I believe this would be a third linter.

Copy link
Member

Choose a reason for hiding this comment

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

cool. lets discuss this after the PR.

@@ -31,69 +33,52 @@
require 'gherkin_lint/linter/use_outline'
require 'multi_json'
require 'set'

require 'gherkin_lint/configuration'
Copy link
Member

Choose a reason for hiding this comment

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

could there be both usages possible? the cli way and the config way? how to express a pattern for your require_tags switch?

Copy link
Contributor Author

@johngluckmdsol johngluckmdsol Jul 10, 2017

Choose a reason for hiding this comment

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

Really, I'm not certain I see the value of the CLI if the config file exists. In fact, many of the linters you have could be configured. You just decided on a hardcoded threshhold, but ideally it would be up to the individual user to decide. I'd be more inclined to move in this direction. Frankly, I don't see the configuration changing that significantly between runs, which leads me to think that the CLI approach is less necessary with this change.

It's up to you to decide this, but I think the CLI would get very complicated if we started trying to pass args through

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have you thought anymore about this?

@@ -111,8 +96,7 @@ def report
linter.issues
end.flatten

issues.each { |issue| puts issue.render }

print(issues)
Copy link
Member

Choose a reason for hiding this comment

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

print issues?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just encapsulating the each to appease rubocop

Copy link
Member

Choose a reason for hiding this comment

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

I mean you could write it without brackets?

result[key] = suppress_tags(value, tags)
end
result
end

def suppress(data, tags)
data.select { |item| !tags.map { |tag| "@#{tag}" }.include? item[:name] }
data.reject { |item| tags.map { |tag| "@#{tag}" }.include? item[:name] }
Copy link
Member

Choose a reason for hiding this comment

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

thanks for the ton of simplifications like this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rubocop

@johngluckmdsol
Copy link
Contributor Author

@dpoetzsch You shouldn't need my PR to disable linters. At this point, you can pass them in on the command line using --disable

@johngluckmdsol
Copy link
Contributor Author

johngluckmdsol commented Aug 23, 2017

Hey @lindt it has been a while. Are you going to merge these changes, because if not we will have to keep our fork. Please let us know. We prefer not to fork

@lindt lindt merged commit 073c604 into funkwerk:master Aug 24, 2017
@lindt
Copy link
Member

lindt commented Aug 24, 2017

merged just now and tagged it as a new major change. 1.0.0.
But didn't have time to test it properly. Would be nice if you guys could test it and report errors. Thanks.

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.

5 participants