foodcritic 10.4.1 → 11.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -1
  3. data/Rakefile +7 -1
  4. data/features/step_definitions/cookbook_steps.rb +0 -96
  5. data/lib/foodcritic/api.rb +77 -79
  6. data/lib/foodcritic/rules/fc008.rb +2 -2
  7. data/lib/foodcritic/rules/fc029.rb +1 -1
  8. data/lib/foodcritic/rules/fc042.rb +1 -1
  9. data/lib/foodcritic/rules/fc052.rb +1 -1
  10. data/lib/foodcritic/rules/fc053.rb +1 -1
  11. data/lib/foodcritic/rules/fc057.rb +1 -1
  12. data/lib/foodcritic/rules/fc058.rb +1 -1
  13. data/lib/foodcritic/rules/fc060.rb +1 -1
  14. data/lib/foodcritic/rules/fc061.rb +3 -3
  15. data/lib/foodcritic/rules/fc063.rb +1 -2
  16. data/lib/foodcritic/rules/fc069.rb +2 -4
  17. data/lib/foodcritic/rules/fc078.rb +2 -3
  18. data/lib/foodcritic/rules/fc079.rb +6 -0
  19. data/lib/foodcritic/rules/fc080.rb +6 -0
  20. data/lib/foodcritic/rules/fc081.rb +6 -0
  21. data/lib/foodcritic/rules/fc082.rb +9 -0
  22. data/lib/foodcritic/rules/fc083.rb +6 -0
  23. data/lib/foodcritic/rules/fc084.rb +9 -0
  24. data/lib/foodcritic/rules/fc085.rb +12 -0
  25. data/lib/foodcritic/version.rb +1 -1
  26. data/spec/functional/fc007_spec.rb +127 -0
  27. data/spec/functional/fc049_spec.rb +56 -0
  28. data/spec/functional/fc050_spec.rb +85 -0
  29. data/spec/functional/fc066_spec.rb +20 -0
  30. data/spec/functional/fc069_spec.rb +15 -0
  31. data/spec/functional/fc070_spec.rb +9 -0
  32. data/spec/functional/fc076_spec.rb +2 -2
  33. data/spec/functional/fc077_spec.rb +2 -2
  34. data/spec/functional/fc078_spec.rb +14 -4
  35. data/spec/functional/fc079_spec.rb +21 -0
  36. data/spec/functional/fc080_spec.rb +68 -0
  37. data/spec/functional/fc081_spec.rb +25 -0
  38. data/spec/functional/fc082_spec.rb +23 -0
  39. data/spec/functional/fc083_spec.rb +34 -0
  40. data/spec/functional/fc084_spec.rb +49 -0
  41. data/spec/functional/fc085_spec.rb +58 -0
  42. data/spec/regression/expected/aix.txt +1 -0
  43. data/spec/regression/expected/aws.txt +3 -0
  44. data/spec/regression/expected/chef-client.txt +2 -0
  45. data/spec/regression/expected/chef.txt +67 -0
  46. data/spec/regression/expected/database.txt +1 -0
  47. data/spec/regression/expected/drbd.txt +1 -0
  48. data/spec/regression/expected/jetty.txt +1 -0
  49. data/spec/regression/expected/mysql.txt +4 -0
  50. data/spec/regression/expected/openssh.txt +1 -0
  51. data/spec/regression/expected/postfix.txt +1 -0
  52. data/spec/regression/expected/rsyslog.txt +1 -0
  53. data/spec/regression/expected/smokeping.txt +1 -0
  54. data/spec/regression/expected/sql_server.txt +1 -0
  55. data/spec/regression/expected/ufw.txt +1 -0
  56. data/spec/regression/expected/users.txt +2 -0
  57. data/spec/regression/expected/xml.txt +1 -0
  58. data/spec/regression/expected/yum.txt +0 -1
  59. data/spec/regression/regression_spec.rb +2 -2
  60. data/spec/unit/api_spec.rb +130 -149
  61. metadata +20 -32
  62. data/features/007_check_for_undeclared_recipe_dependencies.feature +0 -59
  63. data/features/049_check_for_role_name_mismatch_with_file_name.feature +0 -31
  64. data/features/050_check_for_invalid_name.feature +0 -33
  65. data/man/foodcritic.1 +0 -81
  66. data/spec/foodcritic/coverage/assets/0.10.0/application.css +0 -799
  67. data/spec/foodcritic/coverage/assets/0.10.0/application.js +0 -1707
  68. data/spec/foodcritic/coverage/assets/0.10.0/colorbox/border.png +0 -0
  69. data/spec/foodcritic/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  70. data/spec/foodcritic/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  71. data/spec/foodcritic/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  72. data/spec/foodcritic/coverage/assets/0.10.0/favicon_green.png +0 -0
  73. data/spec/foodcritic/coverage/assets/0.10.0/favicon_red.png +0 -0
  74. data/spec/foodcritic/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  75. data/spec/foodcritic/coverage/assets/0.10.0/loading.gif +0 -0
  76. data/spec/foodcritic/coverage/assets/0.10.0/magnify.png +0 -0
  77. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  78. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  79. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  80. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  81. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  82. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  83. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  84. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  85. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  86. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  87. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  88. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  89. data/spec/foodcritic/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  90. data/spec/foodcritic/coverage/index.html +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6be21ce54a9247d8ab5fb96fa65056e1b4031a32
