foodcritic 16.1.1 → 16.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,7 +34,7 @@ module FoodCritic
34
34
 
35
35
  def to_s
36
36
  @rules.sort_by(&:code)
37
- .map { |r| r.to_s }.join("\n")
37
+ .map(&:to_s).join("\n")
38
38
  end
39
39
 
40
40
  end
@@ -50,12 +50,12 @@ module FoodCritic
50
50
 
51
51
  # If any of the warnings in this review have failed or not.
52
52
  def failed?
53
- warnings.any? { |w| w.failed? }
53
+ warnings.any?(&:failed?)
54
54
  end
55
55
 
56
56
  # Returns an array of warnings that are marked as failed.
57
57
  def failures
58
- warnings.select { |w| w.failed? }
58
+ warnings.select(&:failed?)
59
59
  end
60
60
 
61
61
  # Returns a string representation of this review. This representation is
@@ -76,8 +76,8 @@ module FoodCritic
76
76
  # A rule to be matched against.
77
77
  class Rule
78
78
  attr_accessor :code, :name, :applies_to, :cookbook, :attributes, :recipe,
79
- :provider, :resource, :metadata, :library, :template, :role,
80
- :environment
79
+ :provider, :resource, :metadata, :library, :template, :role,
80
+ :environment
81
81
 
82
82
  attr_writer :tags
83
83
 
@@ -1,3 +1,4 @@
1
+ require "open-uri"
1
2
  require "pathname"
2
3
 
3
4
  module FoodCritic
@@ -76,7 +77,9 @@ module FoodCritic
76
77
  paths.map do |path|
77
78
  File.directory?(path) ? Dir["#{path}/**/*.rb"].sort : path
78
79
  end.flatten.each do |path|
79
- dsl.instance_eval(File.read(path), path)
80
+ rule_file = open(path)
81
+ dsl.instance_eval(rule_file.read, path)
82
+ rule_file.close
80
83
  end
81
84
  dsl.rules
82
85
  end
@@ -1,3 +1,4 @@
1
+ require "open-uri"
1
2
  require "optparse"
2
3
  require "ripper"
3
4
  require "set"
@@ -9,7 +10,7 @@ module FoodCritic
9
10
 
10
11
  # The default version that will be used to determine relevant rules. This
11
12
  # can be over-ridden at the command line with the `--chef-version` option.
12
- DEFAULT_CHEF_VERSION = "15.1.36".freeze
13
+ DEFAULT_CHEF_VERSION = "15.4.45".freeze
13
14
  attr_reader :chef_version
14
15
 
15
16
  # Perform a lint check. This method is intended for use by the command-line
@@ -19,6 +20,7 @@ module FoodCritic
19
20
  # The first item is the string output, the second is exit code.
20
21
  return [cmd_line.help, 0] if cmd_line.show_help?
21
22
  return [cmd_line.version, 0] if cmd_line.show_version?
23
+
22
24
  if !cmd_line.valid_grammar?
23
25
  [cmd_line.help, 4]
24
26
  elsif cmd_line.list_rules?
@@ -112,8 +114,8 @@ module FoodCritic
112
114
  # Convert the matches into warnings
113
115
  matches.each do |match|
114
116
  warnings << Warning.new(state[:rule],
115
- { filename: state[:file] }.merge(match),
116
- options)
117
+ { filename: state[:file] }.merge(match),
118
+ options)
117
119
  matched_rule_tags << state[:rule].tags
118
120
  end
119
121
  end
@@ -133,16 +135,19 @@ module FoodCritic
133
135
 
134
136
  if dsl_method_for_file(state[:file])
135
137
  cbk_matches += matches(state[:rule].send(
136
- dsl_method_for_file(state[:file])), state[:ast], state[:file])
138
+ dsl_method_for_file(state[:file])
139
+ ), state[:ast], state[:file])
137
140
  end
138
141
 
139
142
  per_cookbook_rules(state[:last_dir], state[:file]) do
140
143
  if File.basename(state[:file]) == "metadata.rb"
141
144
  cbk_matches += matches(
142
- state[:rule].metadata, state[:ast], state[:file])
145
+ state[:rule].metadata, state[:ast], state[:file]
146
+ )
143
147
  end
144
148
  cbk_matches += matches(
145
- state[:rule].cookbook, cookbook_dir(state[:file]))
149
+ state[:rule].cookbook, cookbook_dir(state[:file])
150
+ )
146
151
  end
147
152
 
148
153
  cbk_matches
@@ -197,6 +202,7 @@ module FoodCritic
197
202
  # Some rules are version specific.
