Compare commits

...
Author SHA1 Message Date
Guilherme Carreiro c4a6987a30 Update error handling for keeping backward-compatibility on error messages in the render tag 2025-10-30 11:56:41 +01:00
Julia Boutin eab457f8ca Missing inline snippets should display same error as filebased 2025-10-29 11:37:30 -06:00
Julia Boutin 99b0b38806 Raise error on invalid snippet name 2025-10-28 11:30:24 -06:00
Julia Boutin d109bc9242 Remove unneeded read method 2025-10-27 12:41:59 -06:00
Julia Boutin 47288ccca0 Add liquid_public_docs yard tag to snippet tag 2025-10-27 10:13:05 -06:00
Julia Boutin de23ed82d5 Extract snippet resource scoring logic into assign_score_of 2025-10-27 10:13:05 -06:00
Julia Boutin 1069b944a0 Allow render tag to recognize drops that respond to to_partial 2025-10-27 10:13:04 -06:00
Julia Boutin c6c342ab32 Remove ... syntax references 2025-10-27 10:13:04 -06:00
Julia Boutin 3556c3371e Raise syntax error on incorrect render identifier type 2025-10-27 10:13:04 -06:00
Julia Boutin 96a44927b5 Support prop spreading 2025-10-27 10:13:04 -06:00
Julia Boutin fb6cf17099 Implement resource limits and remove leftover string references 2025-10-27 10:12:43 -06:00
Julia Boutin 9409dd8f4a Render arguments should maintain correct precedence 2025-10-27 10:12:43 -06:00
Julia Boutin b5ecd4d0f8 Remove inline snippet specific example files 2025-10-27 10:11:22 -06:00
Julia Boutin ba5aa0abf6 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-27 10:11:22 -06:00
Julia Boutin a384e229d8 Support ... inline snippet syntax 2025-10-27 10:11:22 -06:00
Julia Boutin db474d5b57 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-27 10:11:22 -06:00
Julia Boutin 3a13ac7e6c Create SnippetDrop and set in scope 2025-10-27 10:11:22 -06:00
Julia BoutinandOrlando Qiu 4d13f030f8 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 <[email protected]>
2025-10-27 10:11:21 -06:00
Josh FaiganandJulia Boutin 8c6149782d 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-27 10:11:21 -06:00
8 changed files with 1187 additions and 18 deletions
+1
View File
@@ -67,6 +67,7 @@ require 'liquid/i18n'
require 'liquid/drop'
require 'liquid/tablerowloop_drop'
require 'liquid/forloop_drop'
require 'liquid/snippet_drop'
require 'liquid/extensions'
require 'liquid/errors'
require 'liquid/interrupts'
+3
View File
@@ -5,6 +5,7 @@
block_tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: {% %{tag} %}{% end%{tag} %}"
assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
snippet: "Syntax Error in 'snippet' - Valid syntax: snippet [var]"
case: "Syntax Error in 'case' - Valid syntax: case [condition]"
case_invalid_when: "Syntax Error in tag 'case' - Valid when condition: {% when [condition] [or condition2...] %}"
case_invalid_else: "Syntax Error in tag 'case' - Valid else condition: {% else %} (no parameters) "
@@ -19,6 +20,7 @@
invalid_delimiter: "'%{tag}' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
invalid_template_encoding: "Invalid template encoding"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
render_invalid_template_name: "Syntax error in tag 'render' - Expected a string or identifier, found %{found}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
table_row_invalid_attribute: "Invalid attribute '%{attribute}' in tablerow loop. Valid attributes are cols, limit, offset, and range"
tag_never_closed: "'%{block_name}' tag was never closed"
@@ -29,5 +31,6 @@
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
argument:
include: "Argument error in tag 'include' - Illegal template name"
render: "Argument error in tag 'render' - Dynamically chosen templates are not allowed"
disabled:
tag: "usage is not allowed in this context"
+22
View File
@@ -0,0 +1,22 @@
# frozen_string_literal: true
module Liquid
class SnippetDrop < Drop
attr_reader :body, :name, :filename
def initialize(body, name, filename)
super()
@body = body
@name = name
@filename = filename
end
def to_partial
@body
end
def to_s
'SnippetDrop'
end
end
end
+2
View File
@@ -20,6 +20,7 @@ require_relative "tags/raw"
require_relative "tags/render"
require_relative "tags/cycle"
require_relative "tags/doc"
require_relative "tags/snippet"
module Liquid
module Tags
@@ -44,6 +45,7 @@ module Liquid
'echo' => Echo,
'tablerow' => TableRow,
'doc' => Doc,
'snippet' => Snippet,
}.freeze
end
end
+20 -14
View File
@@ -27,7 +27,7 @@ module Liquid
# @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
class Render < Tag
FOR = 'for'
SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
SYNTAX = /(#{QuotedString}+|#{VariableSegment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
disable_tags "include"
@@ -47,21 +47,23 @@ module Liquid
end
def render_tag(context, output)
# The expression should be a String literal, which parses to a String object
template_name = @template_name_expr
raise ::ArgumentError unless template_name.is_a?(String)
template = context.evaluate(@template_name_expr)
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context,
)
context_variable_name = @alias_name || template_name.split('/').last
if template.respond_to?(:to_partial)
partial = template.to_partial
template_name = template.filename
context_variable_name = @alias_name || template.name
elsif @template_name_expr.is_a?(String)
partial = PartialCache.load(template, context: context, parse_context: parse_context)
template_name = partial.name
context_variable_name = @alias_name || template_name.split('/').last
else
raise ::ArgumentError
end
render_partial_func = ->(var, forloop) {
inner_context = context.new_isolated_subcontext
inner_context.template_name = partial.name
inner_context.template_name = template_name
inner_context.partial = true
inner_context['forloop'] = forloop if forloop
@@ -101,14 +103,18 @@ module Liquid
key = p.consume
p.consume(:colon)
@attributes[key] = safe_parse_expression(p)
p.consume?(:comma)
p.consume?(:comma) # optional comma
end
p.consume(:end_of_string)
end
def rigid_template_name(p)
p.consume(:string)
return p.consume(:string) if p.look(:string)
return p.consume(:id) if p.look(:id)
found = p.consume || "nothing"
raise SyntaxError, options[:locale].t("errors.syntax.render_invalid_template_name", found: found)
end
def strict_parse(markup)
+45
View File
@@ -0,0 +1,45 @@
# frozen_string_literal: true
module Liquid
# @liquid_public_docs
# @liquid_type tag
# @liquid_category variable
# @liquid_name snippet
# @liquid_summary
# Creates a new inline snippet.
# @liquid_description
# You can create inline snippets to make your Liquid code more modular.
# @liquid_syntax
# {% snippet snippet_name %}
# value
# {% endsnippet %}
class Snippet < Block
def initialize(tag_name, markup, options)
super
p = @parse_context.new_parser(markup)
if p.look(:id)
@to = p.consume(:id)
p.consume(:end_of_string)
else
raise SyntaxError, options[:locale].t("errors.syntax.snippet")
end
end
def render_to_output_buffer(context, output)
snippet_drop = SnippetDrop.new(@body, @to, context.template_name)
context.scopes.last[@to] = snippet_drop
context.resource_limits.increment_assign_score(assign_score_of(snippet_drop))
output
end
def blank?
true
end
private
def assign_score_of(snippet_drop)
snippet_drop.body.nodelist.sum { |node| node.to_s.bytesize }
end
end
end
+7 -4
View File
@@ -101,10 +101,6 @@ class RenderTagTest < Minitest::Test
end
end
def test_dynamically_choosen_templates_are_not_allowed
assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
end
def test_rigid_parsing_errors
with_error_modes(:lax, :strict) do
assert_template_result(
@@ -294,6 +290,13 @@ class RenderTagTest < Minitest::Test
)
end
def test_render_tag_with_snippet_drop
assert_template_result(
"Hello from snippet",
"{% snippet my_snippet %}Hello from snippet{% endsnippet %}{% render my_snippet %}",
)
end
def test_render_tag_renders_error_with_template_name
assert_template_result(
'Liquid error (foo line 1): standard error',
File diff suppressed because it is too large Load Diff