foodcritic 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: