Class: Herb::Engine::Compiler

Inherits:
Visitor
  • Object
show all
Defined in:
lib/herb/engine/compiler.rb,
sig/herb/engine/compiler.rbs

Constant Summary collapse

EXPRESSION_TOKEN_TYPES =

Returns:

  • (Object)
[:expr, :expr_escaped, :expr_block, :expr_block_escaped].freeze
TRAILING_WHITESPACE =

Returns:

  • (::Regexp)
/[ \t]+\z/
TRAILING_INDENTATION =

Returns:

  • (::Regexp)
/\n[ \t]+\z/
TRAILING_INDENTATION_CAPTURE =

Returns:

  • (::Regexp)
/\n([ \t]+)\z/
WHITESPACE_ONLY =

Returns:

  • (::Regexp)
/\A[ \t]+\z/
WHITESPACE_ONLY_CAPTURE =

Returns:

  • (::Regexp)
/\A([ \t]+)\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Visitor

#visit, #visit_all, #visit_child_nodes, #visit_erb_node, #visit_erb_render_node, #visit_erb_strict_locals_node, #visit_html_conditional_open_tag_node, #visit_node, #visit_ruby_html_attributes_splat_node, #visit_ruby_parameter_node, #visit_ruby_render_keywords_node, #visit_ruby_render_local_node

Methods included from AST::Helpers

#erb_comment?, #erb_graphql?, #erb_output?, #erb_outputs?, #inline_ruby_comment?

Constructor Details

#initialize(engine, options = {}) ⇒ Compiler

Returns a new instance of Compiler.

Parameters:

  • engine (Object)
  • options (Object) (defaults to: {})


16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/herb/engine/compiler.rb', line 16

def initialize(engine, options = {})
  super()

  @engine = engine
  @escape = options.fetch(:escape) { options.fetch(:escape_html, false) }
  @tokens = [] #: Array[untyped]
  @element_stack = [] #: Array[String]
  @context_stack = [:html_content]
  @trim_next_whitespace = false
  @last_trim_consumed_newline = false
  @pending_leading_whitespace = nil
  @pending_leading_whitespace_insert_index = 0
  @current_element_source = nil
end

Instance Attribute Details

#tokensObject (readonly)

Returns the value of attribute tokens.

Returns:

  • (Object)


14
15
16
# File 'lib/herb/engine/compiler.rb', line 14

def tokens
  @tokens
end

Instance Method Details

#add_code(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


455
456
457
# File 'lib/herb/engine/compiler.rb', line 455

def add_code(code)
  @tokens << [:code, code, current_context]
end

#add_expression(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


459
460
461
462
# File 'lib/herb/engine/compiler.rb', line 459

def add_expression(code)
  @tokens << [:expr, code, current_context]
  @last_trim_consumed_newline = false
end

#add_expression_escaped(code) ⇒ Object

Parameters:

  • code (Object)

Returns:

  • (Object)


464
465
466
467
# File 'lib/herb/engine/compiler.rb', line 464

def add_expression_escaped(code)
  @tokens << [:expr_escaped, code, current_context]
  @last_trim_consumed_newline = false
end

#add_expression_with_escaping(code, should_escape) ⇒ Object

Parameters:

  • code (Object)
  • should_escape (Object)

Returns:

  • (Object)


577
578
579
580
581
582
583
# File 'lib/herb/engine/compiler.rb', line 577

def add_expression_with_escaping(code, should_escape)
  if should_escape
    add_expression_escaped(code)
  else
    add_expression(code)
  end
end

#add_text(text) ⇒ Object

Parameters:

  • text (Object)

Returns:

  • (Object)


431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/herb/engine/compiler.rb', line 431

def add_text(text)
  return if text.empty?

  if @trim_next_whitespace
    @last_trim_consumed_newline = text.match?(/\A[ \t]*\r?\n/)
    text = text.sub(/\A[ \t]*\r?\n/, "")
    @trim_next_whitespace = false

    restore_pending_leading_whitespace! unless @last_trim_consumed_newline
  else
    @last_trim_consumed_newline = false
  end

  @pending_leading_whitespace = nil

  return if text.empty?

  @tokens << [:text, text, current_context]
end

#add_whitespace(whitespace) ⇒ Object

Parameters:

  • whitespace (Object)

Returns:

  • (Object)


451
452
453
# File 'lib/herb/engine/compiler.rb', line 451

