foodcritic 0.11.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/foodcritic.rb CHANGED
@@ -9,7 +9,7 @@ require_relative 'foodcritic/chef'
9
9
  require_relative 'foodcritic/command_line'
10
10
  require_relative 'foodcritic/domain'
11
11
  require_relative 'foodcritic/error_checker'
12
- require_relative 'foodcritic/helpers'
12
+ require_relative 'foodcritic/api'
13
13
  require_relative 'foodcritic/dsl'
14
14
  require_relative 'foodcritic/linter'
15
15
  require_relative 'foodcritic/output'
@@ -3,36 +3,55 @@ require 'nokogiri'
3
3
  module FoodCritic
4
4
 
5
5
  # Helper methods that form part of the Rules DSL.
6
- module Helpers
6
+ module Api
7
7
 
8
8
  include FoodCritic::Chef
9
- include FoodCritic::Chef::Search
10
9
 
11
- # Create a match from the specified node.
10
+ # Find attribute accesses by type.
12
11
  #
13
- # @param [Nokogiri::XML::Node] node The node to create a match for
14
- # @return [Hash] Hash with the matched node name and position with the recipe
15
- def match(node)
16
- pos = node.xpath('descendant::pos').first
17
- {:matched => node.respond_to?(:name) ? node.name : '', :line => pos['line'], :column => pos['column']}
18
- end
12
+ # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
13
+ # @param [Symbol] type The approach used to access the attributes
14
+ # (:string, :symbol or :vivified).
15
+ # @param [Boolean] ignore_calls Exclude attribute accesses that mix
16
+ # strings/symbols with dot notation. Defaults to false.
17
+ # @return [Array] The matching nodes if any
18
+ def attribute_access(ast, options = {})
19
+ options = {:type => :any, :ignore_calls => false}.merge!(options)
20
+ return [] unless ast.respond_to?(:xpath)
21
+ unless [:string, :symbol, :vivified].include?(options[:type])
22
+ raise ArgumentError, "Node type not recognised"
23
+ end
19
24
 
20
- # Create a match for a specified file. Use this if the presence of the file triggers the warning rather than content.
21
- #
22
- # @param [String] file The filename to create a match for
23
- # @return [Hash] Hash with the match details
24
- # @see FoodCritic::Helpers#match
25
- def file_match(file)
26
- {:filename => file, :matched => file, :line => 1, :column => 1}
25
+ (if options[:type] == :vivified
26
+ calls = ast.xpath(%q{//*[self::call or self::field]
27
+ [is_att_type(vcall/ident/@value) or
28
+ is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
29
+ calls.select do |call|
30
+ call.xpath("aref/args_add_block").size == 0 and
31
+ (call.xpath("descendant::ident").size > 1 and
32
+ ! chef_dsl_methods.include?(call.xpath("ident/@value").to_s.to_sym))
33
+ end
34
+ else
35
+ type = (options[:type] == :string) ? 'tstring_content' : options[:type]
36
+ expr = '//*[self::aref_field or self::aref]'
37
+ expr += '[is_att_type(descendant::ident'
38
+ expr += '[not(ancestor::aref/call)]' if options[:ignore_calls]
39
+ expr += "/@value)]/descendant::#{type}"
40
+ ast.xpath(expr, AttFilter.new)
41
+ end
42
+ ).sort
27
43
  end
28
44
 
29
45
  # Does the specified recipe check for Chef Solo?
30
46
  #
31
47
  # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
32
- # @return [Boolean] True if there is a test for Chef::Config[:solo] in the recipe
48
+ # @return [Boolean] True if there is a test for Chef::Config[:solo] in the
49
+ # recipe
33
50
  def checks_for_chef_solo?(ast)
34
- ! ast.xpath(%q{//if/aref[count(descendant::const[@value = 'Chef' or @value = 'Config']) = 2 and
35
- count(descendant::ident[@value='solo']) > 0]}).empty?
51
+ raise_unless_xpath!(ast)
52
+ ! ast.xpath(%q{//if/aref[count(descendant::const[@value = 'Chef' or
53
+ @value = 'Config']) = 2
54
+ and count(descendant::ident[@value='solo']) > 0]}).empty?
36
55
  end
37
56
 
38
57
  # Is the chef-solo-search library available?
@@ -40,93 +59,144 @@ module FoodCritic
40
59
  # @param [String] recipe_path The path to the current recipe
41
60
  # @return [Boolean] True if the chef-solo-search library is available.
42
61
  def chef_solo_search_supported?(recipe_path)
43
- search_libs = Dir[File.join(Pathname.new(File.join(recipe_path, '../../..')).realpath, "*/libraries/search.rb")]
62
+ return false if recipe_path.nil? || ! File.exists?(recipe_path)
63
+ cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
64
+ search_libs = Dir[File.join(cbk_tree_path.realpath, "*/libraries/search.rb")]
44
65
  search_libs.any? do |lib|
45
- ! read_file(lib).xpath(%q{//class[count(descendant::const[@value='Chef' or @value='Recipe']) = 2]/
46
- descendant::def/ident[@value='search']}).empty?
66
+ ! read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
67
+ ) = 1]/descendant::def/ident[@value='search']}).empty?
47
68
  end
48
69
  end
49
70
 
50
- # Searches performed by the specified recipe.
71
+ # The name of the cookbook containing the specified file.
51
72
  #
52
- # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
53
- # @return [Boolean] True if the recipe performs a search
54
- def searches(ast)
55
- ast.xpath("//fcall/ident[@value = 'search']")
73
+ # @param [String] file The file in the cookbook
74
+ # @return [String] The name of the containing cookbook
75
+ def cookbook_name(file)
76
+ raise ArgumentError, 'File cannot be nil or empty' if file.to_s.empty?
77
+ until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty? do
78
+ file = File.absolute_path(File.dirname(file.to_s))
79
+ end
80
+ file = File.dirname(file) unless File.extname(file).empty?
81
+ File.basename(file)
56
82
  end
57
83
 
58
- # Searches performed by the specified recipe that are literal strings. Searches with a query formed from a
59
- # subexpression will be ignored.
84
+ # The dependencies declared in cookbook metadata.
60
85
  #
