foodcritic 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,7 +36,7 @@ module FoodCritic
36
36
  # Chef::Config['solo'] in the recipe
37
37
  def checks_for_chef_solo?(ast)
38
38
  raise_unless_xpath!(ast)
39
- ! ast.xpath(%q{//if/aref[count(descendant::const[@value = 'Chef' or
39
+ ! ast.xpath(%q{//if/*[self::aref or self::call][count(descendant::const[@value = 'Chef' or
40
40
  @value = 'Config']) = 2
41
41
  and
42
42
  ( count(descendant::ident[@value='solo']) > 0
@@ -137,13 +137,22 @@ module FoodCritic
137
137
  # Retrieve the recipes that are included within the given recipe AST.
138
138
  #
139
139
  # @param [Nokogiri::XML::Node] ast The recipe AST
140
+ # @param [Hash] options Options to filter recipes to include
141
+ # @option [Symbol] :with_partial_names Include string literals for recipes
142
+ # that have an embedded sub-expression.
140
143
  # @return [Hash] include_recipe nodes keyed by included recipe name
141
- def included_recipes(ast)
144
+ def included_recipes(ast, options = {:with_partial_names => true})
142
145
  raise_unless_xpath!(ast)
146
+
147
+ filter = ['[count(descendant::args_add) = 1]']
148
+ unless options[:with_partial_names]
149
+ filter << '[count(descendant::string_embexpr) = 0]'
150
+ end
151
+
143
152
  # we only support literal strings, ignoring sub-expressions
144
- included = ast.xpath(%q{//command[ident/@value = 'include_recipe'][count(
145
- descendant::args_add) = 1][descendant::args_add/string_literal]/
146
- descendant::tstring_content})
153
+ included = ast.xpath(%Q{//command[ident/@value = 'include_recipe']
154
+ #{filter.join}
155
+ [descendant::args_add/string_literal]/descendant::tstring_content})
147
156
  included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
148
157
  end
149
158
 
@@ -179,6 +188,61 @@ module FoodCritic
179
188
  :line => pos['line'].to_i, :column => pos['column'].to_i}
180
189
  end
181
190
 
