Compare commits

..

5 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
48 changed files with 520 additions and 2001 deletions

1
.gitignore vendored
View File

@@ -7,4 +7,3 @@ pkg
.ruby-version .ruby-version
Gemfile.lock Gemfile.lock
.bundle .bundle
.byebug_history

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,131 @@
inherit_from: inherit_from:
- https://shopify.github.io/ruby-style-guide/rubocop.yml
- .rubocop_todo.yml - .rubocop_todo.yml
- ./.rubocop_todo.yml
require: rubocop-performance
Performance:
Enabled: true
AllCops: AllCops:
Exclude: Exclude:
- 'vendor/bundle/**/*' - 'performance/shopify/*'
- 'pkg/**'
Metrics/BlockNesting:
Max: 3
Metrics/ModuleLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
Lint/AssignmentInCondition:
Enabled: false
Lint/AmbiguousOperator:
Enabled: false
Lint/AmbiguousRegexpLiteral:
Enabled: false
Lint/ParenthesesAsGroupedExpression:
Enabled: false
Lint/UnusedBlockArgument:
Enabled: false
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
Lint/UnusedMethodArgument:
Enabled: false
Style/SingleLineBlockParams:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/StringLiteralsInInterpolation:
Enabled: false
Style/AndOr:
Enabled: false
Style/SignalException:
Enabled: false
Style/StringLiterals:
Enabled: false
Style/BracesAroundHashParameters:
Enabled: false
Style/NumericLiterals:
Enabled: false
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
Layout/SpaceBeforeBlockBraces:
Enabled: false
Style/Documentation:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/TrailingCommaInArrayLiteral:
Enabled: false
Style/TrailingCommaInHashLiteral:
Enabled: false
Layout/IndentHash:
EnforcedStyle: consistent
Style/FormatString:
Enabled: false
Layout/AlignParameters:
EnforcedStyle: with_fixed_indentation
Layout/MultilineOperationIndentation:
EnforcedStyle: indented
Style/IfUnlessModifier:
Enabled: false
Style/RaiseArgs:
Enabled: false
Style/PreferredHashMethods:
Enabled: false
Style/RegexpLiteral:
Enabled: false
Style/SymbolLiteral:
Enabled: false
Performance/Count:
Enabled: false
Naming/ConstantName:
Enabled: false
Layout/CaseIndentation:
Enabled: false
Style/ClassVars:
Enabled: false
Style/PerlBackrefs:
Enabled: false
Style/TrivialAccessors:
AllowPredicates: true
Style/WordArray:
Enabled: false
Naming/MethodName: Naming/MethodName:
Exclude: Exclude:

View File