def add_whitespace(whitespace)
  @tokens << [:whitespace, whitespace, current_context]
end

#adjacent_whitespace?(tokens, index) ⇒ Boolean

Parameters:

  • tokens (Object)
  • index (Object)

Returns:

  • (Boolean)


512
513
514
515
516
517
# File 'lib/herb/engine/compiler.rb', line 512

def adjacent_whitespace?(tokens, index)
  prev_token = index.positive? ? tokens[index - 1] : nil
  next_token = index < tokens.length - 1 ? tokens[index + 1] : nil

  trailing_whitespace?(prev_token) || leading_whitespace?(next_token)
end

#apply_trim(node, code) ⇒ Object

Parameters:

  • node (Object)
  • code (Object)

Returns:

  • (Object)


665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
# File 'lib/herb/engine/compiler.rb', line 665

def apply_trim(node, code)
  follows_newline = leading_space_follows_newline?
  removed_whitespace = left_trim?(node) ? remove_trailing_whitespace_from_last_token! : ""

  if at_line_start?
    leading_space = extract_and_remove_leading_space!
    effective_leading_space = leading_space.empty? ? removed_whitespace : leading_space
    right_space = Herb::Engine.heredoc?(code) ? "\n" : " \n"

    @pending_leading_whitespace_insert_index = @tokens.length
    @pending_leading_whitespace = effective_leading_space if !effective_leading_space.empty? && follows_newline
    @tokens << [:code, "#{effective_leading_space}#{code}#{right_space}", current_context]
    @trim_next_whitespace = true
  else
    @tokens << [:code, code, current_context]
  end
end

#at_line_start?Boolean

Returns:

  • (Boolean)


585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/herb/engine/compiler.rb', line 585

def at_line_start?
  return true if @tokens.empty?

  last_type = @tokens.last[0]
  last_value = @tokens.last[1]

  if last_type == :text
    last_value.empty? || last_value.end_with?("\n") || (last_value =~ WHITESPACE_ONLY && preceding_token_ends_with_newline?) || last_value =~ TRAILING_INDENTATION
  elsif EXPRESSION_TOKEN_TYPES.include?(last_type)
    @last_trim_consumed_newline
  else
    last_value.end_with?("\n")
  end
end

#check_for_escaped_erb_tag!(opening) ⇒ Object

Parameters:

  • opening (Object)

Returns:

  • (Object)

Raises:



361
362
363
364
365
366
367
368
# File 'lib/herb/engine/compiler.rb', line 361

def check_for_escaped_erb_tag!(opening)
  return unless opening.start_with?("<%%")

  raise Herb::Engine::GeneratorTemplateError,
        "This file appears to be a generator template (a template used to generate ERB files) " \
        "rather than a standard ERB template. It contains escaped ERB tags like <%%= %> which " \
        "produce literal ERB output in the generated file."
end

#compact_whitespace_tokens(tokens) ⇒ Object

Parameters:

  • tokens (Object)

Returns:

  • (Object)


499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/herb/engine/compiler.rb', line 499

def compact_whitespace_tokens(tokens)
  return tokens if tokens.empty?

  tokens.map.with_index { |token, index|
    next token unless token[0] == :whitespace

    next nil if adjacent_whitespace?(tokens, index)
    next nil if whitespace_before_code_sequence?(tokens, index)

    [:text, token[1], token[2]]
  }.compact
end

#context_aware_context?(context) ⇒ Boolean

Parameters:

  • context (Object)

Returns:

  • (Boolean)


568
569
570
# File 'lib/herb/engine/compiler.rb', line 568

def context_aware_context?(context)
  [:attribute_value, :script_content, :style_content].include?(context)
end

#current_contextObject

Returns:

  • (Object)


370
371
372
# File 'lib/herb/engine/compiler.rb', line 370

def current_context
  @context_stack.last
end

#extract_and_remove_leading_space!Object

Returns:

  • (Object)


648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/herb/engine/compiler.rb', line 648

def extract_and_remove_leading_space!
  leading_space = extract_leading_space
  return leading_space if leading_space.empty?

  text = @tokens.last[1]

  if text =~ TRAILING_INDENTATION
    text.sub!(TRAILING_WHITESPACE, "")
  elsif text =~ WHITESPACE_ONLY
    text.replace("")
  end

  @tokens.last[1] = text

  leading_space
end

#extract_leading_spaceObject

Returns:

  • (Object)


