better_html 1.0.1 → 1.0.2
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.
- checksums.yaml +4 -4
- data/lib/better_html/ast/iterator.rb +3 -3
- data/lib/better_html/ast/node.rb +6 -2
- data/lib/better_html/errors.rb +2 -11
- data/lib/better_html/test_helper/ruby_node.rb +103 -0
- data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +29 -0
- data/lib/better_html/test_helper/safe_erb/base.rb +56 -0
- data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +34 -0
- data/lib/better_html/test_helper/safe_erb/no_statements.rb +40 -0
- data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +65 -0
- data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +163 -0
- data/lib/better_html/test_helper/safe_erb_tester.rb +32 -285
- data/lib/better_html/tokenizer/location.rb +1 -1
- data/lib/better_html/version.rb +1 -1
- data/test/better_html/errors_test.rb +13 -0
- data/test/better_html/test_helper/ruby_node_test.rb +288 -0
- data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +45 -0
- data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +37 -0
- data/test/better_html/test_helper/safe_erb/no_statements_test.rb +128 -0
- data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +149 -0
- data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +295 -0
- data/test/test_helper.rb +1 -0
- metadata +23 -7
- data/lib/better_html/test_helper/ruby_expr.rb +0 -117
- data/test/better_html/test_helper/ruby_expr_test.rb +0 -283
- data/test/better_html/test_helper/safe_erb_tester_test.rb +0 -450
@@ -1,6 +1,11 @@
|
|
1
|
-
require 'better_html/test_helper/ruby_expr'
|
2
|
-
require 'better_html/test_helper/safety_error'
|
3
1
|
require 'better_html/parser'
|
2
|
+
require 'better_html/test_helper/safety_error'
|
3
|
+
require 'better_html/test_helper/safe_erb/base'
|
4
|
+
require 'better_html/test_helper/safe_erb/no_statements'
|
5
|
+
require 'better_html/test_helper/safe_erb/allowed_script_type'
|
6
|
+
require 'better_html/test_helper/safe_erb/no_javascript_tag_helper'
|
7
|
+
require 'better_html/test_helper/safe_erb/tag_interpolation'
|
8
|
+
require 'better_html/test_helper/safe_erb/script_interpolation'
|
4
9
|
require 'better_html/tree/tag'
|
5
10
|
|
6
11
|
module BetterHtml
|
@@ -30,292 +35,34 @@ Always use raw and to_json together within <script> tags:
|
|
30
35
|
EOF
|
31
36
|
|
32
37
|
def assert_erb_safety(data, **options)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
options = options.present? ? options.dup : {}
|
39
|
+
options[:template_language] ||= :html
|
40
|
+
parser = BetterHtml::Parser.new(data, options)
|
41
|
+
|
42
|
+
tester_classes = [
|
43
|
+
SafeErb::NoStatements,
|
44
|
+
SafeErb::AllowedScriptType,
|
45
|
+
SafeErb::NoJavascriptTagHelper,
|
46
|
+
SafeErb::TagInterpolation,
|
47
|
+
SafeErb::ScriptInterpolation,
|
48
|
+
]
|
49
|
+
|
50
|
+
testers = tester_classes.map do |tester_klass|
|
51
|
+
tester = tester_klass.new(parser)
|
52
|
+
end
|
53
|
+
testers.each(&:validate)
|
54
|
+
errors = testers.map(&:errors).flatten
|
55
|
+
|
56
|
+
messages = errors.map do |error|
|
57
|
+
<<~EOL
|
58
|
+
On line #{error.location.line}
|
59
|
+
#{error.message}
|
60
|
+
#{error.location.line_source_with_underline}\n
|
41
61
|
EOL
|
42
62
|
end
|
63
|
+
messages << SAFETY_TIPS
|
43
64
|
|
44
|
-
|
45
|
-
|
46
|
-
assert_predicate tester.errors, :empty?, message
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
class Tester
|
52
|
-
attr_reader :errors
|
53
|
-
|
54
|
-
VALID_JAVASCRIPT_TAG_TYPES = ['text/javascript', 'text/template', 'text/html']
|
55
|
-
|
56
|
-
def initialize(data, config: BetterHtml.config, **options)
|
57
|
-
@data = data
|
58
|
-
@config = config
|
59
|
-
@errors = Errors.new
|
60
|
-
@options = options.present? ? options.dup : {}
|
61
|
-
@options[:template_language] ||= :html
|
62
|
-
@parser = BetterHtml::Parser.new(data, @options.slice(:template_language))
|
63
|
-
validate!
|
64
|
-
end
|
65
|
-
|
66
|
-
def add_error(message, location:)
|
67
|
-
@errors.add(SafetyError.new(message, location: location))
|
68
|
-
end
|
69
|
-
|
70
|
-
def validate!
|
71
|
-
@parser.nodes_with_type(:tag).each do |tag_node|
|
72
|
-
tag = Tree::Tag.from_node(tag_node)
|
73
|
-
next if tag.closing?
|
74
|
-
|
75
|
-
validate_tag_attributes(tag)
|
76
|
-
|
77
|
-
if tag.name == 'script'
|
78
|
-
index = @parser.ast.to_a.find_index(tag_node)
|
79
|
-
next_node = @parser.ast.to_a[index + 1]
|
80
|
-
if next_node.type == :text
|
81
|
-
if (tag.attributes['type']&.value || "text/javascript") == "text/javascript"
|
82
|
-
validate_script_tag_content(next_node)
|
83
|
-
end
|
84
|
-
validate_no_statements(next_node) unless tag.attributes['type']&.value == "text/html"
|
85
|
-
end
|
86
|
-
|
87
|
-
validate_javascript_tag_type(tag)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
@parser.nodes_with_type(:text).each do |node|
|
92
|
-
validate_text_node(node)
|
93
|
-
|
94
|
-
if @parser.template_language == :javascript
|
95
|
-
validate_script_tag_content(node)
|
96
|
-
validate_no_statements(node)
|
97
|
-
else
|
98
|
-
validate_no_javascript_tag(node)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
@parser.nodes_with_type(:cdata, :comment).each do |node|
|
103
|
-
validate_no_statements(node)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def erb_nodes(node)
|
108
|
-
Enumerator.new do |yielder|
|
109
|
-
next if node.nil?
|
110
|
-
node.descendants(:erb).each do |erb_node|
|
111
|
-
indicator_node, _, code_node, _ = *erb_node
|
112
|
-
yielder.yield(erb_node, indicator_node, code_node)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def validate_javascript_tag_type(tag)
|
118
|
-
return unless type_attribute = tag.attributes['type']
|
119
|
-
return if VALID_JAVASCRIPT_TAG_TYPES.include?(type_attribute.value)
|
120
|
-
|
121
|
-
add_error(
|
122
|
-
"#{type_attribute.value} is not a valid type, valid types are #{VALID_JAVASCRIPT_TAG_TYPES.join(', ')}",
|
123
|
-
location: type_attribute.loc
|
124
|
-
)
|
125
|
-
end
|
126
|
-
|
127
|
-
def validate_tag_attributes(tag)
|
128
|
-
tag.attributes.each do |attribute|
|
129
|
-
erb_nodes(attribute.value_node).each do |erb_node, indicator_node, code_node|
|
130
|
-
next if indicator_node.nil?
|
131
|
-
|
132
|
-
indicator = indicator_node.loc.source
|
133
|
-
source = code_node.loc.source
|
134
|
-
|
135
|
-
if indicator == '='
|
136
|
-
begin
|
137
|
-
expr = RubyExpr.parse(source)
|
138
|
-
validate_tag_expression(code_node, expr, attribute.name)
|
139
|
-
rescue RubyExpr::ParseError
|
140
|
-
nil
|
141
|
-
end
|
142
|
-
elsif indicator == '=='
|
143
|
-
add_error(
|
144
|
-
"erb interpolation with '<%==' inside html attribute is never safe",
|
145
|
-
location: erb_node.loc
|
146
|
-
)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def validate_text_node(text_node)
|
153
|
-
erb_nodes(text_node).each do |erb_node, indicator_node, code_node|
|
154
|
-
indicator = indicator_node&.loc&.source
|
155
|
-
next if indicator == '#'
|
156
|
-
source = code_node.loc.source
|
157
|
-
|
158
|
-
begin
|
159
|
-
expr = RubyExpr.parse(source)
|
160
|
-
validate_ruby_helper(code_node, expr)
|
161
|
-
rescue RubyExpr::ParseError
|
162
|
-
nil
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def validate_ruby_helper(parent_token, expr)
|
168
|
-
expr.traverse(only: [:send, :csend]) do |send_node|
|
169
|
-
expr.each_child_node(send_node, only: :hash) do |hash_node|
|
170
|
-
expr.each_child_node(hash_node, only: :pair) do |pair_node|
|
171
|
-
validate_ruby_helper_hash_entry(parent_token, expr, nil, *pair_node.children)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def validate_ruby_helper_hash_entry(parent_token, expr, key_prefix, key_node, value_node)
|
178
|
-
return unless [:sym, :str].include?(key_node.type)
|
179
|
-
key = [key_prefix, key_node.children.first.to_s].compact.join('-').dasherize
|
180
|
-
case value_node.type
|
181
|
-
when :dstr
|
182
|
-
validate_ruby_helper_hash_value(parent_token, expr, key, value_node)
|
183
|
-
when :hash
|
184
|
-
if key == 'data'
|
185
|
-
expr.each_child_node(value_node, only: :pair) do |pair_node|
|
186
|
-
validate_ruby_helper_hash_entry(parent_token, expr, key, *pair_node.children)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def validate_ruby_helper_hash_value(parent_token, expr, attr_name, hash_value)
|
193
|
-
expr.each_child_node(hash_value, only: :begin) do |child|
|
194
|
-
validate_tag_expression(parent_token, RubyExpr.new(child), attr_name)
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def validate_tag_expression(parent_token, expr, attr_name)
|
199
|
-
return if expr.static_value?
|
200
|
-
|
201
|
-
if @config.javascript_attribute_name?(attr_name) && expr.calls.empty?
|
202
|
-
add_error(
|
203
|
-
"erb interpolation in javascript attribute must call '(...).to_json'",
|
204
|
-
location: Tokenizer::Location.new(
|
205
|
-
@data,
|
206
|
-
parent_token.loc.start + expr.start,
|
207
|
-
parent_token.loc.start + expr.end - 1
|
208
|
-
)
|
209
|
-
)
|
210
|
-
return
|
211
|
-
end
|
212
|
-
|
213
|
-
expr.calls.each do |call|
|
214
|
-
if call.method == :raw
|
215
|
-
add_error(
|
216
|
-
"erb interpolation with '<%= raw(...) %>' inside html attribute is never safe",
|
217
|
-
location: Tokenizer::Location.new(
|
218
|
-
@data,
|
219
|
-
parent_token.loc.start + expr.start,
|
220
|
-
parent_token.loc.start + expr.end - 1
|
221
|
-
)
|
222
|
-
)
|
223
|
-
elsif call.method == :html_safe
|
224
|
-
add_error(
|
225
|
-
"erb interpolation with '<%= (...).html_safe %>' inside html attribute is never safe",
|
226
|
-
location: Tokenizer::Location.new(
|
227
|
-
@data,
|
228
|
-
parent_token.loc.start + expr.start,
|
229
|
-
parent_token.loc.start + expr.end - 1
|
230
|
-
)
|
231
|
-
)
|
232
|
-
elsif @config.javascript_attribute_name?(attr_name) && !@config.javascript_safe_method?(call.method)
|
233
|
-
add_error(
|
234
|
-
"erb interpolation in javascript attribute must call '(...).to_json'",
|
235
|
-
location: Tokenizer::Location.new(
|
236
|
-
@data,
|
237
|
-
parent_token.loc.start + expr.start,
|
238
|
-
parent_token.loc.start + expr.end - 1
|
239
|
-
)
|
240
|
-
)
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
def validate_script_tag_content(node)
|
246
|
-
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
247
|
-
next unless indicator_node.present?
|
248
|
-
indicator = indicator_node.loc.source
|
249
|
-
next if indicator == '#'
|
250
|
-
source = code_node.loc.source
|
251
|
-
|
252
|
-
begin
|
253
|
-
expr = RubyExpr.parse(source)
|
254
|
-
validate_script_expression(erb_node, expr)
|
255
|
-
rescue RubyExpr::ParseError
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
def validate_script_expression(parent_node, expr)
|
261
|
-
if expr.calls.empty?
|
262
|
-
add_error(
|
263
|
-
"erb interpolation in javascript tag must call '(...).to_json'",
|
264
|
-
location: parent_node.loc,
|
265
|
-
)
|
266
|
-
return
|
267
|
-
end
|
268
|
-
|
269
|
-
expr.calls.each do |call|
|
270
|
-
if call.method == :raw
|
271
|
-
call.arguments.each do |argument_node|
|
272
|
-
arguments_expr = RubyExpr.new(argument_node)
|
273
|
-
validate_script_expression(parent_node, arguments_expr)
|
274
|
-
end
|
275
|
-
elsif call.method == :html_safe
|
276
|
-
instance_expr = RubyExpr.new(call.instance)
|
277
|
-
validate_script_expression(parent_node, instance_expr)
|
278
|
-
elsif !@config.javascript_safe_method?(call.method)
|
279
|
-
add_error(
|
280
|
-
"erb interpolation in javascript tag must call '(...).to_json'",
|
281
|
-
location: parent_node.loc,
|
282
|
-
)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def validate_no_statements(node)
|
288
|
-
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
289
|
-
next unless indicator_node.nil?
|
290
|
-
source = code_node.loc.source
|
291
|
-
next if /\A\s*end/m === source
|
292
|
-
|
293
|
-
add_error(
|
294
|
-
"erb statement not allowed here; did you mean '<%=' ?",
|
295
|
-
location: erb_node.loc,
|
296
|
-
)
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
def validate_no_javascript_tag(node)
|
301
|
-
erb_nodes(node).each do |erb_node, indicator_node, code_node|
|
302
|
-
indicator = indicator_node&.loc&.source
|
303
|
-
next if indicator == '#'
|
304
|
-
source = code_node.loc.source
|
305
|
-
|
306
|
-
begin
|
307
|
-
expr = RubyExpr.parse(source)
|
308
|
-
|
309
|
-
if expr.calls.size == 1 && expr.calls.first.method == :javascript_tag
|
310
|
-
add_error(
|
311
|
-
"'javascript_tag do' syntax is deprecated; use inline <script> instead",
|
312
|
-
location: erb_node.loc,
|
313
|
-
)
|
314
|
-
end
|
315
|
-
rescue RubyExpr::ParseError
|
316
|
-
end
|
317
|
-
end
|
318
|
-
end
|
65
|
+
assert_predicate errors, :empty?, messages.join
|
319
66
|
end
|
320
67
|
end
|
321
68
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module BetterHtml
|
2
2
|
module Tokenizer
|
3
3
|
class Location
|
4
|
-
attr_accessor :start, :stop
|
4
|
+
attr_accessor :document, :start, :stop
|
5
5
|
|
6
6
|
def initialize(document, start, stop)
|
7
7
|
raise ArgumentError, "start location #{start} is out of range for document of size #{document.size}" if start > document.size
|
data/lib/better_html/version.rb
CHANGED
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'better_html/test_helper/ruby_node'
|
3
|
+
|
4
|
+
module BetterHtml
|
5
|
+
module TestHelper
|
6
|
+
class RubyNodeTest < ActiveSupport::TestCase
|
7
|
+
include ::AST::Sexp
|
8
|
+
|
9
|
+
test "simple call" do
|
10
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo")
|
11
|
+
assert_equal 1, expr.return_values.count
|
12
|
+
assert_nil expr.return_values.first.receiver
|
13
|
+
assert_equal :foo, expr.return_values.first.method_name
|
14
|
+
assert_equal [], expr.return_values.first.arguments
|
15
|
+
refute_predicate expr, :static_return_value?
|
16
|
+
end
|
17
|
+
|
18
|
+
test "instance call" do
|
19
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo.bar")
|
20
|
+
assert_equal 1, expr.return_values.count
|
21
|
+
assert_equal s(:send, nil, :foo), expr.return_values.first.receiver
|
22
|
+
assert_equal :bar, expr.return_values.first.method_name
|
23
|
+
assert_equal [], expr.return_values.first.arguments
|
24
|
+
refute_predicate expr, :static_return_value?
|
25
|
+
end
|
26
|
+
|
27
|
+
test "instance call with arguments" do
|
28
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo(x).bar")
|
29
|
+
assert_equal 1, expr.return_values.count
|
30
|
+
assert_equal s(:send, nil, :foo, s(:send, nil, :x)), expr.return_values.first.receiver
|
31
|
+
assert_equal :bar, expr.return_values.first.method_name
|
32
|
+
assert_equal [], expr.return_values.first.arguments
|
33
|
+
refute_predicate expr, :static_return_value?
|
34
|
+
end
|
35
|
+
|
36
|
+
test "instance call with parenthesis" do
|
37
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("(foo).bar")
|
38
|
+
assert_equal 1, expr.return_values.count
|
39
|
+
assert_equal s(:begin, s(:send, nil, :foo)), expr.return_values.first.receiver
|
40
|
+
assert_equal :bar, expr.return_values.first.method_name
|
41
|
+
assert_equal [], expr.return_values.first.arguments
|
42
|
+
refute_predicate expr, :static_return_value?
|
43
|
+
end
|
44
|
+
|
45
|
+
test "instance call with parenthesis 2" do
|
46
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("(foo)")
|
47
|
+
assert_equal 1, expr.return_values.count
|
48
|
+
assert_nil expr.return_values.first.receiver
|
49
|
+
assert_equal :foo, expr.return_values.first.method_name
|
50
|
+
assert_equal [], expr.return_values.first.arguments
|
51
|
+
refute_predicate expr, :static_return_value?
|
52
|
+
end
|
53
|
+
|
54
|
+
test "command call" do
|
55
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo bar")
|
56
|
+
assert_equal 1, expr.return_values.count
|
57
|
+
assert_nil expr.return_values.first.receiver
|
58
|
+
assert_equal :foo, expr.return_values.first.method_name
|
59
|
+
assert_equal [s(:send, nil, :bar)], expr.return_values.first.arguments
|
60
|
+
refute_predicate expr, :static_return_value?
|
61
|
+
end
|
62
|
+
|
63
|
+
test "command call with block" do
|
64
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo bar do")
|
65
|
+
assert_equal 1, expr.return_values.count
|
66
|
+
assert_nil expr.return_values.first.receiver
|
67
|
+
assert_equal :foo, expr.return_values.first.method_name
|
68
|
+
assert_equal [s(:send, nil, :bar)], expr.return_values.first.arguments
|
69
|
+
refute_predicate expr, :static_return_value?
|
70
|
+
end
|
71
|
+
|
72
|
+
test "call with parameters" do
|
73
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo(bar)")
|
74
|
+
assert_equal 1, expr.return_values.count
|
75
|
+
assert_nil expr.return_values.first.receiver
|
76
|
+
assert_equal :foo, expr.return_values.first.method_name
|
77
|
+
assert_equal [s(:send, nil, :bar)], expr.return_values.first.arguments
|
78
|
+
refute_predicate expr, :static_return_value?
|
79
|
+
end
|
80
|
+
|
81
|
+
test "instance call with parameters" do
|
82
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo.bar(baz, x)")
|
83
|
+
assert_equal 1, expr.return_values.count
|
84
|
+
assert_equal s(:send, nil, :foo), expr.return_values.first.receiver
|
85
|
+
assert_equal :bar, expr.return_values.first.method_name
|
86
|
+
assert_equal [s(:send, nil, :baz), s(:send, nil, :x)], expr.return_values.first.arguments
|
87
|
+
refute_predicate expr, :static_return_value?
|
88
|
+
end
|
89
|
+
|
90
|
+
test "call with parameters with if conditional modifier" do
|
91
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo(bar) if something?")
|
92
|
+
assert_equal 1, expr.return_values.count
|
93
|
+
assert_nil expr.return_values.first.receiver
|
94
|
+
assert_equal :foo, expr.return_values.first.method_name
|
95
|
+
assert_equal [s(:send, nil, :bar)], expr.return_values.first.arguments
|
96
|
+
refute_predicate expr, :static_return_value?
|
97
|
+
end
|
98
|
+
|
99
|
+
test "call with parameters with unless conditional modifier" do
|
100
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("foo(bar) unless something?")
|
101
|
+
assert_equal 1, expr.return_values.count
|
102
|
+
assert_nil expr.return_values.first.receiver
|
103
|
+
assert_equal :foo, expr.return_values.first.method_name
|
104
|
+
assert_equal [s(:send, nil, :bar)], expr.return_values.first.arguments
|
105
|
+
refute_predicate expr, :static_return_value?
|
106
|
+
end
|
107
|
+
|
108
|
+
test "expression call in ternary" do
|
109
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("something? ? foo : bar")
|
110
|
+
assert_equal 2, expr.return_values.count
|
111
|
+
refute_predicate expr, :static_return_value?
|
112
|
+
|
113
|
+
assert_nil expr.return_values.to_a[0].receiver
|
114
|
+
assert_equal :foo, expr.return_values.to_a[0].method_name
|
115
|
+
assert_equal [], expr.return_values.to_a[0].arguments
|
116
|
+
|
117
|
+
assert_nil expr.return_values.to_a[1].receiver
|
118
|
+
assert_equal :bar, expr.return_values.to_a[1].method_name
|
119
|
+
assert_equal [], expr.return_values.to_a[1].arguments
|
120
|
+
end
|
121
|
+
|
122
|
+
test "expression call with args in ternary" do
|
123
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("something? ? foo(x) : bar(x)")
|
124
|
+
assert_equal 2, expr.return_values.count
|
125
|
+
|
126
|
+
assert_nil expr.return_values.to_a[0].receiver
|
127
|
+
assert_equal :foo, expr.return_values.to_a[0].method_name
|
128
|
+
assert_equal [s(:send, nil, :x)], expr.return_values.to_a[0].arguments
|
129
|
+
|
130
|
+
assert_nil expr.return_values.to_a[1].receiver
|
131
|
+
assert_equal :bar, expr.return_values.to_a[1].method_name
|
132
|
+
assert_equal [s(:send, nil, :x)], expr.return_values.to_a[1].arguments
|
133
|
+
refute_predicate expr, :static_return_value?
|
134
|
+
end
|
135
|
+
|
136
|
+
test "string without interpolation" do
|
137
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('"foo"')
|
138
|
+
assert_equal 1, expr.return_values.count
|
139
|
+
assert_equal [s(:str, "foo")], expr.return_values.to_a
|
140
|
+
assert_predicate expr, :static_return_value?
|
141
|
+
end
|
142
|
+
|
143
|
+
test "string with interpolation" do
|
144
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('"foo #{bar}"')
|
145
|
+
method_calls = expr.return_values.select(&:method_call?)
|
146
|
+
assert_equal 1, method_calls.count
|
147
|
+
assert_nil method_calls.first.receiver
|
148
|
+
assert_equal :bar, method_calls.first.method_name
|
149
|
+
assert_equal [], method_calls.first.arguments
|
150
|
+
refute_predicate expr, :static_return_value?
|
151
|
+
end
|
152
|
+
|
153
|
+
test "ternary in string with interpolation" do
|
154
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('"foo #{foo? ? bar : baz}"')
|
155
|
+
method_calls = expr.return_values.select(&:method_call?)
|
156
|
+
assert_equal 2, method_calls.count
|
157
|
+
|
158
|
+
assert_nil method_calls.first.receiver
|
159
|
+
assert_equal :bar, method_calls.first.method_name
|
160
|
+
assert_equal [], method_calls.first.arguments
|
161
|
+
|
162
|
+
assert_nil method_calls.last.receiver
|
163
|
+
assert_equal :baz, method_calls.last.method_name
|
164
|
+
assert_equal [], method_calls.first.arguments
|
165
|
+
refute_predicate expr, :static_return_value?
|
166
|
+
end
|
167
|
+
|
168
|
+
test "assignment to variable" do
|
169
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('x = foo.bar')
|
170
|
+
assert_equal 1, expr.return_values.count
|
171
|
+
assert_equal s(:send, nil, :foo), expr.return_values.first.receiver
|
172
|
+
assert_equal :bar, expr.return_values.first.method_name
|
173
|
+
assert_equal [], expr.return_values.first.arguments
|
174
|
+
refute_predicate expr, :static_return_value?
|
175
|
+
end
|
176
|
+
|
177
|
+
test "assignment to variable with command call" do
|
178
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('raw x = foo.bar')
|
179
|
+
assert_equal 1, expr.return_values.count
|
180
|
+
assert_nil expr.return_values.first.receiver
|
181
|
+
assert_equal :raw, expr.return_values.first.method_name
|
182
|
+
assert_equal [s(:lvasgn, :x, s(:send, s(:send, nil, :foo), :bar))], expr.return_values.first.arguments
|
183
|
+
refute_predicate expr, :static_return_value?
|
184
|
+
end
|
185
|
+
|
186
|
+
test "assignment with instance call" do
|
187
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('(x = foo).bar')
|
188
|
+
assert_equal 1, expr.return_values.count
|
189
|
+
assert_equal s(:begin, s(:lvasgn, :x, s(:send, nil, :foo))), expr.return_values.first.receiver
|
190
|
+
assert_equal :bar, expr.return_values.first.method_name
|
191
|
+
assert_equal [], expr.return_values.first.arguments
|
192
|
+
refute_predicate expr, :static_return_value?
|
193
|
+
end
|
194
|
+
|
195
|
+
test "assignment to multiple variables" do
|
196
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('x, y = foo.bar')
|
197
|
+
assert_equal 1, expr.return_values.count
|
198
|
+
assert_equal s(:send, nil, :foo), expr.return_values.first.receiver
|
199
|
+
assert_equal :bar, expr.return_values.first.method_name
|
200
|
+
assert_equal [], expr.return_values.first.arguments
|
201
|
+
refute_predicate expr, :static_return_value?
|
202
|
+
end
|
203
|
+
|
204
|
+
test "safe navigation operator" do
|
205
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('foo&.bar')
|
206
|
+
assert_equal 1, expr.return_values.count
|
207
|
+
assert_equal s(:send, nil, :foo), expr.return_values.to_a[0].receiver
|
208
|
+
assert_equal :bar, expr.return_values.to_a[0].method_name
|
209
|
+
assert_equal [], expr.return_values.to_a[0].arguments
|
210
|
+
refute_predicate expr, :static_return_value?
|
211
|
+
end
|
212
|
+
|
213
|
+
test "instance variable" do
|
214
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('@foo')
|
215
|
+
assert_equal 0, expr.return_values.select(&:method_call?).count
|
216
|
+
refute_predicate expr, :static_return_value?
|
217
|
+
end
|
218
|
+
|
219
|
+
test "instance method on variable" do
|
220
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('@foo.bar')
|
221
|
+
assert_equal 1, expr.return_values.count
|
222
|
+
assert_equal s(:ivar, :@foo), expr.return_values.first.receiver
|
223
|
+
assert_equal :bar, expr.return_values.first.method_name
|
224
|
+
assert_equal [], expr.return_values.first.arguments
|
225
|
+
refute_predicate expr, :static_return_value?
|
226
|
+
end
|
227
|
+
|
228
|
+
test "index into array" do
|
229
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('local_assigns[:text_class] if local_assigns[:text_class]')
|
230
|
+
assert_equal 1, expr.return_values.count
|
231
|
+
assert_equal s(:send, nil, :local_assigns), expr.return_values.first.receiver
|
232
|
+
assert_equal :[], expr.return_values.first.method_name
|
233
|
+
assert_equal [s(:sym, :text_class)], expr.return_values.first.arguments
|
234
|
+
refute_predicate expr, :static_return_value?
|
235
|
+
end
|
236
|
+
|
237
|
+
test "static_return_value? for ivar" do
|
238
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('@foo')
|
239
|
+
refute_predicate expr, :static_return_value?
|
240
|
+
end
|
241
|
+
|
242
|
+
test "static_return_value? for str" do
|
243
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("'str'")
|
244
|
+
assert_predicate expr, :static_return_value?
|
245
|
+
end
|
246
|
+
|
247
|
+
test "static_return_value? for int" do
|
248
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("1")
|
249
|
+
assert_predicate expr, :static_return_value?
|
250
|
+
end
|
251
|
+
|
252
|
+
test "static_return_value? for bool" do
|
253
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("true")
|
254
|
+
assert_predicate expr, :static_return_value?
|
255
|
+
end
|
256
|
+
|
257
|
+
test "static_return_value? for nil" do
|
258
|
+
expr = BetterHtml::TestHelper::RubyNode.parse("nil")
|
259
|
+
assert_predicate expr, :static_return_value?
|
260
|
+
end
|
261
|
+
|
262
|
+
test "static_return_value? for dstr without interpolate" do
|
263
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('"str"')
|
264
|
+
assert_predicate expr, :static_return_value?
|
265
|
+
end
|
266
|
+
|
267
|
+
test "static_return_value? for dstr with interpolate" do
|
268
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('"str #{foo}"')
|
269
|
+
refute_predicate expr, :static_return_value?
|
270
|
+
end
|
271
|
+
|
272
|
+
test "static_return_value? with safe ternary" do
|
273
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('foo ? \'a\' : \'b\'')
|
274
|
+
assert_predicate expr, :static_return_value?
|
275
|
+
end
|
276
|
+
|
277
|
+
test "static_return_value? with safe conditional" do
|
278
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('\'foo\' if bar?')
|
279
|
+
assert_predicate expr, :static_return_value?
|
280
|
+
end
|
281
|
+
|
282
|
+
test "static_return_value? with safe assignment" do
|
283
|
+
expr = BetterHtml::TestHelper::RubyNode.parse('x = \'foo\'')
|
284
|
+
assert_predicate expr, :static_return_value?
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|