4
- data.tar.gz: 2de7963ef01d1170af9ca58bf88ba381d057391f
3
+ metadata.gz: c5c06e37056b5db3985f94fea5101961eeec4164
4
+ data.tar.gz: 610fe94a45ecd4412e77ac18abcd66129fe3d75d
5
5
  SHA512:
6
- metadata.gz: 194e8fb9cdc2e0372cddedd2290c14608a1395c9f7669fc256d4570ed20494ea0d2236b5ce9469a4f0811410a09308c6332e1773c8317366d0840d97156a5601
7
- data.tar.gz: bf129cbbab3b85414d432e64ee7c04edcf413158494cf076ab3323c89442df3290221973844e2d4c9eaf7af0f2212e885d58a389b89d523c2d8cbe9103727205
6
+ metadata.gz: 6471cb860d9e922e4ee238b9b88647cb68c7c161123d7ad223dfec4733b3c741683db9173889a19f524f8269ae223483a3aec21eaaef020ac3681942f890efe4
7
+ data.tar.gz: 31ba9be090cafd5935d660ed91456da49a8efbff002f3301ec5e81466b2c9295a7a07a4982b1b58334adb05da5ed3419de54faaba99ebdd3303173ecdcb6978f
data/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # Foodcritic Changelog:
2
2
 
3
- ## [10.4.0](https://github.com/acrmp/foodcritic/tree/v10.4.1) (2017-04-17)
3
+ ## [11.0.0](https://github.com/acrmp/foodcritic/tree/v11.0.0) (2017-04-24)
4
+
5
+ [Full Changelog](https://github.com/acrmp/foodcritic/compare/v10.4.1...v11.0.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Added `FC079` to detect the usage of the easy_install_package resource which is deprecated in Chef 13\. Tags: deprecated, chef13.
10
+ - Added `FC080` to detect user resources that include the supports property, which is deprecated in Chef 13\. Tags: deprecated, chef13.
11
+ - Added `FC081` to detect a cookbook that depends on the partial_search cookbook as partial search functionality is built into Chef 12 and later. Tags: chef12.
12
+ - Added `FC082` to detect the usage of node.set and node.set_unless which will be removed in Chef 14\. Tags: deprecated, chef14.
13
+ - Added `FC083` to detect execute resources that include the path property, which is deprecated in Chef 12\. Tags: deprecated, chef13.
14
+ - Added `FC084` to detect usage of the deprecated Chef::REST class. Tags: deprecated, chef13.
15
+ - Added `FC085` to detect usage of new_resource.updated_by_last_action to converge resources. Tags: deprecated, chef13.
16
+ - Updated and refactored API methods `declared_dependencies`, `supported_platforms`, and `word_list_values`
17
+ - Deprecated API methods `checks_for_chef_solo` and `chef_solo_search_supported?` have been removed.
18
+ - Added a new API method `json_file_to_hash` for loading json files as a hash.
19
+ - Added a new rake command to run the regression test on just a single cookbook
20
+
21
+ **Fixed bugs:**
22
+
23
+ - Multiple rules have been rewritten to use Foodcritic APIs instead of using XPATH queries directly. This avoids false positives created by overly simplistic queries.
24
+ - Fixed FC069 to skip if the license metadata is any formatting of 'All Rights Reserved'.
25
+ - Added the `license` and `supermarket` tag to FC078.
26
+ - Updated the `field` and `field_value` API methods to correctly recognize additional formats of data in the metdata.
27
+
28
+ ## [10.4.1](https://github.com/acrmp/foodcritic/tree/v10.4.1) (2017-04-17)
4
29
 
5
30
  [Full Changelog](https://github.com/acrmp/foodcritic/compare/v10.4.0...v10.4.1)
6
31
 
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ RSpec::Core::RakeTask.new(:spec, :tag) do |t, args|
10
10
  a << "--format #{ENV['CI'] ? 'documentation' : 'Fuubar'}"
11
11
  a << "--backtrace" if ENV["DEBUG"]
12
12
  a << "--seed #{ENV['SEED']}" if ENV["SEED"]
13
- a << "--tag ~regression" unless ENV["CI"] || args[:tag] == "regression"
13
+ a << "--tag ~regression" unless ENV["CI"] || args[:tag].to_s =~ /regression/
14
14
  a << "--tag #{args[:tag]}" if args[:tag]
15
15
  end.join(" ")
16
16
  end
@@ -65,3 +65,9 @@ task :regen_regression do
65
65
  end
66
66
  end
67
67
  end
68
+
69
+ desc "Run one regression test (or all of them)"
70
+ task :regression, [:cookbook] do |t, args|
71
+ tag = args[:cookbook] ? "regression_#{args[:cookbook]}" : "regression"
72
+ Rake::Task["spec"].invoke(tag)
73
+ end
@@ -409,61 +409,6 @@ Given 'a cookbook recipe that has a confusingly named local variable "default"'
409
409
  }
410
410
  end
411
411
 
412
- Given /^a cookbook recipe that includes a local recipe(.*)$/ do |diff_name|
413
- cookbook = diff_name.empty? ? "example" : "foo"
414
- write_recipe %Q{
415
- include_recipe '#{cookbook}::server'
416
- }
417
- write_metadata %Q{
418
- name '#{cookbook}'
419
- }
420
- end
421
-
422
- Given /^a cookbook recipe that includes a recipe name from an( embedded)? expression(.*)$/ do |embedded, expr|
423
- if embedded
424
- write_recipe %Q{
425
- include_recipe "#{expr.strip}"
426
- }
427
- else
428
- write_recipe %q{
429
- include_recipe node['foo']['bar']
430
- }
431
- end
432
-
433
- write_metadata %q{
434
- depends "foo"
435
- }
436
- end
437
-
438
- Given /^a cookbook recipe that includes a(n un| )?declared recipe dependency(?: {0,1})(unscoped)?( with parentheses)?$/ do |undeclared, unscoped, parens|
439
- recipe_with_dependency(:is_declared => undeclared.strip.empty?,
440
- :is_scoped => unscoped.nil?, :parentheses => parens)
441
- end
442
-
443
- Given "a cookbook recipe that includes both declared and undeclared recipe dependencies" do
444
- write_recipe %q{
445
- include_recipe "foo::default"
446
- include_recipe "bar::default"
447
- file "/tmp/something" do
448
- action :delete
449
- end
450
- include_recipe "baz::default"
451
- }
452
- write_metadata %q{
453
- ['foo', 'bar'].each{|cbk| depends cbk}
454
- }
455
- end
456
-
457
- Given "a cookbook that uses the include_recipe shorthand syntax" do
458
- write_recipe %q{
459
- include_recipe "::some_recipe"
460
- }
461
- end
462
-
463
- Given /^a cookbook recipe that includes several declared recipe dependencies - (brace|block)$/ do |brace_or_block|
464
- cookbook_declares_dependencies(brace_or_block.to_sym)
465
- end
466
-
467
412
  Given /a cookbook recipe that (install|upgrade)s (a gem|multiple gems)(.*)$/ do |action, arity, approach|
468
413
  if arity == "a gem"
469
414
  if approach.empty?
@@ -942,12 +887,6 @@ Given /^a cookbook that does not contain a definition and has (no|a) definitions
942
887
  }
943
888
  end
944
889
 
945
- Given "a cookbook that does not have defined metadata" do
946
- write_recipe %q{
947
- include_recipe "foo::default"
948
- }
949
- end
950
-
951
890
  Given /^a cookbook that has ([^ ]+) problems$/ do |problems|
952
891
  cookbook_that_matches_rules(
953
892
  problems.split(",").map do |problem|
@@ -1132,14 +1071,6 @@ Given /^a directory that contains a role file ([^ ]+) in (json|ruby) that define
1132
1071
  role(:role_name => %Q{"#{role_name}"}, :file_name => file_name, :format => format.to_sym)
1133
1072
  end
1134
1073
 
1135
- Given "a directory that contains a ruby role that declares the role name more than once" do
1136
- role(:role_name => ['"webserver"', '"apache"'], :file_name => "webserver.rb")
1137
- end
1138
-
1139
- Given "a directory that contains a ruby role with an expression as its name" do
1140
- role(:role_name => '"#{foo}#{bar}"', :file_name => "webserver.rb")
1141
- end
1142
-
1143
1074
  Given /^a directory that contains an environment file (.*) in ruby that defines environment name (.*)$/ do |file_name, env_name|
1144
1075
  environment(:environment_name => %Q{"#{env_name}"}, :file_name => "production.rb")
1145
1076
  end
@@ -1411,10 +1342,6 @@ Given "the gems have been vendored" do
1411
1342
  vendor_gems
1412
1343
  end
1413
1344
 
1414
- Given "the last role name declared does not match the containing filename" do
1415
-
1416
- end
1417
-
1418
1345
  Given /^the inferred template contains the expression (.*)$/ do |expr|
1419
1346
  write_file "cookbooks/example/templates/default/config.conf.erb", %Q{
1420
1347
  <%= #{expr} %>
@@ -1489,10 +1416,6 @@ Given "a recipe that tries to mask a systemd service" do
1489
1416
  }
1490
1417
  end
1491
1418
 
1492
- Given /^a ruby environment file that defines an environment with name (.*)$/ do |env_name|
1493
- environment(:environment_name => %Q{"#{env_name}"}, :file_name => "production.rb")
1494
- end
1495
-
1496
1419
  Given /^a ruby environment that triggers FC050 with comment (.*)$/ do |comment|
1497
1420
  write_file "environments/production.rb", %Q{
1498
1421
  name "Production (eu-west-1)" #{comment}
@@ -1500,10 +1423,6 @@ Given /^a ruby environment that triggers FC050 with comment (.*)$/ do |comment|
1500
1423
  }.strip
1501
1424
  end
1502
1425
 
1503
- Given /^a ruby role file that defines a role with name (.*)$/ do |role_name|
1504
- role(:role_name => [%Q{"#{role_name}"}], :file_name => "webserver.rb")
1505
- end
1506
-
1507
1426
  Given /^a ruby role that triggers FC049 with comment (.*)$/ do |comment|
1508
1427
  write_file "roles/webserver.rb", %Q{
1509
1428
  name "apache" #{comment}
@@ -1634,15 +1553,6 @@ When "I check the role directory" do
1634
1553
  run_lint ["--no-progress", "-R", "roles"]
1635
1554
  end
1636
1555
 
1637
- When /^I check the role directory as a (default|cookbook|role) path$/ do |path_type|
1638
- options = case path_type
1639
- when "default" then ["--no-progress", "roles"]
1640
- when "cookbook" then ["--no-progress", "-B", "roles"]
1641
- when "role" then ["--no-progress", "-R", "roles"]
1642
- end
1643
- run_lint(options)
1644
- end
1645
-
1646
1556
  When "I check the webserver role only" do
1647
1557
  run_lint ["--no-progress", "-R", "roles/webserver.rb"]
1648
1558
  end
@@ -1945,12 +1855,6 @@ Then /^the template partials loop indefinitely warning 051 should (not )?be disp
1945
1855
  :expect_warning => ! not_shown)
1946
1856
  end
1947
1857
 
1948
- Then "the undeclared dependency warning 007 should be displayed only for the undeclared dependencies" do
1949
- expect_warning("FC007", :file => "recipes/default.rb", :line => 1, :expect_warning => false)
1950
- expect_warning("FC007", :file => "recipes/default.rb", :line => 2, :expect_warning => false)
1951
- expect_warning("FC007", :file => "recipes/default.rb", :line => 6, :expect_warning => true)
1952
- end
1953
-
1954
1858
  Then /^the unused template variables warning 034 should (not )?be displayed against the (?:inferred )?template(.*)?$/ do |not_shown, ext|
1955
1859
  file = if ext.empty?
1956
1860
  "templates/default/config.conf.erb"
@@ -1,5 +1,6 @@
1
1
  require "nokogiri"
2
2
  require "rufus-lru"
3
+ require "json"
3
4
 
4
5
  module FoodCritic
5
6
  # Helper methods that form part of the Rules DSL.
@@ -44,56 +45,6 @@ module FoodCritic
44
45
  end
45
46
  end
46
47
 
47
- # Does the specified recipe check for Chef Solo?
48
- #
49
- # @deprecated chef-solo functionality in Chef has been replaced with local-mode
50
- # so this helper is no longer necessary and will be removed in Foodcritic 11.0
51
- def checks_for_chef_solo?(ast)
52
- $stderr.puts "the checks_for_chef_solo? helper is deprecated and will be removed from the next release of Foodcritic"
53
- raise_unless_xpath!(ast)
54
- # TODO: This expression is too loose, but also will fail to match other
55
- # types of conditionals.
56
- (!ast.xpath(%q{//*[self::if or self::ifop or self::unless]/
57
- *[self::aref or child::aref or self::call]
58
- [count(descendant::const[@value = 'Chef' or @value = 'Config']) = 2
59
- and
60
- ( count(descendant::ident[@value='solo']) > 0
61
- or count(descendant::tstring_content[@value='solo']) > 0
62
- )
63
- ]}).empty?) ||
64
- ast.xpath('//if_mod[return][aref/descendant::ident/@value="solo"]/aref/
65
- const_path_ref/descendant::const').map do |c|
66
- c["value"]
67
- end == %w{Chef Config}
68
- end
69
-
70
- # Is the chef-solo-search library available?
71
- #
72
- # @see https://github.com/edelight/chef-solo-search
73
- # @deprecated chef-solo functionality in Chef has been replaced with local-mode
74
- # so this helper is no longer necessary and will be removed in Foodcritic 11.0
75
- def chef_solo_search_supported?(recipe_path)
76
- $stderr.puts "the chef_solo_search_supported? helper is deprecated and will be removed from the next release of Foodcritic"
77
- return false if recipe_path.nil? || !File.exist?(recipe_path)
78
-
79
- # Look for the chef-solo-search library.
80
- #
81
- # TODO: This will not work if the cookbook that contains the library
82
- # is not under the same `cookbook_path` as the cookbook being checked.
83
- cbk_tree_path = Pathname.new(File.join(recipe_path, "../../.."))
84
- search_libs = Dir[File.join(cbk_tree_path.realpath,
85
- "*/libraries/search.rb")]
86
-
87
- # True if any of the candidate library files match the signature:
88
- #
89
- # class Chef
90
- # def search
91
- search_libs.any? do |lib|
92
- !read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
93
- ) = 1]/descendant::def/ident[@value='search']}).empty?
94
- end
95
- end
96
-
97
48
  # The absolute path of a cookbook from the specified file.
98
49
  #
99
50
  # @author Tim Smith - tsmith@chef.io
@@ -113,7 +64,11 @@ module FoodCritic
113
64
  file
114
65
  end
115
66
 
116
- # Support function to retrieve a metadata field
67
+ # Retrieves a value of a metadata field.
68
+ #
69
+ # @author Miguel Fonseca
70
+ # @since 7.0.0
71
+ # @return [String] the value of the metadata field
117
72
  def metadata_field(file, field)
118
73
  until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty?
119
74
  file = File.absolute_path(File.dirname(file.to_s))
@@ -133,6 +88,9 @@ module FoodCritic
133
88
  end
134
89
 
135
90
  # The name of the cookbook containing the specified file.
91
+ #
92
+ # @param file [String] file within a cookbook
93
+ # @return [String] name of the cookbook
136
94
  def cookbook_name(file)
137
95
  raise ArgumentError, "File cannot be nil or empty" if file.to_s.empty?
138
96
 
@@ -149,14 +107,20 @@ module FoodCritic
149
107
  end
150
108
  end
151
109
 
152
- # The maintainer of the cookbook containing the specified file.
110
+ # Return metadata maintainer property given any file in the cookbook
111
+ #
112
+ # @param file [String] file within a cookbook
113
+ # @return [String] the maintainer of the cookbook
153
114
  def cookbook_maintainer(file)
154
115
  raise ArgumentError, "File cannot be nil or empty" if file.to_s.empty?
155
116
 
156
117
  metadata_field(file, "maintainer")
157
118
  end
158
119
 
159
- # The maintainer email of the cookbook containing the specified file.
120
+ # Return metadata maintainer_email property given any file in the cookbook
121
+ #
122
+ # @param file [String] file within a cookbook
123
+ # @return [String] email of the maintainer of the cookbook
160
124
  def cookbook_maintainer_email(file)
161
125
  raise ArgumentError, "File cannot be nil or empty" if file.to_s.empty?
162
126
 
@@ -167,33 +131,38 @@ module FoodCritic
167
131
  def declared_dependencies(ast)
168
132
  raise_unless_xpath!(ast)
169
133
 
134
+ deps = []
170
135
  # String literals.
171
136
  #
172
137
  # depends 'foo'
173
- deps = ast.xpath(%q{//command[ident/@value='depends']/
174
- descendant::args_add/descendant::tstring_content[1]})
138
+ deps += field(ast, "depends").xpath("descendant::args_add/descendant::tstring_content[1]")
175
139
 
176
140
  # Quoted word arrays are also common.
177
141
  #
178
142
  # %w{foo bar baz}.each do |cbk|
179
143
  # depends cbk
180
144
  # end
181
- deps = deps.to_a +
182
- word_list_values(ast, "//command[ident/@value='depends']")
183
- deps.uniq.map { |dep| dep["value"].strip }
145
+ deps += word_list_values(field(ast, "depends"))
146
+ deps.uniq!
147
+ deps.map! { |dep| dep["value"].strip }
148
+ deps
184
149
  end
185
150
 
186
- # The key / value pair in an environment or role ruby file
151
+ # Look for a method call with a given name.
152
+ #
153
+ # @param ast [Nokogiri::XML::Node] Document to search under
154
+ # @param field_name [String] Method name to search for
155
+ # @return [Nokogiri::XML::NodeSet]
187
156
  def field(ast, field_name)
188
157
  if field_name.nil? || field_name.to_s.empty?
189
158
  raise ArgumentError, "Field name cannot be nil or empty"
190
159
  end
191
- ast.xpath("//command[ident/@value='#{field_name}']")
160
+ ast.xpath("(.//command[ident/@value='#{field_name}']|.//fcall[ident/@value='#{field_name}']/..)")
192
161
  end
193
162
 
194
163
  # The value for a specific key in an environment or role ruby file
195
164
  def field_value(ast, field_name)
196
- field(ast, field_name).xpath('args_add_block/descendant::tstring_content
165
+ field(ast, field_name).xpath('.//args_add_block//tstring_content
197
166
  [count(ancestor::args_add) = 1][count(ancestor::string_add) = 1]
198
167
  /@value').map { |a| a.to_s }.last
199
168
  end
@@ -376,25 +345,33 @@ module FoodCritic
376
345
  end
377
346
 
378
347
  # The list of standard cookbook sub-directories.
348
+ #
349
+ # @since 1.0.0
350
+ # @return [array] array of all default sub-directories in a cookbook
379
351
  def standard_cookbook_subdirs
380
352
  %w{attributes definitions files libraries providers recipes resources
381
353
  templates}
382
354
  end
383
355
 
384
- # Platforms declared as supported in cookbook metadata
356
+ # Platforms declared as supported in cookbook metadata. Returns an array
357
+ # of hashes containing the name and version constraints for each platform.
358
+ #
359
+ # @param ast [Nokogiri::XML::Node] Document to search from.
360
+ # @return [Array<Hash>]
385
361
  def supported_platforms(ast)
386
- platforms = ast.xpath('//command[ident/@value="supports"]/
387
- descendant::*[self::string_literal or self::symbol_literal]
388
- [position() = 1]
389
- [self::symbol_literal or count(descendant::string_add) = 1]/
390
- descendant::*[self::tstring_content | self::ident]')
391
- platforms = platforms.to_a +
392
- word_list_values(ast, "//command[ident/@value='supports']")
362
+ # Find the supports() method call.
363
+ platforms_ast = field(ast, "supports")
364
+ # Look for the first argument (the node next to the top args_new) and
365
+ # filter out anything with a string_embexpr since that can't be parsed
366
+ # statically. Then grab the static value for both strings and symbols, and
367
+ # finally combine it with the word list (%w{}) analyzer.
368
+ platforms = platforms_ast.xpath("(.//args_new)[1]/../*[not(.//string_embexpr)]").xpath(".//tstring_content|.//symbol/ident") | word_list_values(platforms_ast)
393
369
  platforms.map do |platform|
394
- versions = platform.xpath('ancestor::args_add[position() > 1]/
395
- string_literal/descendant::tstring_content/@value').map { |v| v.to_s }
396
- { platform: platform["value"].lstrip, versions: versions }
397
- end.sort { |a, b| a[:platform] <=> b[:platform] }
370
+ # For each platform value, look for all arguments after the first, then
371
+ # extract the string literal value.
372
+ versions = platform.xpath("ancestor::args_add[not(args_new)]/*[position()=2]//tstring_content/@value")
373
+ { platform: platform["value"].lstrip, versions: versions.map(&:to_s) }
374
+ end.sort_by { |p| p[:platform] }
398
375
  end
399
376
 
400
377
  # Template filename
@@ -436,6 +413,23 @@ module FoodCritic
436
413
  end
437
414
  end
438
415
 
416
+ # Give a filename path it returns the hash of the JSON contents
417
+ #
418
+ # @author Tim Smith - tsmith@chef.io
419
+ # @since 11.0
420
+ # @param filename [String] path to a file in JSON format
421
+ # @return [Hash] hash of JSON content
422
+ def json_file_to_hash(filename)
423
+ raise "File #{filename} not found" unless File.exist?(filename)
424
+
425
+ file = File.read(filename)
426
+ begin
427
+ JSON.parse(file)
428
+ rescue RuntimeError
429
+ raise "File #{filename} does not appear to contain valid JSON"
430
+ end
431
+ end
432
+
439
433
  private
440
434
 
441
435
  def block_attributes(resource)
@@ -642,14 +636,18 @@ module FoodCritic
642
636
  end.sort
643
637
  end
644
638
 
645
- def word_list_values(ast, xpath)
646
- var_ref = ast.xpath("#{xpath}/descendant::var_ref/ident")
639
+ def word_list_values(ast, xpath = nil)
640
+ # Find the node for the field argument variable. (e.g. given `foo d`, find `d`)
641
+ var_ref = ast.xpath("#{xpath ? xpath + '/' : ''}descendant::var_ref/ident")
647
642
  if var_ref.empty?
648
- []
643
+ # The field is either a static value, or took no arguments, or something
644
+ # more complex than we care to evaluate.
645
+ Nokogiri::XML::NodeSet.new(ast.document)
649
646
  else
650
- ast.xpath(%Q{descendant::block_var/params/
651
- ident#{var_ref.first['value']}/ancestor::method_add_block/call/
652
- descendant::tstring_content})
647
+ # Look back out the tree for a method_add_block which contains a block
648
+ # variable matching the field argument variable, and then drill down to
649
+ # all the literal content nodes.
650
+ ast.xpath(%Q{ancestor::method_add_block[//block_var//ident/@value='#{var_ref.first['value']}']/call//tstring_content})
653
651
  end
654
652
  end
655
653
  end