61
- # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
62
- # @return [Nokogiri::XML::Node] The matching nodes
63
- def literal_searches(ast)
64
- ast.xpath("//method_add_arg[fcall/ident/@value = 'search' and count(descendant::string_embexpr) = 0]/descendant::tstring_content")
86
+ # @param [Nokogiri::XML::Node] ast The metadata rb AST
87
+ # @return [Array] List of cookbooks depended on
88
+ def declared_dependencies(ast)
89
+ raise_unless_xpath!(ast)
90
+ deps = ast.xpath(%q{//command[ident/@value='depends']/
91
+ descendant::args_add/descendant::tstring_content})
92
+ # handle quoted word arrays
93
+ var_ref = ast.xpath(%q{//command[ident/@value='depends']/
94
+ descendant::var_ref/ident})
95
+ unless var_ref.empty?
96
+ deps += ast.xpath(%Q{//block_var/params/ident#{var_ref.first['value']}/
97
+ ancestor::method_add_block/call/descendant::tstring_content})
98
+ end
99
+ deps.map{|dep| dep['value']}
100
+ end
101
+
102
+ # Create a match for a specified file. Use this if the presence of the file
103
+ # triggers the warning rather than content.
104
+ #
105
+ # @param [String] file The filename to create a match for
106
+ # @return [Hash] Hash with the match details
107
+ # @see FoodCritic::Api#match
108
+ def file_match(file)
109
+ raise ArgumentError, "Filename cannot be nil" if file.nil?
110
+ {:filename => file, :matched => file, :line => 1, :column => 1}
111
+ end
112
+
113
+ # Find Chef resources of the specified type.
114
+ # TODO: Include blockless resources
115
+ #
116
+ # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
117
+ # @param [Hash] options The find options
118
+ # @option [Symbol] :type The type of resource to look for (or :any for all
119
+ # resources)
120
+ # @return [Array] AST nodes of Chef resources.
121
+ def find_resources(ast, options = {})
122
+ options = {:type => :any}.merge!(options)
123
+ return [] unless ast.respond_to?(:xpath)
124
+ scope_type = ''
125
+ scope_type = "[@value='#{options[:type]}']" unless options[:type] == :any
126
+ ast.xpath("//method_add_block[command/ident#{scope_type}]")
127
+ end
128
+
129
+ # Retrieve the recipes that are included within the given recipe AST.
130
+ #
131
+ # @param [Nokogiri::XML::Node] ast The recipe AST
132
+ # @return [Hash] include_recipe nodes keyed by included recipe name
133
+ def included_recipes(ast)
134
+ raise_unless_xpath!(ast)
135
+ # we only support literal strings, ignoring sub-expressions
136
+ included = ast.xpath(%q{//command[ident/@value = 'include_recipe' and
137
+ count(descendant::string_embexpr) = 0]/descendant::tstring_content})
138
+ included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
65
139
  end
66
140
 
67
141
  class AttFilter
68
142
  def is_att_type(value)
143
+ return [] unless value.respond_to?(:select)
69
144
  value.select{|n| %w{node default override set normal}.include?(n.to_s)}
70
145
  end
71
146
  end
72
147
 
73
- # Find attribute accesses by type.
148
+ # Searches performed by the specified recipe that are literal strings.
149
+ # Searches with a query formed from a subexpression will be ignored.
74
150
  #
75
151
  # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
