foodcritic 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.md +83 -0
- data/chef_dsl_metadata/chef_0.10.0.json +2 -3
- data/chef_dsl_metadata/chef_0.10.10.json +2 -3
- data/chef_dsl_metadata/chef_0.10.2.json +2 -3
- data/chef_dsl_metadata/chef_0.10.4.json +2 -3
- data/chef_dsl_metadata/chef_0.10.6.json +2 -3
- data/chef_dsl_metadata/chef_0.10.8.json +2 -3
- data/chef_dsl_metadata/chef_0.8.14.json +2 -3
- data/chef_dsl_metadata/chef_0.8.16.json +2 -3
- data/chef_dsl_metadata/chef_0.9.0.json +2 -3
- data/chef_dsl_metadata/chef_0.9.10.json +2 -3
- data/chef_dsl_metadata/chef_0.9.12.json +2 -3
- data/chef_dsl_metadata/chef_0.9.14.json +2 -3
- data/chef_dsl_metadata/chef_0.9.16.json +2 -3
- data/chef_dsl_metadata/chef_0.9.18.json +2 -3
- data/chef_dsl_metadata/chef_0.9.2.json +2 -3
- data/chef_dsl_metadata/chef_0.9.4.json +2 -3
- data/chef_dsl_metadata/chef_0.9.6.json +2 -3
- data/chef_dsl_metadata/chef_0.9.8.json +2 -3
- data/chef_dsl_metadata/chef_10.12.0.json +2 -3
- data/chef_dsl_metadata/chef_10.14.0.json +2 -3
- data/chef_dsl_metadata/chef_10.14.2.json +2 -3
- data/chef_dsl_metadata/chef_10.14.4.json +2 -3
- data/chef_dsl_metadata/chef_10.16.0.json +2 -3
- data/chef_dsl_metadata/chef_10.16.2.json +2 -3
- data/chef_dsl_metadata/chef_10.16.4.json +2 -3
- data/chef_dsl_metadata/chef_10.16.6.json +2 -3
- data/chef_dsl_metadata/chef_10.18.0.json +2 -3
- data/chef_dsl_metadata/chef_10.18.2.json +2 -3
- data/chef_dsl_metadata/chef_10.20.0.json +2 -3
- data/chef_dsl_metadata/chef_10.22.0.json +2 -3
- data/chef_dsl_metadata/chef_10.24.0.json +2 -3
- data/chef_dsl_metadata/chef_10.24.4.json +2 -3
- data/chef_dsl_metadata/chef_10.26.0.json +2 -3
- data/chef_dsl_metadata/chef_11.0.0.json +2 -3
- data/chef_dsl_metadata/chef_11.2.0.json +2 -3
- data/chef_dsl_metadata/chef_11.4.0.json +2 -3
- data/chef_dsl_metadata/chef_11.4.2.json +2 -3
- data/chef_dsl_metadata/chef_11.4.4.json +2 -3
- data/chef_dsl_metadata/chef_11.6.0.json +9734 -0
- data/features/007_check_for_undeclared_recipe_dependencies.feature +18 -34
- data/features/017_check_for_no_lwrp_notifications.feature +25 -0
- data/features/019_check_for_consistent_node_access.feature +1 -0
- data/features/033_check_for_missing_template.feature +20 -64
- data/features/034_check_for_unused_template_variables.feature +44 -0
- data/features/047_check_for_attribute_assignment_without_precedence.feature +47 -0
- data/features/048_check_for_shellout.feature +34 -0
- data/features/049_check_for_role_name_mismatch_with_file_name.feature +31 -0
- data/features/050_check_for_invalid_name.feature +33 -0
- data/features/051_check_for_template_partial_loops.feature +21 -0
- data/features/command_line_help.feature +15 -0
- data/features/ignore_via_line_comments.feature +18 -0
- data/features/individual_file.feature +17 -1
- data/features/multiple_paths.feature +26 -2
- data/features/step_definitions/cookbook_steps.rb +328 -9
- data/features/support/command_helpers.rb +71 -10
- data/features/support/cookbook_helpers.rb +88 -6
- data/lib/foodcritic/api.rb +89 -20
- data/lib/foodcritic/command_line.rb +64 -18
- data/lib/foodcritic/domain.rb +26 -7
- data/lib/foodcritic/dsl.rb +3 -0
- data/lib/foodcritic/linter.rb +93 -61
- data/lib/foodcritic/rake_task.rb +3 -2
- data/lib/foodcritic/rules.rb +105 -14
- data/lib/foodcritic/template.rb +34 -1
- data/lib/foodcritic/version.rb +1 -1
- data/man/foodcritic.1 +13 -1
- data/man/foodcritic.1.ronn +9 -0
- data/spec/foodcritic/api_spec.rb +210 -1
- data/spec/foodcritic/command_line_spec.rb +13 -0
- data/spec/foodcritic/domain_spec.rb +40 -5
- data/spec/foodcritic/linter_spec.rb +19 -22
- data/spec/foodcritic/template_spec.rb +8 -4
- data/spec/regression/expected-output.txt +139 -60
- metadata +31 -26
@@ -44,11 +44,16 @@ module FoodCritic
|
|
44
44
|
'FC039' => 'Node method cannot be accessed with key',
|
45
45
|
'FC040' => 'Execute resource used to run git commands',
|
46
46
|
'FC041' => 'Execute resource used to run curl or wget commands',
|
47
|
-
'FC042' => 'Prefer include_recipe to require_recipe',
|
47
|
+
'FC042' => 'Prefer include_recipe to require_recipe',
|
48
48
|
'FC043' => 'Prefer new notification syntax',
|
49
49
|
'FC044' => 'Avoid bare attribute keys',
|
50
50
|
'FC045' => 'Consider setting cookbook name in metadata',
|
51
51
|
'FC046' => 'Attribute assignment uses assign unless nil',
|
52
|
+
'FC047' => 'Attribute assignment does not specify precedence',
|
53
|
+
'FC048' => 'Prefer Mixlib::ShellOut',
|
54
|
+
'FC049' => 'Role name does not match containing file name',
|
55
|
+
'FC050' => 'Name includes invalid characters',
|
56
|
+
'FC051' => 'Template partials loop indefinitely',
|
52
57
|
'FCTEST001' => 'Test Rule'
|
53
58
|
}
|
54
59
|
|
@@ -97,10 +102,14 @@ module FoodCritic
|
|
97
102
|
:resource => 'resources/site.rb', :libraries => 'libraries/lib.rb'}[options[:file_type]]
|
98
103
|
end
|
99
104
|
options = {:line => 1, :expect_warning => true, :file => 'recipes/default.rb'}.merge!(options)
|
105
|
+
unless options[:file].include?('roles') ||
|
106
|
+
options[:file].include?('environments')
|
107
|
+
options[:file] = "cookbooks/example/#{options[:file]}"
|
108
|
+
end
|
100
109
|
if options[:warning_only]
|
101
110
|
warning = "#{code}: #{WARNINGS[code]}"
|
102
111
|
else
|
103
|
-
warning = "#{code}: #{WARNINGS[code]}:
|
112
|
+
warning = "#{code}: #{WARNINGS[code]}: #{options[:file]}:#{options[:line]}#{"\n" if ! options[:line].nil?}"
|
104
113
|
end
|
105
114
|
options[:expect_warning] ? expect_output(warning) : expect_no_output(warning)
|
106
115
|
end
|
@@ -122,19 +131,29 @@ module FoodCritic
|
|
122
131
|
expect_output(Regexp.new(expected_switch))
|
123
132
|
end
|
124
133
|
|
134
|
+
def man_page_options
|
135
|
+
man_path = Pathname.new(__FILE__) + '../../../man/foodcritic.1.ronn'
|
136
|
+
option_lines = File.read(man_path).split('## ').find do |s|
|
137
|
+
s.start_with?('OPTIONS')
|
138
|
+
end.split("\n").select{|o| o.start_with?(' *')}
|
139
|
+
option_lines.map do |o|
|
140
|
+
o.sub('`[`no-`]`', '').split('`').select{|f| f.include?('-')}
|
141
|
+
end.map do |option|
|
142
|
+
{:short => option.first.sub(/^-/, ''),
|
143
|
+
:long => option.last.sub(/^--/, '')}
|
144
|
+
end.sort_by{|o| o[:short]}
|
145
|
+
end
|
146
|
+
|
125
147
|
# Assert that the usage message is displayed.
|
126
148
|
#
|
127
149
|
# @param [Boolean] is_exit_zero The exit code to check for.
|
128
150
|
def usage_displayed(is_exit_zero)
|
129
151
|
expect_output 'foodcritic [cookbook_paths]'
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
expect_usage_option('I', 'include PATH', 'Additional rule file path(s) to load.')
|
136
|
-
expect_usage_option('S', 'search-grammar PATH', 'Specify grammar to use when validating search syntax.')
|
137
|
-
expect_usage_option('V', 'version', 'Display the foodcritic version.')
|
152
|
+
|
153
|
+
usage_options.each do |option|
|
154
|
+
expect_usage_option(option[:short], option[:long], option[:description])
|
155
|
+
end
|
156
|
+
|
138
157
|
if is_exit_zero
|
139
158
|
assert_no_error_occurred
|
140
159
|
else
|
@@ -142,6 +161,48 @@ module FoodCritic
|
|
142
161
|
end
|
143
162
|
end
|
144
163
|
|
164
|
+
def usage_options
|
165
|
+
[
|
166
|
+
{:short => 'c', :long => 'chef-version VERSION',
|
167
|
+
:description => 'Only check against rules valid for this version of Chef.'},
|
168
|
+
|
169
|
+
{:short => 'f', :long => 'epic-fail TAGS',
|
170
|
+
:description => "Fail the build if any of the specified tags are matched ('any' -> fail on any match)."},
|
171
|
+
|
172
|
+
{:short => 't', :long => 'tags TAGS',
|
173
|
+
:description => 'Only check against rules with the specified tags.'},
|
174
|
+
|
175
|
+
{:short => 'B', :long => 'cookbook-path PATH',
|
176
|
+
:description => 'Cookbook path(s) to check.'},
|
177
|
+
|
178
|
+
{:short => 'C', :long => '[no-]context',
|
179
|
+
:description => 'Show lines matched against rather than the default summary.'},
|
180
|
+
|
181
|
+
{:short => 'E', :long => 'environment-path PATH',
|
182
|
+
:description => 'Environment path(s) to check.'},
|
183
|
+
|
184
|
+
{:short => 'I', :long => 'include PATH',
|
185
|
+
:description => 'Additional rule file path(s) to load.'},
|
186
|
+
|
187
|
+
{:short => 'R', :long => 'role-path PATH',
|
188
|
+
:description => 'Role path(s) to check.'},
|
189
|
+
|
190
|
+
{:short => 'S', :long => 'search-grammar PATH',
|
191
|
+
:description => 'Specify grammar to use when validating search syntax.'},
|
192
|
+
|
193
|
+
{:short => 'V', :long => 'version',
|
194
|
+
:description => 'Display the foodcritic version.'}
|
195
|
+
|
196
|
+
]
|
197
|
+
end
|
198
|
+
|
199
|
+
def usage_options_for_diff
|
200
|
+
usage_options.map do |o|
|
201
|
+
{:short => o[:short],
|
202
|
+
:long => o[:long].split(' ').first.sub(/^\[no-\]/, '')}
|
203
|
+
end.sort_by{|o| o[:short]}
|
204
|
+
end
|
205
|
+
|
145
206
|
end
|
146
207
|
|
147
208
|
# Helpers used when features are executed in-process.
|
@@ -13,7 +13,7 @@ module FoodCritic
|
|
13
13
|
# Create a Gemfile for a cookbook
|
14
14
|
def buildable_gemfile
|
15
15
|
write_file 'cookbooks/example/Gemfile', %q{
|
16
|
-
source
|
16
|
+
source 'https://rubygems.org/'
|
17
17
|
gem 'rake'
|
18
18
|
gem 'foodcritic', :path => '../../../..'
|
19
19
|
}
|
@@ -82,8 +82,10 @@ module FoodCritic
|
|
82
82
|
# @param [Hash] lwrp The options to use for the created LWRP
|
83
83
|
# @option lwrp [Symbol] :default_action One of :no_default_action, :ruby_default_action, :dsl_default_action
|
84
84
|
# @option lwrp [Symbol] :notifies One of :does_not_notify, :does_notify, :does_notify_without_parens, :deprecated_syntax, :class_variable
|
85
|
+
# @option lwrp [Symbol] :use_inline_resources Defaults to false
|
85
86
|
def cookbook_with_lwrp(lwrp)
|
86
|
-
lwrp = {:default_action => false, :notifies => :does_not_notify
|
87
|
+
lwrp = {:default_action => false, :notifies => :does_not_notify,
|
88
|
+
:use_inline_resources => false}.merge!(lwrp)
|
87
89
|
ruby_default_action = %q{
|
88
90
|
def initialize(*args)
|
89
91
|
super
|
@@ -101,6 +103,7 @@ module FoodCritic
|
|
101
103
|
:deprecated_syntax => 'new_resource.updated = true',
|
102
104
|
:class_variable => '@updated = true'}
|
103
105
|
write_provider("site", %Q{
|
106
|
+
#{'use_inline_resources' if lwrp[:use_inline_resources]}
|
104
107
|
action :create do
|
105
108
|
log "Here is where I would create a site"
|
106
109
|
#{notifications[lwrp[:notifies]]}
|
@@ -108,6 +111,14 @@ module FoodCritic
|
|
108
111
|
})
|
109
112
|
end
|
110
113
|
|
114
|
+
def cookbook_with_lwrp_actions(actions)
|
115
|
+
write_resource("site", %Q{
|
116
|
+
actions #{actions.map{|a| a[:name].inspect}.join(', ')}
|
117
|
+
attribute :name, :kind_of => String, :name_attribute => true
|
118
|
+
})
|
119
|
+
write_provider("site", actions.map{|a| provider_action(a)}.join("\n"))
|
120
|
+
end
|
121
|
+
|
111
122
|
# Create an cookbook with the maintainer specified in the metadata
|
112
123
|
#
|
113
124
|
# @param [String] name The maintainer name
|
@@ -136,6 +147,20 @@ module FoodCritic
|
|
136
147
|
}
|
137
148
|
end
|
138
149
|
|
150
|
+
# Create an environment file
|
151
|
+
#
|
152
|
+
# @param [Hash] options The options to use for the environment
|
153
|
+
# @option options [String] :dir The relative directory to write to
|
154
|
+
# @option options [String] :environment_name The name of the environment declared in the file
|
155
|
+
# @option options [String] :file_name The containing file relative to the environments directory
|
156
|
+
def environment(options={})
|
157
|
+
options = {:dir => 'environments'}.merge(options)
|
158
|
+
write_file "#{options[:dir]}/#{options[:file_name]}", %Q{
|
159
|
+
#{Array(options[:environment_name]).map{|r| "name #{r}"}.join("\n")}
|
160
|
+
cookbook "apache2"
|
161
|
+
}.strip
|
162
|
+
end
|
163
|
+
|
139
164
|
# Create a placeholder minitest spec that would be linted due to its path
|
140
165
|
# unless an exclusion is specified.
|
141
166
|
def minitest_spec_attributes
|
@@ -145,6 +170,30 @@ module FoodCritic
|
|
145
170
|
}
|
146
171
|
end
|
147
172
|
|
173
|
+
def provider_action(action)
|
174
|
+
case action[:notify_type]
|
175
|
+
when :none then %Q{
|
176
|
+
action #{action[:name].inspect} do
|
177
|
+
log "Would take action here"
|
178
|
+
end
|
179
|
+
}
|
180
|
+
when :updated_by_last_action then %Q{
|
181
|
+
action #{action[:name].inspect} do
|
182
|
+
log "Would take action here"
|
183
|
+
# Explicitly update
|
184
|
+
new_resource.updated_by_last_action(true)
|
185
|
+
end
|
186
|
+
}
|
187
|
+
when :converge_by then %Q{
|
188
|
+
action #{action[:name].inspect} do
|
189
|
+
converge_by "#{action[:name]} site" do
|
190
|
+
log "Would take action here"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
}
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
148
197
|
# Create a Rakefile that uses the linter rake task
|
149
198
|
#
|
150
199
|
# @param [Symbol] task Type of task
|
@@ -273,11 +322,16 @@ module FoodCritic
|
|
273
322
|
# @param [Hash] dep The options to use for dependency
|
274
323
|
# @option dep [Boolean] :is_declared True if this dependency has been declared in the cookbook metadata
|
275
324
|
# @option dep [Boolean] :is_scoped True if the include_recipe references a specific recipe or the cookbook
|
325
|
+
# @option dep [Boolean] :parentheses True if the include_recipe is called with parentheses
|
276
326
|
def recipe_with_dependency(dep)
|
277
|
-
dep = {:is_scoped => true, :is_declared => true
|
278
|
-
|
279
|
-
|
280
|
-
|
327
|
+
dep = {:is_scoped => true, :is_declared => true,
|
328
|
+
:parentheses => false}.merge!(dep)
|
329
|
+
recipe = "foo#{dep[:is_scoped] ? '::default' : ''}"
|
330
|
+
write_recipe(if dep[:parentheses]
|
331
|
+
"include_recipe('#{recipe}')"
|
332
|
+
else
|
333
|
+
"include_recipe '#{recipe}'"
|
334
|
+
end)
|
281
335
|
write_metadata %Q{
|
282
336
|
version "1.9.0"
|
283
337
|
depends "#{dep[:is_declared] ? 'foo' : 'dogs'}"
|
@@ -377,6 +431,34 @@ module FoodCritic
|
|
377
431
|
}
|
378
432
|
end
|
379
433
|
|
434
|
+
# Create a role file
|
435
|
+
#
|
436
|
+
# @param [Hash] options The options to use for the role
|
437
|
+
# @option options [String] :role_name The name of the role declared in the role file
|
438
|
+
# @option options [String] :file_name The containing file relative to the roles directory
|
439
|
+
# @option options [Symbol] :format Either :ruby or :json. Default is :ruby
|
440
|
+
def role(options={})
|
441
|
+
options = {:format => :ruby, :dir => 'roles'}.merge(options)
|
442
|
+
content = if options[:format] == :json
|
443
|
+
%Q{
|
444
|
+
{
|
445
|
+
"chef_type": "role",
|
446
|
+
"json_class": "Chef::Role",
|
447
|
+
#{Array(options[:role_name]).map{|r| "name: #{r},"}.join("\n")}
|
448
|
+
"run_list": [
|
449
|
+
"recipe[apache2]",
|
450
|
+
]
|
451
|
+
}
|
452
|
+
}
|
453
|
+
else
|
454
|
+
%Q{
|
455
|
+
#{Array(options[:role_name]).map{|r| "name #{r}"}.join("\n")}
|
456
|
+
run_list "recipe[apache2]"
|
457
|
+
}
|
458
|
+
end
|
459
|
+
write_file "#{options[:dir]}/#{options[:file_name]}", content.strip
|
460
|
+
end
|
461
|
+
|
380
462
|
# Create a rule with the specified Chef version constraints
|
381
463
|
#
|
382
464
|
# @param [String] from_version The from version
|
data/lib/foodcritic/api.rb
CHANGED
@@ -11,6 +11,8 @@ module FoodCritic
|
|
11
11
|
include FoodCritic::Chef
|
12
12
|
include FoodCritic::Notifications
|
13
13
|
|
14
|
+
class RecursedTooFarError < StandardError; end
|
15
|
+
|
14
16
|
# Find attribute access by type.
|
15
17
|
def attribute_access(ast, options = {})
|
16
18
|
options = {:type => :any, :ignore_calls => false}.merge!(options)
|
@@ -22,10 +24,10 @@ module FoodCritic
|
|
22
24
|
|
23
25
|
case options[:type]
|
24
26
|
when :any then
|
25
|
-
vivified_attribute_access(ast, options
|
26
|
-
|
27
|
+
vivified_attribute_access(ast, options) +
|
28
|
+
standard_attribute_access(ast, options)
|
27
29
|
when :vivified then
|
28
|
-
vivified_attribute_access(ast, options
|
30
|
+
vivified_attribute_access(ast, options)
|
29
31
|
else
|
30
32
|
standard_attribute_access(ast, options)
|
31
33
|
end
|
@@ -108,7 +110,22 @@ module FoodCritic
|
|
108
110
|
# depends cbk
|
109
111
|
# end
|
110
112
|
deps = deps.to_a + word_list_values(ast, "//command[ident/@value='depends']")
|
111
|
-
deps.map{|dep| dep['value']}
|
113
|
+
deps.uniq.map{|dep| dep['value'].strip }
|
114
|
+
end
|
115
|
+
|
116
|
+
# The key / value pair in an environment or role ruby file
|
117
|
+
def field(ast, field_name)
|
118
|
+
if field_name.nil? || field_name.to_s.empty?
|
119
|
+
raise ArgumentError, "Field name cannot be nil or empty"
|
120
|
+
end
|
121
|
+
ast.xpath("//command[ident/@value='#{field_name}']")
|
122
|
+
end
|
123
|
+
|
124
|
+
# The value for a specific key in an environment or role ruby file
|
125
|
+
def field_value(ast, field_name)
|
126
|
+
field(ast, field_name).xpath('args_add_block/descendant::tstring_content
|
127
|
+
[count(ancestor::args_add) = 1][count(ancestor::string_add) = 1]
|
128
|
+
/@value').map{|a| a.to_s}.last
|
112
129
|
end
|
113
130
|
|
114
131
|
# Create a match for a specified file. Use this if the presence of the file
|
@@ -164,8 +181,13 @@ module FoodCritic
|
|
164
181
|
filter << '[count(descendant::string_embexpr) = 0]'
|
165
182
|
end
|
166
183
|
|
167
|
-
|
168
|
-
|
184
|
+
string_desc = '[descendant::args_add/string_literal]/descendant::tstring_content'
|
185
|
+
included = ast.xpath([
|
186
|
+
"//command[ident/@value = 'include_recipe']",
|
187
|
+
"//fcall[ident/@value = 'include_recipe']/following-sibling::arg_paren",
|
188
|
+
].map do |recipe_include|
|
189
|
+
recipe_include + filter.join + string_desc
|
190
|
+
end.join(' | '))
|
169
191
|
|
170
192
|
# Hash keyed by recipe name with matched nodes.
|
171
193
|
included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
|
@@ -191,8 +213,7 @@ module FoodCritic
|
|
191
213
|
# Read the AST for the given Ruby source file
|
192
214
|
def read_ast(file)
|
193
215
|
source = if file.to_s.split(File::SEPARATOR).include?('templates')
|
194
|
-
|
195
|
-
File.read(file)).map{|e| e[:code]}.join(';')
|
216
|
+
template_expressions_only(file)
|
196
217
|
else
|
197
218
|
File.read(file)
|
198
219
|
end
|
@@ -312,10 +333,23 @@ module FoodCritic
|
|
312
333
|
end
|
313
334
|
end
|
314
335
|
|
336
|
+
def templates_included(all_templates, template_path, depth=1)
|
337
|
+
raise RecursedTooFarError.new(template_path) if depth > 10
|
338
|
+
partials = read_ast(template_path).xpath('//*[self::command or
|
339
|
+
child::fcall][descendant::ident/@value="render"]//args_add/
|
340
|
+
string_literal//tstring_content/@value').map{|p| p.to_s}
|
341
|
+
Array(template_path) + partials.map do |included_partial|
|
342
|
+
partial_path = Array(all_templates).find do |path|
|
343
|
+
File.basename(path) == included_partial.to_s
|
344
|
+
end
|
345
|
+
Array(partial_path) + templates_included(all_templates, partial_path, depth + 1)
|
346
|
+
end.flatten.uniq
|
347
|
+
end
|
348
|
+
|
315
349
|
# Templates in the current cookbook
|
316
350
|
def template_paths(recipe_path)
|
317
|
-
Dir
|
318
|
-
'**/*'
|
351
|
+
Dir.glob(Pathname.new(recipe_path).dirname.dirname + 'templates' +
|
352
|
+
'**/*', File::FNM_DOTMATCH).select{|path| File.file?(path)}
|
319
353
|
end
|
320
354
|
|
321
355
|
private
|
@@ -390,6 +424,12 @@ module FoodCritic
|
|
390
424
|
end
|
391
425
|
end
|
392
426
|
|
427
|
+
def ignore_attributes_xpath(ignores)
|
428
|
+
Array(ignores).map do |ignore|
|
429
|
+
"[count(descendant::*[@value='#{ignore}']) = 0]"
|
430
|
+
end.join
|
431
|
+
end
|
432
|
+
|
393
433
|
def node_method?(meth, cookbook_dir)
|
394
434
|
chef_dsl_methods.include?(meth) || patched_node_method?(meth, cookbook_dir)
|
395
435
|
end
|
@@ -433,7 +473,22 @@ module FoodCritic
|
|
433
473
|
class AttFilter
|
434
474
|
def is_att_type(value)
|
435
475
|
return [] unless value.respond_to?(:select)
|
436
|
-
value.select
|
476
|
+
value.select do |n|
|
477
|
+
%w{
|
478
|
+
automatic_attrs
|
479
|
+
default
|
480
|
+
default_unless
|
481
|
+
force_default
|
482
|
+
force_override
|
483
|
+
node
|
484
|
+
normal
|
485
|
+
normal_unless
|
486
|
+
override
|
487
|
+
override_unless
|
488
|
+
set
|
489
|
+
set_unless
|
490
|
+
}.include?(n.to_s)
|
491
|
+
end
|
437
492
|
end
|
438
493
|
end
|
439
494
|
|
@@ -444,31 +499,45 @@ module FoodCritic
|
|
444
499
|
end.inject(:+)
|
445
500
|
else
|
446
501
|
type = if options[:type] == :string
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
502
|
+
'tstring_content'
|
503
|
+
else
|
504
|
+
'*[self::symbol or self::dyna_symbol]'
|
505
|
+
end
|
451
506
|
expr = '//*[self::aref_field or self::aref][count(method_add_arg) = 0]'
|
452
507
|
expr += '[count(is_att_type(descendant::var_ref/ident/@value)) =
|
453
508
|
count(descendant::var_ref/ident/@value)]'
|
454
509
|
expr += '[is_att_type(descendant::ident'
|
455
510
|
expr += '[not(ancestor::aref/call)]' if options[:ignore_calls]
|
456
|
-
expr +=
|
457
|
-
|
511
|
+
expr += '/@value)]'
|
512
|
+
expr += ignore_attributes_xpath(options[:ignore])
|
513
|
+
expr += "/descendant::#{type}"
|
514
|
+
if options[:type] == :string
|
458
515
|
expr += '[count(ancestor::dyna_symbol) = 0]'
|
459
516
|
end
|
460
517
|
ast.xpath(expr, AttFilter.new).sort
|
461
518
|
end
|
462
519
|
end
|
463
520
|
|
464
|
-
def
|
465
|
-
|
521
|
+
def template_expressions_only(file)
|
522
|
+
exprs = Template::ExpressionExtractor.new.extract(File.read(file))
|
523
|
+
lines = Array.new(exprs.map{|e| e[:line]}.max || 0, '')
|
524
|
+
exprs.each do |e|
|
525
|
+
lines[e[:line] -1] += ';' unless lines[e[:line] -1].empty?
|
526
|
+
lines[e[:line] -1] += e[:code]
|
527
|
+
end
|
528
|
+
lines.join("\n")
|
529
|
+
end
|
530
|
+
|
531
|
+
def vivified_attribute_access(ast, options={})
|
532
|
+
calls = ast.xpath(%Q{//*[self::call or self::field]
|
466
533
|
[is_att_type(vcall/ident/@value) or is_att_type(var_ref/ident/@value)]
|
534
|
+
#{ignore_attributes_xpath(options[:ignore])}
|
467
535
|
[@value='.'][count(following-sibling::arg_paren) = 0]}, AttFilter.new)
|
468
536
|
calls.select do |call|
|
469
537
|
call.xpath("aref/args_add_block").size == 0 and
|
470
538
|
(call.xpath("descendant::ident").size > 1 and
|
471
|
-
! node_method?(call.xpath("ident/@value").to_s.to_sym,
|
539
|
+
! node_method?(call.xpath("ident/@value").to_s.to_sym,
|
540
|
+
options[:cookbook_dir]))
|
472
541
|
end.sort
|
473
542
|
end
|
474
543
|
|