mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 03:10:39 -07:00
Merge upstream branch 'master' into this branch
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
name: Liquid
|
name: Liquid
|
||||||
on: [push]
|
on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module Liquid
|
|||||||
@body = new_body
|
@body = new_body
|
||||||
while parse_body(@body, tokens)
|
while parse_body(@body, tokens)
|
||||||
end
|
end
|
||||||
|
@body.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
# For backwards compatibility
|
# For backwards compatibility
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse(tokenizer, parse_context, &block)
|
def parse(tokenizer, parse_context, &block)
|
||||||
|
raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
|
||||||
|
|
||||||
parse_context.line_number = tokenizer.line_number
|
parse_context.line_number = tokenizer.line_number
|
||||||
|
|
||||||
if tokenizer.for_liquid_tag
|
if tokenizer.for_liquid_tag
|
||||||
@@ -28,6 +30,11 @@ module Liquid
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def freeze
|
||||||
|
@nodelist.freeze
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
private def parse_for_liquid_tag(tokenizer, parse_context)
|
private def parse_for_liquid_tag(tokenizer, parse_context)
|
||||||
while (token = tokenizer.shift)
|
while (token = tokenizer.shift)
|
||||||
unless token.empty? || token =~ WhitespaceOrNothing
|
unless token.empty? || token =~ WhitespaceOrNothing
|
||||||
@@ -192,6 +199,8 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_to_output_buffer(context, output)
|
def render_to_output_buffer(context, output)
|
||||||
|
freeze unless frozen?
|
||||||
|
|
||||||
context.resource_limits.increment_render_score(@nodelist.length)
|
context.resource_limits.increment_render_score(@nodelist.length)
|
||||||
|
|
||||||
idx = 0
|
idx = 0
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ module Liquid
|
|||||||
@@operators
|
@@operators
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.parse_expression(markup)
|
def self.parse_expression(parse_context, markup)
|
||||||
@@method_literals[markup] || Expression.parse(markup)
|
@@method_literals[markup] || parse_context.parse_expression(markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :attachment, :child_condition
|
attr_reader :attachment, :child_condition
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ module Liquid
|
|||||||
def parse(tokenizer, parse_context)
|
def parse(tokenizer, parse_context)
|
||||||
while parse_body(tokenizer)
|
while parse_body(tokenizer)
|
||||||
end
|
end
|
||||||
|
@body.freeze
|
||||||
rescue SyntaxError => e
|
rescue SyntaxError => e
|
||||||
e.line_number ||= parse_context.line_number
|
e.line_number ||= parse_context.line_number
|
||||||
raise
|
raise
|
||||||
|
|||||||
+19
-16
@@ -10,25 +10,28 @@ module Liquid
|
|||||||
'empty' => ''
|
'empty' => ''
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
|
SINGLE_QUOTED_STRING = /\A\s*'(.*)'\s*\z/m
|
||||||
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m
|
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
|
||||||
INTEGERS_REGEX = /\A(-?\d+)\z/
|
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
|
||||||
FLOATS_REGEX = /\A(-?\d[\d\.]+)\z/
|
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
|
||||||
RANGES_REGEX = /\A\((\S+)\.\.(\S+)\)\z/
|
RANGES_REGEX = /\A\s*\(\s*(\S+)\s*\.\.\s*(\S+)\s*\)\s*\z/
|
||||||
|
|
||||||
def self.parse(markup)
|
def self.parse(markup)
|
||||||
if LITERALS.key?(markup)
|
case markup
|
||||||
LITERALS[markup]
|
when nil
|
||||||
|
nil
|
||||||
|
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
||||||
|
Regexp.last_match(1)
|
||||||
|
when INTEGERS_REGEX
|
||||||
|
Regexp.last_match(1).to_i
|
||||||
|
when RANGES_REGEX
|
||||||
|
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
|
||||||
|
when FLOATS_REGEX
|
||||||
|
Regexp.last_match(1).to_f
|
||||||
else
|
else
|
||||||
case markup
|
markup = markup.strip
|
||||||
when SINGLE_QUOTED_STRING, DOUBLE_QUOTED_STRING
|
if LITERALS.key?(markup)
|
||||||
Regexp.last_match(1)
|
LITERALS[markup]
|
||||||
when INTEGERS_REGEX
|
|
||||||
Regexp.last_match(1).to_i
|
|
||||||
when RANGES_REGEX
|
|
||||||
RangeLookup.parse(Regexp.last_match(1), Regexp.last_match(2))
|
|
||||||
when FLOATS_REGEX
|
|
||||||
Regexp.last_match(1).to_f
|
|
||||||
else
|
else
|
||||||
VariableLookup.parse(markup)
|
VariableLookup.parse(markup)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ module Liquid
|
|||||||
Liquid::BlockBody.new
|
Liquid::BlockBody.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_expression(markup)
|
||||||
|
Expression.parse(markup)
|
||||||
|
end
|
||||||
|
|
||||||
def partial=(value)
|
def partial=(value)
|
||||||
@partial = value
|
@partial = value
|
||||||
@options = value ? partial_options : @template_options
|
@options = value ? partial_options : @template_options
|
||||||
|
|||||||
@@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
module Liquid
|
module Liquid
|
||||||
module ParserSwitching
|
module ParserSwitching
|
||||||
|
def strict_parse_with_error_mode_fallback(markup)
|
||||||
|
strict_parse_with_error_context(markup)
|
||||||
|
rescue SyntaxError => e
|
||||||
|
case parse_context.error_mode
|
||||||
|
when :strict
|
||||||
|
raise
|
||||||
|
when :warn
|
||||||
|
parse_context.warnings << e
|
||||||
|
end
|
||||||
|
lax_parse(markup)
|
||||||
|
end
|
||||||
|
|
||||||
def parse_with_selected_parser(markup)
|
def parse_with_selected_parser(markup)
|
||||||
case parse_context.error_mode
|
case parse_context.error_mode
|
||||||
when :strict then strict_parse_with_error_context(markup)
|
when :strict then strict_parse_with_error_context(markup)
|
||||||
|
|||||||
@@ -55,5 +55,11 @@ module Liquid
|
|||||||
def blank?
|
def blank?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_expression(markup)
|
||||||
|
parse_context.parse_expression(markup)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module Liquid
|
|||||||
@blocks = []
|
@blocks = []
|
||||||
|
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@left = Expression.parse(Regexp.last_match(1))
|
@left = parse_expression(Regexp.last_match(1))
|
||||||
else
|
else
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
raise SyntaxError, options[:locale].t("errors.syntax.case")
|
||||||
end
|
end
|
||||||
@@ -21,8 +21,9 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
body = new_body
|
body = new_body
|
||||||
body = @blocks.last.attachment while parse_body(body, tokens)
|
body = @blocks.last.attachment while parse_body(body, tokens)
|
||||||
if blank?
|
@blocks.each do |condition|
|
||||||
@blocks.each { |condition| condition.attachment.remove_blank_strings }
|
condition.attachment.remove_blank_strings if blank?
|
||||||
|
condition.attachment.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ module Liquid
|
|||||||
|
|
||||||
markup = Regexp.last_match(2)
|
markup = Regexp.last_match(2)
|
||||||
|
|
||||||
block = Condition.new(@left, '==', Condition.parse_expression(Regexp.last_match(1)))
|
block = Condition.new(@left, '==', Condition.parse_expression(parse_context, Regexp.last_match(1)))
|
||||||
block.attach(body)
|
block.attach(body)
|
||||||
@blocks << block
|
@blocks << block
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module Liquid
|
|||||||
case markup
|
case markup
|
||||||
when NamedSyntax
|
when NamedSyntax
|
||||||
@variables = variables_from_string(Regexp.last_match(2))
|
@variables = variables_from_string(Regexp.last_match(2))
|
||||||
@name = Expression.parse(Regexp.last_match(1))
|
@name = parse_expression(Regexp.last_match(1))
|
||||||
when SimpleSyntax
|
when SimpleSyntax
|
||||||
@variables = variables_from_string(markup)
|
@variables = variables_from_string(markup)
|
||||||
@name = @variables.to_s
|
@name = @variables.to_s
|
||||||
@@ -61,7 +61,7 @@ module Liquid
|
|||||||
def variables_from_string(markup)
|
def variables_from_string(markup)
|
||||||
markup.split(',').collect do |var|
|
markup.split(',').collect do |var|
|
||||||
var =~ /\s*(#{QuotedFragment})\s*/o
|
var =~ /\s*(#{QuotedFragment})\s*/o
|
||||||
Regexp.last_match(1) ? Expression.parse(Regexp.last_match(1)) : nil
|
Regexp.last_match(1) ? parse_expression(Regexp.last_match(1)) : nil
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ module Liquid
|
|||||||
@for_block.remove_blank_strings
|
@for_block.remove_blank_strings
|
||||||
@else_block&.remove_blank_strings
|
@else_block&.remove_blank_strings
|
||||||
end
|
end
|
||||||
|
@for_block.freeze
|
||||||
|
@else_block&.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodelist
|
def nodelist
|
||||||
@@ -97,7 +99,7 @@ module Liquid
|
|||||||
collection_name = Regexp.last_match(2)
|
collection_name = Regexp.last_match(2)
|
||||||
@reversed = !!Regexp.last_match(3)
|
@reversed = !!Regexp.last_match(3)
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
@name = "#{@variable_name}-#{collection_name}"
|
||||||
@collection_name = Expression.parse(collection_name)
|
@collection_name = parse_expression(collection_name)
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
set_attribute(key, value)
|
set_attribute(key, value)
|
||||||
end
|
end
|
||||||
@@ -112,7 +114,7 @@ module Liquid
|
|||||||
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')
|
||||||
|
|
||||||
collection_name = p.expression
|
collection_name = p.expression
|
||||||
@collection_name = Expression.parse(collection_name)
|
@collection_name = parse_expression(collection_name)
|
||||||
|
|
||||||
@name = "#{@variable_name}-#{collection_name}"
|
@name = "#{@variable_name}-#{collection_name}"
|
||||||
@reversed = p.id?('reversed')
|
@reversed = p.id?('reversed')
|
||||||
@@ -198,10 +200,10 @@ module Liquid
|
|||||||
@from = if expr == 'continue'
|
@from = if expr == 'continue'
|
||||||
:continue
|
:continue
|
||||||
else
|
else
|
||||||
Expression.parse(expr)
|
parse_expression(expr)
|
||||||
end
|
end
|
||||||
when 'limit'
|
when 'limit'
|
||||||
@limit = Expression.parse(expr)
|
@limit = parse_expression(expr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ module Liquid
|
|||||||
def parse(tokens)
|
def parse(tokens)
|
||||||
while parse_body(@blocks.last.attachment, tokens)
|
while parse_body(@blocks.last.attachment, tokens)
|
||||||
end
|
end
|
||||||
if blank?
|
@blocks.each do |block|
|
||||||
@blocks.each { |condition| condition.attachment.remove_blank_strings }
|
block.attachment.remove_blank_strings if blank?
|
||||||
|
block.attachment.freeze
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_expression(markup)
|
def parse_expression(markup)
|
||||||
Condition.parse_expression(markup)
|
Condition.parse_expression(parse_context, markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lax_parse(markup)
|
def lax_parse(markup)
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ module Liquid
|
|||||||
variable_name = Regexp.last_match(3)
|
variable_name = Regexp.last_match(3)
|
||||||
|
|
||||||
@alias_name = Regexp.last_match(5)
|
@alias_name = Regexp.last_match(5)
|
||||||
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||||
@template_name_expr = Expression.parse(template_name)
|
@template_name_expr = parse_expression(template_name)
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
|
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = Expression.parse(value)
|
@attributes[key] = parse_expression(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ module Liquid
|
|||||||
variable_name = Regexp.last_match(4)
|
variable_name = Regexp.last_match(4)
|
||||||
|
|
||||||
@alias_name = Regexp.last_match(6)
|
@alias_name = Regexp.last_match(6)
|
||||||
@variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
|
@variable_name_expr = variable_name ? parse_expression(variable_name) : nil
|
||||||
@template_name_expr = Expression.parse(template_name)
|
@template_name_expr = parse_expression(template_name)
|
||||||
@for = (with_or_for == FOR)
|
@for = (with_or_for == FOR)
|
||||||
|
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = Expression.parse(value)
|
@attributes[key] = parse_expression(value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ module Liquid
|
|||||||
super
|
super
|
||||||
if markup =~ Syntax
|
if markup =~ Syntax
|
||||||
@variable_name = Regexp.last_match(1)
|
@variable_name = Regexp.last_match(1)
|
||||||
@collection_name = Expression.parse(Regexp.last_match(2))
|
@collection_name = parse_expression(Regexp.last_match(2))
|
||||||
@attributes = {}
|
@attributes = {}
|
||||||
markup.scan(TagAttributes) do |key, value|
|
markup.scan(TagAttributes) do |key, value|
|
||||||
@attributes[key] = Expression.parse(value)
|
@attributes[key] = parse_expression(value)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
|
raise SyntaxError, options[:locale].t("errors.syntax.table_row")
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ module Liquid
|
|||||||
@parse_context = parse_context
|
@parse_context = parse_context
|
||||||
@line_number = parse_context.line_number
|
@line_number = parse_context.line_number
|
||||||
|
|
||||||
parse_with_selected_parser(markup)
|
strict_parse_with_error_mode_fallback(markup)
|
||||||
end
|
end
|
||||||
|
|
||||||
def raw
|
def raw
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ class AssignTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_expression_with_whitespace_in_square_brackets
|
||||||
|
source = "{% assign r = a[ 'b' ] %}{{ r }}"
|
||||||
|
assert_template_result('result', source, 'a' => { 'b' => 'result' })
|
||||||
|
end
|
||||||
|
|
||||||
def test_assign_score_exceeding_resource_limit
|
def test_assign_score_exceeding_resource_limit
|
||||||
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
|
||||||
t.resource_limits.assign_score_limit = 1
|
t.resource_limits.assign_score_limit = 1
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ExpressionTest < Minitest::Test
|
||||||
|
def test_keyword_literals
|
||||||
|
assert_equal(true, parse_and_eval("true"))
|
||||||
|
assert_equal(true, parse_and_eval(" true "))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_string
|
||||||
|
assert_equal("single quoted", parse_and_eval("'single quoted'"))
|
||||||
|
assert_equal("double quoted", parse_and_eval('"double quoted"'))
|
||||||
|
assert_equal("spaced", parse_and_eval(" 'spaced' "))
|
||||||
|
assert_equal("spaced2", parse_and_eval(' "spaced2" '))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_int
|
||||||
|
assert_equal(123, parse_and_eval("123"))
|
||||||
|
assert_equal(456, parse_and_eval(" 456 "))
|
||||||
|
assert_equal(12, parse_and_eval("012"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_float
|
||||||
|
assert_equal(1.5, parse_and_eval("1.5"))
|
||||||
|
assert_equal(2.5, parse_and_eval(" 2.5 "))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_range
|
||||||
|
assert_equal(1..2, parse_and_eval("(1..2)"))
|
||||||
|
assert_equal(3..4, parse_and_eval(" ( 3 .. 4 ) "))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_and_eval(markup, **assigns)
|
||||||
|
if Liquid::Template.error_mode == :strict
|
||||||
|
p = Liquid::Parser.new(markup)
|
||||||
|
markup = p.expression
|
||||||
|
p.consume(:end_of_string)
|
||||||
|
end
|
||||||
|
expression = Liquid::Expression.parse(markup)
|
||||||
|
context = Liquid::Context.new(assigns)
|
||||||
|
context.evaluate(expression)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -257,9 +257,8 @@ class TemplateTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_nil_value_does_not_raise
|
def test_nil_value_does_not_raise
|
||||||
Liquid::Template.error_mode = :strict
|
t = Template.parse("some{{x}}thing", error_mode: :strict)
|
||||||
t = Template.parse("some{{x}}thing")
|
result = t.render!({ 'x' => nil }, strict_variables: true)
|
||||||
result = t.render!({ 'x' => nil }, strict_variables: true)
|
|
||||||
|
|
||||||
assert_equal(0, t.errors.count)
|
assert_equal(0, t.errors.count)
|
||||||
assert_equal('something', result)
|
assert_equal('something', result)
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ class VariableTest < Minitest::Test
|
|||||||
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully'))
|
assert_equal(' worked wonderfully ', template.render!('test' => 'worked wonderfully'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_expression_with_whitespace_in_square_brackets
|
||||||
|
assert_template_result('result', "{{ a[ 'b' ] }}", 'a' => { 'b' => 'result' })
|
||||||
|
assert_template_result('result', "{{ a[ [ 'b' ] ] }}", 'b' => 'c', 'a' => { 'c' => 'result' })
|
||||||
|
end
|
||||||
|
|
||||||
def test_ignore_unknown
|
def test_ignore_unknown
|
||||||
template = Template.parse(%({{ test }}))
|
template = Template.parse(%({{ test }}))
|
||||||
assert_equal('', template.render!)
|
assert_equal('', template.render!)
|
||||||
|
|||||||
Reference in New Issue
Block a user