From 346166b600f08856250534faa3693742332b0349 Mon Sep 17 00:00:00 2001 From: Alok Swamy Date: Mon, 23 Mar 2026 12:15:03 -0400 Subject: [PATCH] Add for_loop? to Include tag for AST-based keyword detection Store @is_for_loop during parsing so consumers can determine the with/for keyword from the AST instead of re-parsing raw markup. Matches the existing pattern in the Render tag. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/liquid/tags/include.rb | 17 +++++++-- test/integration/tags/include_tag_test.rb | 45 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 969482d49..f0161c6e9 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -20,7 +20,8 @@ module Liquid class Include < Tag prepend Tag::Disableable - SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o + FOR = 'for' + SYNTAX = /(#{QuotedFragment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o Syntax = SYNTAX attr_reader :template_name_expr, :variable_name_expr, :attributes @@ -84,12 +85,18 @@ def render_to_output_buffer(context, output) alias_method :parse_context, :options private :parse_context + def for_loop? + @is_for_loop + end + def strict2_parse(markup) p = @parse_context.new_parser(markup) @template_name_expr = safe_parse_expression(p) - @variable_name_expr = safe_parse_expression(p) if p.id?("for") || p.id?("with") + with_or_for = p.id?("for") || p.id?("with") + @variable_name_expr = safe_parse_expression(p) if with_or_for @alias_name = p.consume(:id) if p.id?("as") + @is_for_loop = (with_or_for == FOR) p.consume?(:comma) @@ -111,11 +118,13 @@ def strict_parse(markup) def lax_parse(markup) if markup =~ SYNTAX template_name = Regexp.last_match(1) - variable_name = Regexp.last_match(3) + with_or_for = Regexp.last_match(3) + variable_name = Regexp.last_match(4) - @alias_name = Regexp.last_match(5) + @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| diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 44c0dcda7..bd4085f21 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -439,4 +439,49 @@ def test_include_attribute_with_invalid_expression assert_match(/Unexpected character =/, error.message) end end + + def test_include_for_loop_true_with_for_keyword + with_error_modes(:lax, :strict, :strict2) do + template = Template.parse("{% include 'product' for products %}") + include_node = template.root.nodelist.first + + assert(include_node.for_loop?, "Expected for_loop? to be true for 'for' keyword") + end + end + + def test_include_for_loop_false_with_with_keyword + with_error_modes(:lax, :strict, :strict2) do + template = Template.parse("{% include 'product' with product %}") + include_node = template.root.nodelist.first + + refute(include_node.for_loop?, "Expected for_loop? to be false for 'with' keyword") + end + end + + def test_include_for_loop_false_without_keyword + with_error_modes(:lax, :strict, :strict2) do + template = Template.parse("{% include 'header' %}") + include_node = template.root.nodelist.first + + refute(include_node.for_loop?, "Expected for_loop? to be false when no keyword") + end + end + + def test_include_for_loop_with_alias + with_error_modes(:lax, :strict, :strict2) do + template = Template.parse("{% include 'product' for products as item %}") + include_node = template.root.nodelist.first + + assert(include_node.for_loop?, "Expected for_loop? to be true for 'for' with alias") + end + end + + def test_include_with_keyword_and_alias + with_error_modes(:lax, :strict, :strict2) do + template = Template.parse("{% include 'product' with products[0] as item %}") + include_node = template.root.nodelist.first + + refute(include_node.for_loop?, "Expected for_loop? to be false for 'with' with alias") + end + end end # IncludeTagTest