Skip to content

Commit e59bf8c

Browse files
Engine: Ensure Engine produces valid Ruby in test suite
Co-authored-by: Julián Pinzón Eslava <julian@buildkite.com>
1 parent 3dbbbdd commit e59bf8c

23 files changed

Lines changed: 1568 additions & 28 deletions

File tree

lib/herb/engine.rb

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ def initialize(input, properties = {})
7676
preamble = properties[:preamble] || "#{@bufvar} = #{bufval};"
7777
postamble = properties[:postamble] || "#{@bufvar}.to_s\n"
7878

79+
preamble = "#{preamble}; " unless preamble.empty? || preamble.end_with?(";", " ", "\n")
80+
7981
@src << "# frozen_string_literal: true\n" if @freeze
8082

8183
if properties[:ensure]
@@ -234,16 +236,36 @@ def add_expression_block(indicator, code)
234236

235237
def add_expression_block_result(code)
236238
with_buffer {
237-
@src << " << " << code << comment_aware_newline(code)
239+
@src << " << (" << code << comment_aware_newline(code)
238240
}
239241
end
240242

241243
def add_expression_block_result_escaped(code)
242244
with_buffer {
243-
@src << " << " << @escapefunc << "(" << code << comment_aware_newline(code) << ")"
245+
@src << " << " << @escapefunc << "((" << code << comment_aware_newline(code)
244246
}
245247
end
246248

249+
def add_expression_block_end(code, escaped: false)
250+
terminate_expression
251+
252+
trailing_newline = code.end_with?("\n")
253+
code_stripped = code.chomp
254+
255+
@src.chomp! if @src.end_with?("\n") && code_stripped.start_with?(" ")
256+
257+
@src << " " << code_stripped
258+
@src << (escaped ? "))" : ")")
259+
260+
@src << if code.include?("#") || trailing_newline
261+
"\n"
262+
else
263+
";"
264+
end
265+
266+
@buffer_on_stack = false
267+
end
268+
247269
def comment_aware_newline(code)
248270
code.include?("#") ? "\n" : ""
249271
end

lib/herb/engine/compiler.rb

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def initialize(engine, options = {})
2222
def generate_output
2323
optimized_tokens = optimize_tokens(@tokens)
2424

25-
optimized_tokens.each do |type, value, context|
25+
optimized_tokens.each do |type, value, context, extra|
2626
case type
2727
when :text
2828
@engine.send(:add_text, value)
@@ -48,6 +48,8 @@ def generate_output
4848
when :expr_block_escaped
4949
indicator = @escape ? "=" : "=="
5050
@engine.send(:add_expression_block, indicator, value)
51+
when :expr_block_end
52+
@engine.send(:add_expression_block_end, value, escaped: extra)
5153
end
5254
end
5355
end
@@ -281,7 +283,7 @@ def visit_erb_block_node(node)
281283
end
282284

283285
visit_all(node.body)
284-
visit(node.end_node)
286+
visit_erb_block_end_node(node.end_node, escaped: should_escape)
285287
else
286288
visit_erb_control_node(node) do
287289
visit_all(node.body)
@@ -290,6 +292,24 @@ def visit_erb_block_node(node)
290292
end
291293
end
292294

295+
def visit_erb_block_end_node(node, escaped: false)
296+
has_left_trim = node.tag_opening.value.start_with?("<%-")
297+
298+
remove_trailing_whitespace_from_last_token! if has_left_trim
299+
300+
code = node.content.value.strip
301+
302+
if at_line_start?
303+
lspace = extract_and_remove_lspace!
304+
rspace = " \n"
305+
306+
@tokens << [:expr_block_end, "#{lspace}#{code}#{rspace}", current_context, escaped]
307+
@trim_next_whitespace = true
308+
else
309+
@tokens << [:expr_block_end, code, current_context, escaped]
310+
end
311+
end
312+
293313
def visit_erb_control_with_parts(node, *parts)
294314
visit_erb_control_node(node) do
295315
parts.each do |part|
@@ -394,7 +414,7 @@ def optimize_tokens(tokens)
394414
current_text = ""
395415
current_context = nil
396416

397-
compacted.each do |type, value, context|
417+
compacted.each do |type, value, context, extra|
398418
if type == :text
399419
current_text += value
400420
current_context ||= context
@@ -406,7 +426,7 @@ def optimize_tokens(tokens)
406426
current_context = nil
407427
end
408428

409-
optimized << [type, value, context]
429+
optimized << [type, value, context, extra]
410430
end
411431
end
412432

test/snapshot_utils.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def assert_parsed_snapshot(source, **options)
3636

3737
def assert_compiled_snapshot(source, options = {}, **kwargs)
3838
require_relative "../lib/herb/engine"
39+
require "prism"
3940

4041
enforce_erubi_equality = kwargs.delete(:enforce_erubi_equality) || false
4142
engine_options = options.merge(kwargs)
@@ -46,6 +47,18 @@ def assert_compiled_snapshot(source, options = {}, **kwargs)
4647
snapshot_key = { source: source, options: engine_options }.to_s
4748
assert_snapshot_matches(expected, snapshot_key)
4849

50+
prism_result = Prism.parse(engine.src)
51+
syntax_errors = prism_result.errors.reject { |e| e.type == :invalid_yield }
52+
53+
assert syntax_errors.empty?, <<~MESSAGE
54+
Compiled output is not valid Ruby:
55+
56+
#{syntax_errors.map { |e| " - #{e.message} (line #{e.location.start_line})" }.join("\n")}
57+
58+
Compiled source:
59+
#{engine.src}
60+
MESSAGE
61+
4962
if should_compare_with_erubi? || enforce_erubi_equality
5063
compare_with_erubi_compiled(source, engine.src, engine_options, enforce_equality: enforce_erubi_equality)
5164
end

test/snapshots/analyze/conditional_open_tag_test/test_0017_complete_elements_in_if_else_should_not_trigger_error_78b0d6817f2c1a1686df70a1dab0abb2.txt

Lines changed: 182 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)