625
626
627
628
629
630
631
632
633
634
# File 'lib/herb/engine/compiler.rb', line 625

def extract_leading_space
  token = last_text_token
  return "" unless token

  text = token[1]

  return Regexp.last_match(1) if text =~ TRAILING_INDENTATION_CAPTURE || text =~ WHITESPACE_ONLY_CAPTURE

  ""
end

#find_token_before_code_sequence(tokens, whitespace_index) ⇒ Object

Parameters:

  • tokens (Object)
  • whitespace_index (Object)

Returns:

  • (Object)


541
542
543
544
545
546
547
# File 'lib/herb/engine/compiler.rb', line 541

def find_token_before_code_sequence(tokens, whitespace_index)
  search_index = whitespace_index - 1

  search_index -= 1 while search_index >= 0 && tokens[search_index][0] == :code

  search_index >= 0 ? tokens[search_index] : nil
end

#generate_outputObject

Returns:

  • (Object)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/herb/engine/compiler.rb', line 31

def generate_output
  optimized_tokens = optimize_tokens(@tokens)

  optimized_tokens.each do |type, value, context, escaped|
    case type
    when :text
      @engine.send(:add_text, value)
    when :code
      @engine.send(:add_code, value)
    when :expr, :expr_escaped
      indicator = indicator_for(type)

      if context_aware_context?(context)
        @engine.send(:add_context_aware_expression, indicator, value, context)
      else
        @engine.send(:add_expression, indicator, value)
      end
    when :expr_block, :expr_block_escaped
      @engine.send(:add_expression_block, indicator_for(type), value)
    when :expr_block_end
      @engine.send(:add_expression_block_end, value, escaped: escaped)
    end
  end
end

#indicator_for(type) ⇒ Object

Parameters:

  • type (Object)

Returns:

  • (Object)


562
563
564
565
566
# File 'lib/herb/engine/compiler.rb', line 562

def indicator_for(type)
  escaped = [:expr_escaped, :expr_block_escaped].include?(type)

  escaped ^ @escape ? "==" : "="
end

#last_text_tokenObject

Returns:

  • (Object)


619
620
621
622
623
# File 'lib/herb/engine/compiler.rb', line 619

def last_text_token
  return unless @tokens.last && @tokens.last[0] == :text

  @tokens.last
end

#leading_space_follows_newline?Boolean

Returns:

  • (Boolean)


636
637
638
639
640
641
642
643
644
645
646
# File 'lib/herb/engine/compiler.rb', line 636

def leading_space_follows_newline?
  token = last_text_token
  return false unless token

  text = token[1]

  return true if text.match?(TRAILING_INDENTATION)
  return true if @last_trim_consumed_newline && text.match?(WHITESPACE_ONLY)

  false
end

#leading_whitespace?(token) ⇒ Boolean

Parameters:

  • token (Object)

Returns:

  • (Boolean)


525
526
527
# File 'lib/herb/engine/compiler.rb', line 525

def leading_whitespace?(token)
  token && token[0] == :text && token[1] =~ /\A\s/
end

#left_trim?(node) ⇒ Boolean

Parameters:

  • node (Object)

Returns:

  • (Boolean)


611
612
613
# File 'lib/herb/engine/compiler.rb', line 611

def left_trim?(node)
  node.tag_opening.value == "<%-"
end

#optimize_tokens(tokens) ⇒ Object

Parameters:

  • tokens (Object)

Returns:

  • (Object)


469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/herb/engine/compiler.rb', line 469

def optimize_tokens(tokens)
  return tokens if tokens.empty?

  compacted = compact_whitespace_tokens(tokens)

  optimized = [] #: Array[untyped]
  current_text = ""
  current_context = nil

  compacted.each do |type, value, context, escaped|
    if type == :text
      current_text += value
      current_context ||= context
    else
      unless current_text.empty?
        optimized << [:text, current_text, current_context]

        current_text = ""
        current_context = nil
      end

      optimized << [type, value, context, escaped]
    end
  end

  optimized << [:text, current_text, current_context] unless current_text.empty?

  optimized
end

#pop_contextObject

Returns:

  • (Object)


378
379
380
# File 'lib/herb/engine/compiler.rb', line 378

def pop_context
  @context_stack.pop
end

#preceding_token_ends_with_newline?Boolean

Returns:

  • (Boolean)


600
601
602
603
604
605
606
607
608
609
# File 'lib/herb/engine/compiler.rb', line 600

def preceding_token_ends_with_newline?
  return true unless @tokens.length >= 2

  preceding = @tokens[-2]
  return @last_trim_consumed_newline if EXPRESSION_TOKEN_TYPES.include?(preceding[0])
  return preceding[1].end_with?("\n") if preceding[0] == :expr_block_end
  return true unless preceding[0] == :text

  preceding[1].end_with?("\n")
end

#process_erb_output(node, opening, code) ⇒ Object

Parameters:

  • node (Object)
  • opening (Object)
  • code (Object)

Returns:

  • (Object)


549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/herb/engine/compiler.rb', line 549

def process_erb_output(node, opening, code)
  if @trim_next_whitespace && @pending_leading_whitespace
    restore_pending_leading_whitespace!
    @pending_leading_whitespace = nil
    @trim_next_whitespace = false
    @last_trim_consumed_newline = false
  end

  should_escape = should_escape_output?(opening)
  add_expression_with_escaping(code, should_escape)
  @trim_next_whitespace = true if right_trim?(node)
end

#process_erb_tag(node, skip_comment_check: false) ⇒ Object

Parameters:

  • node (Object)
  • skip_comment_check: (Object) (defaults to: false)

Returns:

  • (Object)


404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/herb/engine/compiler.rb', line 404

def process_erb_tag(node, skip_comment_check: false)
  opening = node.tag_opening.value

  check_for_escaped_erb_tag!(opening)

  if !skip_comment_check && erb_comment?(opening)
    follows_newline = leading_space_follows_newline?
    remove_trailing_whitespace_from_last_token! if left_trim?(node)

    if at_line_start?
      leading_space = extract_and_remove_leading_space!
      @trim_next_whitespace = true
      save_pending_leading_whitespace!(leading_space) if !leading_space.empty? && follows_newline
    end
    return
  end
  return if erb_graphql?(opening)

  code = node.content.value.strip

  if erb_output?(opening)
    process_erb_output(node, opening, code)
  else
    apply_trim(node, code)
  end
end

#push_context(context) ⇒ Object

Parameters:

  • context (Object)

Returns:

  • (Object)


374
375
376
# File 'lib/herb/engine/compiler.rb', line 374

def push_context(context)
  @context_stack.push(context)
end

#remove_trailing_whitespace_from_last_token!Object

Returns:

  • (Object)


694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/herb/engine/compiler.rb', line 694

def remove_trailing_whitespace_from_last_token!
  token = last_text_token
  return "" unless token

  text = token[1]
  removed = text[TRAILING_WHITESPACE] || ""

  if text =~ TRAILING_INDENTATION
    text.sub!(TRAILING_WHITESPACE, "")
    token[1] = text
  elsif text =~ WHITESPACE_ONLY
    text.replace("")
    token[1] = text
  end

  removed
end

#restore_pending_leading_whitespace!Object

Returns:

  • (Object)


688
689
690
691
692
# File 'lib/herb/engine/compiler.rb', line 688

def restore_pending_leading_whitespace!
  return unless @pending_leading_whitespace

  @tokens.insert(@pending_leading_whitespace_insert_index, [:text, @pending_leading_whitespace, current_context])
end

#right_trim?(node) ⇒ Boolean

Parameters:

  • node (Object)

Returns:

  • (Boolean)


615
616
617
# File 'lib/herb/engine/compiler.rb', line 615

def right_trim?(node)
  node.tag_closing&.value == "-%>"
end

#save_pending_leading_whitespace!(whitespace) ⇒ Object

Parameters:

  • whitespace (Object)

Returns:

  • (Object)


683
684
685
686
# File 'lib/herb/engine/compiler.rb', line 683

def save_pending_leading_whitespace!(whitespace)
  @pending_leading_whitespace = whitespace
  @pending_leading_whitespace_insert_index = @tokens.length
end

#should_escape_output?(opening) ⇒ Boolean

Parameters:

  • opening (Object)

Returns:

  • (Boolean)


572
573
574
575
# File 'lib/herb/engine/compiler.rb', line 572

def should_escape_output?(opening)
  is_double_equals = opening == "<%=="
  is_double_equals ? !@escape : @escape
end

#trailing_whitespace?(token) ⇒ Boolean

Parameters:

  • token (Object)

Returns:

  • (Boolean)


519
520
521
522
523
# File 'lib/herb/engine/compiler.rb', line 519

def trailing_whitespace?(token)
  return false unless token

  token[0] == :whitespace || (token[0] == :text && token[1] =~ /\s\z/)
end

#visit_cdata_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


206
207
208
209
210
# File 'lib/herb/engine/compiler.rb', line 206

def visit_cdata_node(node)
  add_text(node.tag_opening.value)
  visit_all(node.children)
  add_text(node.tag_closing.value)
end

#visit_document_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


56
57
58
# File 'lib/herb/engine/compiler.rb', line 56

def visit_document_node(node)
  visit_all(node.children)
end

#visit_erb_begin_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


268
269
270
# File 'lib/herb/engine/compiler.rb', line 268

def visit_erb_begin_node(node)
  visit_erb_control_with_parts(node, :statements, :rescue_clause, :else_clause, :ensure_clause, :end_node)
end

#visit_erb_block_end_node(node, escaped: false) ⇒ Object

Parameters:

  • node (Object)
  • escaped: (Object) (defaults to: false)

Returns:

  • (Object)


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/herb/engine/compiler.rb', line 327

def visit_erb_block_end_node(node, escaped: false)
  remove_trailing_whitespace_from_last_token! if left_trim?(node)

  code = node.content.value.strip

  if at_line_start?
    leading_space = extract_and_remove_leading_space!
    right_space = " \n"

    @tokens << [:expr_block_end, "#{leading_space}#{code}#{right_space}", current_context, escaped]
    @trim_next_whitespace = true
  else
    @tokens << [:expr_block_end, code, current_context, escaped]
  end
end

#visit_erb_block_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/herb/engine/compiler.rb', line 296

def visit_erb_block_node(node)
  opening = node.tag_opening.value

  check_for_escaped_erb_tag!(opening)

  if opening.include?("=")
    should_escape = should_escape_output?(opening)
    code = node.content.value.strip

    @tokens << if should_escape
                 [:expr_block_escaped, code, current_context]
               else
                 [:expr_block, code, current_context]
               end

    @last_trim_consumed_newline = false
    @trim_next_whitespace = true if right_trim?(node)

    visit_all(node.body)
    visit_erb_block_end_node(node.end_node, escaped: should_escape)
  else
    visit_erb_control_node(node) do
      visit_all(node.body)
      visit(node.rescue_clause)
      visit(node.else_clause)
      visit(node.ensure_clause)
      visit(node.end_node)
    end
  end
end

#visit_erb_case_match_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


284
285
286
# File 'lib/herb/engine/compiler.rb', line 284

def visit_erb_case_match_node(node)
  visit_erb_control_with_parts(node, :conditions, :else_clause, :end_node)
end

#visit_erb_case_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


248
249
250
# File 'lib/herb/engine/compiler.rb', line 248

def visit_erb_case_node(node)
  visit_erb_control_with_parts(node, :conditions, :else_clause, :end_node)
end

#visit_erb_content_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


212
213
214
215
216
# File 'lib/herb/engine/compiler.rb', line 212

def visit_erb_content_node(node)
  return if inline_ruby_comment?(node)

  process_erb_tag(node)
end

#visit_erb_control_node(node) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Object)


218
219
220
221
222
223
224
# File 'lib/herb/engine/compiler.rb', line 218

def visit_erb_control_node(node, &)
  if node.content
    apply_trim(node, node.content.value.strip)
  end

  yield if block_given?
end

#visit_erb_control_with_parts(node, *parts) ⇒ Object

Parameters:

  • node (Object)
  • parts (Object)

Returns:

  • (Object)


343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/herb/engine/compiler.rb', line 343

def visit_erb_control_with_parts(node, *parts)
  visit_erb_control_node(node) do
    parts.each do |part|
      value = node.send(part)
      case value
      when Array
        visit_all(value)
      when nil
        # Skip nil values
      else
        visit(value)
      end
    end
  end
end

#visit_erb_else_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


234
235
236
237
238
# File 'lib/herb/engine/compiler.rb', line 234

def visit_erb_else_node(node)
  visit_erb_control_node(node) do
    visit_all(node.statements)
  end
end

#visit_erb_end_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


280
281
282
# File 'lib/herb/engine/compiler.rb', line 280

def visit_erb_end_node(node)
  visit_erb_control_node(node)
end

#visit_erb_ensure_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


276
277
278
# File 'lib/herb/engine/compiler.rb', line 276

def visit_erb_ensure_node(node)
  visit_erb_control_with_parts(node, :statements)
end

#visit_erb_for_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


256
257
258
# File 'lib/herb/engine/compiler.rb', line 256

def visit_erb_for_node(node)
  visit_erb_control_with_parts(node, :statements, :end_node)
end

#visit_erb_if_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


226
227
228
229
230
231
232
# File 'lib/herb/engine/compiler.rb', line 226

def visit_erb_if_node(node)
  visit_erb_control_node(node) do
    visit_all(node.statements)
    visit(node.subsequent)
    visit(node.end_node)
  end
end

#visit_erb_in_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


288
289
290
# File 'lib/herb/engine/compiler.rb', line 288

def visit_erb_in_node(node)
  visit_erb_control_with_parts(node, :statements)
end

#visit_erb_open_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/herb/engine/compiler.rb', line 125

def visit_erb_open_tag_node(node)
  tag_name = node.tag_name&.value

  if tag_name
    is_void = Herb::HTML::Util.void_element?(tag_name)
    uses_self_closing = is_void && @current_element_source != "ActionView::Helpers::TagHelper#tag"

    add_text("<")
    add_text(tag_name)

    node.children.each do |child|
      visit(child)
    end

    add_text(uses_self_closing ? " />" : ">")
  else
    process_erb_tag(node)
  end
end

#visit_erb_rescue_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


272
273
274
# File 'lib/herb/engine/compiler.rb', line 272

def visit_erb_rescue_node(node)
  visit_erb_control_with_parts(node, :statements, :subsequent)
end

#visit_erb_unless_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


240
241
242
243
244
245
246
# File 'lib/herb/engine/compiler.rb', line 240

def visit_erb_unless_node(node)
  visit_erb_control_node(node) do
    visit_all(node.statements)
    visit(node.else_clause)
    visit(node.end_node)
  end
end

#visit_erb_until_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


264
265
266
# File 'lib/herb/engine/compiler.rb', line 264

def visit_erb_until_node(node)
  visit_erb_control_with_parts(node, :statements, :end_node)
end

#visit_erb_when_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


252
253
254
# File 'lib/herb/engine/compiler.rb', line 252

def visit_erb_when_node(node)
  visit_erb_control_with_parts(node, :statements)
end

#visit_erb_while_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


260
261
262
# File 'lib/herb/engine/compiler.rb', line 260

def visit_erb_while_node(node)
  visit_erb_control_with_parts(node, :statements, :end_node)
end

#visit_erb_yield_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


292
293
294
# File 'lib/herb/engine/compiler.rb', line 292

def visit_erb_yield_node(node)
  process_erb_tag(node, skip_comment_check: true)
end

#visit_html_attribute_name_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


111
112
113
# File 'lib/herb/engine/compiler.rb', line 111

def visit_html_attribute_name_node(node)
  visit_all(node.children)
end

#visit_html_attribute_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/herb/engine/compiler.rb', line 98

def visit_html_attribute_node(node)
  add_whitespace(" ")

  visit(node.name)

  return unless node.value

  has_equals = node.equals.value&.include?("=")
  add_text(has_equals ? node.equals.value : "=")

  visit(node.value)
end

#visit_html_attribute_value_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


115
116
117
118
119
120
121
122
123
# File 'lib/herb/engine/compiler.rb', line 115

def visit_html_attribute_value_node(node)
  push_context(:attribute_value)

  add_text(node.open_quote&.value || '"') if node.quoted
  visit_all(node.children)
  add_text(node.close_quote&.value || '"') if node.quoted

  pop_context
end

#visit_html_close_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/herb/engine/compiler.rb', line 159

def visit_html_close_tag_node(node)
  tag_name = node.tag_name&.value&.downcase

  if @engine.content_for_head && tag_name == "head"
    escaped_html = @engine.content_for_head.gsub("'", "\\\\'")
    @tokens << [:expr, "'#{escaped_html}'.html_safe", current_context]
  end

  add_text(node.tag_opening&.value)
  add_text(node.tag_name&.value)
  add_text(node.tag_closing&.value)
end

#visit_html_comment_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


188
189
190
191
192
# File 'lib/herb/engine/compiler.rb', line 188

def visit_html_comment_node(node)
  add_text(node.comment_start.value)
  visit_all(node.children)
  add_text(node.comment_end.value)
end

#visit_html_conditional_element_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


81
82
83
84
85
86
87
# File 'lib/herb/engine/compiler.rb', line 81

def visit_html_conditional_element_node(node)
  with_element_context(node) do
    visit(node.open_conditional)
    visit_all(node.body)
    visit(node.close_conditional)
  end
end

#visit_html_doctype_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


194
195
196
197
198
# File 'lib/herb/engine/compiler.rb', line 194

def visit_html_doctype_node(node)
  add_text(node.tag_opening.value)
  visit_all(node.children)
  add_text(node.tag_closing.value)
end

#visit_html_element_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/herb/engine/compiler.rb', line 60

def visit_html_element_node(node)
  with_element_context(node) do
    visit(node.open_tag)
    visit_all(node.body)

    tag_name = node.tag_name&.value&.downcase

    if node.open_tag.is_a?(Herb::AST::ERBOpenTagNode) && tag_name && node.close_tag
      if node.close_tag.is_a?(Herb::AST::ERBEndNode)
        remove_trailing_whitespace_from_last_token! if left_trim?(node.close_tag)
        add_text("</#{tag_name}>")
        @trim_next_whitespace = true
      else
        add_text("</#{tag_name}>")
      end
    else
      visit(node.close_tag)
    end
  end
end

#visit_html_omitted_close_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


172
173
174
# File 'lib/herb/engine/compiler.rb', line 172

def visit_html_omitted_close_tag_node(node)
  # no-op
end

#visit_html_open_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


89
90
91
92
93
94
95
96
# File 'lib/herb/engine/compiler.rb', line 89

def visit_html_open_tag_node(node)
  add_text(node.tag_opening&.value || "<")
  add_text(node.tag_name.value) if node.tag_name

  visit_all(node.children)

  add_text(node.tag_closing&.value || ">")
end

#visit_html_text_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


176
177
178
# File 'lib/herb/engine/compiler.rb', line 176

def visit_html_text_node(node)
  add_text(node.content)
end

#visit_html_virtual_close_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


145
146
147
148
149
150
151
152
153
# File 'lib/herb/engine/compiler.rb', line 145

def visit_html_virtual_close_tag_node(node)
  tag_name = node.tag_name&.value

  return unless tag_name

  add_text("</")
  add_text(tag_name)
  add_text(">")
end

#visit_literal_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


180
181
182
# File 'lib/herb/engine/compiler.rb', line 180

def visit_literal_node(node)
  add_text(node.content)
end

#visit_ruby_literal_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


155
156
157
# File 'lib/herb/engine/compiler.rb', line 155

def visit_ruby_literal_node(node)
  add_expression(node.content)
end

#visit_whitespace_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


184
185
186
# File 'lib/herb/engine/compiler.rb', line 184

def visit_whitespace_node(node)
  add_whitespace(node.value.value)
end

#visit_xml_declaration_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


200
201
202
203
204
# File 'lib/herb/engine/compiler.rb', line 200

def visit_xml_declaration_node(node)
  add_text(node.tag_opening.value)
  visit_all(node.children)
  add_text(node.tag_closing.value)
end

#whitespace_before_code_sequence?(tokens, current_index) ⇒ Boolean

Parameters:

  • tokens (Object)
  • current_index (Object)

Returns:

  • (Boolean)


529
530
531
532
533
534
535
536
537
538
539
# File 'lib/herb/engine/compiler.rb', line 529

def whitespace_before_code_sequence?(tokens, current_index)
  previous_token = tokens[current_index - 1] if current_index.positive?

  return false unless previous_token && previous_token[0] == :code

  token_before_code = find_token_before_code_sequence(tokens, current_index)

  return false unless token_before_code

  trailing_whitespace?(token_before_code)
end

#with_element_context(node) { ... } ⇒ Object

: (untyped node) { () -> untyped } -> untyped

Parameters:

  • node (Object)

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)


383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/herb/engine/compiler.rb', line 383

def with_element_context(node)
  tag_name = node.tag_name&.value&.downcase
  previous_element_source = @current_element_source
  @current_element_source = node.element_source

  @element_stack.push(tag_name) if tag_name

  if tag_name == "script"
    push_context(:script_content)
  elsif tag_name == "style"
    push_context(:style_content)
  end

  yield

  pop_context if ["script", "style"].include?(tag_name)

  @element_stack.pop if tag_name
  @current_element_source = previous_element_source
end