Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adding support for an ignore file. #57

Closed
wants to merge 2 commits into from

5 participants

Dana Merrick Don't Add Me To Your Organization a.k.a The Travis Bot Andrew Crump Phil Dibowitz Patrick Connolly
Dana Merrick

I love foodcritic. It's an awesome linter and very useful to maintain a quality standard in a chef repo shared among many people.

Very rarely, we get a few foodcritic warnings that we wish to ignore... be it from issues we do not wish to fix (for whatever reason) or occasional false positives.

Since we use foodcritic as part of our continuous delivery pipeline, we don't want our build to fail for these warnings, but we don't want to ignore an entire class of rules. It was with this in mind that I added the ability to pass an "ignore file" via the command line. An example file looks like this:

## foodcritic ignore file

# omitted because refactoring this makes it LESS elegant
FC004: Use a service resource to start and stop services: cookbooks/runit/recipes/default.rb:22
# omitted because it's too short and specific to make a library
FC014: Consider extracting long ruby_block to library: cookbooks/mongodb/recipes/mms-agent.rb:37
# omitted because contains false positives
FC017: LWRP does not notify when updated: cookbooks/python/providers/pip.rb

It's basically the output of a foodcritic run. You can choose to ignore a warning on a specific line, or omit the line number to ignore the rule across the entire file (c.p. the last line in the above example). Comments and newlines are ignored as expected.

I believe that this is a practical change, simply because foodcritic is often used in build pipelines and it's suboptimal to have to make changes to "trick" foodcritic into ignoring false positives.

Also, I know it's not perfect, but I figured I'd share it with you guys to collect feedback and to see what it would take to get it into master.

Thanks!
-Dana

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged ee701b0 into d84c095).

Andrew Crump
Owner

Hi Dana,

Thanks! This looks like it solves a common problem for people and avoids excluding rules en-masse.

I was initially minded to merge it but thinking about it I have a couple of concerns:

  • I like the fact that you can specify the rule without line numbers in the ignore file. This helps avoid the situation where you are vulnerable to cookbook edits changing the line offsets of matches. This does raise a situation where you intend to exclude a specific instance of a warning and unknowingly introduce and exclude another warning in a subsequent edit. @jaymzh raised a request for similar functionality ages ago in issue #10 but suggested an approach that would be more robust by declaring the ignore within the cookbook next to the offending line. The downside of this approach is that it looked more involved to implement.
    #10 (comment)

  • Another alternative (which feels cleaner) is to not add this functionality to foodcritic at all and instead to provide examples in the docs of using grep with patterns from a file to exclude. I can imagine wanting to exclude based on a substring or regular expression which would slightly complicate the foodcritic implementation but is easy if you just use grep. This is my preferred option at the moment but I'd like to explore if there are things we can't do with grep that would be better implemented in foodcritic.

It also occurs to me that we could add an option to print out a checksum for each match which would make grepping out individual matches less brittle. I'm not sure if this would be a popular feature though.

For reference there are a couple of things I'd change if we were to merge:

  • Add tests to verify that the matches in the ignore file are excluded correctly and that it copes with a missing or malformed ignore file. The include custom rules feature 859a6f3 has similar scenarios.
  • Modify the loading of the ignore file to allow foodcritic rules that start with a different prefix, to allow exclusion of custom rules and not just those that ship with foodcritic.

Sorry for the brain dump but thanks very much for the contribution and for sparking a conversation early. Thanks for taking the time to make foodcritic better.

Cheers,

Andrew.

Andrew Crump
Owner

Ah. Sorry I just realised that you're specifically dealing with build failure which changes the situation a bit.

I'm going to sleep on it but would appreciate people's thoughts on this kind of functionality and the best place for it.

Dana Merrick

Thanks so much for your reply. You make some great points.

