Fixup cycle rigid parsing to be backwards compatible

This commit is contained in:
Charles-P. Clermont
2025-10-27 16:33:31 +01:00
committed by Guilherme Carreiro
parent 902ff978a6
commit 1be1e36a8d
2 changed files with 88 additions and 34 deletions
+29 -13
View File
@@ -58,19 +58,27 @@ module Liquid
def rigid_parse(markup)
p = @parse_context.new_parser(markup)
if p.look(:id) && p.look(:colon, 1)
@name = p.consume(:id)
@is_named = true
p.consume(:colon)
end
@variables = []
raise SyntaxError, options[:locale].t("errors.syntax.cycle") if p.look(:end_of_string)
while (var = safe_parse_expression(p))
@variables << var
break unless p.consume?(:comma)
first_expression = safe_parse_expression(p)
if p.look(:colon)
# cycle name: expr1, expr2, ...
@name = first_expression
@is_named = true
p.consume(:colon)
# After the colon, parse the first variable (required for named cycles)
@variables << maybe_dup_lookup(safe_parse_expression(p))
else
# cycle expr1, expr2, ...
@variables << maybe_dup_lookup(first_expression)
end
# Parse remaining comma-separated expressions
while p.consume?(:comma)
break if p.look(:end_of_string)
@variables << maybe_dup_lookup(safe_parse_expression(p))
end
p.consume(:end_of_string)
@@ -106,14 +114,22 @@ module Liquid
var =~ /\s*(#{QuotedFragment})\s*/o
next unless Regexp.last_match(1)
# Expression Parser returns cached objects, and we need to dup them to
# start the cycle over for each new cycle call.
# Liquid-C does not have a cache, so we don't need to dup the object.
var = parse_expression(Regexp.last_match(1))
var.is_a?(VariableLookup) ? var.dup : var
maybe_dup_lookup(var)
end.compact
end
# For backwards compatibility, whenever a lookup is used in an unnamed cycle,
# we make it so that the @variables.to_s produces different strings for cycles
# called with the same arguments (since @variables.to_s is used as the cycle counter key)
# This makes it so {% cycle a, b %} and {% cycle a, b %} have independent counters even if a and b share value.
# This is not true for literal values, {% cycle "a", "b" %} and {% cycle "a", "b" %} share the same counter.
# I was really scratching my head about this one, but migrating away from this would be more headache
# than it's worth. So we're keeping this quirk for now.
def maybe_dup_lookup(var)
var.is_a?(VariableLookup) ? var.dup : var
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
Array(@node.variables)