herb 0.9.1-aarch64-linux-gnu → 0.9.3-aarch64-linux-gnu
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/README.md +2 -0
- data/config.yml +125 -0
- data/ext/herb/error_helpers.c +172 -2
- data/ext/herb/extconf.rb +6 -0
- data/ext/herb/extension.c +16 -2
- data/ext/herb/extension_helpers.c +6 -5
- data/ext/herb/extension_helpers.h +4 -4
- data/ext/herb/nodes.c +89 -3
- data/lib/herb/3.0/herb.so +0 -0
- data/lib/herb/3.1/herb.so +0 -0
- data/lib/herb/3.2/herb.so +0 -0
- data/lib/herb/3.3/herb.so +0 -0
- data/lib/herb/3.4/herb.so +0 -0
- data/lib/herb/4.0/herb.so +0 -0
- data/lib/herb/ast/erb_content_node.rb +32 -0
- data/lib/herb/ast/nodes.rb +244 -3
- data/lib/herb/cli.rb +12 -2
- data/lib/herb/engine/compiler.rb +136 -97
- data/lib/herb/engine/validators/security_validator.rb +40 -0
- data/lib/herb/engine.rb +21 -0
- data/lib/herb/errors.rb +268 -0
- data/lib/herb/parser_options.rb +7 -2
- data/lib/herb/version.rb +1 -1
- data/lib/herb/visitor.rb +82 -0
- data/lib/herb.rb +1 -0
- data/sig/herb/ast/erb_content_node.rbs +13 -0
- data/sig/herb/ast/nodes.rbs +98 -2
- data/sig/herb/engine/compiler.rbs +18 -3
- data/sig/herb/engine/validators/security_validator.rbs +4 -0
- data/sig/herb/engine.rbs +4 -0
- data/sig/herb/errors.rbs +122 -0
- data/sig/herb/parser_options.rbs +6 -2
- data/sig/herb/visitor.rbs +12 -0
- data/sig/serialized_ast_errors.rbs +29 -0
- data/sig/serialized_ast_nodes.rbs +19 -0
- data/src/analyze/action_view/attribute_extraction_helpers.c +425 -87
- data/src/analyze/action_view/image_tag.c +87 -0
- data/src/analyze/action_view/javascript_include_tag.c +102 -0
- data/src/analyze/action_view/javascript_tag.c +55 -0
- data/src/analyze/action_view/registry.c +10 -3
- data/src/analyze/action_view/tag.c +19 -2
- data/src/analyze/action_view/tag_helper_node_builders.c +119 -37
- data/src/analyze/action_view/tag_helpers.c +1033 -32
- data/src/analyze/analyze.c +165 -10
- data/src/analyze/{helpers.c → analyze_helpers.c} +1 -1
- data/src/analyze/analyzed_ruby.c +1 -1
- data/src/analyze/builders.c +11 -8
- data/src/analyze/conditional_elements.c +6 -7
- data/src/analyze/conditional_open_tags.c +6 -7
- data/src/analyze/control_type.c +4 -2
- data/src/analyze/invalid_structures.c +5 -5
- data/src/analyze/missing_end.c +2 -2
- data/src/analyze/parse_errors.c +5 -5
- data/src/analyze/prism_annotate.c +7 -7
- data/src/analyze/render_nodes.c +6 -26
- data/src/analyze/strict_locals.c +637 -0
- data/src/analyze/transform.c +7 -0
- data/src/{ast_node.c → ast/ast_node.c} +8 -8
- data/src/{ast_nodes.c → ast/ast_nodes.c} +82 -11
- data/src/{ast_pretty_print.c → ast/ast_pretty_print.c} +113 -9
- data/src/{pretty_print.c → ast/pretty_print.c} +9 -9
- data/src/errors.c +398 -8
- data/src/extract.c +5 -5
- data/src/herb.c +15 -5
- data/src/include/analyze/action_view/attribute_extraction_helpers.h +3 -1
- data/src/include/analyze/action_view/tag_helper_handler.h +3 -3
- data/src/include/analyze/action_view/tag_helper_node_builders.h +34 -5
- data/src/include/analyze/action_view/tag_helpers.h +4 -3
- data/src/include/analyze/analyze.h +6 -4
- data/src/include/analyze/analyzed_ruby.h +2 -2
- data/src/include/analyze/builders.h +4 -4
- data/src/include/analyze/conditional_elements.h +2 -2
- data/src/include/analyze/conditional_open_tags.h +2 -2
- data/src/include/analyze/control_type.h +1 -1
- data/src/include/analyze/helpers.h +2 -2
- data/src/include/analyze/invalid_structures.h +1 -1
- data/src/include/analyze/prism_annotate.h +2 -2
- data/src/include/analyze/render_nodes.h +1 -1
- data/src/include/analyze/strict_locals.h +11 -0
- data/src/include/{ast_node.h → ast/ast_node.h} +4 -4
- data/src/include/{ast_nodes.h → ast/ast_nodes.h} +38 -14
- data/src/include/{ast_pretty_print.h → ast/ast_pretty_print.h} +3 -3
- data/src/include/{pretty_print.h → ast/pretty_print.h} +4 -4
- data/src/include/errors.h +65 -7
- data/src/include/extract.h +2 -2
- data/src/include/herb.h +5 -5
- data/src/include/{lex_helpers.h → lexer/lex_helpers.h} +5 -5
- data/src/include/{lexer.h → lexer/lexer.h} +1 -1
- data/src/include/{lexer_peek_helpers.h → lexer/lexer_peek_helpers.h} +2 -2
- data/src/include/{lexer_struct.h → lexer/lexer_struct.h} +2 -2
- data/src/include/{token.h → lexer/token.h} +3 -3
- data/src/include/{token_matchers.h → lexer/token_matchers.h} +1 -1
- data/src/include/{token_struct.h → lexer/token_struct.h} +3 -3
- data/src/include/{util → lib}/hb_foreach.h +1 -1
- data/src/include/{util → lib}/hb_string.h +5 -1
- data/src/include/{location.h → location/location.h} +1 -1
- data/src/include/parser/dot_notation.h +12 -0
- data/src/include/{parser.h → parser/parser.h} +11 -4
- data/src/include/{parser_helpers.h → parser/parser_helpers.h} +6 -6
- data/src/include/{prism_context.h → prism/prism_context.h} +2 -2
- data/src/include/{prism_helpers.h → prism/prism_helpers.h} +6 -6
- data/src/include/{html_util.h → util/html_util.h} +2 -1
- data/src/include/util/ruby_util.h +9 -0
- data/src/include/{utf8.h → util/utf8.h} +1 -1
- data/src/include/{util.h → util/util.h} +1 -1
- data/src/include/version.h +1 -1
- data/src/include/visitor.h +3 -3
- data/src/{lexer_peek_helpers.c → lexer/lexer_peek_helpers.c} +3 -3
- data/src/{token.c → lexer/token.c} +8 -8
- data/src/{token_matchers.c → lexer/token_matchers.c} +2 -2
- data/src/lexer.c +6 -6
- data/src/{util → lib}/hb_allocator.c +2 -2
- data/src/{util → lib}/hb_arena.c +4 -8
- data/src/{util → lib}/hb_arena_debug.c +2 -2
- data/src/{util → lib}/hb_array.c +2 -2
- data/src/{util → lib}/hb_buffer.c +2 -2
- data/src/{util → lib}/hb_narray.c +1 -1
- data/src/{util → lib}/hb_string.c +2 -2
- data/src/{location.c → location/location.c} +2 -2
- data/src/{position.c → location/position.c} +2 -2
- data/src/{range.c → location/range.c} +1 -1
- data/src/main.c +11 -11
- data/src/parser/dot_notation.c +100 -0
- data/src/{parser_match_tags.c → parser/match_tags.c} +34 -5
- data/src/{parser_helpers.c → parser/parser_helpers.c} +10 -10
- data/src/parser.c +68 -32
- data/src/{prism_helpers.c → prism/prism_helpers.c} +7 -7
- data/src/{ruby_parser.c → prism/ruby_parser.c} +1 -1
- data/src/{html_util.c → util/html_util.c} +54 -4
- data/src/{io.c → util/io.c} +3 -3
- data/src/util/ruby_util.c +42 -0
- data/src/{utf8.c → util/utf8.c} +2 -2
- data/src/{util.c → util/util.c} +4 -4
- data/src/visitor.c +35 -3
- data/templates/ext/herb/error_helpers.c.erb +2 -2
- data/templates/ext/herb/nodes.c.erb +1 -1
- data/templates/java/error_helpers.c.erb +1 -1
- data/templates/java/error_helpers.h.erb +2 -2
- data/templates/java/nodes.c.erb +4 -4
- data/templates/java/nodes.h.erb +1 -1
- data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +4 -4
- data/templates/javascript/packages/node/extension/nodes.cpp.erb +4 -4
- data/templates/lib/herb/visitor.rb.erb +14 -0
- data/templates/src/analyze/missing_end.c.erb +2 -2
- data/templates/src/{ast_nodes.c.erb → ast/ast_nodes.c.erb} +9 -9
- data/templates/src/{ast_pretty_print.c.erb → ast/ast_pretty_print.c.erb} +8 -8
- data/templates/src/errors.c.erb +8 -8
- data/templates/src/include/{ast_nodes.h.erb → ast/ast_nodes.h.erb} +11 -12
- data/templates/src/include/{ast_pretty_print.h.erb → ast/ast_pretty_print.h.erb} +2 -2
- data/templates/src/include/errors.h.erb +7 -7
- data/templates/src/{parser_match_tags.c.erb → parser/match_tags.c.erb} +4 -4
- data/templates/src/visitor.c.erb +3 -3
- data/templates/wasm/error_helpers.cpp.erb +4 -4
- data/templates/wasm/nodes.cpp.erb +5 -5
- metadata +78 -68
- data/src/include/element_source.h +0 -10
- /data/src/include/{util → lib}/hb_allocator.h +0 -0
- /data/src/include/{util → lib}/hb_arena.h +0 -0
- /data/src/include/{util → lib}/hb_arena_debug.h +0 -0
- /data/src/include/{util → lib}/hb_array.h +0 -0
- /data/src/include/{util → lib}/hb_buffer.h +0 -0
- /data/src/include/{util → lib}/hb_narray.h +0 -0
- /data/src/include/{util → lib}/string.h +0 -0
- /data/src/include/{position.h → location/position.h} +0 -0
- /data/src/include/{range.h → location/range.h} +0 -0
- /data/src/include/{herb_prism_node.h → prism/herb_prism_node.h} +0 -0
- /data/src/include/{prism_serialized.h → prism/prism_serialized.h} +0 -0
- /data/src/include/{ruby_parser.h → prism/ruby_parser.h} +0 -0
- /data/src/include/{io.h → util/io.h} +0 -0
- /data/templates/src/include/{util → lib}/hb_foreach.h.erb +0 -0
data/lib/herb/engine/compiler.rb
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Herb
|
|
4
4
|
class Engine
|
|
5
5
|
class Compiler < ::Herb::Visitor
|
|
6
|
+
EXPRESSION_TOKEN_TYPES = [:expr, :expr_escaped, :expr_block, :expr_block_escaped].freeze
|
|
7
|
+
|
|
6
8
|
attr_reader :tokens
|
|
7
9
|
|
|
8
10
|
def initialize(engine, options = {})
|
|
@@ -10,13 +12,13 @@ module Herb
|
|
|
10
12
|
|
|
11
13
|
@engine = engine
|
|
12
14
|
@escape = options.fetch(:escape) { options.fetch(:escape_html, false) }
|
|
13
|
-
@attrfunc = options.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
|
|
14
|
-
@jsfunc = options.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
|
|
15
|
-
@cssfunc = options.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
|
|
16
15
|
@tokens = [] #: Array[untyped]
|
|
17
16
|
@element_stack = [] #: Array[String]
|
|
18
17
|
@context_stack = [:html_content]
|
|
19
18
|
@trim_next_whitespace = false
|
|
19
|
+
@last_trim_consumed_newline = false
|
|
20
|
+
@pending_leading_whitespace = nil
|
|
21
|
+
@pending_leading_whitespace_insert_index = 0
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def generate_output
|
|
@@ -28,26 +30,16 @@ module Herb
|
|
|
28
30
|
@engine.send(:add_text, value)
|
|
29
31
|
when :code
|
|
30
32
|
@engine.send(:add_code, value)
|
|
31
|
-
when :expr
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@engine.send(:add_expression, indicator, value)
|
|
37
|
-
end
|
|
38
|
-
when :expr_escaped
|
|
39
|
-
if [:attribute_value, :script_content, :style_content].include?(context)
|
|
40
|
-
add_context_aware_expression(value, context)
|
|
33
|
+
when :expr, :expr_escaped
|
|
34
|
+
indicator = indicator_for(type)
|
|
35
|
+
|
|
36
|
+
if context_aware_context?(context)
|
|
37
|
+
@engine.send(:add_context_aware_expression, indicator, value, context)
|
|
41
38
|
else
|
|
42
|
-
indicator = @escape ? "=" : "=="
|
|
43
39
|
@engine.send(:add_expression, indicator, value)
|
|
44
40
|
end
|
|
45
|
-
when :expr_block
|
|
46
|
-
|
|
47
|
-
@engine.send(:add_expression_block, indicator, value)
|
|
48
|
-
when :expr_block_escaped
|
|
49
|
-
indicator = @escape ? "=" : "=="
|
|
50
|
-
@engine.send(:add_expression_block, indicator, value)
|
|
41
|
+
when :expr_block, :expr_block_escaped
|
|
42
|
+
@engine.send(:add_expression_block, indicator_for(type), value)
|
|
51
43
|
when :expr_block_end
|
|
52
44
|
@engine.send(:add_expression_block_end, value, escaped: escaped)
|
|
53
45
|
end
|
|
@@ -59,43 +51,19 @@ module Herb
|
|
|
59
51
|
end
|
|
60
52
|
|
|
61
53
|
def visit_html_element_node(node)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if tag_name == "script"
|
|
67
|
-
push_context(:script_content)
|
|
68
|
-
elsif tag_name == "style"
|
|
69
|
-
push_context(:style_content)
|
|
54
|
+
with_element_context(node) do
|
|
55
|
+
visit(node.open_tag)
|
|
56
|
+
visit_all(node.body)
|
|
57
|
+
visit(node.close_tag)
|
|
70
58
|
end
|
|
71
|
-
|
|
72
|
-
visit(node.open_tag)
|
|
73
|
-
visit_all(node.body)
|
|
74
|
-
visit(node.close_tag)
|
|
75
|
-
|
|
76
|
-
pop_context if ["script", "style"].include?(tag_name)
|
|
77
|
-
|
|
78
|
-
@element_stack.pop if tag_name
|
|
79
59
|
end
|
|
80
60
|
|
|
81
61
|
def visit_html_conditional_element_node(node)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if tag_name == "script"
|
|
87
|
-
push_context(:script_content)
|
|
88
|
-
elsif tag_name == "style"
|
|
89
|
-
push_context(:style_content)
|
|
62
|
+
with_element_context(node) do
|
|
63
|
+
visit(node.open_conditional)
|
|
64
|
+
visit_all(node.body)
|
|
65
|
+
visit(node.close_conditional)
|
|
90
66
|
end
|
|
91
|
-
|
|
92
|
-
visit(node.open_conditional)
|
|
93
|
-
visit_all(node.body)
|
|
94
|
-
visit(node.close_conditional)
|
|
95
|
-
|
|
96
|
-
pop_context if ["script", "style"].include?(tag_name)
|
|
97
|
-
|
|
98
|
-
@element_stack.pop if tag_name
|
|
99
67
|
end
|
|
100
68
|
|
|
101
69
|
def visit_html_open_tag_node(node)
|
|
@@ -283,12 +251,16 @@ module Herb
|
|
|
283
251
|
else
|
|
284
252
|
[:expr_block, code, current_context]
|
|
285
253
|
end
|
|
254
|
+
@last_trim_consumed_newline = false
|
|
286
255
|
|
|
287
256
|
visit_all(node.body)
|
|
288
257
|
visit_erb_block_end_node(node.end_node, escaped: should_escape)
|
|
289
258
|
else
|
|
290
259
|
visit_erb_control_node(node) do
|
|
291
260
|
visit_all(node.body)
|
|
261
|
+
visit(node.rescue_clause)
|
|
262
|
+
visit(node.else_clause)
|
|
263
|
+
visit(node.ensure_clause)
|
|
292
264
|
visit(node.end_node)
|
|
293
265
|
end
|
|
294
266
|
end
|
|
@@ -302,10 +274,10 @@ module Herb
|
|
|
302
274
|
code = node.content.value.strip
|
|
303
275
|
|
|
304
276
|
if at_line_start?
|
|
305
|
-
|
|
306
|
-
|
|
277
|
+
leading_space = extract_and_remove_leading_space!
|
|
278
|
+
right_space = " \n"
|
|
307
279
|
|
|
308
|
-
@tokens << [:expr_block_end, "#{
|
|
280
|
+
@tokens << [:expr_block_end, "#{leading_space}#{code}#{right_space}", current_context, escaped]
|
|
309
281
|
@trim_next_whitespace = true
|
|
310
282
|
else
|
|
311
283
|
@tokens << [:expr_block_end, code, current_context, escaped]
|
|
@@ -342,25 +314,23 @@ module Herb
|
|
|
342
314
|
@context_stack.pop
|
|
343
315
|
end
|
|
344
316
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
@engine.src << " << #{@jsfunc}((" << code << closing
|
|
356
|
-
}
|
|
357
|
-
when :style_content
|
|
358
|
-
@engine.send(:with_buffer) {
|
|
359
|
-
@engine.src << " << #{@cssfunc}((" << code << closing
|
|
360
|
-
}
|
|
361
|
-
else
|
|
362
|
-
@engine.send(:add_expression_result_escaped, code)
|
|
317
|
+
#: (untyped node) { () -> untyped } -> untyped
|
|
318
|
+
def with_element_context(node)
|
|
319
|
+
tag_name = node.tag_name&.value&.downcase
|
|
320
|
+
|
|
321
|
+
@element_stack.push(tag_name) if tag_name
|
|
322
|
+
|
|
323
|
+
if tag_name == "script"
|
|
324
|
+
push_context(:script_content)
|
|
325
|
+
elsif tag_name == "style"
|
|
326
|
+
push_context(:style_content)
|
|
363
327
|
end
|
|
328
|
+
|
|
329
|
+
yield
|
|
330
|
+
|
|
331
|
+
pop_context if ["script", "style"].include?(tag_name)
|
|
332
|
+
|
|
333
|
+
@element_stack.pop if tag_name
|
|
364
334
|
end
|
|
365
335
|
|
|
366
336
|
def process_erb_tag(node, skip_comment_check: false)
|
|
@@ -368,11 +338,13 @@ module Herb
|
|
|
368
338
|
|
|
369
339
|
if !skip_comment_check && erb_comment?(opening)
|
|
370
340
|
has_left_trim = opening.start_with?("<%-")
|
|
341
|
+
follows_newline = leading_space_follows_newline?
|
|
371
342
|
remove_trailing_whitespace_from_last_token! if has_left_trim
|
|
372
343
|
|
|
373
344
|
if at_line_start?
|
|
374
|
-
|
|
345
|
+
leading_space = extract_and_remove_leading_space!
|
|
375
346
|
@trim_next_whitespace = true
|
|
347
|
+
save_pending_leading_whitespace!(leading_space) if !leading_space.empty? && follows_newline
|
|
376
348
|
end
|
|
377
349
|
return
|
|
378
350
|
end
|
|
@@ -391,10 +363,17 @@ module Herb
|
|
|
391
363
|
return if text.empty?
|
|
392
364
|
|
|
393
365
|
if @trim_next_whitespace
|
|
366
|
+
@last_trim_consumed_newline = text.match?(/\A[ \t]*\r?\n/)
|
|
394
367
|
text = text.sub(/\A[ \t]*\r?\n/, "")
|
|
395
368
|
@trim_next_whitespace = false
|
|
369
|
+
|
|
370
|
+
restore_pending_leading_whitespace! unless @last_trim_consumed_newline
|
|
371
|
+
else
|
|
372
|
+
@last_trim_consumed_newline = false
|
|
396
373
|
end
|
|
397
374
|
|
|
375
|
+
@pending_leading_whitespace = nil
|
|
376
|
+
|
|
398
377
|
return if text.empty?
|
|
399
378
|
|
|
400
379
|
@tokens << [:text, text, current_context]
|
|
@@ -410,10 +389,12 @@ module Herb
|
|
|
410
389
|
|
|
411
390
|
def add_expression(code)
|
|
412
391
|
@tokens << [:expr, code, current_context]
|
|
392
|
+
@last_trim_consumed_newline = false
|
|
413
393
|
end
|
|
414
394
|
|
|
415
395
|
def add_expression_escaped(code)
|
|
416
396
|
@tokens << [:expr_escaped, code, current_context]
|
|
397
|
+
@last_trim_consumed_newline = false
|
|
417
398
|
end
|
|
418
399
|
|
|
419
400
|
def optimize_tokens(tokens)
|
|
@@ -497,12 +478,29 @@ module Herb
|
|
|
497
478
|
end
|
|
498
479
|
|
|
499
480
|
def process_erb_output(node, opening, code)
|
|
481
|
+
if @trim_next_whitespace && @pending_leading_whitespace
|
|
482
|
+
restore_pending_leading_whitespace!
|
|
483
|
+
@pending_leading_whitespace = nil
|
|
484
|
+
@trim_next_whitespace = false
|
|
485
|
+
@last_trim_consumed_newline = false
|
|
486
|
+
end
|
|
487
|
+
|
|
500
488
|
has_right_trim = node.tag_closing&.value == "-%>"
|
|
501
489
|
should_escape = should_escape_output?(opening)
|
|
502
490
|
add_expression_with_escaping(code, should_escape)
|
|
503
491
|
@trim_next_whitespace = true if has_right_trim
|
|
504
492
|
end
|
|
505
493
|
|
|
494
|
+
def indicator_for(type)
|
|
495
|
+
escaped = [:expr_escaped, :expr_block_escaped].include?(type)
|
|
496
|
+
|
|
497
|
+
escaped ^ @escape ? "==" : "="
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def context_aware_context?(context)
|
|
501
|
+
[:attribute_value, :script_content, :style_content].include?(context)
|
|
502
|
+
end
|
|
503
|
+
|
|
506
504
|
def should_escape_output?(opening)
|
|
507
505
|
is_double_equals = opening == "<%=="
|
|
508
506
|
is_double_equals ? !@escape : @escape
|
|
@@ -517,78 +515,119 @@ module Herb
|
|
|
517
515
|
end
|
|
518
516
|
|
|
519
517
|
def at_line_start?
|
|
520
|
-
@tokens.empty?
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
518
|
+
return true if @tokens.empty?
|
|
519
|
+
|
|
520
|
+
last_type = @tokens.last[0]
|
|
521
|
+
last_value = @tokens.last[1]
|
|
522
|
+
|
|
523
|
+
if last_type == :text
|
|
524
|
+
last_value.empty? || last_value.end_with?("\n") || (last_value =~ /\A[ \t]+\z/ && preceding_token_ends_with_newline?) || last_value =~ /\n[ \t]+\z/
|
|
525
|
+
elsif EXPRESSION_TOKEN_TYPES.include?(last_type)
|
|
526
|
+
@last_trim_consumed_newline
|
|
527
|
+
else
|
|
528
|
+
last_value.end_with?("\n")
|
|
529
|
+
end
|
|
526
530
|
end
|
|
527
531
|
|
|
528
532
|
def preceding_token_ends_with_newline?
|
|
529
533
|
return true unless @tokens.length >= 2
|
|
530
534
|
|
|
531
535
|
preceding = @tokens[-2]
|
|
532
|
-
return
|
|
536
|
+
return @last_trim_consumed_newline if EXPRESSION_TOKEN_TYPES.include?(preceding[0])
|
|
537
|
+
return preceding[1].end_with?("\n") if preceding[0] == :expr_block_end
|
|
533
538
|
return true unless preceding[0] == :text
|
|
534
539
|
|
|
535
540
|
preceding[1].end_with?("\n")
|
|
536
541
|
end
|
|
537
542
|
|
|
538
|
-
def
|
|
539
|
-
return
|
|
543
|
+
def last_text_token
|
|
544
|
+
return unless @tokens.last && @tokens.last[0] == :text
|
|
540
545
|
|
|
541
|
-
|
|
546
|
+
@tokens.last
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def extract_leading_space
|
|
550
|
+
token = last_text_token
|
|
551
|
+
return "" unless token
|
|
552
|
+
|
|
553
|
+
text = token[1]
|
|
542
554
|
|
|
543
555
|
return Regexp.last_match(1) if text =~ /\n([ \t]+)\z/ || text =~ /\A([ \t]+)\z/
|
|
544
556
|
|
|
545
557
|
""
|
|
546
558
|
end
|
|
547
559
|
|
|
548
|
-
def
|
|
549
|
-
|
|
550
|
-
return
|
|
560
|
+
def leading_space_follows_newline?
|
|
561
|
+
token = last_text_token
|
|
562
|
+
return false unless token
|
|
563
|
+
|
|
564
|
+
token[1].match?(/\n[ \t]+\z/)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def extract_and_remove_leading_space!
|
|
568
|
+
leading_space = extract_leading_space
|
|
569
|
+
return leading_space if leading_space.empty?
|
|
551
570
|
|
|
552
571
|
text = @tokens.last[1]
|
|
572
|
+
|
|
553
573
|
if text =~ /\n[ \t]+\z/
|
|
554
574
|
text.sub!(/[ \t]+\z/, "")
|
|
555
575
|
elsif text =~ /\A[ \t]+\z/
|
|
556
576
|
text.replace("")
|
|
557
577
|
end
|
|
578
|
+
|
|
558
579
|
@tokens.last[1] = text
|
|
559
580
|
|
|
560
|
-
|
|
581
|
+
leading_space
|
|
561
582
|
end
|
|
562
583
|
|
|
563
584
|
def apply_trim(node, code)
|
|
564
585
|
has_left_trim = node.tag_opening.value.start_with?("<%-")
|
|
565
|
-
node.tag_closing&.value
|
|
566
586
|
|
|
567
|
-
|
|
587
|
+
follows_newline = leading_space_follows_newline?
|
|
588
|
+
removed_whitespace = has_left_trim ? remove_trailing_whitespace_from_last_token! : ""
|
|
568
589
|
|
|
569
590
|
if at_line_start?
|
|
570
|
-
|
|
571
|
-
|
|
591
|
+
leading_space = extract_and_remove_leading_space!
|
|
592
|
+
effective_leading_space = leading_space.empty? ? removed_whitespace : leading_space
|
|
593
|
+
right_space = Herb::Engine.heredoc?(code) ? "\n" : " \n"
|
|
572
594
|
|
|
573
|
-
@
|
|
595
|
+
@pending_leading_whitespace_insert_index = @tokens.length
|
|
596
|
+
@pending_leading_whitespace = effective_leading_space if !effective_leading_space.empty? && follows_newline
|
|
597
|
+
@tokens << [:code, "#{effective_leading_space}#{code}#{right_space}", current_context]
|
|
574
598
|
@trim_next_whitespace = true
|
|
575
599
|
else
|
|
576
600
|
@tokens << [:code, code, current_context]
|
|
577
601
|
end
|
|
578
602
|
end
|
|
579
603
|
|
|
604
|
+
def save_pending_leading_whitespace!(whitespace)
|
|
605
|
+
@pending_leading_whitespace = whitespace
|
|
606
|
+
@pending_leading_whitespace_insert_index = @tokens.length
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def restore_pending_leading_whitespace!
|
|
610
|
+
return unless @pending_leading_whitespace
|
|
611
|
+
|
|
612
|
+
@tokens.insert(@pending_leading_whitespace_insert_index, [:text, @pending_leading_whitespace, current_context])
|
|
613
|
+
end
|
|
614
|
+
|
|
580
615
|
def remove_trailing_whitespace_from_last_token!
|
|
581
|
-
|
|
616
|
+
token = last_text_token
|
|
617
|
+
return "" unless token
|
|
582
618
|
|
|
583
|
-
text =
|
|
619
|
+
text = token[1]
|
|
620
|
+
removed = text[/[ \t]+\z/] || ""
|
|
584
621
|
|
|
585
622
|
if text =~ /\n[ \t]+\z/
|
|
586
623
|
text.sub!(/[ \t]+\z/, "")
|
|
587
|
-
|
|
624
|
+
token[1] = text
|
|
588
625
|
elsif text =~ /\A[ \t]+\z/
|
|
589
626
|
text.replace("")
|
|
590
|
-
|
|
627
|
+
token[1] = text
|
|
591
628
|
end
|
|
629
|
+
|
|
630
|
+
removed
|
|
592
631
|
end
|
|
593
632
|
end
|
|
594
633
|
end
|
|
@@ -28,6 +28,20 @@ module Herb
|
|
|
28
28
|
|
|
29
29
|
next unless child.is_a?(Herb::AST::ERBContentNode) && erb_outputs?(child)
|
|
30
30
|
|
|
31
|
+
prism_node = child.prism
|
|
32
|
+
|
|
33
|
+
next if prism_node && tag_attributes_prism_node?(prism_node)
|
|
34
|
+
|
|
35
|
+
if prism_node && conditional_tag_attributes_prism_node?(prism_node)
|
|
36
|
+
add_security_error(
|
|
37
|
+
child.location,
|
|
38
|
+
"Avoid using conditional `tag.attributes` in attribute position.",
|
|
39
|
+
"Use `<% if ... %><%= tag.attributes(...) %><% end %>` instead."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
|
|
31
45
|
add_security_error(
|
|
32
46
|
child.location,
|
|
33
47
|
"ERB output tags (<%= %>) are not allowed in attribute position.",
|
|
@@ -48,6 +62,32 @@ module Herb
|
|
|
48
62
|
end
|
|
49
63
|
end
|
|
50
64
|
|
|
65
|
+
def tag_attributes_prism_node?(prism_node)
|
|
66
|
+
return false unless prism_node.is_a?(Prism::CallNode)
|
|
67
|
+
return false unless prism_node.name == :attributes
|
|
68
|
+
|
|
69
|
+
receiver = prism_node.receiver
|
|
70
|
+
return false unless receiver.is_a?(Prism::CallNode)
|
|
71
|
+
|
|
72
|
+
receiver.name == :tag
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def conditional_tag_attributes_prism_node?(prism_node)
|
|
76
|
+
if prism_node.is_a?(Prism::IfNode) || prism_node.is_a?(Prism::UnlessNode)
|
|
77
|
+
body = prism_node.statements&.body
|
|
78
|
+
return false unless body
|
|
79
|
+
return false unless body.length == 1
|
|
80
|
+
|
|
81
|
+
return tag_attributes_prism_node?(body.first)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if prism_node.is_a?(Prism::AndNode) || prism_node.is_a?(Prism::OrNode)
|
|
85
|
+
return tag_attributes_prism_node?(prism_node.right)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
|
|
51
91
|
def add_security_error(location, message, suggestion)
|
|
52
92
|
add_diagnostic(message, location, :error, code: "SecurityViolation", source: "SecurityValidator",
|
|
53
93
|
suggestion: suggestion)
|
data/lib/herb/engine.rb
CHANGED
|
@@ -55,6 +55,9 @@ module Herb
|
|
|
55
55
|
@bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
|
|
56
56
|
@escape = properties.fetch(:escape) { properties.fetch(:escape_html, false) }
|
|
57
57
|
@escapefunc = properties.fetch(:escapefunc, @escape ? "__herb.h" : "::Herb::Engine.h")
|
|
58
|
+
@attrfunc = properties.fetch(:attrfunc, @escape ? "__herb.attr" : "::Herb::Engine.attr")
|
|
59
|
+
@jsfunc = properties.fetch(:jsfunc, @escape ? "__herb.js" : "::Herb::Engine.js")
|
|
60
|
+
@cssfunc = properties.fetch(:cssfunc, @escape ? "__herb.css" : "::Herb::Engine.css")
|
|
58
61
|
@src = properties[:src] || String.new
|
|
59
62
|
@chain_appends = properties[:chain_appends]
|
|
60
63
|
@buffer_on_stack = false
|
|
@@ -244,6 +247,24 @@ module Herb
|
|
|
244
247
|
end
|
|
245
248
|
end
|
|
246
249
|
|
|
250
|
+
def add_context_aware_expression(indicator, code, context)
|
|
251
|
+
escapefunc = context_escape_function(context)
|
|
252
|
+
|
|
253
|
+
if escapefunc.nil?
|
|
254
|
+
add_expression(indicator, code)
|
|
255
|
+
else
|
|
256
|
+
with_buffer { @src << " << #{escapefunc}((" << code << trailing_newline(code) << "))" }
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def context_escape_function(context)
|
|
261
|
+
case context
|
|
262
|
+
when :attribute_value then @attrfunc
|
|
263
|
+
when :script_content then @jsfunc
|
|
264
|
+
when :style_content then @cssfunc
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
247
268
|
def add_expression_result(code)
|
|
248
269
|
with_buffer {
|
|
249
270
|
@src << " << (" << code << trailing_newline(code) << ").to_s"
|