76
- # @param [Symbol] accessed_via The approach used to access the attributes (:string, :symbol or :vivified)
77
- # @param [Boolean] exclude_with_dots Exclude attribute accesses that mix strings/symbols with dot notation.
78
- # @return [Array] The matching nodes if any
79
- def attribute_access(ast, accessed_via, exclude_with_dots)
80
- (if accessed_via == :vivified
81
- calls = ast.xpath(%Q{//*[self::call or self::field][is_att_type(vcall/ident/@value) or
82
- is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
83
- calls.select do |call|
84
- call.xpath("aref/args_add_block").size == 0 and (call.xpath("descendant::ident").size > 1 and
85
- ! dsl_methods.include?(call.xpath("ident/@value").to_s.to_sym))
86
- end
87
- else
88
- accessed_via = 'tstring_content' if accessed_via == :string
89
- expr = '//*[self::aref_field or self::aref][is_att_type(descendant::ident'
90
- expr += '[not(ancestor::aref/call)]' if exclude_with_dots
91
- expr += "/@value)]/descendant::#{accessed_via}"
92
- ast.xpath(expr, AttFilter.new)
93
- end
94
- ).sort
152
+ # @return [Array] The matching nodes
153
+ def literal_searches(ast)
154
+ return [] unless ast.respond_to?(:xpath)
155
+ ast.xpath("//method_add_arg[fcall/ident/@value = 'search' and
156
+ count(descendant::string_embexpr) = 0]/descendant::tstring_content")
95
157
  end
96
158
 
97
- # Find Chef resources of the specified type.
98
- # TODO: Include blockless resources
159
+ # Create a match from the specified node.
99
160
  #
100
- # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check
101
- # @param [String] type The type of resource to look for (or nil for all resources)
102
- # @return [Array] AST nodes of Chef resources.
103
- def find_resources(ast, type = nil)
104
- ast.xpath(%Q{//method_add_block[command/ident#{type.nil? ? '' : "[@value='#{type}']"}]})
161
+ # @param [Nokogiri::XML::Node] node The node to create a match for
162
+ # @return [Hash] Hash with the matched node name and position with the recipe
163
+ def match(node)
164
+ raise_unless_xpath!(node)
165
+ pos = node.xpath('descendant::pos').first
166
+ return nil if pos.nil?
167
+ {:matched => node.respond_to?(:name) ? node.name : '',
168
+ :line => pos['line'].to_i, :column => pos['column'].to_i}
105
169
  end
106
170
 
107
- # Return the type, e.g. 'package' for a given resource
171
+ # Does the provided string look like an Operating System command? This is a
172
+ # rough heuristic to be taken with a pinch of salt.
108
173
  #
109
- # @param [Nokogiri::XML::Node] resource The resource AST
110
- # @return [String] The type of resource
111
- def resource_type(resource)
112
- resource.xpath('string(command/ident/@value)')
174
+ # @param [String] str The string to check
175
+ # @return [Boolean] True if this string might be an OS command
176
+ def os_command?(str)
177
+ str.start_with?('grep ', 'which ') or # common commands
178
+ str.include?('|') or # probably a pipe, could be alternation
179
+ str.match(/^[\w]+$/) or # command name only
180
+ str.match(/ --?[a-z]/i) # command-line flag
113
181
  end
114
182
 
115
- # Retrieve the name attribute associated with the specified resource.
183
+ # Read the AST for the given Ruby source file
116
184
  #
117
- # @param [Nokogiri::XML::Node] resource The resource AST to lookup the name attribute under
118
- # @return [String] The name attribute value
119
- def resource_name(resource)
120
- resource.xpath('string(command//tstring_content/@value)')
185
+ # @param [String] file The file to read
186
+ # @return [Nokogiri::XML::Node] The recipe AST
187
+ def read_ast(file)
188
+ build_xml(Ripper::SexpBuilder.new(File.read(file)).parse)
121
189
  end
122
190
 
123
191
  # Retrieve a single-valued attribute from the specified resource.
124
192
  #
193
+ # @param [Nokogiri::XML::Node] resource The resource AST to lookup the
194
+ # attribute under
125
195
  # @param [String] name The attribute name
126
- # @param [Nokogiri::XML::Node] resource The resource AST to lookup the attribute under
127
196
  # @return [String] The attribute value for the specified attribute
128
- def resource_attribute(name, resource)
129
- resource_attributes(resource)[name]
197
+ def resource_attribute(resource, name)
198
+ raise ArgumentError, "Attribute name cannot be empty" if name.empty?
199
+ resource_attributes(resource)[name.to_s]
130
200
  end
131
201
 
132
202
  # Retrieve all attributes from the specified resource.
@@ -134,8 +204,11 @@ module FoodCritic
134
204
  # @param [Nokogiri::XML::Node] resource The resource AST
135
205
  # @return [Hash] The resource attributes
136
206
  def resource_attributes(resource)
137
- atts = {:name => resource_name(resource)}
138
- resource.xpath('do_block/descendant::command[count(ancestor::do_block) = 1]').each do |att|
207
+ atts = {}
208
+ name = resource_name(resource)
209
+ atts[:name] = name unless name.empty?
210
+ resource.xpath('do_block/descendant::command
211
+ [count(ancestor::do_block) = 1]').each do |att|
139
212
  if att.xpath('descendant::symbol').empty?
140
213
  att_value = att.xpath('string(descendant::tstring_content/@value)')
141
214
  else
@@ -146,20 +219,10 @@ module FoodCritic
146
219
  atts
147
220
  end
148
221
 
149
- # Retrieve all resources of a given type
150
- #
151
- # @param [Nokogiri::XML::Node] ast The recipe AST
152
- # @return [Hash] The matching resources
153
- def resources_by_type(ast)
154
- result = Hash.new{|hash, key| hash[key] = Array.new}
155
- find_resources(ast).each{|resource| result[resource_type(resource)] << resource}
156
- result
157
- end
158
-
159
222
  # Retrieve the attributes as a hash for all resources of a given type.
160
223
  #
161
224
  # @param [Nokogiri::XML::Node] ast The recipe AST
162
- # @return [Hash] An array of resource attributes keyed by type.
225
+ # @return [Hash] Resources keyed by type, with an array for each
163
226
  def resource_attributes_by_type(ast)
164
227
  result = {}
165
228
  resources_by_type(ast).each do |type,resources|
@@ -168,47 +231,75 @@ module FoodCritic
168
231
  result
169
232
  end
170
233
 
171
- # Retrieve the recipes that are included within the given recipe AST.
234
+ # Retrieve the name attribute associated with the specified resource.
235
+ #
236
+ # @param [Nokogiri::XML::Node] resource The resource AST to lookup the name
237
+ # attribute under
238
+ # @return [String] The name attribute value
239
+ def resource_name(resource)
240
+ raise_unless_xpath!(resource)
241
+ resource.xpath('string(command//tstring_content/@value)')
242
+ end
243
+
244
+ # Retrieve all resources of a given type
172
245
  #
173
246
  # @param [Nokogiri::XML::Node] ast The recipe AST
174
- # @return [Hash] include_recipe nodes keyed by included recipe name
175
- def included_recipes(ast)
176
- # we only support literal strings, ignoring sub-expressions
177
- included = ast.xpath(%q{//command[ident/@value = 'include_recipe' and count(descendant::string_embexpr) = 0]/
178
- descendant::tstring_content})
179
- Hash[included.map{|recipe|recipe['value']}.zip(included)]
247
+ # @return [Hash] The matching resources
248
+ def resources_by_type(ast)
249
+ raise_unless_xpath!(ast)
250
+ result = Hash.new{|hash, key| hash[key] = Array.new}
251
+ find_resources(ast).each do |resource|
252
+ result[resource_type(resource)] << resource
253
+ end
254
+ result
180
255
  end
181
256
 
182
- # The name of the cookbook containing the specified file.
257
+ # Return the type, e.g. 'package' for a given resource
183
258
  #
184
- # @param [String] file The file in the cookbook
185
- # @return [String] The name of the containing cookbook
186
- def cookbook_name(file)
187
- File.basename(File.absolute_path(File.join(File.dirname(file), '..')))
259
+ # @param [Nokogiri::XML::Node] resource The resource AST
260
+ # @return [String] The type of resource
261
+ def resource_type(resource)
262
+ raise_unless_xpath!(resource)
263
+ type = resource.xpath('string(command/ident/@value)')
264
+ if type.empty?
265
+ raise ArgumentError, "Provided AST node is not a resource"
266
+ end
267
+ type
188
268
  end
189
269
 
190
- # The dependencies declared in cookbook metadata.
270
+ # Does the provided string look like ruby code?
191
271
  #
192
- # @param [Nokogiri::XML::Node] ast The metadata rb AST
193
- # @return [Array] List of cookbooks depended on
194
- def declared_dependencies(ast)
195
- deps = ast.xpath("//command[ident/@value='depends']/descendant::args_add/descendant::tstring_content")
196
- # handle quoted word arrays
197
- var_ref = ast.xpath("//command[ident/@value='depends']/descendant::var_ref/ident")
198
- deps += ast.xpath(%Q{//block_var/params/ident#{var_ref.first['value']}/ancestor::method_add_block/
199
- call/descendant::tstring_content}) unless var_ref.empty?
200
- deps.map{|dep| dep['value']}
272
+ # @param [String] str The string to check for rubiness
273
+ # @return [Boolean] True if this string could be syntactically valid Ruby
274
+ def ruby_code?(str)
275
+ str = str.to_s
276
+ return false if str.empty?
277
+ checker = FoodCritic::ErrorChecker.new(str)
278
+ checker.parse
279
+ ! checker.error?
201
280
  end
202
281
 
203
- # If the provided node is the line / column information.
282
+ # Searches performed by the specified recipe.
204
283
  #
205
- # @param [Nokogiri::XML::Node] node A node within the AST
206
- # @return [Boolean] True if this node holds the position data
207
- def position_node?(node)
208
- node.respond_to?(:length) and node.length == 2 and node.respond_to?(:all?) and node.all?{|child| child.respond_to?(:to_i)}
284
+ # @param [Nokogiri::XML::Node] ast The AST of the cookbook recipe to check.
285
+ # @return [Array] The AST nodes in the recipe where searches are performed
286
+ def searches(ast)
287
+ return [] unless ast.respond_to?(:xpath)
288
+ ast.xpath("//fcall/ident[@value = 'search']")
209
289
  end
210
290
 
211
- # Recurse the nested arrays provided by Ripper to create a tree we can more easily apply expressions to.
291
+ # The list of standard cookbook sub-directories.
292
+ #
293
+ # @return [Array] The standard list of directories.
294
+ def standard_cookbook_subdirs
295
+ %w{attributes definitions files libraries providers recipes resources
296
+ templates}
297
+ end
298
+
299
+ private
300
+
301
+ # Recurse the nested arrays provided by Ripper to create a tree we can more
302
+ # easily apply expressions to.
212
303
  #
213
304
  # @param [Array] node The AST
214
305
  # @param [Nokogiri::XML::Document] doc The document being constructed
@@ -228,7 +319,8 @@ module FoodCritic
228
319
  xml_node.add_child(pos)
229
320
  else
230
321
  if child.respond_to?(:first)
231
- n = Nokogiri::XML::Node.new(child.first.to_s.gsub(/[^a-z_]/, ''), doc)
322
+ n = Nokogiri::XML::Node.new(
323
+ child.first.to_s.gsub(/[^a-z_]/, ''), doc)
232
324
  xml_node.add_child(build_xml(child, doc, n))
233
325
  else
234
326
  xml_node['value'] = child.to_s unless child.nil?
@@ -239,34 +331,19 @@ module FoodCritic
239
331
  xml_node
240
332
  end
241
333
 
242
- # Read the AST for the given Ruby file
243
- #
244
- # @param [String] file The file to read
245
- # @return [Nokogiri::XML::Node] The recipe AST
246
- def read_file(file)
247
- build_xml(Ripper::SexpBuilder.new(IO.read(file)).parse)
248
- end
249
-
250
- # Does the provided string look like ruby code?
334
+ # If the provided node is the line / column information.
251
335
  #
252
- # @param [String] str The string to check for rubiness
253
- # @return [Boolean] True if this string could be syntactically valid Ruby
254
- def ruby_code?(str)
255
- checker = FoodCritic::ErrorChecker.new(str)
256
- checker.parse
257
- ! checker.error?
336
+ # @param [Nokogiri::XML::Node] node A node within the AST
337
+ # @return [Boolean] True if this node holds the position data
338
+ def position_node?(node)
339
+ node.respond_to?(:length) and node.length == 2 and
340
+ node.respond_to?(:all?) and node.all?{|child| child.respond_to?(:to_i)}
258
341
  end
259
342
 
260
- # Does the provided string look like an Operating System command? This is a rough heuristic to be taken with a
261
- # pinch of salt.
262
- #
263
- # @param [String] str The string to check
264
- # @return [Boolean] True if this string might be an OS command
265
- def os_command?(str)
266
- str.start_with?('grep ', 'which ') or # common commands
267
- str.include?('|') or # probably a pipe, could be alternation
268
- str.match(/^[\w]+$/) or # command name only
269
- str.match(/ --?[a-z]/i) # command-line flag
343
+ def raise_unless_xpath!(ast)
344
+ unless ast.respond_to?(:xpath)
345
+ raise ArgumentError, "AST must support #xpath"
346
+ end
270
347
  end
271
348
 
272
349
  end
@@ -3,31 +3,50 @@ module FoodCritic
3
3
  # Encapsulates functions that previously were calls to the Chef gem.
4
4
  module Chef
5
5
 
6
- def load_metadata
7
- metadata_path = File.join(File.dirname(__FILE__), '..', '..', 'chef_dsl_metadata.json')
8
- @dsl_metadata ||= Yajl::Parser.parse(IO.read(metadata_path), :symbolize_keys => true)
9
- end
10
-
11
6
  # The set of methods in the Chef DSL
12
7
  #
13
8
  # @return [Array] Array of method symbols
14
- def dsl_methods
9
+ def chef_dsl_methods
15
10
  load_metadata
16
11
  @dsl_metadata[:dsl_methods].map{|m| m.to_sym}
17
12
  end
18
13
 
19
- # Is the specified attribute valid for the type of resource?
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.
20
16
  #
21
17
  # @param [Symbol] resource_type The type of Chef resource
22
18
  # @param [Symbol] attribute_name The attribute name
23
- def attribute?(resource_type, attribute_name)
19
+ def resource_attribute?(resource_type, attribute_name)
20
+ if resource_type.to_s.empty? || attribute_name.to_s.empty?
21
+ raise ArgumentError, "Arguments cannot be nil or empty."
22
+ end
24
23
  load_metadata
25
24
  resource_attributes = @dsl_metadata[:attributes]
26
- return true unless resource_attributes.include?(resource_type)
27
- resource_attributes[resource_type].include?(attribute_name.to_s)
25
+ return true unless resource_attributes.include?(resource_type.to_sym)
26
+ resource_attributes[resource_type.to_sym].include?(attribute_name.to_s)
27
+ end
28
+
29
+ # Is this a valid Lucene query?
30
+ #
31
+ # @param [String] query The query to check for syntax errors
32
+ # @return [Boolean] True if the query is well-formed
33
+ def valid_query?(query)
34
+ raise ArgumentError, "Query cannot be nil or empty" if query.to_s.empty?
35
+ search = FoodCritic::Chef::Search.new
36
+ search.create_parser(search.chef_search_grammars)
37
+ search.parser? ? (! search.parser.parse(query.to_s).nil?) : true
28
38
  end
29
39
 
30
- module Search
40
+ private
41
+
42
+ def load_metadata
43
+ metadata_path = File.join(File.dirname(__FILE__), '..', '..',
44
+ 'chef_dsl_metadata.json')
45
+ @dsl_metadata ||= Yajl::Parser.parse(IO.read(metadata_path),
46
+ :symbolize_keys => true)
47
+ end
48
+
49
+ class Search
31
50
 
32
51
  # The search grammars that ship with any Chef gems installed locally.
33
52
  # These are returned in descending version order (a newer Chef version
@@ -43,7 +62,7 @@ module FoodCritic
43
62
  # Create the search parser from the first loadable grammar.
44
63
  #
45
64
  # @param [Array] grammar_paths Full paths to candidate treetop grammars
46
- def load_search_parser(grammar_paths)
65
+ def create_parser(grammar_paths)
47
66
  @search_parser ||= grammar_paths.inject(nil) do |parser,lucene_grammar|
48
67
  begin
49
68
  break parser unless parser.nil?
@@ -59,18 +78,14 @@ module FoodCritic
59
78
  # Has the search parser been loaded?
60
79
  #
61
80
  # @return [Boolean] True if the search parser has been loaded.
62
- def search_parser_loaded?
81
+ def parser?
63
82
  ! @search_parser.nil?
64
83
  end
65
84
 
66
- # Is this a valid Lucene query?
67
- #
68
- # @param [String] query The query to check for syntax errors
69
- # @return [Boolean] True if the query is well-formed
70
- def valid_query?(query)
71
- load_search_parser(chef_search_grammars)
72
- search_parser_loaded? ? (! @search_parser.parse(query).nil?) : true
85
+ def parser
86
+ @search_parser
73
87
  end
88
+
74
89
  end
75
90
  end
76
91
 
@@ -3,22 +3,23 @@ module FoodCritic
3
3
  # Command line parsing.
4
4
  class CommandLine
5
5
 
6
- include FoodCritic::Chef::Search
7
-
8
6
  # Create a new instance of CommandLine
9
7
  #
10
8
  # @param [Array] args The command line arguments
11
9
  def initialize(args)
12
10
  @args = args
11
+ @original_args = args.dup
13
12
  @options = {}
14
- @options[:fail_tags] = []; @options[:tags] = []
13
+ @options[:fail_tags] = []; @options[:tags] = []; @options[:include_rules] = []
15
14
  @parser = OptionParser.new do |opts|
16
15
  opts.banner = 'foodcritic [cookbook_path]'
17
16
  opts.on("-r", "--[no-]repl", "Drop into a REPL for interactive rule editing.") {|r|options[:repl] = r}
18
17
  opts.on("-t", "--tags TAGS", "Only check against rules with the specified tags.") {|t|options[:tags] << t}
19
18
  opts.on("-f", "--epic-fail TAGS", "Fail the build if any of the specified tags are matched.") {|t|options[:fail_tags] << t}
20
19
  opts.on("-C", "--[no-]context", "Show lines matched against rather than the default summary.") {|c|options[:context] = c}
20
+ opts.on("-I", "--include PATH", "Additional rule file path(s) to load.") {|i|options[:include_rules] << i}
21
21
  opts.on("-S", "--search-grammar PATH", "Specify grammar to use when validating search syntax.") {|s|options[:search_grammar] = s}
22
+ opts.on("-V", "--version", "Display version."){|v|options[:version] = true}
22
23
  end
23
24
  @parser.parse!(args) unless show_help?
24
25
  end
@@ -37,6 +38,20 @@ module FoodCritic
37
38
  @parser.help
38
39
  end
39
40
 
41
+ # Show the current version to the end user?
42
+ #
43
+ # @return [Boolean] True if the version should be shown.
44
+ def show_version?
45
+ @options.key?(:version) and @original_args != ['-v']
46
+ end
47
+
48
+ # The version string.
49
+ #
50
+ # @return [String] Current installed version.
51
+ def version
52
+ "foodcritic #{FoodCritic::VERSION}"
53
+ end
54
+
40
55
  # If the cookbook path provided is valid
41
56
  #
42
57
  # @return [Boolean] True if the path is a directory that exists.
@@ -57,8 +72,9 @@ module FoodCritic
57
72
  def valid_grammar?
58
73
  return true unless options.key?(:search_grammar)
59
74
  return false unless File.exists?(options[:search_grammar])
60
- load_search_parser([options[:search_grammar]])
61
- search_parser_loaded?
75
+ search = FoodCritic::Chef::Search.new
76
+ search.create_parser([options[:search_grammar]])
77
+ search.parser?
62
78
  end
63
79
 
64
80
  # If matches should be shown with context rather than the default summary display.
@@ -51,7 +51,8 @@ module FoodCritic
51
51
 
52
52
  # A rule to be matched against.
53
53
  class Rule
54
- attr_accessor :code, :name, :cookbook, :recipe, :provider, :resource, :tags
54
+ attr_accessor :code, :name, :cookbook, :recipe, :provider, :resource
55
+ attr_writer :tags
55
56
 
56
57
  # Create a new rule
57
58
  #
@@ -62,6 +63,10 @@ module FoodCritic
62
63
  @tags = [code]
63
64
  end
64
65
 
66
+ def tags
67
+ ['any'] + @tags
68
+ end
69
+
65
70
  # Returns a string representation of this rule.
66
71
  #
67
72
  # @return [String] Rule as a string.
@@ -70,4 +75,4 @@ module FoodCritic
70
75
  end
71
76
  end
72
77
 
73
- end
78
+ end
@@ -7,12 +7,13 @@ module FoodCritic
7
7
  class RuleDsl
8
8
  attr_reader :rules
9
9
 
10
- include Helpers
10
+ include Api
11
11
 
12
12
  # Define a new rule
13
13
  #
14
14
  # @param [String] code The short unique identifier for this rule, e.g. 'FC001'
15
- # @param [String] name The short descriptive name of this rule presented to the end user.
15
+ # @param [String] name The short descriptive name of this rule presented to
16
+ # the end user.
16
17
  # @param [Block] block The rule definition
17
18
  def rule(code, name, &block)
18
19
  @rules = [] if @rules.nil?
@@ -57,14 +58,19 @@ module FoodCritic
57
58
 
58
59
  # Load the ruleset
59
60
  #
60
- # @param [String] filename The path to the ruleset to load
61
- # @return [Array] The loaded rules, ready to be matched against provided cookbooks.
62
- def self.load(filename, with_repl)
61
+ # @param [Array] paths The paths to the rulesets to load
62
+ # @return [Array] The loaded rules, ready to be matched against provided
63
+ # cookbooks.
64
+ def self.load(paths, with_repl)
63
65
  dsl = RuleDsl.new
64
- dsl.instance_eval(File.read(filename), filename)
66
+ paths.map do |path|
67
+ File.directory?(path) ? Dir["#{path}/**/*.rb"].sort : path
68
+ end.flatten.each do |path|
69
+ dsl.instance_eval(File.read(path), path)
70
+ end
65
71
  dsl.instance_eval { binding.pry } if with_repl
66
72
  dsl.rules
67
73
  end
68
74
  end
69
75
 
70
- end
76
+ end
@@ -8,18 +8,22 @@ module FoodCritic
8
8
  # The main entry point for linting your Chef cookbooks.
9
9
  class Linter
10
10
 
11
- include FoodCritic::Helpers
11
+ include FoodCritic::Api
12
12
 
13
- # Perform option parsing from the provided arguments and do a lint check based on those arguments.
13
+ # Perform option parsing from the provided arguments and do a lint check
14
+ # based on those arguments.
14
15
  #
15
16
  # @param [Array] args The command-line arguments to parse
16
- # @return [Array] Pair - the first item is string output, the second is the exit code.
17
+ # @return [Array] Pair - the first item is string output, the second is the
18
+ # exit code.
17
19
  def self.check(cmd_line)
18
20
  return [cmd_line.help, 0] if cmd_line.show_help?
21
+ return [cmd_line.version, 0] if cmd_line.show_version?
19
22
  if ! cmd_line.valid_grammar?
20
23
  [cmd_line.help, 4]
21
24
  elsif cmd_line.valid_path?
22
- review = FoodCritic::Linter.new.check(cmd_line.cookbook_path, cmd_line.options)
25
+ review = FoodCritic::Linter.new.check(cmd_line.cookbook_path,
26
+ cmd_line.options)
23
27
  [review, review.failed? ? 3 : 0]
24
28
  else
25
29
  [cmd_line.help, 2]
@@ -31,22 +35,27 @@ module FoodCritic
31
35
 
32
36
  end
33
37
 
34
- # Review the cookbooks at the provided path, identifying potential improvements.
38
+ # Review the cookbooks at the provided path, identifying potential
39
+ # improvements.
35
40
  #
36
- # @param [String] cookbook_path The file path to an individual cookbook directory
41
+ # @param [String] cookbook_path The file path to an individual cookbook
42
+ # directory
37
43
  # @param [Hash] options Options to apply to the linting
38
44
  # @option options [Array] tags The tags to filter rules based on
39
45
  # @option options [Array] fail_tags The tags to fail the build on
40
- # @return [FoodCritic::Review] A review of your cookbooks, with any warnings issued.
46
+ # @return [FoodCritic::Review] A review of your cookbooks, with any
47
+ # warnings issued.
41
48
  def check(cookbook_path, options)
49
+ raise ArgumentError, "Cookbook path is required" if cookbook_path.nil?
42
50
  @last_cookbook_path, @last_options = cookbook_path, options
43
51
  load_rules unless defined? @rules
44
52
  warnings = []; last_dir = nil; matched_rule_tags = Set.new
45
53
 
46
- active_rules = @rules.select{|rule| matching_tags?(options[:tags], rule.tags)}
54
+ active_rules = @rules.select{|rule| matching_tags?(options[:tags],
55
+ rule.tags)}
47
56
  files_to_process(cookbook_path).each do |file|
48
57
  cookbook_dir = Pathname.new(File.join(File.dirname(file), '..')).cleanpath
49
- ast = read_file(file)
58
+ ast = read_ast(file)
50
59
  active_rules.each do |rule|
51
60
  rule_matches = matches(rule.recipe, ast, file)
52
61
  rule_matches += matches(rule.provider, ast, file) if File.basename(File.dirname(file)) == 'providers'
@@ -54,13 +63,14 @@ module FoodCritic
54
63
  rule_matches += matches(rule.cookbook, cookbook_dir) if last_dir != cookbook_dir
55
64
  rule_matches.each do |match|
56
65
  warnings << Warning.new(rule, {:filename => file}.merge(match))
57
- matched_rule_tags += rule.tags
66
+ matched_rule_tags << rule.tags
58
67
  end
59
68
  end
60
69
  last_dir = cookbook_dir
61
70
  end
62
71
 
63
- @review = Review.new(cookbook_path, warnings, should_fail_build?(options[:fail_tags], matched_rule_tags))
72
+ @review = Review.new(cookbook_path, warnings,
73
+ should_fail_build?(options[:fail_tags], matched_rule_tags))
64
74
 
65
75
  binding.pry if options[:repl]
66
76
  @review
@@ -73,12 +83,14 @@ module FoodCritic
73
83
 
74
84
  # Load the rules from the (fairly unnecessary) DSL.
75
85
  def load_rules
76
- @rules = RuleDsl.load(File.join(File.dirname(__FILE__), 'rules.rb'), @last_options[:repl])
86
+ @rules = RuleDsl.load([File.join(File.dirname(__FILE__), 'rules.rb')] +
87
+ @last_options[:include_rules], @last_options[:repl])
77
88
  end
78
89
 
79
90
  alias_method :reset_rules, :load_rules
80
91
 
81
- # Convenience method to retrieve the last review. Intended to be used from the REPL.
92
+ # Convenience method to retrieve the last review. Intended to be used from
93
+ # the REPL.
82
94
  #
83
95
  # @return [Review] The last review performed.
84
96
  def review
@@ -98,29 +110,27 @@ module FoodCritic
98
110
  matches.respond_to?(:each) ? matches : []
99
111
  end
100
112
 
101
- # Return the files within a cookbook tree that we are interested in trying to match rules against.
113
+ # Return the files within a cookbook tree that we are interested in trying
114
+ # to match rules against.
102
115
  #
103
116
  # @param [String] dir The cookbook directory
104
- # @return [Array] The files underneath the provided directory to be processed.
117
+ # @return [Array] The files underneath the provided directory to be
118
+ # processed.
105
119
  def files_to_process(dir)
106
120
  return [dir] unless File.directory? dir
107
121
  Dir.glob(File.join(dir, '{attributes,providers,recipes,resources}/*.rb')) +
108
- Dir.glob(File.join(dir, '*/{attributes,providers,recipes,resources}/*.rb'))
122
+ Dir.glob(File.join(dir, '*/{attributes,providers,recipes,resources}/*.rb'))
109
123
  end
110
124
 
111
125
  # Whether to fail the build.
112
126
  #
113
- # @param [Array] fail_tags The tags that should cause the build to fail, or special value 'any' for any tag.
127
+ # @param [Array] fail_tags The tags that should cause the build to fail, or
128
+ # special value 'any' for any tag.
114
129
  # @param [Set] matched_tags The tags of warnings we have matches for
115
130
  # @return [Boolean] True if the build should be failed
116
131
  def should_fail_build?(fail_tags, matched_tags)
117
- if fail_tags.empty?
118
- false
119
- elsif fail_tags.include? 'any'
120
- true
121
- else
122
- matching_tags?(fail_tags, matched_tags)
123
- end
132
+ return false if fail_tags.empty?
133
+ matched_tags.any?{|tags| matching_tags?(fail_tags, tags)}
124
134
  end
125
135
 
126
136
  # Evaluate the specified tags
@@ -129,7 +139,9 @@ module FoodCritic
129
139
  # @param [Array] tags Tags to match against
130
140
  # @return [Boolean] True if the tags match
131
141
  def matching_tags?(tag_expr, tags)
132
- Gherkin::TagExpression.new(tag_expr).eval(tags.map{|t| Gherkin::Formatter::Model::Tag.new(t, 1)})
142
+ Gherkin::TagExpression.new(tag_expr).eval(tags.map do |t|
143
+ Gherkin::Formatter::Model::Tag.new(t, 1)
144
+ end)
133
145
  end
134
146
 
135
147
  end
@@ -1,7 +1,7 @@
1
1
  rule "FC001", "Use strings in preference to symbols to access node attributes" do
2
2
  tags %w{style attributes}
3
3
  recipe do |ast|
4
- attribute_access(ast, :symbol, false).map{|ar| match(ar)}
4
+ attribute_access(ast, :type => :symbol).map{|ar| match(ar)}
5
5
  end
6
6
  end
7
7
 
@@ -23,8 +23,8 @@ end
23
23
  rule "FC004", "Use a service resource to start and stop services" do
24
24
  tags %w{style services}
25
25
  recipe do |ast|
26
- find_resources(ast, 'execute').find_all do |cmd|
27
- cmd_str = (resource_attribute('command', cmd) || resource_name(cmd)).to_s
26
+ find_resources(ast, :type => 'execute').find_all do |cmd|
27
+ cmd_str = (resource_attribute(cmd, 'command') || resource_name(cmd)).to_s
28
28
  cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') || cmd_str.start_with?('/sbin/service ') ||
29
29
  cmd_str.start_with?('start ') || cmd_str.start_with?('stop ') || cmd_str.start_with?('invoke-rc.d ')
30
30
  end.map{|cmd| match(cmd)}
@@ -58,11 +58,16 @@ rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
58
58
  recipe do |ast,filename|
59
59
  metadata_path = Pathname.new(File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
60
60
  next unless File.exists? metadata_path
61
- undeclared = included_recipes(ast).keys.map{|recipe|recipe.split('::').first} - [cookbook_name(filename)] -
62
- declared_dependencies(read_file(metadata_path))
63
- included_recipes(ast).map do |recipe, resource|
64
- match(resource) if undeclared.include?(recipe) || undeclared.any?{|u| recipe.start_with?("#{u}::")}
65
- end.compact
61
+ undeclared = included_recipes(ast).keys.map do |recipe|
62
+ recipe.split('::').first
63
+ end - [cookbook_name(filename)] -
64
+ declared_dependencies(read_ast(metadata_path))
65
+ included_recipes(ast).map do |recipe, include_stmts|
66
+ if undeclared.include?(recipe) ||
67
+ undeclared.any?{|u| recipe.start_with?("#{u}::")}
68
+ include_stmts.map{|i| match(i)}
69
+ end
70
+ end.flatten.compact
66
71
  end
67
72
  end
68
73
 
@@ -71,7 +76,7 @@ rule "FC008", "Generated cookbook metadata needs updating" do
71
76
  cookbook do |filename|
72
77
  metadata_path = Pathname.new(File.join(filename, 'metadata.rb')).cleanpath
73
78
  next unless File.exists? metadata_path
74
- md = read_file(metadata_path)
79
+ md = read_ast(metadata_path)
75
80
  {'maintainer' => 'YOUR_COMPANY_NAME', 'maintainer_email' => 'YOUR_EMAIL'}.map do |field,value|
76
81
  md.xpath(%Q{//command[ident/@value='#{field}']/descendant::tstring_content[@value='#{value}']}).map do |m|
77
82
  match(m).merge(:filename => metadata_path)
@@ -86,8 +91,12 @@ rule "FC009", "Resource attribute not recognised" do
86
91
  matches = []
87
92
  resource_attributes_by_type(ast).each do |type,resources|
88
93
  resources.each do |resource|
89
- resource.keys.map{|att|att.to_sym}.reject{|att| attribute?(type.to_sym, att)}.each do |invalid_att|
90
- matches << match(find_resources(ast, type).find{|res|resource_attributes(res).include?(invalid_att.to_s)})
94
+ resource.keys.map{|att|att.to_sym}.reject do |att|
95
+ resource_attribute?(type.to_sym, att)
96
+ end.each do |invalid_att|
97
+ matches << match(find_resources(ast, :type => type).find do |res|
98
+ resource_attributes(res).include?(invalid_att.to_s)
99
+ end)
91
100
  end
92
101
  end
93
102
  end
@@ -120,8 +129,8 @@ end
120
129
  rule "FC013", "Use file_cache_path rather than hard-coding tmp paths" do
121
130
  tags %w{style files}
122
131
  recipe do |ast|
123
- find_resources(ast, 'remote_file').find_all do |download|
124
- path = (resource_attribute('path', download) || resource_name(download)).to_s
132
+ find_resources(ast, :type => 'remote_file').find_all do |download|
133
+ path = (resource_attribute(download, 'path') || resource_name(download)).to_s
125
134
  path.start_with?('/tmp/')
126
135
  end.map{|download| match(download)}
127
136
  end
@@ -130,7 +139,7 @@ end
130
139
  rule "FC014", "Consider extracting long ruby_block to library" do
131
140
  tags %w{style libraries}
132
141
  recipe do |ast|
133
- find_resources(ast, 'ruby_block').find_all do |rb|
142
+ find_resources(ast, :type => 'ruby_block').find_all do |rb|
134
143
  ! rb.xpath("//fcall[ident/@value='block' and count(ancestor::*) = 8]/../../do_block[count(descendant::*) > 100]").empty?
135
144
  end.map{|block| match(block)}
136
145
  end
@@ -172,9 +181,9 @@ end
172
181
  rule "FC019", "Access node attributes in a consistent manner" do
173
182
  tags %w{style attributes}
174
183
  cookbook do |cookbook_dir|
175
- asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map{|file| {:path => file, :ast => read_file(file)}}
184
+ asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].map{|file| {:path => file, :ast => read_ast(file)}}
176
185
  types = [:string, :symbol, :vivified].map{|type| {:access_type => type, :count => files.map do |file|
177
- attribute_access(file[:ast], type, true).tap{|ast|
186
+ attribute_access(file[:ast], :type => type, :ignore_calls => true).tap{|ast|
178
187
  asts[type] = {:ast => ast, :path => file[:path]} if (! ast.empty?) and (! asts.has_key?(type))
179
188
  }.size
180
189
  end.inject(:+)}}.reject{|type| type[:count] == 0}
@@ -1,4 +1,4 @@
1
1
  module FoodCritic
2
2
  # The current version of foodcritic
3
- VERSION = '0.11.1'
3
+ VERSION = '1.0.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foodcritic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,25 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-29 00:00:00.000000000 Z
12
+ date: 2012-03-04 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: json
16
- requirement: &2152841480 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: 1.4.4
22
- - - <=
23
- - !ruby/object:Gem::Version
24
- version: 1.6.1
25
- type: :runtime
26
- prerelease: false
27
- version_requirements: *2152841480
28
14
  - !ruby/object:Gem::Dependency
29
15
  name: gherkin
30
- requirement: &2152869780 !ruby/object:Gem::Requirement
16
+ requirement: &2157955580 !ruby/object:Gem::Requirement
31
17
  none: false
32
18
  requirements:
33
19
  - - ~>
@@ -35,10 +21,10 @@ dependencies:
35
21
  version: 2.8.0
36
22
  type: :runtime
37
23
  prerelease: false
38
- version_requirements: *2152869780
24
+ version_requirements: *2157955580
39
25
  - !ruby/object:Gem::Dependency
40
26
  name: gist
41
- requirement: &2152867120 !ruby/object:Gem::Requirement
27
+ requirement: &2157954980 !ruby/object:Gem::Requirement
42
28
  none: false
43
29
  requirements:
44
30
  - - ~>
@@ -46,10 +32,10 @@ dependencies:
46
32
  version: 2.0.4
47
33
  type: :runtime
48
34
  prerelease: false
49
- version_requirements: *2152867120
35
+ version_requirements: *2157954980
50
36
  - !ruby/object:Gem::Dependency
51
37
  name: nokogiri
52
- requirement: &2152882420 !ruby/object:Gem::Requirement
38
+ requirement: &2157954500 !ruby/object:Gem::Requirement
53
39
  none: false
54
40
  requirements:
55
41
  - - ~>
@@ -57,10 +43,10 @@ dependencies:
57
43
  version: 1.5.0
58
44
  type: :runtime
59
45
  prerelease: false
60
- version_requirements: *2152882420
46
+ version_requirements: *2157954500
61
47
  - !ruby/object:Gem::Dependency
62
48
  name: pry
63
- requirement: &2152880280 !ruby/object:Gem::Requirement
49
+ requirement: &2157953920 !ruby/object:Gem::Requirement
64
50
  none: false
65
51
  requirements:
66
52
  - - ~>
@@ -68,10 +54,10 @@ dependencies:
68
54
  version: 0.9.7.4
69
55
  type: :runtime
70
56
  prerelease: false
71
- version_requirements: *2152880280
57
+ version_requirements: *2157953920
72
58
  - !ruby/object:Gem::Dependency
73
59
  name: pry-doc
74
- requirement: &2152877780 !ruby/object:Gem::Requirement
60
+ requirement: &2157953460 !ruby/object:Gem::Requirement
75
61
  none: false
76
62
  requirements:
77
63
  - - ~>
@@ -79,10 +65,10 @@ dependencies:
79
65
  version: 0.3.0
80
66
  type: :runtime
81
67
  prerelease: false
82
- version_requirements: *2152877780
68
+ version_requirements: *2157953460
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rak
85
- requirement: &2152876160 !ruby/object:Gem::Requirement
71
+ requirement: &2157952960 !ruby/object:Gem::Requirement
86
72
  none: false
87
73
  requirements:
88
74
  - - ~>
@@ -90,10 +76,10 @@ dependencies:
90
76
  version: '1.4'
91
77
  type: :runtime
92
78
  prerelease: false
93
- version_requirements: *2152876160
79
+ version_requirements: *2157952960
94
80
  - !ruby/object:Gem::Dependency
95
81
  name: treetop
96
- requirement: &2152891220 !ruby/object:Gem::Requirement
82
+ requirement: &2157952180 !ruby/object:Gem::Requirement
97
83
  none: false
98
84
  requirements:
99
85
  - - ~>
@@ -101,10 +87,10 @@ dependencies:
101
87
  version: 1.4.10
102
88
  type: :runtime
103
89
  prerelease: false
104
- version_requirements: *2152891220
90
+ version_requirements: *2157952180
105
91
  - !ruby/object:Gem::Dependency
106
92
  name: yajl-ruby
107
- requirement: &2152888700 !ruby/object:Gem::Requirement
93
+ requirement: &2157951540 !ruby/object:Gem::Requirement
108
94
  none: false
109
95
  requirements:
110
96
  - - ~>
@@ -112,7 +98,7 @@ dependencies:
112
98
  version: 1.1.0
113
99
  type: :runtime
114
100
  prerelease: false
115
- version_requirements: *2152888700
101
+ version_requirements: *2157951540
116
102
  description: Lint tool for Opscode Chef cookbooks.
117
103
  email:
118
104
  executables:
@@ -120,12 +106,12 @@ executables:
120
106
  extensions: []
121
107
  extra_rdoc_files: []
122
108
  files:
109
+ - lib/foodcritic/api.rb
123
110
  - lib/foodcritic/chef.rb
124
111
  - lib/foodcritic/command_line.rb
125
112
  - lib/foodcritic/domain.rb
126
113
  - lib/foodcritic/dsl.rb
127
114
  - lib/foodcritic/error_checker.rb
128
- - lib/foodcritic/helpers.rb
129
115
  - lib/foodcritic/linter.rb
130
116
  - lib/foodcritic/output.rb
131
117
  - lib/foodcritic/rules.rb
@@ -154,12 +140,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
140
  version: '0'
155
141
  segments:
156
142
  - 0
157
- hash: -3754378815808582034
143
+ hash: 4610293132888604111
158
144
  requirements: []
159
145
  rubyforge_project:
160
146
  rubygems_version: 1.8.10
161
147
  signing_key:
162
148
  specification_version: 3
163
- summary: foodcritic-0.11.1
149
+ summary: foodcritic-1.0.0
164
150
  test_files: []
165
151
  has_rdoc: