Compare commits

...
Author SHA1 Message Date
Charles-P. Clermont e46de3b6ab --wip-- [skip ci] 2025-09-30 09:18:40 -04:00
Alok SwamyandGitHub 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 GilmoreandGitHub 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
iainandGitHub 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 MengandGitHub 9bd408f5d0 Merge pull request #1965 from Shopify/jm/bump_liquid
Bump Liquid to 5.8.7
2025-06-09 12:42:27 -07:00
James Meng 79b831d96c Bump Liquid to 5.8.7 2025-06-09 12:38:04 -07:00
James MengandGitHub aebd75e5e8 Merge pull request #1954 from Shopify/jm/doc_body
Expose tag body in the Doc tag
2025-06-09 12:25:19 -07:00
James Meng 7f2f8a226b Add tests for new public methods 2025-06-09 12:24:59 -07:00
James Meng 7b2b25fda1 Fix Doc tag blank? method to check body content
Previously the blank? method always returned true. Now it properly checks
if the body is empty, making the tag behavior consistent with other tags.

Also updated test to use whitespace control for cleaner assertions.
2025-06-09 11:36:18 -07:00
James Meng 8548b96a97 Remove body attr_reader and initiliaze @body instance variable in parse method 2025-06-06 13:08:06 -07:00
upgrade-umpire[bot]andGitHub fc96e66e14 Merge pull request #1953 from Shopify/actions-commit
Applying Merge
2025-06-05 20:15:14 +00:00
James Meng 79a771d724 Add test for doc tag capturing token before enddoc 2025-06-04 19:30:47 -07:00
James Meng 65b1dedac5 Expose tag body in the Doc tag 2025-06-04 12:04:41 -07:00
Ian Ker-SeymerandGitHub f375d7b3aa Add unit test for custom Liquid tag registration (#1960)
Adds EnvironmentTest to verify custom tag registration and rendering.
2025-05-22 11:38:10 -04:00
Brian Chen 6b3f6c6fb4 update github actions to commits 2025-04-29 14:04:22 -04:00
Justin D. HarrisandGitHub aefd48e341 template.rb: Correct typo in docs 2025-04-22 09:50:47 -04:00
17 changed files with 249 additions and 32 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
+4 -4
View File
@@ -31,8 +31,8 @@ jobs:
- { ruby: ruby-head, allowed-failure: false, rubyopt: "--yjit" }
name: Test Ruby ${{ matrix.entry.ruby }}
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@dffc446db9ba5a0c4446edb5bca1c5c473a806c5 # v1.235.0
with:
ruby-version: ${{ matrix.entry.ruby }}
bundler-cache: true
@@ -45,8 +45,8 @@ jobs:
memory_profile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ruby/setup-ruby@dffc446db9ba5a0c4446edb5bca1c5c473a806c5 # v1.235.0
with:
bundler-cache: true
- run: bundle exec rake memory_profile:run
+2 -1
View File
@@ -1,6 +1,7 @@
# Liquid Change Log
## 5.8.1 (unreleased)
## 5.8.7
* Expose body content in the `Doc` tag [James Meng]
## 5.8.1
+17 -1
View File
@@ -2,10 +2,17 @@
module Liquid
module ParserSwitching
# We want "rigid" which is like strict but more strict only in some cases.
# We want to make it so strict behaves as is
# We want "rigid" to be stricter (prob beta flag)
# It shouldn't be slower to run rigid than strict, it's not rigid -> strict -> lax
# It's rigid -> lax or it's strict -> lax
def strict_parse_with_error_mode_fallback(markup)
strict_parse_with_error_context(markup)
rigid_parse_with_error_context(markup)
rescue SyntaxError => e
case parse_context.error_mode
when :rigid
raise
when :strict
raise
when :warn
@@ -16,6 +23,7 @@ module Liquid
def parse_with_selected_parser(markup)
case parse_context.error_mode
when :rigid then rigid_parse_with_error_context(markup)
when :strict then strict_parse_with_error_context(markup)
when :lax then lax_parse(markup)
when :warn
@@ -30,6 +38,14 @@ module Liquid
private
def rigid_parse_with_error_context(markup)
respond_to?(:rigid_parse) ? rigid_parse(markup) : strict_parse(markup)
rescue SyntaxError => e
e.line_number = line_number
e.markup_context = markup_context(markup)
raise e
end
def strict_parse_with_error_context(markup)
strict_parse(markup)
rescue SyntaxError => e
+4
View File
@@ -9,6 +9,10 @@ module Liquid
# Creates a new variable.
# @liquid_description
# You can create variables of any [basic type](/docs/api/liquid/basics#types), [object](/docs/api/liquid/objects), or object property.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% assign variable_name = value %}
# @liquid_syntax_keyword variable_name The name of the variable being created.
+4
View File
@@ -9,6 +9,10 @@ module Liquid
# Creates a new variable with a string value.
# @liquid_description
# You can create complex strings with Liquid logic and variables.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_syntax
# {% capture variable %}
# value
+42 -12
View File
@@ -22,18 +22,7 @@ module Liquid
def initialize(tag_name, markup, options)
super
case markup
when NamedSyntax
@variables = variables_from_string(Regexp.last_match(2))
@name = parse_expression(Regexp.last_match(1))
@is_named = true
when SimpleSyntax
@variables = variables_from_string(markup)
@name = @variables.to_s
@is_named = !@name.match?(/\w+:0x\h{8}/)
else
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end
parse_with_selected_parser(markup)
end
def named?
@@ -65,6 +54,47 @@ module Liquid
private
# cycle [name:] expression(, expression)*
def rigid_parse(markup)
$stderr.puts "using rigid"
p = @parse_context.new_parser(markup)
if p.look(:id) && p.peek(1) == :colon
@name = p.consume(:id)
@is_named = true
p.consume(:colon)
end
@variables = []
while (var = p.expression)
@variables << var
break unless p.consume?(:comma)
end
raise_syntax_error(options) if @variables.empty?
end
# Temporarily until we migrate
def strict_parse(markup)
$stderr.puts "using rigid"
lax_parse(markup)
end
def lax_parse(markup)
case markup
when NamedSyntax
@variables = variables_from_string(Regexp.last_match(2))
@name = parse_expression(Regexp.last_match(1))
@is_named = true
when SimpleSyntax
@variables = variables_from_string(markup)
@name = @variables.to_s
@is_named = !@name.match?(/\w+:0x\h{8}/)
else
raise SyntaxError, options[:locale].t("errors.syntax.cycle")
end
end
def variables_from_string(markup)
markup.split(',').collect do |var|
var =~ /\s*(#{QuotedFragment})\s*/o
+4
View File
@@ -7,6 +7,10 @@ module Liquid
# @liquid_name decrement
# @liquid_summary
# Creates a new variable, with a default value of -1, that's decreased by 1 with each subsequent call.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_description
# Variables that are declared with `decrement` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
+6 -2
View File
@@ -36,6 +36,8 @@ module Liquid
end
def parse(tokens)
@body = +""
while (token = tokens.shift)
tag_name = token =~ BlockBody::FullTokenPossiblyInvalid && Regexp.last_match(2)
@@ -43,8 +45,10 @@ module Liquid
if tag_name == block_delimiter
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end
@body << token unless token.empty?
end
raise_tag_never_closed(block_name)
@@ -55,11 +59,11 @@ module Liquid
end
def blank?
true
@body.empty?
end
def nodelist
[]
[@body]
end
private
+2 -2
View File
@@ -20,8 +20,8 @@ module Liquid
# @liquid_syntax_keyword variable The current item in the array.
# @liquid_syntax_keyword array The array to iterate over.
# @liquid_syntax_keyword expression The expression to render for each iteration.
# @liquid_optional_param limit [number] The number of iterations to perform.
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
# @liquid_optional_param limit: [number] The number of iterations to perform.
# @liquid_optional_param offset: [number] The 1-based index to start iterating at.
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
# @liquid_optional_param reversed [untyped] Iterate in reverse order.
class For < Block
+4
View File
@@ -7,6 +7,10 @@ module Liquid
# @liquid_name increment
# @liquid_summary
# Creates a new variable, with a default value of 0, that's increased by 1 with each subsequent call.
#
# > Caution:
# > Predefined Liquid objects can be overridden by variables with the same name.
# > To make sure that you can access all Liquid objects, make sure that your variable name doesn't match a predefined object's name.
# @liquid_description
# Variables that are declared with `increment` are unique to the [layout](/themes/architecture/layouts), [template](/themes/architecture/templates),
# or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
+3 -3
View File
@@ -19,9 +19,9 @@ module Liquid
# @liquid_syntax_keyword variable The current item in the array.
# @liquid_syntax_keyword array The array to iterate over.
# @liquid_syntax_keyword expression The expression to render.
# @liquid_optional_param cols [number] The number of columns that the table should have.
# @liquid_optional_param limit [number] The number of iterations to perform.
# @liquid_optional_param offset [number] The 1-based index to start iterating at.
# @liquid_optional_param cols: [number] The number of columns that the table should have.
# @liquid_optional_param limit: [number] The number of iterations to perform.
# @liquid_optional_param offset: [number] The 1-based index to start iterating at.
# @liquid_optional_param range [untyped] A custom numeric range to iterate over.
class TableRow < Block
Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
+2 -1
View File
@@ -2,7 +2,7 @@
module Liquid
# Templates are central to liquid.
# Interpretating templates is a two step process. First you compile the
# Interpreting templates is a two step process. First you compile the
# source code you got. During compile time some extensive error checking is performed.
# your code should expect to get some SyntaxErrors.
#
@@ -25,6 +25,7 @@ module Liquid
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
# :warn is the default and will give deprecation warnings when invalid syntax is used.
# :strict will enforce correct syntax.
# :rigid will is stricter even.
def error_mode=(mode)
Deprecations.warn("Template.error_mode=", "Environment#error_mode=")
Environment.default.error_mode = mode
+1 -1
View File
@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "5.8.6"
VERSION = "5.8.7"
end
+15
View File
@@ -45,4 +45,19 @@ class CycleTagTest < Minitest::Test
assert_template_result("11", template)
end
def test_cycle_tag_with_error_mode
# QuotedFragment is more permissive than what Parser#expression allows.
[:lax, :strict].each do |mode|
with_error_mode(mode) do
assert_template_result("a", "{% cycle .5: 'a', 'b' %}")
assert_template_result("b", "{% assign 5 = 'b' %}{% cycle .5, .4 %}")
end
end
with_error_mode(:rigid) do
assert_raises(Liquid::SyntaxError) { Template.parse("{% cycle .5: 'a', 'b' %}") }
assert_raises(Liquid::SyntaxError) { Template.parse("{% cycle .5, .4 %}") }
end
end
end
+25
View File
@@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'test_helper'
class EnvironmentTest < Minitest::Test
include Liquid
class UnsubscribeFooter < Liquid::Tag
def render(_context)
'Unsubscribe Footer'
end
end
def test_custom_tag
email_environment = Liquid::Environment.build do |environment|
environment.register_tag("unsubscribe_footer", UnsubscribeFooter)
end
assert(email_environment.tags["unsubscribe_footer"])
assert(email_environment.tag_for_name("unsubscribe_footer"))
template = Liquid::Template.parse("{% unsubscribe_footer %}", environment: email_environment)
assert_equal('Unsubscribe Footer', template.render)
end
end
+108 -5
View File
@@ -20,6 +20,21 @@ class DocTagUnitTest < Minitest::Test
assert_template_result('', template)
end
def test_doc_tag_body_content
doc_content = " Documentation content\n @param {string} foo - test\n"
template_source = "{% doc %}#{doc_content}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(doc_content, doc_tag.nodelist.first.to_s)
end
def test_doc_tag_does_not_support_extra_arguments
error = assert_raises(Liquid::SyntaxError) do
template = <<~LIQUID.chomp
@@ -116,6 +131,20 @@ class DocTagUnitTest < Minitest::Test
assert_template_result('', template)
end
def test_doc_tag_captures_token_before_enddoc
template_source = "{% doc %}{{ incomplete{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal("{{ incomplete", doc_tag.nodelist.first.to_s)
end
def test_doc_tag_preserves_error_line_numbers
template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true)
{% doc %}
@@ -145,11 +174,11 @@ class DocTagUnitTest < Minitest::Test
def test_doc_tag_delimiter_handling
assert_template_result('', <<~LIQUID.chomp)
{% if true %}
{% doc %}
{% docEXTRA %}wut{% enddocEXTRA %}xyz
{% enddoc %}
{% endif %}
{%- if true -%}
{%- doc -%}
{%- docEXTRA -%}wut{% enddocEXTRA -%}xyz
{%- enddoc -%}
{%- endif -%}
LIQUID
assert_template_result('', "{% doc %}123{% enddoc xyz %}")
@@ -167,6 +196,80 @@ class DocTagUnitTest < Minitest::Test
)
end
def test_doc_tag_blank_with_empty_content
template_source = "{% doc %}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(true, doc_tag.blank?)
end
def test_doc_tag_blank_with_content
template_source = "{% doc %}Some documentation{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(false, doc_tag.blank?)
end
def test_doc_tag_blank_with_whitespace_only
template_source = "{% doc %} {% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal(false, doc_tag.blank?)
end
def test_doc_tag_nodelist_returns_array_with_body
doc_content = "Documentation content\n@param {string} foo"
template_source = "{% doc %}#{doc_content}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal([doc_content], doc_tag.nodelist)
assert_equal(1, doc_tag.nodelist.length)
assert_equal(doc_content, doc_tag.nodelist.first)
end
def test_doc_tag_nodelist_with_empty_content
template_source = "{% doc %}{% enddoc %}"
doc_tag = nil
ParseTreeVisitor
.for(Template.parse(template_source).root)
.add_callback_for(Liquid::Doc) do |tag|
doc_tag = tag
end
.visit
assert_equal([""], doc_tag.nodelist)
assert_equal(1, doc_tag.nodelist.length)
end
private
def traversal(template)