Regardless of how it is implemented, I think the ability to ignore warnings is an important feature. I, too, considered adding in support for marking certain sections of code with a comment, but I decided to do it like this because it doesn't cruft up existing code, it doesn't require write access to the chef repo, and it was easier to implement.

At the moment, it simply omits specified warnings from the output, and does not affect the "epic fail" status. I was curious as to your thoughts on this; should my changes be refactored into FoodCritic::Linter.check(), or should I manually overwrite @is_failed in FoodCritic::Review? Either way, I could add a way for a user to pass another command line flag that determines if ignored warnings can affect the epic fail status, which could be very useful in separating the "I just don't want to see this in the output" and the more worrisome "I don't want this warning to fail my build anymore" use cases.

Another thing I considered was to build this into a second executable that acts as an output filter, so you could run something like foodcritic --whatever cookbooks | fc_ignore ignore.file and get more or less the same result. If you were morally against the idea of ignoring rules, this is the approach I would probably take.

Regarding tests: I'm totally on board with what you said. I started to write tests and it was taking me a while and I got to the point where I said "hmm, maybe I'll see if Mr. Foodcritic likes this idea before I keep going on this..."

Regarding loading the ignore file: great point! I didn't realize custom rules could have custom prefixes, so thanks so much for pointing it out.

Anyway, thanks for taking a look!

-Dana

Phil Dibowitz
Collaborator
Andrew Crump
Owner

@jaymzh - I think Dana's pull request does handle the cases you have described. The ignore file specifies the file path and optionally line of the match to ignore.

As above relying on line numbers does have drawbacks but seems like the simplest thing (tm).

Phil Dibowitz
Collaborator
Andrew Crump
Owner

Hi Dana,

Sorry for the lack of feedback on your pull request!

I see you have made related changes recently on your fork for epic fail support, would it be possible to update this pull request?

Cheers,

Andrew.

Patrick Connolly

This convo is perfect guys :+1:

Just wanted to chime in that I'm inclined to favor @jaymzh's inline approach. It would seem less brittle, and more coherent. The one downside is that there's no single file to review periodically (in case some are for pending bugfixes), but so long as the flagging string is unique, we can simply git grep FCIGNORE

Phil Dibowitz
Collaborator

Exactly. When someone's looking at the code I want it to be obvious something is exempted. It also then makes it clear where the comment would go since the code and the exemption are in one place. And yes, grep seems like the right way to find all instances.

Dana Merrick

Thanks so much to everyone for getting involved in the discussion. I knew that implementing some form of ignorefile support was an important feature to Foodcritic, so I went about it through the path of least resistance. We've been using my fork in my organization for the past few weeks and it has been suiting our needs (albeit on occasion causing us to update the line numbers due to code changes).

I agree with everyone in that the optimal solution would be some sort of inline "marking" that would cause Foodcritic to skip rule evaluation. Given the current implementation of Foodcritic's code parser, I suspect this will be a difficult fix. If I can help in any way, please let me know!

There are a couple more commits in my fork... fixing the epic-fail functionality and various changes to allow for a working gem package. I can add these changes into the pull request once I figure out how to add to an existing pull request without creating a new one :).

I still don't have tests... I'd made the judgement call that my code probably wasn't going to be merged into Foodcritic proper, so I opted not to take the time to finish them. I hope this doesn't prevent anyone from using my code!

Thanks again for taking the time to check out my work.

Patrick Connolly

Thanks @dmerrick. Couldn't hurt to share if you don't mind :)

I think you'll need to start a new PR, as I don't think it's possible to adjust the reference from a commit hash to a branch -- a branch is needed to have an evolving PR, I believe. Maybe create a new PR and reference this before closing?

Andrew Crump
Owner

Closing this as similar functionality was released in foodcritic 2.0.0.

Thanks to Dana for the original pull request and everyone involved for the quality discussion.

Andrew Crump acrmp closed this
Dana Merrick

Thanks for adding this in 2.0!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 15, 2012
  1. Dana Merrick
