herb 0.7.5 → 0.8.0

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.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +8 -5
  3. data/config.yml +26 -6
  4. data/ext/herb/error_helpers.c +57 -3
  5. data/ext/herb/error_helpers.h +1 -1
  6. data/ext/herb/extconf.rb +1 -0
  7. data/ext/herb/extension.c +10 -24
  8. data/ext/herb/extension_helpers.c +3 -3
  9. data/ext/herb/extension_helpers.h +1 -1
  10. data/ext/herb/nodes.c +72 -37
  11. data/herb.gemspec +0 -2
  12. data/lib/herb/ast/helpers.rb +11 -0
  13. data/lib/herb/ast/node.rb +15 -6
  14. data/lib/herb/ast/nodes.rb +609 -392
  15. data/lib/herb/cli.rb +31 -0
  16. data/lib/herb/colors.rb +82 -0
  17. data/lib/herb/engine/compiler.rb +140 -14
  18. data/lib/herb/engine/debug_visitor.rb +1 -5
  19. data/lib/herb/engine/parser_error_overlay.rb +1 -1
  20. data/lib/herb/engine.rb +8 -14
  21. data/lib/herb/errors.rb +166 -56
  22. data/lib/herb/location.rb +2 -2
  23. data/lib/herb/project.rb +86 -21
  24. data/lib/herb/token.rb +14 -2
  25. data/lib/herb/version.rb +1 -1
  26. data/lib/herb.rb +1 -0
  27. data/sig/herb/ast/helpers.rbs +3 -0
  28. data/sig/herb/ast/node.rbs +12 -5
  29. data/sig/herb/ast/nodes.rbs +124 -62
  30. data/sig/herb/colors.rbs +35 -0
  31. data/sig/herb/engine/compiler.rbs +23 -1
  32. data/sig/herb/errors.rbs +74 -20
  33. data/sig/herb/token.rbs +8 -0
  34. data/sig/herb_c_extension.rbs +1 -1
  35. data/sig/serialized_ast_errors.rbs +8 -0
  36. data/src/analyze.c +420 -171
  37. data/src/analyze_helpers.c +5 -0
  38. data/src/analyze_missing_end.c +147 -0
  39. data/src/analyze_transform.c +196 -0
  40. data/src/analyzed_ruby.c +23 -2
  41. data/src/ast_node.c +5 -5
  42. data/src/ast_nodes.c +179 -179
  43. data/src/ast_pretty_print.c +232 -232
  44. data/src/element_source.c +7 -6
  45. data/src/errors.c +246 -126
  46. data/src/extract.c +92 -34
  47. data/src/herb.c +37 -49
  48. data/src/html_util.c +34 -96
  49. data/src/include/analyze.h +10 -2
  50. data/src/include/analyze_helpers.h +3 -0
  51. data/src/include/analyzed_ruby.h +4 -2
  52. data/src/include/ast_node.h +2 -2
  53. data/src/include/ast_nodes.h +67 -66
  54. data/src/include/ast_pretty_print.h +2 -2
  55. data/src/include/element_source.h +3 -1
  56. data/src/include/errors.h +30 -14
  57. data/src/include/extract.h +4 -4
  58. data/src/include/herb.h +6 -7
  59. data/src/include/html_util.h +4 -5
  60. data/src/include/lexer.h +1 -3
  61. data/src/include/lexer_peek_helpers.h +14 -14
  62. data/src/include/lexer_struct.h +3 -2
  63. data/src/include/macros.h +4 -0
  64. data/src/include/parser.h +12 -6
  65. data/src/include/parser_helpers.h +25 -15
  66. data/src/include/pretty_print.h +38 -28
  67. data/src/include/token.h +5 -8
  68. data/src/include/utf8.h +3 -2
  69. data/src/include/util/hb_arena.h +31 -0
  70. data/src/include/util/hb_arena_debug.h +8 -0
  71. data/src/include/util/hb_array.h +33 -0
  72. data/src/include/util/hb_buffer.h +34 -0
  73. data/src/include/util/hb_string.h +29 -0
  74. data/src/include/util/hb_system.h +9 -0
  75. data/src/include/util.h +3 -14
  76. data/src/include/version.h +1 -1
  77. data/src/include/visitor.h +1 -1
  78. data/src/io.c +7 -4
  79. data/src/lexer.c +61 -88
  80. data/src/lexer_peek_helpers.c +35 -37
  81. data/src/main.c +19 -23
  82. data/src/parser.c +282 -201
  83. data/src/parser_helpers.c +46 -40
  84. data/src/parser_match_tags.c +316 -0
  85. data/src/pretty_print.c +82 -106
  86. data/src/token.c +18 -65
  87. data/src/utf8.c +4 -4
  88. data/src/util/hb_arena.c +179 -0
  89. data/src/util/hb_arena_debug.c +237 -0
  90. data/src/{array.c → util/hb_array.c} +26 -27
  91. data/src/util/hb_buffer.c +203 -0
  92. data/src/util/hb_string.c +85 -0
  93. data/src/util/hb_system.c +30 -0
  94. data/src/util.c +29 -99
  95. data/src/visitor.c +54 -54
  96. data/templates/ext/herb/error_helpers.c.erb +3 -3
  97. data/templates/ext/herb/error_helpers.h.erb +1 -1
  98. data/templates/ext/herb/nodes.c.erb +11 -6
  99. data/templates/java/error_helpers.c.erb +75 -0
  100. data/templates/java/error_helpers.h.erb +20 -0
  101. data/templates/java/nodes.c.erb +97 -0
  102. data/templates/java/nodes.h.erb +23 -0
  103. data/templates/java/org/herb/ast/Errors.java.erb +121 -0
  104. data/templates/java/org/herb/ast/NodeVisitor.java.erb +14 -0
  105. data/templates/java/org/herb/ast/Nodes.java.erb +220 -0
  106. data/templates/java/org/herb/ast/Visitor.java.erb +56 -0
  107. data/templates/javascript/packages/node/extension/error_helpers.cpp.erb +8 -8
  108. data/templates/javascript/packages/node/extension/error_helpers.h.erb +1 -1
  109. data/templates/javascript/packages/node/extension/nodes.cpp.erb +9 -9
  110. data/templates/javascript/packages/node/extension/nodes.h.erb +1 -1
  111. data/templates/lib/herb/ast/nodes.rb.erb +28 -16
  112. data/templates/lib/herb/errors.rb.erb +17 -12
  113. data/templates/rust/src/ast/nodes.rs.erb +220 -0
  114. data/templates/rust/src/errors.rs.erb +216 -0
  115. data/templates/rust/src/nodes.rs.erb +374 -0
  116. data/templates/src/analyze_missing_end.c.erb +36 -0
  117. data/templates/src/analyze_transform.c.erb +24 -0
  118. data/templates/src/ast_nodes.c.erb +14 -14
  119. data/templates/src/ast_pretty_print.c.erb +36 -36
  120. data/templates/src/errors.c.erb +31 -31
  121. data/templates/src/include/ast_nodes.h.erb +10 -9
  122. data/templates/src/include/ast_pretty_print.h.erb +2 -2
  123. data/templates/src/include/errors.h.erb +6 -6
  124. data/templates/src/parser_match_tags.c.erb +38 -0
  125. data/templates/src/visitor.c.erb +4 -4
  126. data/templates/template.rb +22 -3
  127. data/templates/wasm/error_helpers.cpp.erb +9 -9
  128. data/templates/wasm/error_helpers.h.erb +1 -1
  129. data/templates/wasm/nodes.cpp.erb +9 -9
  130. data/templates/wasm/nodes.h.erb +1 -1
  131. data/vendor/prism/Rakefile +4 -1
  132. data/vendor/prism/config.yml +2 -1
  133. data/vendor/prism/include/prism/ast.h +31 -1
  134. data/vendor/prism/include/prism/diagnostic.h +1 -0
  135. data/vendor/prism/include/prism/version.h +3 -3
  136. data/vendor/prism/src/diagnostic.c +3 -1
  137. data/vendor/prism/src/prism.c +130 -71
  138. data/vendor/prism/src/util/pm_string.c +6 -8
  139. data/vendor/prism/templates/include/prism/ast.h.erb +2 -0
  140. data/vendor/prism/templates/java/org/prism/Loader.java.erb +2 -2
  141. data/vendor/prism/templates/javascript/src/deserialize.js.erb +2 -2
  142. data/vendor/prism/templates/lib/prism/serialize.rb.erb +2 -2
  143. data/vendor/prism/templates/sig/prism.rbs.erb +4 -0
  144. data/vendor/prism/templates/src/diagnostic.c.erb +1 -0
  145. metadata +34 -20
  146. data/lib/herb/libherb/array.rb +0 -51
  147. data/lib/herb/libherb/ast_node.rb +0 -50
  148. data/lib/herb/libherb/buffer.rb +0 -56
  149. data/lib/herb/libherb/extract_result.rb +0 -20
  150. data/lib/herb/libherb/lex_result.rb +0 -32
  151. data/lib/herb/libherb/libherb.rb +0 -52
  152. data/lib/herb/libherb/parse_result.rb +0 -20
  153. data/lib/herb/libherb/token.rb +0 -46
  154. data/lib/herb/libherb.rb +0 -35
  155. data/src/buffer.c +0 -241
  156. data/src/include/array.h +0 -33
  157. data/src/include/buffer.h +0 -39
  158. data/src/include/json.h +0 -28
  159. data/src/include/memory.h +0 -12
  160. data/src/json.c +0 -205
  161. data/src/memory.c +0 -53
data/lib/herb/cli.rb CHANGED
@@ -6,6 +6,8 @@
6
6
  require "optparse"
7
7
 
8
8
  class Herb::CLI
9
+ include Herb::Colors
10
+
9
11
  attr_accessor :json, :silent, :no_interactive, :no_log_file, :no_timing, :local, :escape, :no_escape, :freeze, :debug
10
12
 
11
13
  def initialize(args)
@@ -27,6 +29,8 @@ class Herb::CLI
27
29
  puts result.value.to_json
28
30
  else
29
31
  puts result.value.inspect
32
+
33
+ print_error_summary(result.errors) if @command == "parse" && result.respond_to?(:errors) && result.errors.any?
30
34
  end
31
35
  end
32
36
 
@@ -223,6 +227,33 @@ class Herb::CLI
223
227
 
224
228
  private
225
229
 
230
+ def print_error_summary(errors)
231
+ puts
232
+ puts white("#{bold(red("Errors"))} #{dimmed("(#{errors.size} total)")}")
233
+ puts
234
+
235
+ errors.each_with_index do |error, index|
236
+ error_type = error.error_name
237
+ error_location = format_location_for_copy(error.location)
238
+ error_message = error.message
239
+
240
+ puts white(" #{bold("#{index + 1}.")} #{bold(red(error_type))} #{dimmed("at #{error_location}")}")
241
+ puts white(" #{error_message}")
242
+ puts unless index == errors.size - 1
243
+ end
244
+ end
245
+
246
+ def format_location_for_copy(location)
247
+ line = location.start.line
248
+ column = location.start.column
249
+
250
+ if @file
251
+ "#{@file}:#{line}:#{column}"
252
+ else
253
+ "#{line}:#{column}"
254
+ end
255
+ end
256
+
226
257
  def compile_template
227
258
  require_relative "engine"
228
259
 
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ # rbs_inline: enabled
5
+
6
+ module Herb
7
+ module Colors
8
+ module_function
9
+
10
+ #: () -> bool
11
+ def enabled?
12
+ return false if ENV["NO_COLOR"]
13
+ return false if defined?(IRB)
14
+ return false if defined?(Minitest)
15
+
16
+ $stdout.tty?
17
+ end
18
+
19
+ #: (String) -> String
20
+ def white(string)
21
+ return string unless enabled?
22
+
23
+ "\e[37m#{string}\e[0m"
24
+ end
25
+
26
+ #: (String) -> String
27
+ def yellow(string)
28
+ return string unless enabled?
29
+
30
+ "\e[33m#{string}\e[0m"
31
+ end
32
+
33
+ #: (String) -> String
34
+ def green(string)
35
+ return string unless enabled?
36
+
37
+ "\e[32m#{string}\e[0m"
38
+ end
39
+
40
+ #: (String) -> String
41
+ def red(string)
42
+ return string unless enabled?
43
+
44
+ "\e[31m#{string}\e[0m"
45
+ end
46
+
47
+ #: (String) -> String
48
+ def magenta(string)
49
+ return string unless enabled?
50
+
51
+ "\e[35m#{string}\e[0m"
52
+ end
53
+
54
+ #: (String) -> String
55
+ def cyan(string)
56
+ return string unless enabled?
57
+
58
+ "\e[36m#{string}\e[0m"
59
+ end
60
+
61
+ #: (String) -> String
62
+ def bright_magenta(string)
63
+ return string unless enabled?
64
+
65
+ "\e[95m#{string}\e[0m"
66
+ end
67
+
68
+ #: (String) -> String
69
+ def dimmed(string)
70
+ return string unless enabled?
71
+
72
+ "\e[2m#{string}\e[0m"
73
+ end
74
+
75
+ #: (String) -> String
76
+ def bold(string)
77
+ return string unless enabled?
78
+
79
+ "\e[1m#{string}\e[0m"
80
+ end
81
+ end
82
+ end
@@ -10,6 +10,9 @@ module Herb
10
10
 
11
11
  @engine = engine
12
12
  @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")
13
16
  @tokens = [] #: Array[untyped]
14
17
  @element_stack = [] #: Array[String]
15
18
  @context_stack = [:html_content]
@@ -83,7 +86,7 @@ module Herb
83
86
  end
84
87
 
85
88
  def visit_html_attribute_node(node)
86
- add_text(" ")
89
+ add_whitespace(" ")
87
90
 
88
91
  visit(node.name)
89
92
 
@@ -131,7 +134,7 @@ module Herb
131
134
  end
132
135
 
133
136
  def visit_whitespace_node(node)
134
- add_text(node.value.value) if node.value
137
+ add_whitespace(node.value.value)
135
138
  end
136
139
 
137
140
  def visit_html_comment_node(node)
@@ -159,11 +162,13 @@ module Herb
159
162
  end
160
163
 
161
164
  def visit_erb_content_node(node)
165
+ return if inline_ruby_comment?(node)
166
+
162
167
  process_erb_tag(node)
163
168
  end
164
169
 
165
170
  def visit_erb_control_node(node, &_block)
166
- add_code(node.content.value.strip)
171
+ apply_trim(node, node.content.value.strip)
167
172
 
168
173
  yield if block_given?
169
174
  end
@@ -227,7 +232,7 @@ module Herb
227
232
  end
228
233
 
229
234
  def visit_erb_case_match_node(node)
230
- visit_erb_control_with_parts(node, :children, :conditions, :else_clause, :end_node)
235
+ visit_erb_control_with_parts(node, :conditions, :else_clause, :end_node)
231
236
  end
232
237
 
233
238
  def visit_erb_in_node(node)
@@ -294,11 +299,17 @@ module Herb
294
299
  def add_context_aware_expression(code, context)
295
300
  case context
296
301
  when :attribute_value
297
- @engine.send(:with_buffer) { @engine.instance_variable_get(:@src) << " << ::Herb::Engine.attr((" << code << "))" }
302
+ @engine.send(:with_buffer) {
303
+ @engine.src << " << #{@attrfunc}((" << code << "))"
304
+ }
298
305
  when :script_content
299
- @engine.send(:with_buffer) { @engine.instance_variable_get(:@src) << " << ::Herb::Engine.js((" << code << "))" }
306
+ @engine.send(:with_buffer) {
307
+ @engine.src << " << #{@jsfunc}((" << code << "))"
308
+ }
300
309
  when :style_content
301
- @engine.send(:with_buffer) { @engine.instance_variable_get(:@src) << " << ::Herb::Engine.css((" << code << "))" }
310
+ @engine.send(:with_buffer) {
311
+ @engine.src << " << #{@cssfunc}((" << code << "))"
312
+ }
302
313
  else
303
314
  @engine.send(:add_expression_result_escaped, code)
304
315
  end
@@ -314,17 +325,15 @@ module Herb
314
325
  if erb_output?(opening)
315
326
  process_erb_output(opening, code)
316
327
  else
317
- add_code(code)
328
+ apply_trim(node, code)
318
329
  end
319
-
320
- handle_whitespace_trimming(node)
321
330
  end
322
331
 
323
332
  def add_text(text)
324
333
  return if text.empty?
325
334
 
326
335
  if @trim_next_whitespace
327
- text = text.lstrip
336
+ text = text.sub(/\A[ \t]*\r?\n/, "")
328
337
  @trim_next_whitespace = false
329
338
  end
330
339
 
@@ -333,6 +342,10 @@ module Herb
333
342
  @tokens << [:text, text, current_context]
334
343
  end
335
344
 
345
+ def add_whitespace(whitespace)
346
+ @tokens << [:whitespace, whitespace, current_context]
347
+ end
348
+
336
349
  def add_code(code)
337
350
  @tokens << [:code, code, current_context]
338
351
  end
@@ -348,11 +361,13 @@ module Herb
348
361
  def optimize_tokens(tokens)
349
362
  return tokens if tokens.empty?
350
363
 
364
+ compacted = compact_whitespace_tokens(tokens)
365
+
351
366
  optimized = [] #: Array[untyped]
352
367
  current_text = ""
353
368
  current_context = nil
354
369
 
355
- tokens.each do |type, value, context|
370
+ compacted.each do |type, value, context|
356
371
  if type == :text
357
372
  current_text += value
358
373
  current_context ||= context
@@ -373,6 +388,56 @@ module Herb
373
388
  optimized
374
389
  end
375
390
 
391
+ def compact_whitespace_tokens(tokens)
392
+ return tokens if tokens.empty?
393
+
394
+ tokens.map.with_index { |token, index|
395
+ next token unless token[0] == :whitespace
396
+
397
+ next nil if adjacent_whitespace?(tokens, index)
398
+ next nil if whitespace_before_code_sequence?(tokens, index)
399
+
400
+ [:text, token[1], token[2]]
401
+ }.compact
402
+ end
403
+
404
+ def adjacent_whitespace?(tokens, index)
405
+ prev_token = index.positive? ? tokens[index - 1] : nil
406
+ next_token = index < tokens.length - 1 ? tokens[index + 1] : nil
407
+
408
+ trailing_whitespace?(prev_token) || leading_whitespace?(next_token)
409
+ end
410
+
411
+ def trailing_whitespace?(token)
412
+ return false unless token
413
+
414
+ token[0] == :whitespace || (token[0] == :text && token[1] =~ /\s\z/)
415
+ end
416
+
417
+ def leading_whitespace?(token)
418
+ token && token[0] == :text && token[1] =~ /\A\s/
419
+ end
420
+
421
+ def whitespace_before_code_sequence?(tokens, current_index)
422
+ previous_token = tokens[current_index - 1] if current_index.positive?
423
+
424
+ return false unless previous_token && previous_token[0] == :code
425
+
426
+ token_before_code = find_token_before_code_sequence(tokens, current_index)
427
+
428
+ return false unless token_before_code
429
+
430
+ trailing_whitespace?(token_before_code)
431
+ end
432
+
433
+ def find_token_before_code_sequence(tokens, whitespace_index)
434
+ search_index = whitespace_index - 1
435
+
436
+ search_index -= 1 while search_index >= 0 && tokens[search_index][0] == :code
437
+
438
+ search_index >= 0 ? tokens[search_index] : nil
439
+ end
440
+
376
441
  def process_erb_output(opening, code)
377
442
  should_escape = should_escape_output?(opening)
378
443
  add_expression_with_escaping(code, should_escape)
@@ -391,8 +456,69 @@ module Herb
391
456
  end
392
457
  end
393
458
 
394
- def handle_whitespace_trimming(node)
395
- @trim_next_whitespace = true if node.tag_closing&.value == "-%>"
459
+ def at_line_start?
460
+ @tokens.empty? ||
461
+ @tokens.last[0] != :text ||
462
+ @tokens.last[1].empty? ||
463
+ @tokens.last[1].end_with?("\n") ||
464
+ @tokens.last[1] =~ /\A[ \t]+\z/ ||
465
+ @tokens.last[1] =~ /\n[ \t]+\z/
466
+ end
467
+
468
+ def extract_lspace
469
+ return "" unless @tokens.last && @tokens.last[0] == :text
470
+
471
+ text = @tokens.last[1]
472
+
473
+ return Regexp.last_match(1) if text =~ /\n([ \t]+)\z/ || text =~ /\A([ \t]+)\z/
474
+
475
+ ""
476
+ end
477
+
478
+ def extract_and_remove_lspace!
479
+ lspace = extract_lspace
480
+ return lspace if lspace.empty?
481
+
482
+ text = @tokens.last[1]
483
+ if text =~ /\n[ \t]+\z/
484
+ text.sub!(/[ \t]+\z/, "")
485
+ elsif text =~ /\A[ \t]+\z/
486
+ text.replace("")
487
+ end
488
+ @tokens.last[1] = text
489
+
490
+ lspace
491
+ end
492
+
493
+ def apply_trim(node, code)
494
+ has_left_trim = node.tag_opening.value.start_with?("<%-")
495
+ node.tag_closing&.value
496
+
497
+ remove_trailing_whitespace_from_last_token! if has_left_trim
498
+
499
+ if at_line_start?
500
+ lspace = extract_and_remove_lspace!
501
+ rspace = " \n"
502
+
503
+ @tokens << [:code, "#{lspace}#{code}#{rspace}", current_context]
504
+ @trim_next_whitespace = true
505
+ else
506
+ @tokens << [:code, code, current_context]
507
+ end
508
+ end
509
+
510
+ def remove_trailing_whitespace_from_last_token!
511
+ return unless @tokens.last && @tokens.last[0] == :text
512
+
513
+ text = @tokens.last[1]
514
+
515
+ if text =~ /\n[ \t]+\z/
516
+ text.sub!(/[ \t]+\z/, "")
517
+ @tokens.last[1] = text
518
+ elsif text =~ /\A[ \t]+\z/
519
+ text.replace("")
520
+ @tokens.last[1] = text
521
+ end
396
522
  end
397
523
  end
398
524
  end
@@ -180,9 +180,7 @@ module Herb
180
180
  debug_attributes << create_debug_attribute("data-herb-debug-attach-to-parent", "true")
181
181
  end
182
182
 
183
- debug_attributes.each do |attr|
184
- open_tag_node.children << attr
185
- end
183
+ open_tag_node.children.concat(debug_attributes)
186
184
 
187
185
  @debug_attributes_applied = true
188
186
  end
@@ -233,9 +231,7 @@ module Herb
233
231
  ]
234
232
 
235
233
  debug_attributes << create_debug_attribute("data-herb-debug-line", line.to_s) if line
236
-
237
234
  debug_attributes << create_debug_attribute("data-herb-debug-column", (column + 1).to_s) if column
238
-
239
235
  debug_attributes << create_debug_attribute("style", "display: contents;")
240
236
 
241
237
  tag_name_token = create_token(:tag_name, "span")
@@ -529,7 +529,7 @@ module Herb
529
529
  HTML
530
530
  end
531
531
 
532
- section_id = "suggestions-#{rand(1000)}"
532
+ section_id = "suggestions-#{Digest::MD5.hexdigest(suggestions.inspect)}"
533
533
 
534
534
  <<~HTML
535
535
  <div class="herb-section">
data/lib/herb/engine.rb CHANGED
@@ -43,7 +43,7 @@ module Herb
43
43
 
44
44
  @bufvar = properties[:bufvar] || properties[:outvar] || "_buf"
45
45
  @escape = properties.fetch(:escape) { properties.fetch(:escape_html, false) }
46
- @escapefunc = properties[:escapefunc]
46
+ @escapefunc = properties.fetch(:escapefunc, @escape ? "__herb.h" : "::Herb::Engine.h")
47
47
  @src = properties[:src] || String.new
48
48
  @chain_appends = properties[:chain_appends]
49
49
  @buffer_on_stack = false
@@ -67,12 +67,6 @@ module Herb
67
67
  "validation_mode must be one of :raise, :overlay, or :none, got #{@validation_mode.inspect}"
68
68
  end
69
69
 
70
- @escapefunc ||= if @escape
71
- "__herb.h"
72
- else
73
- "::Herb::Engine.h"
74
- end
75
-
76
70
  @freeze = properties[:freeze]
77
71
  @freeze_template_literals = properties.fetch(:freeze_template_literals, true)
78
72
  @text_end = @freeze_template_literals ? "'.freeze" : "'"
@@ -93,11 +87,9 @@ module Herb
93
87
  end
94
88
 
95
89
  @src << "__herb = ::Herb::Engine; " if @escape && @escapefunc == "__herb.h"
96
-
97
90
  @src << preamble
98
- @src << "\n" unless preamble.end_with?("\n")
99
91
 
100
- parse_result = ::Herb.parse(input)
92
+ parse_result = ::Herb.parse(input, track_whitespace: true)
101
93
  ast = parse_result.value
102
94
  parser_errors = parse_result.errors
103
95
 
@@ -136,7 +128,7 @@ module Herb
136
128
  end
137
129
 
138
130
  @src << "\n" unless @src.end_with?("\n")
139
- send(:add_postamble, postamble)
131
+ add_postamble(postamble)
140
132
 
141
133
  @src << "; ensure\n #{@bufvar} = __original_outvar\nend\n" if properties[:ensure]
142
134
 
@@ -196,6 +188,8 @@ module Herb
196
188
  if code.include?("=begin") || code.include?("=end")
197
189
  @src << "\n" << code << "\n"
198
190
  else
191
+ @src.chomp! if @src.end_with?("\n") && code.start_with?(" ") && !code.end_with?("\n")
192
+
199
193
  @src << " " << code
200
194
 
201
195
  # TODO: rework and check for Prism::InlineComment as soon as we expose the Prism Nodes in the Herb AST
@@ -291,10 +285,10 @@ module Herb
291
285
  when :overlay
292
286
  add_parser_error_overlay(parser_errors, input)
293
287
  @src << "\n" unless @src.end_with?("\n")
294
- send(:add_postamble, "#{@bufvar}.to_s\n")
288
+ add_postamble("#{@bufvar}.to_s\n")
295
289
  when :none
296
290
  @src << "\n" unless @src.end_with?("\n")
297
- send(:add_postamble, "#{@bufvar}.to_s\n")
291
+ add_postamble("#{@bufvar}.to_s\n")
298
292
  end
299
293
  end
300
294
 
@@ -350,7 +344,7 @@ module Herb
350
344
  data-filename="#{escape_attr(@relative_file_path)}"
351
345
  data-message="#{escaped_message}"
352
346
  #{"data-suggestion=\"#{escaped_suggestion}\"" if error[:suggestion]}
353
- data-timestamp="#{Time.now.iso8601}"
347
+ data-timestamp="#{Time.now.utc.iso8601}"
354
348
  >#{html_fragment}</template>
355
349
  TEMPLATE
356
350
  }.join