mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-12 23:40:45 -07:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
807d45a6b3 | ||
|
|
7a5e45fc47 | ||
|
|
9efca9f718 | ||
|
|
529800f46b | ||
|
|
7b368dffb8 | ||
|
|
742ac3dbf5 | ||
|
|
1954a2655c | ||
|
|
6d81b1b68c | ||
|
|
dfddd8f390 | ||
|
|
95ce7e7fa1 | ||
|
|
197d755e0c | ||
|
|
d0c5444db1 | ||
|
|
c99036046e | ||
|
|
a9c85622dd | ||
|
|
9f4d7e78b8 | ||
|
|
fd68d076dd | ||
|
|
96aa47d13f | ||
|
|
ad70c5c459 | ||
|
|
d824de701c |
@@ -22,6 +22,7 @@ jobs:
|
||||
}
|
||||
- { ruby: 4.0, allowed-failure: false, rubyopt: "--yjit" }
|
||||
- { ruby: 4.0, allowed-failure: false, rubyopt: "--zjit" }
|
||||
- { ruby: truffleruby, allowed-failure: false }
|
||||
|
||||
# Head can have failures due to being in development
|
||||
- { ruby: head, allowed-failure: true }
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.4.1
|
||||
4.0.2
|
||||
|
||||
@@ -32,6 +32,7 @@ group :test do
|
||||
end
|
||||
|
||||
group :spec do
|
||||
gem 'liquid-spec', github: 'Shopify/liquid-spec', branch: 'main'
|
||||
# Using feature branch until https://github.com/Shopify/liquid-spec/pull/144 is merged
|
||||
gem 'liquid-spec', github: 'Shopify/liquid-spec', branch: 'self-drop-env-lookup-specs'
|
||||
gem 'activesupport', require: false
|
||||
end
|
||||
|
||||
+12
@@ -1,5 +1,17 @@
|
||||
# Liquid Change Log
|
||||
|
||||
## 5.13.0
|
||||
|
||||
* Add TruffleRuby in CI [Benoit Daloze]
|
||||
* Skip slow test raising many exceptions on non-CRuby [Benoit Daloze]
|
||||
* Reject bare-bracket syntax in strict2 and introduce `self` keyword by [Alok Swamy]
|
||||
* Add strict2_parse to assign and capture tags by [Alok Swamy]
|
||||
* Add strict2_parse to increment and decrement tags by [Alok Swamy]
|
||||
* Update liquid-spec adapters for `missing_features` [Ian Ker-Seymer]
|
||||
* Prevent `SelfDrop` context mutation across render boundaries [Guilherme Carreiro]
|
||||
* Fix `SelfDrop` equality [Guilherme Carreiro]
|
||||
* Let environment `self` shadow `SelfDrop` [Ian Ker-Seymer]
|
||||
|
||||
## 5.11.0
|
||||
* 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]
|
||||
|
||||
@@ -151,6 +151,8 @@ end
|
||||
|
||||
desc('run liquid-spec suite across all adapters')
|
||||
task :spec do
|
||||
adapters = Dir['./spec/*.rb'].join(',')
|
||||
sh "bundle exec liquid-spec matrix --adapters=#{adapters} --reference=ruby_liquid"
|
||||
Dir['./spec/*.rb'].sort.each do |adapter|
|
||||
puts "=== Running #{adapter} ==="
|
||||
sh 'bundle', 'exec', 'liquid-spec', 'run', adapter, '--no-max-failures'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -99,7 +99,9 @@ module Liquid
|
||||
context.handle_error(exc, line_number)
|
||||
else
|
||||
error_message = context.handle_error(exc, line_number)
|
||||
unless blank_tag # conditional for backwards compatibility
|
||||
error_mode = context.registers.static[:template_error_mode]
|
||||
suppress_error_text = blank_tag && error_mode != :strict2 && error_mode != :rigid
|
||||
unless suppress_error_text # blank-tag suppression is kept for backwards compatibility outside strict2
|
||||
output << error_message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -206,16 +206,21 @@ module Liquid
|
||||
# path and find_index() is optimized in MRI to reduce object allocation
|
||||
index = @scopes.find_index { |s| s.key?(key) }
|
||||
|
||||
# `self` resolves to a SelfDrop (enabling `self['var']` lookups),
|
||||
# but only when it hasn't been explicitly assigned as a local variable.
|
||||
return SelfDrop.new(self) if key == Expression::SELF && !index
|
||||
fallback_to_self_drop = key == Expression::SELF && index.nil?
|
||||
|
||||
variable = if index
|
||||
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
|
||||
else
|
||||
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
|
||||
try_variable_find_in_environments(
|
||||
key,
|
||||
raise_on_not_found: raise_on_not_found && !fallback_to_self_drop,
|
||||
)
|
||||
end
|
||||
|
||||
# `self` resolves to a SelfDrop (enabling `self['var']` lookups),
|
||||
# but only after the normal environment lookup doesn't find a value.
|
||||
return @self_drop ||= SelfDrop.new(self) if fallback_to_self_drop && variable.nil?
|
||||
|
||||
# update variable's context before invoking #to_liquid
|
||||
variable.context = self if variable.respond_to?(:context=)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ module Liquid
|
||||
str << variable_lookups
|
||||
when :open_square
|
||||
if @reject_bare_brackets
|
||||
raise SyntaxError, "Bare bracket access is not allowed in strict2 mode. Use #{Expression::SELF}['...'] instead"
|
||||
raise SyntaxError, "Bare bracket access is not allowed. Use #{Expression::SELF}['...'] instead"
|
||||
end
|
||||
str = consume.dup
|
||||
str << expression
|
||||
|
||||
+20
-4
@@ -16,23 +16,39 @@ module Liquid
|
||||
# then the local value takes precedence over the `self` object.
|
||||
# @liquid_access global
|
||||
class SelfDrop < Drop
|
||||
def initialize(context)
|
||||
def initialize(self_context)
|
||||
super()
|
||||
@context = context
|
||||
@self_context = self_context
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@context.find_variable(key)
|
||||
@self_context.find_variable(key)
|
||||
rescue UndefinedVariable
|
||||
nil
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@context.variable_defined?(key)
|
||||
@self_context.variable_defined?(key)
|
||||
end
|
||||
|
||||
def to_liquid
|
||||
self
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.is_a?(SelfDrop) && other.self_context.equal?(@self_context)
|
||||
end
|
||||
|
||||
alias_method :eql?, :==
|
||||
|
||||
def hash
|
||||
@self_context.object_id.hash
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :self_context
|
||||
|
||||
undef context=
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,10 +8,19 @@ module Liquid
|
||||
MAX_I32 = (1 << 31) - 1
|
||||
private_constant :MAX_I32
|
||||
|
||||
MIN_I64 = -(1 << 63)
|
||||
MAX_I64 = (1 << 63) - 1
|
||||
I64_RANGE = MIN_I64..MAX_I64
|
||||
private_constant :MIN_I64, :MAX_I64, :I64_RANGE
|
||||
supports_64bit_indices = begin
|
||||
[][1 << 33, 1 << 33]
|
||||
true
|
||||
rescue RangeError
|
||||
false
|
||||
end
|
||||
|
||||
INDEX_RANGE = if supports_64bit_indices
|
||||
(-(1 << 63))..((1 << 63) - 1)
|
||||
else
|
||||
(-(1 << 31))..((1 << 31) - 1)
|
||||
end
|
||||
private_constant :INDEX_RANGE
|
||||
|
||||
HTML_ESCAPE = {
|
||||
'&' => '&',
|
||||
@@ -214,11 +223,11 @@ module Liquid
|
||||
Utils.to_s(input).slice(offset, length) || ''
|
||||
end
|
||||
rescue RangeError
|
||||
if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
|
||||
if INDEX_RANGE.cover?(length) && INDEX_RANGE.cover?(offset)
|
||||
raise # unexpected error
|
||||
end
|
||||
offset = offset.clamp(I64_RANGE)
|
||||
length = length.clamp(I64_RANGE)
|
||||
offset = offset.clamp(INDEX_RANGE)
|
||||
length = length.clamp(INDEX_RANGE)
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,13 +23,29 @@ module Liquid
|
||||
# {% decrement variable_name %}
|
||||
# @liquid_syntax_keyword variable_name The name of the variable being decremented.
|
||||
class Decrement < Tag
|
||||
include ParserSwitching
|
||||
|
||||
attr_reader :variable_name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
parse_with_selected_parser(markup)
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
@variable_name = markup.strip
|
||||
end
|
||||
|
||||
def strict_parse(markup)
|
||||
lax_parse(markup)
|
||||
end
|
||||
|
||||
def strict2_parse(markup)
|
||||
p = @parse_context.new_parser(markup.strip)
|
||||
@variable_name = p.consume(:id)
|
||||
p.consume(:end_of_string)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
counter_environment = context.environments.first
|
||||
value = counter_environment[@variable_name] || 0
|
||||
|
||||
@@ -23,13 +23,29 @@ module Liquid
|
||||
# {% increment variable_name %}
|
||||
# @liquid_syntax_keyword variable_name The name of the variable being incremented.
|
||||
class Increment < Tag
|
||||
include ParserSwitching
|
||||
|
||||
attr_reader :variable_name
|
||||
|
||||
def initialize(tag_name, markup, options)
|
||||
super
|
||||
parse_with_selected_parser(markup)
|
||||
end
|
||||
|
||||
def lax_parse(markup)
|
||||
@variable_name = markup.strip
|
||||
end
|
||||
|
||||
def strict_parse(markup)
|
||||
lax_parse(markup)
|
||||
end
|
||||
|
||||
def strict2_parse(markup)
|
||||
p = @parse_context.new_parser(markup.strip)
|
||||
@variable_name = p.consume(:id)
|
||||
p.consume(:end_of_string)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
counter_environment = context.environments.first
|
||||
value = counter_environment[@variable_name] || 0
|
||||
|
||||
+13
-2
@@ -151,8 +151,10 @@ module Liquid
|
||||
|
||||
c
|
||||
when Liquid::Drop
|
||||
drop = args.shift
|
||||
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
||||
drop = args.shift
|
||||
c = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
||||
drop.context = c if drop.respond_to?(:context=)
|
||||
c
|
||||
when Hash
|
||||
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
|
||||
when nil
|
||||
@@ -187,12 +189,20 @@ module Liquid
|
||||
|
||||
context.template_name ||= name
|
||||
|
||||
previous_error_mode = context.registers.static[:template_error_mode]
|
||||
context.registers.static[:template_error_mode] = @error_mode
|
||||
|
||||
begin
|
||||
# render the nodelist.
|
||||
@root.render_to_output_buffer(context, output || +'')
|
||||
rescue Liquid::MemoryError => e
|
||||
context.handle_error(e)
|
||||
ensure
|
||||
if previous_error_mode
|
||||
context.registers.static[:template_error_mode] = previous_error_mode
|
||||
else
|
||||
context.registers.static.delete(:template_error_mode)
|
||||
end
|
||||
@errors = context.errors
|
||||
end
|
||||
end
|
||||
@@ -224,6 +234,7 @@ module Liquid
|
||||
end
|
||||
|
||||
@warnings = parse_context.warnings
|
||||
@error_mode = parse_context.error_mode
|
||||
parse_context
|
||||
end
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
VERSION = "5.12.0"
|
||||
VERSION = "5.13.0"
|
||||
end
|
||||
|
||||
+16
-3
@@ -6,14 +6,24 @@
|
||||
|
||||
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
||||
require 'liquid'
|
||||
require_relative 'support/liquid_spec_adapter_helper'
|
||||
|
||||
LiquidSpec.configure do |config|
|
||||
# Run core Liquid specs
|
||||
config.features = [:core]
|
||||
config.missing_features = [
|
||||
:activesupport,
|
||||
:lax_parsing,
|
||||
:shopify_filters,
|
||||
:shopify_includes,
|
||||
:shopify_blank,
|
||||
:shopify_error_handling,
|
||||
:shopify_error_format,
|
||||
:shopify_string_access,
|
||||
]
|
||||
end
|
||||
|
||||
# Compile a template string into a Liquid::Template
|
||||
LiquidSpec.compile do |ctx, source, options|
|
||||
options[:error_mode] ||= :strict
|
||||
ctx[:template] = Liquid::Template.parse(source, **options)
|
||||
end
|
||||
|
||||
@@ -28,9 +38,12 @@ LiquidSpec.render do |ctx, assigns, options|
|
||||
static_environments: assigns,
|
||||
registers: registers,
|
||||
rethrow_errors: options[:strict_errors],
|
||||
resource_limits: LiquidSpecAdapterHelper.resource_limits(options),
|
||||
)
|
||||
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
|
||||
ctx[:template].render(context)
|
||||
LiquidSpecAdapterHelper.with_frozen_time do
|
||||
ctx[:template].render(context)
|
||||
end
|
||||
end
|
||||
|
||||
+16
-4
@@ -6,15 +6,24 @@
|
||||
|
||||
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
||||
require 'liquid'
|
||||
require_relative 'support/liquid_spec_adapter_helper'
|
||||
|
||||
LiquidSpec.configure do |config|
|
||||
config.features = [:core, :lax_parsing]
|
||||
config.missing_features = [
|
||||
:activesupport,
|
||||
:shopify_filters,
|
||||
:shopify_includes,
|
||||
:shopify_blank,
|
||||
:shopify_error_handling,
|
||||
:shopify_error_format,
|
||||
:shopify_string_access,
|
||||
]
|
||||
end
|
||||
|
||||
# Compile a template string into a Liquid::Template
|
||||
LiquidSpec.compile do |ctx, source, options|
|
||||
# Force lax mode
|
||||
options = options.merge(error_mode: :lax)
|
||||
# Default to lax mode while still honoring specs that explicitly set error_mode.
|
||||
options = { error_mode: :lax }.merge(options)
|
||||
ctx[:template] = Liquid::Template.parse(source, **options)
|
||||
end
|
||||
|
||||
@@ -26,9 +35,12 @@ LiquidSpec.render do |ctx, assigns, options|
|
||||
static_environments: assigns,
|
||||
registers: registers,
|
||||
rethrow_errors: options[:strict_errors],
|
||||
resource_limits: LiquidSpecAdapterHelper.resource_limits(options),
|
||||
)
|
||||
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
|
||||
ctx[:template].render(context)
|
||||
LiquidSpecAdapterHelper.with_frozen_time do
|
||||
ctx[:template].render(context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,14 +7,23 @@
|
||||
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
||||
require 'active_support/all'
|
||||
require 'liquid'
|
||||
require_relative 'support/liquid_spec_adapter_helper'
|
||||
|
||||
LiquidSpec.configure do |config|
|
||||
# Run core Liquid specs plus ActiveSupport SafeBuffer tests
|
||||
config.features = [:core, :activesupport]
|
||||
config.missing_features = [
|
||||
:lax_parsing,
|
||||
:shopify_filters,
|
||||
:shopify_includes,
|
||||
:shopify_blank,
|
||||
:shopify_error_handling,
|
||||
:shopify_error_format,
|
||||
:shopify_string_access,
|
||||
]
|
||||
end
|
||||
|
||||
# Compile a template string into a Liquid::Template
|
||||
LiquidSpec.compile do |ctx, source, options|
|
||||
options[:error_mode] ||= :strict
|
||||
ctx[:template] = Liquid::Template.parse(source, **options)
|
||||
end
|
||||
|
||||
@@ -29,9 +38,12 @@ LiquidSpec.render do |ctx, assigns, options|
|
||||
static_environments: assigns,
|
||||
registers: registers,
|
||||
rethrow_errors: options[:strict_errors],
|
||||
resource_limits: LiquidSpecAdapterHelper.resource_limits(options),
|
||||
)
|
||||
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
|
||||
ctx[:template].render(context)
|
||||
LiquidSpecAdapterHelper.with_frozen_time do
|
||||
ctx[:template].render(context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,9 +13,18 @@ end
|
||||
|
||||
require 'active_support/all'
|
||||
require 'liquid'
|
||||
require_relative 'support/liquid_spec_adapter_helper'
|
||||
|
||||
LiquidSpec.configure do |config|
|
||||
config.features = [:core, :activesupport]
|
||||
config.missing_features = [
|
||||
:lax_parsing,
|
||||
:shopify_filters,
|
||||
:shopify_includes,
|
||||
:shopify_blank,
|
||||
:shopify_error_handling,
|
||||
:shopify_error_format,
|
||||
:shopify_string_access,
|
||||
]
|
||||
end
|
||||
|
||||
# Compile a template string into a Liquid::Template
|
||||
@@ -33,9 +42,12 @@ LiquidSpec.render do |ctx, assigns, options|
|
||||
static_environments: assigns,
|
||||
registers: registers,
|
||||
rethrow_errors: options[:strict_errors],
|
||||
resource_limits: LiquidSpecAdapterHelper.resource_limits(options),
|
||||
)
|
||||
|
||||
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||
|
||||
ctx[:template].render(context)
|
||||
LiquidSpecAdapterHelper.with_frozen_time do
|
||||
ctx[:template].render(context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module LiquidSpecAdapterHelper
|
||||
extend self
|
||||
|
||||
def resource_limits(render_options)
|
||||
return unless render_options[:resource_limits]
|
||||
|
||||
Liquid::ResourceLimits.new({}).tap do |limits|
|
||||
render_options[:resource_limits].each do |key, value|
|
||||
limits.public_send(:"#{key}=", value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_frozen_time(&block)
|
||||
original_tz = ENV['TZ']
|
||||
ENV['TZ'] = 'UTC'
|
||||
|
||||
Liquid::Spec::TimeFreezer.freeze(Liquid::Spec::AdapterRunner::TEST_TIME, &block)
|
||||
ensure
|
||||
ENV['TZ'] = original_tz
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class BlankBodyErrorHandlingTest < Minitest::Test
|
||||
COMPARISON_ERROR = 'Liquid error (line 1): comparison of Integer with String failed'
|
||||
INVALID_INTEGER_ERROR = 'Liquid error (line 1): invalid integer'
|
||||
|
||||
def render_inline(source, error_mode:, assigns: {})
|
||||
Liquid::Template.parse(source, line_numbers: true, error_mode: error_mode).render(assigns, render_errors: true)
|
||||
end
|
||||
|
||||
def assert_render_raises(source, error_mode:, assigns: {}, message: nil)
|
||||
error = assert_raises(Liquid::ArgumentError) do
|
||||
Liquid::Template.parse(source, line_numbers: true, error_mode: error_mode).render!(assigns)
|
||||
end
|
||||
assert_includes error.message, message if message
|
||||
end
|
||||
|
||||
def test_blank_if_body_suppresses_inline_error_text_in_lax_and_strict
|
||||
[:lax, :strict].each do |mode|
|
||||
assert_equal '', render_inline('{% if 5 > "x" %}{% endif %}', error_mode: mode)
|
||||
end
|
||||
end
|
||||
|
||||
def test_blank_unless_body_suppresses_inline_error_text_in_lax_and_strict
|
||||
[:lax, :strict].each do |mode|
|
||||
assert_equal '', render_inline('{% unless 5 > "x" %} {% endunless %}', error_mode: mode)
|
||||
end
|
||||
end
|
||||
|
||||
def test_blank_for_body_suppresses_inline_error_text_in_lax_and_strict
|
||||
[:lax, :strict].each do |mode|
|
||||
assert_equal '', render_inline('{% for i in (1..3) offset: xs %}{% endfor %}', error_mode: mode, assigns: { 'xs' => 'bad' })
|
||||
end
|
||||
end
|
||||
|
||||
def test_strict2_blank_if_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% endif %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_whitespace_if_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %} {% endif %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_assign_if_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% assign a = 1 %}{% endif %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_comment_if_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% comment %}c{% endcomment %}{% endif %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_capture_if_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% capture c %}text{% endcapture %}{% endif %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_blank_unless_body_shows_inline_error_text
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% unless 5 > "x" %} {% endunless %}', error_mode: :strict2)
|
||||
end
|
||||
|
||||
def test_strict2_blank_for_body_shows_inline_error_text
|
||||
assert_equal INVALID_INTEGER_ERROR, render_inline('{% for i in (1..3) offset: xs %}{% endfor %}', error_mode: :strict2, assigns: { 'xs' => 'bad' })
|
||||
end
|
||||
|
||||
def test_nonblank_bodies_show_inline_error_text_in_all_modes
|
||||
[:lax, :strict, :strict2].each do |mode|
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% echo 1 %}{% endif %}', error_mode: mode)
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{{ "" }}{% endif %}', error_mode: mode)
|
||||
assert_equal COMPARISON_ERROR, render_inline('{% if 5 > "x" %}{% else %}E{% endif %}', error_mode: mode)
|
||||
end
|
||||
end
|
||||
|
||||
def test_raised_errors_are_not_swallowed_by_blank_if_body
|
||||
[:lax, :strict, :strict2].each do |mode|
|
||||
assert_render_raises('{% if 5 > "x" %}{% endif %}', error_mode: mode, message: 'comparison of Integer with String failed')
|
||||
end
|
||||
end
|
||||
|
||||
def test_raised_errors_are_not_swallowed_by_blank_for_body
|
||||
[:lax, :strict, :strict2].each do |mode|
|
||||
assert_render_raises('{% for i in (1..3) offset: xs %}{% endfor %}', error_mode: mode, assigns: { 'xs' => 'bad' }, message: 'invalid integer')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -44,32 +44,39 @@ class SecurityTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_does_not_permanently_add_filters_to_symbol_table
|
||||
current_symbols = Symbol.all_symbols
|
||||
assert_no_new_symbols do
|
||||
# MRI imprecisely marks objects found on the C stack, which can result
|
||||
# in uninitialized memory being marked. This can even result in the test failing
|
||||
# deterministically for a given compilation of ruby. Using a separate thread will
|
||||
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
||||
# collected after Thread#join.
|
||||
Thread.new do
|
||||
test = %( {{ "some_string" | a_bad_filter }} )
|
||||
Template.parse(test).render!
|
||||
nil
|
||||
end.join
|
||||
|
||||
# MRI imprecisely marks objects found on the C stack, which can result
|
||||
# in uninitialized memory being marked. This can even result in the test failing
|
||||
# deterministically for a given compilation of ruby. Using a separate thread will
|
||||
# keep these writes of the symbol pointer on a separate stack that will be garbage
|
||||
# collected after Thread#join.
|
||||
Thread.new do
|
||||
test = %( {{ "some_string" | a_bad_filter }} )
|
||||
Template.parse(test).render!
|
||||
nil
|
||||
end.join
|
||||
|
||||
GC.start
|
||||
|
||||
assert_equal([], Symbol.all_symbols - current_symbols)
|
||||
GC.start
|
||||
end
|
||||
end
|
||||
|
||||
def test_does_not_add_drop_methods_to_symbol_table
|
||||
assert_no_new_symbols do
|
||||
assigns = { 'drop' => Drop.new }
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!)
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!)
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!)
|
||||
end
|
||||
end
|
||||
|
||||
def assert_no_new_symbols
|
||||
# Run once to trigger any first-time initialization which might create some symbols,
|
||||
# for example autoload or lazy method parsing might create symbols on first execution.
|
||||
yield
|
||||
|
||||
# Ensure no new symbols for further runs, i.e. the code does not leak symbols
|
||||
current_symbols = Symbol.all_symbols
|
||||
|
||||
assigns = { 'drop' => Drop.new }
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_1 }}", assigns).render!)
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_2 }}", assigns).render!)
|
||||
assert_equal("", Template.parse("{{ drop.custom_method_3 }}", assigns).render!)
|
||||
|
||||
yield
|
||||
assert_equal([], Symbol.all_symbols - current_symbols)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class SelfDropContextTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_self_drop_passed_as_render_param_preserves_original_scope
|
||||
source = <<~LIQUID
|
||||
{%- assign var = 42 -%}
|
||||
{%- assign s = self -%}
|
||||
{%- render "snippet1", other_self: s -%}
|
||||
LIQUID
|
||||
|
||||
partials = {
|
||||
'snippet1' => <<~LIQUID,
|
||||
{%- assign var = 43 -%}
|
||||
{{- other_self.var }}|{{ self.var -}}
|
||||
LIQUID
|
||||
}
|
||||
|
||||
assert_template_result('42|43', source, partials: partials)
|
||||
end
|
||||
|
||||
def test_self_drop_in_render_without_passing_resolves_inner_scope
|
||||
source = <<~LIQUID
|
||||
{%- assign var = 42 -%}
|
||||
{%- render "snippet1" -%}
|
||||
LIQUID
|
||||
|
||||
partials = {
|
||||
'snippet1' => <<~LIQUID,
|
||||
{%- assign var = 99 -%}
|
||||
{{- self.var -}}
|
||||
LIQUID
|
||||
}
|
||||
|
||||
assert_template_result('99', source, partials: partials)
|
||||
end
|
||||
|
||||
def test_self_drop_passed_to_nested_renders_preserves_each_level
|
||||
source = <<~LIQUID
|
||||
{%- assign a = 1 -%}
|
||||
{%- assign s1 = self -%}
|
||||
{%- render "snippet1", outer: s1 -%}
|
||||
LIQUID
|
||||
|
||||
partials = {
|
||||
'snippet1' => <<~LIQUID,
|
||||
{%- assign a = 2 -%}
|
||||
{%- assign s2 = self -%}
|
||||
{%- render "snippet2", outer: outer, middle: s2 -%}
|
||||
LIQUID
|
||||
'snippet2' => <<~LIQUID,
|
||||
{%- assign a = 3 -%}
|
||||
{{- outer.a }}|{{ middle.a }}|{{ self.a -}}
|
||||
LIQUID
|
||||
}
|
||||
|
||||
assert_template_result('1|2|3', source, partials: partials)
|
||||
end
|
||||
|
||||
def test_self_drop_reflects_variables_assigned_after_creation
|
||||
source = <<~LIQUID
|
||||
{%- assign s = self -%}
|
||||
{%- assign x = 42 %}{{ s.x -}}
|
||||
LIQUID
|
||||
|
||||
assert_template_result('42', source)
|
||||
end
|
||||
|
||||
def test_self_drop_context_setter_is_undefined
|
||||
context = Context.new
|
||||
drop = SelfDrop.new(context)
|
||||
refute(drop.respond_to?(:context=))
|
||||
|
||||
assert_template_result('42', '{{ self.x }}', { 'x' => 42 })
|
||||
end
|
||||
|
||||
def test_self_drop_repeated_lookups_compare_equal_for_same_context
|
||||
context = Context.new
|
||||
drop = context.find_variable("self")
|
||||
cached_drop = context.find_variable("self")
|
||||
|
||||
assert_same(drop, cached_drop)
|
||||
assert_equal(drop.object_id, cached_drop.object_id)
|
||||
assert_equal(drop, cached_drop)
|
||||
end
|
||||
|
||||
def test_assigned_self_drop_compares_equal_to_itself
|
||||
assert_template_result('T', '{% assign s = self %}{% if s == s %}T{% else %}F{% endif %}')
|
||||
end
|
||||
|
||||
def test_distinct_self_assignments_compare_equal_for_same_context
|
||||
assert_template_result('T', '{% assign a = self %}{% assign b = self %}{% if a == b %}T{% else %}F{% endif %}')
|
||||
end
|
||||
|
||||
def test_bare_self_compares_equal_to_bare_self
|
||||
assert_template_result('T', '{% if self == self %}T{% else %}F{% endif %}')
|
||||
end
|
||||
|
||||
def test_self_drop_with_strict_variables_does_not_raise_for_defined_var
|
||||
t = Template.parse('{{ self.x }}')
|
||||
result = t.render({ 'x' => 42 }, strict_variables: true)
|
||||
assert_equal('42', result)
|
||||
end
|
||||
|
||||
def test_self_drop_with_strict_variables_returns_nil_for_undefined_var
|
||||
t = Template.parse('{{ self.x }}')
|
||||
result = t.render({}, strict_variables: true)
|
||||
assert_equal('', result)
|
||||
end
|
||||
|
||||
def test_self_drop_can_be_passed_as_bare_drop_to_render
|
||||
t = Template.parse('{{ self.x }}')
|
||||
drop = SelfDrop.new(Context.new({ 'x' => 42 }))
|
||||
result = t.render(drop)
|
||||
assert_equal('42', result)
|
||||
end
|
||||
end
|
||||
@@ -1181,6 +1181,8 @@ class StandardFiltersTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_all_filters_never_raise_non_liquid_exception
|
||||
skip("too slow on non-CRuby due to many exceptions") unless RUBY_ENGINE == 'ruby'
|
||||
|
||||
test_drop = TestDrop.new(value: "test")
|
||||
test_drop.context = Context.new
|
||||
test_enum = TestEnumerable.new
|
||||
|
||||
@@ -27,4 +27,50 @@ class IncrementTagTest < Minitest::Test
|
||||
'{%decrement starboard %}',
|
||||
)
|
||||
end
|
||||
|
||||
def test_increment_strict2_rejects_invalid_variable_name
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% increment foo bar %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_increment_strict2_rejects_variable_starting_with_number
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% increment 11aa %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_increment_strict2_accepts_valid_variable_name
|
||||
template = Template.parse('{% increment my-var %}', error_mode: :strict2)
|
||||
assert_equal('0', template.render)
|
||||
end
|
||||
|
||||
def test_decrement_strict2_rejects_invalid_variable_name
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% decrement foo bar %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_decrement_strict2_rejects_variable_starting_with_number
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% decrement 11aa %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_decrement_strict2_accepts_valid_variable_name
|
||||
template = Template.parse('{% decrement my-var %}', error_mode: :strict2)
|
||||
assert_equal('-1', template.render)
|
||||
end
|
||||
|
||||
def test_increment_strict2_rejects_empty_variable_name
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% increment %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
|
||||
def test_decrement_strict2_rejects_empty_variable_name
|
||||
assert_raises(Liquid::SyntaxError) do
|
||||
Template.parse('{% decrement %}', error_mode: :strict2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -174,16 +174,16 @@ class RenderTagTest < Minitest::Test
|
||||
def test_increment_is_isolated_between_renders
|
||||
assert_template_result(
|
||||
'010',
|
||||
'{% increment %}{% increment %}{% render "incr" %}',
|
||||
partials: { 'incr' => '{% increment %}' },
|
||||
'{% increment port %}{% increment port %}{% render "incr" %}',
|
||||
partials: { 'incr' => '{% increment port %}' },
|
||||
)
|
||||
end
|
||||
|
||||
def test_decrement_is_isolated_between_renders
|
||||
assert_template_result(
|
||||
'-1-2-1',
|
||||
'{% decrement %}{% decrement %}{% render "decr" %}',
|
||||
partials: { 'decr' => '{% decrement %}' },
|
||||
'{% decrement port %}{% decrement port %}{% render "decr" %}',
|
||||
partials: { 'decr' => '{% decrement port %}' },
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user