Guilherme Carreiro
248f3a412f
Bump to 5.10.0
v5.10.0
2025-10-30 12:28:52 +01:00
Guilherme Carreiro
a16ec56a40
Update error handling for keeping backward-compatibility on error messages in the render tag
2025-10-30 12:00:44 +01:00
Julia Boutin
12fd93fbe2
Missing inline snippets should display same error as filebased
2025-10-30 12:00:44 +01:00
Julia Boutin
5ceb0e9cec
Raise error on invalid snippet name
2025-10-30 12:00:44 +01:00
Julia Boutin
98fbd985d8
Remove unneeded read method
2025-10-30 12:00:44 +01:00
Julia Boutin
ae05ba071c
Add liquid_public_docs yard tag to snippet tag
2025-10-30 12:00:44 +01:00
Julia Boutin
4205131148
Extract snippet resource scoring logic into assign_score_of
2025-10-30 12:00:44 +01:00
Julia Boutin
db350c54ff
Allow render tag to recognize drops that respond to to_partial
2025-10-30 12:00:44 +01:00
Julia Boutin
0cc6cdd553
Remove ... syntax references
2025-10-30 12:00:44 +01:00
Julia Boutin
40e45e32ac
Raise syntax error on incorrect render identifier type
2025-10-30 12:00:44 +01:00
Julia Boutin
d4d2237b90
Support prop spreading
2025-10-30 12:00:44 +01:00
Julia Boutin
0ceeefba02
Implement resource limits and remove leftover string references
2025-10-30 12:00:44 +01:00
Julia Boutin
65fb80a347
Render arguments should maintain correct precedence
2025-10-30 12:00:44 +01:00
Julia Boutin
489a03118c
Remove inline snippet specific example files
2025-10-30 12:00:44 +01:00
Julia Boutin
99116638fd
Support with, for, and as inline snippet syntax
...
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
2025-10-30 12:00:44 +01:00
Julia Boutin
9bcfd32e65
Support ... inline snippet syntax
2025-10-30 12:00:44 +01:00
Julia Boutin
c7ad1c90ca
Change inline snippet identifier from string to variable
...
Currently, snippet files identified by strings. This
PR makes changes to render to allow for new inline
snippets to use variables as identifiers instead
2025-10-30 12:00:44 +01:00
Julia Boutin
12bbbc4537
Create SnippetDrop and set in scope
2025-10-30 12:00:44 +01:00
Julia Boutin
1eca707c4a
Update inline snippets syntax
...
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 <orlando.qiu@shopify.com>
2025-10-30 12:00:44 +01:00
Josh Faigan
ed9c4e31c4
Introduce new inline snippets tag
...
Inline snippets will reduce code duplication and
improve the developer experience, eliminating the
need for one-off snippet files
2025-10-30 12:00:44 +01:00
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