Both tags previously accepted any string as a variable name via
`markup.strip`. Now they use `parse_with_selected_parser` and validate
the variable name with `p.consume(:id)` in strict2 mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Both tags previously used only regex (VariableSignature) to validate
variable names, which allowed invalid identifiers like (a(b(c) and
[x.y] in all parse modes.
- assign: strict2_parse uses Parser to validate the LHS as a valid
identifier before delegating RHS to Variable
- capture: strict2_parse uses Parser to validate the variable name
as a valid identifier
- Both tags now include ParserSwitching and dispatch through
strict_parse_with_error_mode_fallback
- Lax mode is unchanged — invalid names are still accepted
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
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) <[email protected]>
Add bare-bracket rejection to Parser#expression in strict2 mode, so that
`['var']` is disallowed and `self['var']` is the required syntax.
- Add `Expression::SELF` constant ('self')
- Add `Parser#reject_bare_brackets` option, checked in `expression`
- Add `ParseContext#reject_bare_brackets?` and `force_reject_bare_brackets`
- Add `VariableLookupDrop` for `self['var']` scope-chain lookups
- Add `Variable#==` for rewriter state comparison
- Update `Context#find_variable` to return `VariableLookupDrop` for `self`
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* feat: add cumulative resource score tracking across partial renders
Add cumulative_render_score and cumulative_assign_score counters to
ResourceLimits that accumulate across reset() calls, with optional
cumulative_render_score_limit and cumulative_assign_score_limit to
cap total work across all partial renders.
Also add a reached? check in BlockBody's render loop so that once a
cumulative limit triggers, the parent template stops processing
further nodes.
Bump version to 5.12.0.
* refactor: move cumulative limit enforcement into reset()
Instead of checking reached? in BlockBody's render loop, enforce
cumulative limits in reset() itself. Since reset() is called before
the begin/rescue MemoryError block in Template#render, the raise
propagates to the parent naturally — no changes to BlockBody needed.
- nil is NOT empty (but IS blank) - matches Shopify production
- String first/last returns '' for empty strings, not nil - matches ActiveSupport
- Add test for nil not being empty
Implement ActiveSupport-compatible behaviors internally so Liquid works
correctly without ActiveSupport being loaded:
1. String first/last via property access (name.first, name.last)
- VariableLookup now handles string[0] and string[-1] for first/last
2. String first/last via filters (name | first, name | last)
- StandardFilters#first and #last now handle strings
3. blank?/empty? comparisons for types without these methods
- Condition now implements liquid_blank? and liquid_empty? internally
- blank? matches ActiveSupport: nil, false, empty/whitespace strings,
empty arrays/hashes are all blank
- empty? checks length == 0 only (whitespace is NOT empty)
This fixes spec failures for templates like:
- {{ name.first }} / {{ name | first }} on strings
- {% if x == blank %} for whitespace strings, empty hashes/arrays
- {% case ' ' %}{% when blank %} matching whitespace
This commit reverts the Inline Snippets tag (#2001) and bumps
Liquid to 5.11. For now, the inclusion of Inline Snippets
in the latest Liquid release is being treated as a bug.
While #2001 does implement the scope contained in RFC#1916,
we need to take a step back to make sure we’re setting our
sights high enough with this feature, and that we’re truly
supporting theme developers in the ways they need.
If you have any feedback, please leave a comment on RFC#1916.
- Liquid Developer Tools
Previously if you set `strict_variables` to `true` on the context using
`key?('key_name')` would raise a `Liquid::UndefinedVariable` error.
Raising this error makes sense if you're trying to access the variable
directly with something like `context['key_name']` but by using `key?`
you're safely checking if it exists first.
You should be able to enable `strict_variables` and use `key?` in
combination with each other to ensure code safety.
This commit updates the render method to share parts
of the snippet and block rendering logic to enable
inline snippets to support `with`, `for`, and `as`
syntax
Currently, snippet files identified by strings. This
PR makes changes to render to allow for new inline
snippets to use variables as identifiers instead
Previously, inline snippets syntax looked a bit
different, they:
- used strings as tag identifiers
- defined tag arguments {% snippet "input" |type| %}
This PR updates snippets to better reflect
the currently proposed syntax
Co-authored-by: Orlando Qiu <[email protected]>
Most of changes update this:
```
[:lax, :strict].each do |mode|
with_error_mode(mode) do
assert_template_result(...
```
to be this:
```
with_error_mode(:lax, :strict) do
assert_template_result(...
```
* lax_parse - no changes
* strict_parse - uses the `lax_parse_filter_expressions` (as it was doing before)
* rigid_parse - uses the `rigid_parse_filter_expressions`
* Remove development helpers from parse context
* Simplify strict_parse_with_error_mode_fallback and update
documentation
* Add unit tests for `Liquid::Expression` and `Liquid::ParseContext`
* Update test helpers to work better with the `:rigid` mode
- `respond_to?` was returning `false` in the parser switcher
because `rigid_parse` was private
It was working before because `parse_context` was doing
the double-parsing thing, but when we removed that, this
test fairly started breaking