From e4276bdf6b6406b2615b90960200d370f3145c5f Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 19 Nov 2025 12:32:18 +0100 Subject: [PATCH 1/4] 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/liquid.rb | 1 - lib/liquid/locales/en.yml | 3 - lib/liquid/snippet_drop.rb | 22 - lib/liquid/tags.rb | 2 - lib/liquid/tags/render.rb | 36 +- lib/liquid/tags/snippet.rb | 45 - test/integration/tags/render_tag_test.rb | 11 +- test/integration/tags/snippet_test.rb | 1087 ---------------------- 8 files changed, 19 insertions(+), 1188 deletions(-) delete mode 100644 lib/liquid/snippet_drop.rb delete mode 100644 lib/liquid/tags/snippet.rb delete mode 100644 test/integration/tags/snippet_test.rb diff --git a/lib/liquid.rb b/lib/liquid.rb index 09cafe947..4d0a71a64 100644 --- a/lib/liquid.rb +++ b/lib/liquid.rb @@ -67,7 +67,6 @@ module Liquid require 'liquid/drop' require 'liquid/tablerowloop_drop' require 'liquid/forloop_drop' -require 'liquid/snippet_drop' require 'liquid/extensions' require 'liquid/errors' require 'liquid/interrupts' diff --git a/lib/liquid/locales/en.yml b/lib/liquid/locales/en.yml index 7a9fb71e4..b99d490c8 100644 --- a/lib/liquid/locales/en.yml +++ b/lib/liquid/locales/en.yml @@ -5,7 +5,6 @@ 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) " @@ -20,7 +19,6 @@ 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" @@ -31,6 +29,5 @@ 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" diff --git a/lib/liquid/snippet_drop.rb b/lib/liquid/snippet_drop.rb deleted file mode 100644 index 98686999c..000000000 --- a/lib/liquid/snippet_drop.rb +++ /dev/null @@ -1,22 +0,0 @@ -# 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 diff --git a/lib/liquid/tags.rb b/lib/liquid/tags.rb index 3ec5e7cfb..dff7553f7 100644 --- a/lib/liquid/tags.rb +++ b/lib/liquid/tags.rb @@ -20,7 +20,6 @@ require_relative "tags/render" require_relative "tags/cycle" require_relative "tags/doc" -require_relative "tags/snippet" module Liquid module Tags @@ -45,7 +44,6 @@ module Tags 'echo' => Echo, 'tablerow' => TableRow, 'doc' => Doc, - 'snippet' => Snippet, }.freeze end end diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index dec725ed4..4d29e420e 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -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}+|#{VariableSegment}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o + SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o disable_tags "include" @@ -47,23 +47,21 @@ def render_to_output_buffer(context, output) end def render_tag(context, output) - template = context.evaluate(@template_name_expr) - - 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 + # 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) + + partial = PartialCache.load( + template_name, + context: context, + parse_context: parse_context, + ) + + context_variable_name = @alias_name || template_name.split('/').last render_partial_func = ->(var, forloop) { inner_context = context.new_isolated_subcontext - inner_context.template_name = template_name + inner_context.template_name = partial.name inner_context.partial = true inner_context['forloop'] = forloop if forloop @@ -103,18 +101,14 @@ def rigid_parse(markup) key = p.consume p.consume(:colon) @attributes[key] = safe_parse_expression(p) - p.consume?(:comma) # optional comma + p.consume?(:comma) end p.consume(:end_of_string) end def rigid_template_name(p) - 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) + p.consume(:string) end def strict_parse(markup) diff --git a/lib/liquid/tags/snippet.rb b/lib/liquid/tags/snippet.rb deleted file mode 100644 index 87e2a0ded..000000000 --- a/lib/liquid/tags/snippet.rb +++ /dev/null @@ -1,45 +0,0 @@ -# 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 diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index 6fe19abb4..d6453fb51 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -101,6 +101,10 @@ def test_sub_contexts_count_towards_the_same_recursion_limit 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( @@ -290,13 +294,6 @@ def test_render_tag_with_drop ) 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', diff --git a/test/integration/tags/snippet_test.rb b/test/integration/tags/snippet_test.rb deleted file mode 100644 index 6d1cb35d7..000000000 --- a/test/integration/tags/snippet_test.rb +++ /dev/null @@ -1,1087 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -class SnippetTest < Minitest::Test - include Liquid - - class LaxMode < SnippetTest - def test_valid_inline_snippet - template = <<~LIQUID.strip - {% snippet input %} - Hey - {% endsnippet %} - LIQUID - expected = '' - - assert_template_result(expected, template) - end - - def test_render_inline_snippet - template = <<~LIQUID.strip - {% snippet hey %} - Hey - {% endsnippet %} - - {%- render hey -%} - LIQUID - expected = <<~OUTPUT - - Hey - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with_variable - template = <<~LIQUID.strip - {% snippet hey %} -

Today is {{ "hello" | capitalize }}

- {% endsnippet %} - - {%- render hey -%} - LIQUID - expected = <<~OUTPUT - -

Today is Hello

- OUTPUT - - assert_template_result(expected, template) - end - - def test_render_multiple_inline_snippets - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {% snippet banner %} - - Welcome to my store! - - {% endsnippet %} - - {%- render input -%} - {%- render banner -%} - LIQUID - expected = <<~OUTPUT - - - - - - - Welcome to my store! - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with_argument - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with_doc_tag - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with_evaluated_assign - template = <<~LIQUID.strip - {% snippet input %} -

{{ greeting }}

- {% endsnippet %} - - {%- assign greeting = "hello" | upcase -%} - {%- render input, greeting: greeting -%} - LIQUID - expected = <<~OUTPUT - -

HELLO

- OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with_multiple_arguments - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text", value: "Hello" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippets_using_same_argument_name - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {% snippet inputs %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - {%- render inputs, type: "password", value: "pass" -%} - LIQUID - - expected = <<~OUTPUT - - - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_empty_string_when_missing_argument - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_snippets_as_arguments - template = <<~LIQUID.strip - {% assign color_scheme = 'dark' %} - - {% snippet header %} -
- {{ message }} -
- {% endsnippet %} - - {% snippet main %} - {% assign color_scheme = 'auto' %} - -
- {% render header, message: 'Welcome!' %} -
- {% endsnippet %} - - {% render main, header: header %} - LIQUID - - expected = <<~OUTPUT - - - - - - - - - -
- -
- Welcome! -
- -
- OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_shouldnt_leak_context - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text", value: "Hello" -%} - - {{ type }} - {{ value }} - LIQUID - expected = <<~OUTPUT - - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_multiple_inline_snippets_without_leaking_context - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - {% enddoc %} - - - {% endsnippet %} - - {% snippet no_leak %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - {%- render no_leak -%} - LIQUID - expected = <<~OUTPUT - - - - - - - - - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_ignores_outside_context - template = <<~LIQUID.strip - {% assign color_scheme = 'dark' %} - - {% snippet header %} -
- {{ message }} -
- {% endsnippet %} - - - {% render header, message: 'Welcome!' %} - LIQUID - expected = <<~OUTPUT - - - - - - -
- Welcome! -
- OUTPUT - - assert_template_result(expected, template) - end - - def test_render_captured_snippet - template = <<~LIQUID - {% snippet header %} -
- {{ message }} -
- {% endsnippet %} - - {% capture up_header %} - {%- render header, message: 'Welcome!' -%} - {% endcapture %} - - {{ up_header | upcase }} - - {{ header | upcase }} - - {{ header }} - LIQUID - expected = <<~OUTPUT - - - - - -
- WELCOME! -
- - - SNIPPETDROP - - SnippetDrop - OUTPUT - - assert_template_result(expected, template) - end - - def test_inline_snippet_local_scope_takes_precedence - template = <<~LIQUID - {% assign color_scheme = 'dark' %} - - {% snippet header %} - {% assign color_scheme = 'light' %} -
- {{ message }} -
- {% endsnippet %} - - {{ color_scheme }} - - {% render header, message: 'Welcome!', color_scheme: color_scheme %} - - {{ color_scheme }} - LIQUID - expected = <<~OUTPUT - - - - - dark - - - -
- Welcome! -
- - - dark - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_forloop - template = <<~LIQUID.strip - {% snippet item %} -
  • {{ forloop.index }}: {{ item }}
  • - {% endsnippet %} - - {% assign items = "A,B,C" | split: "," %} - {%- render item for items -%} - LIQUID - expected = <<~OUTPUT - - - -
  • 1: A
  • - -
  • 2: B
  • - -
  • 3: C
  • - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_with - template = <<~LIQUID.strip - {% snippet header %} -
    {{ header }}
    - {% endsnippet %} - - {% assign product = "Apple" %} - {%- render header with product -%} - LIQUID - expected = <<~OUTPUT - - - -
    Apple
    - OUTPUT - - assert_template_result(expected, template) - end - - def test_render_inline_snippet_alias - template = <<~LIQUID.strip - {% snippet product_card %} -
    {{ item }}
    - {% endsnippet %} - - {% assign featured = "Apple" %} - {%- render product_card with featured as item -%} - LIQUID - expected = <<~OUTPUT - - - -
    Apple
    - OUTPUT - - assert_template_result(expected, template) - end - - def test_snippet_with_invalid_identifier - template = <<~LIQUID - {% snippet header foo bar %} - Invalid - {% endsnippet %} - LIQUID - - exception = assert_raises(SyntaxError) { Liquid::Template.parse(template) } - - assert_match("Expected end_of_string but found id", exception.message) - end - - def test_render_with_non_existent_tag - template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true) - {% snippet foo %} - {% render non_existent %} - {% endsnippet %} - - {% render foo %} - LIQUID - - expected = <<~TEXT - - - - Liquid error (index line 2): internal - TEXT - template.name = "index" - - assert_equal(expected, template.render('errors' => ErrorDrop.new)) - end - - def test_render_handles_errors - template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true) - {% snippet foo %} - {% render non_existent %} will raise an error. - - Bla bla test. - - This is an argument error: {{ 'test' | slice: 'not a number' }} - {% endsnippet %} - - {% render foo %} - LIQUID - - expected = <<~TEXT - - - - Liquid error (index line 2): internal will raise an error. - - Bla bla test. - - This is an argument error: Liquid error (index line 6): invalid integer - TEXT - template.name = "index" - - assert_equal(expected, template.render('errors' => ErrorDrop.new)) - end - end - - class RigidMode < SnippetTest - def test_valid_inline_snippet - template = <<~LIQUID.strip - {% snippet input %} - Hey - {% endsnippet %} - LIQUID - expected = '' - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet - template = <<~LIQUID.strip - {% snippet hey %} - Hey - {% endsnippet %} - - {%- render hey -%} - LIQUID - expected = <<~OUTPUT - - Hey - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with_variable - template = <<~LIQUID.strip - {% snippet hey %} -

    Today is {{ "hello" | capitalize }}

    - {% endsnippet %} - - {%- render hey -%} - LIQUID - expected = <<~OUTPUT - -

    Today is Hello

    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_multiple_inline_snippets - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {% snippet banner %} - - Welcome to my store! - - {% endsnippet %} - - {%- render input -%} - {%- render banner -%} - LIQUID - expected = <<~OUTPUT - - - - - - - Welcome to my store! - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with_argument - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with_doc_tag - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with_evaluated_assign - template = <<~LIQUID.strip - {% snippet input %} -

    {{ greeting }}

    - {% endsnippet %} - - {%- assign greeting = "hello" | upcase -%} - {%- render input, greeting: greeting -%} - LIQUID - expected = <<~OUTPUT - -

    HELLO

    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with_multiple_arguments - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text", value: "Hello" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippets_using_same_argument_name - template = <<~LIQUID.strip - {% snippet input %} - - {% endsnippet %} - - {% snippet inputs %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - {%- render inputs, type: "password", value: "pass" -%} - LIQUID - - expected = <<~OUTPUT - - - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_empty_string_when_missing_argument - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text" -%} - LIQUID - expected = <<~OUTPUT - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_snippets_as_arguments - template = <<~LIQUID.strip - {% assign color_scheme = 'dark' %} - - {% snippet header %} -
    - {{ message }} -
    - {% endsnippet %} - - {% snippet main %} - {% assign color_scheme = 'auto' %} - -
    - {% render header, message: 'Welcome!' %} -
    - {% endsnippet %} - - {% render main, header: header %} - LIQUID - - expected = <<~OUTPUT - - - - - - - - - -
    - -
    - Welcome! -
    - -
    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_shouldnt_leak_context - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - @param {string} value - Input value. - {% enddoc %} - - - {% endsnippet %} - - {%- render input, type: "text", value: "Hello" -%} - - {{ type }} - {{ value }} - LIQUID - expected = <<~OUTPUT - - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_multiple_inline_snippets_without_leaking_context - template = <<~LIQUID.strip - {% snippet input %} - {% doc %} - @param {string} type - Input type. - {% enddoc %} - - - {% endsnippet %} - - {% snippet no_leak %} - - {% endsnippet %} - - {%- render input, type: "text" -%} - {%- render no_leak -%} - LIQUID - expected = <<~OUTPUT - - - - - - - - - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_ignores_outside_context - template = <<~LIQUID.strip - {% assign color_scheme = 'dark' %} - - {% snippet header %} -
    - {{ message }} -
    - {% endsnippet %} - - - {% render header, message: 'Welcome!' %} - LIQUID - expected = <<~OUTPUT - - - - - - -
    - Welcome! -
    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_inline_snippet_local_scope_takes_precedence - template = <<~LIQUID - {% assign color_scheme = 'dark' %} - - {% snippet header %} - {% assign color_scheme = 'light' %} -
    - {{ message }} -
    - {% endsnippet %} - - {{ color_scheme }} - - {% render header, message: 'Welcome!', color_scheme: color_scheme %} - - {{ color_scheme }} - LIQUID - expected = <<~OUTPUT - - - - - dark - - - -
    - Welcome! -
    - - - dark - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_forloop - template = <<~LIQUID.strip - {% snippet item %} -
  • {{ forloop.index }}: {{ item }}
  • - {% endsnippet %} - - {% assign items = "A,B,C" | split: "," %} - {%- render item for items -%} - LIQUID - expected = <<~OUTPUT - - - -
  • 1: A
  • - -
  • 2: B
  • - -
  • 3: C
  • - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_with - template = <<~LIQUID.strip - {% snippet header %} -
    {{ header }}
    - {% endsnippet %} - - {% assign product = "Apple" %} - {%- render header with product -%} - LIQUID - expected = <<~OUTPUT - - - -
    Apple
    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_inline_snippet_alias - template = <<~LIQUID.strip - {% snippet product_card %} -
    {{ item }}
    - {% endsnippet %} - - {% assign featured = "Apple" %} - {%- render product_card with featured as item -%} - LIQUID - expected = <<~OUTPUT - - - -
    Apple
    - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_captured_snippet - template = <<~LIQUID - {% snippet header %} -
    - {{ message }} -
    - {% endsnippet %} - - {% capture up_header %} - {%- render header, message: 'Welcome!' -%} - {% endcapture %} - - {{ up_header | upcase }} - - {{ header | upcase }} - - {{ header }} - LIQUID - expected = <<~OUTPUT - - - - - -
    - WELCOME! -
    - - - SNIPPETDROP - - SnippetDrop - OUTPUT - - assert_template_result(expected, template, error_mode: :rigid) - end - - def test_render_with_invalid_identifier - template = "{% render 123 %}" - - exception = assert_raises(SyntaxError) do - Liquid::Template.parse(template, error_mode: :rigid) - end - - assert_match("Expected a string or identifier, found 123", exception.message) - end - - def test_render_with_non_existent_tag - template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true, error_mode: :rigid) - {% snippet foo %} - {% render non_existent %} - {% endsnippet %} - - {% render foo %} - LIQUID - - expected = <<~TEXT - - - - Liquid error (index line 2): internal - TEXT - template.name = "index" - - assert_equal(expected, template.render('errors' => ErrorDrop.new)) - end - - def test_render_handles_errors - template = Liquid::Template.parse(<<~LIQUID.chomp, line_numbers: true, error_mode: :rigid) - {% snippet foo %} - {% render non_existent %} will raise an error. - - Bla bla test. - - This is an argument error: {{ 'test' | slice: 'not a number' }} - {% endsnippet %} - - {% render foo %} - LIQUID - - expected = <<~TEXT - - - - Liquid error (index line 2): internal will raise an error. - - Bla bla test. - - This is an argument error: Liquid error (index line 6): invalid integer - TEXT - template.name = "index" - - assert_equal(expected, template.render('errors' => ErrorDrop.new)) - end - - def test_render_with_no_identifier - template = "{% render %}" - - exception = assert_raises(SyntaxError) do - Liquid::Template.parse(template, error_mode: :rigid) - end - - assert_match("Expected a string or identifier, found nothing", exception.message) - end - - def test_snippet_with_invalid_identifier - template = <<~LIQUID - {% snippet header foo bar %} - Invalid - {% endsnippet %} - LIQUID - - exception = assert_raises(SyntaxError) do - Liquid::Template.parse(template, error_mode: :rigid) - end - - assert_match("Expected end_of_string but found id", exception.message) - end - end - - class ResourceLimits < SnippetTest - def test_increment_assign_score_by_bytes_not_characters - t = Template.parse("{% snippet foo %}すごい{% endsnippet %}") - t.render! - assert_equal(9, t.resource_limits.assign_score) - end - end -end From a336a2fffd01f9320eb023f92eb93a5a450dfc8e Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 19 Nov 2025 13:24:55 +0100 Subject: [PATCH 2/4] Rename rigid to strict2 mode --- README.md | 8 ++-- Rakefile | 16 +++---- bin/render | 2 +- lib/liquid/environment.rb | 2 +- lib/liquid/parse_context.rb | 8 ++-- lib/liquid/parser_switching.rb | 28 ++++++++---- lib/liquid/tags/case.rb | 8 ++-- lib/liquid/tags/cycle.rb | 2 +- lib/liquid/tags/for.rb | 2 +- lib/liquid/tags/if.rb | 2 +- lib/liquid/tags/include.rb | 2 +- lib/liquid/tags/render.rb | 6 +-- lib/liquid/tags/table_row.rb | 2 +- lib/liquid/template.rb | 2 +- lib/liquid/variable.rb | 6 +-- test/integration/tags/cycle_tag_test.rb | 8 ++-- test/integration/tags/include_tag_test.rb | 10 ++-- test/integration/tags/render_tag_test.rb | 8 ++-- test/integration/tags/table_row_test.rb | 56 +++++++++++------------ test/integration/variable_test.rb | 10 ++-- test/unit/condition_unit_test.rb | 10 ++-- test/unit/parse_context_unit_test.rb | 54 +++++++++++----------- test/unit/partial_cache_unit_test.rb | 4 +- test/unit/tags/case_tag_unit_test.rb | 12 ++--- test/unit/variable_unit_test.rb | 4 +- 25 files changed, 140 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 34884464d..5066dc659 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,10 @@ Liquid also comes with different parsers that can be used when editing templates when templates are invalid. You can enable this new parser like this: ```ruby -Liquid::Environment.default.error_mode = :rigid # Raises a SyntaxError when invalid syntax is used in all tags -Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used in some tags -Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal -Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything. +Liquid::Environment.default.error_mode = :strict2 # Raises a SyntaxError when invalid syntax is used in all tags +Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used in some tags +Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal +Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything. ``` If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`: diff --git a/Rakefile b/Rakefile index 878536bf5..83afcbfa0 100755 --- a/Rakefile +++ b/Rakefile @@ -33,7 +33,7 @@ task :rubocop do end end -desc('runs test suite with lax, strict, and rigid parsers') +desc('runs test suite with lax, strict, and strict2 parsers') task :test do ENV['LIQUID_PARSER_MODE'] = 'lax' Rake::Task['base_test'].invoke @@ -42,7 +42,7 @@ task :test do Rake::Task['base_test'].reenable Rake::Task['base_test'].invoke - ENV['LIQUID_PARSER_MODE'] = 'rigid' + ENV['LIQUID_PARSER_MODE'] = 'strict2' Rake::Task['base_test'].reenable Rake::Task['base_test'].invoke @@ -55,7 +55,7 @@ task :test do Rake::Task['integration_test'].reenable Rake::Task['integration_test'].invoke - ENV['LIQUID_PARSER_MODE'] = 'rigid' + ENV['LIQUID_PARSER_MODE'] = 'strict2' Rake::Task['integration_test'].reenable Rake::Task['integration_test'].invoke end @@ -88,13 +88,13 @@ namespace :benchmark do ruby "./performance/benchmark.rb strict" end - desc "Run the liquid benchmark with rigid parsing" - task :rigid do - ruby "./performance/benchmark.rb rigid" + desc "Run the liquid benchmark with strict2 parsing" + task :strict2 do + ruby "./performance/benchmark.rb strict2" end - desc "Run the liquid benchmark with lax, strict, and rigid parsing" - task run: [:lax, :strict, :rigid] + desc "Run the liquid benchmark with lax, strict, and strict2 parsing" + task run: [:lax, :strict, :strict2] desc "Run unit benchmarks" namespace :unit do diff --git a/bin/render b/bin/render index 26d9b53d8..012b27179 100755 --- a/bin/render +++ b/bin/render @@ -41,6 +41,6 @@ def assigns end puts Liquid::Template - .parse(source, error_mode: :rigid) + .parse(source, error_mode: :strict2) .tap { |t| t.registers[:file_system] = VirtualFileSystem.new } .render(assigns) diff --git a/lib/liquid/environment.rb b/lib/liquid/environment.rb index 59719c4ad..100bd0bbd 100644 --- a/lib/liquid/environment.rb +++ b/lib/liquid/environment.rb @@ -34,7 +34,7 @@ class << self # @param file_system The default file system that is used # to load templates from. # @param error_mode [Symbol] The default error mode for all templates - # (either :rigid, :strict, :warn, or :lax). + # (either :strict2, :strict, :warn, or :lax). # @param exception_renderer [Proc] The exception renderer that is used to # render exceptions. # @yieldparam environment [Environment] The environment instance that is being built. diff --git a/lib/liquid/parse_context.rb b/lib/liquid/parse_context.rb index 476123393..855acc64e 100644 --- a/lib/liquid/parse_context.rb +++ b/lib/liquid/parse_context.rb @@ -55,15 +55,15 @@ def safe_parse_expression(parser) end def parse_expression(markup, safe: false) - if !safe && @error_mode == :rigid + if !safe && @error_mode == :strict2 # parse_expression is a widely used API. To maintain backward - # compatibility while raising awareness about rigid parser standards, + # compatibility while raising awareness about strict2 parser standards, # the safe flag supports API users make a deliberate decision. # - # In rigid mode, markup MUST come from a string returned by the parser + # In strict2 mode, markup MUST come from a string returned by the parser # (e.g., parser.expression). We're not calling the parser here to # prevent redundant parser overhead. - raise Liquid::InternalError, "unsafe parse_expression cannot be used in rigid mode" + raise Liquid::InternalError, "unsafe parse_expression cannot be used in strict2 mode" end Expression.parse(markup, @string_scanner, @expression_cache) diff --git a/lib/liquid/parser_switching.rb b/lib/liquid/parser_switching.rb index 7c662e928..e419dc999 100644 --- a/lib/liquid/parser_switching.rb +++ b/lib/liquid/parser_switching.rb @@ -7,16 +7,19 @@ module ParserSwitching # It's basically doing the same thing the {#parse_with_selected_parser}, # except this will try the strict parser regardless of the error mode, # and fall back to the lax parser if the error mode is lax or warn, - # except when in rigid mode where it uses the rigid parser. + # except when in strict2 mode where it uses the strict2 parser. # # @deprecated Use {#parse_with_selected_parser} instead. def strict_parse_with_error_mode_fallback(markup) - return rigid_parse_with_error_context(markup) if rigid_mode? + return strict2_parse_with_error_context(markup) if strict2_mode? strict_parse_with_error_context(markup) rescue SyntaxError => e case parse_context.error_mode when :rigid + rigid_warn + raise + when :strict2 raise when :strict raise @@ -28,12 +31,13 @@ def strict_parse_with_error_mode_fallback(markup) 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 :rigid then rigid_warn && strict2_parse_with_error_context(markup) + when :strict2 then strict2_parse_with_error_context(markup) + when :strict then strict_parse_with_error_context(markup) + when :lax then lax_parse(markup) when :warn begin - rigid_parse_with_error_context(markup) + strict2_parse_with_error_context(markup) rescue SyntaxError => e parse_context.warnings << e lax_parse(markup) @@ -41,14 +45,18 @@ def parse_with_selected_parser(markup) end end - def rigid_mode? - parse_context.error_mode == :rigid + def strict2_mode? + parse_context.error_mode == :strict2 || parse_context.error_mode == :rigid end private - def rigid_parse_with_error_context(markup) - rigid_parse(markup) + def rigid_warn + Deprecations.warn(':rigid', ':strict2') + end + + def strict2_parse_with_error_context(markup) + strict2_parse(markup) rescue SyntaxError => e e.line_number = line_number e.markup_context = markup_context(markup) diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 926e4107a..b32ea1ae7 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -86,7 +86,7 @@ def render_to_output_buffer(context, output) private - def rigid_parse(markup) + def strict2_parse(markup) parser = @parse_context.new_parser(markup) @left = safe_parse_expression(parser) parser.consume(:end_of_string) @@ -107,14 +107,14 @@ def lax_parse(markup) def record_when_condition(markup) body = new_body - if rigid_mode? - parse_rigid_when(markup, body) + if strict2_mode? + parse_strict2_when(markup, body) else parse_lax_when(markup, body) end end - def parse_rigid_when(markup, body) + def parse_strict2_when(markup, body) parser = @parse_context.new_parser(markup) loop do diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb index 7e17a9662..b7d3069c8 100644 --- a/lib/liquid/tags/cycle.rb +++ b/lib/liquid/tags/cycle.rb @@ -56,7 +56,7 @@ def render_to_output_buffer(context, output) private # cycle [name:] expression(, expression)* - def rigid_parse(markup) + def strict2_parse(markup) p = @parse_context.new_parser(markup) @variables = [] diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb index da06d64ad..cbea85bcb 100644 --- a/lib/liquid/tags/for.rb +++ b/lib/liquid/tags/for.rb @@ -111,7 +111,7 @@ def strict_parse(markup) private - def rigid_parse(markup) + def strict2_parse(markup) strict_parse(markup) end diff --git a/lib/liquid/tags/if.rb b/lib/liquid/tags/if.rb index e25d62505..c423c1e84 100644 --- a/lib/liquid/tags/if.rb +++ b/lib/liquid/tags/if.rb @@ -66,7 +66,7 @@ def render_to_output_buffer(context, output) private - def rigid_parse(markup) + def strict2_parse(markup) strict_parse(markup) end diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb index 5b2aaa432..969482d49 100644 --- a/lib/liquid/tags/include.rb +++ b/lib/liquid/tags/include.rb @@ -84,7 +84,7 @@ def render_to_output_buffer(context, output) alias_method :parse_context, :options private :parse_context - def rigid_parse(markup) + def strict2_parse(markup) p = @parse_context.new_parser(markup) @template_name_expr = safe_parse_expression(p) diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb index 4d29e420e..6e1559cc2 100644 --- a/lib/liquid/tags/render.rb +++ b/lib/liquid/tags/render.rb @@ -85,10 +85,10 @@ def render_tag(context, output) end # render (string) (with|for expression)? (as id)? (key: value)* - def rigid_parse(markup) + def strict2_parse(markup) p = @parse_context.new_parser(markup) - @template_name_expr = parse_expression(rigid_template_name(p), safe: true) + @template_name_expr = parse_expression(strict2_template_name(p), safe: true) with_or_for = p.id?("for") || p.id?("with") @variable_name_expr = safe_parse_expression(p) if with_or_for @alias_name = p.consume(:id) if p.id?("as") @@ -107,7 +107,7 @@ def rigid_parse(markup) p.consume(:end_of_string) end - def rigid_template_name(p) + def strict2_template_name(p) p.consume(:string) end diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb index 7348b5d91..b69f91486 100644 --- a/lib/liquid/tags/table_row.rb +++ b/lib/liquid/tags/table_row.rb @@ -34,7 +34,7 @@ def initialize(tag_name, markup, options) parse_with_selected_parser(markup) end - def rigid_parse(markup) + def strict2_parse(markup) p = @parse_context.new_parser(markup) @variable_name = p.consume(:id) diff --git a/lib/liquid/template.rb b/lib/liquid/template.rb index 5d349abd6..b007765ce 100644 --- a/lib/liquid/template.rb +++ b/lib/liquid/template.rb @@ -25,7 +25,7 @@ class << self # :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 enforces correct syntax for most tags - # :rigid enforces correct syntax for all tags + # :strict2 enforces correct syntax for all tags def error_mode=(mode) Deprecations.warn("Template.error_mode=", "Environment#error_mode=") Environment.default.error_mode = mode diff --git a/lib/liquid/variable.rb b/lib/liquid/variable.rb index 3fd947a7d..6b5fb412b 100644 --- a/lib/liquid/variable.rb +++ b/lib/liquid/variable.rb @@ -74,14 +74,14 @@ def strict_parse(markup) p.consume(:end_of_string) end - def rigid_parse(markup) + def strict2_parse(markup) @filters = [] p = @parse_context.new_parser(markup) return if p.look(:end_of_string) @name = parse_context.safe_parse_expression(p) - @filters << rigid_parse_filter_expressions(p) while p.consume?(:pipe) + @filters << strict2_parse_filter_expressions(p) while p.consume?(:pipe) p.consume(:end_of_string) end @@ -156,7 +156,7 @@ def lax_parse_filter_expressions(filter_name, unparsed_args) # argument = (positional_argument | keyword_argument) # positional_argument = expression # keyword_argument = id ":" expression - def rigid_parse_filter_expressions(p) + def strict2_parse_filter_expressions(p) filtername = p.consume(:id) filter_args = [] keyword_args = {} diff --git a/test/integration/tags/cycle_tag_test.rb b/test/integration/tags/cycle_tag_test.rb index f3f865f08..dfb5984d8 100644 --- a/test/integration/tags/cycle_tag_test.rb +++ b/test/integration/tags/cycle_tag_test.rb @@ -101,7 +101,7 @@ def test_cycle_tag_with_error_mode assert_template_result("a", template2) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error1 = assert_raises(Liquid::SyntaxError) { Template.parse(template1) } error2 = assert_raises(Liquid::SyntaxError) { Template.parse(template2) } @@ -129,7 +129,7 @@ def test_cycle_with_trailing_elements assert_template_result("N", template5) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error1 = assert_raises(Liquid::SyntaxError) { Template.parse(template1) } error2 = assert_raises(Liquid::SyntaxError) { Template.parse(template2) } error3 = assert_raises(Liquid::SyntaxError) { Template.parse(template3) } @@ -157,7 +157,7 @@ def test_cycle_name_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end @@ -174,7 +174,7 @@ def test_cycle_variable_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb index 3ccd17794..44c0dcda7 100644 --- a/test/integration/tags/include_tag_test.rb +++ b/test/integration/tags/include_tag_test.rb @@ -204,7 +204,7 @@ def test_dynamically_choosen_template ) end - def test_rigid_parsing_errors + def test_strict2_parsing_errors with_error_modes(:lax, :strict) do assert_template_result( 'hello value1 value2', @@ -213,7 +213,7 @@ def test_rigid_parsing_errors ) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_syntax_error( '{% include "snippet" !!! arg1: "value1" ~~~ arg2: "value2" %}', ) @@ -408,7 +408,7 @@ def test_include_template_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end @@ -421,7 +421,7 @@ def test_include_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end @@ -434,7 +434,7 @@ def test_include_attribute_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb index d6453fb51..0bd09d088 100644 --- a/test/integration/tags/render_tag_test.rb +++ b/test/integration/tags/render_tag_test.rb @@ -105,7 +105,7 @@ def test_dynamically_choosen_templates_are_not_allowed assert_syntax_error("{% assign name = 'snippet' %}{% render name %}") end - def test_rigid_parsing_errors + def test_strict2_parsing_errors with_error_modes(:lax, :strict) do assert_template_result( 'hello value1 value2', @@ -114,7 +114,7 @@ def test_rigid_parsing_errors ) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_syntax_error( '{% render "snippet" !!! arg1: "value1" ~~~ arg2: "value2" %}', ) @@ -322,7 +322,7 @@ def test_render_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end @@ -335,7 +335,7 @@ def test_render_attribute_with_invalid_expression refute_nil(Template.parse(template)) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end diff --git a/test/integration/tags/table_row_test.rb b/test/integration/tags/table_row_test.rb index 44cdcdd99..45628ce9d 100644 --- a/test/integration/tags/table_row_test.rb +++ b/test/integration/tags/table_row_test.rb @@ -259,7 +259,7 @@ def test_table_row_does_not_leak_interrupts ) end - def test_tablerow_with_cols_attribute_in_rigid_mode + def test_tablerow_with_cols_attribute_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..6) cols: 3 %}{{ i }}{% endtablerow %} LIQUID @@ -270,12 +270,12 @@ def test_tablerow_with_cols_attribute_in_rigid_mode 456 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_with_limit_attribute_in_rigid_mode + def test_tablerow_with_limit_attribute_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..10) limit: 3 %}{{ i }}{% endtablerow %} LIQUID @@ -285,12 +285,12 @@ def test_tablerow_with_limit_attribute_in_rigid_mode 123 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_with_offset_attribute_in_rigid_mode + def test_tablerow_with_offset_attribute_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..5) offset: 2 %}{{ i }}{% endtablerow %} LIQUID @@ -300,12 +300,12 @@ def test_tablerow_with_offset_attribute_in_rigid_mode 345 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_with_range_attribute_in_rigid_mode + def test_tablerow_with_range_attribute_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..3) range: (1..10) %}{{ i }}{% endtablerow %} LIQUID @@ -315,12 +315,12 @@ def test_tablerow_with_range_attribute_in_rigid_mode 123 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_with_multiple_attributes_in_rigid_mode + def test_tablerow_with_multiple_attributes_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..10) cols: 2, limit: 4, offset: 1 %}{{ i }}{% endtablerow %} LIQUID @@ -331,12 +331,12 @@ def test_tablerow_with_multiple_attributes_in_rigid_mode 45 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_with_variable_collection_in_rigid_mode + def test_tablerow_with_variable_collection_in_strict2_mode template = <<~LIQUID.chomp {% tablerow n in numbers cols: 2 %}{{ n }}{% endtablerow %} LIQUID @@ -347,12 +347,12 @@ def test_tablerow_with_variable_collection_in_rigid_mode 34 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template, { 'numbers' => [1, 2, 3, 4] }) end end - def test_tablerow_with_dotted_access_in_rigid_mode + def test_tablerow_with_dotted_access_in_strict2_mode template = <<~LIQUID.chomp {% tablerow n in obj.numbers cols: 2 %}{{ n }}{% endtablerow %} LIQUID @@ -363,12 +363,12 @@ def test_tablerow_with_dotted_access_in_rigid_mode 34 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template, { 'obj' => { 'numbers' => [1, 2, 3, 4] } }) end end - def test_tablerow_with_bracketed_access_in_rigid_mode + def test_tablerow_with_bracketed_access_in_strict2_mode template = <<~LIQUID.chomp {% tablerow n in obj["numbers"] cols: 2 %}{{ n }}{% endtablerow %} LIQUID @@ -378,12 +378,12 @@ def test_tablerow_with_bracketed_access_in_rigid_mode 1020 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template, { 'obj' => { 'numbers' => [10, 20] } }) end end - def test_tablerow_without_attributes_in_rigid_mode + def test_tablerow_without_attributes_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in (1..3) %}{{ i }}{% endtablerow %} LIQUID @@ -393,30 +393,30 @@ def test_tablerow_without_attributes_in_rigid_mode 123 OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template) end end - def test_tablerow_without_in_keyword_in_rigid_mode + def test_tablerow_without_in_keyword_in_strict2_mode template = '{% tablerow i (1..10) %}{{ i }}{% endtablerow %}' - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(SyntaxError) { Template.parse(template) } assert_equal("Liquid syntax error: For loops require an 'in' clause in \"i (1..10)\"", error.message) end end - def test_tablerow_with_multiple_invalid_attributes_reports_first_in_rigid_mode + def test_tablerow_with_multiple_invalid_attributes_reports_first_in_strict2_mode template = '{% tablerow i in (1..10) invalid1: 5, invalid2: 10 %}{{ i }}{% endtablerow %}' - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(SyntaxError) { Template.parse(template) } assert_equal("Liquid syntax error: Invalid attribute 'invalid1' in tablerow loop. Valid attributes are cols, limit, offset, and range in \"i in (1..10) invalid1: 5, invalid2: 10\"", error.message) end end - def test_tablerow_with_empty_collection_in_rigid_mode + def test_tablerow_with_empty_collection_in_strict2_mode template = <<~LIQUID.chomp {% tablerow i in empty_array cols: 2 %}{{ i }}{% endtablerow %} LIQUID @@ -426,12 +426,12 @@ def test_tablerow_with_empty_collection_in_rigid_mode OUTPUT - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result(expected, template, { 'empty_array' => [] }) end end - def test_tablerow_with_invalid_attribute_strict_vs_rigid + def test_tablerow_with_invalid_attribute_strict_vs_strict2 template = '{% tablerow i in (1..5) invalid_attr: 10 %}{{ i }}{% endtablerow %}' expected = <<~OUTPUT @@ -443,13 +443,13 @@ def test_tablerow_with_invalid_attribute_strict_vs_rigid assert_template_result(expected, template) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(SyntaxError) { Template.parse(template) } assert_match(/Invalid attribute 'invalid_attr'/, error.message) end end - def test_tablerow_with_invalid_expression_strict_vs_rigid + def test_tablerow_with_invalid_expression_strict_vs_strict2 template = '{% tablerow i in (1..5) limit: foo=>bar %}{{ i }}{% endtablerow %}' with_error_modes(:lax, :strict) do @@ -460,7 +460,7 @@ def test_tablerow_with_invalid_expression_strict_vs_rigid assert_template_result(expected, template) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) end diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb index f0f2bc409..e272b96b9 100644 --- a/test/integration/variable_test.rb +++ b/test/integration/variable_test.rb @@ -218,7 +218,7 @@ def test_filter_with_single_trailing_comma assert_match(/is not a valid expression/, error.message) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result('helloworld', template) end end @@ -231,7 +231,7 @@ def test_multiple_filters_with_trailing_commas assert_match(/is not a valid expression/, error.message) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result('hello12', template) end end @@ -244,7 +244,7 @@ def test_filter_with_colon_but_no_arguments assert_match(/is not a valid expression/, error.message) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result('TEST', template) end end @@ -257,7 +257,7 @@ def test_filter_chain_with_colon_no_args assert_match(/is not a valid expression/, error.message) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result('TESTX', template) end end @@ -270,7 +270,7 @@ def test_combining_trailing_comma_and_empty_args assert_match(/is not a valid expression/, error.message) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do assert_template_result('TESTX', template) end end diff --git a/test/unit/condition_unit_test.rb b/test/unit/condition_unit_test.rb index f5206ff53..eb466f7a9 100644 --- a/test/unit/condition_unit_test.rb +++ b/test/unit/condition_unit_test.rb @@ -176,19 +176,19 @@ def test_parse_expression_in_strict_mode assert_equal(['title'], result.lookups) end - def test_parse_expression_in_rigid_mode_raises_internal_error - environment = Environment.build(error_mode: :rigid) + def test_parse_expression_in_strict2_mode_raises_internal_error + environment = Environment.build(error_mode: :strict2) parse_context = ParseContext.new(environment: environment) error = assert_raises(Liquid::InternalError) do Condition.parse_expression(parse_context, 'product.title') end - assert_match(/unsafe parse_expression cannot be used in rigid mode/, error.message) + assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message) end - def test_parse_expression_with_safe_true_in_rigid_mode - environment = Environment.build(error_mode: :rigid) + def test_parse_expression_with_safe_true_in_strict2_mode + environment = Environment.build(error_mode: :strict2) parse_context = ParseContext.new(environment: environment) result = Condition.parse_expression(parse_context, 'product.title', safe: true) diff --git a/test/unit/parse_context_unit_test.rb b/test/unit/parse_context_unit_test.rb index b1c8fe470..f53cd1148 100644 --- a/test/unit/parse_context_unit_test.rb +++ b/test/unit/parse_context_unit_test.rb @@ -9,32 +9,32 @@ def test_safe_parse_expression_with_variable_lookup parser_strict = strict_parse_context.new_parser('product.title') result_strict = strict_parse_context.safe_parse_expression(parser_strict) - parser_rigid = rigid_parse_context.new_parser('product.title') - result_rigid = rigid_parse_context.safe_parse_expression(parser_rigid) + parser_strict2 = strict2_parse_context.new_parser('product.title') + result_strict2 = strict2_parse_context.safe_parse_expression(parser_strict2) assert_instance_of(VariableLookup, result_strict) assert_equal('product', result_strict.name) assert_equal(['title'], result_strict.lookups) - assert_instance_of(VariableLookup, result_rigid) - assert_equal('product', result_rigid.name) - assert_equal(['title'], result_rigid.lookups) + assert_instance_of(VariableLookup, result_strict2) + assert_equal('product', result_strict2.name) + assert_equal(['title'], result_strict2.lookups) end def test_safe_parse_expression_raises_syntax_error_for_invalid_expression parser_strict = strict_parse_context.new_parser('') - parser_rigid = rigid_parse_context.new_parser('') + parser_strict2 = strict2_parse_context.new_parser('') error_strict = assert_raises(Liquid::SyntaxError) do strict_parse_context.safe_parse_expression(parser_strict) end assert_match(/is not a valid expression/, error_strict.message) - error_rigid = assert_raises(Liquid::SyntaxError) do - rigid_parse_context.safe_parse_expression(parser_rigid) + error_strict2 = assert_raises(Liquid::SyntaxError) do + strict2_parse_context.safe_parse_expression(parser_strict2) end - assert_match(/is not a valid expression/, error_rigid.message) + assert_match(/is not a valid expression/, error_strict2.message) end def test_parse_expression_with_variable_lookup @@ -45,10 +45,10 @@ def test_parse_expression_with_variable_lookup assert_equal(['title'], result_strict.lookups) error = assert_raises(Liquid::InternalError) do - rigid_parse_context.parse_expression('product.title') + strict2_parse_context.parse_expression('product.title') end - assert_match(/unsafe parse_expression cannot be used in rigid mode/, error.message) + assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message) end def test_parse_expression_with_safe_true @@ -58,11 +58,11 @@ def test_parse_expression_with_safe_true assert_equal('product', result_strict.name) assert_equal(['title'], result_strict.lookups) - result_rigid = rigid_parse_context.parse_expression('product.title', safe: true) + result_strict2 = strict2_parse_context.parse_expression('product.title', safe: true) - assert_instance_of(VariableLookup, result_rigid) - assert_equal('product', result_rigid.name) - assert_equal(['title'], result_rigid.lookups) + assert_instance_of(VariableLookup, result_strict2) + assert_equal('product', result_strict2.name) + assert_equal(['title'], result_strict2.lookups) end def test_parse_expression_with_empty_string @@ -70,40 +70,40 @@ def test_parse_expression_with_empty_string assert_nil(result_strict) error = assert_raises(Liquid::InternalError) do - rigid_parse_context.parse_expression('') + strict2_parse_context.parse_expression('') end - assert_match(/unsafe parse_expression cannot be used in rigid mode/, error.message) + assert_match(/unsafe parse_expression cannot be used in strict2 mode/, error.message) end def test_parse_expression_with_empty_string_and_safe_true result_strict = strict_parse_context.parse_expression('', safe: true) assert_nil(result_strict) - result_rigid = rigid_parse_context.parse_expression('', safe: true) - assert_nil(result_rigid) + result_strict2 = strict2_parse_context.parse_expression('', safe: true) + assert_nil(result_strict2) end def test_safe_parse_expression_advances_parser_pointer - parser = rigid_parse_context.new_parser('foo, bar') + parser = strict2_parse_context.new_parser('foo, bar') # safe_parse_expression consumes "foo" - first_result = rigid_parse_context.safe_parse_expression(parser) + first_result = strict2_parse_context.safe_parse_expression(parser) assert_instance_of(VariableLookup, first_result) assert_equal('foo', first_result.name) parser.consume(:comma) # safe_parse_expression consumes "bar" - second_result = rigid_parse_context.safe_parse_expression(parser) + second_result = strict2_parse_context.safe_parse_expression(parser) assert_instance_of(VariableLookup, second_result) assert_equal('bar', second_result.name) parser.consume(:end_of_string) end - def test_parse_expression_with_whitespace_in_rigid_mode - result = rigid_parse_context.parse_expression(' ', safe: true) + def test_parse_expression_with_whitespace_in_strict2_mode + result = strict2_parse_context.parse_expression(' ', safe: true) assert_nil(result) end @@ -115,9 +115,9 @@ def strict_parse_context ) end - def rigid_parse_context - @rigid_parse_context ||= ParseContext.new( - environment: Environment.build(error_mode: :rigid), + def strict2_parse_context + @strict2_parse_context ||= ParseContext.new( + environment: Environment.build(error_mode: :strict2), ) end end diff --git a/test/unit/partial_cache_unit_test.rb b/test/unit/partial_cache_unit_test.rb index 31790b0d8..623241b40 100644 --- a/test/unit/partial_cache_unit_test.rb +++ b/test/unit/partial_cache_unit_test.rb @@ -184,7 +184,7 @@ def test_includes_error_mode_into_template_cache }, ) - [:lax, :warn, :strict, :rigid].each do |error_mode| + [:lax, :warn, :strict, :strict2].each do |error_mode| Liquid::PartialCache.load( 'my_partial', context: context, @@ -193,7 +193,7 @@ def test_includes_error_mode_into_template_cache end assert_equal( - ["my_partial:lax", "my_partial:warn", "my_partial:strict", "my_partial:rigid"], + ["my_partial:lax", "my_partial:warn", "my_partial:strict", "my_partial:strict2"], context.registers[:cached_partials].keys, ) end diff --git a/test/unit/tags/case_tag_unit_test.rb b/test/unit/tags/case_tag_unit_test.rb index 5e181f3eb..9d7d34a39 100644 --- a/test/unit/tags/case_tag_unit_test.rb +++ b/test/unit/tags/case_tag_unit_test.rb @@ -24,7 +24,7 @@ def test_case_with_trailing_element assert_template_result("one", template) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Expected end_of_string but found/, error.message) @@ -45,7 +45,7 @@ def test_case_when_with_trailing_element assert_template_result("one", template) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Expected end_of_string but found/, error.message) @@ -62,7 +62,7 @@ def test_case_when_with_comma {%- endcase -%} LIQUID - with_error_modes(:lax, :strict, :rigid) do + with_error_modes(:lax, :strict, :strict2) do assert_template_result("one", template) end end @@ -77,7 +77,7 @@ def test_case_when_with_or {%- endcase -%} LIQUID - with_error_modes(:lax, :strict, :rigid) do + with_error_modes(:lax, :strict, :strict2) do assert_template_result("one", template) end end @@ -97,7 +97,7 @@ def test_case_with_invalid_expression assert_template_result("one", template, assigns) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) @@ -119,7 +119,7 @@ def test_case_when_with_invalid_expression assert_template_result("one", template, assigns) end - with_error_modes(:rigid) do + with_error_modes(:strict2) do error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } assert_match(/Unexpected character =/, error.message) diff --git a/test/unit/variable_unit_test.rb b/test/unit/variable_unit_test.rb index cb3a6d8ce..03f5862aa 100644 --- a/test/unit/variable_unit_test.rb +++ b/test/unit/variable_unit_test.rb @@ -161,8 +161,8 @@ def test_strict_filter_argument_parsing end end - def test_rigid_filter_argument_parsing - with_error_modes(:rigid) do + def test_strict2_filter_argument_parsing + with_error_modes(:strict2) do # optional colon var = create_variable(%(n | f1 | f2:)) assert_equal([['f1', []], ['f2', []]], var.filters) From e2699e00bedafe8796cdfd07882412bc82d5c4ef Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 19 Nov 2025 13:34:13 +0100 Subject: [PATCH 3/4] Bump to 5.11 --- History.md | 10 ++++------ lib/liquid/version.rb | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/History.md b/History.md index 183a8ab93..9fab79cf8 100644 --- a/History.md +++ b/History.md @@ -1,13 +1,11 @@ # Liquid Change Log +## 5.11.0 +* Revert the Inline Snippets tag (#2001), treats its inclusion in the latest Liquid release as a bug, and aims to allow feedback on RFC#1916 for better support Liquid developers [Guilherme Carreiro] +* Rename `:rigid` error mode to `:strict2` and display a warning when users use the `:rigid` mode [Guilherme Carreiro] + ## 5.10.0 * Introduce support for Inline Snippets [Julia Boutin] - ``` - {%- snippet snowdevil -%} - Snowdevil - {%- endsnippet -%} - {% render snowdevil %} - ``` ## 5.9.0 * Introduce `:rigid` error mode for stricter, safer parsing of all tags [CP Clermont, Guilherme Carreiro] diff --git a/lib/liquid/version.rb b/lib/liquid/version.rb index c16430f2f..c396a3db7 100644 --- a/lib/liquid/version.rb +++ b/lib/liquid/version.rb @@ -2,5 +2,5 @@ # frozen_string_literal: true module Liquid - VERSION = "5.10.0" + VERSION = "5.11.0" end From 55f9cc04fcedaf19bf2c71605fbbc52b81d9eab3 Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Wed, 19 Nov 2025 17:57:57 +0100 Subject: [PATCH 4/4] Fix formatting and wording in change log --- History.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 9fab79cf8..94feaabfd 100644 --- a/History.md +++ b/History.md @@ -1,8 +1,8 @@ # Liquid Change Log ## 5.11.0 -* Revert the Inline Snippets tag (#2001), treats its inclusion in the latest Liquid release as a bug, and aims to allow feedback on RFC#1916 for better support Liquid developers [Guilherme Carreiro] -* Rename `:rigid` error mode to `:strict2` and display a warning when users use the `:rigid` mode [Guilherme Carreiro] +* Revert the Inline Snippets tag (#2001), treat its inclusion in the latest Liquid release as a bug, and allow for feedback on RFC#1916 to better support Liquid developers [Guilherme Carreiro] +* Rename the `:rigid` error mode to `:strict2` and display a warning when users attempt to use the `:rigid` mode [Guilherme Carreiro] ## 5.10.0 * Introduce support for Inline Snippets [Julia Boutin]