Skip to content

Commit 09a13bd

Browse files
Engine: Ensure Engine produces valid Ruby in test suite (#1207)
Inspired by marcoroth/reactionview#78 (comment) This pull request updates our `assert_compiled_snapshot` assertion helper to make sure the compiled snapshot is valid Ruby by passing the engine output to Prism and checking for syntax errors. Co-authored-by: JuliΓ‘n PinzΓ³n Eslava <julian@buildkite.com>
1 parent 9d17fc6 commit 09a13bd

File tree

20 files changed

+100
-37
lines changed

20 files changed

+100
-37
lines changed

β€Žlib/herb/engine.rbβ€Ž

Lines changed: 35 additions & 10 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]
@@ -214,17 +216,13 @@ def add_expression(indicator, code)
214216

215217
def add_expression_result(code)
216218
with_buffer {
217-
@src << " << (" << code
218-
@src << "\n" if heredoc?(code)
219-
@src << comment_aware_newline(code) << ").to_s"
219+
@src << " << (" << code << trailing_newline(code) << ").to_s"
220220
}
221221
end
222222

223223
def add_expression_result_escaped(code)
224224
with_buffer {
225-
@src << " << " << @escapefunc << "((" << code
226-
@src << "\n" if heredoc?(code)
227-
@src << comment_aware_newline(code) << "))"
225+
@src << " << " << @escapefunc << "((" << code << trailing_newline(code) << "))"
228226
}
229227
end
230228

@@ -238,18 +236,45 @@ def add_expression_block(indicator, code)
238236

239237
def add_expression_block_result(code)
240238
with_buffer {
241-
@src << " << " << code << comment_aware_newline(code)
239+
@src << " << (" << code << trailing_newline(code)
242240
}
243241
end
244242

245243
def add_expression_block_result_escaped(code)
246244
with_buffer {
247-
@src << " << " << @escapefunc << "(" << code << comment_aware_newline(code) << ")"
245+
@src << " << " << @escapefunc << "((" << code << trailing_newline(code)
248246
}
249247
end
250248

251-
def comment_aware_newline(code)
252-
code.include?("#") ? "\n" : ""
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+
269+
def trailing_newline(code)
270+
return "\n" if comment?(code)
271+
return "\n" if heredoc?(code)
272+
273+
""
274+
end
275+
276+
def comment?(code)
277+
code.include?("#")
253278
end
254279

255280
def heredoc?(code)

β€Ž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, escaped|
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: escaped)
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, escaped|
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, escaped]
410430
end
411431
end
412432

β€Žsig/herb/engine.rbsβ€Ž

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žsig/herb/engine/compiler.rbsβ€Ž

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

β€Ž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/engine/debug_mode_test/test_0016_block_expressions_get_debug_spans_c71ab7a64204c499e9b116ffc3fbf03d.txtβ€Ž

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

β€Žtest/snapshots/engine/debug_mode_test/test_0017_render_block_expressions_get_outline_boundaries_a8600827a6adf41bc60a28a65a7ebe50.txtβ€Ž

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

β€Žtest/snapshots/engine/debug_mode_test/test_0025_turbo_frame_tag_does_NOT_get_erb-output_outline_type_6441400221f6c4babe86ffc5da0cd758.txtβ€Ž

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žtest/snapshots/engine/debug_mode_test/test_0026_content_for_with_block_does_NOT_get_erb-output_outline_type_1d1f4277e9176d7fdfd12943bfdca2fe.txtβ€Ž

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žtest/snapshots/engine/debug_mode_test/test_0027_content_tag_with_block_does_NOT_get_erb-output_outline_type_085c2022e3fc5db7ee51f4bdb45e58cc.txtβ€Ž

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
Β (0)