@@ -1,37 +1,23 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config` # `rubocop --auto-gen-config`
# on 2019-08-29 12:16:25 +1000 using RuboCop version 0.74.0. # 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 # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again. # versions of RuboCop, may require this file to be generated again.
# Offense count: 13 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. # Configuration parameters: Include, TreatCommentsAsGroupSeparators.
# SupportedHashRocketStyles: key, separator, table # Include: **/*.gemspec
# SupportedColonStyles: key, separator, table Gemspec/OrderedDependencies:
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
Layout/AlignHash:
Exclude: Exclude:
- 'lib/liquid/condition.rb' - 'liquid.gemspec'
- 'lib/liquid/expression.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/database.rb'
- 'performance/shopify/paginate.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
Layout/ExtraSpacing:
Exclude:
- 'performance/shopify/paginate.rb'
# Offense count: 5 # Offense count: 5
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
# SupportedStyles: squiggly, active_support, powerpack, unindent # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
Layout/IndentHeredoc: Layout/IndentHeredoc:
Exclude: Exclude:
- 'test/integration/tags/for_tag_test.rb' - 'test/integration/tags/for_tag_test.rb'
@@ -46,62 +32,6 @@ Layout/MultilineMethodCallBraceLayout:
- 'test/integration/error_handling_test.rb' - 'test/integration/error_handling_test.rb'
- 'test/unit/strainer_unit_test.rb' - 'test/unit/strainer_unit_test.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
Layout/SpaceAroundOperators:
Exclude:
- 'lib/liquid/condition.rb'
- 'performance/shopify/paginate.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceBeforeBlockBraces:
Exclude:
- 'example/server/server.rb'
- 'lib/liquid/variable.rb'
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/if_else_tag_test.rb'
# Offense count: 19
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
# SupportedStyles: space, no_space, compact
# SupportedStylesForEmptyBrackets: space, no_space
Layout/SpaceInsideArrayLiteralBrackets:
Exclude:
- 'test/integration/drop_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/integration/tags/standard_tag_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 2
Lint/AmbiguousOperator:
Exclude:
- 'test/unit/condition_unit_test.rb'
# Offense count: 16
# Configuration parameters: AllowSafeAssignment.
Lint/AssignmentInCondition:
Exclude:
- 'lib/liquid/block_body.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/variable.rb'
- 'performance/profile.rb'
- 'test/test_helper.rb'
- 'test/unit/tokenizer_unit_test.rb'
# Offense count: 2 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
@@ -110,184 +40,145 @@ Lint/InheritException:
Exclude: Exclude:
- 'lib/liquid/interrupts.rb' - 'lib/liquid/interrupts.rb'
# Offense count: 10
# Cop supports --auto-correct.
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
Lint/UnusedBlockArgument:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/context.rb'
- 'lib/liquid/document.rb'
- 'lib/liquid/parse_context.rb'
- 'lib/liquid/template.rb'
- 'performance/shopify/json_filter.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/render_profiling_test.rb'
- 'test/integration/variable_test.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 12
# Cop supports --auto-correct.
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
Lint/UnusedMethodArgument:
Exclude:
- 'example/server/liquid_servlet.rb'
- 'test/integration/blank_test.rb'
- 'test/integration/error_handling_test.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/output_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/strainer_unit_test.rb'
# Offense count: 2
Lint/UselessAssignment:
Exclude:
- 'performance/shopify/database.rb'
# Offense count: 1 # Offense count: 1
# Configuration parameters: CheckForMethodsWithNoSideEffects. # Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void: Lint/Void:
Exclude: Exclude:
- 'lib/liquid/parse_context.rb' - 'lib/liquid/parse_context.rb'
# Offense count: 95 # Offense count: 54
# Cop supports --auto-correct. Metrics/AbcSize:
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. Max: 56
# URISchemes: http, https
Metrics/LineLength:
Max: 294
# Offense count: 44 # Offense count: 12
Naming/ConstantName: Metrics/CyclomaticComplexity:
Max: 12
# Offense count: 112
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 37
# Offense count: 8
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 52
# Configuration parameters: Blacklist.
# Blacklist: END, (?-mix:EO[A-Z]{1})
Naming/HeredocDelimiterNaming:
Exclude: Exclude:
- 'lib/liquid.rb' - 'test/integration/assign_test.rb'
- 'lib/liquid/block_body.rb' - 'test/integration/capture_test.rb'
- 'lib/liquid/tags/assign.rb' - 'test/integration/trim_mode_test.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb' # Offense count: 23
- 'lib/liquid/tags/cycle.rb' # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
- 'lib/liquid/tags/for.rb' # 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/tags/if.rb'
- 'lib/liquid/tags/include.rb' - 'lib/liquid/utils.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb' - 'lib/liquid/variable.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 2
# Configuration parameters: .
# SupportedStyles: snake_case, camelCase
Naming/MethodName:
EnforcedStyle: snake_case
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, conditionals
Style/AndOr:
Exclude:
- 'lib/liquid/i18n.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/tokenizer.rb'
# Offense count: 40
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: braces, no_braces, context_dependent
Style/BracesAroundHashParameters:
Exclude:
- 'test/integration/error_handling_test.rb'
- 'test/integration/filter_test.rb' - 'test/integration/filter_test.rb'
- 'test/integration/render_profiling_test.rb'
- 'test/integration/standard_filter_test.rb' - 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/echo_test.rb' - 'test/integration/tags/for_tag_test.rb'
- 'test/integration/tags/increment_tag_test.rb'
- 'test/integration/tags/standard_tag_test.rb'
- 'test/integration/template_test.rb' - 'test/integration/template_test.rb'
- 'test/unit/condition_unit_test.rb' - 'test/unit/condition_unit_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 5 # Offense count: 10
Style/ClassVars: # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: prefer_alias, prefer_alias_method
Style/Alias:
Exclude: Exclude:
- 'lib/liquid/condition.rb' - 'lib/liquid/drop.rb'
- 'lib/liquid/strainer.rb' - 'lib/liquid/i18n.rb'
- 'lib/liquid/template.rb' - 'lib/liquid/profiler/hooks.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tag.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/variable.rb'
# Offense count: 2 # Offense count: 22
Style/CommentedKeyword:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition # SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment: Style/ConditionalAssignment:
Exclude: Exclude:
- 'lib/liquid/errors.rb' - 'lib/liquid/errors.rb'
- 'performance/shopify/shop_filter.rb'
# Offense count: 1 # Offense count: 1
# Configuration parameters: AllowCoercion.
Style/DateTime: Style/DateTime:
Exclude: Exclude:
- 'test/unit/context_unit_test.rb' - 'test/unit/context_unit_test.rb'
# Offense count: 2 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- 'performance/shopify/database.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyCaseCondition: Style/EmptyCaseCondition:
Exclude: Exclude:
- 'lib/liquid/block_body.rb'
- 'lib/liquid/lexer.rb' - 'lib/liquid/lexer.rb'
# Offense count: 1 # Offense count: 5
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: EnforcedStyle.
# SupportedStyles: each, for # SupportedStyles: compact, expanded
Style/For: Style/EmptyMethod:
Exclude: Exclude:
- 'performance/shopify/shop_filter.rb' - 'lib/liquid/tag.rb'
- 'lib/liquid/tags/comment.rb'
- 'lib/liquid/tags/include.rb'
- 'test/integration/tags/include_tag_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 9 # Offense count: 3
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. Style/Encoding:
# SupportedStyles: format, sprintf, percent Exclude:
Style/FormatString: - 'lib/liquid/version.rb'
- 'liquid.gemspec'
- 'test/integration/standard_filter_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/ExpandPathArguments:
Exclude:
- 'Rakefile'
- 'liquid.gemspec'
# Offense count: 7
# Configuration parameters: EnforcedStyle.
# SupportedStyles: annotated, template, unannotated
Style/FormatStringToken:
Exclude: Exclude:
- 'example/server/example_servlet.rb'
- 'performance/shopify/money_filter.rb'
- 'performance/shopify/weight_filter.rb'
- 'test/integration/filter_test.rb' - 'test/integration/filter_test.rb'
- 'test/integration/hash_ordering_test.rb' - 'test/integration/hash_ordering_test.rb'
# Offense count: 115 # Offense count: 14
# Cop supports --auto-correct. # Configuration parameters: MinBodyLength.
# Configuration parameters: EnforcedStyle. Style/GuardClause:
# SupportedStyles: always, never
Style/FrozenStringLiteralComment:
Enabled: false
# Offense count: 30
# Cop supports --auto-correct.
# Configuration parameters: IgnoreMacros, IgnoredMethods, IncludedMacros, AllowParenthesesInMultilineCall, AllowParenthesesInChaining, AllowParenthesesInCamelCaseMethod, EnforcedStyle.
# SupportedStyles: require_parentheses, omit_parentheses
Style/MethodCallWithArgsParentheses:
Exclude: Exclude:
- 'Gemfile' - 'lib/liquid/condition.rb'
- 'Rakefile' - 'lib/liquid/lexer.rb'
- 'lib/liquid/block_body.rb' - 'lib/liquid/strainer.rb'
- 'lib/liquid/parser.rb' - 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- 'lib/liquid/tags/for.rb' - 'lib/liquid/tags/for.rb'
- 'liquid.gemspec' - 'lib/liquid/tags/include.rb'
- 'performance/shopify/database.rb' - 'lib/liquid/tags/raw.rb'
- 'performance/shopify/liquid.rb' - 'lib/liquid/tags/table_row.rb'
- 'test/test_helper.rb' - 'lib/liquid/variable.rb'
- 'test/unit/condition_unit_test.rb' - 'test/unit/tokenizer_unit_test.rb'
- 'test/unit/tags/if_tag_unit_test.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
@@ -297,17 +188,27 @@ Style/Next:
Exclude: Exclude:
- 'lib/liquid/tags/for.rb' - 'lib/liquid/tags/for.rb'
# Offense count: 52 # Offense count: 4
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/PerlBackrefs: # Configuration parameters: AutoCorrect, EnforcedStyle.
Enabled: false # SupportedStyles: predicate, comparison
Style/NumericPredicate:
Exclude:
- 'spec/**/*'
- 'lib/liquid/context.rb'
- 'lib/liquid/forloop_drop.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tablerowloop_drop.rb'
# Offense count: 33 # Offense count: 14
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: PreferredDelimiters.
# SupportedStyles: compact, exploded Style/PercentLiteralDelimiters:
Style/RaiseArgs: Exclude:
Enabled: false - 'lib/liquid/tags/if.rb'
- 'liquid.gemspec'
- 'test/integration/assign_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
@@ -315,52 +216,21 @@ Style/RedundantSelf:
Exclude: Exclude:
- 'lib/liquid/strainer.rb' - 'lib/liquid/strainer.rb'
# Offense count: 5 # Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Exclude:
- 'lib/liquid/file_system.rb'
- 'lib/liquid/standardfilters.rb'
- 'performance/shopify/shop_filter.rb'
- 'test/unit/condition_unit_test.rb'
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, Whitelist.
# Whitelist: present?, blank?, presence, try, try!
Style/SafeNavigation:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/tokenizer.rb'
# Offense count: 10
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowAsExpressionSeparator. # Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon: Style/Semicolon:
Exclude: Exclude:
- 'performance/shopify/database.rb'
- 'test/integration/error_handling_test.rb' - 'test/integration/error_handling_test.rb'
- 'test/integration/template_test.rb' - 'test/integration/template_test.rb'
- 'test/unit/context_unit_test.rb' - 'test/unit/context_unit_test.rb'
# Offense count: 1 # Offense count: 7
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle. # Configuration parameters: MinSize.
# SupportedStyles: use_perl_names, use_english_names # SupportedStyles: percent, brackets
Style/SpecialGlobalVars: Style/SymbolArray:
Exclude: EnforcedStyle: brackets
- 'performance/shopify/liquid.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: single_quotes, double_quotes
Style/StringLiteralsInInterpolation:
Exclude:
- 'performance/shopify/tag_filter.rb'
# Offense count: 2 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
@@ -371,33 +241,6 @@ Style/TernaryParentheses:
- 'lib/liquid/context.rb' - 'lib/liquid/context.rb'
- 'lib/liquid/utils.rb' - 'lib/liquid/utils.rb'
# Offense count: 21
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInArrayLiteral:
Exclude:
- 'lib/liquid/parse_tree_visitor.rb'
- 'lib/liquid/tags/include.rb'
- 'test/integration/parse_tree_visitor_test.rb'
- 'test/integration/standard_filter_test.rb'
# Offense count: 9
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, no_comma
Style/TrailingCommaInHashLiteral:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/standardfilters.rb'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/database.rb'
- 'performance/shopify/paginate.rb'
- 'performance/theme_runner.rb'
- 'test/integration/output_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 2 # Offense count: 2
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/UnneededPercentQ: Style/UnneededPercentQ:
@@ -409,3 +252,9 @@ Style/UnneededPercentQ:
Style/WhileUntilModifier: Style/WhileUntilModifier:
Exclude: Exclude:
- 'lib/liquid/tags/case.rb' - 'lib/liquid/tags/case.rb'
# Offense count: 640
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294

View File

@@ -1,26 +1,29 @@
language: ruby language: ruby
rvm: rvm:
- 2.1
- 2.2
- 2.3
- 2.4 - 2.4
- 2.5 - 2.5
- &latest_ruby 2.6
- 2.7
- ruby-head - ruby-head
- jruby-head - jruby-head
- truffleruby # - rbx-2
sudo: false
addons:
apt:
packages:
- libgmp3-dev
matrix: matrix:
include:
- rvm: *latest_ruby
script: bundle exec rake memory_profile:run
name: Profiling Memory Usage
allow_failures: allow_failures:
- rvm: ruby-head - rvm: ruby-head
- rvm: jruby-head - rvm: jruby-head
- rvm: truffleruby
cache: bundler install:
- bundle install
script: bundle exec rake script: bundle exec rake

10
Gemfile
View File

@@ -8,18 +8,16 @@ gemspec
group :benchmark, :test do group :benchmark, :test do
gem 'benchmark-ips' gem 'benchmark-ips'
gem 'memory_profiler' gem 'memory_profiler'
gem 'terminal-table'
install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ && RUBY_ENGINE != 'truffleruby' } do install_if -> { RUBY_PLATFORM !~ /mingw|mswin|java/ } do
gem 'stackprof' gem 'stackprof'
end end
end end
group :test do group :test do
gem 'rubocop', '~> 0.74.0', require: false gem 'rubocop', '~> 0.53.0'
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do platform :mri do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'liquid-tag' gem 'liquid-c', github: 'Shopify/liquid-c', ref: '9168659de45d6d576fce30c735f857e597fa26f6'
end end
end end

View File

@@ -19,11 +19,9 @@ task :warn_test do
end end
task :rubocop do task :rubocop do
if RUBY_ENGINE == 'ruby'
require 'rubocop/rake_task' require 'rubocop/rake_task'
RuboCop::RakeTask.new RuboCop::RakeTask.new
end end
end
desc 'runs test suite with both strict and lax parsers' desc 'runs test suite with both strict and lax parsers'
task :test do task :test do
@@ -34,8 +32,8 @@ task :test do
Rake::Task['base_test'].reenable Rake::Task['base_test'].reenable
Rake::Task['base_test'].invoke Rake::Task['base_test'].invoke
if RUBY_ENGINE == 'ruby' || RUBY_ENGINE == 'truffleruby' if RUBY_ENGINE == 'ruby'
ENV['LIQUID_C'] = '1' ENV['LIQUID-C'] = '1'
ENV['LIQUID_PARSER_MODE'] = 'lax' ENV['LIQUID_PARSER_MODE'] = 'lax'
Rake::Task['base_test'].reenable Rake::Task['base_test'].reenable

View File

@@ -13,7 +13,6 @@ module Liquid
end end
end end
# For backwards compatibility
def render(context) def render(context)
@body.render(context) @body.render(context)
end end

View File

@@ -1,7 +1,6 @@
module Liquid module Liquid
class BlockBody class BlockBody
LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
WhitespaceOrNothing = /\A\s*\z/ WhitespaceOrNothing = /\A\s*\z/
TAGSTART = "{%".freeze TAGSTART = "{%".freeze
@@ -14,42 +13,8 @@ module Liquid
@blank = true @blank = true
end end
def parse(tokenizer, parse_context, &block) def parse(tokenizer, parse_context)
parse_context.line_number = tokenizer.line_number parse_context.line_number = tokenizer.line_number
if tokenizer.for_liquid_tag
parse_for_liquid_tag(tokenizer, parse_context, &block)
else
parse_for_document(tokenizer, parse_context, &block)
end
end
private def parse_for_liquid_tag(tokenizer, parse_context)
while token = tokenizer.shift
unless token.empty? || token =~ WhitespaceOrNothing
unless token =~ LiquidTagToken
# line isn't empty but didn't match tag syntax, yield and let the
# caller raise a syntax error
return yield token, token
end
tag_name = $1
markup = $2
unless tag = registered_tags[tag_name]
# end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed
return yield tag_name, markup
end
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
@blank &&= new_tag.blank?
@nodelist << new_tag
end
parse_context.line_number = tokenizer.line_number
end
yield nil, nil
end
private def parse_for_document(tokenizer, parse_context, &block)
while token = tokenizer.shift while token = tokenizer.shift
next if token.empty? next if token.empty?
case case
@@ -58,20 +23,9 @@ module Liquid
unless token =~ FullToken unless token =~ FullToken
raise_missing_tag_terminator(token, parse_context) raise_missing_tag_terminator(token, parse_context)
end end
tag_name = $2 tag_name = $1
markup = $4 markup = $2
# fetch the tag from registered blocks
if parse_context.line_number
# newlines inside the tag should increase the line number,
# particularly important for multiline {% liquid %} tags
parse_context.line_number += $1.count("\n".freeze) + $3.count("\n".freeze)
end
if tag_name == 'liquid'.freeze
liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
next parse_for_liquid_tag(liquid_tag_tokenizer, parse_context, &block)
end
unless tag = registered_tags[tag_name] unless tag = registered_tags[tag_name]
# end parsing if we reach an unknown tag and let the caller decide # end parsing if we reach an unknown tag and let the caller decide
# determine how to proceed # determine how to proceed
@@ -113,23 +67,19 @@ module Liquid
end end
def render(context) def render(context)
render_to_output_buffer(context, '') output = []
end
def render_to_output_buffer(context, output)
context.resource_limits.render_score += @nodelist.length context.resource_limits.render_score += @nodelist.length
idx = 0 idx = 0
while node = @nodelist[idx] while node = @nodelist[idx]
previous_output_size = output.bytesize
case node case node
when String when String
check_resources(context, node)
output << node output << node
when Variable when Variable
render_node(context, output, node) render_node_to_output(node, output, context)
when Block when Block
render_node(context, node.blank? ? '' : output, node) render_node_to_output(node, output, context, node.blank?)
break if context.interrupt? # might have happened in a for-block break if context.interrupt? # might have happened in a for-block
when Continue, Break when Continue, Break
# If we get an Interrupt that means the block must stop processing. An # If we get an Interrupt that means the block must stop processing. An
@@ -138,30 +88,34 @@ module Liquid
context.push_interrupt(node.interrupt) context.push_interrupt(node.interrupt)
break break
else # Other non-Block tags else # Other non-Block tags
render_node(context, output, node) render_node_to_output(node, output, context)
break if context.interrupt? # might have happened through an include break if context.interrupt? # might have happened through an include
end end
idx += 1 idx += 1
raise_if_resource_limits_reached(context, output.bytesize - previous_output_size)
end end
output output.join
end end
private private
def render_node(context, output, node) def render_node_to_output(node, output, context, skip_output = false)
node.render_to_output_buffer(context, output) node_output = node.render(context)
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
check_resources(context, node_output)
output << node_output unless skip_output
rescue MemoryError => e
raise e
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number) context.handle_error(e, node.line_number)
output << nil
rescue ::StandardError => e rescue ::StandardError => e
line_number = node.is_a?(String) ? nil : node.line_number line_number = node.is_a?(String) ? nil : node.line_number
output << context.handle_error(e, line_number) output << context.handle_error(e, line_number)
end end
def raise_if_resource_limits_reached(context, length) def check_resources(context, node_output)
context.resource_limits.render_length += length context.resource_limits.render_length += node_output.bytesize
return unless context.resource_limits.reached? return unless context.resource_limits.reached?
raise MemoryError.new("Memory limits exceeded".freeze) raise MemoryError.new("Memory limits exceeded".freeze)
end end

View File

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

View File

@@ -44,14 +44,11 @@ module Liquid
tok[0] == type tok[0] == type
end end
SINGLE_TOKEN_EXPRESSION_TYPES = [:string, :number].freeze
private_constant :SINGLE_TOKEN_EXPRESSION_TYPES
def expression def expression
token = @tokens[@p] token = @tokens[@p]
if token[0] == :id if token[0] == :id
variable_signature variable_signature
elsif SINGLE_TOKEN_EXPRESSION_TYPES.include? token[0] elsif [:string, :number].include? token[0]
consume consume
elsif token.first == :open_round elsif token.first == :open_round
consume consume

View File

@@ -1,23 +1,23 @@
module Liquid module Liquid
class BlockBody class BlockBody
def render_node_with_profiling(context, output, node) def render_node_with_profiling(node, output, context, skip_output = false)
Profiler.profile_node_render(node) do Profiler.profile_node_render(node) do
render_node_without_profiling(context, output, node) render_node_without_profiling(node, output, context, skip_output)
end end
end end
alias_method :render_node_without_profiling, :render_node alias_method :render_node_without_profiling, :render_node_to_output
alias_method :render_node, :render_node_with_profiling alias_method :render_node_to_output, :render_node_with_profiling
end end
class Include < Tag class Include < Tag
def render_to_output_buffer_with_profiling(context, output) def render_with_profiling(context)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
render_to_output_buffer_without_profiling(context, output) render_without_profiling(context)
end end
end end
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer alias_method :render_without_profiling, :render
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling alias_method :render, :render_with_profiling
end end
end end

View File

@@ -79,7 +79,7 @@ module Liquid
truncate_string_str = truncate_string.to_s truncate_string_str = truncate_string.to_s
l = length - truncate_string_str.length l = length - truncate_string_str.length
l = 0 if l < 0 l = 0 if l < 0
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str input_str.length > length ? input_str[0...l] + truncate_string_str : input_str
end end
def truncatewords(input, words = 15, truncate_string = "...".freeze) def truncatewords(input, words = 15, truncate_string = "...".freeze)
@@ -88,7 +88,7 @@ module Liquid
words = Utils.to_integer(words) words = Utils.to_integer(words)
l = words - 1 l = words - 1
l = 0 if l < 0 l = 0 if l < 0
wordlist.length > l ? wordlist[0..l].join(" ".freeze).concat(truncate_string.to_s) : input wordlist.length > l ? wordlist[0..l].join(" ".freeze) + truncate_string.to_s : input
end end
# Split input string into an array of substrings separated by given pattern. # Split input string into an array of substrings separated by given pattern.

View File

@@ -5,8 +5,8 @@ module Liquid
include ParserSwitching include ParserSwitching
class << self class << self
def parse(tag_name, markup, tokenizer, parse_context) def parse(tag_name, markup, tokenizer, options)
tag = new(tag_name, markup, parse_context) tag = new(tag_name, markup, options)
tag.parse(tokenizer) tag.parse(tokenizer)
tag tag
end end
@@ -36,14 +36,6 @@ module Liquid
''.freeze ''.freeze
end end
# For backwards compatibility with custom tags. In a future release, the semantics
# of the `render_to_output_buffer` method will become the default and the `render`
# method will be removed.
def render_to_output_buffer(context, output)
output << render(context)
output
end
def blank? def blank?
false false
end end

View File

@@ -10,10 +10,6 @@ module Liquid
class Assign < Tag class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def self.syntax_error_translation_key
"errors.syntax.assign".freeze
end
attr_reader :to, :from attr_reader :to, :from
def initialize(tag_name, markup, options) def initialize(tag_name, markup, options)
@@ -22,15 +18,15 @@ module Liquid
@to = $1 @to = $1
@from = Variable.new($2, options) @from = Variable.new($2, options)
else else
raise SyntaxError.new(options[:locale].t(self.class.syntax_error_translation_key)) raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
end end
end end
def render_to_output_buffer(context, output) def render(context)
val = @from.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) context.resource_limits.assign_score += assign_score_of(val)
output ''.freeze
end end
def blank? def blank?

View File

@@ -22,12 +22,11 @@ module Liquid
end end
end end
def render_to_output_buffer(context, output) def render(context)
previous_output_size = output.bytesize output = super
super context.set_root(@to, output)
context.scopes.last[@to] = output context.resource_limits.assign_score += output.bytesize
context.resource_limits.assign_score += (output.bytesize - previous_output_size) ''.freeze
output
end end
def blank? def blank?

View File

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

View File

@@ -1,7 +1,7 @@
module Liquid module Liquid
class Comment < Block class Comment < Block
def render_to_output_buffer(_context, output) def render(_context)
output ''.freeze
end end
def unknown_tag(_tag, _markup, _tokens) def unknown_tag(_tag, _markup, _tokens)

View File

@@ -31,29 +31,16 @@ module Liquid
end end
end end
def render_to_output_buffer(context, output) def render(context)
context.registers[:cycle] ||= {} context.registers[:cycle] ||= {}
context.stack do
key = context.evaluate(@name) key = context.evaluate(@name)
iteration = context.registers[:cycle][key].to_i iteration = context.registers[:cycle][key].to_i
result = context.evaluate(@variables[iteration])
val = context.evaluate(@variables[iteration])
if val.is_a?(Array)
val = val.join
elsif !val.is_a?(String)
val = val.to_s
end
output << val
iteration += 1 iteration += 1
iteration = 0 if iteration >= @variables.size iteration = 0 if iteration >= @variables.size
context.registers[:cycle][key] = iteration context.registers[:cycle][key] = iteration
end result
output
end end
private private

View File

@@ -23,12 +23,11 @@ module Liquid
@variable = markup.strip @variable = markup.strip
end end
def render_to_output_buffer(context, output) def render(context)
value = context.environments.first[@variable] ||= 0 value = context.environments.first[@variable] ||= 0
value -= 1 value -= 1
context.environments.first[@variable] = value context.environments.first[@variable] = value
output << value.to_s value.to_s
output
end end
end end

View File

@@ -1,24 +0,0 @@
module Liquid
# Echo outputs an expression
#
# {% echo monkey %}
# {% echo user.name %}
#
# This is identical to variable output syntax, like {{ foo }}, but works
# inside {% liquid %} tags. The full syntax is supported, including filters:
#
# {% echo user | link %}
#
class Echo < Tag
def initialize(tag_name, markup, parse_context)
super
@variable = Variable.new(markup, parse_context)
end
def render(context)
@variable.render_to_output_buffer(context, '')
end
end
Template.register_tag('echo'.freeze, Echo)
end

View File

@@ -70,16 +70,14 @@ module Liquid
@else_block = BlockBody.new @else_block = BlockBody.new
end end
def render_to_output_buffer(context, output) def render(context)
segment = collection_segment(context) segment = collection_segment(context)
if segment.empty? if segment.empty?
render_else(context, output) render_else(context)
else else
render_segment(context, output, segment) render_segment(context, segment)
end end
output
end end
protected protected
@@ -152,23 +150,25 @@ module Liquid
segment segment
end end
def render_segment(context, output, segment) def render_segment(context, segment)
for_stack = context.registers[:for_stack] ||= [] for_stack = context.registers[:for_stack] ||= []
length = segment.length length = segment.length
context.stack do result = ''
context.stack('forloop', @variable_name) do
loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1]) loop_vars = Liquid::ForloopDrop.new(@name, length, for_stack[-1])
for_stack.push(loop_vars) for_stack.push(loop_vars)
begin begin
context['forloop'.freeze] = loop_vars context['forloop'.freeze] = loop_vars
level = context.create_level(@variable_name)
segment.each do |item| segment.each do |item|
context[@variable_name] = item context.set_level(@variable_name, item, level)
@for_block.render_to_output_buffer(context, output) result << @for_block.render(context)
loop_vars.send(:increment!) loop_vars.send(:increment!)
# Handle any interrupts if they exist. # Handle any interrupts if they exist.
if context.interrupt? if context.interrupt?
interrupt = context.pop_interrupt interrupt = context.pop_interrupt
@@ -176,12 +176,15 @@ module Liquid
next if interrupt.is_a? ContinueInterrupt next if interrupt.is_a? ContinueInterrupt
end end
end end
context.unset(@variable_name)
context.unset('forloop'.freeze)
ensure ensure
for_stack.pop for_stack.pop
end end
end end
output result
end end
def set_attribute(key, expr) def set_attribute(key, expr)
@@ -197,12 +200,8 @@ module Liquid
end end
end end
def render_else(context, output) def render_else(context)
if @else_block @else_block ? @else_block.render(context) : ''.freeze
@else_block.render_to_output_buffer(context, output)
else
output
end
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor

View File

@@ -39,16 +39,13 @@ module Liquid
end end
end end
def render_to_output_buffer(context, output) def render(context)
context.stack do
@blocks.each do |block| @blocks.each do |block|
if block.evaluate(context) if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output) return block.attachment.render(context)
end end
end end
end ''.freeze
output
end end
private private

View File

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

View File

@@ -42,7 +42,7 @@ module Liquid
def parse(_tokens) def parse(_tokens)
end end
def render_to_output_buffer(context, output) def render(context)
template_name = context.evaluate(@template_name_expr) template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
@@ -60,27 +60,25 @@ module Liquid
begin begin
context.template_name = template_name context.template_name = template_name
context.partial = true context.partial = true
context.stack do context.stack(context_variable_name, *@attributes.keys) do
@attributes.each do |key, value| @attributes.each do |key, value|
context[key] = context.evaluate(value) context[key] = context.evaluate(value)
end end
if variable.is_a?(Array) if variable.is_a?(Array)
variable.each do |var| variable.collect do |var|
context[context_variable_name] = var context[context_variable_name] = var
partial.render_to_output_buffer(context, output) partial.render(context)
end end
else else
context[context_variable_name] = variable context[context_variable_name] = variable
partial.render_to_output_buffer(context, output) partial.render(context)
end end
end end
ensure ensure
context.template_name = old_template_name context.template_name = old_template_name
context.partial = old_partial context.partial = old_partial
end end
output
end end
private private

View File

@@ -20,11 +20,10 @@ module Liquid
@variable = markup.strip @variable = markup.strip
end end
def render_to_output_buffer(context, output) def render(context)
value = context.environments.first[@variable] ||= 0 value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1 context.environments.first[@variable] = value + 1
output << value.to_s value.to_s
output
end end
end end

View File

@@ -22,9 +22,8 @@ module Liquid
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name)) raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
end end
def render_to_output_buffer(_context, output) def render(_context)
output << @body @body
output
end end
def nodelist def nodelist

View File

@@ -18,7 +18,7 @@ module Liquid
end end
end end
def render_to_output_buffer(context, output) def render(context)
collection = context.evaluate(@collection_name) or return ''.freeze collection = context.evaluate(@collection_name) or return ''.freeze
from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0 from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
@@ -30,28 +30,25 @@ module Liquid
cols = context.evaluate(@attributes['cols'.freeze]).to_i cols = context.evaluate(@attributes['cols'.freeze]).to_i
output << "<tr class=\"row1\">\n" result = "<tr class=\"row1\">\n"
context.stack do context.stack('tablerowloop', @variable_name) do
tablerowloop = Liquid::TablerowloopDrop.new(length, cols) tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
context['tablerowloop'.freeze] = tablerowloop context['tablerowloop'.freeze] = tablerowloop
collection.each do |item| collection.each do |item|
context[@variable_name] = item context[@variable_name] = item
output << "<td class=\"col#{tablerowloop.col}\">" result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
super
output << '</td>'
if tablerowloop.col_last && !tablerowloop.last if tablerowloop.col_last && !tablerowloop.last
output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">" result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
end end
tablerowloop.send(:increment!) tablerowloop.send(:increment!)
end end
end end
result << "</tr>\n"
output << "</tr>\n" result
output
end end
class ParseTreeVisitor < Liquid::ParseTreeVisitor class ParseTreeVisitor < Liquid::ParseTreeVisitor

View File

@@ -6,23 +6,21 @@ module Liquid
# {% unless x < 0 %} x is greater than zero {% endunless %} # {% unless x < 0 %} x is greater than zero {% endunless %}
# #
class Unless < If class Unless < If
def render_to_output_buffer(context, output) def render(context)
context.stack do
# First condition is interpreted backwards ( if not ) # First condition is interpreted backwards ( if not )
first_block = @blocks.first first_block = @blocks.first
unless first_block.evaluate(context) unless first_block.evaluate(context)
return first_block.attachment.render_to_output_buffer(context, output) return first_block.attachment.render(context)
end end
# After the first condition unless works just like if # After the first condition unless works just like if
@blocks[1..-1].each do |block| @blocks[1..-1].each do |block|
if block.evaluate(context) if block.evaluate(context)
return block.attachment.render_to_output_buffer(context, output) return block.attachment.render(context)
end
end end
end end
output ''.freeze
end end
end end

View File

@@ -50,7 +50,7 @@ module Liquid
private private
def lookup_class(name) def lookup_class(name)
Object.const_get(name) name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
end end
end end
@@ -187,12 +187,9 @@ module Liquid
raise ArgumentError, "Expected Hash or Liquid::Context as parameter" raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end end
output = nil
case args.last case args.last
when Hash when Hash
options = args.pop options = args.pop
output = options[:output] if options[:output]
registers.merge!(options[:registers]) if options[:registers].is_a?(Hash) registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
@@ -207,9 +204,10 @@ module Liquid
begin begin
# render the nodelist. # render the nodelist.
# for performance reasons we get an array back here. join will make a string out of it. # for performance reasons we get an array back here. join will make a string out of it.
with_profiling(context) do result = with_profiling(context) do
@root.render_to_output_buffer(context, output || '') @root.render(context)
end end
result.respond_to?(:join) ? result.join : result
rescue Liquid::MemoryError => e rescue Liquid::MemoryError => e
context.handle_error(e) context.handle_error(e)
ensure ensure
@@ -222,10 +220,6 @@ module Liquid
render(*args) render(*args)
end end
def render_to_output_buffer(context, output)
render(context, output: output)
end
private private
def tokenize(source) def tokenize(source)

View File

@@ -1,31 +1,25 @@
module Liquid module Liquid
class Tokenizer class Tokenizer
attr_reader :line_number, :for_liquid_tag attr_reader :line_number
def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false) def initialize(source, line_numbers = false)
@source = source @source = source
@line_number = line_number || (line_numbers ? 1 : nil) @line_number = line_numbers ? 1 : nil
@for_liquid_tag = for_liquid_tag
@tokens = tokenize @tokens = tokenize
end end
def shift def shift
token = @tokens.shift or return token = @tokens.shift
@line_number += token.count("\n") if @line_number && token
if @line_number
@line_number += @for_liquid_tag ? 1 : token.count("\n")
end
token token
end end
private private
def tokenize def tokenize
@source = @source.source if @source.respond_to?(:source)
return [] if @source.to_s.empty? return [] if @source.to_s.empty?
return @source.split("\n") if @for_liquid_tag
tokens = @source.split(TemplateParser) tokens = @source.split(TemplateParser)
# removes the rogue empty element at the beginning of the array # removes the rogue empty element at the beginning of the array

View File

@@ -85,23 +85,12 @@ module Liquid
end end
obj = context.apply_global_filter(obj) obj = context.apply_global_filter(obj)
taint_check(context, obj) taint_check(context, obj)
obj obj
end end
def render_to_output_buffer(context, output)
obj = render(context)
if obj.is_a?(Array)
output << obj.join
elsif obj.nil?
else
output << obj.to_s
end
output
end
private private
def parse_filter_expressions(filter_name, unparsed_args) def parse_filter_expressions(filter_name, unparsed_args)

View File

@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
s.license = "MIT" s.license = "MIT"
# s.description = "A secure, non-evaling end user template engine with aesthetic markup." # s.description = "A secure, non-evaling end user template engine with aesthetic markup."
s.required_ruby_version = ">= 2.4.0" s.required_ruby_version = ">= 2.1.0"
s.required_rubygems_version = ">= 1.3.7" s.required_rubygems_version = ">= 1.3.7"
s.test_files = Dir.glob("{test}/**/*") s.test_files = Dir.glob("{test}/**/*")

View File

@@ -2,62 +2,25 @@
require 'benchmark/ips' require 'benchmark/ips'
require 'memory_profiler' require 'memory_profiler'
require 'terminal-table'
require_relative 'theme_runner' require_relative 'theme_runner'
class Profiler
LOG_LABEL = "Profiling: ".rjust(14).freeze
REPORTS_DIR = File.expand_path('.memprof', __dir__).freeze
def self.run
puts
yield new
end
def initialize
@allocated = []
@retained = []
@headings = []
end
def profile(phase, &block) def profile(phase, &block)
print LOG_LABEL
print "#{phase}.. ".ljust(10)
report = MemoryProfiler.report(&block)
puts 'Done.'
@headings << phase.capitalize
@allocated << "#{report.scale_bytes(report.total_allocated_memsize)} (#{report.total_allocated} objects)"
@retained << "#{report.scale_bytes(report.total_retained_memsize)} (#{report.total_retained} objects)"
return if ENV['CI']
require 'fileutils'
report_file = File.join(REPORTS_DIR, "#{sanitize(phase)}.txt")
FileUtils.mkdir_p(REPORTS_DIR)
report.pretty_print(to_file: report_file, scale_bytes: true)
end
def tabulate
table = Terminal::Table.new(headings: @headings.unshift('Phase')) do |t|
t << @allocated.unshift('Total allocated')
t << @retained.unshift('Total retained')
end
puts puts
puts table puts "#{phase}:"
puts "\nDetailed report(s) saved to #{REPORTS_DIR}/" unless ENV['CI'] puts
end
def sanitize(string) report = MemoryProfiler.report(&block)
string.downcase.gsub(/[\W]/, '-').squeeze('-')
end report.pretty_print(
color_output: true,
scale_bytes: true,
detailed_report: true
)
end end
Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first Liquid::Template.error_mode = ARGV.first.to_sym if ARGV.first
runner = ThemeRunner.new profiler = ThemeRunner.new
Profiler.run do |x|
x.profile('parse') { runner.compile } profile("Parsing") { profiler.compile }
x.profile('render') { runner.render } profile("Rendering") { profiler.render }
x.tabulate
end

View File

@@ -12,10 +12,10 @@ class CommentForm < Liquid::Block
end end
end end
def render_to_output_buffer(context, output) def render(context)
article = context[@variable_name] article = context[@variable_name]
context.stack do context.stack('form') do
context['form'] = { context['form'] = {
'posted_successfully?' => context.registers[:posted_successfully], 'posted_successfully?' => context.registers[:posted_successfully],
'errors' => context['comment.errors'], 'errors' => context['comment.errors'],
@@ -23,9 +23,7 @@ class CommentForm < Liquid::Block
'email' => context['comment.email'], 'email' => context['comment.email'],
'body' => context['comment.body'] 'body' => context['comment.body']
} }
wrap_in_form(article, render_all(@nodelist, context))
output << wrap_in_form(article, render_all(@nodelist, context, output))
output
end end
end end

View File

@@ -21,10 +21,10 @@ class Paginate < Liquid::Block
end end
end end
def render_to_output_buffer(context, output) def render(context)
@context = context @context = context
context.stack do context.stack('paginate') do
current_page = context['current_page'].to_i current_page = context['current_page'].to_i
pagination = { pagination = {

View File

@@ -1,10 +1,11 @@
require 'test_helper' require 'test_helper'
class FoobarTag < Liquid::Tag class FoobarTag < Liquid::Tag
def render_to_output_buffer(context, output) def render(*args)
output << ' ' " "
output
end end
Liquid::Template.register_tag('foobar', FoobarTag)
end end
class BlankTestFileSystem class BlankTestFileSystem
@@ -30,10 +31,8 @@ class BlankTest < Minitest::Test
end end
def test_new_tags_are_not_blank_by_default def test_new_tags_are_not_blank_by_default
with_custom_tag('foobar', FoobarTag) do
assert_template_result(" " * N, wrap_in_for("{% foobar %}")) assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
end end
end
def test_loops_are_blank def test_loops_are_blank
assert_template_result("", wrap_in_for(" ")) assert_template_result("", wrap_in_for(" "))

View File

@@ -1,14 +1,6 @@
require 'test_helper' require 'test_helper'
class ContextDrop < Liquid::Drop class ContextDrop < Liquid::Drop
def scopes
@context.scopes.size
end
def scopes_as_array
(1..@context.scopes.size).to_a
end
def loop_pos def loop_pos
@context['forloop.index'] @context['forloop.index']
end end
@@ -194,31 +186,6 @@ class DropsTest < Minitest::Test
end end
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 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]) assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
end end

View File

@@ -405,11 +405,11 @@ class StandardFiltersTest < Minitest::Test
def test_map_over_drops_returning_procs def test_map_over_drops_returning_procs
drops = [ drops = [
{ {
"proc" => ->{ "foo" }, "proc" => ->{ "foo" }
}, },
{ {
"proc" => ->{ "bar" }, "proc" => ->{ "bar" }
}, }
] ]
templ = '{{ drops | map: "proc" }}' templ = '{{ drops | map: "proc" }}'
assert_template_result "foobar", templ, "drops" => drops assert_template_result "foobar", templ, "drops" => drops

View File

@@ -1,11 +0,0 @@
require 'test_helper'
class EchoTest < Minitest::Test
include Liquid
def test_echo_outputs_its_input
assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' })
{%- echo variable-name | upcase -%}
LIQUID
end
end

View File

@@ -368,6 +368,23 @@ HERE
assert_template_result(expected, template, assigns) assert_template_result(expected, template, assigns)
end 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 class LoaderDrop < Liquid::Drop
attr_accessor :each_called, :load_slice_called attr_accessor :each_called, :load_slice_called

View File

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

View File

@@ -66,9 +66,8 @@ class CustomInclude < Liquid::Tag
def parse(tokens) def parse(tokens)
end end
def render_to_output_buffer(context, output) def render(context)
output << @template_name[1..-2] @template_name[1..-2]
output
end end
end end

View File

@@ -1,104 +0,0 @@
require 'test_helper'
class LiquidTagTest < Minitest::Test
include Liquid
def test_liquid_tag
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
{%- liquid
echo array | join: " "
-%}
LIQUID
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
{%- liquid
for value in array
echo value
unless forloop.last
echo " "
endunless
endfor
-%}
LIQUID
assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3])
{%- liquid
for value in array
assign double_value = value | times: 2
echo double_value | times: 2
unless forloop.last
echo " "
endunless
endfor
echo " "
echo double_value
-%}
LIQUID
assert_template_result('abc', <<~LIQUID)
{%- liquid echo "a" -%}
b
{%- liquid echo "c" -%}
LIQUID
end
def test_liquid_tag_errors
assert_match_syntax_error("syntax error (line 1): Unknown tag 'error'", <<~LIQUID)
{%- liquid error no such tag -%}
LIQUID
assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID)
{{ test }}
{%-
liquid
for value in array
error no such tag
endfor
-%}
LIQUID
assert_match_syntax_error("syntax error (line 2): Unknown tag '!!! the guards are vigilant'", <<~LIQUID)
{%- liquid
!!! the guards are vigilant
-%}
LIQUID
assert_match_syntax_error("syntax error (line 4): 'for' tag was never closed", <<~LIQUID)
{%- liquid
for value in array
echo 'forgot to close the for tag'
-%}
LIQUID
end
def test_line_number_is_correct_after_a_blank_token
assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n\n error %}")
assert_match_syntax_error("syntax error (line 3): Unknown tag 'error'", "{% liquid echo ''\n \n error %}")
end
def test_cannot_open_blocks_living_past_a_liquid_tag
assert_match_syntax_error("syntax error (line 3): 'if' tag was never closed", <<~LIQUID)
{%- liquid
if true
-%}
{%- endif -%}
LIQUID
end
def test_quirk_can_close_blocks_created_before_a_liquid_tag
assert_template_result("42", <<~LIQUID)
{%- if true -%}
42
{%- liquid endif -%}
LIQUID
end
def test_liquid_tag_in_raw
assert_template_result("{% liquid echo 'test' %}\n", <<~LIQUID)
{% raw %}{% liquid echo 'test' %}{% endraw %}
LIQUID
end
end

View File

@@ -14,7 +14,7 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
end end
Liquid::Template.error_mode = mode Liquid::Template.error_mode = mode
if ENV['LIQUID_C'] == '1' if ENV['LIQUID-C'] == '1'
puts "-- LIQUID C" puts "-- LIQUID C"
require 'liquid/c' require 'liquid/c'
end end
@@ -37,18 +37,18 @@ module Minitest
include Liquid include Liquid
def assert_template_result(expected, template, assigns = {}, message = nil) def assert_template_result(expected, template, assigns = {}, message = nil)
assert_equal expected, Template.parse(template, line_numbers: true).render!(assigns), message assert_equal expected, Template.parse(template).render!(assigns), message
end end
def assert_template_result_matches(expected, template, assigns = {}, message = nil) def assert_template_result_matches(expected, template, assigns = {}, message = nil)
return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp return assert_template_result(expected, template, assigns, message) unless expected.is_a? Regexp
assert_match expected, Template.parse(template, line_numbers: true).render!(assigns), message assert_match expected, Template.parse(template).render!(assigns), message
end end
def assert_match_syntax_error(match, template, assigns = {}) def assert_match_syntax_error(match, template, assigns = {})
exception = assert_raises(Liquid::SyntaxError) do exception = assert_raises(Liquid::SyntaxError) do
Template.parse(template, line_numbers: true).render(assigns) Template.parse(template).render(assigns)
end end
assert_match match, exception.message assert_match match, exception.message
end end
@@ -84,13 +84,6 @@ module Minitest
ensure ensure
Liquid::Template.error_mode = old_mode Liquid::Template.error_mode = old_mode
end end
def with_custom_tag(tag_name, tag_class)
Liquid::Template.register_tag(tag_name, tag_class)
yield
ensure
Liquid::Template.tags.delete(tag_name)
end
end end
end end

View File

@@ -44,47 +44,10 @@ class BlockUnitTest < Minitest::Test
end end
def test_with_custom_tag def test_with_custom_tag
with_custom_tag('testtag', Block) do Liquid::Template.register_tag("testtag", Block)
assert Liquid::Template.parse("{% testtag %} {% endtesttag %}") assert Liquid::Template.parse("{% testtag %} {% endtesttag %}")
end ensure
end Liquid::Template.tags.delete('testtag')
def test_custom_block_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
klass1 = Class.new(Block) do
def render(*)
'hello'
end
end
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %} bla {% endblabla %}")
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
end
klass2 = Class.new(klass1) do
def render(*)
'foo' + super + 'bar'
end
end
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %} foo {% endblabla %}")
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
end
end end
private private

View File

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

View File

@@ -18,42 +18,4 @@ class TagUnitTest < Minitest::Test
tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new) tag = Tag.parse("some_tag", "", Tokenizer.new(""), ParseContext.new)
assert_equal 'some_tag', tag.tag_name assert_equal 'some_tag', tag.tag_name
end end
def test_custom_tags_have_a_default_render_to_output_buffer_method_for_backwards_compatibility
klass1 = Class.new(Tag) do
def render(*)
'hello'
end
end
with_custom_tag('blabla', klass1) do
template = Liquid::Template.parse("{% blabla %}")
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
end
klass2 = Class.new(klass1) do
def render(*)
'foo' + super + 'bar'
end
end
with_custom_tag('blabla', klass2) do
template = Liquid::Template.parse("{% blabla %}")
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
end
end
end end