Guilherme Carreiro
c357f91e0c
Bump to 5.9.0
v5.9.0
2025-10-27 17:25:36 +01:00
Guilherme Carreiro
1f58216f48
Add unit test mixing positional and kwargs arguments
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
d38795168b
Update test/integration/tags/table_row_test.rb
...
Co-authored-by: Gray Gilmore <graygilmore@gmail.com>
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
51f312b220
Update README.md
...
Co-authored-by: Gray Gilmore <graygilmore@gmail.com>
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
44f0429c08
Extract /\w+:0x\h{8}/ regex to UNNAMED_CYCLE_PATTERN constant
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
e57b7efe4e
Simplify render/include tags following PR review feedback
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
15430c0770
Add rigid mode to rake benchmark task
...
The benchmark results show that rigid mode performs a bit better than both strict
and lax modes across most metrics, including tokenization, parsing, rendering,
and their combined operations. Rigid mode consistently delivers the highest
number of iterations per second, with performance differences staying within
1–2% compared to the other modes
```
================================================================================
/opt/rubies/3.4.1/bin/ruby ./performance/benchmark.rb lax
Running benchmark for 20 seconds (with 10 seconds warmup).
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
tokenize: 332.000 i/100ms
parse: 14.000 i/100ms
render: 61.000 i/100ms
parse & render: 11.000 i/100ms
Calculating -------------------------------------
tokenize: 3.325k (± 1.0%) i/s (300.73 μs/i) - 66.732k in 20.070562s
parse: 148.166 (± 0.7%) i/s (6.75 ms/i) - 2.968k in 20.032971s
render: 654.428 (± 4.0%) i/s (1.53 ms/i) - 13.115k in 20.090452s
parse & render: 116.108 (± 1.7%) i/s (8.61 ms/i) - 2.332k in 20.089221s
================================================================================
/opt/rubies/3.4.1/bin/ruby ./performance/benchmark.rb strict
Running benchmark for 20 seconds (with 10 seconds warmup).
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
tokenize: 332.000 i/100ms
parse: 14.000 i/100ms
render: 61.000 i/100ms
parse & render: 11.000 i/100ms
Calculating -------------------------------------
tokenize: 3.332k (± 0.2%) i/s (300.14 μs/i) - 66.732k in 20.029095s
parse: 145.674 (± 0.0%) i/s (6.86 ms/i) - 2.926k in 20.086104s
render: 656.711 (± 4.6%) i/s (1.52 ms/i) - 13.115k in 20.050810s
parse & render: 114.705 (± 0.0%) i/s (8.72 ms/i) - 2.299k in 20.043028s
================================================================================
/opt/rubies/3.4.1/bin/ruby ./performance/benchmark.rb rigid
Running benchmark for 20 seconds (with 10 seconds warmup).
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [arm64-darwin23]
Warming up --------------------------------------
tokenize: 333.000 i/100ms
parse: 14.000 i/100ms
render: 62.000 i/100ms
parse & render: 11.000 i/100ms
Calculating -------------------------------------
tokenize: 3.334k (± 0.3%) i/s (299.93 μs/i) - 66.933k in 20.075484s
parse: 148.349 (± 2.0%) i/s (6.74 ms/i) - 2.968k in 20.019775s
render: 663.752 (± 2.6%) i/s (1.51 ms/i) - 13.268k in 20.010303s
parse & render: 116.464 (± 2.6%) i/s (8.59 ms/i) - 2.332k in 20.037869s
liquid$
```
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
6aa4041c0f
Rename with_error_mode(...) to with_error_modes(...)
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
d63dd104de
Update test/integration/tags/render_tag_test.rb
...
Co-authored-by: Alok Swamy <alok.swamy@shopify.com>
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
1733586242
Update test/unit/tags/case_tag_unit_test.rb
...
Co-authored-by: Alok Swamy <alok.swamy@shopify.com>
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
5a210fc8f6
Update Rakefile
...
Co-authored-by: Alok Swamy <alok.swamy@shopify.com>
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
8d7bb9b6f8
Update History.md (5.8.8 -> 5.9.0)
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
4f35764d44
Update History.md
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
018442b7d1
Covered changes with more tests, remove redundant cases, and the new with_error_mode(*modes)
...
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(...
```
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
a23c71e40b
Fix variable to keep it backward-compatible in strict mode
...
* 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`
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
738540a601
Update infrastructure that handles parsing switching:
...
* 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
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
4cd367d971
* Update bin/render script to present an error when no template is passed
...
* Remove `bin/example.liquid` as it's not executable
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
34ab3bdc8e
Fix assert_template_result tests not picking up Liquid::Environment.default.error_mode
...
The `rake test` command gave us the impression that we were running all the tests
on all the error modes, that was false.
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
1be1e36a8d
Fixup cycle rigid parsing to be backwards compatible
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
902ff978a6
Fixup include parsing of with expression
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
2ba81b3f1a
Fix alias parsing
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
5660ce6945
render end of string is not optional
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
5ec3008b37
Add rigid parser to tablerow tag
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
5248025439
Remove redundant tests where rigid and strict modes have the same
...
behavior
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
0bc69b86b7
No longer test ParseContext directly on RigidModeUnitTest as
...
now the `safe: true` calls are considered safe
Test the entire template instead
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
b5fbad08c6
rigid set_attribute in for parsing
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
b8958f626d
Stricter 1:1 refactor of strict_parse for Variable
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
94bbf6ca32
Make it possible to safe_parse subsets of expressions
...
e.g. sometimes you want to only accept strings | lookups.
{% render snippetName %} for example. snippetName is a string right now.
We don't want safe_parse_expression because this would allow snippetName
to be a number, a boolean, etc. But we still want to strict parse this.
So what we'll do is use parse_expression(string, safe: true), this is
an optional opt-in to say "I know what I'm doing". Usually that's
because you're using the output of Parser#something as the input of
parse_expression.
It is true that Parser#expression is subset of Expression.parse, it is
not true of the opposite (e.g. Expression.parse doesn't care about .5
and happily parses that as a global lookup of the variable named "5",
Parser#expression throws for that.)
diff --git a/lib/liquid/condition.rb b/lib/liquid/condition.rb
index e5c321dc..9ab350f0 100644
--- a/lib/liquid/condition.rb
+++ b/lib/liquid/condition.rb
@@ -48,8 +48,8 @@ module Liquid
@@operators
end
- def self.parse_expression(parse_context, markup)
- @@method_literals[markup] || parse_context.parse_expression(markup)
+ def self.parse_expression(parse_context, markup, safe: false)
+ @@method_literals[markup] || parse_context.parse_expression(markup, safe: safe)
end
attr_reader :attachment, :child_condition
diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb
index 1c59fe4a..82cf5768 100644
--- a/lib/liquid/parse_context.rb
+++ b/lib/liquid/parse_context.rb
@@ -51,13 +51,13 @@ module Liquid
end
def safe_parse_expression(parser)
- Expression.safe_parse(parser)
+ Expression.safe_parse(parser, @string_scanner, @expression_cache)
end
- def parse_expression(markup)
+ def parse_expression(markup, safe: false)
# todo(guilherme): remove this once rigid mode is fully using safe_parse_expression
- # raise Liquid::InternalError, "parse_expression is not supported in rigid mode" if @error_mode == :rigid
- puts("🚨 parse_expression used in rigid mode") if @error_mode == :rigid
+ # raise Liquid::InternalError, "parse_expression is not supported in rigid mode" if !safe && @error_mode == :rigid
+ puts("🚨 parse_expression used in rigid mode") if !safe && @error_mode == :rigid
Expression.parse(markup, @string_scanner, @expression_cache)
end
diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb
index 656d2e47..374ee511 100644
--- a/lib/liquid/tag.rb
+++ b/lib/liquid/tag.rb
@@ -72,8 +72,8 @@ module Liquid
parse_context.safe_parse_expression(parser)
end
- def parse_expression(markup)
- parse_context.parse_expression(markup)
+ def parse_expression(markup, safe: false)
+ parse_context.parse_expression(markup, safe: safe)
end
end
end
diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb
index c2be5db1..3182983b 100644
--- a/lib/liquid/tags/for.rb
+++ b/lib/liquid/tags/for.rb
@@ -93,7 +93,7 @@ module Liquid
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
collection_name = p.expression
- @collection_name = parse_expression(collection_name)
+ @collection_name = parse_expression(collection_name, safe: true)
@name = "#{@variable_name}-#{collection_name}"
@reversed = p.id?('reversed')
diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb
index 342374f1..e25d6250 100644
--- a/lib/liquid/tags/if.rb
+++ b/lib/liquid/tags/if.rb
@@ -81,8 +81,8 @@ module Liquid
block.attach(new_body)
end
- def parse_expression(markup)
- Condition.parse_expression(parse_context, markup)
+ def parse_expression(markup, safe: false)
+ Condition.parse_expression(parse_context, markup, safe: safe)
end
def lax_parse(markup)
@@ -124,9 +124,9 @@ module Liquid
end
def parse_comparison(p)
- a = parse_expression(p.expression)
+ a = parse_expression(p.expression, safe: true)
if (op = p.consume?(:comparison))
- b = parse_expression(p.expression)
+ b = parse_expression(p.expression, safe: true)
Condition.new(a, op, b)
else
Condition.new(a)
diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb
index 6cdbfd6f..b72a235b 100644
--- a/lib/liquid/tags/include.rb
+++ b/lib/liquid/tags/include.rb
@@ -87,10 +87,11 @@ module Liquid
def rigid_parse(markup)
p = @parse_context.new_parser(markup)
- template_name = p.expression
+ @template_name_expr = safe_parse_expression(p)
with_or_for = p.id?("for") || p.id?("with") || nil
+ @variable_name_expr = nil
if with_or_for
- variable_name = p.expression
+ @variable_name_expr = parse_expression(p.consume(:id), safe: true)
end
alias_name = nil
@@ -98,8 +99,6 @@ module Liquid
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
@@ -109,7 +108,7 @@ module Liquid
while p.look(:id)
key = p.consume
p.consume(:colon)
- @attributes[key] = parse_expression(p.expression)
+ @attributes[key] = safe_parse_expression(p)
p.consume?(:comma) # optional comma
end
end
diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb
index 89c11063..4f716b24 100644
--- a/lib/liquid/tags/render.rb
+++ b/lib/liquid/tags/render.rb
@@ -88,10 +88,11 @@ module Liquid
def rigid_parse(markup)
p = @parse_context.new_parser(markup)
- template_name = rigid_template_name(p)
+ @template_name_expr = parse_expression(rigid_template_name(p), safe: true)
+ @variable_name_expr = nil
with_or_for = p.id?("for") || p.id?("with") || nil
if with_or_for
- variable_name = p.expression
+ @variable_name_expr = safe_parse_expression(p)
end
alias_name = nil
@@ -99,8 +100,6 @@ module Liquid
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
@is_for_loop = (with_or_for == FOR)
@@ -111,7 +110,7 @@ module Liquid
while p.look(:id)
key = p.consume
p.consume(:colon)
- @attributes[key] = parse_expression(p.expression)
+ @attributes[key] = safe_parse_expression(p)
p.consume?(:comma) # optional comma
end
end
diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb
index 20957065..a3623bc5 100644
--- a/lib/liquid/variable.rb
+++ b/lib/liquid/variable.rb
@@ -65,11 +65,11 @@ module Liquid
return if p.look(:end_of_string)
- @name = parse_context.parse_expression(p.expression)
+ @name = parse_context.safe_parse_expression(p)
while p.consume?(:pipe)
filtername = p.consume(:id)
filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY
- @filters << parse_filter_expressions(filtername, filterargs)
+ @filters << parse_filter_expressions(filtername, filterargs, safe: true)
end
p.consume(:end_of_string)
end
@@ -122,15 +122,15 @@ module Liquid
private
- def parse_filter_expressions(filter_name, unparsed_args)
+ def parse_filter_expressions(filter_name, unparsed_args, safe: false)
filter_args = []
keyword_args = nil
unparsed_args.each do |a|
- if (matches = a.match(JustTagAttributes))
+ if (matches = a.match(JustTagAttributes)) # we'll need to fix this
keyword_args ||= {}
- keyword_args[matches[1]] = parse_context.parse_expression(matches[2])
+ keyword_args[matches[1]] = parse_context.parse_expression(matches[2], safe: false)
else
- filter_args << parse_context.parse_expression(a)
+ filter_args << parse_context.parse_expression(a, safe: safe)
end
end
result = [filter_name, filter_args]
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
4f7dafbcac
Use safe_parse_expression instead of parse_expression
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
d77662cd0e
Remove unnecessary skips
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
65a1c167b3
Add rigid_parse to case/when
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
e2a15334f0
Fail with trailing elements in the cycle tag
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
327790cdce
Fix an int the cycle tag, add extra unit tests, and updated parser switcher:
...
- Fixed NoMethod error with .peek (using look instead)
- Add friendlier error message when {% cycle %}
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
75c95d0791
Fix cycle tag
...
- `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
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
e413104e78
Remove ExpressionParser in favor of ParseContext#safe_parse
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
d56e3c50f9
Use ExpressionParser in the ParseContext when parsing in :rigid mode
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
1bff382ebc
Add ExpressionParser and ExpressionConsumer
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
6a9e46dd19
Add rigid_parse method to include
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
e58ac0e75b
Add rigid_parse to render
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
c78bf20010
Add a rigid_parse method to cycle
2025-10-27 16:33:31 +01:00
Charles-P. Clermont
6d585a24f1
Add rigid_parse_with_error_context and clarifications
2025-10-27 16:33:31 +01:00
Guilherme Carreiro
edf06c2882
Introduce :rigid parsing mode
2025-10-27 16:33:31 +01:00
Michael Go
1c1e711906
Merge pull request #1995 from Shopify/avoid-regex-allocation
...
avoid new regex allocation in util functions
2025-10-15 10:43:52 -03:00
Michael Go
b5b36665e6
avoid new regex allocation in util functions
2025-10-15 10:08:13 -03:00
Alok Swamy
9942592ea8
Merge pull request #1984 from Shopify/update-loop-named-params
...
Update liquid docs for loop's named params
2025-09-08 06:24:06 -04:00
Alok Swamy
786381c762
Update liquid docs for loop's named params
2025-09-05 16:43:48 -04:00
Gray Gilmore
8ad91b5e36
Merge pull request #1952 from Shopify/jus/template.rb-typo-docs
...
template.rb: Correct typo in docs
2025-09-03 09:03:54 -07:00
iain
9bb7fbf123
Merge pull request #1968 from Shopify/shopify-dev-docs-formatting
...
Inline some information that previously lived at category level
2025-07-03 10:59:58 -04:00
Iain Campbell
8555fd8a20
inline warning previuosly at category level
2025-06-27 16:57:00 -04:00
James Meng
9bd408f5d0
Merge pull request #1965 from Shopify/jm/bump_liquid
...
Bump Liquid to 5.8.7
v5.8.7
2025-06-09 12:42:27 -07:00