198
203
  def applies_to_version?(rule, version)
199
204
  return true unless version
205
+
200
206
  rule.applies_to.yield(Gem::Version.create(version))
201
207
  end
202
208
 
@@ -217,7 +223,8 @@ module FoodCritic
217
223
 
218
224
  # if a rule file has been specified use that. Otherwise use the .foodcritic file in the CB
219
225
  tags = if @options[:rule_file]
220
- raise "ERROR: Could not find the specified rule file at #{@options[:rule_file]}" unless File.exist?(@options[:rule_file])
226
+ raise "ERROR: Could not find the specified rule file at #{@options[:rule_file]}" if is_local_file?(@options[:rule_file]) && ! File.exist?(@options[:rule_file])
227
+
221
228
  parse_rule_file(@options[:rule_file])
222
229
  else
223
230
  File.exist?("#{cookbook}/.foodcritic") ? parse_rule_file("#{cookbook}/.foodcritic") : []
@@ -234,7 +241,9 @@ module FoodCritic
234
241
  def parse_rule_file(file)
235
242
  tags = []
236
243
  begin
237
- tag_text = File.read file
244
+ rule_file = open(file)
245
+ tag_text = rule_file.read
246
+ rule_file.close
238
247
  tags = tag_text.split(/\s/)
239
248
  rescue
240
249
  raise "ERROR: Could not read or parse the specified rule file at #{file}"
@@ -339,9 +348,15 @@ module FoodCritic
339
348
  end.flatten
340
349
  end
341
350
 
351
+ # Determine if the rule file name is a local file or a url
352
+ def is_local_file?(file_name)
353
+ ! /^(ftps*:|https*:|file:)/.match(file_name)
354
+ end
355
+
342
356
  # Invoke the DSL method with the provided parameters.
343
357
  def matches(match_method, *params)
344
358
  return [] unless match_method.respond_to?(:yield)
359
+
345
360
  matches = match_method.yield(*params)
346
361
  return [] unless matches.respond_to?(:each)
347
362
 
@@ -133,13 +133,15 @@ module FoodCritic
133
133
  is_variable = true unless notify.xpath("args_add_block/args_add//args_add[aref or vcall or call or var_ref]").empty?
134
134
  string_val = notify.xpath("descendant::args_add/string_literal/string_add/tstring_content/@value").first
135
135
  symbol_val = notify.xpath('descendant::args_add/args_add//symbol/ident/@value |
136
- descendant::dyna_symbol[1]/xstring_add/tstring_content/@value').first
136
+ descendant::dyna_symbol[1]/xstring_add/tstring_content/@value |
137
+ descendant::args_add/args_add//dyna_symbol/string_add/tstring_content/@value').first
137
138
 
138
139
  # 1) return a nil if the action is a variable like node['foo']['bar']
139
140
  # 2) return the symbol if it exists
140
141
  # 3) return the string since we're positive that we're not a symbol or variable
141
142
  return nil if is_variable
142
143
  return symbol_val.value.to_sym unless symbol_val.nil?
144
+
143
145
  string_val.value
144
146
  end
145
147
 
@@ -128,7 +128,7 @@ module FoodCritic
128
128
  fmt << 40 + colors.index(bg.to_s) if bg
129
129
  fmt << attrs.index(attr.to_s) if attr
130
130
  if fmt
131
- puts "#{escape % fmt.join(';')}#{text}#{escape % 0}"
131
+ puts "#{escape % fmt.join(";")}#{text}#{escape % 0}"
132
132
  else
133
133
  puts text
134
134
  end
@@ -1,8 +1,8 @@
1
1
  rule "FC001",
2
- "Use strings in preference to symbols to access node attributes" do
3
- tags %w{style attributes}
4
- recipe do |ast|
5
- # node.run_state is not actually an attribute so ignore that
6
- attribute_access(ast, type: :symbol, ignore: "run_state")
2
+ "Use strings in preference to symbols to access node attributes" do
3
+ tags %w{style attributes}
4
+ recipe do |ast|
5
+ # node.run_state is not actually an attribute so ignore that
6
+ attribute_access(ast, type: :symbol, ignore: "run_state")
7
+ end
7
8
  end
8
- end
@@ -4,6 +4,7 @@ rule "FC004", "Use a service resource to start and stop services" do
4
4
  find_resources(ast, type: "execute").find_all do |cmd|
5
5
  cmd_str = (resource_attribute(cmd, "command") || resource_name(cmd)).to_s
6
6
  next if cmd_str.include?(".exe") # don't catch windows commands
7
+
7
8
  cmd_str.start_with?( "start ", "stop ", "reload ", "restart ") || # upstart jobs
8
9
  ( [ "/etc/init.d", "service ", "/sbin/service ", "invoke-rc.d ", "systemctl "].any? do |service_cmd| # upstart / sys-v / systemd
9
10
  cmd_str.start_with?(service_cmd)
@@ -1,11 +1,11 @@
1
1
  rule "FC006", "Mode should be quoted or fully specified when "\
2
2
  "setting file permissions" do
3
- tags %w{correctness files}
4
- recipe do |ast|
5
- ast.xpath(%q{//ident[@value='mode']/parent::command/
6
- descendant::int[string-length(@value) < 5
7
- and not(starts-with(@value, "0")
8
- and string-length(@value) = 4)][count(ancestor::aref) = 0]/
9
- ancestor::method_add_block})
10
- end
11
- end
3
+ tags %w{correctness files}
4
+ recipe do |ast|
5
+ ast.xpath(%q{//ident[@value='mode']/parent::command/
6
+ descendant::int[string-length(@value) < 5
7
+ and not(starts-with(@value, "0")
8
+ and string-length(@value) = 4)][count(ancestor::aref) = 0]/
9
+ ancestor::method_add_block})
10
+ end
11
+ end
@@ -2,8 +2,10 @@ rule "FC007", "Ensure recipe dependencies are reflected in cookbook metadata" do
2
2
  tags %w{correctness metadata}
3
3
  recipe do |ast, filename|
4
4
  metadata_path = Pathname.new(
5
- File.join(File.dirname(filename), "..", "metadata.rb")).cleanpath
5
+ File.join(File.dirname(filename), "..", "metadata.rb")
6
+ ).cleanpath
6
7
  next unless File.exist? metadata_path
8
+
7
9
  actual_included = included_recipes(ast, with_partial_names: false)
8
10
  undeclared = actual_included.keys.map do |recipe|
9
11
  recipe.split("::").first unless recipe =~ /^::/ # skip shorthand included recipes. They're local
@@ -3,6 +3,7 @@ rule "FC016", "LWRP does not declare a default action" do
3
3
  resource do |ast, filename|
4
4
  # See if we're in a custom resource not an LWRP. Only LWRPs need the default_action
5
5
  next if ast.xpath("//ident/@value='action'")
6
+
6
7
  unless ["//ident/@value='default_action'",
7
8
  "//def/bodystmt/descendant::assign/
8
9
  var_field/ivar/@value='@action'"].any? { |expr| ast.xpath(expr) }
@@ -3,20 +3,21 @@ rule "FC019", "Access node attributes in a consistent manner" do
3
3
  cookbook do |cookbook_dir|
4
4
  asts = {}; files = Dir["#{cookbook_dir}/*/*.rb"].reject do |file|
5
5
  relative_path = Pathname.new(file).relative_path_from(
6
- Pathname.new(cookbook_dir))
6
+ Pathname.new(cookbook_dir)
7
+ )
7
8
  relative_path.to_s.split(File::SEPARATOR).include?("spec")
8
9
  end.map do |file|
9
10
  { path: file, ast: read_ast(file) }
10
11
  end
11
- types = [:string, :symbol, :vivified].map do |type|
12
+ types = %i{string symbol vivified}.map do |type|
12
13
  {
13
14
  access_type: type, count: files.map do |file|
14
15
  attribute_access(file[:ast], type: type, ignore_calls: true,
15
16
  cookbook_dir: cookbook_dir, ignore: "run_state").tap do |ast|
16
- unless ast.empty?
17
- (asts[type] ||= []) << { ast: ast, path: file[:path] }
18
- end
19
- end.size
17
+ unless ast.empty?
18
+ (asts[type] ||= []) << { ast: ast, path: file[:path] }
19
+ end
20
+ end.size
20
21
  end.inject(:+)
21
22
  }
22
23
  end.reject { |type| type[:count] == 0 }
@@ -4,30 +4,29 @@ rule "FC022", "Resource condition within loop may not behave as expected" do
4
4
  ast.xpath("//call[ident/@value='each']/../do_block[count(ancestor::
5
5
  method_add_block/method_add_arg/fcall/ident[@value='only_if' or
6
6
  @value = 'not_if']) = 0]").map do |lp|
7
- block_vars = lp.xpath("block_var/params/child::*").map do |n|
8
- n.name.sub(/^ident/, "")
9
- end + lp.xpath("block_var/params/child::*/descendant::ident").map do |v|
10
- v["value"]
11
- end
12
- find_resources(lp).map do |resource|
13
- # if any of the parameters to the block are used in a condition then we
14
- # have a match
15
- unless (block_vars &
16
- (resource.xpath(%q{descendant::ident[@value='not_if' or
17
- @value='only_if']/ancestor::*[self::method_add_block or
18
- self::command][1]/descendant::ident/@value}).map do |a|
19
- a.value
20
- end)).empty?
21
- c = resource.xpath("command[count(descendant::string_embexpr) = 0]")
22
- if resource.xpath("command/ident/@value").first.value == "define"
23
- next
24
- end
25
- resource unless c.empty? || block_vars.any? do |var|
26
- !resource.xpath(%Q{command/args_add_block/args_add/
27
- var_ref/ident[@value='#{var}']}).empty?
28
- end
29
- end
30
- end
31
- end.flatten.compact
7
+ block_vars = lp.xpath("block_var/params/child::*").map do |n|
8
+ n.name.sub(/^ident/, "")
9
+ end + lp.xpath("block_var/params/child::*/descendant::ident").map do |v|
10
+ v["value"]
11
+ end
12
+ find_resources(lp).map do |resource|
13
+ # if any of the parameters to the block are used in a condition then we
14
+ # have a match
15
+ unless (block_vars &
16
+ (resource.xpath(%q{descendant::ident[@value='not_if' or
17
+ @value='only_if']/ancestor::*[self::method_add_block or
18
+ self::command][1]/descendant::ident/@value}).map(&:value))).empty?
19
+ c = resource.xpath("command[count(descendant::string_embexpr) = 0]")
20
+ if resource.xpath("command/ident/@value").first.value == "define"
21
+ next
22
+ end
23
+
24
+ resource unless c.empty? || block_vars.any? do |var|
25
+ !resource.xpath(%Q{command/args_add_block/args_add/
26
+ var_ref/ident[@value='#{var}']}).empty?
27
+ end
28
+ end
29
+ end
30
+ end.flatten.compact
32
31
  end
33
32
  end
@@ -3,11 +3,14 @@ rule "FC024", "Consider adding platform equivalents" do
3
3
  RHEL = %w{centos redhat scientific oracle}.freeze
4
4
  recipe do |ast, filename|
5
5
  next if Pathname.new(filename).basename.to_s == "metadata.rb"
6
+
6
7
  metadata_path = Pathname.new(
7
- File.join(File.dirname(filename), "..", "metadata.rb")).cleanpath
8
+ File.join(File.dirname(filename), "..", "metadata.rb")
9
+ ).cleanpath
8
10
  md_platforms = if File.exist?(metadata_path)
9
11
  supported_platforms(read_ast(
10
- metadata_path)).map { |p| p[:platform] }
12
+ metadata_path
13
+ )).map { |p| p[:platform] }
11
14
  else
12
15
  []
13
16
  end
@@ -16,16 +19,16 @@ rule "FC024", "Consider adding platform equivalents" do
16
19
  ['//method_add_arg[fcall/ident/@value="platform?"]/
17
20
  arg_paren/args_add_block',
18
21
  "//when"].map do |expr|
19
- ast.xpath(expr).map do |whn|
20
- platforms = whn.xpath('args_add/
21
- descendant::tstring_content').map do |p|
22
- p["value"]
23
- end.sort
24
- unless platforms.size == 1 || (md_platforms & platforms).empty?
25
- whn unless (platforms & RHEL).empty? ||
26
- ((md_platforms & RHEL) - (platforms & RHEL)).empty?
27
- end
28
- end.compact
29
- end.flatten
22
+ ast.xpath(expr).map do |whn|
23
+ platforms = whn.xpath('args_add/
24
+ descendant::tstring_content').map do |p|
25
+ p["value"]
26
+ end.sort
27
+ unless platforms.size == 1 || (md_platforms & platforms).empty?
28
+ whn unless (platforms & RHEL).empty? ||
29
+ ((md_platforms & RHEL) - (platforms & RHEL)).empty?
30
+ end
31
+ end.compact
32
+ end.flatten
30
33
  end
31
34
  end
@@ -3,6 +3,7 @@ rule "FC029", "No leading cookbook name in recipe metadata" do
3
3
  metadata do |ast, filename|
4
4
  field(ast, "recipe").map do |declared_recipe|
5
5
  next unless declared_recipe.xpath("count(//vcall|//var_ref)").to_i == 0
6
+
6
7
  recipe_name = declared_recipe.xpath('args_add_block/
7
8
  descendant::tstring_content[1]/@value').to_s
8
9
  unless recipe_name.empty? ||
@@ -1,7 +1,7 @@
1
1
  rule "FC031", "Cookbook without metadata.rb file" do
2
2
  tags %w{correctness metadata}
3
3
  cookbook do |filename|
4
- if !File.exist?(File.join(filename, "metadata.rb"))
4
+ unless File.exist?(File.join(filename, "metadata.rb"))
5
5
  [file_match(File.join(filename, "metadata.rb"))]
6
6
  end
7
7
  end
@@ -2,9 +2,9 @@ rule "FC032", "Invalid notification timing" do
2
2
  tags %w{correctness notifications}
3
3
  recipe do |ast|
4
4
  valid_timings = if resource_attribute?("file", "notifies_before")
5
- [:delayed, :immediate, :before]
5
+ %i{delayed immediate before}
6
6
  else
7
- [:delayed, :immediate]
7
+ %i{delayed immediate}
8
8
  end
9
9
  find_resources(ast).select do |resource|
10
10
  notifications(resource).any? do |notification|
@@ -9,7 +9,7 @@ rule "FC033", "Missing template file" do
9
9
  end.map do |resource|
10
10
  # fetch the specified file to the template
11
11
  file = template_file(resource_attributes(resource,
12
- return_expressions: true))
12
+ return_expressions: true))
13
13
  { resource: resource, file: file }
14
14
  end.reject do |resource|
15
15
  # skip the check if the file path is derived since
@@ -25,7 +25,7 @@ rule "FC033", "Missing template file" do
25
25
  # templates/ubuntu/something.erb down to something.erb, which breaks
26
26
  # legit nested dirs in the templates dir like templates/something/something.erb
27
27
  break if template_path.dirname.basename.to_s == "templates" ||
28
- template_path.dirname.dirname.basename.to_s == "templates"
28
+ template_path.dirname.dirname.basename.to_s == "templates"
29
29
  end
30
30
  File.join(relative_path.reverse) == resource[:file]
31
31
  end
@@ -9,13 +9,16 @@ rule "FC034", "Unused template variables" do
9
9
  File.basename(path) == template_file(resource)
10
10
  end
11
11
  next unless template_paths.any?
12
+
12
13
  passed_vars = resource["variables"].xpath(
13
- "symbol/ident/@value").map { |tv| tv.to_s }
14
+ "symbol/ident/@value"
15
+ ).map(&:to_s)
14
16
 
15
17
  unused_vars_exist = template_paths.all? do |template_path|
16
18
  begin
17
19
  template_vars = templates_included(
18
- all_templates, template_path).map do |template|
20
+ all_templates, template_path
21
+ ).map do |template|
19
22
  read_ast(template).xpath("//var_ref/ivar/@value").map do |v|
20
23
  v.to_s.sub(/^@/, "")
21
24
  end
@@ -3,17 +3,17 @@ rule "FC039", "Node method cannot be accessed with key" do
3
3
  recipe do |ast|
4
4
  [{ type: :string, path: "@value" },
5
5
  { type: :symbol, path: "ident/@value" }].map do |access_type|
6
- attribute_access(ast, type: access_type[:type]).select do |att|
7
- att_name = att.xpath(access_type[:path]).to_s.to_sym
8
- att_name != :tags && chef_node_methods.include?(att_name)
9
- end.select do |att|
10
- !att.xpath('ancestor::args_add_block[position() = 1]
11
- [preceding-sibling::vcall | preceding-sibling::var_ref]').empty?
12
- end.select do |att|
13
- att_type = att.xpath('ancestor::args_add_block[position() = 1]
14
- /../var_ref/ident/@value').to_s
15
- ast.xpath("//assign/var_field/ident[@value='#{att_type}']").empty?
16
- end
17
- end.flatten
6
+ attribute_access(ast, type: access_type[:type]).select do |att|
7
+ att_name = att.xpath(access_type[:path]).to_s.to_sym
8
+ att_name != :tags && chef_node_methods.include?(att_name)
9
+ end.select do |att|
10
+ !att.xpath('ancestor::args_add_block[position() = 1]
11
+ [preceding-sibling::vcall | preceding-sibling::var_ref]').empty?
12
+ end.select do |att|
13
+ att_type = att.xpath('ancestor::args_add_block[position() = 1]
14
+ /../var_ref/ident/@value').to_s
15
+ ast.xpath("//assign/var_field/ident[@value='#{att_type}']").empty?
16
+ end
17
+ end.flatten
18
18
  end
19
19
  end