foodcritic 10.4.1 → 11.0.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.
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