Compare commits

..

48 Commits

Author SHA1 Message Date
Marc-André Cournoyer
42b6c07cd0 Merge pull request #1384 from Shopify/allowed_push_host
Add allowed_push_host to gemspec
2021-01-06 10:18:16 -05:00
Marc-André Cournoyer
c91a6827f2 Add allowed_push_host to gemspec
Required to push gem to rubygems.
2021-01-06 10:03:13 -05:00
Marc-André Cournoyer
5dbc3d5701 Merge pull request #1383 from Shopify/bump-version
Bump version to 5.0.0
2021-01-06 09:38:04 -05:00
Marc-André Cournoyer
22683cbd2a Bump version to 5.0.0
Bump major because of the numerous breaking changes.
2021-01-05 15:02:14 -05:00
Peter Zhu
abfab3bef2 Merge pull request #1380 from Shopify/pz-serialize-compat
Fixes for serialization
2021-01-05 14:10:54 -05:00
Peter Zhu
51e8d6234a Freeze blocks in reverse order 2020-12-16 15:18:34 -05:00
Peter Zhu
7ca2846d9c Move configure options to a method 2020-12-16 15:08:55 -05:00
Dylan Thacker-Smith
7ba0fc7952 Merge pull request #1376 from Shopify/support-app-liquid-context
Use the same context class for isolated subcontexts for the require tag
2020-12-14 12:12:46 -05:00
Marc-André Cournoyer
e2c86d137f Merge pull request #1377 from Shopify/update-history
Fix latest changelog
2020-12-13 12:27:15 -05:00
Marc-André Cournoyer
776a63b61d Update latest changelog to mention Ruby 2.5 req 2020-12-13 12:13:49 -05:00
Marc-André Cournoyer
84f9d6957c Merge pull request #1374 from Shopify/update-history
Update History.md with changes since last release
2020-12-11 17:05:33 -05:00
Marc-André Cournoyer
7d32728e16 Split latest changelog in Features, Fixes, Changes & Perf
Also remove duplicated entries
2020-12-11 16:35:19 -05:00
Dylan Thacker-Smith
40a9b72b3c Allow a block to finish context init before squashing instance assigns
Liquid::Context#squash_instance_assigns_with_environments can result in
Proc objects in the environment to be eagerly evaluated. So it should be
possible to finish initializing the context object before this is done.
Allowing a block to be used for this purpose avoids the need to add
additional parameters for this purpose.
2020-12-11 14:52:09 -05:00
Dylan Thacker-Smith
4ff26cd707 Use the same context class for isolated subcontexts for the require tag 2020-12-11 14:52:09 -05:00
Dylan Thacker-Smith
462919a28f Merge pull request #1375 from Shopify/update-rubocop
Update rubocop and related configuration
2020-12-11 14:47:56 -05:00
Dylan Thacker-Smith
f3e2be9f85 Update rubocop 2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
4d40f83457 Update .rubocop_todo.yml to ignore Lint/MissingSuper offenses in drops
I don't think we can rely on the application to call `super` in their
drop's initializers at the moment, so doing that consistently in liquid
would prevent this from being properly tested.
2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
00be1e4dd4 Update inherited rubocop shopify style guide configuration 2020-12-11 14:14:15 -05:00
Dylan Thacker-Smith
f7d67b946e rubocop autocorrect Style/MethodCallWithArgsParentheses 2020-12-11 13:51:00 -05:00
Marc-André Cournoyer
ae9aee896b Update History.md with changes since last release 2020-12-10 08:03:17 -05:00
Dylan Thacker-Smith
6dec172743 Merge pull request #1366 from Shopify/profile-render-node
Create top-level profile timing nodes for multiple template renders
2020-12-09 10:07:31 -05:00
Dylan Thacker-Smith
da581d988a Create top-level profile timing nodes for multiple template renders 2020-12-09 10:06:02 -05:00
Dylan Thacker-Smith
7960826552 Rename render_profiling_test.rb to profiler_test.rb
so it corresponds to the class name being tested
2020-12-09 10:06:02 -05:00
Dylan Thacker-Smith
84059691b8 Merge pull request #1365 from Shopify/profiling-multiple-renders
Support using a profiler for multiple renders
2020-12-09 10:05:48 -05:00
Dylan Thacker-Smith
896288eff1 Move start of profiling to a Document#render_to_output_buffer patch 2020-12-09 10:04:34 -05:00
Dylan Thacker-Smith
b3f132efd1 Fix total_render_time if a Profiler gets used for multiple renders 2020-12-09 10:04:34 -05:00
Dylan Thacker-Smith
60214b957c Store the profiler in the context instead of a thread-local variable (#1364) 2020-12-09 10:04:20 -05:00
Dylan Thacker-Smith
7361220af6 Merge pull request #1363 from Shopify/profiler-context-template-name
Fix template name in profile result for render tag timing objects
2020-12-09 10:03:42 -05:00
Dylan Thacker-Smith
cb2ad71a31 Remove the Profiler#initialize argument which is effectively now unused
The @root_timing Timing object that it was used with never exposed that
name.
2020-12-09 10:01:16 -05:00
Dylan Thacker-Smith
900e3a6491 Fix template name in profile result for render tag timing objects 2020-12-09 10:01:16 -05:00
Dylan Thacker-Smith
f18084203d Use monotonic time to measure durations in Liquid::Profiler (#1362) 2020-12-09 10:00:44 -05:00
Peter Zhu
3358a892f2 Merge pull request #1371 from Shopify/pz-revert-instrument
Revert instrumentation of end_tag_params and range_float
2020-12-01 15:24:52 -05:00
Peter Zhu
bbfcaa2cc0 Revert "Merge pull request #1350 from Shopify/pz-instrument-invalid-end-tag"
This reverts commit e6eef4b2c4, reversing
changes made to c7c21e88f0.
2020-12-01 14:38:04 -05:00
Peter Zhu
ba657871bc Revert "Merge pull request #1359 from Shopify/pz-instrument-range-floats"
This reverts commit 300adfd7ae, reversing
changes made to ed0aebcbc9.
2020-12-01 14:37:23 -05:00
Peter Zhu
29d5d9674a Merge pull request #1370 from Shopify/pz-freeze-case-body
Freeze the body for case tags
2020-11-27 12:03:03 -05:00
Peter Zhu
0a645e72c1 Freeze the body for case 2020-11-27 11:29:17 -05:00
Dylan Thacker-Smith
1850511334 Use an atomic subgroup in range regex to avoid pathological backtracking (#1360) 2020-11-16 10:29:36 -05:00
Peter Zhu
300adfd7ae Merge pull request #1359 from Shopify/pz-instrument-range-floats
Instrument usage of floats in ranges
2020-11-13 16:08:47 -05:00
Peter Zhu
f357662f37 Instrument floats in ranges 2020-11-13 16:05:29 -05:00
Peter Zhu
ed0aebcbc9 Merge pull request #1355 from Shopify/pz-instrument-forloop-name
Instrument forloop.name
2020-11-12 14:24:08 -05:00
Peter Zhu
ea4f1885f8 Instrument forloop.name 2020-11-12 14:19:12 -05:00
Peter Zhu
2f75db604f Merge pull request #1354 from Shopify/pz-instrument-for-offset-continue
Instrument usage of offset:continue in for loops
2020-11-12 14:14:09 -05:00
Peter Zhu
d844a3dd8b Instrument usage of offset:continue in for loops 2020-11-12 13:39:02 -05:00
Max Melentiev
9fcba1a26c Remove unused translation (#1033) 2020-11-11 10:21:04 -05:00
Peter Zhu
0659891e68 Merge pull request #1352 from Shopify/pz-test-trim-blank
Test trim without any content
2020-11-10 10:36:21 -05:00
Peter Zhu
e7fb3b18f3 Test trim without any content 2020-11-10 10:31:07 -05:00
Peter Zhu
e6eef4b2c4 Merge pull request #1350 from Shopify/pz-instrument-invalid-end-tag
Instrument invalid end tags
2020-11-10 10:06:25 -05:00
Peter Zhu
2ce577e36b Instrument for bug #1346 2020-11-09 14:27:17 -05:00
39 changed files with 345 additions and 256 deletions

View File

@@ -1,5 +1,3 @@
# Recommended rubocop version: ~> 0.78.0
AllCops:
Exclude:
- 'db/schema.rb'
@@ -78,13 +76,6 @@ Style/BlockDelimiters:
- proc
- it
Style/BracesAroundHashParameters:
EnforcedStyle: no_braces
SupportedStyles:
- braces
- no_braces
- context_dependent
Layout/CaseIndentation:
EnforcedStyle: end
SupportedStyles:
@@ -204,6 +195,7 @@ Style/FrozenStringLiteralComment:
SupportedStyles:
- always
- never
SafeAutoCorrect: true
Style/GlobalVars:
AllowedVariables: []
@@ -273,7 +265,7 @@ Style/MethodCallWithArgsParentheses:
- raise
- puts
Exclude:
- Gemfile
- '**/Gemfile'
Style/MethodDefParentheses:
EnforcedStyle: require_parentheses
@@ -586,6 +578,7 @@ Layout/BlockEndNewline:
Style/CaseEquality:
Enabled: true
AllowOnConstant: true
Style/CharacterLiteral:
Enabled: true
@@ -668,6 +661,9 @@ Style/IfWithSemicolon:
Style/IdenticalConditionalBranches:
Enabled: true
Layout/IndentationStyle:
Enabled: true
Style/InfiniteLoop:
Enabled: true
@@ -680,7 +676,7 @@ Style/LineEndConcatenation:
Style/MethodCallWithoutArgsParentheses:
Enabled: true
Style/MethodMissingSuper:
Lint/MissingSuper:
Enabled: true
Style/MissingRespondToMissing:
@@ -812,9 +808,6 @@ Layout/SpaceInsideRangeLiteral:
Style/SymbolLiteral:
Enabled: true
Layout/Tab:
Enabled: true
Layout/TrailingWhitespace:
Enabled: true
@@ -843,7 +836,7 @@ Style/ZeroLengthPredicate:
Enabled: true
Layout/HeredocIndentation:
EnforcedStyle: squiggly
Enabled: true
Lint/AmbiguousOperator:
Enabled: true
@@ -881,9 +874,6 @@ Lint/EmptyEnsure:
Lint/EmptyInterpolation:
Enabled: true
Lint/EndInMethod:
Enabled: true
Lint/EnsureReturn:
Enabled: true
@@ -976,7 +966,7 @@ Lint/UselessAccessModifier:
Lint/UselessAssignment:
Enabled: true
Lint/UselessComparison:
Lint/BinaryOperatorWithIdenticalOperands:
Enabled: true
Lint/UselessElseWithoutRescue:
@@ -1027,3 +1017,6 @@ Style/ModuleFunction:
Lint/OrderedMagicComments:
Enabled: true
Lint/DeprecatedOpenSSLConstant:
Enabled: true

View File

@@ -9,6 +9,7 @@ Performance:
AllCops:
TargetRubyVersion: 2.4
NewCops: disable
Exclude:
- 'vendor/bundle/**/*'

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-09-11 06:34:25 +1000 using RuboCop version 0.74.0.
# on 2020-12-11 18:53:41 UTC using RuboCop version 1.6.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -14,14 +14,26 @@ Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 98
# Offense count: 113
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 294
Max: 260
# Offense count: 44
# Offense count: 8
Lint/MissingSuper:
Exclude:
- 'lib/liquid/forloop_drop.rb'
- 'lib/liquid/tablerowloop_drop.rb'
- 'test/integration/assign_test.rb'
- 'test/integration/context_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/table_row_test.rb'
# Offense count: 43
Naming/ConstantName:
Exclude:
- 'lib/liquid.rb'
@@ -32,7 +44,6 @@ Naming/ConstantName:
- 'lib/liquid/tags/cycle.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
@@ -40,9 +51,7 @@ Naming/ConstantName:
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 5
# Offense count: 2
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'

View File

@@ -18,7 +18,7 @@ group :benchmark, :test do
end
group :test do
gem 'rubocop', '~> 0.78.0', require: false
gem 'rubocop', '~> 1.4', require: false
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do

View File

@@ -1,10 +1,37 @@
# Liquid Change Log
### Unreleased
## 5.0.0 / 2021-01-06
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
### Features
* Add new `{% render %}` tag (#1122) [Samuel Doiron]
* Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
* Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
* Add [usage tracking](README.md#usage-tracking) [Mike Angell]
* Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
* Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
### Fixes
* Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
* Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
* Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
* Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
* Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
* Fix duplication of text in raw tags (#1304) [Peter Zhu]
* Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
* Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
### Breaking Changes
* Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
* Remove support for taint checking (#1268) [Dylan Thacker-Smith]
* Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
* Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
* Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
* And several internal changes
### Performance Improvements
* Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
* Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
## 4.0.3 / 2019-03-12

View File

@@ -18,8 +18,8 @@ module Liquid
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
# rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments)
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
@@ -44,6 +44,8 @@ module Liquid
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
end
yield self if block_given?
# Do this last, since it could result in this object being passed to a Proc in the environment
squash_instance_assigns_with_environments
end
@@ -135,7 +137,7 @@ module Liquid
def new_isolated_subcontext
check_overflow
Context.build(
self.class.build(
resource_limits: resource_limits,
static_environments: static_environments,
registers: StaticRegisters.new(registers)

View File

@@ -42,7 +42,7 @@ module Liquid
end
def render(context)
@body.render(context)
render_to_output_buffer(context, +'')
end
private

View File

@@ -14,7 +14,10 @@ module Liquid
DOUBLE_QUOTED_STRING = /\A\s*"(.*)"\s*\z/m
INTEGERS_REGEX = /\A\s*(-?\d+)\s*\z/
FLOATS_REGEX = /\A\s*(-?\d[\d\.]+)\s*\z/
RANGES_REGEX = /\A\s*\(\s*(\S+)\s*\.\.\s*(\S+)\s*\)\s*\z/
# Use an atomic group (?>...) to avoid pathological backtracing from
# malicious input as described in https://github.com/Shopify/liquid/issues/1357
RANGES_REGEX = /\A\s*\(\s*(?>(\S+)\s*\.\.)\s*(\S+)\s*\)\s*\z/
def self.parse(markup)
case markup

View File

@@ -9,7 +9,12 @@ module Liquid
@index = 0
end
attr_reader :name, :length, :parentloop
attr_reader :length, :parentloop
def name
Usage.increment('forloop_drop_name')
@name
end
def index
@index + 1

View File

@@ -20,7 +20,6 @@
tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
tag_never_closed: "'%{block_name}' tag was never closed"
meta_syntax_error: "Liquid syntax error: #{e.message}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:

View File

@@ -25,7 +25,7 @@ module Liquid
# node.code
#
# # Which template and line number of this node.
# # If top level, this will be "<root>".
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
# node.partial
# node.line_number
#
@@ -46,126 +46,94 @@ module Liquid
include Enumerable
class Timing
attr_reader :code, :partial, :line_number, :children, :total_time, :self_time
attr_reader :code, :template_name, :line_number, :children
attr_accessor :total_time
alias_method :render_time, :total_time
alias_method :partial, :template_name
def initialize(node, partial)
@code = node.respond_to?(:raw) ? node.raw : node
@partial = partial
@line_number = node.respond_to?(:line_number) ? node.line_number : nil
@children = []
def initialize(code: nil, template_name: nil, line_number: nil)
@code = code
@template_name = template_name
@line_number = line_number
@children = []
end
def self.start(node, partial)
new(node, partial).tap(&:start)
end
def start
@start_time = Time.now
end
def finish
@end_time = Time.now
@total_time = @end_time - @start_time
if @children.empty?
@self_time = @total_time
else
total_children_time = 0
def self_time
@self_time ||= begin
total_children_time = 0.0
@children.each do |child|
total_children_time += child.total_time
end
@self_time = @total_time - total_children_time
@total_time - total_children_time
end
end
end
def render_time
@end_time - @start_time
attr_reader :total_time
alias_method :total_render_time, :total_time
def initialize
@root_children = []
@current_children = nil
@total_time = 0.0
end
def profile(template_name, &block)
# nested renders are done from a tag that already has a timing node
return yield if @current_children
root_children = @root_children
render_idx = root_children.length
begin
@current_children = root_children
profile_node(template_name, &block)
ensure
@current_children = nil
if (timing = root_children[render_idx])
@total_time += timing.total_time
end
end
end
def self.profile_node_render(node)
if Profiler.current_profile && node.respond_to?(:render)
Profiler.current_profile.start_node(node)
output = yield
Profiler.current_profile.end_node(node)
output
def children
children = @root_children
if children.length == 1
children.first.children
else
yield
children
end
end
def self.profile_children(template_name)
if Profiler.current_profile
Profiler.current_profile.push_partial(template_name)
output = yield
Profiler.current_profile.pop_partial
output
else
yield
end
end
def self.current_profile
Thread.current[:liquid_profiler]
end
def initialize(partial_name = "<root>")
@partial_stack = [partial_name]
@root_timing = Timing.new("", current_partial)
@timing_stack = [@root_timing]
@render_start_at = Time.now
@render_end_at = @render_start_at
end
def start
Thread.current[:liquid_profiler] = self
@render_start_at = Time.now
end
def stop
Thread.current[:liquid_profiler] = nil
@render_end_at = Time.now
end
def total_render_time
@render_end_at - @render_start_at
end
def each(&block)
@root_timing.children.each(&block)
children.each(&block)
end
def [](idx)
@root_timing.children[idx]
children[idx]
end
def length
@root_timing.children.length
children.length
end
def start_node(node)
@timing_stack.push(Timing.start(node, current_partial))
def profile_node(template_name, code: nil, line_number: nil)
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
parent_children = @current_children
start_time = monotonic_time
begin
@current_children = timing.children
yield
ensure
@current_children = parent_children
timing.total_time = monotonic_time - start_time
parent_children << timing
end
end
def end_node(_node)
timing = @timing_stack.pop
timing.finish
private
@timing_stack.last.children << timing
end
def current_partial
@partial_stack.last
end
def push_partial(partial_name)
@partial_stack.push(partial_name)
end
def pop_partial
@partial_stack.pop
def monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
end
end

View File

@@ -3,19 +3,33 @@
module Liquid
module BlockBodyProfilingHook
def render_node(context, output, node)
Profiler.profile_node_render(node) do
if (profiler = context.profiler)
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
super
end
else
super
end
end
end
BlockBody.prepend(BlockBodyProfilingHook)
module IncludeProfilingHook
module DocumentProfilingHook
def render_to_output_buffer(context, output)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
super
end
return super unless context.profiler
context.profiler.profile(context.template_name) { super }
end
end
Include.prepend(IncludeProfilingHook)
Document.prepend(DocumentProfilingHook)
module ContextProfilingHook
attr_accessor :profiler
def new_isolated_subcontext
new_context = super
new_context.profiler = profiler
new_context
end
end
Context.prepend(ContextProfilingHook)
end

View File

@@ -19,15 +19,16 @@ module Liquid
end
def parse(tokens)
body = new_body
body = case_body = new_body
body = @blocks.last.attachment while parse_body(body, tokens)
@blocks.each do |condition|
@blocks.reverse_each do |condition|
body = condition.attachment
unless body.frozen?
body.remove_blank_strings if blank?
body.freeze
end
end
case_body.freeze
end
def nodelist

View File

@@ -63,11 +63,11 @@ module Liquid
parse_body(@else_block, tokens)
end
if blank?
@for_block.remove_blank_strings
@else_block&.remove_blank_strings
@for_block.remove_blank_strings
end
@for_block.freeze
@else_block&.freeze
@for_block.freeze
end
def nodelist
@@ -198,6 +198,7 @@ module Liquid
case key
when 'offset'
@from = if expr == 'continue'
Usage.increment('for_offset_continue')
:continue
else
parse_expression(expr)

View File

@@ -31,7 +31,7 @@ module Liquid
def parse(tokens)
while parse_body(@blocks.last.attachment, tokens)
end
@blocks.each do |block|
@blocks.reverse_each do |block|
block.attachment.remove_blank_strings if blank?
block.attachment.freeze
end

View File

@@ -3,7 +3,7 @@
module Liquid
class Raw < Block
Syntax = /\A\s*\z/
EndRawTag = /\A(.*)#{TagStart}\s*endraw.*?#{TagEnd}\z/om
FullTokenPossiblyInvalid = /\A(.*)#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}\z/om
def initialize(tag_name, markup, parse_context)
super
@@ -14,7 +14,7 @@ module Liquid
def parse(tokens)
@body = +''
while (token = tokens.shift)
if token =~ EndRawTag
if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
@body << Regexp.last_match(1) if Regexp.last_match(1) != ""
return
end

View File

@@ -106,12 +106,8 @@ module Liquid
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@options = options
@profiling = options[:profile]
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
parse_context = configure_options(options)
@root = Document.parse(tokenize(source), parse_context)
@warnings = parse_context.warnings
self
end
@@ -189,11 +185,13 @@ module Liquid
# Retrying a render resets resource usage
context.resource_limits.reset
if @profiling && context.profiler.nil?
@profiler = context.profiler = Liquid::Profiler.new
end
begin
# render the nodelist.
with_profiling(context) do
@root.render_to_output_buffer(context, output || +'')
end
@root.render_to_output_buffer(context, output || +'')
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@@ -212,25 +210,21 @@ module Liquid
private
def tokenize(source)
Tokenizer.new(source, @line_numbers)
def configure_options(options)
if (profiling = options[:profile])
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
end
@options = options
@profiling = profiling
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@warnings = parse_context.warnings
parse_context
end
def with_profiling(context)
if @profiling && !context.partial
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
@profiler = Profiler.new(context.template_name)
@profiler.start
begin
yield
ensure
@profiler.stop
end
else
yield
end
def tokenize(source)
Tokenizer.new(source, @line_numbers)
end
def apply_options_to_context(context, options)

View File

@@ -2,5 +2,5 @@
# frozen_string_literal: true
module Liquid
VERSION = "4.0.3"
VERSION = "5.0.0"
end

View File

@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
s.required_ruby_version = ">= 2.5.0"
s.required_rubygems_version = ">= 1.3.7"
s.metadata['allowed_push_host'] = 'https://rubygems.org'
s.test_files = Dir.glob("{test}/**/*")
s.files = Dir.glob("{lib}/**/*") + %w(LICENSE README.md)

View File

@@ -44,7 +44,7 @@ class AssignTest < Minitest::Test
end
end
with_error_mode(:lax) do
assert Template.parse("{% assign foo = ('X' | downcase) %}")
assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
end
end

View File

@@ -14,7 +14,7 @@ class BlockTest < Minitest::Test
def test_with_custom_tag
with_custom_tag('testtag', Block) do
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
assert(Liquid::Template.parse("{% testtag %} {% endtesttag %}"))
end
end
@@ -28,13 +28,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
assert_equal 'hello', template.render
assert_equal('hello', template.render)
buf = +''
output = template.render({}, output: buf)
assert_equal 'hello', output
assert_equal 'hello', buf
assert_equal buf.object_id, output.object_id
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
end
klass2 = Class.new(klass1) do
@@ -46,13 +46,13 @@ class BlockTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
assert_equal 'foohellobar', template.render
assert_equal('foohellobar', template.render)
buf = +''
output = template.render({}, output: buf)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf
assert_equal buf.object_id, output.object_id
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
end
end
end

View File

@@ -566,9 +566,9 @@ class ContextTest < Minitest::Test
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")
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(true, context.tag_disabled?("bar"))
assert_equal(false, context.tag_disabled?("unknown"))
end
end
@@ -576,19 +576,19 @@ class ContextTest < Minitest::Test
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")
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")
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")
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")
assert_equal(true, context.tag_disabled?("foo"))
assert_equal(false, context.tag_disabled?("bar"))
end
end
@@ -606,16 +606,16 @@ class ContextTest < Minitest::Test
end
with_global_filter(global) do
assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
assert_equal('Global test', Template.parse("{{'test' | notice }}").render!)
assert_equal('Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local]))
end
end
def test_has_key_will_not_add_an_error_for_missing_keys
with_error_mode :strict do
with_error_mode(:strict) do
context = Context.new
context.key?('unknown')
assert_empty context.errors
assert_empty(context.errors)
end
end

View File

@@ -167,7 +167,7 @@ class DropsTest < Minitest::Test
def test_object_methods_not_allowed
[:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
assert_equal ' ', output
assert_equal(' ', output)
end
end
@@ -210,28 +210,28 @@ class DropsTest < Minitest::Test
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
["select", "each", "map", "cycle"].each do |method|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
assert_equal(method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal(method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
end
end
def test_some_enumerable_methods_still_get_invoked
[:count, :max].each do |method|
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
assert_equal("3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
end
assert_equal("yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new))
[:min, :first].each do |method|
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new))
assert_equal("1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new))
end
end

View File

@@ -193,7 +193,7 @@ class ErrorHandlingTest < Minitest::Test
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
def test_exceptions_propagate
assert_raises Exception do
assert_raises(Exception) do
template = Liquid::Template.parse('{{ errors.exception }}')
template.render('errors' => ErrorDrop.new)
end

View File

@@ -169,9 +169,9 @@ class FiltersInTemplate < Minitest::Test
def test_local_global
with_global_filter(MoneyFilter) do
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
assert_equal(" 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil))
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter))
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter]))
end
end

View File

@@ -19,7 +19,7 @@ class HashOrderingTest < Minitest::Test
def test_global_register_order
with_global_filter(MoneyFilter, CanadianMoneyFilter) do
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil)
assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil))
end
end
end

View File

@@ -32,7 +32,7 @@ class ParsingQuirksTest < Minitest::Test
assert(Template.parse("{{test}}"))
with_error_mode(:lax) do
assert Template.parse("{{|test}}")
assert(Template.parse("{{|test}}"))
end
with_error_mode(:strict) do

View File

@@ -2,7 +2,7 @@
require 'test_helper'
class RenderProfilingTest < Minitest::Test
class ProfilerTest < Minitest::Test
include Liquid
class ProfilingFileSystem
@@ -62,6 +62,17 @@ class RenderProfilingTest < Minitest::Test
assert_equal(2, included_children[1].line_number)
end
def test_profiling_render_tag
t = Template.parse("{% render 'a_template' %}", profile: true)
t.render!
render_children = t.profiler[0].children
render_children.each do |timing|
assert_equal('a_template', timing.partial)
end
assert_equal([1, 2], render_children.map(&:line_number))
end
def test_profiling_times_the_rendering_of_tokens
t = Template.parse("{% include 'a_template' %}", profile: true)
t.render!
@@ -77,6 +88,37 @@ class RenderProfilingTest < Minitest::Test
assert(t.profiler.total_render_time >= 0, "Total render time was not calculated")
end
class SleepTag < Liquid::Tag
def initialize(tag_name, markup, parse_context)
super
@duration = Float(markup)
end
def render_to_output_buffer(_context, _output)
sleep(@duration)
end
end
def test_profiling_multiple_renders
with_custom_tag('sleep', SleepTag) do
context = Liquid::Context.new
t = Liquid::Template.parse("{% sleep 0.001 %}", profile: true)
context.template_name = 'index'
t.render!(context)
context.template_name = 'layout'
first_render_time = context.profiler.total_time
t.render!(context)
profiler = context.profiler
children = profiler.children
assert_operator(first_render_time, :>=, 0.001)
assert_operator(profiler.total_time, :>=, 0.001 + first_render_time)
assert_equal(["index", "layout"], children.map(&:template_name))
assert_equal([nil, nil], children.map(&:code))
assert_equal(profiler.total_time, children.map(&:total_time).reduce(&:+))
end
end
def test_profiling_uses_include_to_mark_children
t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", profile: true)
t.render!
@@ -91,7 +133,7 @@ class RenderProfilingTest < Minitest::Test
include_node = t.profiler[1]
include_node.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
end
@@ -101,12 +143,12 @@ class RenderProfilingTest < Minitest::Test
a_template = t.profiler[1]
a_template.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
b_template = t.profiler[2]
b_template.children.each do |child|
assert_equal "b_template", child.partial
assert_equal("b_template", child.partial)
end
end
@@ -116,12 +158,12 @@ class RenderProfilingTest < Minitest::Test
a_template1 = t.profiler[1]
a_template1.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
a_template2 = t.profiler[2]
a_template2.children.each do |child|
assert_equal "a_template", child.partial
assert_equal("a_template", child.partial)
end
end

View File

@@ -159,7 +159,7 @@ class StandardFiltersTest < Minitest::Test
assert_equal('1', @filters.url_decode(1))
assert_equal('2001-02-03', @filters.url_decode(Date.new(2001, 2, 3)))
assert_nil(@filters.url_decode(nil))
exception = assert_raises Liquid::ArgumentError do
exception = assert_raises(Liquid::ArgumentError) do
@filters.url_decode('%ff')
end
assert_equal('Liquid error: invalid byte sequence in UTF-8', exception.message)
@@ -286,7 +286,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.sort(foo, "bar")
end
end
@@ -302,7 +302,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.sort_natural(foo, "bar")
end
end
@@ -337,7 +337,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.uniq(foo, "bar")
end
end
@@ -353,7 +353,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.compact(foo, "bar")
end
end
@@ -430,7 +430,7 @@ class StandardFiltersTest < Minitest::Test
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.map(foo, "bar")
end
end
@@ -441,7 +441,7 @@ class StandardFiltersTest < Minitest::Test
[2],
[3],
]
assert_raises Liquid::ArgumentError do
assert_raises(Liquid::ArgumentError) do
@filters.map(foo, nil)
end
end
@@ -485,8 +485,8 @@ class StandardFiltersTest < Minitest::Test
assert_equal('', @filters.date('', "%B"))
with_timezone("UTC") do
assert_equal "07/05/2006", @filters.date(1152098955, "%m/%d/%Y")
assert_equal "07/05/2006", @filters.date("1152098955", "%m/%d/%Y")
assert_equal("07/05/2006", @filters.date(1152098955, "%m/%d/%Y"))
assert_equal("07/05/2006", @filters.date("1152098955", "%m/%d/%Y"))
end
end
@@ -587,7 +587,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("0.5", "{{ 2.0 | divided_by:4 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result("4", "{{ 1 | modulo: 0 }}")
end
assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
@@ -596,7 +596,7 @@ class StandardFiltersTest < Minitest::Test
def test_modulo
assert_template_result("1", "{{ 3 | modulo:2 }}")
assert_raises(Liquid::ZeroDivisionError) do
assert_template_result "4", "{{ 1 | modulo: 0 }}"
assert_template_result("4", "{{ 1 | modulo: 0 }}")
end
assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
@@ -607,7 +607,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ '4.3' | round }}")
assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | round }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
end
assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
@@ -618,7 +618,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
assert_template_result("5", "{{ '4.3' | ceil }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | ceil }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
end
assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
@@ -628,7 +628,7 @@ class StandardFiltersTest < Minitest::Test
assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
assert_template_result("4", "{{ '4.3' | floor }}")
assert_raises(Liquid::FloatDomainError) do
assert_template_result "4", "{{ 1.0 | divided_by: 0.0 | floor }}"
assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
end
assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))

View File

@@ -15,13 +15,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal 'hello', template.render
assert_equal('hello', template.render)
buf = +''
output = template.render({}, output: buf)
assert_equal 'hello', output
assert_equal 'hello', buf
assert_equal buf.object_id, output.object_id
assert_equal('hello', output)
assert_equal('hello', buf)
assert_equal(buf.object_id, output.object_id)
end
klass2 = Class.new(klass1) do
@@ -33,13 +33,13 @@ class TagTest < Minitest::Test
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %}")
assert_equal 'foohellobar', template.render
assert_equal('foohellobar', template.render)
buf = +''
output = template.render({}, output: buf)
assert_equal 'foohellobar', output
assert_equal 'foohellobar', buf
assert_equal buf.object_id, output.object_id
assert_equal('foohellobar', output)
assert_equal('foohellobar', buf)
assert_equal(buf.object_id, output.object_id)
end
end
end

View File

@@ -437,4 +437,30 @@ HERE
assert(context.registers[:for_stack].empty?)
end
def test_instrument_for_offset_continue
assert_usage_increment('for_offset_continue') do
Template.parse('{% for item in items offset:continue %}{{item}}{% endfor %}')
end
assert_usage_increment('for_offset_continue', times: 0) do
Template.parse('{% for item in items offset:2 %}{{item}}{% endfor %}')
end
end
def test_instrument_forloop_drop_name
assigns = { 'items' => [1, 2, 3, 4, 5] }
assert_usage_increment('forloop_drop_name', times: 5) do
Template.parse('{% for item in items %}{{forloop.name}}{% endfor %}').render!(assigns)
end
assert_usage_increment('forloop_drop_name', times: 0) do
Template.parse('{% for item in items %}{{forloop.index}}{% endfor %}').render!(assigns)
end
assert_usage_increment('forloop_drop_name', times: 0) do
Template.parse('{% for item in items %}{{item}}{% endfor %}').render!(assigns)
end
end
end

View File

@@ -184,7 +184,7 @@ class IfElseTagTest < Minitest::Test
tests.each do |vals, expected|
a, b, c = vals
assigns = { 'a' => a, 'b' => b, 'c' => c }
assert_template_result expected.to_s, tpl, assigns, assigns.to_s
assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
end
end
end

View File

@@ -226,13 +226,13 @@ class IncludeTagTest < Minitest::Test
Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}')
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: true).render!("template" => '{{ "X" || downcase }}'))
end
assert_raises(Liquid::SyntaxError) do
Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:locale]).render!("template" => '{{ "X" || downcase }}')
end
with_error_mode(:lax) do
assert_equal 'x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}')
assert_equal('x', Template.parse("{% include template %}", error_mode: :strict, include_options_blacklist: [:error_mode]).render!("template" => '{{ "X" || downcase }}'))
end
end

View File

@@ -12,8 +12,6 @@ class RawTagTest < Minitest::Test
def test_output_in_raw
assert_template_result('{{ test }}', '{% raw %}{{ test }}{% endraw %}')
assert_template_result('test', '{% raw %}test{% endraw{% f %}')
assert_template_result('test', '{% raw %}test{% endraw{% |f %}')
end
def test_open_tag_in_raw

View File

@@ -58,7 +58,7 @@ class RenderTagTest < Minitest::Test
def test_recursively_rendered_template_does_not_produce_endless_loop
Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
assert_raises Liquid::StackLevelError do
assert_raises(Liquid::StackLevelError) do
Template.parse('{% render "loop" %}').render!
end
end
@@ -67,7 +67,7 @@ class RenderTagTest < Minitest::Test
Liquid::Template.file_system = StubFileSystem.new(
'loop_render' => '{% render "loop_render" %}',
)
assert_raises Liquid::StackLevelError do
assert_raises(Liquid::StackLevelError) do
Template.parse('{% render "loop_render" %}').render!
end
end
@@ -75,7 +75,7 @@ class RenderTagTest < Minitest::Test
def test_dynamically_choosen_templates_are_not_allowed
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
assert_raises Liquid::SyntaxError do
assert_raises(Liquid::SyntaxError) do
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
end
end

View File

@@ -198,7 +198,7 @@ class TemplateTest < Minitest::Test
context = Context.new('drop' => ErroneousDrop.new)
t = Template.new.parse('{{ drop.bad_method }}')
e = assert_raises RuntimeError do
e = assert_raises(RuntimeError) do
t.render!(context)
end
assert_equal('ruby error in drop', e.message)
@@ -267,7 +267,7 @@ class TemplateTest < Minitest::Test
def test_undefined_variables_raise
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
assert_raises UndefinedVariable do
assert_raises(UndefinedVariable) do
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
end
end
@@ -286,7 +286,7 @@ class TemplateTest < Minitest::Test
d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}')
assert_raises UndefinedDropMethod do
assert_raises(UndefinedDropMethod) do
t.render!(d, strict_variables: true)
end
end
@@ -309,7 +309,7 @@ class TemplateTest < Minitest::Test
def test_undefined_filters_raise
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
assert_raises UndefinedFilter do
assert_raises(UndefinedFilter) do
t.render!({ 'x' => 'foo' }, strict_filters: true)
end
end

View File

@@ -556,4 +556,8 @@ class TrimModeTest < Minitest::Test
template = Liquid::Template.parse("B\n {%- if true %}{% endif %}", bug_compatible_whitespace_trimming: true)
assert_equal("B", template.render)
end
def test_trim_blank
assert_template_result('foobar', 'foo {{-}} bar')
end
end # TrimModeTest

View File

@@ -28,7 +28,7 @@ class I18nUnitTest < Minitest::Test
# end
def test_raises_unknown_translation
assert_raises I18n::TranslationError do
assert_raises(I18n::TranslationError) do
@i18n.translate("doesnt_exist")
end
end

View File

@@ -108,7 +108,7 @@ class VariableUnitTest < Minitest::Test
assert_equal(VariableLookup.new('foo-bar'), create_variable('foo-bar').name)
assert_equal(VariableLookup.new('foo-bar-2'), create_variable('foo-bar-2').name)
with_error_mode :strict do
with_error_mode(:strict) do
assert_raises(Liquid::SyntaxError) { create_variable('foo - bar') }
assert_raises(Liquid::SyntaxError) { create_variable('-foo') }
assert_raises(Liquid::SyntaxError) { create_variable('2foo') }