191
+ # Decode resource notifications.
192
+ #
193
+ # @param [Nokogiri::XML::Node] ast The AST to check for notifications.
194
+ # @return [Array] A flat array of notifications. The resource_name may be
195
+ # a string or a Node if the resource name is an expression.
196
+ def notifications(ast)
197
+ return [] unless ast.respond_to?(:xpath)
198
+ ast.xpath('descendant::command[ident/@value="notifies" or
199
+ ident/@value="subscribes"]').map do |notifies|
200
+
201
+ params = notifies.xpath('descendant::method_add_arg[fcall/ident/
202
+ @value="resources"]/descendant::assoc_new')
203
+ timing = notifies.xpath('args_add_block/args_add/symbol_literal[last()]/
204
+ symbol/ident[1]/@value')
205
+ if params.empty?
206
+ target = notifies.xpath('args_add_block/args_add/
207
+ descendant::tstring_content/@value').to_s
208
+ match = target.match(/^([^\[]+)\[(.*)\]$/)
209
+ next unless match
210
+ resource_type, resource_name =
211
+ match.captures.tap{|m| m[0] = m[0].to_sym}
212
+ if notifies.xpath('descendant::string_embexpr').empty?
213
+ next if resource_name.empty?
214
+ else
215
+ resource_name =
216
+ notifies.xpath('args_add_block/args_add/string_literal')
217
+ end
218
+ else
219
+ resource_type = params.xpath('symbol[1]/ident/@value').to_s.to_sym
220
+ resource_name = params.xpath('string_add[1][count(../
221
+ descendant::string_add) = 1]/tstring_content/@value').to_s
222
+ resource_name = params if resource_name.empty?
223
+ end
224
+ {
225
+ :type =>
226
+ notifies.xpath('ident/@value[1]').to_s.to_sym,
227
+ :resource_type => resource_type,
228
+ :resource_name => resource_name,
229
+ :style => params.empty? ? :new : :old,
230
+ :action =>
231
+ notifies.xpath('descendant::symbol[1]/ident/@value').to_s.to_sym,
232
+ :timing =>
233
+ if timing.empty?
234
+ :delayed
235
+ else
236
+ case timing.first.to_s.to_sym
237
+ when :immediately, :immediate then :immediate
238
+ else timing.first.to_s.to_sym
239
+ end
240
+ end
241
+ }
242
+
243
+ end.compact
244
+ end
245
+
182
246
  # Does the provided string look like an Operating System command? This is a
183
247
  # rough heuristic to be taken with a pinch of salt.
184
248
  #
@@ -224,6 +288,10 @@ module FoodCritic
224
288
  att_value =
225
289
  if ! att.xpath('args_add_block[count(descendant::args_add)>1]').empty?
226
290
  att.xpath('args_add_block').first
291
+ elsif ! att.xpath('args_add_block/args_add/
292
+ var_ref/kw[@value="true" or @value="false"]').empty?
293
+ att.xpath('string(args_add_block/args_add/
294
+ var_ref/kw/@value)') == 'true'
227
295
  elsif att.xpath('descendant::symbol').empty?
228
296
  att.xpath('string(descendant::tstring_content/@value)')
229
297
  else
@@ -406,8 +474,8 @@ module FoodCritic
406
474
 
407
475
  def vivified_attribute_access(ast, cookbook_dir)
408
476
  calls = ast.xpath(%q{//*[self::call or self::field]
409
- [is_att_type(vcall/ident/@value) or
410
- is_att_type(var_ref/ident/@value)][@value='.']}, AttFilter.new)
477
+ [is_att_type(vcall/ident/@value) or is_att_type(var_ref/ident/@value)]
478
+ [@value='.'][count(following-sibling::arg_paren) = 0]}, AttFilter.new)
411
479
  calls.select do |call|
412
480
  call.xpath("aref/args_add_block").size == 0 and
413
481
  (call.xpath("descendant::ident").size > 1 and
@@ -78,7 +78,7 @@ module FoodCritic
78
78
  rule_matches += matches(rule.provider, ast, file)
79
79
  end
80
80
  if File.basename(File.dirname(file)) == 'resources'
81
- rule_matches += matches(rule.resource, ast, file)
81
+ rule_matches += matches(rule.resource, ast, file)
82
82
  end
83
83
  if last_dir != cookbook_dir
84
84
  rule_matches += matches(rule.cookbook, cookbook_dir)
@@ -85,11 +85,12 @@ rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
85
85
  metadata_path =Pathname.new(
86
86
  File.join(File.dirname(filename), '..', 'metadata.rb')).cleanpath
87
87
  next unless File.exists? metadata_path
88
- undeclared = included_recipes(ast).keys.map do |recipe|
88
+ actual_included = included_recipes(ast, :with_partial_names => false)
89
+ undeclared = actual_included.keys.map do |recipe|
89
90
  recipe.split('::').first
90
91
  end - [cookbook_name(filename)] -
91
92
  declared_dependencies(read_ast(metadata_path))
92
- included_recipes(ast).map do |recipe, include_stmts|
93
+ actual_included.map do |recipe, include_stmts|
93
94
  if undeclared.include?(recipe) ||
94
95
  undeclared.any?{|u| recipe.start_with?("#{u}::")}
95
96
  include_stmts
@@ -378,3 +379,79 @@ rule "FC026", "Conditional execution block attribute contains only string" do
378
379
  end
379
380
  end
380
381
  end
382
+
383
+ rule "FC027", "Resource sets internal attribute" do
384
+ tags %w{correctness}
385
+ recipe do |ast|
386
+ find_resources(ast, :type => :service).map do |service|
387
+ service unless (resource_attributes(service).keys &
388
+ ['enabled', 'running']).empty?
389
+ end.compact
390
+ end
391
+ end
392
+
393
+ rule "FC028", "Incorrect #platform? usage" do
394
+ tags %w{correctness}
395
+ recipe do |ast|
396
+ ast.xpath(%q{//*[self::call | self::command_call]
397
+ [(var_ref|vcall)/ident/@value='node']
398
+ [ident/@value="platform?"]})
399
+ end
400
+ end
401
+
402
+ rule "FC029", "No leading cookbook name in recipe metadata" do
403
+ tags %w{correctness metadata}
404
+ cookbook do |filename|
405
+ metadata_path = Pathname.new(File.join(filename, 'metadata.rb')).cleanpath
406
+ next unless File.exists? metadata_path
407
+ read_ast(metadata_path).xpath('//command[ident/@value="recipe"]').map do |declared_recipe|
408
+ next unless declared_recipe.xpath('count(//vcall|//var_ref)').to_i == 0
409
+ recipe_name = declared_recipe.xpath('args_add_block/
410
+ descendant::tstring_content[1]/@value').to_s
411
+ unless recipe_name.empty? ||
412
+ recipe_name.split('::').first == cookbook_name(filename.to_s)
413
+ declared_recipe
414
+ end
415
+ end.compact.map {|m| match(m).merge(:filename => metadata_path.to_s) }
416
+ end
417
+ end
418
+
419
+ rule "FC030", "Cookbook contains debugger breakpoints" do
420
+ tags %w{annoyances}
421
+ cookbook do |cookbook_dir|
422
+ Dir[cookbook_dir + '**/*.rb'].map do |ruby_file|
423
+ read_ast(ruby_file).xpath('//call[(vcall|var_ref)/ident/@value="binding"]
424
+ [ident/@value="pry"]').map do |bp|
425
+ match(bp).merge({:filename => ruby_file})
426
+ end
427
+ end +
428
+ Dir[cookbook_dir + 'templates/**/*.erb'].map do |template_file|
429
+ IO.read(template_file).lines.with_index(1).map do |line, line_number|
430
+ # Not properly parsing the template
431
+ if line =~ /binding\.pry/
432
+ {:filename => template_file, :line => line_number}
433
+ end
434
+ end.compact
435
+ end
436
+ end
437
+ end
438
+
439
+ rule "FC031", "Cookbook without metadata file" do
440
+ tags %w{correctness metadata}
441
+ cookbook do |filename|
442
+ if ! File.exists?(File.join(filename, 'metadata.rb'))
443
+ [file_match(File.join(filename, 'metadata.rb'))]
444
+ end
445
+ end
446
+ end
447
+
448
+ rule "FC032", "Invalid notification timing" do
449
+ tags %w{correctness notifications}
450
+ recipe do |ast|
451
+ find_resources(ast).select do |resource|
452
+ notifications(resource).any? do |notification|
453
+ ! [:delayed, :immediate].include? notification[:timing]
454
+ end
455
+ end
456
+ end
457
+ end
@@ -1,4 +1,4 @@
1
1
  module FoodCritic
2
2
  # The current version of foodcritic
3
- VERSION = '1.3.1'
3
+ VERSION = '1.4.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: 1.3.1
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-10 00:00:00.000000000 Z
12
+ date: 2012-06-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gherkin
@@ -181,12 +181,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
181
  version: '0'
182
182
  segments:
183
183
  - 0
184
- hash: 2247396416415780367
184
+ hash: -3352294596244898334
185
185
  requirements: []
186
186
  rubyforge_project:
187
187
  rubygems_version: 1.8.19
188
188
  signing_key:
189
189
  specification_version: 3
190
- summary: foodcritic-1.3.1
190
+ summary: foodcritic-1.4.0
191
191
  test_files: []
192
192
  has_rdoc: