foodcritic 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ module FoodCritic
2
+ module AST
3
+
4
+ private
5
+
6
+ def ast_hash_node?(node)
7
+ node.first.respond_to?(:first) and node.first.first == :assoc_new
8
+ end
9
+
10
+ def ast_node_has_children?(node)
11
+ node.respond_to?(:first)
12
+ end
13
+
14
+ # If the provided node is the line / column information.
15
+ def position_node?(node)
16
+ node.respond_to?(:length) and node.length == 2 and
17
+ node.respond_to?(:all?) and node.all?{|child| child.respond_to?(:to_i)}
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -3,43 +3,50 @@ module FoodCritic
3
3
  # Encapsulates functions that previously were calls to the Chef gem.
4
4
  module Chef
5
5
 
6
- # The set of methods in the Chef DSL
7
- #
8
- # @return [Array] Array of method symbols
9
6
  def chef_dsl_methods
10
7
  load_metadata
11
8
  @dsl_metadata[:dsl_methods].map(&:to_sym)
12
9
  end
13
10
 
14
- # Is the specified attribute valid for the type of resource? Note that this
15
- # method will return true if the resource_type is not recognised.
16
- #
17
- # @param [Symbol] resource_type The type of Chef resource
18
- # @param [Symbol] attribute_name The attribute name
19
- # @return [Boolean] False if the attribute is known not to be valid
11
+ # Is the specified action valid for the type of resource?
12
+ def resource_action?(resource_type, action)
13
+ resource_check?(:actions, resource_type, action)
14
+ end
15
+
16
+ # Is the specified attribute valid for the type of resource?
20
17
  def resource_attribute?(resource_type, attribute_name)
21
- if resource_type.to_s.empty? || attribute_name.to_s.empty?
22
- raise ArgumentError, "Arguments cannot be nil or empty."
23
- end
24
- load_metadata
25
- resource_attributes = @dsl_metadata[:attributes]
26
- return true unless resource_attributes.include?(resource_type.to_sym)
27
- resource_attributes[resource_type.to_sym].include?(attribute_name.to_s)
18
+ resource_check?(:attributes, resource_type, attribute_name)
28
19
  end
29
20
 
30
21
  # Is this a valid Lucene query?
31
- #
32
- # @param [String] query The query to check for syntax errors
33
- # @return [Boolean] True if the query is well-formed
34
22
  def valid_query?(query)
35
23
  raise ArgumentError, "Query cannot be nil or empty" if query.to_s.empty?
24
+
25
+ # Attempt to create a search query parser
36
26
  search = FoodCritic::Chef::Search.new
37
27
  search.create_parser(search.chef_search_grammars)
38
- search.parser? ? (! search.parser.parse(query.to_s).nil?) : true
28
+
29
+ if search.parser?
30
+ search.parser.parse(query.to_s)
31
+ else
32
+ # If we didn't manage to get a parser then we can't know if the query
33
+ # is valid or not.
34
+ true
35
+ end
39
36
  end
40
37
 
41
38
  private
42
39
 
40
+ # To avoid the runtime hit of loading the Chef gem and its dependencies
41
+ # we load the DSL metadata from a JSON file shipped with our gem.
42
+ #
43
+ # The DSL metadata therefore reflects the version of Chef in the gemset
44
+ # where foodcritic was built, rather than the version in the local user
45
+ # gemset.
46
+ #
47
+ # TODO: Now that the effective version of Chef to check with can be passed
48
+ # on the command-line we should bundle metadata for historical Chef gem
49
+ # versions.
43
50
  def load_metadata
44
51
  metadata_path = File.join(File.dirname(__FILE__), '..', '..',
45
52
  'chef_dsl_metadata.json')
@@ -47,14 +54,28 @@ module FoodCritic
47
54
  :symbolize_keys => true)
48
55
  end
49
56
 
50
- # Chef Search
57
+ def resource_check?(key, resource_type, field)
58
+ if resource_type.to_s.empty? || field.to_s.empty?
59
+ raise ArgumentError, "Arguments cannot be nil or empty."
60
+ end
61
+
62
+ load_metadata
63
+ resource_fields = @dsl_metadata[key]
64
+
65
+ # If the resource type is not recognised then it may be a user-defined
66
+ # resource. We could introspect these but at present we simply return
67
+ # true.
68
+ return true unless resource_fields.include?(resource_type.to_sym)
69
+
70
+ # Otherwise the resource field must exist in our metadata to succeed
71
+ resource_fields[resource_type.to_sym].include?(field.to_s)
72
+ end
73
+
51
74
  class Search
52
75
 
53
76
  # The search grammars that ship with any Chef gems installed locally.
54
77
  # These are returned in descending version order (a newer Chef version
55
78
  # could break our ability to load the grammar).
56
- #
57
- # @return [Array] File paths of Chef search grammars installed locally.
58
79
  def chef_search_grammars
59
80
  Gem.path.map do |gem_path|
60
81
  Dir["#{gem_path}/gems/chef-*/**/lucene.treetop"]
@@ -62,32 +83,26 @@ module FoodCritic
62
83
  end
63
84
 
64
85
  # Create the search parser from the first loadable grammar.
65
- #
66
- # @param [Array] grammar_paths Full paths to candidate treetop grammars
67
86
  def create_parser(grammar_paths)
68
87
  @search_parser ||= grammar_paths.inject(nil) do |parser,lucene_grammar|
69
88
  begin
70
89
  break parser unless parser.nil?
71
- # don't instantiate custom nodes
90
+ # Don't instantiate custom nodes
72
91
  Treetop.load_from_string(
73
92
  IO.read(lucene_grammar).gsub(/<[^>]+>/, ''))
74
93
  LuceneParser.new
75
94
  rescue
76
- # silently swallow and try the next grammar
95
+ # Silently swallow and try the next grammar
77
96
  end
78
97
  end
79
98
  end
80
99
 
81
100
  # Has the search parser been loaded?
82
- #
83
- # @return [Boolean] True if the search parser has been loaded.
84
101
  def parser?
85
102
  ! @search_parser.nil?
86
103
  end
87
104
 
88
105
  # The search parser
89
- #
90
- # @return [LuceneParser] The search parser
91
106
  def parser
92
107
  @search_parser
93
108
  end
@@ -45,7 +45,17 @@ module FoodCritic
45
45
  options[:version] = true
46
46
  end
47
47
  end
48
- @parser.parse!(args) unless show_help?
48
+ # -v is not implemented but OptionParser gives the Foodcritic's version
49
+ # if that flag is passed
50
+ if args.include? '-v'
51
+ help
52
+ else
53
+ begin
54
+ @parser.parse!(args) unless show_help?
55
+ rescue OptionParser::InvalidOption => e
56
+ e.recover args
57
+ end
58
+ end
49
59
  end
50
60
 
51
61
  # Show the command help to the end user?
@@ -66,7 +76,7 @@ module FoodCritic
66
76
  #
67
77
  # @return [Boolean] True if the version should be shown.
68
78
  def show_version?
69
- @options.key?(:version) and @original_args != ['-v']
79
+ @options.key?(:version)
70
80
  end
71
81
 
72
82
  # The version string.
@@ -4,14 +4,11 @@ module FoodCritic
4
4
  class Warning
5
5
  attr_reader :rule, :match
6
6
 
7
- # Create a new warning
7
+ # Create a new warning.
8
+ #
9
+ # Warning.new(rule, :filename => 'foo/recipes.default.rb',
10
+ # :line => 5, :column=> 40)
8
11
  #
9
- # @param [FoodCritic::Rule] rule The rule which raised this warning
10
- # @param [Hash] match The match data
11
- # @option match [String] :filename The filename the warning was raised
12
- # against
13
- # @option match [Integer] :line The identified line
14
- # @option match [Integer] :column The identified column
15
12
  def initialize(rule, match={})
16
13
  @rule, @match = rule, match
17
14
  end
@@ -22,36 +19,29 @@ module FoodCritic
22
19
 
23
20
  attr_reader :cookbook_paths, :warnings
24
21
 
25
- # Create a new review
26
- #
27
- # @param [Array] cookbook_paths The path this review was performed against
28
- # @param [Array] warnings The warnings raised in this review
29
- # @param [Boolean] is_failed Have warnings been raised that mean this
30
- # should be considered failed?
31
22
  def initialize(cookbook_paths, warnings, is_failed)
32
23
  @cookbook_paths = Array(cookbook_paths)
33
24
  @warnings = warnings
34
25
  @is_failed = is_failed
35
26
  end
36
27
 
37
- # Provided for backwards compatibility
38
- # @deprecated Multiple cookbook paths may be provided.
28
+ # Provided for backwards compatibility. Deprecated and will be removed in a
29
+ # later version.
39
30
  def cookbook_path
40
31
  @cookbook_paths.first
41
32
  end
42
33
 
43
34
  # If this review has failed or not.
44
- #
45
- # @return [Boolean] True if this review has failed.
46
35
  def failed?
47
36
  @is_failed
48
37
  end
49
38
 
50
- # Returns a string representation of this review.
51
- #
52
- # @return [String] Review as a string, this representation is liable to
53
- # change.
39
+ # Returns a string representation of this review. This representation is
40
+ # liable to change.
54
41
  def to_s
42
+ # Sorted by filename and line number.
43
+ #
44
+ # FC123: My rule name: foo/recipes/default.rb
55
45
  @warnings.map do |w|
56
46
  ["#{w.rule.code}: #{w.rule.name}: #{w.match[:filename]}",
57
47
  w.match[:line].to_i]
@@ -63,32 +53,23 @@ module FoodCritic
63
53
 
64
54
  # A rule to be matched against.
65
55
  class Rule
66
- attr_accessor :code, :name, :applies_to, :cookbook, :recipe, :provider, :resource
56
+ attr_accessor :code, :name, :applies_to, :cookbook, :recipe, :provider,
57
+ :resource, :metadata, :library, :template
67
58
  attr_writer :tags
68
59
 
69
- # Create a new rule
70
- #
71
- # @param [String] code The short unique identifier for this rule,
72
- # e.g. 'FC001'
73
- # @param [String] name The short descriptive name of this rule presented to
74
- # the end user.
75
60
  def initialize(code, name)
76
61
  @code, @name = code, name
77
62
  @tags = [code]
78
63
  @applies_to = Proc.new {|version| true}
79
64
  end
80
65
 
81
- # The tags associated with this rule.
82
- # A rule is always tagged with the tags 'any' and the rule code.
83
- #
84
- # @return [Array] The tags associated.
66
+ # The tags associated with this rule. Rule is always tagged with the tag
67
+ # `any` and the rule code.
85
68
  def tags
86
69
  ['any'] + @tags
87
70
  end
88
71
 
89
72
  # Returns a string representation of this rule.
90
- #
91
- # @return [String] Rule as a string.
92
73
  def to_s
93
74
  "#{@code}: #{@name}"
94
75
  end
@@ -1,74 +1,69 @@
1
1
  require 'pathname'
2
2
 
3
- # FoodCritic is a lint tool for Chef cookbooks.
4
3
  module FoodCritic
5
4
 
6
- # The DSL methods exposed for defining rules.
5
+ # The DSL methods exposed for defining rules. A minimal example rule:
6
+ #
7
+ # rule "FC123", "My rule name" do
8
+ # tags %w{style attributes}
9
+ # recipe do |ast|
10
+ # ## Rule implementation body
11
+ # end
12
+ # end
13
+ #
14
+ # * Each rule is defined within a `rule` block that defines the code and name
15
+ # of the rule.
16
+ # * Each rule block may contain further nested blocks for the components of
17
+ # the cookbook that it is interested in linting.
18
+ # For example `cookbook`, `recipe` or `library`.
19
+ #
20
+ # * Further DSL methods are available to define the `tags` and Chef versions
21
+ # the rule `applies_to`.
22
+ #
23
+
7
24
  class RuleDsl
8
25
  attr_reader :rules
9
26
 
10
27
  include Api
11
28
 
12
- # Define a new rule
13
- #
14
- # @param [String] code The short unique identifier for this rule,
15
- # e.g. 'FC001'
16
- # @param [String] name The short descriptive name of this rule presented to
17
- # the end user.
18
- # @param [Block] block The rule definition
29
+ # Define a new rule, the outer block of a rule definition.
19
30
  def rule(code, name, &block)
20
31
  @rules = [] if @rules.nil?
21
32
  @rules << Rule.new(code, name)
22
33
  yield self
23
34
  end
24
35
 
25
- # Add tags to the rule which can be used to filter the rules to be applied.
26
- #
27
- # @param [Array] tags The tags associated with this rule.
36
+ # Add tags to the rule which can be used by the end user to filter the rules
37
+ # to be applied.
28
38
  def tags(tags)
29
39
  rules.last.tags += tags
30
40
  end
31
41
 
32
- # Limit the versions that this rule can be seen to apply to.
33
- #
34
- # @param [block] block Your version constraint logic.
42
+ # Alternative to tags. Commonly used to constrain a rule to run only when
43
+ # linting specific Chef versions.
35
44
  def applies_to(&block)
36
45
  rules.last.applies_to = block
37
46
  end
38
47
 
39
- # Define a matcher that will be passed the AST with this method.
40
- #
41
- # @param [block] block Your implemented matcher that returns a match Hash.
42
- def recipe(&block)
43
- rules.last.recipe = block
48
+ def self.rule_block(name)
49
+ define_method(name) do |&block|
50
+ rules.last.send("#{name}=".to_sym, block)
51
+ end
44
52
  end
45
53
 
46
- # Define a matcher that will be passed the AST with this method.
47
- #
48
- # @param [block] block Your implemented matcher that returns a match Hash.
49
- def resource(&block)
50
- rules.last.resource = block
51
- end
54
+ # The most frequently used block within a rule. A slight misnomer because
55
+ # `recipe` rule blocks are also evaluated against providers.
56
+ rule_block :recipe
52
57
 
53
- # Define a matcher that will be passed the AST with this method.
54
- #
55
- # @param [block] block Your implemented matcher that returns a match Hash.
56
- def provider(&block)
57
- rules.last.provider = block
58
- end
59
58
 
60
- # Define a matcher that will be passed the cookbook path with this method.
61
- #
62
- # @param [block] block Your implemented matcher that returns a match Hash.
63
- def cookbook(&block)
64
- rules.last.cookbook = block
65
- end
59
+ rule_block :cookbook
60
+ rule_block :metadata
61
+ rule_block :resource
62
+ rule_block :provider
63
+ rule_block :library
64
+ rule_block :template
66
65
 
67
- # Load the ruleset
68
- #
69
- # @param [Array] paths The paths to the rulesets to load
70
- # @return [Array] The loaded rules, ready to be matched against provided
71
- # cookbooks.
66
+ # Load the ruleset(s).
72
67
  def self.load(paths, with_repl)
73
68
  dsl = RuleDsl.new
74
69
  paths.map do |path|
@@ -76,9 +71,13 @@ module FoodCritic
76
71
  end.flatten.each do |path|
77
72
  dsl.instance_eval(File.read(path), path)
78
73
  end
74
+
75
+ # Drop into the REPL for exploratory rule development.
79
76
  dsl.instance_eval { binding.pry } if with_repl
77
+
80
78
  dsl.rules
81
79
  end
80
+
82
81
  end
83
82
 
84
83
  end
@@ -5,22 +5,21 @@ require 'gherkin/tag_expression'
5
5
  require 'set'
6
6
 
7
7
  module FoodCritic
8
-
9
8
  # The main entry point for linting your Chef cookbooks.
10
9
  class Linter
11
10
 
12
11
  include FoodCritic::Api
12
+ include FoodCritic::REPL
13
13
 
14
- # The default version that will be used to determine relevant rules
14
+ # The default version that will be used to determine relevant rules. This
15
+ # can be over-ridden at the command line with the `--chef-version` option.
15
16
  DEFAULT_CHEF_VERSION = "0.10.10"
16
17
 
17
- # Perform option parsing from the provided arguments and do a lint check
18
- # based on those arguments.
19
- #
20
- # @param [Array] args The command-line arguments to parse
21
- # @return [Array] Pair - the first item is string output, the second is the
22
- # exit code.
18
+ # Perform a lint check. This method is intended for use by the command-line
19
+ # wrapper. If you are programatically using foodcritic you should use
20
+ # `#check` below.
23
21
  def self.check(cmd_line)
22
+ # The first item is the string output, the second is exit code.
24
23
  return [cmd_line.help, 0] if cmd_line.show_help?
25
24
  return [cmd_line.version, 0] if cmd_line.show_version?
26
25
  if ! cmd_line.valid_grammar?
@@ -34,113 +33,131 @@ module FoodCritic
34
33
  end
35
34
  end
36
35
 
37
- # Create a new Linter.
38
- def initialize
39
-
40
- end
41
-
42
36
  # Review the cookbooks at the provided path, identifying potential
43
37
  # improvements.
44
38
  #
45
- # @param [Array] cookbook_paths The file path(s) to the individual
46
- # cookbook(s) being checked
47
- # @param [Hash] options Options to apply to the linting
48
- # @option options [Array] include_rules Paths to local rules to apply
49
- # @option options [Array] tags The tags to filter rules based on
50
- # @option options [Array] fail_tags The tags to fail the build on
51
- # @return [FoodCritic::Review] A review of your cookbooks, with any
52
- # warnings issued.
39
+ # The `options` are a hash where the valid keys are:
40
+ #
41
+ # * `:include_rules` - Paths to additional rules to apply
42
+ # * `:tags` - The tags to filter rules based on
43
+ # * `:fail_tags` - The tags to fail the build on
44
+ # * `:exclude_paths` - Paths to exclude from linting
45
+ #
53
46
  def check(cookbook_paths, options)
54
- raise ArgumentError, "Cookbook paths are required" if cookbook_paths.nil?
55
- cookbook_paths = Array(cookbook_paths)
56
- if cookbook_paths.empty?
57
- raise ArgumentError, "Cookbook paths cannot be empty"
58
- end
59
-
60
- options = {:tags => [], :fail_tags => [],
61
- :include_rules => []}.merge(options)
62
-
63
- @last_cookbook_paths, @last_options = cookbook_paths, options
64
- load_rules unless defined? @rules
65
- warnings = []; last_dir = nil; matched_rule_tags = Set.new
66
47
 
67
- active_rules = @rules.select do |rule|
68
- matching_tags?(options[:tags], rule.tags) and
69
- applies_to_version?(rule, options[:chef_version] || DEFAULT_CHEF_VERSION)
70
- end
71
- files_to_process(cookbook_paths).each do |file|
72
- cookbook_dir = Pathname.new(
73
- File.join(File.dirname(file), '..')).cleanpath
74
- ast = read_ast(file)
75
- active_rules.each do |rule|
76
- rule_matches = matches(rule.recipe, ast, file)
77
- if File.basename(File.dirname(file)) == 'providers'
78
- rule_matches += matches(rule.provider, ast, file)
79
- end
80
- if File.basename(File.dirname(file)) == 'resources'
81
- rule_matches += matches(rule.resource, ast, file)
82
- end
83
- if last_dir != cookbook_dir
84
- rule_matches += matches(rule.cookbook, cookbook_dir)
85
- end
86
- rule_matches.each do |match|
87
- warnings << Warning.new(rule, {:filename => file}.merge(match))
88
- matched_rule_tags << rule.tags
48
+ cookbook_paths = sanity_check_cookbook_paths(cookbook_paths)
49
+ options = setup_defaults(options)
50
+
51
+ # Enable checks to be easily repeated at the REPL
52
+ with_repl(cookbook_paths, options) do
53
+ warnings = []; last_dir = nil; matched_rule_tags = Set.new
54
+
55
+ load_rules
56
+
57
+ # Loop through each file to be processed and apply the rules
58
+ files_to_process(cookbook_paths, options[:exclude_paths]).each do |file|
59
+ ast = read_ast(file)
60
+ active_rules(options).each do |rule|
61
+ rule_matches = matches(rule.recipe, ast, file)
62
+
63
+ if dsl_method_for_file(file)
64
+ rule_matches += matches(rule.send(dsl_method_for_file(file)),
65
+ ast, file)
66
+ end
67
+
68
+ per_cookbook_rules(last_dir, file) do
69
+ if File.basename(file) == 'metadata.rb'
70
+ rule_matches += matches(rule.metadata, ast, file)
71
+ end
72
+ rule_matches += matches(rule.cookbook, cookbook_dir(file))
73
+ end
74
+
75
+ # Convert the matches into warnings
76
+ rule_matches.each do |match|
77
+ warnings << Warning.new(rule, {:filename => file}.merge(match))
78
+ matched_rule_tags << rule.tags
79
+ end
89
80
  end
81
+ last_dir = cookbook_dir(file)
90
82
  end
91
- last_dir = cookbook_dir
92
- end
93
83
 
94
- @review = Review.new(cookbook_paths, warnings,
95
- should_fail_build?(options[:fail_tags], matched_rule_tags))
96
-
97
- binding.pry if options[:repl]
98
- @review
99
- end
100
-
101
- # Convenience method to repeat the last check. Intended to be used from the
102
- # REPL.
103
- def recheck
104
- check(@last_cookbook_paths, @last_options)
84
+ Review.new(cookbook_paths, warnings,
85
+ should_fail_build?(options[:fail_tags], matched_rule_tags))
86
+ end
105
87
  end
106
88
 
107
89
  # Load the rules from the (fairly unnecessary) DSL.
108
90
  def load_rules
109
- @rules = RuleDsl.load([File.join(File.dirname(__FILE__), 'rules.rb')] +
110
- @last_options[:include_rules], @last_options[:repl])
91
+ load_rules!(@last_options) unless defined? @rules
111
92
  end
112
93
 
113
- alias_method :reset_rules, :load_rules
114
-
115
- # Convenience method to retrieve the last review. Intended to be used from
116
- # the REPL.
117
- #
118
- # @return [Review] The last review performed.
119
- def review
120
- @review
94
+ def load_rules!(options)
95
+ @rules = RuleDsl.load([File.join(File.dirname(__FILE__), 'rules.rb')] +
96
+ options[:include_rules], options[:repl])
121
97
  end
122
98
 
123
99
  private
124
100
 
125
101
  # Some rules are version specific.
126
- #
127
- # @param [FoodCritic::Rule] rule The rule determine applicability for
128
- # @param [String] version The version of Chef
129
- # @return [Boolean] True if the rule applies to this version of Chef
130
102
  def applies_to_version?(rule, version)
131
103
  return true unless version
132
104
  rule.applies_to.yield(Gem::Version.create(version))
133
105
  end
134
106
 
107
+ def active_rules(options)
108
+ @rules.select do |rule|
109
+ matching_tags?(options[:tags], rule.tags) and
110
+ applies_to_version?(rule, options[:chef_version] || DEFAULT_CHEF_VERSION)
111
+ end
112
+ end
113
+
114
+ def cookbook_dir(file)
115
+ Pathname.new(File.join(File.dirname(file),
116
+ case File.basename(file)
117
+ when 'metadata.rb' then ''
118
+ when /\.erb$/ then '../..'
119
+ else '..'
120
+ end)).cleanpath
121
+ end
122
+
123
+ def dsl_method_for_file(file)
124
+ dir_mapping = {
125
+ 'libraries' => :library,
126
+ 'providers' => :provider,
127
+ 'resources' => :resource,
128
+ 'templates' => :template
129
+ }
130
+ if file.end_with? '.erb'
131
+ dir_mapping[File.basename(File.dirname(File.dirname(file)))]
132
+ else
133
+ dir_mapping[File.basename(File.dirname(file))]
134
+ end
135
+ end
136
+
137
+ # Return the files within a cookbook tree that we are interested in trying
138
+ # to match rules against.
139
+ def files_to_process(dirs, exclude_paths = [])
140
+ files = []
141
+ dirs.each do |dir|
142
+ exclusions = Dir.glob(exclude_paths.map{|p| File.join(dir, p)})
143
+ if File.directory? dir
144
+ cookbook_glob = '{metadata.rb,{attributes,libraries,providers,recipes,resources}/*.rb,templates/*/*.erb}'
145
+ files += (Dir.glob(File.join(dir, cookbook_glob)) +
146
+ Dir.glob(File.join(dir, "*/#{cookbook_glob}")) - exclusions)
147
+ else
148
+ files << dir unless exclusions.include?(dir)
149
+ end
150
+ end
151
+ files
152
+ end
153
+
135
154
  # Invoke the DSL method with the provided parameters.
136
- #
137
- # @param [Proc] match_method Proc to invoke
138
- # @param params Parameters for the proc
139
- # @return [Array] The returned matches
140
155
  def matches(match_method, *params)
141
156
  return [] unless match_method.respond_to?(:yield)
142
157
  matches = match_method.yield(*params)
143
158
  return [] unless matches.respond_to?(:each)
159
+
160
+ # We convert Nokogiri nodes to matches transparently
144
161
  matches.map do |m|
145
162
  if m.respond_to?(:node_name)
146
163
  match(m)
@@ -152,49 +169,35 @@ module FoodCritic
152
169
  end.flatten
153
170
  end
154
171
 
155
- # Return the files within a cookbook tree that we are interested in trying
156
- # to match rules against.
157
- #
158
- # @param [Array<String>] dirs The cookbook path(s)
159
- # @return [Array] The files underneath the provided paths to be
160
- # processed.
161
- def files_to_process(dirs)
162
- files = []
172
+ # We use the Gherkin (Cucumber) syntax to specify tags.
173
+ def matching_tags?(tag_expr, tags)
174
+ Gherkin::TagExpression.new(tag_expr).eval(tags.map do |t|
175
+ Gherkin::Formatter::Model::Tag.new(t, 1)
176
+ end)
177
+ end
163
178
 
164
- dirs.each do |dir|
165
- if File.directory? dir
166
- cookbook_glob = '{attributes,providers,recipes,resources}/*.rb'
167
- files += Dir.glob(File.join(dir, cookbook_glob)) +
168
- Dir.glob(File.join(dir, "*/#{cookbook_glob}"))
169
- else
170
- files << dir
171
- end
179
+ def per_cookbook_rules(last_dir, file)
180
+ yield if last_dir != cookbook_dir(file)
181
+ end
182
+
183
+ def sanity_check_cookbook_paths(cookbook_paths)
184
+ raise ArgumentError, "Cookbook paths are required" if cookbook_paths.nil?
185
+ cookbook_paths = Array(cookbook_paths)
186
+ if cookbook_paths.empty?
187
+ raise ArgumentError, "Cookbook paths cannot be empty"
172
188
  end
189
+ cookbook_paths
190
+ end
173
191
 
174
- files
192
+ def setup_defaults(options)
193
+ {:tags => [], :fail_tags => [],
194
+ :include_rules => [], :exclude_paths => []}.merge(options)
175
195
  end
176
196
 
177
- # Whether to fail the build.
178
- #
179
- # @param [Array] fail_tags The tags that should cause the build to fail, or
180
- # special value 'any' for any tag.
181
- # @param [Set] matched_tags The tags of warnings we have matches for
182
- # @return [Boolean] True if the build should be failed
183
197
  def should_fail_build?(fail_tags, matched_tags)
184
198
  return false if fail_tags.empty?
185
199
  matched_tags.any?{|tags| matching_tags?(fail_tags, tags)}
186
200
  end
187
201
 
188
- # Evaluate the specified tags
189
- #
190
- # @param [Array] tag_expr Tag expressions
191
- # @param [Array] tags Tags to match against
192
- # @return [Boolean] True if the tags match
193
- def matching_tags?(tag_expr, tags)
194
- Gherkin::TagExpression.new(tag_expr).eval(tags.map do |t|
195
- Gherkin::Formatter::Model::Tag.new(t, 1)
196
- end)
197
- end
198
-
199
202
  end
200
203
  end