From 65a1c167b33a8d8fe28ec657b6108c7f35258873 Mon Sep 17 00:00:00 2001 From: Guilherme Carreiro Date: Thu, 2 Oct 2025 12:51:42 +0200 Subject: [PATCH] Add rigid_parse to `case/when` --- lib/liquid/parser_switching.rb | 4 + lib/liquid/tags/case.rb | 49 +++++++++-- test/unit/tags/case_tag_unit_test.rb | 120 +++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 6 deletions(-) diff --git a/lib/liquid/parser_switching.rb b/lib/liquid/parser_switching.rb index 78b86b23..799250a0 100644 --- a/lib/liquid/parser_switching.rb +++ b/lib/liquid/parser_switching.rb @@ -37,6 +37,10 @@ module Liquid end end + def rigid_mode? + parse_context.error_mode == :rigid + end + private def rigid_parse_with_error_context(markup) diff --git a/lib/liquid/tags/case.rb b/lib/liquid/tags/case.rb index 6b67601f..bdf329a2 100644 --- a/lib/liquid/tags/case.rb +++ b/lib/liquid/tags/case.rb @@ -31,12 +31,7 @@ module Liquid def initialize(tag_name, markup, options) super @blocks = [] - - if markup =~ Syntax - @left = parse_expression(Regexp.last_match(1)) - else - raise SyntaxError, options[:locale].t("errors.syntax.case") - end + parse_with_selected_parser(markup) end def parse(tokens) @@ -91,9 +86,51 @@ module Liquid private + def rigid_parse(markup) + parser = @parse_context.new_parser(markup) + @left = safe_parse_expression(parser) + parser.consume(:end_of_string) + end + + def strict_parse(markup) + lax_parse(markup) + end + + def lax_parse(markup) + if markup =~ Syntax + @left = parse_expression(Regexp.last_match(1)) + else + raise SyntaxError, options[:locale].t("errors.syntax.case") + end + end + def record_when_condition(markup) body = new_body + if rigid_mode? + parse_rigid_when(markup, body) + else + parse_lax_when(markup, body) + end + end + + def parse_rigid_when(markup, body) + parser = @parse_context.new_parser(markup) + + loop do + expr = safe_parse_expression(parser) + block = Condition.new(@left, '==', expr) + block.attach(body) + @blocks << block + + # Temporarily until support :or lexeme. + break unless parser.id?('or') || parser.consume?(:comma) + end + + parser.consume(:end_of_string) + end + + def parse_lax_when(markup, body) while markup unless markup =~ WhenSyntax raise SyntaxError, options[:locale].t("errors.syntax.case_invalid_when") diff --git a/test/unit/tags/case_tag_unit_test.rb b/test/unit/tags/case_tag_unit_test.rb index a94d167f..687cf9d9 100644 --- a/test/unit/tags/case_tag_unit_test.rb +++ b/test/unit/tags/case_tag_unit_test.rb @@ -9,4 +9,124 @@ class CaseTagUnitTest < Minitest::Test template = Liquid::Template.parse('{% case var %}{% when true %}WHEN{% else %}ELSE{% endcase %}') assert_equal(['WHEN', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten) end + + def test_case_with_trailing_element + template = <<~LIQUID + {%- case 1 bar -%} + {%- when 1 -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + + [:lax, :strict].each do |mode| + with_error_mode(mode) { assert_template_result("one", template) } + end + + with_error_mode(:rigid) do + error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } + + assert_match(/Expected end_of_string but found/, error.message) + end + end + + def test_case_when_trailing_element + template = <<~LIQUID + {%- case 1 -%} + {%- when 1 bar -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + + [:lax, :strict].each do |mode| + with_error_mode(mode) { assert_template_result("one", template) } + end + + with_error_mode(:rigid) do + error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } + + assert_match(/Expected end_of_string but found/, error.message) + end + end + + def test_case_when_with_comma + template = <<~LIQUID + {%- case 1 -%} + {%- when 2, 1 -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + + [:lax, :strict, :rigid].each do |mode| + with_error_mode(mode) { assert_template_result("one", template) } + end + end + + def test_case_when_with_or + template = <<~LIQUID + {%- case 1 -%} + {%- when 2 or 1 -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + + [:lax, :strict, :rigid].each do |mode| + with_error_mode(mode) { assert_template_result("one", template) } + end + end + + def test_case_with_invalid_expression + template = <<~LIQUID + {%- case foo=>bar -%} + {%- when 'baz' -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + assigns = { 'foo' => { 'bar' => 'baz' } } + + [:lax, :strict].each do |mode| + with_error_mode(mode) do + assert_template_result("one", template, assigns) + end + end + + with_error_mode(:rigid) do + error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } + + assert_match(/Unexpected character =/, error.message) + end + end + + def test_case_when_with_invalid_expression + template = <<~LIQUID + {%- case 'baz' -%} + {%- when foo=>bar -%} + one + {%- else -%} + two + {%- endcase -%} + LIQUID + assigns = { 'foo' => { 'bar' => 'baz' } } + + [:lax, :strict].each do |mode| + with_error_mode(mode) do + assert_template_result("one", template, assigns) + end + end + + with_error_mode(:rigid) do + error = assert_raises(Liquid::SyntaxError) { Template.parse(template) } + + assert_match(/Unexpected character =/, error.message) + end + end end