foodcritic 3.0.3 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/CHANGELOG.md +65 -0
- data/README.md +1 -1
- data/chef_dsl_metadata/chef_11.10.0.json +10272 -0
- data/chef_dsl_metadata/chef_11.10.2.json +10272 -0
- data/chef_dsl_metadata/chef_11.10.4.json +10272 -0
- data/chef_dsl_metadata/chef_11.6.2.json +9566 -0
- data/chef_dsl_metadata/chef_11.8.0.json +10074 -0
- data/chef_dsl_metadata/chef_11.8.2.json +10074 -0
- data/features/001_check_node_access.feature +60 -0
- data/features/003_check_for_chef_server.feature +5 -0
- data/features/006_check_file_mode.feature +1 -0
- data/features/022_check_for_dodgy_conditions_within_loop.feature +10 -0
- data/features/040_check_raw_git_usage.feature +6 -0
- data/features/047_check_for_attribute_assignment_without_precedence.feature +2 -0
- data/features/build_framework_support.feature +10 -0
- data/features/exclude_paths_to_lint.feature +19 -0
- data/features/show_lines_matched.feature +4 -4
- data/features/sort_warnings.feature +1 -1
- data/features/step_definitions/cookbook_steps.rb +117 -24
- data/features/support/command_helpers.rb +45 -12
- data/features/support/env.rb +5 -0
- data/lib/foodcritic/api.rb +122 -99
- data/lib/foodcritic/ast.rb +6 -7
- data/lib/foodcritic/chef.rb +24 -23
- data/lib/foodcritic/command_line.rb +49 -41
- data/lib/foodcritic/domain.rb +12 -10
- data/lib/foodcritic/dsl.rb +4 -7
- data/lib/foodcritic/error_checker.rb +0 -3
- data/lib/foodcritic/linter.rb +45 -43
- data/lib/foodcritic/notifications.rb +32 -32
- data/lib/foodcritic/output.rb +3 -6
- data/lib/foodcritic/rake_task.rb +9 -10
- data/lib/foodcritic/rules.rb +278 -240
- data/lib/foodcritic/template.rb +6 -13
- data/lib/foodcritic/version.rb +1 -1
- data/lib/foodcritic/xml.rb +1 -3
- data/man/foodcritic.1 +6 -2
- data/man/foodcritic.1.ronn +4 -1
- data/spec/foodcritic/linter_spec.rb +2 -2
- data/spec/regression/expected-output.txt +296 -1
- metadata +160 -138
data/features/support/env.rb
CHANGED
data/lib/foodcritic/api.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'nokogiri'
|
2
|
+
require 'rufus-lru'
|
2
3
|
|
3
4
|
module FoodCritic
|
4
|
-
|
5
5
|
# Helper methods that form part of the Rules DSL.
|
6
6
|
module Api
|
7
|
-
|
8
7
|
include FoodCritic::AST
|
9
8
|
include FoodCritic::XML
|
10
9
|
|
@@ -15,20 +14,20 @@ module FoodCritic
|
|
15
14
|
|
16
15
|
# Find attribute access by type.
|
17
16
|
def attribute_access(ast, options = {})
|
18
|
-
options = {:
|
17
|
+
options = { type: :any, ignore_calls: false }.merge!(options)
|
19
18
|
return [] unless ast.respond_to?(:xpath)
|
20
19
|
|
21
20
|
unless [:any, :string, :symbol, :vivified].include?(options[:type])
|
22
|
-
|
21
|
+
fail ArgumentError, 'Node type not recognised'
|
23
22
|
end
|
24
23
|
|
25
24
|
case options[:type]
|
26
|
-
|
25
|
+
when :any then
|
27
26
|
vivified_attribute_access(ast, options) +
|
28
27
|
standard_attribute_access(ast, options)
|
29
|
-
|
28
|
+
when :vivified then
|
30
29
|
vivified_attribute_access(ast, options)
|
31
|
-
|
30
|
+
else
|
32
31
|
standard_attribute_access(ast, options)
|
33
32
|
end
|
34
33
|
end
|
@@ -38,8 +37,8 @@ module FoodCritic
|
|
38
37
|
raise_unless_xpath!(ast)
|
39
38
|
# TODO: This expression is too loose, but also will fail to match other
|
40
39
|
# types of conditionals.
|
41
|
-
(!
|
42
|
-
child::aref or self::call]
|
40
|
+
(!ast.xpath(%q{//*[self::if or self::ifop or self::unless]/
|
41
|
+
*[self::aref or child::aref or self::call]
|
43
42
|
[count(descendant::const[@value = 'Chef' or @value = 'Config']) = 2
|
44
43
|
and
|
45
44
|
( count(descendant::ident[@value='solo']) > 0
|
@@ -47,13 +46,16 @@ module FoodCritic
|
|
47
46
|
)
|
48
47
|
]}).empty?) ||
|
49
48
|
ast.xpath('//if_mod[return][aref/descendant::ident/@value="solo"]/aref/
|
50
|
-
const_path_ref/descendant::const').map
|
49
|
+
const_path_ref/descendant::const').map do |c|
|
50
|
+
c['value']
|
51
|
+
end == %w(Chef Config)
|
51
52
|
end
|
52
53
|
|
53
|
-
# Is the
|
54
|
+
# Is the
|
55
|
+
# [chef-solo-search library](https://github.com/edelight/chef-solo-search)
|
54
56
|
# available?
|
55
57
|
def chef_solo_search_supported?(recipe_path)
|
56
|
-
return false if recipe_path.nil? || !
|
58
|
+
return false if recipe_path.nil? || !File.exist?(recipe_path)
|
57
59
|
|
58
60
|
# Look for the chef-solo-search library.
|
59
61
|
#
|
@@ -61,21 +63,21 @@ module FoodCritic
|
|
61
63
|
# is not under the same `cookbook_path` as the cookbook being checked.
|
62
64
|
cbk_tree_path = Pathname.new(File.join(recipe_path, '../../..'))
|
63
65
|
search_libs = Dir[File.join(cbk_tree_path.realpath,
|
64
|
-
|
66
|
+
'*/libraries/search.rb')]
|
65
67
|
|
66
68
|
# True if any of the candidate library files match the signature:
|
67
69
|
#
|
68
70
|
# class Chef
|
69
71
|
# def search
|
70
72
|
search_libs.any? do |lib|
|
71
|
-
!
|
73
|
+
!read_ast(lib).xpath(%q{//class[count(descendant::const[@value='Chef']
|
72
74
|
) = 1]/descendant::def/ident[@value='search']}).empty?
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
78
|
# The name of the cookbook containing the specified file.
|
77
79
|
def cookbook_name(file)
|
78
|
-
|
80
|
+
fail ArgumentError, 'File cannot be nil or empty' if file.to_s.empty?
|
79
81
|
|
80
82
|
until (file.split(File::SEPARATOR) & standard_cookbook_subdirs).empty? do
|
81
83
|
file = File.absolute_path(File.dirname(file.to_s))
|
@@ -86,9 +88,10 @@ module FoodCritic
|
|
86
88
|
# We also need to consult the metadata in case the cookbook name has been
|
87
89
|
# overridden there. This supports only string literals.
|
88
90
|
md_path = File.join(file, 'metadata.rb')
|
89
|
-
if File.
|
91
|
+
if File.exist?(md_path)
|
90
92
|
name = read_ast(md_path).xpath("//stmts_add/
|
91
|
-
command[ident/@value='name']/
|
93
|
+
command[ident/@value='name']/
|
94
|
+
descendant::tstring_content/@value").to_s
|
92
95
|
return name unless name.empty?
|
93
96
|
end
|
94
97
|
File.basename(file)
|
@@ -109,14 +112,15 @@ module FoodCritic
|
|
109
112
|
# %w{foo bar baz}.each do |cbk|
|
110
113
|
# depends cbk
|
111
114
|
# end
|
112
|
-
deps = deps.to_a +
|
113
|
-
|
115
|
+
deps = deps.to_a +
|
116
|
+
word_list_values(ast, "//command[ident/@value='depends']")
|
117
|
+
deps.uniq.map { |dep| dep['value'].strip }
|
114
118
|
end
|
115
119
|
|
116
120
|
# The key / value pair in an environment or role ruby file
|
117
121
|
def field(ast, field_name)
|
118
122
|
if field_name.nil? || field_name.to_s.empty?
|
119
|
-
|
123
|
+
fail ArgumentError, 'Field name cannot be nil or empty'
|
120
124
|
end
|
121
125
|
ast.xpath("//command[ident/@value='#{field_name}']")
|
122
126
|
end
|
@@ -125,14 +129,14 @@ module FoodCritic
|
|
125
129
|
def field_value(ast, field_name)
|
126
130
|
field(ast, field_name).xpath('args_add_block/descendant::tstring_content
|
127
131
|
[count(ancestor::args_add) = 1][count(ancestor::string_add) = 1]
|
128
|
-
/@value').map{|a| a.to_s}.last
|
132
|
+
/@value').map { |a| a.to_s }.last
|
129
133
|
end
|
130
134
|
|
131
135
|
# Create a match for a specified file. Use this if the presence of the file
|
132
136
|
# triggers the warning rather than content.
|
133
137
|
def file_match(file)
|
134
|
-
|
135
|
-
{:
|
138
|
+
fail ArgumentError, 'Filename cannot be nil' if file.nil?
|
139
|
+
{ filename: file, matched: file, line: 1, column: 1 }
|
136
140
|
end
|
137
141
|
|
138
142
|
# Find Chef resources of the specified type.
|
@@ -148,7 +152,7 @@ module FoodCritic
|
|
148
152
|
# find_resources(ast, :type => :service)
|
149
153
|
#
|
150
154
|
def find_resources(ast, options = {})
|
151
|
-
options = {:
|
155
|
+
options = { type: :any }.merge!(options)
|
152
156
|
return [] unless ast.respond_to?(:xpath)
|
153
157
|
scope_type = ''
|
154
158
|
scope_type = "[@value='#{options[:type]}']" unless options[:type] == :any
|
@@ -170,7 +174,7 @@ module FoodCritic
|
|
170
174
|
# included_recipes(ast)
|
171
175
|
# included_recipes(ast, :with_partial_names => true)
|
172
176
|
#
|
173
|
-
def included_recipes(ast, options = {:
|
177
|
+
def included_recipes(ast, options = { with_partial_names: true })
|
174
178
|
raise_unless_xpath!(ast)
|
175
179
|
|
176
180
|
filter = ['[count(descendant::args_add) = 1]']
|
@@ -181,16 +185,18 @@ module FoodCritic
|
|
181
185
|
filter << '[count(descendant::string_embexpr) = 0]'
|
182
186
|
end
|
183
187
|
|
184
|
-
string_desc = '[descendant::args_add/string_literal]/
|
188
|
+
string_desc = '[descendant::args_add/string_literal]/
|
189
|
+
descendant::tstring_content'
|
185
190
|
included = ast.xpath([
|
186
191
|
"//command[ident/@value = 'include_recipe']",
|
187
|
-
"//fcall[ident/@value = 'include_recipe']/
|
192
|
+
"//fcall[ident/@value = 'include_recipe']/
|
193
|
+
following-sibling::arg_paren",
|
188
194
|
].map do |recipe_include|
|
189
195
|
recipe_include + filter.join + string_desc
|
190
196
|
end.join(' | '))
|
191
197
|
|
192
198
|
# Hash keyed by recipe name with matched nodes.
|
193
|
-
included.inject(Hash.new([])){|h, i| h[i['value']] += [i]; h}
|
199
|
+
included.inject(Hash.new([])) { |h, i| h[i['value']] += [i]; h }
|
194
200
|
end
|
195
201
|
|
196
202
|
# Searches performed by the specified recipe that are literal strings.
|
@@ -206,28 +212,28 @@ module FoodCritic
|
|
206
212
|
raise_unless_xpath!(node)
|
207
213
|
pos = node.xpath('descendant::pos').first
|
208
214
|
return nil if pos.nil?
|
209
|
-
{:
|
210
|
-
:
|
215
|
+
{ matched: node.respond_to?(:name) ? node.name : '',
|
216
|
+
line: pos['line'].to_i, column: pos['column'].to_i }
|
211
217
|
end
|
212
218
|
|
213
219
|
# Read the AST for the given Ruby source file
|
214
220
|
def read_ast(file)
|
215
|
-
|
216
|
-
|
221
|
+
@ast_cache ||= Rufus::Lru::Hash.new(5)
|
222
|
+
if @ast_cache.include?(file)
|
223
|
+
@ast_cache[file]
|
217
224
|
else
|
218
|
-
|
225
|
+
@ast_cache[file] = uncached_read_ast(file)
|
219
226
|
end
|
220
|
-
build_xml(Ripper::SexpBuilder.new(source).parse)
|
221
227
|
end
|
222
228
|
|
223
229
|
# Retrieve a single-valued attribute from the specified resource.
|
224
230
|
def resource_attribute(resource, name)
|
225
|
-
|
231
|
+
fail ArgumentError, 'Attribute name cannot be empty' if name.empty?
|
226
232
|
resource_attributes(resource)[name.to_s]
|
227
233
|
end
|
228
234
|
|
229
235
|
# Retrieve all attributes from the specified resource.
|
230
|
-
def resource_attributes(resource, options={})
|
236
|
+
def resource_attributes(resource, options = {})
|
231
237
|
atts = {}
|
232
238
|
name = resource_name(resource, options)
|
233
239
|
atts[:name] = name unless name.empty?
|
@@ -239,8 +245,10 @@ module FoodCritic
|
|
239
245
|
# Resources keyed by type, with an array of matching nodes for each.
|
240
246
|
def resource_attributes_by_type(ast)
|
241
247
|
result = {}
|
242
|
-
resources_by_type(ast).each do |type,resources|
|
243
|
-
result[type] = resources.map
|
248
|
+
resources_by_type(ast).each do |type, resources|
|
249
|
+
result[type] = resources.map do |resource|
|
250
|
+
resource_attributes(resource)
|
251
|
+
end
|
244
252
|
end
|
245
253
|
result
|
246
254
|
end
|
@@ -248,13 +256,14 @@ module FoodCritic
|
|
248
256
|
# Retrieve the name attribute associated with the specified resource.
|
249
257
|
def resource_name(resource, options = {})
|
250
258
|
raise_unless_xpath!(resource)
|
251
|
-
options = {:
|
259
|
+
options = { return_expressions: false }.merge(options)
|
252
260
|
if options[:return_expressions]
|
253
261
|
name = resource.xpath('command/args_add_block')
|
254
|
-
if name.xpath('descendant::string_add').size == 1
|
255
|
-
name.xpath('descendant::string_literal').size == 1
|
256
|
-
name.xpath(
|
257
|
-
|
262
|
+
if name.xpath('descendant::string_add').size == 1 &&
|
263
|
+
name.xpath('descendant::string_literal').size == 1 &&
|
264
|
+
name.xpath(
|
265
|
+
'descendant::*[self::call or self::string_embexpr]').empty?
|
266
|
+
name.xpath('descendant::tstring_content/@value').to_s
|
258
267
|
else
|
259
268
|
name
|
260
269
|
end
|
@@ -267,7 +276,7 @@ module FoodCritic
|
|
267
276
|
# Resources in an AST, keyed by type.
|
268
277
|
def resources_by_type(ast)
|
269
278
|
raise_unless_xpath!(ast)
|
270
|
-
result = Hash.new{|hash, key| hash[key] = Array.new}
|
279
|
+
result = Hash.new { |hash, key| hash[key] = Array.new }
|
271
280
|
find_resources(ast).each do |resource|
|
272
281
|
result[resource_type(resource)] << resource
|
273
282
|
end
|
@@ -279,7 +288,7 @@ module FoodCritic
|
|
279
288
|
raise_unless_xpath!(resource)
|
280
289
|
type = resource.xpath('string(command/ident/@value)')
|
281
290
|
if type.empty?
|
282
|
-
|
291
|
+
fail ArgumentError, 'Provided AST node is not a resource'
|
283
292
|
end
|
284
293
|
type
|
285
294
|
end
|
@@ -291,7 +300,7 @@ module FoodCritic
|
|
291
300
|
|
292
301
|
checker = FoodCritic::ErrorChecker.new(str)
|
293
302
|
checker.parse
|
294
|
-
!
|
303
|
+
!checker.error?
|
295
304
|
end
|
296
305
|
|
297
306
|
# Searches performed by the provided AST.
|
@@ -302,22 +311,24 @@ module FoodCritic
|
|
302
311
|
|
303
312
|
# The list of standard cookbook sub-directories.
|
304
313
|
def standard_cookbook_subdirs
|
305
|
-
%w
|
306
|
-
templates
|
314
|
+
%w(attributes definitions files libraries providers recipes resources
|
315
|
+
templates)
|
307
316
|
end
|
308
317
|
|
309
318
|
# Platforms declared as supported in cookbook metadata
|
310
319
|
def supported_platforms(ast)
|
311
320
|
platforms = ast.xpath('//command[ident/@value="supports"]/
|
312
|
-
descendant::*[self::string_literal or self::symbol_literal]
|
321
|
+
descendant::*[self::string_literal or self::symbol_literal]
|
322
|
+
[position() = 1]
|
313
323
|
[self::symbol_literal or count(descendant::string_add) = 1]/
|
314
324
|
descendant::*[self::tstring_content | self::ident]')
|
315
|
-
platforms = platforms.to_a +
|
325
|
+
platforms = platforms.to_a +
|
326
|
+
word_list_values(ast, "//command[ident/@value='supports']")
|
316
327
|
platforms.map do |platform|
|
317
328
|
versions = platform.xpath('ancestor::args_add[position() > 1]/
|
318
|
-
string_literal/descendant::tstring_content/@value').map{|v| v.to_s}
|
319
|
-
{:
|
320
|
-
end.sort{|a,b| a[:platform] <=> b[:platform]}
|
329
|
+
string_literal/descendant::tstring_content/@value').map { |v| v.to_s }
|
330
|
+
{ platform: platform['value'], versions: versions }
|
331
|
+
end.sort { |a, b| a[:platform] <=> b[:platform] }
|
321
332
|
end
|
322
333
|
|
323
334
|
# Template filename
|
@@ -333,11 +344,11 @@ module FoodCritic
|
|
333
344
|
end
|
334
345
|
end
|
335
346
|
|
336
|
-
def templates_included(all_templates, template_path, depth=1)
|
337
|
-
|
347
|
+
def templates_included(all_templates, template_path, depth = 1)
|
348
|
+
fail RecursedTooFarError.new(template_path) if depth > 10
|
338
349
|
partials = read_ast(template_path).xpath('//*[self::command or
|
339
350
|
child::fcall][descendant::ident/@value="render"]//args_add/
|
340
|
-
string_literal//tstring_content/@value').map{|p| p.to_s}
|
351
|
+
string_literal//tstring_content/@value').map { |p| p.to_s }
|
341
352
|
Array(template_path) + partials.map do |included_partial|
|
342
353
|
partial_path = Array(all_templates).find do |path|
|
343
354
|
(Pathname.new(template_path).dirname + included_partial).to_s == path
|
@@ -367,21 +378,21 @@ module FoodCritic
|
|
367
378
|
atts = {}
|
368
379
|
resource.xpath("do_block/descendant::method_add_block[
|
369
380
|
count(ancestor::do_block) = 1][brace_block | do_block]").each do |batt|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
381
|
+
att_name = batt.xpath('string(method_add_arg/fcall/ident/@value)')
|
382
|
+
if att_name && !att_name.empty? && batt.children.length > 1
|
383
|
+
atts[att_name] = batt.children[1]
|
384
|
+
end
|
374
385
|
end
|
375
386
|
atts
|
376
387
|
end
|
377
388
|
|
378
389
|
def block_depth(resource)
|
379
|
-
resource.path.split('/').group_by{|e|e}['method_add_block'].size
|
390
|
+
resource.path.split('/').group_by { |e|e }['method_add_block'].size
|
380
391
|
end
|
381
392
|
|
382
393
|
# Recurse the nested arrays provided by Ripper to create a tree we can more
|
383
394
|
# easily apply expressions to.
|
384
|
-
def build_xml(node, doc = nil, xml_node=nil)
|
395
|
+
def build_xml(node, doc = nil, xml_node = nil)
|
385
396
|
doc, xml_node = xml_document(doc, xml_node)
|
386
397
|
|
387
398
|
if node.respond_to?(:each)
|
@@ -408,20 +419,21 @@ module FoodCritic
|
|
408
419
|
end
|
409
420
|
|
410
421
|
def extract_attribute_value(att, options = {})
|
411
|
-
if !
|
422
|
+
if !att.xpath('args_add_block[count(descendant::args_add)>1]').empty?
|
412
423
|
att.xpath('args_add_block').first
|
413
|
-
elsif !
|
424
|
+
elsif !att.xpath('args_add_block/args_add/
|
414
425
|
var_ref/kw[@value="true" or @value="false"]').empty?
|
415
426
|
att.xpath('string(args_add_block/args_add/
|
416
427
|
var_ref/kw/@value)') == 'true'
|
417
|
-
elsif !
|
428
|
+
elsif !att.xpath('descendant::assoc_new').empty?
|
418
429
|
att.xpath('descendant::assoc_new')
|
419
|
-
elsif !
|
430
|
+
elsif !att.xpath('descendant::int').empty?
|
420
431
|
att.xpath('descendant::int/@value').to_s
|
421
432
|
elsif att.xpath('descendant::symbol').empty?
|
422
|
-
if options[:return_expressions]
|
423
|
-
(att.xpath('descendant::string_add').size != 1
|
424
|
-
|
433
|
+
if options[:return_expressions] &&
|
434
|
+
(att.xpath('descendant::string_add').size != 1 ||
|
435
|
+
att.xpath('descendant::*[self::call or
|
436
|
+
self::string_embexpr]').any?)
|
425
437
|
att
|
426
438
|
else
|
427
439
|
att.xpath('string(descendant::tstring_content/@value)')
|
@@ -438,33 +450,34 @@ module FoodCritic
|
|
438
450
|
end
|
439
451
|
|
440
452
|
def node_method?(meth, cookbook_dir)
|
441
|
-
chef_dsl_methods.include?(meth) ||
|
453
|
+
chef_dsl_methods.include?(meth) ||
|
454
|
+
patched_node_method?(meth, cookbook_dir)
|
442
455
|
end
|
443
456
|
|
444
457
|
def normal_attributes(resource, options = {})
|
445
458
|
atts = {}
|
446
459
|
resource.xpath('do_block/descendant::*[self::command or
|
447
460
|
self::method_add_arg][count(ancestor::do_block) >= 1]').each do |att|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
461
|
+
blocks = att.xpath('ancestor::method_add_block/method_add_arg/fcall')
|
462
|
+
next if blocks.any? { |a| block_depth(a) > block_depth(resource) }
|
463
|
+
att_name = att.xpath('string(ident/@value |
|
464
|
+
fcall/ident[@value="variables"]/@value)')
|
465
|
+
unless att_name.empty?
|
466
|
+
atts[att_name] = extract_attribute_value(att, options)
|
467
|
+
end
|
455
468
|
end
|
456
469
|
atts
|
457
470
|
end
|
458
471
|
|
459
472
|
def patched_node_method?(meth, cookbook_dir)
|
460
|
-
return false if cookbook_dir.nil? || !
|
473
|
+
return false if cookbook_dir.nil? || !Dir.exists?(cookbook_dir)
|
461
474
|
|
462
475
|
# TODO: Modify this to work with multiple cookbook paths
|
463
476
|
cbk_tree_path = Pathname.new(File.join(cookbook_dir, '..'))
|
464
477
|
libs = Dir[File.join(cbk_tree_path.realpath, '*/libraries/*.rb')]
|
465
478
|
|
466
479
|
libs.any? do |lib|
|
467
|
-
!
|
480
|
+
!read_ast(lib).xpath(%Q{//class[count(descendant::const[@value='Chef'])
|
468
481
|
> 0][count(descendant::const[@value='Node']) > 0]/descendant::def/
|
469
482
|
ident[@value='#{meth.to_s}']}).empty?
|
470
483
|
end
|
@@ -472,18 +485,28 @@ module FoodCritic
|
|
472
485
|
|
473
486
|
def raise_unless_xpath!(ast)
|
474
487
|
unless ast.respond_to?(:xpath)
|
475
|
-
|
488
|
+
fail ArgumentError, 'AST must support #xpath'
|
476
489
|
end
|
477
490
|
end
|
478
491
|
|
492
|
+
def uncached_read_ast(file)
|
493
|
+
source = if file.to_s.split(File::SEPARATOR).include?('templates')
|
494
|
+
template_expressions_only(file)
|
495
|
+
else
|
496
|
+
File.read(file)
|
497
|
+
end
|
498
|
+
build_xml(Ripper::SexpBuilder.new(source).parse)
|
499
|
+
end
|
500
|
+
|
479
501
|
# XPath custom function
|
480
502
|
class AttFilter
|
481
503
|
def is_att_type(value)
|
482
504
|
return [] unless value.respond_to?(:select)
|
483
505
|
value.select do |n|
|
484
|
-
%w
|
506
|
+
%w(
|
485
507
|
automatic_attrs
|
486
508
|
default
|
509
|
+
default!
|
487
510
|
default_unless
|
488
511
|
force_default
|
489
512
|
force_override
|
@@ -491,10 +514,11 @@ module FoodCritic
|
|
491
514
|
normal
|
492
515
|
normal_unless
|
493
516
|
override
|
517
|
+
override!
|
494
518
|
override_unless
|
495
519
|
set
|
496
520
|
set_unless
|
497
|
-
|
521
|
+
).include?(n.to_s)
|
498
522
|
end
|
499
523
|
end
|
500
524
|
end
|
@@ -502,14 +526,14 @@ module FoodCritic
|
|
502
526
|
def standard_attribute_access(ast, options)
|
503
527
|
if options[:type] == :any
|
504
528
|
[:string, :symbol].map do |type|
|
505
|
-
standard_attribute_access(ast, options.merge(:
|
529
|
+
standard_attribute_access(ast, options.merge(type: type))
|
506
530
|
end.inject(:+)
|
507
531
|
else
|
508
532
|
type = if options[:type] == :string
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
533
|
+
'tstring_content'
|
534
|
+
else
|
535
|
+
'*[self::symbol or self::dyna_symbol]'
|
536
|
+
end
|
513
537
|
expr = '//*[self::aref_field or self::aref][count(method_add_arg) = 0]'
|
514
538
|
expr += '[count(is_att_type(descendant::var_ref/ident/@value)) =
|
515
539
|
count(descendant::var_ref/ident/@value)]'
|
@@ -527,24 +551,24 @@ module FoodCritic
|
|
527
551
|
|
528
552
|
def template_expressions_only(file)
|
529
553
|
exprs = Template::ExpressionExtractor.new.extract(File.read(file))
|
530
|
-
lines = Array.new(exprs.map{|e| e[:line]}.max || 0, '')
|
554
|
+
lines = Array.new(exprs.map { |e| e[:line] }.max || 0, '')
|
531
555
|
exprs.each do |e|
|
532
|
-
lines[e[:line] -1] += ';' unless lines[e[:line] -1].empty?
|
533
|
-
lines[e[:line] -1] += e[:code]
|
556
|
+
lines[e[:line] - 1] += ';' unless lines[e[:line] - 1].empty?
|
557
|
+
lines[e[:line] - 1] += e[:code]
|
534
558
|
end
|
535
559
|
lines.join("\n")
|
536
560
|
end
|
537
561
|
|
538
|
-
def vivified_attribute_access(ast, options={})
|
562
|
+
def vivified_attribute_access(ast, options = {})
|
539
563
|
calls = ast.xpath(%Q{//*[self::call or self::field]
|
540
564
|
[is_att_type(vcall/ident/@value) or is_att_type(var_ref/ident/@value)]
|
541
565
|
#{ignore_attributes_xpath(options[:ignore])}
|
542
566
|
[@value='.'][count(following-sibling::arg_paren) = 0]}, AttFilter.new)
|
543
567
|
calls.select do |call|
|
544
|
-
call.xpath(
|
545
|
-
(call.xpath(
|
546
|
-
!
|
547
|
-
|
568
|
+
call.xpath('aref/args_add_block').size == 0 &&
|
569
|
+
(call.xpath('descendant::ident').size > 1 &&
|
570
|
+
!node_method?(call.xpath('ident/@value').to_s.to_sym,
|
571
|
+
options[:cookbook_dir]))
|
548
572
|
end.sort
|
549
573
|
end
|
550
574
|
|
@@ -553,11 +577,10 @@ module FoodCritic
|
|
553
577
|
if var_ref.empty?
|
554
578
|
[]
|
555
579
|
else
|
556
|
-
ast.xpath(%Q
|
557
|
-
ancestor::method_add_block/call/
|
580
|
+
ast.xpath(%Q(descendant::block_var/params/
|
581
|
+
ident#{var_ref.first['value']}/ancestor::method_add_block/call/
|
582
|
+
descendant::tstring_content))
|
558
583
|
end
|
559
584
|
end
|
560
|
-
|
561
585
|
end
|
562
|
-
|
563
586
|
end
|