Commits on Aug 16, 2012
  1. Dana Merrick
This page is out of date. Refresh to see the latest.
4 lib/foodcritic/command_line.rb
View
@@ -40,6 +40,10 @@ def initialize(args)
"Specify grammar to use when validating search syntax.") do |s|
options[:search_grammar] = s
end
+ opts.on("-i", "--ignore-file FILE",
+ "Ignore the results listed in the specified file.") do |i|
+ options[:ignore_file] = i
+ end
opts.on("-V", "--version",
"Display the foodcritic version.") do |v|
options[:version] = true
44 lib/foodcritic/domain.rb
View
@@ -19,10 +19,31 @@ class Review
attr_reader :cookbook_paths, :warnings
- def initialize(cookbook_paths, warnings, is_failed)
+ def initialize(cookbook_paths, warnings, is_failed, ignore_file = nil)
@cookbook_paths = Array(cookbook_paths)
@warnings = warnings
@is_failed = is_failed
+ @ignore_file = ignore_file
+ end
+
+ # Returns a list of warnings where those that are listed in an ignore file
+ # are omitted.
+ def quieter_warnings
+ # Remove any warnings that match the ignore rules
+ @warnings.reject do |w|
+ ignore_rules.any? do |code, file, line|
+
+ # If no line is provided in the ignore file, use the warning's line
+ # This will ignore the rule across the entire file
+ line = line ? line.to_i : w.match[:line]
+
+ same_code = code == w.rule.code
+ same_file = file == w.match[:filename]
+ same_line = line == w.match[:line]
+
+ same_code && same_file && same_line
+ end
+ end
end
# Provided for backwards compatibility. Deprecated and will be removed in a
@@ -42,13 +63,32 @@ def to_s
# Sorted by filename and line number.
#
# FC123: My rule name: foo/recipes/default.rb
- @warnings.map do |w|
+ warnings_to_display = @ignore_file ? quieter_warnings : @warnings
+ warnings_to_display.map do |w|
["#{w.rule.code}: #{w.rule.name}: #{w.match[:filename]}",
w.match[:line].to_i]
end.sort do |x,y|
x.first == y.first ? x[1] <=> y[1] : x.first <=> y.first
end.map{|w|"#{w.first}:#{w[1]}"}.uniq.join("\n")
end
+
+ private
+
+ # Returns a multi-dimensional array of warnings to ignore in the review.
+ #
+ # [["FC004", "cookbooks/foo/recipes/default.rb", "22"],
+ # ["FC014", "cookbooks/bar/recipes/qux.rb", "37"],
+ # ["FC017", "cookbooks/baz/providers/quux.rb", "1"]]
+ def ignore_rules
+ # Read the ignore file
+ ignore = File.open(@ignore_file).readlines
+ # Strip out comments and blank lines
+ ignore.reject! {|line| line !~ /^FC/}
+ # Split the line into its component parts
+ ignore.map! {|rule| rule.chomp.split(/: ?/)}
+ # Throw away the rule name
+ ignore.each {|rule| rule.delete_at(1)}
+ end
end
# A rule to be matched against.
2  lib/foodcritic/linter.rb
View
@@ -82,7 +82,7 @@ def check(cookbook_paths, options)
end
Review.new(cookbook_paths, warnings,
- should_fail_build?(options[:fail_tags], matched_rule_tags))
+ should_fail_build?(options[:fail_tags], matched_rule_tags), options[:ignore_file])
end
end
6 spec/foodcritic/domain_spec.rb
View
@@ -32,4 +32,10 @@
FoodCritic::Review.new('example', [warning], false).warnings.must_equal [warning]
end
end
+ describe "#quieter_warnings" do
+ it "returns empty when there are no warnings" do
+ ignore_file = ""
+ FoodCritic::Review.new('example', [], false, ignore_file).quieter_warnings.must_be_empty
+ end
+ end
end
Something went wrong with that request. Please try again.