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