From e58ac0e75b8954c17e32172abe1c0c2b9de66bf3 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Wed, 1 Oct 2025 08:45:32 -0400 Subject: [PATCH] Add rigid_parse to `render` --- lib/liquid/tags/render.rb | 75 +++++++++++++++++++----- test/integration/tags/render_tag_test.rb | 26 ++++++++ 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index 26004d64..89c11063 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -35,22 +35,7 @@ module Liquid def initialize(tag_name, markup, options) super - - raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX - - template_name = Regexp.last_match(1) - with_or_for = Regexp.last_match(3) - variable_name = Regexp.last_match(4) - - @alias_name = Regexp.last_match(6) - @variable_name_expr = variable_name ? parse_expression(variable_name) : nil - @template_name_expr = parse_expression(template_name) - @is_for_loop = (with_or_for == FOR) - - @attributes = {} - markup.scan(TagAttributes) do |key, value| - @attributes[key] = parse_expression(value) - end + parse_with_selected_parser(markup) end def for_loop? @@ -99,6 +84,64 @@ module Liquid output end + # render (string) (with|for expression)? (as id)? (key: value)* + def rigid_parse(markup) + p = @parse_context.new_parser(markup) + + template_name = rigid_template_name(p) + with_or_for = p.id?("for") || p.id?("with") || nil + if with_or_for + variable_name = p.expression + end + + alias_name = nil + if p.consume?(:as) + alias_name = p.consume(:id) + end + + @template_name_expr = parse_expression(template_name) + @variable_name_expr = variable_name ? parse_expression(variable_name) : nil + @alias_name = alias_name + @is_for_loop = (with_or_for == FOR) + + # optional comma + p.consume?(:comma) + + @attributes = {} + while p.look(:id) + key = p.consume + p.consume(:colon) + @attributes[key] = parse_expression(p.expression) + p.consume?(:comma) # optional comma + end + end + + def rigid_template_name(p) + p.consume(:string) + end + + def strict_parse(markup) + lax_parse(markup) + end + + def lax_parse(markup) + raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX + + template_name = Regexp.last_match(1) + with_or_for = Regexp.last_match(3) + variable_name = Regexp.last_match(4) + + @alias_name = Regexp.last_match(6) + @variable_name_expr = variable_name ? parse_expression(variable_name) : nil + @template_name_expr = parse_expression(template_name) + @is_for_loop = (with_or_for == FOR) + + @attributes = {} + markup.scan(TagAttributes) do |key, value| + @attributes[key] = parse_expression(value) + end + end + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index eda80a04..b6cd5cfb 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -105,6 +105,32 @@ class RenderTagTest < Minitest::Test assert_syntax_error("{% assign name = 'snippet' %}{% render name %}") end + def test_rigid_parsing_errors + [:lax, :strict].each do |mode| + with_error_mode(mode) do + assert_template_result( + 'hello value1 value2', + '{% render "snippet" !!! arg1: "value1" ~~~ arg2: "value2" %}', + partials: { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' }, + ) + end + end + + [:rigid].each do |mode| + assert_syntax_error( + '{% render "snippet" !!! arg1: "value1" ~~~ arg2: "value2" %}', + error_mode: mode, + ) + end + end + + def test_optional_commas + partials = { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' } + assert_template_result('hello value1 value2', '{% render "snippet", arg1: "value1", arg2: "value2" %}', partials: partials) + assert_template_result('hello value1 value2', '{% render "snippet" arg1: "value1", arg2: "value2" %}', partials: partials) + assert_template_result('hello value1 value2', '{% render "snippet" arg1: "value1" arg2: "value2" %}', partials: partials) + end + def test_include_tag_caches_second_read_of_same_partial file_system = StubFileSystem.new('snippet' => 'echo') assert_equal(