Skip to content

Commit 346166b

Browse files
aswamyclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 532b439 commit 346166b

2 files changed

Lines changed: 58 additions & 4 deletions

File tree

lib/liquid/tags/include.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ module Liquid
2020
class Include < Tag
2121
prepend Tag::Disableable
2222

23-
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
23+
FOR = 'for'
24+
SYNTAX = /(#{QuotedFragment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
2425
Syntax = SYNTAX
2526

2627
attr_reader :template_name_expr, :variable_name_expr, :attributes
@@ -84,12 +85,18 @@ def render_to_output_buffer(context, output)
8485
alias_method :parse_context, :options
8586
private :parse_context
8687

88+
def for_loop?
89+
@is_for_loop
90+
end
91+
8792
def strict2_parse(markup)
8893
p = @parse_context.new_parser(markup)
8994

9095
@template_name_expr = safe_parse_expression(p)
91-
@variable_name_expr = safe_parse_expression(p) if p.id?("for") || p.id?("with")
96+
with_or_for = p.id?("for") || p.id?("with")
97+
@variable_name_expr = safe_parse_expression(p) if with_or_for
9298
@alias_name = p.consume(:id) if p.id?("as")
99+
@is_for_loop = (with_or_for == FOR)
93100

94101
p.consume?(:comma)
95102

@@ -111,11 +118,13 @@ def strict_parse(markup)
111118
def lax_parse(markup)
112119
if markup =~ SYNTAX
113120
template_name = Regexp.last_match(1)
114-
variable_name = Regexp.last_match(3)
121+
with_or_for = Regexp.last_match(3)
122+
variable_name = Regexp.last_match(4)
115123

116-
@alias_name = Regexp.last_match(5)
124+
@alias_name = Regexp.last_match(6)
117125
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
118126
@template_name_expr = parse_expression(template_name)
127+
@is_for_loop = (with_or_for == FOR)
119128
@attributes = {}
120129

121130
markup.scan(TagAttributes) do |key, value|

test/integration/tags/include_tag_test.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,4 +439,49 @@ def test_include_attribute_with_invalid_expression
439439
assert_match(/Unexpected character =/, error.message)
440440
end
441441
end
442+
443+
def test_include_for_loop_true_with_for_keyword
444+
with_error_modes(:lax, :strict, :strict2) do
445+
template = Template.parse("{% include 'product' for products %}")
446+
include_node = template.root.nodelist.first
447+
448+
assert(include_node.for_loop?, "Expected for_loop? to be true for 'for' keyword")
449+
end
450+
end
451+
452+
def test_include_for_loop_false_with_with_keyword
453+
with_error_modes(:lax, :strict, :strict2) do
454+
template = Template.parse("{% include 'product' with product %}")
455+
include_node = template.root.nodelist.first
456+
457+
refute(include_node.for_loop?, "Expected for_loop? to be false for 'with' keyword")
458+
end
459+
end
460+
461+
def test_include_for_loop_false_without_keyword
462+
with_error_modes(:lax, :strict, :strict2) do
463+
template = Template.parse("{% include 'header' %}")
464+
include_node = template.root.nodelist.first
465+
466+
refute(include_node.for_loop?, "Expected for_loop? to be false when no keyword")
467+
end
468+
end
469+
470+
def test_include_for_loop_with_alias
471+
with_error_modes(:lax, :strict, :strict2) do
472+
template = Template.parse("{% include 'product' for products as item %}")
473+
include_node = template.root.nodelist.first
474+
475+
assert(include_node.for_loop?, "Expected for_loop? to be true for 'for' with alias")
476+
end
477+
end
478+
479+
def test_include_with_keyword_and_alias
480+
with_error_modes(:lax, :strict, :strict2) do
481+
template = Template.parse("{% include 'product' with products[0] as item %}")
482+
include_node = template.root.nodelist.first
483+
484+
refute(include_node.for_loop?, "Expected for_loop? to be false for 'with' with alias")
485+
end
486+
end
442487
end # IncludeTagTest

0 commit comments

Comments
 (0)