foodcritic 1.1.0 → 1.2.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.
@@ -117,7 +117,17 @@ module FoodCritic
117
117
  return [] unless ast.respond_to?(:xpath)
118
118
  scope_type = ''
119
119
  scope_type = "[@value='#{options[:type]}']" unless options[:type] == :any
120
- ast.xpath("//method_add_block[command/ident#{scope_type}]")
120
+ # XXX: include nested resources (provider actions)
121
+ no_actions = "[command/ident/@value != 'action']"
122
+ ast.xpath("//method_add_block[command/ident#{scope_type}]#{no_actions}")
123
+ end
124
+
125
+ # Helper to return a comparable version for a string.
126
+ #
127
+ # @param [String] version The version
128
+ # @return [Gem::Version] The comparable version
129
+ def gem_version(version)
130
+ Gem::Version.create(version)
121
131
  end
122
132
 
123
133
  # Retrieve the recipes that are included within the given recipe AST.
@@ -127,8 +137,9 @@ module FoodCritic
127
137
  def included_recipes(ast)
128
138
  raise_unless_xpath!(ast)
129
139
  # we only support literal strings, ignoring sub-expressions
130
- included = ast.xpath(%q{//command[ident/@value = 'include_recipe' and
131
- count(descendant::string_embexpr) = 0]/descendant::tstring_content})
140
+ included = ast.xpath(%q{//command[ident/@value = 'include_recipe'][count(
141
+ descendant::args_add) = 1][descendant::args_add/string_literal]/
142
+ descendant::tstring_content})
132
143
  included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
133
144
  end
134
145
 
@@ -213,6 +224,13 @@ module FoodCritic
213
224
  end
214
225
  atts[att.xpath('string(ident/@value)')] = att_value
215
226
  end
227
+ resource.xpath("do_block/descendant::method_add_block[
228
+ count(ancestor::do_block) = 1][brace_block | do_block]").each do |batt|
229
+ att_name = batt.xpath('string(method_add_arg/fcall/ident/@value)')
230
+ if att_name and ! att_name.empty? and batt.children.length > 1
231
+ atts[att_name] = batt.children[1]
232
+ end
233
+ end
216
234
  atts
217
235
  end
218
236
 
@@ -316,9 +334,20 @@ module FoodCritic
316
334
  xml_node.add_child(pos)
317
335
  else
318
336
  if child.respond_to?(:first)
319
- n = Nokogiri::XML::Node.new(
320
- child.first.to_s.gsub(/[^a-z_]/, ''), doc)
321
- xml_node.add_child(build_xml(child, doc, n))
337
+ if child.first.respond_to?(:first) and
338
+ child.first.first == :assoc_new
339
+ child.each do |c|
340
+ n = Nokogiri::XML::Node.new(
341
+ c.first.to_s.gsub(/[^a-z_]/, ''), doc)
342
+ c.drop(1).each do |a|
343
+ xml_node.add_child(build_xml(a, doc, n))
344
+ end
345
+ end
346
+ else
347
+ n = Nokogiri::XML::Node.new(
348
+ child.first.to_s.gsub(/[^a-z_]/, ''), doc)
349
+ xml_node.add_child(build_xml(child, doc, n))
350
+ end
322
351
  else
323
352
  xml_node['value'] = child.to_s unless child.nil?
324
353
  end
@@ -349,6 +378,7 @@ module FoodCritic
349
378
  expr += '[is_att_type(descendant::ident'
350
379
  expr += '[not(ancestor::aref/call)]' if options[:ignore_calls]
351
380
  expr += "/@value)]/descendant::#{type}"
381
+ expr += "[ident/@value != 'node']" if type == :symbol
352
382
  ast.xpath(expr, AttFilter.new).sort
353
383
  end
354
384
 
@@ -24,6 +24,10 @@ module FoodCritic
24
24
  "Fail the build if any of the specified tags are matched.") do |t|
25
25
  options[:fail_tags] << t
26
26
  end
