mirror of
https://github.com/kemko/liquid.git
synced 2026-01-01 15:55:40 +03:00
Compare commits
10 Commits
fix-contex
...
optimize-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22d746f670 | ||
|
|
7af188dcca | ||
|
|
5532df880f | ||
|
|
2b11efc3ae | ||
|
|
a1d982ca76 | ||
|
|
03be7f1ee3 | ||
|
|
1ced4eaf10 | ||
|
|
4970167726 | ||
|
|
065ccbc4aa | ||
|
|
1feaa63813 |
2
Gemfile
2
Gemfile
@@ -22,6 +22,6 @@ group :test do
|
||||
gem 'rubocop-performance', require: false
|
||||
|
||||
platform :mri, :truffleruby do
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
|
||||
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'optimize-invoke-filter-refactor'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,6 +63,8 @@ require 'liquid/expression'
|
||||
require 'liquid/context'
|
||||
require 'liquid/parser_switching'
|
||||
require 'liquid/tag'
|
||||
require 'liquid/tag/disabler'
|
||||
require 'liquid/tag/disableable'
|
||||
require 'liquid/block'
|
||||
require 'liquid/block_body'
|
||||
require 'liquid/document'
|
||||
@@ -86,4 +88,3 @@ require 'liquid/template_factory'
|
||||
# Load all the tags of the standard library
|
||||
#
|
||||
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
|
||||
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }
|
||||
|
||||
@@ -171,13 +171,7 @@ module Liquid
|
||||
private
|
||||
|
||||
def render_node(context, output, node)
|
||||
if node.disabled?(context)
|
||||
output << node.disabled_error_message
|
||||
return
|
||||
end
|
||||
disable_tags(context, node.disabled_tags) do
|
||||
node.render_to_output_buffer(context, output)
|
||||
end
|
||||
node.render_to_output_buffer(context, output)
|
||||
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
||||
context.handle_error(e, node.line_number)
|
||||
rescue ::StandardError => e
|
||||
@@ -185,11 +179,6 @@ module Liquid
|
||||
output << context.handle_error(e, line_number)
|
||||
end
|
||||
|
||||
def disable_tags(context, tags, &block)
|
||||
return yield if tags.empty?
|
||||
context.registers[:disabled_tags].disable(tags, &block)
|
||||
end
|
||||
|
||||
def raise_if_resource_limits_reached(context, length)
|
||||
context.resource_limits.render_length += length
|
||||
return unless context.resource_limits.reached?
|
||||
@@ -197,8 +186,8 @@ module Liquid
|
||||
end
|
||||
|
||||
def create_variable(token, parse_context)
|
||||
token.scan(ContentOfVariable) do |content|
|
||||
markup = content.first
|
||||
if token =~ ContentOfVariable
|
||||
markup = Regexp.last_match(1)
|
||||
return Variable.new(markup, parse_context)
|
||||
end
|
||||
raise_missing_variable_terminator(token, parse_context)
|
||||
|
||||
@@ -44,6 +44,7 @@ module Liquid
|
||||
@interrupts = []
|
||||
@filters = []
|
||||
@global_filter = nil
|
||||
@disabled_tags = {}
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
@@ -92,8 +93,13 @@ module Liquid
|
||||
exception_renderer.call(e).to_s
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
strainer.invoke(method, *args).to_liquid
|
||||
def invoke(method, input, *args)
|
||||
strainer.invoke(method, input, *args).to_liquid
|
||||
end
|
||||
|
||||
# @api private
|
||||
def invoke_filter(method, input, args)
|
||||
strainer.invoke_filter(method, input, args).to_liquid
|
||||
end
|
||||
|
||||
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
||||
@@ -144,6 +150,7 @@ module Liquid
|
||||
subcontext.strainer = nil
|
||||
subcontext.errors = errors
|
||||
subcontext.warnings = warnings
|
||||
subcontext.disabled_tags = @disabled_tags
|
||||
end
|
||||
end
|
||||
|
||||
@@ -208,9 +215,24 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def with_disabled_tags(tag_names)
|
||||
tag_names.each do |name|
|
||||
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
tag_names.each do |name|
|
||||
@disabled_tags[name] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
def tag_disabled?(tag_name)
|
||||
@disabled_tags.fetch(tag_name, 0) > 0
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
|
||||
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ module Liquid
|
||||
StandardError = Class.new(Error)
|
||||
SyntaxError = Class.new(Error)
|
||||
StackLevelError = Class.new(Error)
|
||||
TaintedError = Class.new(Error)
|
||||
MemoryError = Class.new(Error)
|
||||
ZeroDivisionError = Class.new(Error)
|
||||
FloatDomainError = Class.new(Error)
|
||||
@@ -54,5 +53,6 @@ module Liquid
|
||||
UndefinedDropMethod = Class.new(Error)
|
||||
UndefinedFilter = Class.new(Error)
|
||||
MethodOverrideError = Class.new(Error)
|
||||
DisabledError = Class.new(Error)
|
||||
InternalError = Class.new(Error)
|
||||
end
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class BlockBody
|
||||
def render_node_with_profiling(context, output, node)
|
||||
module BlockBodyProfilingHook
|
||||
def render_node(context, output, node)
|
||||
Profiler.profile_node_render(node) do
|
||||
render_node_without_profiling(context, output, node)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_node_without_profiling, :render_node
|
||||
alias_method :render_node, :render_node_with_profiling
|
||||
end
|
||||
BlockBody.prepend(BlockBodyProfilingHook)
|
||||
|
||||
class Include < Tag
|
||||
def render_to_output_buffer_with_profiling(context, output)
|
||||
module IncludeProfilingHook
|
||||
def render_to_output_buffer(context, output)
|
||||
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
|
||||
render_to_output_buffer_without_profiling(context, output)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
|
||||
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
|
||||
end
|
||||
Include.prepend(IncludeProfilingHook)
|
||||
end
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
module Liquid
|
||||
class DisabledTags < Register
|
||||
def initialize
|
||||
@disabled_tags = {}
|
||||
end
|
||||
|
||||
def disabled?(tag)
|
||||
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
|
||||
end
|
||||
|
||||
def disable(tags)
|
||||
tags.each(&method(:increment))
|
||||
yield
|
||||
ensure
|
||||
tags.each(&method(:decrement))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def increment(tag)
|
||||
@disabled_tags[tag] ||= 0
|
||||
@disabled_tags[tag] += 1
|
||||
end
|
||||
|
||||
def decrement(tag)
|
||||
@disabled_tags[tag] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
Template.add_register(:disabled_tags, DisabledTags.new)
|
||||
end
|
||||
@@ -41,7 +41,7 @@ module Liquid
|
||||
end
|
||||
|
||||
def escape(input)
|
||||
CGI.escapeHTML(input.to_s).untaint unless input.nil?
|
||||
CGI.escapeHTML(input.to_s) unless input.nil?
|
||||
end
|
||||
alias_method :h, :escape
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ module Liquid
|
||||
|
||||
include(filter)
|
||||
|
||||
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
|
||||
filter_methods.merge(filter.public_instance_methods)
|
||||
end
|
||||
|
||||
def invokable?(method)
|
||||
filter_methods.include?(method.to_s)
|
||||
filter_methods.include?(method.to_sym)
|
||||
end
|
||||
|
||||
private
|
||||
@@ -38,13 +38,18 @@ module Liquid
|
||||
end
|
||||
end
|
||||
|
||||
def invoke(method, *args)
|
||||
def invoke(method, input, *args)
|
||||
invoke_filter(method.to_sym, input, args)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def invoke_filter(method, input, args)
|
||||
if self.class.invokable?(method)
|
||||
send(method, *args)
|
||||
send(method, input, *args)
|
||||
elsif @context.strict_filters
|
||||
raise Liquid::UndefinedFilter, "undefined filter #{method}"
|
||||
else
|
||||
args.first
|
||||
input
|
||||
end
|
||||
rescue ::ArgumentError => e
|
||||
raise Liquid::ArgumentError, e.message, e.backtrace
|
||||
|
||||
@@ -13,15 +13,13 @@ module Liquid
|
||||
tag
|
||||
end
|
||||
|
||||
def disable_tags(*tags)
|
||||
disabled_tags.push(*tags)
|
||||
def disable_tags(*tag_names)
|
||||
@disabled_tags ||= []
|
||||
@disabled_tags.concat(tag_names)
|
||||
prepend(Disabler)
|
||||
end
|
||||
|
||||
private :new
|
||||
|
||||
def disabled_tags
|
||||
@disabled_tags ||= []
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(tag_name, markup, parse_context)
|
||||
@@ -46,14 +44,6 @@ module Liquid
|
||||
''
|
||||
end
|
||||
|
||||
def disabled?(context)
|
||||
context.registers[:disabled_tags].disabled?(tag_name)
|
||||
end
|
||||
|
||||
def disabled_error_message
|
||||
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
|
||||
end
|
||||
|
||||
# For backwards compatibility with custom tags. In a future release, the semantics
|
||||
# of the `render_to_output_buffer` method will become the default and the `render`
|
||||
# method will be removed.
|
||||
@@ -65,9 +55,5 @@ module Liquid
|
||||
def blank?
|
||||
false
|
||||
end
|
||||
|
||||
def disabled_tags
|
||||
self.class.disabled_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
22
lib/liquid/tag/disableable.rb
Normal file
22
lib/liquid/tag/disableable.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Tag
|
||||
module Disableable
|
||||
def render_to_output_buffer(context, output)
|
||||
if context.tag_disabled?(tag_name)
|
||||
output << disabled_error(context)
|
||||
return
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def disabled_error(context)
|
||||
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
|
||||
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
|
||||
rescue DisabledError => exc
|
||||
context.handle_error(exc, line_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
lib/liquid/tag/disabler.rb
Normal file
21
lib/liquid/tag/disabler.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Liquid
|
||||
class Tag
|
||||
module Disabler
|
||||
module ClassMethods
|
||||
attr_reader :disabled_tags
|
||||
end
|
||||
|
||||
def self.prepended(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
context.with_disabled_tags(self.class.disabled_tags) do
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,8 @@ module Liquid
|
||||
# {% include 'product' for products %}
|
||||
#
|
||||
class Include < Tag
|
||||
prepend Tag::Disableable
|
||||
|
||||
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
|
||||
Syntax = SYNTAX
|
||||
|
||||
|
||||
@@ -64,23 +64,6 @@ module Liquid
|
||||
attr_accessor :error_mode
|
||||
Template.error_mode = :lax
|
||||
|
||||
attr_reader :taint_mode
|
||||
|
||||
# Sets how strict the taint checker should be.
|
||||
# :lax is the default, and ignores the taint flag completely
|
||||
# :warn adds a warning, but does not interrupt the rendering
|
||||
# :error raises an error when tainted output is used
|
||||
# @deprecated Since it is being deprecated in ruby itself.
|
||||
def taint_mode=(mode)
|
||||
taint_supported = Object.new.taint.tainted?
|
||||
if mode != :lax && !taint_supported
|
||||
raise NotImplementedError, "#{RUBY_ENGINE} #{RUBY_VERSION} doesn't support taint checking"
|
||||
end
|
||||
@taint_mode = mode
|
||||
end
|
||||
|
||||
Template.taint_mode = :lax
|
||||
|
||||
attr_accessor :default_exception_renderer
|
||||
Template.default_exception_renderer = lambda do |exception|
|
||||
exception
|
||||
@@ -97,14 +80,6 @@ module Liquid
|
||||
tags[name.to_s] = klass
|
||||
end
|
||||
|
||||
attr_accessor :registers
|
||||
Template.registers = {}
|
||||
private :registers=
|
||||
|
||||
def add_register(name, klass)
|
||||
registers[name.to_sym] = klass
|
||||
end
|
||||
|
||||
# Pass a module with filter methods which should be available
|
||||
# to all liquid views. Good for registering the standard library
|
||||
def register_filter(mod)
|
||||
@@ -211,10 +186,6 @@ module Liquid
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
|
||||
Template.registers.each do |key, register|
|
||||
context_register[key] = register unless context_register.key?(key)
|
||||
end
|
||||
|
||||
# Retrying a render resets resource usage
|
||||
context.resource_limits.reset
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ module Liquid
|
||||
JustTagAttributes = /\A#{TagAttributes}\z/o
|
||||
MarkupWithQuotedFragment = /(#{QuotedFragment})(.*)/om
|
||||
|
||||
attr_accessor :filters, :name, :line_number
|
||||
attr_accessor :name, :line_number
|
||||
attr_reader :parse_context
|
||||
alias_method :options, :parse_context
|
||||
|
||||
@@ -81,14 +81,15 @@ module Liquid
|
||||
end
|
||||
|
||||
def render(context)
|
||||
obj = @filters.inject(context.evaluate(@name)) do |output, (filter_name, filter_args, filter_kwargs)|
|
||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||
context.invoke(filter_name, output, *filter_args)
|
||||
result = context.evaluate(@name)
|
||||
@filters.each do |filter_name, constant_args, filter_args, filter_kwargs|
|
||||
unless constant_args
|
||||
filter_args = evaluate_filter_expressions(context, filter_args, filter_kwargs)
|
||||
end
|
||||
result = context.invoke_filter(filter_name, result, filter_args)
|
||||
end
|
||||
|
||||
obj = context.apply_global_filter(obj)
|
||||
taint_check(context, obj)
|
||||
obj
|
||||
context.apply_global_filter(result)
|
||||
end
|
||||
|
||||
def render_to_output_buffer(context, output)
|
||||
@@ -112,21 +113,49 @@ module Liquid
|
||||
[]
|
||||
end
|
||||
|
||||
def filters
|
||||
@filters.map do |filter_name, constant_args, *filter_args_and_kwargs|
|
||||
filter_name = filter_name.to_s
|
||||
if constant_args
|
||||
filter_args = filter_args_and_kwargs.first
|
||||
if filter_args.last.is_a?(Hash)
|
||||
filter_args = filter_args.dup
|
||||
[filter_name, filter_args, filter_args.pop]
|
||||
else
|
||||
[filter_name, *filter_args_and_kwargs]
|
||||
end
|
||||
else
|
||||
[filter_name, *filter_args_and_kwargs]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_filter_expressions(filter_name, unparsed_args)
|
||||
filter_args = []
|
||||
constant_args = true
|
||||
filter_args = []
|
||||
keyword_args = nil
|
||||
unparsed_args.each do |a|
|
||||
if (matches = a.match(JustTagAttributes))
|
||||
keyword_args ||= {}
|
||||
keyword_args[matches[1]] = Expression.parse(matches[2])
|
||||
keyword_args ||= {}
|
||||
expression = Expression.parse(matches[2])
|
||||
constant_args &&= !expression.is_a?(VariableLookup)
|
||||
keyword_args[matches[1]] = expression
|
||||
else
|
||||
filter_args << Expression.parse(a)
|
||||
expression = Expression.parse(a)
|
||||
constant_args &&= !expression.is_a?(VariableLookup)
|
||||
filter_args << expression
|
||||
end
|
||||
end
|
||||
result = [filter_name.to_sym, constant_args, filter_args]
|
||||
if keyword_args
|
||||
if constant_args
|
||||
filter_args << keyword_args
|
||||
else
|
||||
result << keyword_args
|
||||
end
|
||||
end
|
||||
result = [filter_name, filter_args]
|
||||
result << keyword_args if keyword_args
|
||||
result
|
||||
end
|
||||
|
||||
@@ -142,25 +171,6 @@ module Liquid
|
||||
parsed_args
|
||||
end
|
||||
|
||||
def taint_check(context, obj)
|
||||
return if Template.taint_mode == :lax
|
||||
return unless obj.tainted?
|
||||
|
||||
@markup =~ QuotedFragment
|
||||
name = Regexp.last_match(0)
|
||||
|
||||
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
|
||||
error.line_number = line_number
|
||||
error.template_name = context.template_name
|
||||
|
||||
case Template.taint_mode
|
||||
when :warn
|
||||
context.warnings << error
|
||||
when :error
|
||||
raise error
|
||||
end
|
||||
end
|
||||
|
||||
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
||||
def children
|
||||
[@node.name] + @node.filters.flatten
|
||||
|
||||
@@ -27,6 +27,6 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.require_path = "lib"
|
||||
|
||||
s.add_development_dependency('rake', '~> 11.3')
|
||||
s.add_development_dependency('rake', '~> 13.0')
|
||||
s.add_development_dependency('minitest')
|
||||
end
|
||||
|
||||
@@ -49,10 +49,6 @@ class ProductDrop < Liquid::Drop
|
||||
ContextDrop.new
|
||||
end
|
||||
|
||||
def user_input
|
||||
(+"foo").taint
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def callmenot
|
||||
@@ -114,34 +110,6 @@ class DropsTest < Minitest::Test
|
||||
assert_equal(' ', tpl.render!('product' => ProductDrop.new))
|
||||
end
|
||||
|
||||
if taint_supported?
|
||||
def test_rendering_raises_on_tainted_attr
|
||||
with_taint_mode(:error) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||
assert_raises TaintedError do
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_rendering_warns_on_tainted_attr
|
||||
with_taint_mode(:warn) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
||||
context = Context.new('product' => ProductDrop.new)
|
||||
tpl.render!(context)
|
||||
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
||||
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
|
||||
end
|
||||
end
|
||||
|
||||
def test_rendering_doesnt_raise_on_escaped_tainted_attr
|
||||
with_taint_mode(:error) do
|
||||
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
|
||||
tpl.render!('product' => ProductDrop.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_drop_does_only_respond_to_whitelisted_methods
|
||||
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
|
||||
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
|
||||
@@ -281,7 +249,7 @@ class DropsTest < Minitest::Test
|
||||
end
|
||||
|
||||
def test_invokable_methods
|
||||
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
|
||||
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class DisabledTagsTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class DisableRaw < Block
|
||||
disable_tags "raw"
|
||||
end
|
||||
|
||||
class DisableRawEcho < Block
|
||||
disable_tags "raw", "echo"
|
||||
end
|
||||
|
||||
def test_disables_raw
|
||||
with_custom_tag('disable', DisableRaw) do
|
||||
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_echo_and_raw
|
||||
with_custom_tag('disable', DisableRawEcho) do
|
||||
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -48,10 +48,11 @@ class SecurityTest < Minitest::Test
|
||||
|
||||
test = %( {{ "some_string" | a_bad_filter }} )
|
||||
|
||||
template = Template.parse(test)
|
||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||
Template.parse(test).render!
|
||||
|
||||
GC.start
|
||||
GC.start
|
||||
|
||||
template.render!
|
||||
assert_equal([], (Symbol.all_symbols - current_symbols))
|
||||
end
|
||||
|
||||
|
||||
51
test/integration/tag/disableable_test.rb
Normal file
51
test/integration/tag/disableable_test.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class TagDisableableTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
class DisableRaw < Block
|
||||
disable_tags "raw"
|
||||
end
|
||||
|
||||
class DisableRawEcho < Block
|
||||
disable_tags "raw", "echo"
|
||||
end
|
||||
|
||||
class DisableableRaw < Liquid::Raw
|
||||
prepend Liquid::Tag::Disableable
|
||||
end
|
||||
|
||||
class DisableableEcho < Liquid::Echo
|
||||
prepend Liquid::Tag::Disableable
|
||||
end
|
||||
|
||||
def test_disables_raw
|
||||
with_disableable_tags do
|
||||
with_custom_tag('disable', DisableRaw) do
|
||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
||||
assert_equal('Liquid error: raw usage is not allowed in this contextfoo', output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_echo_and_raw
|
||||
with_disableable_tags do
|
||||
with_custom_tag('disable', DisableRawEcho) do
|
||||
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
|
||||
assert_equal('Liquid error: raw usage is not allowed in this contextLiquid error: echo usage is not allowed in this context', output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_disableable_tags
|
||||
with_custom_tag('raw', DisableableRaw) do
|
||||
with_custom_tag('echo', DisableableEcho) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -42,34 +42,6 @@ class RenderTagTest < Minitest::Test
|
||||
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
|
||||
end
|
||||
|
||||
if taint_supported?
|
||||
def test_render_sets_the_correct_template_name_for_errors
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
|
||||
|
||||
with_taint_mode :error do
|
||||
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
|
||||
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
|
||||
template.render(context)
|
||||
|
||||
assert_equal [Liquid::TaintedError], template.errors.map(&:class)
|
||||
assert_equal 'snippet', template.errors.first.template_name
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_sets_the_correct_template_name_for_warnings
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
|
||||
|
||||
with_taint_mode :warn do
|
||||
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
|
||||
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
|
||||
template.render(context)
|
||||
|
||||
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
||||
assert_equal 'snippet', context.warnings.first.template_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_render_does_not_mutate_parent_scope
|
||||
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
|
||||
assert_template_result('', "{% render 'snippet' %}{{ inner }}")
|
||||
@@ -155,7 +127,10 @@ class RenderTagTest < Minitest::Test
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}')
|
||||
exc = assert_raises(Liquid::DisabledError) do
|
||||
Liquid::Template.parse('{% render "test_include" %}').render!
|
||||
end
|
||||
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
|
||||
end
|
||||
|
||||
def test_includes_will_not_render_inside_nested_sibling_tags
|
||||
@@ -165,7 +140,8 @@ class RenderTagTest < Minitest::Test
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
|
||||
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
|
||||
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
|
||||
end
|
||||
|
||||
def test_render_tag_with
|
||||
|
||||
@@ -361,56 +361,4 @@ class TemplateTest < Minitest::Test
|
||||
result = t.render('x' => 1, 'y' => 5)
|
||||
assert_equal('12345', result)
|
||||
end
|
||||
|
||||
def test_render_uses_correct_disabled_tags_instance
|
||||
Liquid::Template.file_system = StubFileSystem.new(
|
||||
'foo' => 'bar',
|
||||
'test_include' => '{% include "foo" %}'
|
||||
)
|
||||
|
||||
disabled_tags = DisabledTags.new
|
||||
context = Context.build(registers: { disabled_tags: disabled_tags })
|
||||
|
||||
source = "{% render 'test_include' %}"
|
||||
parse_context = Liquid::ParseContext.new(line_numbers: true, error_mode: :strict)
|
||||
document = Document.parse(Liquid::Tokenizer.new(source, true), parse_context)
|
||||
|
||||
assert_equal("include usage is not allowed in this context", document.render(context))
|
||||
end
|
||||
|
||||
def test_render_sets_context_static_register_when_register_key_does_exist
|
||||
disabled_tags_for_test = DisabledTags.new
|
||||
Template.add_register(:disabled_tags, disabled_tags_for_test)
|
||||
|
||||
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||
|
||||
context = Context.new
|
||||
refute(context.registers.key?(:disabled_tags))
|
||||
|
||||
t.render(context)
|
||||
|
||||
assert(context.registers.key?(:disabled_tags))
|
||||
assert_equal(disabled_tags_for_test, context.registers[:disabled_tags])
|
||||
end
|
||||
|
||||
def test_render_does_not_override_context_static_register_when_register_key_exists
|
||||
context = Context.new
|
||||
context.registers[:random_register] = nil
|
||||
Template.add_register(:random_register, {})
|
||||
|
||||
t = Template.parse("{% if true %} Test Template {% endif %}")
|
||||
|
||||
t.render(context)
|
||||
|
||||
assert_nil(context.registers[:random_register])
|
||||
assert(context.registers.key?(:random_register))
|
||||
end
|
||||
|
||||
unless taint_supported?
|
||||
def test_taint_mode
|
||||
assert_raises(NotImplementedError) do
|
||||
Template.taint_mode = :warn
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,10 +32,6 @@ module Minitest
|
||||
def fixture(name)
|
||||
File.join(File.expand_path(__dir__), "fixtures", name)
|
||||
end
|
||||
|
||||
def self.taint_supported?
|
||||
Object.new.taint.tainted?
|
||||
end
|
||||
end
|
||||
|
||||
module Assertions
|
||||
@@ -93,14 +89,6 @@ module Minitest
|
||||
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
|
||||
end
|
||||
|
||||
def with_taint_mode(mode)
|
||||
old_mode = Liquid::Template.taint_mode
|
||||
Liquid::Template.taint_mode = mode
|
||||
yield
|
||||
ensure
|
||||
Liquid::Template.taint_mode = old_mode
|
||||
end
|
||||
|
||||
def with_error_mode(mode)
|
||||
old_mode = Liquid::Template.error_mode
|
||||
Liquid::Template.error_mode = mode
|
||||
@@ -110,10 +98,17 @@ module Minitest
|
||||
end
|
||||
|
||||
def with_custom_tag(tag_name, tag_class)
|
||||
Liquid::Template.register_tag(tag_name, tag_class)
|
||||
yield
|
||||
ensure
|
||||
Liquid::Template.tags.delete(tag_name)
|
||||
old_tag = Liquid::Template.tags[tag_name]
|
||||
begin
|
||||
Liquid::Template.register_tag(tag_name, tag_class)
|
||||
yield
|
||||
ensure
|
||||
if old_tag
|
||||
Liquid::Template.tags[tag_name] = old_tag
|
||||
else
|
||||
Liquid::Template.tags.delete(tag_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -563,6 +563,35 @@ class ContextUnitTest < Minitest::Test
|
||||
assert_equal('my filter result', template.render(subcontext))
|
||||
end
|
||||
|
||||
def test_disables_tag_specified
|
||||
context = Context.new
|
||||
context.with_disabled_tags(%w(foo bar)) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
assert_equal false, context.tag_disabled?("unknown")
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_nested_tags
|
||||
context = Context.new
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal false, context.tag_disabled?("bar")
|
||||
end
|
||||
context.with_disabled_tags(["bar"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
context.with_disabled_tags(["foo"]) do
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal true, context.tag_disabled?("bar")
|
||||
end
|
||||
end
|
||||
assert_equal true, context.tag_disabled?("foo")
|
||||
assert_equal false, context.tag_disabled?("bar")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_no_object_allocations
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class DisabledTagsUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
def test_disables_tag_specified
|
||||
register = DisabledTags.new
|
||||
register.disable(%w(foo bar)) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
assert_equal false, register.disabled?("unknown")
|
||||
end
|
||||
end
|
||||
|
||||
def test_disables_nested_tags
|
||||
register = DisabledTags.new
|
||||
register.disable(["foo"]) do
|
||||
register.disable(["foo"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal false, register.disabled?("bar")
|
||||
end
|
||||
register.disable(["bar"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
register.disable(["foo"]) do
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal true, register.disabled?("bar")
|
||||
end
|
||||
end
|
||||
assert_equal true, register.disabled?("foo")
|
||||
assert_equal false, register.disabled?("bar")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -52,6 +52,12 @@ class StaticRegistersUnitTest < Minitest::Test
|
||||
static_register.fetch(:d)
|
||||
end
|
||||
assert_equal("default", static_register.fetch(:d, "default"))
|
||||
|
||||
result = static_register.fetch(:d) { "default" }
|
||||
assert_equal("default", result)
|
||||
|
||||
result = static_register.fetch(:d, "default 1") { "default 2" }
|
||||
assert_equal("default 2", result)
|
||||
end
|
||||
|
||||
def test_key
|
||||
|
||||
@@ -6,11 +6,11 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
include Liquid
|
||||
|
||||
module AccessScopeFilters
|
||||
def public_filter
|
||||
def public_filter(_input)
|
||||
"public"
|
||||
end
|
||||
|
||||
def private_filter
|
||||
def private_filter(_input)
|
||||
"private"
|
||||
end
|
||||
private :private_filter
|
||||
@@ -31,13 +31,13 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
def test_strainer
|
||||
strainer = StrainerFactory.create(@context)
|
||||
assert_equal(5, strainer.invoke('size', 'input'))
|
||||
assert_equal("public", strainer.invoke("public_filter"))
|
||||
assert_equal("public", strainer.invoke("public_filter", 'input'))
|
||||
end
|
||||
|
||||
def test_stainer_raises_argument_error
|
||||
strainer = StrainerFactory.create(@context)
|
||||
assert_raises(Liquid::ArgumentError) do
|
||||
strainer.invoke("public_filter", 1)
|
||||
strainer.invoke("public_filter", 'input', 1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,11 +45,11 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
strainer = StrainerFactory.create(@context)
|
||||
|
||||
exception = assert_raises(Liquid::ArgumentError) do
|
||||
strainer.invoke("public_filter", 1)
|
||||
strainer.invoke("public_filter", 'input', 1)
|
||||
end
|
||||
|
||||
assert_match(
|
||||
/\ALiquid error: wrong number of arguments \((1 for 0|given 1, expected 0)\)\z/,
|
||||
/\ALiquid error: wrong number of arguments \((2 for 1|given 2, expected 1)\)\z/,
|
||||
exception.message
|
||||
)
|
||||
assert_equal(exception.backtrace[0].split(':')[0], __FILE__)
|
||||
@@ -66,8 +66,8 @@ class StrainerFactoryUnitTest < Minitest::Test
|
||||
|
||||
def test_strainer_returns_nil_if_no_filter_method_found
|
||||
strainer = StrainerFactory.create(@context)
|
||||
assert_nil(strainer.invoke("private_filter"))
|
||||
assert_nil(strainer.invoke("undef_the_filter"))
|
||||
assert_nil(strainer.invoke("private_filter", nil))
|
||||
assert_nil(strainer.invoke("undef_the_filter", nil))
|
||||
end
|
||||
|
||||
def test_strainer_returns_first_argument_if_no_method_and_arguments_given
|
||||
|
||||
@@ -60,7 +60,7 @@ class StrainerTemplateUnitTest < Minitest::Test
|
||||
strainer = Context.new.strainer
|
||||
with_global_filter do
|
||||
strainer.class.add_filter(PublicMethodOverrideFilter)
|
||||
assert(strainer.class.send(:filter_methods).include?('public_filter'))
|
||||
assert(strainer.class.send(:filter_methods).include?(:public_filter))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user