From 6a9e46dd1919069878e14ef9cc32868ecac25ec1 Mon Sep 17 00:00:00 2001 From: "Charles-P. Clermont" Date: Wed, 1 Oct 2025 08:58:14 -0400 Subject: [PATCH] Add rigid_parse method to `include` --- lib/liquid/tags/include.rb | 73 +++++++++++++++++------ test/integration/tags/include_tag_test.rb | 26 ++++++++ 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 1fefa16f..6cdbfd6f 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -27,24 +27,7 @@ module Liquid def initialize(tag_name, markup, options) super - - if markup =~ SYNTAX - - template_name = Regexp.last_match(1) - variable_name = Regexp.last_match(3) - - @alias_name = Regexp.last_match(5) - @variable_name_expr = variable_name ? parse_expression(variable_name) : nil - @template_name_expr = parse_expression(template_name) - @attributes = {} - - markup.scan(TagAttributes) do |key, value| - @attributes[key] = parse_expression(value) - end - - else - raise SyntaxError, options[:locale].t("errors.syntax.include") - end + parse_with_selected_parser(markup) end def parse(_tokens) @@ -101,6 +84,60 @@ module Liquid alias_method :parse_context, :options private :parse_context + def rigid_parse(markup) + p = @parse_context.new_parser(markup) + + template_name = p.expression + 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 + + # 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 strict_parse(markup) + lax_parse(markup) + end + + def lax_parse(markup) + if markup =~ SYNTAX + + template_name = Regexp.last_match(1) + variable_name = Regexp.last_match(3) + + @alias_name = Regexp.last_match(5) + @variable_name_expr = variable_name ? parse_expression(variable_name) : nil + @template_name_expr = parse_expression(template_name) + @attributes = {} + + markup.scan(TagAttributes) do |key, value| + @attributes[key] = parse_expression(value) + end + + else + raise SyntaxError, options[:locale].t("errors.syntax.include") + end + end + class ParseTreeVisitor < Liquid::ParseTreeVisitor def children [ diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 6e164966..e8cd6857 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -204,6 +204,32 @@ class IncludeTagTest < Minitest::Test ) end + def test_rigid_parsing_errors + [:lax, :strict].each do |mode| + with_error_mode(mode) do + assert_template_result( + 'hello value1 value2', + '{% include "snippet" !!! arg1: "value1" ~~~ arg2: "value2" %}', + partials: { 'snippet' => 'hello {{ arg1 }} {{ arg2 }}' }, + ) + end + end + + [:rigid].each do |mode| + assert_syntax_error( + '{% include "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', '{% include "snippet", arg1: "value1", arg2: "value2" %}', partials: partials) + assert_template_result('hello value1 value2', '{% include "snippet" arg1: "value1", arg2: "value2" %}', partials: partials) + assert_template_result('hello value1 value2', '{% include "snippet" arg1: "value1" arg2: "value2" %}', partials: partials) + end + def test_include_tag_caches_second_read_of_same_partial file_system = CountingFileSystem.new environment = Liquid::Environment.build(file_system: file_system)