27
+ opts.on("-c", "--chef-version VERSION",
28
+ "Only check against rules valid for this version of Chef.") do |c|
29
+ options[:chef_version] = c
30
+ end
27
31
  opts.on("-C", "--[no-]context",
28
32
  "Show lines matched against rather than the default summary.") do |c|
29
33
  options[:context] = c
@@ -37,7 +41,7 @@ module FoodCritic
37
41
  options[:search_grammar] = s
38
42
  end
39
43
  opts.on("-V", "--version",
40
- "Display version.") do |v|
44
+ "Display the foodcritic version.") do |v|
41
45
  options[:version] = true
42
46
  end
43
47
  end
@@ -57,7 +57,7 @@ module FoodCritic
57
57
 
58
58
  # A rule to be matched against.
59
59
  class Rule
60
- attr_accessor :code, :name, :cookbook, :recipe, :provider, :resource
60
+ attr_accessor :code, :name, :applies_to, :cookbook, :recipe, :provider, :resource
61
61
  attr_writer :tags
62
62
 
63
63
  # Create a new rule
@@ -69,6 +69,7 @@ module FoodCritic
69
69
  def initialize(code, name)
70
70
  @code, @name = code, name
71
71
  @tags = [code]
72
+ @applies_to = Proc.new {|version| true}
72
73
  end
73
74
 
74
75
  # The tags associated with this rule.
@@ -29,6 +29,13 @@ module FoodCritic
29
29
  rules.last.tags += tags
30
30
  end
31
31
 
32
+ # Limit the versions that this rule can be seen to apply to.
33
+ #
34
+ # @param [block] block Your version constraint logic.
35
+ def applies_to(&block)
36
+ rules.last.applies_to = block
37
+ end
38
+
32
39
  # Define a matcher that will be passed the AST with this method.
33
40
  #
34
41
  # @param [block] block Your implemented matcher that returns a match Hash.
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
  require 'ripper'
3
+ require 'rubygems'
3
4
  require 'gherkin/tag_expression'
4
5
  require 'set'
5
6
 
@@ -10,6 +11,9 @@ module FoodCritic
10
11
 
11
12
  include FoodCritic::Api
12
13
 
14
+ # The default version that will be used to determine relevant rules
15
+ DEFAULT_CHEF_VERSION = "0.10.8"
16
+
13
17
  # Perform option parsing from the provided arguments and do a lint check
14
18
  # based on those arguments.
15
19
  #
@@ -51,8 +55,10 @@ module FoodCritic
51
55
  load_rules unless defined? @rules
52
56
  warnings = []; last_dir = nil; matched_rule_tags = Set.new
53
57
 
54
- active_rules = @rules.select{|rule| matching_tags?(options[:tags],
55
- rule.tags)}
58
+ active_rules = @rules.select do |rule|
59
+ matching_tags?(options[:tags], rule.tags) and
60
+ applies_to_version?(rule, options[:chef_version] || DEFAULT_CHEF_VERSION)
61
+ end
56
62
  files_to_process(cookbook_path).each do |file|
57
63
  cookbook_dir = Pathname.new(
58
64
  File.join(File.dirname(file), '..')).cleanpath
@@ -107,6 +113,16 @@ module FoodCritic
107
113
 
108
114
  private
109
115
 
116
+ # Some rules are version specific.
117
+ #
118
+ # @param [FoodCritic::Rule] rule The rule determine applicability for
119
+ # @param [String] version The version of Chef
120
+ # @return [Boolean] True if the rule applies to this version of Chef
121
+ def applies_to_version?(rule, version)
122
+ return true unless version
123
+ rule.applies_to.yield(Gem::Version.create(version))
124
+ end
125
+
110
126
  # Invoke the DSL method with the provided parameters.
111
127
  #
112
128
  # @param [Proc] match_method Proc to invoke
@@ -9,9 +9,9 @@ end
9
9
  rule "FC002", "Avoid string interpolation where not required" do
10
10
  tags %w{style strings}
11
11
  recipe do |ast|
12
- ast.xpath(%q{//string_literal[count(descendant::string_embexpr) = 1 and
13
- count(string_add/tstring_content|string_add/string_add/tstring_content)
14
- = 0]})
12
+ ast.xpath(%q{//*[self::string_literal | self::assoc_new]/string_add[
13
+ count(descendant::string_embexpr) = 1 and
14
+ count(tstring_content|string_add/tstring_content) = 0]})
15
15
  end
16
16
  end
17
17
 
@@ -31,10 +31,10 @@ rule "FC004", "Use a service resource to start and stop services" do
31
31
  recipe do |ast|
32
32
  find_resources(ast, :type => 'execute').find_all do |cmd|
33
33
  cmd_str = (resource_attribute(cmd, 'command') || resource_name(cmd)).to_s
34
- cmd_str.include?('/etc/init.d') || cmd_str.start_with?('service ') ||
35
- cmd_str.start_with?('/sbin/service ') ||
36
- cmd_str.start_with?('start ') || cmd_str.start_with?('stop ') ||
37
- cmd_str.start_with?('invoke-rc.d ')
34
+ (cmd_str.include?('/etc/init.d') || ['service ', '/sbin/service ',
35
+ 'start ', 'stop ', 'invoke-rc.d '].any? do |service_cmd|
36
+ cmd_str.start_with?(service_cmd)
37
+ end) && %w{start stop restart reload}.any?{|a| cmd_str.include?(a)}
38
38
  end
39
39
  end
40
40
  end
@@ -45,7 +45,14 @@ rule "FC005", "Avoid repetition of resource declarations" do
45
45
  resources = find_resources(ast).map do |res|
46
46
  resource_attributes(res).merge({:type => resource_type(res),
47
47
  :ast => res})
48
- end.chunk{|res| res[:type]}.reject{|res| res[1].size < 3}
48
+ end.chunk do |res|
49
+ res[:type] +
50
+ res[:ast].xpath("ancestor::*[self::if | self::unless | self::elsif |
51
+ self::else | self::when | self::method_add_block/call][position() = 1]/
52
+ descendant::pos[position() = 1]").to_s +
53
+ res[:ast].xpath("ancestor::method_add_block/command[
54
+ ident/@value='action']/args_add_block/descendant::ident/@value").to_s
55
+ end.reject{|res| res[1].size < 3}
49
56
  resources.map do |cont_res|
50
57
  first_resource = cont_res[1][0][:ast]
51
58
  # we have contiguous resources of the same type, but do they share the
@@ -175,6 +182,7 @@ end
175
182
 
176
183
  rule "FC015", "Consider converting definition to a LWRP" do
177
184
  tags %w{style definitions lwrp}
185
+ applies_to {|version| version >= gem_version("0.7.12")}
178
186
  cookbook do |dir|
179
187
  Dir[File.join(dir, 'definitions', '*.rb')].reject do |entry|
180
188
  ['.', '..'].include? entry
@@ -184,6 +192,7 @@ end
184
192
 
185
193
  rule "FC016", "LWRP does not declare a default action" do
186
194
  tags %w{correctness lwrp}
195
+ applies_to {|version| version >= gem_version("0.7.12")}
187
196
  resource do |ast, filename|
188
197
  unless ["//ident/@value='default_action'",
189
198
  "//def/bodystmt/descendant::assign/
@@ -195,6 +204,9 @@ end
195
204
 
196
205
  rule "FC017", "LWRP does not notify when updated" do
197
206
  tags %w{correctness lwrp}
207
+ applies_to do |version|
208
+ version >= gem_version("0.7.12")
209
+ end
198
210
  provider do |ast, filename|
199
211
  if ast.xpath(%q{//call/*[self::vcall or self::var_ref/ident/
200
212
  @value='new_resource']/../
@@ -206,6 +218,7 @@ end
206
218
 
207
219
  rule "FC018", "LWRP uses deprecated notification syntax" do
208
220
  tags %w{style lwrp deprecated}
221
+ applies_to {|version| version >= gem_version("0.9.10")}
209
222
  provider do |ast|
210
223
  ast.xpath("//assign/var_field/ivar[@value='@updated']").map do |class_var|
211
224
  match(class_var)
@@ -241,6 +254,7 @@ end
241
254
 
242
255
  rule "FC020", "Conditional execution string attribute looks like Ruby" do
243
256
  tags %w{correctness}
257
+ applies_to {|version| version >= gem_version("0.7.4")}
244
258
  recipe do |ast, filename|
245
259
  conditions = ast.xpath(%q{//command[(ident/@value='only_if' or ident/
246
260
  @value='not_if') and descendant::tstring_content]}).map{|m| match(m)}
@@ -260,6 +274,7 @@ end
260
274
 
261
275
  rule "FC021", "Resource condition in provider may not behave as expected" do
262
276
  tags %w{correctness lwrp}
277
+ applies_to {|version| version >= gem_version("0.10.6")}
263
278
  provider do |ast|
264
279
  find_resources(ast).map do |resource|
265
280
  condition = resource.xpath(%q{//method_add_block/
@@ -274,6 +289,7 @@ end
274
289
 
275
290
  rule "FC022", "Resource condition within loop may not behave as expected" do
276
291
  tags %w{correctness}
292
+ applies_to {|version| version >= gem_version("0.10.6")}
277
293
  recipe do |ast|
278
294
  ast.xpath("//call[ident/@value='each']/../do_block").map do |loop|
279
295
  block_vars = loop.xpath("block_var/params/child::*").map do |n|
@@ -301,7 +317,8 @@ rule "FC023", "Prefer conditional attributes" do
301
317
  [@value='only_if' or @value='not_if']) = 0]/ancestor::*[self::if or
302
318
  self::unless][count(descendant::method_add_block[command/ident]) = 1]
303
319
  [count(stmts_add/method_add_block/call) = 0]
304
- [count(stmts_add/stmts_add) = 0]})
320
+ [count(stmts_add/stmts_add) = 0]
321
+ [count(descendant::*[self::else or self::elsif]) = 0]})
305
322
  end
306
323
  end
307
324
 
@@ -324,3 +341,21 @@ rule "FC024", "Consider adding platform equivalents" do
324
341
  end.flatten
325
342
  end
326
343
  end
344
+
345
+ rule "FC025", "Prefer chef_gem to compile-time gem install" do
346
+ tags %w{style deprecated}
347
+ applies_to {|version| version >= gem_version("0.10.10")}
348
+ recipe do |ast|
349
+ gem_install = ast.xpath("//stmts_add/assign[method_add_block[command/ident/
350
+ @value='gem_package'][do_block/stmts_add/command[ident/@value='action']
351
+ [descendant::ident/@value='nothing']]]")
352
+ gem_install.map do |install|
353
+ gem_var = install.xpath("var_field/ident/@value")
354
+ unless ast.xpath("//method_add_arg[call/var_ref/ident/@value='#{gem_var}']
355
+ [arg_paren/descendant::ident/@value='install' or
356
+ arg_paren/descendant::ident/@value='upgrade']").empty?
357
+ gem_install
358
+ end
359
+ end
360
+ end
361
+ end
@@ -1,4 +1,4 @@
1
1
  module FoodCritic
2
2
  # The current version of foodcritic
3
- VERSION = '1.1.0'
3
+ VERSION = '1.2.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.1.0
4
+ version: 1.2.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-03-25 00:00:00.000000000 Z
12
+ date: 2012-04-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gherkin
@@ -193,12 +193,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
193
  version: '0'
194
194
  segments:
195
195
  - 0
196
- hash: -4140544099475579299
196
+ hash: 700162912727168001
197
197
  requirements: []
198
198
  rubyforge_project:
199
199
  rubygems_version: 1.8.19
200
200
  signing_key:
201
201
  specification_version: 3
202
- summary: foodcritic-1.1.0
202
+ summary: foodcritic-1.2.0
203
203
  test_files: []
204
204
  has_rdoc: