herb 0.9.2-arm-linux-gnu → 0.9.4-arm-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 +166 -75
- data/lib/herb/engine/validators/security_validator.rb +40 -0
- data/lib/herb/engine.rb +3 -0
- data/lib/herb/errors.rb +268 -0
- data/lib/herb/parser_options.rb +7 -2
- data/lib/herb/project.rb +58 -17
- 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 +31 -2
- data/sig/herb/engine/validators/security_validator.rbs +4 -0
- data/sig/herb/engine.rbs +3 -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 +420 -91
- data/src/analyze/action_view/image_tag.c +87 -0
- data/src/analyze/action_view/javascript_include_tag.c +22 -12
- data/src/analyze/action_view/registry.c +6 -3
- data/src/analyze/action_view/tag.c +19 -2
- data/src/analyze/action_view/tag_helper_node_builders.c +105 -36
- data/src/analyze/action_view/tag_helpers.c +792 -44
- data/src/analyze/analyze.c +167 -13
- 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 +47 -6
- data/src/analyze/prism_annotate.c +7 -7
- data/src/analyze/render_nodes.c +6 -26
- data/src/analyze/strict_locals.c +651 -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 +12 -5
- 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} +1 -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 +1 -1
- 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} +4 -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 +76 -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,14 @@
|
|
|
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
|
+
|
|
8
|
+
TRAILING_WHITESPACE = /[ \t]+\z/
|
|
9
|
+
TRAILING_INDENTATION = /\n[ \t]+\z/
|
|
10
|
+
TRAILING_INDENTATION_CAPTURE = /\n([ \t]+)\z/
|
|
11
|
+
WHITESPACE_ONLY = /\A[ \t]+\z/
|
|
12
|
+
WHITESPACE_ONLY_CAPTURE = /\A([ \t]+)\z/
|
|
13
|
+
|
|
6
14
|
attr_reader :tokens
|
|
7
15
|
|
|
8
16
|
def initialize(engine, options = {})
|
|
@@ -14,6 +22,9 @@ module Herb
|
|
|
14
22
|
@element_stack = [] #: Array[String]
|
|
15
23
|
@context_stack = [:html_content]
|
|
16
24
|
@trim_next_whitespace = false
|
|
25
|
+
@last_trim_consumed_newline = false
|
|
26
|
+
@pending_leading_whitespace = nil
|
|
27
|
+
@pending_leading_whitespace_insert_index = 0
|
|
17
28
|
end
|
|
18
29
|
|
|
19
30
|
def generate_output
|
|
@@ -46,43 +57,19 @@ module Herb
|
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
def visit_html_element_node(node)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if tag_name == "script"
|
|
54
|
-
push_context(:script_content)
|
|
55
|
-
elsif tag_name == "style"
|
|
56
|
-
push_context(:style_content)
|
|
60
|
+
with_element_context(node) do
|
|
61
|
+
visit(node.open_tag)
|
|
62
|
+
visit_all(node.body)
|
|
63
|
+
visit(node.close_tag)
|
|
57
64
|
end
|
|
58
|
-
|
|
59
|
-
visit(node.open_tag)
|
|
60
|
-
visit_all(node.body)
|
|
61
|
-
visit(node.close_tag)
|
|
62
|
-
|
|
63
|
-
pop_context if ["script", "style"].include?(tag_name)
|
|
64
|
-
|
|
65
|
-
@element_stack.pop if tag_name
|
|
66
65
|
end
|
|
67
66
|
|
|
68
67
|
def visit_html_conditional_element_node(node)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if tag_name == "script"
|
|
74
|
-
push_context(:script_content)
|
|
75
|
-
elsif tag_name == "style"
|
|
76
|
-
push_context(:style_content)
|
|
68
|
+
with_element_context(node) do
|
|
69
|
+
visit(node.open_conditional)
|
|
70
|
+
visit_all(node.body)
|
|
71
|
+
visit(node.close_conditional)
|
|
77
72
|
end
|
|
78
|
-
|
|
79
|
-
visit(node.open_conditional)
|
|
80
|
-
visit_all(node.body)
|
|
81
|
-
visit(node.close_conditional)
|
|
82
|
-
|
|
83
|
-
pop_context if ["script", "style"].include?(tag_name)
|
|
84
|
-
|
|
85
|
-
@element_stack.pop if tag_name
|
|
86
73
|
end
|
|
87
74
|
|
|
88
75
|
def visit_html_open_tag_node(node)
|
|
@@ -261,6 +248,8 @@ module Herb
|
|
|
261
248
|
def visit_erb_block_node(node)
|
|
262
249
|
opening = node.tag_opening.value
|
|
263
250
|
|
|
251
|
+
check_for_escaped_erb_tag!(opening)
|
|
252
|
+
|
|
264
253
|
if opening.include?("=")
|
|
265
254
|
should_escape = should_escape_output?(opening)
|
|
266
255
|
code = node.content.value.strip
|
|
@@ -271,28 +260,32 @@ module Herb
|
|
|
271
260
|
[:expr_block, code, current_context]
|
|
272
261
|
end
|
|
273
262
|
|
|
263
|
+
@last_trim_consumed_newline = false
|
|
264
|
+
@trim_next_whitespace = true if right_trim?(node)
|
|
265
|
+
|
|
274
266
|
visit_all(node.body)
|
|
275
267
|
visit_erb_block_end_node(node.end_node, escaped: should_escape)
|
|
276
268
|
else
|
|
277
269
|
visit_erb_control_node(node) do
|
|
278
270
|
visit_all(node.body)
|
|
271
|
+
visit(node.rescue_clause)
|
|
272
|
+
visit(node.else_clause)
|
|
273
|
+
visit(node.ensure_clause)
|
|
279
274
|
visit(node.end_node)
|
|
280
275
|
end
|
|
281
276
|
end
|
|
282
277
|
end
|
|
283
278
|
|
|
284
279
|
def visit_erb_block_end_node(node, escaped: false)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
remove_trailing_whitespace_from_last_token! if has_left_trim
|
|
280
|
+
remove_trailing_whitespace_from_last_token! if left_trim?(node)
|
|
288
281
|
|
|
289
282
|
code = node.content.value.strip
|
|
290
283
|
|
|
291
284
|
if at_line_start?
|
|
292
|
-
|
|
293
|
-
|
|
285
|
+
leading_space = extract_and_remove_leading_space!
|
|
286
|
+
right_space = " \n"
|
|
294
287
|
|
|
295
|
-
@tokens << [:expr_block_end, "#{
|
|
288
|
+
@tokens << [:expr_block_end, "#{leading_space}#{code}#{right_space}", current_context, escaped]
|
|
296
289
|
@trim_next_whitespace = true
|
|
297
290
|
else
|
|
298
291
|
@tokens << [:expr_block_end, code, current_context, escaped]
|
|
@@ -317,6 +310,15 @@ module Herb
|
|
|
317
310
|
|
|
318
311
|
private
|
|
319
312
|
|
|
313
|
+
def check_for_escaped_erb_tag!(opening)
|
|
314
|
+
return unless opening.start_with?("<%%")
|
|
315
|
+
|
|
316
|
+
raise Herb::Engine::GeneratorTemplateError,
|
|
317
|
+
"This file appears to be a generator template (a template used to generate ERB files) " \
|
|
318
|
+
"rather than a standard ERB template. It contains escaped ERB tags like <%%= %> which " \
|
|
319
|
+
"produce literal ERB output in the generated file."
|
|
320
|
+
end
|
|
321
|
+
|
|
320
322
|
def current_context
|
|
321
323
|
@context_stack.last
|
|
322
324
|
end
|
|
@@ -329,16 +331,38 @@ module Herb
|
|
|
329
331
|
@context_stack.pop
|
|
330
332
|
end
|
|
331
333
|
|
|
334
|
+
#: (untyped node) { () -> untyped } -> untyped
|
|
335
|
+
def with_element_context(node)
|
|
336
|
+
tag_name = node.tag_name&.value&.downcase
|
|
337
|
+
|
|
338
|
+
@element_stack.push(tag_name) if tag_name
|
|
339
|
+
|
|
340
|
+
if tag_name == "script"
|
|
341
|
+
push_context(:script_content)
|
|
342
|
+
elsif tag_name == "style"
|
|
343
|
+
push_context(:style_content)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
yield
|
|
347
|
+
|
|
348
|
+
pop_context if ["script", "style"].include?(tag_name)
|
|
349
|
+
|
|
350
|
+
@element_stack.pop if tag_name
|
|
351
|
+
end
|
|
352
|
+
|
|
332
353
|
def process_erb_tag(node, skip_comment_check: false)
|
|
333
354
|
opening = node.tag_opening.value
|
|
334
355
|
|
|
356
|
+
check_for_escaped_erb_tag!(opening)
|
|
357
|
+
|
|
335
358
|
if !skip_comment_check && erb_comment?(opening)
|
|
336
|
-
|
|
337
|
-
remove_trailing_whitespace_from_last_token! if
|
|
359
|
+
follows_newline = leading_space_follows_newline?
|
|
360
|
+
remove_trailing_whitespace_from_last_token! if left_trim?(node)
|
|
338
361
|
|
|
339
362
|
if at_line_start?
|
|
340
|
-
|
|
363
|
+
leading_space = extract_and_remove_leading_space!
|
|
341
364
|
@trim_next_whitespace = true
|
|
365
|
+
save_pending_leading_whitespace!(leading_space) if !leading_space.empty? && follows_newline
|
|
342
366
|
end
|
|
343
367
|
return
|
|
344
368
|
end
|
|
@@ -357,10 +381,17 @@ module Herb
|
|
|
357
381
|
return if text.empty?
|
|
358
382
|
|
|
359
383
|
if @trim_next_whitespace
|
|
384
|
+
@last_trim_consumed_newline = text.match?(/\A[ \t]*\r?\n/)
|
|
360
385
|
text = text.sub(/\A[ \t]*\r?\n/, "")
|
|
361
386
|
@trim_next_whitespace = false
|
|
387
|
+
|
|
388
|
+
restore_pending_leading_whitespace! unless @last_trim_consumed_newline
|
|
389
|
+
else
|
|
390
|
+
@last_trim_consumed_newline = false
|
|
362
391
|
end
|
|
363
392
|
|
|
393
|
+
@pending_leading_whitespace = nil
|
|
394
|
+
|
|
364
395
|
return if text.empty?
|
|
365
396
|
|
|
366
397
|
@tokens << [:text, text, current_context]
|
|
@@ -376,10 +407,12 @@ module Herb
|
|
|
376
407
|
|
|
377
408
|
def add_expression(code)
|
|
378
409
|
@tokens << [:expr, code, current_context]
|
|
410
|
+
@last_trim_consumed_newline = false
|
|
379
411
|
end
|
|
380
412
|
|
|
381
413
|
def add_expression_escaped(code)
|
|
382
414
|
@tokens << [:expr_escaped, code, current_context]
|
|
415
|
+
@last_trim_consumed_newline = false
|
|
383
416
|
end
|
|
384
417
|
|
|
385
418
|
def optimize_tokens(tokens)
|
|
@@ -463,10 +496,16 @@ module Herb
|
|
|
463
496
|
end
|
|
464
497
|
|
|
465
498
|
def process_erb_output(node, opening, code)
|
|
466
|
-
|
|
499
|
+
if @trim_next_whitespace && @pending_leading_whitespace
|
|
500
|
+
restore_pending_leading_whitespace!
|
|
501
|
+
@pending_leading_whitespace = nil
|
|
502
|
+
@trim_next_whitespace = false
|
|
503
|
+
@last_trim_consumed_newline = false
|
|
504
|
+
end
|
|
505
|
+
|
|
467
506
|
should_escape = should_escape_output?(opening)
|
|
468
507
|
add_expression_with_escaping(code, should_escape)
|
|
469
|
-
@trim_next_whitespace = true if
|
|
508
|
+
@trim_next_whitespace = true if right_trim?(node)
|
|
470
509
|
end
|
|
471
510
|
|
|
472
511
|
def indicator_for(type)
|
|
@@ -493,78 +532,130 @@ module Herb
|
|
|
493
532
|
end
|
|
494
533
|
|
|
495
534
|
def at_line_start?
|
|
496
|
-
@tokens.empty?
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
535
|
+
return true if @tokens.empty?
|
|
536
|
+
|
|
537
|
+
last_type = @tokens.last[0]
|
|
538
|
+
last_value = @tokens.last[1]
|
|
539
|
+
|
|
540
|
+
if last_type == :text
|
|
541
|
+
last_value.empty? || last_value.end_with?("\n") || (last_value =~ WHITESPACE_ONLY && preceding_token_ends_with_newline?) || last_value =~ TRAILING_INDENTATION
|
|
542
|
+
elsif EXPRESSION_TOKEN_TYPES.include?(last_type)
|
|
543
|
+
@last_trim_consumed_newline
|
|
544
|
+
else
|
|
545
|
+
last_value.end_with?("\n")
|
|
546
|
+
end
|
|
502
547
|
end
|
|
503
548
|
|
|
504
549
|
def preceding_token_ends_with_newline?
|
|
505
550
|
return true unless @tokens.length >= 2
|
|
506
551
|
|
|
507
552
|
preceding = @tokens[-2]
|
|
508
|
-
return
|
|
553
|
+
return @last_trim_consumed_newline if EXPRESSION_TOKEN_TYPES.include?(preceding[0])
|
|
554
|
+
return preceding[1].end_with?("\n") if preceding[0] == :expr_block_end
|
|
509
555
|
return true unless preceding[0] == :text
|
|
510
556
|
|
|
511
557
|
preceding[1].end_with?("\n")
|
|
512
558
|
end
|
|
513
559
|
|
|
514
|
-
def
|
|
515
|
-
|
|
560
|
+
def left_trim?(node)
|
|
561
|
+
node.tag_opening.value == "<%-"
|
|
562
|
+
end
|
|
516
563
|
|
|
517
|
-
|
|
564
|
+
def right_trim?(node)
|
|
565
|
+
node.tag_closing&.value == "-%>"
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def last_text_token
|
|
569
|
+
return unless @tokens.last && @tokens.last[0] == :text
|
|
570
|
+
|
|
571
|
+
@tokens.last
|
|
572
|
+
end
|
|
518
573
|
|
|
519
|
-
|
|
574
|
+
def extract_leading_space
|
|
575
|
+
token = last_text_token
|
|
576
|
+
return "" unless token
|
|
577
|
+
|
|
578
|
+
text = token[1]
|
|
579
|
+
|
|
580
|
+
return Regexp.last_match(1) if text =~ TRAILING_INDENTATION_CAPTURE || text =~ WHITESPACE_ONLY_CAPTURE
|
|
520
581
|
|
|
521
582
|
""
|
|
522
583
|
end
|
|
523
584
|
|
|
524
|
-
def
|
|
525
|
-
|
|
526
|
-
return
|
|
585
|
+
def leading_space_follows_newline?
|
|
586
|
+
token = last_text_token
|
|
587
|
+
return false unless token
|
|
588
|
+
|
|
589
|
+
text = token[1]
|
|
590
|
+
|
|
591
|
+
return true if text.match?(TRAILING_INDENTATION)
|
|
592
|
+
return true if @last_trim_consumed_newline && text.match?(WHITESPACE_ONLY)
|
|
593
|
+
|
|
594
|
+
false
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def extract_and_remove_leading_space!
|
|
598
|
+
leading_space = extract_leading_space
|
|
599
|
+
return leading_space if leading_space.empty?
|
|
527
600
|
|
|
528
601
|
text = @tokens.last[1]
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
602
|
+
|
|
603
|
+
if text =~ TRAILING_INDENTATION
|
|
604
|
+
text.sub!(TRAILING_WHITESPACE, "")
|
|
605
|
+
elsif text =~ WHITESPACE_ONLY
|
|
532
606
|
text.replace("")
|
|
533
607
|
end
|
|
608
|
+
|
|
534
609
|
@tokens.last[1] = text
|
|
535
610
|
|
|
536
|
-
|
|
611
|
+
leading_space
|
|
537
612
|
end
|
|
538
613
|
|
|
539
614
|
def apply_trim(node, code)
|
|
540
|
-
|
|
541
|
-
node
|
|
542
|
-
|
|
543
|
-
remove_trailing_whitespace_from_last_token! if has_left_trim
|
|
615
|
+
follows_newline = leading_space_follows_newline?
|
|
616
|
+
removed_whitespace = left_trim?(node) ? remove_trailing_whitespace_from_last_token! : ""
|
|
544
617
|
|
|
545
618
|
if at_line_start?
|
|
546
|
-
|
|
547
|
-
|
|
619
|
+
leading_space = extract_and_remove_leading_space!
|
|
620
|
+
effective_leading_space = leading_space.empty? ? removed_whitespace : leading_space
|
|
621
|
+
right_space = Herb::Engine.heredoc?(code) ? "\n" : " \n"
|
|
548
622
|
|
|
549
|
-
@
|
|
623
|
+
@pending_leading_whitespace_insert_index = @tokens.length
|
|
624
|
+
@pending_leading_whitespace = effective_leading_space if !effective_leading_space.empty? && follows_newline
|
|
625
|
+
@tokens << [:code, "#{effective_leading_space}#{code}#{right_space}", current_context]
|
|
550
626
|
@trim_next_whitespace = true
|
|
551
627
|
else
|
|
552
628
|
@tokens << [:code, code, current_context]
|
|
553
629
|
end
|
|
554
630
|
end
|
|
555
631
|
|
|
632
|
+
def save_pending_leading_whitespace!(whitespace)
|
|
633
|
+
@pending_leading_whitespace = whitespace
|
|
634
|
+
@pending_leading_whitespace_insert_index = @tokens.length
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def restore_pending_leading_whitespace!
|
|
638
|
+
return unless @pending_leading_whitespace
|
|
639
|
+
|
|
640
|
+
@tokens.insert(@pending_leading_whitespace_insert_index, [:text, @pending_leading_whitespace, current_context])
|
|
641
|
+
end
|
|
642
|
+
|
|
556
643
|
def remove_trailing_whitespace_from_last_token!
|
|
557
|
-
|
|
644
|
+
token = last_text_token
|
|
645
|
+
return "" unless token
|
|
558
646
|
|
|
559
|
-
text =
|
|
647
|
+
text = token[1]
|
|
648
|
+
removed = text[TRAILING_WHITESPACE] || ""
|
|
560
649
|
|
|
561
|
-
if text =~
|
|
562
|
-
text.sub!(
|
|
563
|
-
|
|
564
|
-
elsif text =~
|
|
650
|
+
if text =~ TRAILING_INDENTATION
|
|
651
|
+
text.sub!(TRAILING_WHITESPACE, "")
|
|
652
|
+
token[1] = text
|
|
653
|
+
elsif text =~ WHITESPACE_ONLY
|
|
565
654
|
text.replace("")
|
|
566
|
-
|
|
655
|
+
token[1] = text
|
|
567
656
|
end
|
|
657
|
+
|
|
658
|
+
removed
|
|
568
659
|
end
|
|
569
660
|
end
|
|
570
661
|
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)
|