Compare commits

...

39 Commits

Author SHA1 Message Date
Mike Angell
5b3e50f7ae Shortcut for for statement 2019-08-28 05:11:10 +10:00
Mike Angell
3ef7eead27 Stack scope by variable and not by level 2019-08-28 04:25:26 +10:00
Florian Weingarten
250048717c dunnololtest 2019-04-25 10:29:04 -04:00
Florian Weingarten
78d2a437ff rubocop 2019-04-25 10:29:04 -04:00
Florian Weingarten
af614f3a2e Implicit variable scoping 2019-04-25 10:28:43 -04:00
Florian Weingarten
b3b63a683f Merge pull request #1097 from ashmaroli/stackprof-no-jruby
Don't attempt to install stackprof gem on JRuby
2019-04-24 09:11:44 -04:00
Ashwin Maroli
1c577c5b62 Don't attempt to install stackprof gem on JRuby 2019-04-24 11:31:20 +05:30
David Cornu
755d2821f3 Merge pull request #1094 from Shopify/for-tag/invalid-limit-offset
Make sure the for tag's limit and offset are integers
2019-04-23 17:20:54 -04:00
David Cornu
495b3d312f Merge pull request #1095 from Shopify/travis/remove-rainbow-gem
Stop installing the rainbow gem on Travis
2019-04-23 17:20:38 -04:00
David Cornu
453f6348c2 Stop installing the rainbow gem on Travis 2019-04-23 16:55:37 -04:00
David Cornu
70ed1fc86d Make sure the limit and offset values are integers 2019-04-23 16:44:37 -04:00
Florian Weingarten
c2ef247be5 Merge pull request #1092 from Shopify/rake-memory-profiler-task
rake memory_profile:run
2019-04-22 16:33:32 -04:00
Florian Weingarten
1518d3f6f9 Merge pull request #1093 from Shopify/bytesize-not-length
use bytesize, not length
2019-04-18 18:39:21 +01:00
Florian Weingarten
c67b77709d rake memory_profile:run 2019-04-17 19:09:26 +01:00
Florian Weingarten
c89ce9c2ed use bytesize, not length 2019-04-17 18:55:13 +01:00
Richard Monette
b0629f17f7 Merge pull request #1073 from Shopify/defer-alloc-hash
defer hash allocation until needed for unparsed_args
2019-03-20 13:34:48 -04:00
Richard Monette
274f078806 defer hash allocation in parse_filter_expressions
add exploration of GC object allocation

remove performance test

can actually remove one more if branch

use named locals to improve readability
2019-03-20 13:20:31 -04:00
Richard Monette
d7171aa084 Merge pull request #1077 from Shopify/update-cops-for-trailing-comma
update Rubocop for trailing comma styles
2019-03-19 16:02:26 -04:00
Richard Monette
06c4789dc5 update Rubocop for trailing comma styles 2019-03-19 11:05:05 -04:00
Justin Li
f2f467bdbc v4.0.3 2019-03-12 12:43:48 -04:00
Justin Li
ff99d92c18 Merge pull request #1072 from Shopify/fix-interrupts
Fix interrupts through includes
2019-03-12 12:26:12 -04:00
Justin Li
39fecd06db Fix interrupts through includes 2019-03-12 12:18:22 -04:00
Justin Li
8013df8ca2 v4.0.2 2019-03-08 15:43:46 -05:00
Clayton Smith
14cd011cb5 Merge pull request #1070 from Shopify/url-decode-validation
Validate the character encoding in url_decode.
2019-03-08 11:09:40 -05:00
Clayton Smith
e2d9907df2 Validate the character encoding in url_decode. 2019-03-07 14:01:10 -05:00
Justin Li
23d669f5e6 Merge pull request #1032 from printercu/patch-1
Single regexp for strip_html
2019-02-22 13:04:04 -05:00
Justin Li
ed73794f82 Preserve existing strip_html behaviour for weird inputs 2019-02-22 13:00:36 -05:00
Ashwin Maroli
f59f6dea83 Fix simple RuboCop offenses and update TODO file (#1062)
* Fix Layout/EmptyLineAfterMagicComment offense

* Fix Layout/ExtraSpacing offense

* Fix Layout/ClosingParenthesisIndentation offenses

* Fix Style/MutableConstant offense

* Fix Style/UnneededInterpolation offenses

* Fix Style/RedundantParentheses offenses

* Update TODO config for RuboCop

* Add executable bit to test/test_helper.rb

ref: https://travis-ci.org/Shopify/liquid/jobs/488169512#L578
2019-02-22 12:32:56 -05:00
Garland Zhang
7a81fb821a Merge pull request #1059 from Shopify/map_error_checking
Apply error-checking to sort, sort_natural, where, uniq, map, compact filter(s)
2019-02-22 10:42:16 -05:00
Garland Zhang
cec27ea326 Extract raise error line and some filters with begin/rescue blocks 2019-02-21 17:00:20 -05:00
Justin Li
14999e8f7c Merge pull request #1053 from er1/update-changelog-v4.0.1
Updated changelog for v4.0.1 for (#1038)
2019-02-10 10:37:31 -05:00
Eric Chan
b41fc10d8e Updated changelog for v4.0.1 2018-12-03 23:54:00 -05:00
David Cornu
2b3c81cfd0 Merge pull request #1046 from Shopify/make-builds-green
Make builds green
2018-10-24 10:46:01 -04:00
David Cornu
2a2376bfd9 Run :test before :rubocop in the default Rake task 2018-10-19 15:06:36 -04:00
David Cornu
ca9e75db53 Reduce perceived complexity for #sort and #sort_natural 2018-10-19 14:57:33 -04:00
David Cornu
407c8abf30 Use TrailingCommaInLiteral
TrailingCommaInArrayLiteral and TrailingCommaInHashLiteral were introduced in v0.53.0 and we're running v0.49.0.

https://github.com/rubocop-hq/rubocop/blob/master/CHANGELOG.md#0530-2018-03-05
2018-10-19 14:52:16 -04:00
Justin Li
43f181e211 Merge pull request #1044 from Shopify/enable-cla-bot
Enable CLA bot
2018-10-19 09:34:30 -04:00
printercu
89c1ba2b0e Fix rubocop warning 2018-09-27 17:24:01 +03:00
printercu
479d8fb4a4 Single regexp for strip_html 2018-09-27 17:13:35 +03:00
39 changed files with 554 additions and 334 deletions

View File

@@ -1,4 +1,6 @@
inherit_from: ./.rubocop_todo.yml
inherit_from:
- .rubocop_todo.yml
- ./.rubocop_todo.yml
AllCops:
Exclude:
@@ -29,7 +31,7 @@ Lint/ParenthesesAsGroupedExpression:
Lint/UnusedBlockArgument:
Enabled: false
Lint/EndAlignment:
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Lint/UnusedMethodArgument:
@@ -59,7 +61,7 @@ Style/BracesAroundHashParameters:
Style/NumericLiterals:
Enabled: false
Layout/SpaceInsideBrackets:
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
Layout/SpaceBeforeBlockBraces:
@@ -107,7 +109,7 @@ Style/SymbolLiteral:
Performance/Count:
Enabled: false
Style/ConstantName:
Naming/ConstantName:
Enabled: false
Layout/CaseIndentation:

View File

@@ -1,33 +1,22 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2017-11-22 11:35:55 -0500 using RuboCop version 0.49.1.
# on 2019-03-19 11:04:37 -0400 using RuboCop version 0.53.0.
# 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
# versions of RuboCop, may require this file to be generated again.
# Offense count: 3
# Cop supports --auto-correct.
Layout/ClosingParenthesisIndentation:
Exclude:
- 'test/integration/error_handling_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
Layout/EmptyLineAfterMagicComment:
# Configuration parameters: Include, TreatCommentsAsGroupSeparators.
# Include: **/*.gemspec
Gemspec/OrderedDependencies:
Exclude:
- 'lib/liquid/version.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Exclude:
- 'test/integration/parsing_quirks_test.rb'
- 'liquid.gemspec'
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
Layout/IndentHeredoc:
Exclude:
@@ -36,7 +25,7 @@ Layout/IndentHeredoc:
# Offense count: 6
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: symmetrical, new_line, same_line
Layout/MultilineMethodCallBraceLayout:
Exclude:
@@ -45,43 +34,65 @@ Layout/MultilineMethodCallBraceLayout:
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: runtime_error, standard_error
Lint/InheritException:
Exclude:
- 'lib/liquid/interrupts.rb'
# Offense count: 1
Lint/ScriptPermission:
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'test/test_helper.rb'
- 'lib/liquid/parse_context.rb'
# Offense count: 52
# Offense count: 54
Metrics/AbcSize:
Max: 56
# Offense count: 13
# Offense count: 12
Metrics/CyclomaticComplexity:
Max: 12
# Offense count: 620
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294
# Offense count: 102
# Offense count: 112
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 37
# Offense count: 9
# Offense count: 8
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 52
# Configuration parameters: Blacklist.
# Blacklist: END, (?-mix:EO[A-Z]{1})
Naming/HeredocDelimiterNaming:
Exclude:
- 'test/integration/assign_test.rb'
- 'test/integration/capture_test.rb'
- 'test/integration/trim_mode_test.rb'
# Offense count: 23
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id
Naming/UncommunicativeMethodParamName:
Exclude:
- 'example/server/example_servlet.rb'
- 'lib/liquid/condition.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/utils.rb'
- 'lib/liquid/variable.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 10
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: prefer_alias, prefer_alias_method
Style/Alias:
Exclude:
@@ -93,14 +104,23 @@ Style/Alias:
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/variable.rb'
# Offense count: 22
Style/CommentedKeyword:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'lib/liquid/errors.rb'
# Offense count: 1
Style/DateTime:
Exclude:
- 'test/unit/context_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
@@ -110,7 +130,7 @@ Style/EmptyCaseCondition:
# Offense count: 5
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
Exclude:
@@ -120,11 +140,28 @@ Style/EmptyMethod:
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
Style/Encoding:
Exclude:
- 'lib/liquid/version.rb'
- 'liquid.gemspec'
- 'test/integration/standard_filter_test.rb'
# Offense count: 2
# Configuration parameters: SupportedStyles.
# SupportedStyles: annotated, template
# Cop supports --auto-correct.
Style/ExpandPathArguments:
Exclude:
- 'Rakefile'
- 'liquid.gemspec'
# Offense count: 7
# Configuration parameters: EnforcedStyle.
# SupportedStyles: annotated, template, unannotated
Style/FormatStringToken:
EnforcedStyle: template
Exclude:
- 'test/integration/filter_test.rb'
- 'test/integration/hash_ordering_test.rb'
# Offense count: 14
# Configuration parameters: MinBodyLength.
@@ -143,26 +180,9 @@ Style/GuardClause:
- 'lib/liquid/variable.rb'
- 'test/unit/tokenizer_unit_test.rb'
# Offense count: 4
# Configuration parameters: SupportedStyles.
# SupportedStyles: snake_case, camelCase
Style/MethodName:
EnforcedStyle: snake_case
# Offense count: 6
# Cop supports --auto-correct.
Style/MutableConstant:
Exclude:
- 'lib/liquid/expression.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/variable_lookup.rb'
- 'lib/liquid/version.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
# Configuration parameters: EnforcedStyle, MinBodyLength.
# SupportedStyles: skip_modifier_ifs, always
Style/Next:
Exclude:
@@ -170,7 +190,7 @@ Style/Next:
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
@@ -190,12 +210,6 @@ Style/PercentLiteralDelimiters:
- 'test/integration/assign_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/RedundantParentheses:
Exclude:
- 'test/unit/condition_unit_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/RedundantSelf:
@@ -213,27 +227,20 @@ Style/Semicolon:
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: MinSize, SupportedStyles.
# Configuration parameters: MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
EnforcedStyle: brackets
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment.
# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
Style/TernaryParentheses:
Exclude:
- 'lib/liquid/context.rb'
- 'lib/liquid/utils.rb'
# Offense count: 4
# Cop supports --auto-correct.
Style/UnneededInterpolation:
Exclude:
- 'lib/liquid/i18n.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/UnneededPercentQ:
@@ -242,7 +249,12 @@ Style/UnneededPercentQ:
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: MaxLineLength.
Style/WhileUntilModifier:
Exclude:
- 'lib/liquid/tags/case.rb'
# Offense count: 640
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294

View File

@@ -23,7 +23,6 @@ matrix:
- rvm: jruby-head
install:
- gem install rainbow -v 2.2.1
- bundle install
script: bundle exec rake

View File

@@ -5,14 +5,17 @@ end
gemspec
gem 'stackprof', platforms: :mri
group :benchmark, :test do
gem 'benchmark-ips'
gem 'memory_profiler'
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
gem 'stackprof'
end
end
group :test do
gem 'rubocop', '~> 0.49.0'
gem 'rubocop', '~> 0.53.0'
platform :mri do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'

View File

@@ -1,5 +1,58 @@
# Liquid Change Log
## 4.0.3 / 2019-03-12
### Fixed
* Fix break and continue tags inside included templates in loops (#1072) [Justin Li]
## 4.0.2 / 2019-03-08
### Changed
* Add `where` filter (#1026) [Samuel Doiron]
* Add `ParseTreeVisitor` to iterate the Liquid AST (#1025) [Stephen Paul Weber]
* Improve `strip_html` performance (#1032) [printercu]
### Fixed
* Add error checking for invalid combinations of inputs to sort, sort_natural, where, uniq, map, compact filters (#1059) [Garland Zhang]
* Validate the character encoding in url_decode (#1070) [Clayton Smith]
## 4.0.1 / 2018-10-09
### Changed
* Add benchmark group in Gemfile (#855) [Jerry Liu]
* Allow benchmarks to benchmark render by itself (#851) [Jerry Liu]
* Avoid calling `line_number` on String node when rescuing a render error. (#860) [Dylan Thacker-Smith]
* Avoid duck typing to detect whether to call render on a node. [Dylan Thacker-Smith]
* Clarify spelling of `reversed` on `for` block tag (#843) [Mark Crossfield]
* Replace recursion with loop to avoid potential stack overflow from malicious input (#891, #892) [Dylan Thacker-Smith]
* Limit block tag nesting to 100 (#894) [Dylan Thacker-Smith]
* Replace `assert_equal nil` with `assert_nil` (#895) [Dylan Thacker-Smith]
* Remove Spy Gem (#896) [Dylan Thacker-Smith]
* Add `collection_name` and `variable_name` reader to `For` block (#909)
* Symbols render as strings (#920) [Justin Li]
* Remove default value from Hash objects (#932) [Maxime Bedard]
* Remove one level of nesting (#944) [Dylan Thacker-Smith]
* Update Rubocop version (#952) [Justin Li]
* Add `at_least` and `at_most` filters (#954, #958) [Nithin Bekal]
* Add a regression test for a liquid-c trim mode bug (#972) [Dylan Thacker-Smith]
* Use https rather than git protocol to fetch liquid-c [Dylan Thacker-Smith]
* Add tests against Ruby 2.4 (#963) and 2.5 (#981)
* Replace RegExp literals with constants (#988) [Ashwin Maroli]
* Replace unnecessary `#each_with_index` with `#each` (#992) [Ashwin Maroli]
* Improve the unexpected end delimiter message for block tags. (#1003) [Dylan Thacker-Smith]
* Refactor and optimize rendering (#1005) [Christopher Aue]
* Add installation instruction (#1006) [Ben Gift]
* Remove Circle CI (#1010)
* Rename deprecated `BigDecimal.new` to `BigDecimal` (#1024) [Koichi ITO]
* Rename deprecated Rubocop name (#1027) [Justin Li]
### Fixed
* Handle `join` filter on non String joiners (#857) [Richard Monette]
* Fix duplicate inclusion condition logic error of `Liquid::Strainer.add_filter` method (#861)
* Fix `escape`, `url_encode`, `url_decode` not handling non-string values (#898) [Thierry Joyal]
* Fix raise when variable is defined but nil when using `strict_variables` [Pascal Betz]
* Fix `sort` and `sort_natural` to handle arrays with nils (#930) [Eric Chan]
## 4.0.0 / 2016-12-14 / branch "4-0-stable"
### Changed

View File

@@ -3,7 +3,7 @@ require 'rake/testtask'
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
require "liquid/version"
task default: [:rubocop, :test]
task default: [:test, :rubocop]
desc 'run test suite with default parser'
Rake::TestTask.new(:base_test) do |t|
@@ -85,6 +85,13 @@ namespace :profile do
end
end
namespace :memory_profile do
desc "Run memory profiler"
task :run do
ruby "./performance/memory_profile.rb"
end
end
desc "Run example"
task :example do
ruby "-w -d -Ilib example/server/server.rb"

View File

@@ -89,6 +89,7 @@ module Liquid
break
else # Other non-Block tags
render_node_to_output(node, output, context)
break if context.interrupt? # might have happened through an include
end
idx += 1
end
@@ -114,7 +115,7 @@ module Liquid
end
def check_resources(context, node_output)
context.resource_limits.render_length += node_output.length
context.resource_limits.render_length += node_output.bytesize
return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze)
end

View File

@@ -12,12 +12,12 @@ module Liquid
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_reader :scope, :errors, :registers, :environments, :resource_limits
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@scope = outer_scope || {}
@registers = registers
@errors = []
@partial = false
@@ -25,8 +25,6 @@ module Liquid
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
squash_instance_assigns_with_environments
@this_stack_used = false
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
self.exception_renderer = ->(e) { raise }
@@ -35,6 +33,8 @@ module Liquid
@interrupts = []
@filters = []
@global_filter = nil
@stack_level = 0
end
def warnings
@@ -86,21 +86,9 @@ module Liquid
strainer.invoke(method, *args).to_liquid
end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope = {})
@scopes.unshift(new_scope)
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
end
# Merge a hash of variables in the current local scope
def merge(new_scopes)
@scopes[0].merge!(new_scopes)
end
# Pop from the stack. use <tt>Context#stack</tt> instead
def pop
raise ContextError if @scopes.size == 1
@scopes.shift
new_scopes.each { |k, v| self[k] = v }
end
# Pushes a new local scope on the stack, pops it at the end of the block
@@ -111,32 +99,20 @@ module Liquid
# end
#
# context['var] #=> nil
def stack(new_scope = nil)
old_stack_used = @this_stack_used
if new_scope
push(new_scope)
@this_stack_used = true
else
@this_stack_used = false
def stack(*variable_names)
@stack_level += 1
raise StackLevelError, "Nesting too deep".freeze if @stack_level > Block::MAX_DEPTH
begin
yield
ensure
@stack_level -= 1
end
yield
ensure
pop if @this_stack_used
@this_stack_used = old_stack_used
end
def clear_instance_assigns
@scopes[0] = {}
end
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
def []=(key, value)
unless @this_stack_used
@this_stack_used = true
push({})
end
@scopes[0][key] = value
(@scope[key] ||= [nil]) << value
end
# Look up variable, either resolve directly after considering the name. We can directly handle
@@ -151,6 +127,29 @@ module Liquid
evaluate(Expression.parse(expression))
end
def unset(key)
if @scope[key].size <= 1
@scope.delete(key)
else
@scope[key].pop
end
end
def set_root(key, val)
@scope[key] ||= []
@scope[key][0] = val
end
def set_level(key, val, int)
@scope[key] ||= []
@scope[key][int] = val
end
def create_level(key)
(@scope[key] ||= [nil]) << nil
@scope[key].size - 1
end
def key?(key)
self[key] != nil
end
@@ -161,27 +160,23 @@ module Liquid
# Fetches an object starting at the local scope and then moving up the hierachy
def find_variable(key, raise_on_not_found: true)
# This was changed from find() to find_index() because this is a very hot
# path and find_index() is optimized in MRI to reduce object allocation
index = @scopes.find_index { |s| s.key?(key) }
scope = @scopes[index] if index
variable = nil
trigger = false
value = @scope[key]
scope = @scope unless value.nil?
trigger = true unless value.nil?
if scope.nil?
@environments.each do |e|
index = @environments.find_index do |e|
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
# When lookup returned a value OR there is no value but the lookup also did not raise
# then it is the value we are looking for.
if !variable.nil? || @strict_variables && raise_on_not_found
scope = e
break
end
!variable.nil? || @strict_variables && raise_on_not_found
end
scope = @environments[index || -1]
end
scope ||= @environments.last || @scopes.last
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
variable ||= lookup_and_evaluate(scope, key, trigger, raise_on_not_found: raise_on_not_found)
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
@@ -189,12 +184,16 @@ module Liquid
variable
end
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
def lookup_and_evaluate(obj, key, trigger = false, raise_on_not_found: true)
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
raise Liquid::UndefinedVariable, "undefined variable #{key}"
end
value = obj[key]
value = if trigger == true
obj[key][-1]
else
obj[key]
end
if value.is_a?(Proc) && obj.respond_to?(:[]=)
obj[key] = (value.arity == 0) ? value.call : value.call(self)
@@ -213,10 +212,10 @@ module Liquid
end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@scope.each_key do |k|
@environments.each do |env|
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
@scope[k] = [lookup_and_evaluate(env, k)]
break
end
end

View File

@@ -19,7 +19,7 @@ module Liquid
'false'.freeze => false,
'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
}
}.freeze
SINGLE_QUOTED_STRING = /\A'(.*)'\z/m
DOUBLE_QUOTED_STRING = /\A"(.*)"\z/m

View File

@@ -26,7 +26,7 @@ module Liquid
def interpolate(name, vars)
name.gsub(/%\{(\w+)\}/) do
# raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
"#{vars[$1.to_sym]}"
(vars[$1.to_sym]).to_s
end
end

View File

@@ -12,7 +12,7 @@ module Liquid
')'.freeze => :close_round,
'?'.freeze => :question,
'-'.freeze => :dash
}
}.freeze
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
SINGLE_STRING_LITERAL = /'[^\']*'/
DOUBLE_STRING_LITERAL = /"[^\"]*"/

View File

@@ -9,8 +9,14 @@ module Liquid
'<'.freeze => '&lt;'.freeze,
'"'.freeze => '&quot;'.freeze,
"'".freeze => '&#39;'.freeze
}
}.freeze
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
STRIP_HTML_BLOCKS = Regexp.union(
/<script.*?<\/script>/m,
/<!--.*?-->/m,
/<style.*?<\/style>/m
)
STRIP_HTML_TAGS = /<.*?>/m
# Return the size of an array or of an string
def size(input)
@@ -46,7 +52,12 @@ module Liquid
end
def url_decode(input)
CGI.unescape(input.to_s) unless input.nil?
return if input.nil?
result = CGI.unescape(input.to_s)
raise Liquid::ArgumentError, "invalid byte sequence in #{result.encoding}" unless result.valid_encoding?
result
end
def slice(input, offset, length = nil)
@@ -103,7 +114,9 @@ module Liquid
def strip_html(input)
empty = ''.freeze
input.to_s.gsub(/<script.*?<\/script>/m, empty).gsub(/<!--.*?-->/m, empty).gsub(/<style.*?<\/style>/m, empty).gsub(/<.*?>/m, empty)
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
result.gsub!(STRIP_HTML_TAGS, empty)
result
end
# Remove all newlines from the string
@@ -120,25 +133,18 @@ module Liquid
# provide optional property with which to sort an array of hashes or drops
def sort(input, property = nil)
ary = InputIterator.new(input)
return [] if ary.empty?
if property.nil?
ary.sort do |a, b|
if !a.nil? && !b.nil?
a <=> b
else
a.nil? ? 1 : -1
end
nil_safe_compare(a, b)
end
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.all? { |el| el.respond_to?(:[]) }
ary.sort do |a, b|
a = a[property]
b = b[property]
if !a.nil? && !b.nil?
a <=> b
else
a.nil? ? 1 : -1
end
begin
ary.sort { |a, b| nil_safe_compare(a[property], b[property]) }
rescue TypeError
raise_property_error(property)
end
end
end
@@ -148,25 +154,17 @@ module Liquid
def sort_natural(input, property = nil)
ary = InputIterator.new(input)
return [] if ary.empty?
if property.nil?
ary.sort do |a, b|
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
else
a.nil? ? 1 : -1
end
nil_safe_casecmp(a, b)
end
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.all? { |el| el.respond_to?(:[]) }
ary.sort do |a, b|
a = a[property]
b = b[property]
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
else
a.nil? ? 1 : -1
end
begin
ary.sort { |a, b| nil_safe_casecmp(a[property], b[property]) }
rescue TypeError
raise_property_error(property)
end
end
end
@@ -179,9 +177,17 @@ module Liquid
if ary.empty?
[]
elsif ary.first.respond_to?(:[]) && target_value.nil?
ary.where_present(property)
begin
ary.select { |item| item[property] }
rescue TypeError
raise_property_error(property)
end
elsif ary.first.respond_to?(:[])
ary.where(property, target_value)
begin
ary.select { |item| item[property] == target_value }
rescue TypeError
raise_property_error(property)
end
end
end
@@ -195,7 +201,11 @@ module Liquid
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.first.respond_to?(:[])
ary.uniq{ |a| a[property] }
begin
ary.uniq { |a| a[property] }
rescue TypeError
raise_property_error(property)
end
end
end
@@ -217,6 +227,8 @@ module Liquid
r.is_a?(Proc) ? r.call : r
end
end
rescue TypeError
raise_property_error(property)
end
# Remove nils within an array
@@ -229,7 +241,11 @@ module Liquid
elsif ary.empty? # The next two cases assume a non-empty array.
[]
elsif ary.first.respond_to?(:[])
ary.reject{ |a| a[property].nil? }
begin
ary.reject { |a| a[property].nil? }
rescue TypeError
raise_property_error(property)
end
end
end
@@ -413,11 +429,31 @@ module Liquid
private
def raise_property_error(property)
raise Liquid::ArgumentError.new("cannot select the property '#{property}'")
end
def apply_operation(input, operand, operation)
result = Utils.to_number(input).send(operation, Utils.to_number(operand))
result.is_a?(BigDecimal) ? result.to_f : result
end
def nil_safe_compare(a, b)
if !a.nil? && !b.nil?
a <=> b
else
a.nil? ? 1 : -1
end
end
def nil_safe_casecmp(a, b)
if !a.nil? && !b.nil?
a.to_s.casecmp(b.to_s)
else
a.nil? ? 1 : -1
end
end
class InputIterator
include Enumerable
@@ -463,24 +499,6 @@ module Liquid
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
end
end
def where(property, target_value)
select do |item|
item[property] == target_value
end
rescue TypeError
# Cannot index with the given property type (eg. indexing integers with strings
# which are only allowed to be indexed by other integers).
raise ArgumentError.new("cannot select the property `#{property}`")
end
def where_present(property)
select { |item| item[property] }
rescue TypeError
# Cannot index with the given property type (eg. indexing integers with strings
# which are only allowed to be indexed by other integers).
raise ArgumentError.new("cannot select the property `#{property}`")
end
end
end

View File

@@ -24,7 +24,7 @@ module Liquid
def render(context)
val = @from.render(context)
context.scopes.last[@to] = val
context.set_root(@to, val)
context.resource_limits.assign_score += assign_score_of(val)
''.freeze
end
@@ -37,7 +37,7 @@ module Liquid
def assign_score_of(val)
if val.instance_of?(String)
val.length
val.bytesize
elsif val.instance_of?(Array) || val.instance_of?(Hash)
sum = 1
# Uses #each to avoid extra allocations.

View File

@@ -24,8 +24,8 @@ module Liquid
def render(context)
output = super
context.scopes.last[@to] = output
context.resource_limits.assign_score += output.length
context.set_root(@to, output)
context.resource_limits.assign_score += output.bytesize
''.freeze
end

View File

@@ -39,20 +39,19 @@ module Liquid
end
def render(context)
context.stack do
execute_else_block = true
execute_else_block = true
output = ''
@blocks.each do |block|
if block.else?
return block.attachment.render(context) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
output << block.attachment.render(context)
end
output = ''
@blocks.each do |block|
if block.else?
return block.attachment.render(context) if execute_else_block
elsif block.evaluate(context)
execute_else_block = false
output << block.attachment.render(context)
end
output
end
output
end
private

View File

@@ -34,15 +34,13 @@ module Liquid
def render(context)
context.registers[:cycle] ||= {}
context.stack do
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
result = context.evaluate(@variables[iteration])
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
result
end
key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i
result = context.evaluate(@variables[iteration])
iteration += 1
iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration
result
end
private

View File

@@ -124,14 +124,23 @@ module Liquid
from = if @from == :continue
offsets[@name].to_i
else
context.evaluate(@from).to_i
from_value = context.evaluate(@from)
if from_value.nil?
0
else
Utils.to_integer(from_value)
end
end
collection = context.evaluate(@collection_name)
collection = collection.to_a if collection.is_a?(Range)
limit = context.evaluate(@limit)
to = limit ? limit.to_i + from : nil
limit_value = context.evaluate(@limit)
to = if limit_value.nil?
nil
else
Utils.to_integer(limit_value) + from
end
segment = Utils.slice_collection(collection, from, to)
segment.reverse! if @reversed
@@ -147,19 +156,19 @@ module Liquid
result = ''
context.stack do
context.stack('forloop', @variable_name) do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
for_stack.push(loop_vars)
begin
context['forloop'.freeze] = loop_vars
level = context.create_level(@variable_name)
segment.each do |item|
context[@variable_name] = item
context.set_level(@variable_name, item, level)
result << @for_block.render(context)
loop_vars.send(:increment!)
# Handle any interrupts if they exist.
if context.interrupt?
interrupt = context.pop_interrupt
@@ -167,7 +176,10 @@ module Liquid
next if interrupt.is_a? ContinueInterrupt
end
end
context.unset(@variable_name)
context.unset('forloop'.freeze)
ensure
for_stack.pop
end
end

View File

@@ -12,7 +12,7 @@ module Liquid
class If < Block
Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
BOOLEAN_OPERATORS = %w(and or)
BOOLEAN_OPERATORS = %w(and or).freeze
attr_reader :blocks
@@ -40,14 +40,12 @@ module Liquid
end
def render(context)
context.stack do
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render(context)
end
@blocks.each do |block|
if block.evaluate(context)
return block.attachment.render(context)
end
''.freeze
end
''.freeze
end
private

View File

@@ -1,15 +1,13 @@
module Liquid
class Ifchanged < Block
def render(context)
context.stack do
output = super
output = super
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output
else
''.freeze
end
if output != context.registers[:ifchanged]
context.registers[:ifchanged] = output
output
else
''.freeze
end
end
end

View File

@@ -60,7 +60,7 @@ module Liquid
begin
context.template_name = template_name
context.partial = true
context.stack do
context.stack(context_variable_name, *@attributes.keys) do
@attributes.each do |key, value|
context[key] = context.evaluate(value)
end

View File

@@ -31,7 +31,7 @@ module Liquid
cols = context.evaluate(@attributes['cols'.freeze]).to_i
result = "<tr class=\"row1\">\n"
context.stack do
context.stack('tablerowloop', @variable_name) do
tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
context['tablerowloop'.freeze] = tablerowloop

View File

@@ -7,22 +7,20 @@ module Liquid
#
class Unless < If
def render(context)
context.stack do
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
return first_block.attachment.render(context)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return block.attachment.render(context)
end
end
''.freeze
# First condition is interpreted backwards ( if not )
first_block = @blocks.first
unless first_block.evaluate(context)
return first_block.attachment.render(context)
end
# After the first condition unless works just like if
@blocks[1..-1].each do |block|
if block.evaluate(context)
return block.attachment.render(context)
end
end
''.freeze
end
end

View File

@@ -95,16 +95,17 @@ module Liquid
def parse_filter_expressions(filter_name, unparsed_args)
filter_args = []
keyword_args = {}
keyword_args = nil
unparsed_args.each do |a|
if matches = a.match(JustTagAttributes)
keyword_args ||= {}
keyword_args[matches[1]] = Expression.parse(matches[2])
else
filter_args << Expression.parse(a)
end
end
result = [filter_name, filter_args]
result << keyword_args unless keyword_args.empty?
result << keyword_args if keyword_args
result
end

View File

@@ -1,7 +1,7 @@
module Liquid
class VariableLookup
SQUARE_BRACKETED = /\A\[(.*)\]\z/m
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze]
COMMAND_METHODS = ['size'.freeze, 'first'.freeze, 'last'.freeze].freeze
attr_reader :name, :lookups

View File

@@ -1,4 +1,5 @@
# encoding: utf-8
module Liquid
VERSION = "4.0.1"
VERSION = "4.0.3".freeze
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'benchmark/ips'
require 'memory_profiler'
require_relative 'theme_runner'
def profile(phase, &block)
puts
puts "#{phase}:"
puts
report = MemoryProfiler.report(&block)
report.pretty_print(
color_output: true,
scale_bytes: true,
detailed_report: true
)
end
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
profiler = ThemeRunner.new
profile("Parsing") { profiler.compile }
profile("Rendering") { profiler.render }

View File

@@ -15,7 +15,7 @@ class CommentForm < Liquid::Block
def render(context)
article = context[@variable_name]
context.stack do
context.stack('form') do
context['form'] = {
'posted_successfully?' => context.registers[:posted_successfully],
'errors' => context['comment.errors'],

View File

@@ -24,7 +24,7 @@ class Paginate < Liquid::Block
def render(context)
@context = context
context.stack do
context.stack('paginate') do
current_page = context['current_page'].to_i
pagination = {

View File

@@ -1,14 +1,6 @@
require 'test_helper'
class ContextDrop < Liquid::Drop
def scopes
@context.scopes.size
end
def scopes_as_array
(1..@context.scopes.size).to_a
end
def loop_pos
@context['forloop.index']
end
@@ -194,31 +186,6 @@ class DropsTest < Minitest::Test
end
end
def test_scope
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_scope_though_proc
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
end
def test_scope_with_assigns
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
end
def test_scope_from_tags
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '12', Liquid::Template.parse('{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
end
def test_access_context_from_drop
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
end

View File

@@ -123,7 +123,7 @@ class ErrorHandlingTest < Minitest::Test
',
error_mode: :warn,
line_numbers: true
)
)
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
template.warnings.map(&:message)
@@ -140,7 +140,7 @@ class ErrorHandlingTest < Minitest::Test
',
error_mode: :strict,
line_numbers: true
)
)
end
assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
@@ -158,7 +158,7 @@ class ErrorHandlingTest < Minitest::Test
bla
',
line_numbers: true
)
)
end
assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message

View File

@@ -99,7 +99,7 @@ class ParsingQuirksTest < Minitest::Test
# After the messed up quotes a filter without parameters (reverse) should work
# but one with parameters (remove) shouldn't be detected.
assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
end
end

View File

@@ -158,6 +158,10 @@ 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
@filters.url_decode('%ff')
end
assert_equal 'Liquid error: invalid byte sequence in UTF-8', exception.message
end
def test_truncatewords
@@ -177,6 +181,9 @@ class StandardFiltersTest < Minitest::Test
assert_equal 'test', @filters.strip_html("<div\nclass='multiline'>test</div>")
assert_equal 'test', @filters.strip_html("<!-- foo bar \n test -->test")
assert_equal '', @filters.strip_html(nil)
# Quirk of the existing implementation
assert_equal 'foo;', @filters.strip_html("<<<script </script>script>foo;</script>")
end
def test_join
@@ -268,10 +275,34 @@ class StandardFiltersTest < Minitest::Test
assert_equal [], @filters.sort([], "a")
end
def test_sort_invalid_property
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.sort(foo, "bar")
end
end
def test_sort_natural_empty_array
assert_equal [], @filters.sort_natural([], "a")
end
def test_sort_natural_invalid_property
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.sort_natural(foo, "bar")
end
end
def test_legacy_sort_hash
assert_equal [{ a: 1, b: 2 }], @filters.sort({ a: 1, b: 2 })
end
@@ -295,10 +326,34 @@ class StandardFiltersTest < Minitest::Test
assert_equal [], @filters.uniq([], "a")
end
def test_uniq_invalid_property
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.uniq(foo, "bar")
end
end
def test_compact_empty_array
assert_equal [], @filters.compact([], "a")
end
def test_compact_invalid_property
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.compact(foo, "bar")
end
end
def test_reverse
assert_equal [4, 3, 2, 1], @filters.reverse([1, 2, 3, 4])
end
@@ -350,11 +405,11 @@ class StandardFiltersTest < Minitest::Test
def test_map_over_drops_returning_procs
drops = [
{
"proc" => ->{ "foo" },
"proc" => ->{ "foo" }
},
{
"proc" => ->{ "bar" },
},
"proc" => ->{ "bar" }
}
]
templ = '{{ drops | map: "proc" }}'
assert_template_result "foobar", templ, "drops" => drops
@@ -364,6 +419,29 @@ class StandardFiltersTest < Minitest::Test
assert_template_result "123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new
end
def test_map_returns_empty_on_2d_input_array
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.map(foo, "bar")
end
end
def test_map_returns_empty_with_no_property
foo = [
[1],
[2],
[3]
]
assert_raises Liquid::ArgumentError do
@filters.map(foo, nil)
end
end
def test_sort_works_on_enumerables
assert_template_result "213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new
end
@@ -394,9 +472,9 @@ class StandardFiltersTest < Minitest::Test
assert_equal '07/05/2006', @filters.date("2006-07-05 10:00:00", "%m/%d/%Y")
assert_equal "07/16/2004", @filters.date("Fri Jul 16 01:00:00 2004", "%m/%d/%Y")
assert_equal "#{Date.today.year}", @filters.date('now', '%Y')
assert_equal "#{Date.today.year}", @filters.date('today', '%Y')
assert_equal "#{Date.today.year}", @filters.date('Today', '%Y')
assert_equal Date.today.year.to_s, @filters.date('now', '%Y')
assert_equal Date.today.year.to_s, @filters.date('today', '%Y')
assert_equal Date.today.year.to_s, @filters.date('Today', '%Y')
assert_nil @filters.date(nil, "%B")

View File

@@ -103,6 +103,34 @@ HERE
assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
end
def test_limiting_with_invalid_limit
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP
{% for i in array limit: true offset: 1 %}
{{ i }}
{% endfor %}
MKUP
exception = assert_raises(Liquid::ArgumentError) do
Template.parse(template).render!(assigns)
end
assert_equal("Liquid error: invalid integer", exception.message)
end
def test_limiting_with_invalid_offset
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
template = <<-MKUP
{% for i in array limit: 1 offset: true %}
{{ i }}
{% endfor %}
MKUP
exception = assert_raises(Liquid::ArgumentError) do
Template.parse(template).render!(assigns)
end
assert_equal("Liquid error: invalid integer", exception.message)
end
def test_dynamic_variable_limiting
assigns = { 'array' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] }
assigns['limit'] = 2
@@ -340,6 +368,23 @@ HERE
assert_template_result(expected, template, assigns)
end
def test_overwriting_internal_variable
template = <<-HEREDOC
{% assign forloop = 'first' %}
{% for item in items %}
{{ forloop }}
{% assign forloop = 'second' %}
{{ forloop }}
{% endfor %}
{{ forloop }}
HEREDOC
result = Liquid::Template.parse(template).render('items' => '1')
assert_equal 'Liquid::ForloopDrop Liquid::ForloopDrop second', result.split.map(&:strip).join(' ')
end
class LoaderDrop < Liquid::Drop
attr_accessor :each_called, :load_slice_called

View File

@@ -176,7 +176,7 @@ class IfElseTagTest < Minitest::Test
[false, true, true] => true,
[false, true, false] => false,
[false, false, true] => false,
[false, false, false] => false,
[false, false, false] => false
}
tests.each do |vals, expected|

View File

@@ -30,6 +30,9 @@ class TestFileSystem
when 'assignments'
"{% assign foo = 'bar' %}"
when 'break'
"{% break %}"
else
template_path
end
@@ -242,4 +245,9 @@ class IncludeTagTest < Minitest::Test
assert_equal [], template.errors
end
def test_break_through_include
assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}"
assert_template_result "1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}"
end
end # IncludeTagTest

View File

@@ -139,6 +139,16 @@ class TemplateTest < Minitest::Test
refute_nil t.resource_limits.assign_score
end
def test_resource_limits_assign_score_counts_bytes_not_characters
t = Template.parse("{% assign foo = 'すごい' %}")
t.render
assert_equal 9, t.resource_limits.assign_score
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
t.render
assert_equal 9, t.resource_limits.assign_score
end
def test_resource_limits_assign_score_nested
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
@@ -187,6 +197,14 @@ class TemplateTest < Minitest::Test
assert_equal "ababab", t.render
end
def test_render_length_uses_number_of_bytes_not_characters
t = Template.parse("{% if true %}すごい{% endif %}")
t.resource_limits.render_length_limit = 10
assert_equal "Liquid error: Memory limits exceeded", t.render
t.resource_limits.render_length_limit = 18
assert_equal "すごい", t.render
end
def test_default_resource_limits_unaffected_by_render_with_context
context = Context.new
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")

0
test/test_helper.rb Normal file → Executable file
View File

View File

@@ -24,9 +24,9 @@ class ConditionUnitTest < Minitest::Test
assert_evaluates_true 1, '<=', 1
# negative numbers
assert_evaluates_true 1, '>', -1
assert_evaluates_true (-1), '<', 1
assert_evaluates_true -1, '<', 1
assert_evaluates_true 1.0, '>', -1.0
assert_evaluates_true (-1.0), '<', 1.0
assert_evaluates_true -1.0, '<', 1.0
end
def test_default_operators_evalute_false

View File

@@ -102,21 +102,6 @@ class ContextUnitTest < Minitest::Test
assert_nil @context['does_not_exist']
end
def test_scoping
@context.push
@context.pop
assert_raises(Liquid::ContextError) do
@context.pop
end
assert_raises(Liquid::ContextError) do
@context.push
@context.pop
@context.pop
end
end
def test_length_query
@context['numbers'] = [1, 2, 3, 4]
@@ -170,18 +155,12 @@ class ContextUnitTest < Minitest::Test
def test_add_item_in_outer_scope
@context['test'] = 'test'
@context.push
assert_equal 'test', @context['test']
@context.pop
assert_equal 'test', @context['test']
end
def test_add_item_in_inner_scope
@context.push
@context['test'] = 'test'
@context.stack('test') do
assert_equal 'test', @context['test']
end
assert_equal 'test', @context['test']
@context.pop
assert_nil @context['test']
end
def test_hierachical_data