Compare commits

..

22 Commits

Author SHA1 Message Date
Mike Angell
8318be2edc Update readme 2019-09-11 05:20:05 +10:00
Mike Angell
b6547f322e Simplify usage 2019-09-11 04:56:25 +10:00
Mike Angell
b316ff8413 Add usage tracking 2019-09-11 04:20:34 +10:00
Justin Li
806b2622da Switch back to Liquid-C master, since https://github.com/Shopify/liquid-c/pull/50 is merged 2019-09-04 15:12:51 -04:00
Mike Angell
c34f7c9b2c Merge pull request #1145 from Shopify/master-fixes
Render tag styling fixes
2019-09-04 14:25:38 +10:00
Mike Angell
604d899496 Render tag styling fixes 2019-08-31 22:48:25 +10:00
Mike Angell
ddb45cd658 Merge pull request #1139 from Shopify/shopify_ruby_style
Follow Shopify ruby style guide
2019-08-31 21:43:45 +10:00
Justin Li
9876096cf4 Merge pull request #1141 from ashmaroli/reduce-context-constructor-allocations
Reduce allocations from `Liquid::Context.new`
2019-08-30 12:53:50 -04:00
Ashwin Maroli
8750b4b006 Reduce allocations from Liquid::Context.new 2019-08-30 09:01:47 +05:30
Samuel Doiron
34083c96d5 Merge pull request #1122 from Shopify/render-tag
Add new `{% render %}` tag
2019-08-29 16:49:56 -04:00
Samuel
9672ed5285 Add a new {% render %} tag
Example:

```
// the_count.liquid
{{ number }}! Ah ah ah.

// my_template.liquid
{% for number in range (1..3) %}
  {% render "the_count", number: number %}
{% endfor %}

Output:
1! Ah ah ah.
2! Ah ah ah.
3! Ah ah ah.
```

The `render` tag is a more strict version of the `include` tag. It is
designed to isolate itself from the parent rendering context both by
creating a new scope (which does not inherit the parent scope) and by
only inheriting "static" registers.

Static registers are those that do not hold mutable state which could
affect rendering. This again helps `render`ed templates remain entirely
separate from their calling context.

Unlike `include`, `render` does not permit specifying the target
template using a variable, only a string literal. For example, this
means that `{% render my_dynamic_template %}` is invalid syntax. This
will make it possible to statically analyze the dependencies between
templates without making Turing angry.

Note that the `static_environment` of a rendered template is inherited, unlike
the scope and regular environment. This environment is immutable from within the
template.

An alternate syntax, which mimics the `{% include ... for %}` tag is
currently in design discussion.
2019-08-29 16:32:05 -04:00
Justin Li
f3112fc038 Merge pull request #1136 from ashmaroli/travis-selected-branches
Build only pushes to certain branches on Travis CI
2019-08-29 13:47:59 -04:00
Samuel
d338ccb9a6 Add isolated subcontexts
An isolated subcontext inherits the environment, filters,
and static registers of its supercontext, but with a fresh
(isolated) scope.

This will pave the way for adding the `render` tag, which renders
templates in such a subcontext.
2019-08-29 10:27:15 -04:00
Mike Angell
d67de1c9b2 Follow Shopify ruby style
This is the first step in bringing Liquid style inline with Shopify ruby style
2019-08-29 13:39:57 +10:00
Ashwin Maroli
b3097f143c Build only pushes to certain branches on Travis CI 2019-08-28 21:28:49 +05:30
Mike Angell
7b309dc75d Merge pull request #1135 from Shopify/fix-failing-rubocop
Resolve failing rubocop issues
2019-08-29 01:11:25 +10:00
Mike Angell
8f68cffdf1 Resolve failing rubocop issues 2019-08-29 00:45:38 +10:00
Mike Angell
dd27d0fd1d Merge pull request #1133 from Shopify/liquid-tag-fixes
Bugfix for new Liquid tag
2019-08-29 00:36:13 +10:00
Mike Angell
7a26e6b3d8 Merge pull request #1131 from Shopify/bump-ruby-2-4
Rubocop upgrade, Ruby 2.4 minimum and TruffleRuby
2019-08-29 00:33:42 +10:00
Mike Angell
cf4e77ab0c Merge branch 'master' into bump-ruby-2-4 2019-08-29 00:24:45 +10:00
Mike Angell
7bae55dd39 Bugfix for new Liquid tag 2019-08-28 23:39:19 +10:00
Mike Angell
b16b109a80 Bump Minimum version to 2.4 and bump Rubocop 2019-08-28 00:31:44 +10:00
25 changed files with 1916 additions and 378 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,132 +1,16 @@
inherit_from:
- https://shopify.github.io/ruby-style-guide/rubocop.yml
- .rubocop_todo.yml
- ./.rubocop_todo.yml
require: rubocop-performance
Performance:
Enabled: true
AllCops:
Exclude:
- '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
- 'vendor/bundle/**/*'
Naming/MethodName:
Exclude:
- 'example/server/liquid_servlet.rb'
- 'example/server/liquid_servlet.rb'

View File

@@ -1,23 +1,37 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-04-22 19:11:24 -0400 using RuboCop version 0.53.0.
# on 2019-08-29 12:16:25 +1000 using RuboCop version 0.74.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: 1
# Offense count: 13
# Cop supports --auto-correct.
# Configuration parameters: Include, TreatCommentsAsGroupSeparators.
# Include: **/*.gemspec
Gemspec/OrderedDependencies:
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
# SupportedHashRocketStyles: key, separator, table
# SupportedColonStyles: key, separator, table
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
Layout/AlignHash:
Exclude:
- 'liquid.gemspec'
- 'lib/liquid/condition.rb'
- '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
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
# SupportedStyles: squiggly, active_support, powerpack, unindent
Layout/IndentHeredoc:
Exclude:
- 'test/integration/tags/for_tag_test.rb'
@@ -32,6 +46,62 @@ Layout/MultilineMethodCallBraceLayout:
- 'test/integration/error_handling_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
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -40,145 +110,184 @@ Lint/InheritException:
Exclude:
- '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
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
- 'lib/liquid/parse_context.rb'
# Offense count: 53
Metrics/AbcSize:
Max: 56
# Offense count: 95
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294
# Offense count: 12
Metrics/CyclomaticComplexity:
Max: 13
# Offense count: 112
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 38
# Offense count: 8
Metrics/PerceivedComplexity:
Max: 11
# Offense count: 52
# Configuration parameters: Blacklist.
# Blacklist: END, (?-mix:EO[A-Z]{1})
Naming/HeredocDelimiterNaming:
# Offense count: 44
Naming/ConstantName:
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.rb'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- 'lib/liquid/tags/cycle.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/if.rb'
- 'lib/liquid/utils.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.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'
- 'performance/shopify/comment_form.rb'
- 'performance/shopify/paginate.rb'
- 'test/integration/tags/include_tag_test.rb'
# Offense count: 12
# 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: prefer_alias, prefer_alias_method
Style/Alias:
# SupportedStyles: always, conditionals
Style/AndOr:
Exclude:
- 'lib/liquid/drop.rb'
- 'lib/liquid/i18n.rb'
- 'lib/liquid/profiler/hooks.rb'
- 'lib/liquid/standardfilters.rb'
- 'lib/liquid/tag.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/variable.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/tokenizer.rb'
# Offense count: 22
Style/CommentedKeyword:
Enabled: false
# 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/render_profiling_test.rb'
- 'test/integration/standard_filter_test.rb'
- 'test/integration/tags/echo_test.rb'
- 'test/integration/tags/increment_tag_test.rb'
- 'test/integration/tags/standard_tag_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/condition_unit_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 1
# Offense count: 5
Style/ClassVars:
Exclude:
- 'lib/liquid/condition.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/template.rb'
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
# SupportedStyles: assign_to_condition, assign_inside_condition
Style/ConditionalAssignment:
Exclude:
- 'lib/liquid/errors.rb'
- 'performance/shopify/shop_filter.rb'
# Offense count: 1
# Configuration parameters: AllowCoercion.
Style/DateTime:
Exclude:
- 'test/unit/context_unit_test.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/EachWithObject:
Exclude:
- 'performance/shopify/database.rb'
# Offense count: 1
# Cop supports --auto-correct.
Style/EmptyCaseCondition:
Exclude:
- 'lib/liquid/block_body.rb'
- 'lib/liquid/lexer.rb'
# Offense count: 5
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, expanded
Style/EmptyMethod:
# SupportedStyles: each, for
Style/For:
Exclude:
- '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'
- 'performance/shopify/shop_filter.rb'
# Offense count: 3
# Offense count: 9
# Cop supports --auto-correct.
Style/Encoding:
Exclude:
- '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:
# SupportedStyles: format, sprintf, percent
Style/FormatString:
Exclude:
- 'example/server/example_servlet.rb'
- 'performance/shopify/money_filter.rb'
- 'performance/shopify/weight_filter.rb'
- 'test/integration/filter_test.rb'
- 'test/integration/hash_ordering_test.rb'
# Offense count: 14
# Configuration parameters: MinBodyLength.
Style/GuardClause:
# Offense count: 115
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# 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:
- 'lib/liquid/condition.rb'
- 'lib/liquid/lexer.rb'
- 'lib/liquid/strainer.rb'
- 'lib/liquid/tags/assign.rb'
- 'lib/liquid/tags/capture.rb'
- 'lib/liquid/tags/case.rb'
- 'Gemfile'
- 'Rakefile'
- 'lib/liquid/block_body.rb'
- 'lib/liquid/parser.rb'
- 'lib/liquid/tags/for.rb'
- 'lib/liquid/tags/include.rb'
- 'lib/liquid/tags/raw.rb'
- 'lib/liquid/tags/table_row.rb'
- 'lib/liquid/variable.rb'
- 'test/unit/tokenizer_unit_test.rb'
- 'liquid.gemspec'
- 'performance/shopify/database.rb'
- 'performance/shopify/liquid.rb'
- 'test/test_helper.rb'
- 'test/unit/condition_unit_test.rb'
- 'test/unit/tags/if_tag_unit_test.rb'
# Offense count: 1
# Cop supports --auto-correct.
@@ -188,27 +297,17 @@ Style/Next:
Exclude:
- 'lib/liquid/tags/for.rb'
# Offense count: 4
# Offense count: 52
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, EnforcedStyle.
# 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'
Style/PerlBackrefs:
Enabled: false
# Offense count: 14
# Offense count: 33
# Cop supports --auto-correct.
# Configuration parameters: PreferredDelimiters.
Style/PercentLiteralDelimiters:
Exclude:
- 'lib/liquid/tags/if.rb'
- 'liquid.gemspec'
- 'test/integration/assign_test.rb'
- 'test/integration/standard_filter_test.rb'
# Configuration parameters: EnforcedStyle.
# SupportedStyles: compact, exploded
Style/RaiseArgs:
Enabled: false
# Offense count: 1
# Cop supports --auto-correct.
@@ -216,21 +315,52 @@ Style/RedundantSelf:
Exclude:
- 'lib/liquid/strainer.rb'
# Offense count: 9
# Offense count: 5
# 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.
# Configuration parameters: AllowAsExpressionSeparator.
Style/Semicolon:
Exclude:
- 'performance/shopify/database.rb'
- 'test/integration/error_handling_test.rb'
- 'test/integration/template_test.rb'
- 'test/unit/context_unit_test.rb'
# Offense count: 7
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: MinSize.
# SupportedStyles: percent, brackets
Style/SymbolArray:
EnforcedStyle: brackets
# Configuration parameters: EnforcedStyle.
# SupportedStyles: use_perl_names, use_english_names
Style/SpecialGlobalVars:
Exclude:
- '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
# Cop supports --auto-correct.
@@ -241,6 +371,33 @@ Style/TernaryParentheses:
- 'lib/liquid/context.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
# Cop supports --auto-correct.
Style/UnneededPercentQ:
@@ -252,9 +409,3 @@ Style/UnneededPercentQ:
Style/WhileUntilModifier:
Exclude:
- 'lib/liquid/tags/case.rb'
# Offense count: 648
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 294

View File

@@ -1,20 +1,14 @@
language: ruby
cache: bundler
rvm:
- 2.1
- 2.2
- 2.3
- 2.4
- 2.5
- &latest_ruby 2.6
- 2.7
- ruby-head
- jruby-head
# - rbx-2
addons:
apt:
packages:
- libgmp3-dev
- truffleruby
matrix:
include:
@@ -24,11 +18,13 @@ matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: truffleruby
install:
- bundle install
script: bundle exec rake
branches:
only:
- master
- gh-pages
- /.*-stable/
notifications:
disable: true

View File

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

View File

@@ -106,3 +106,9 @@ template = Liquid::Template.parse("{{x}} {{y}}")
template.render!({ 'x' => 1}, { strict_variables: true })
#=> Liquid::UndefinedVariable: Liquid error: undefined variable y
```
### Usage tracking
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.

View File

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

View File

@@ -74,6 +74,8 @@ require 'liquid/condition'
require 'liquid/utils'
require 'liquid/tokenizer'
require 'liquid/parse_context'
require 'liquid/partial_cache'
require 'liquid/usage'
# Load all the tags of the standard library
#

View File

@@ -12,26 +12,41 @@ module Liquid
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
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 || {})]
@registers = registers
@errors = []
@interrupts = []
@filters = []
@global_filter = nil
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
# rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers, static_environments)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {})
@environments = [environments]
@environments.flatten!
@static_environments = [static_environments].flat_map(&:freeze).freeze
@scopes = [(outer_scope || {})]
@registers = registers
@static_registers = static_registers.freeze
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
@base_scope_depth = 0
squash_instance_assigns_with_environments
@this_stack_used = false
self.exception_renderer = Template.default_exception_renderer
self.exception_renderer = ->(e) { raise } if rethrow_errors
if rethrow_errors
self.exception_renderer = ->(e) { raise }
end
squash_instance_assigns_with_environments
@interrupts = []
@filters = []
@global_filter = nil
end
# rubocop:enable Metrics/ParameterLists
def warnings
@warnings ||= []
@@ -83,9 +98,9 @@ module Liquid
end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push
@scopes.unshift({})
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > (Block::MAX_DEPTH + 1)
def push(new_scope = {})
@scopes.unshift(new_scope)
check_overflow
end
# Merge a hash of variables in the current local scope
@@ -107,15 +122,50 @@ module Liquid
# end
#
# context['var] #=> nil
def stack
push
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
end
yield
ensure
pop
pop if @this_stack_used
@this_stack_used = old_stack_used
end
# Creates a new context inheriting resource limits, filters, environment etc.,
# but with an isolated scope.
def new_isolated_subcontext
check_overflow
Context.build(
resource_limits: resource_limits,
static_environments: static_environments,
static_registers: static_registers
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters
subcontext.strainer = nil
subcontext.errors = errors
subcontext.warnings = warnings
end
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
end
@@ -143,12 +193,15 @@ module Liquid
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 = (index = @scopes.find_index { |s| s.key?(key) }) && @scopes[index]
scope ||= (index = @environments.find_index { |s| s.key?(key) }) && @environments[index]
scope ||= {}
variable = if index
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
else
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
end
variable = lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found).to_liquid
variable = variable.to_liquid
variable.context = self if variable.respond_to?(:context=)
variable
@@ -168,8 +221,38 @@ module Liquid
end
end
protected
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
private
attr_reader :base_scope_depth
def try_variable_find_in_environments(key, raise_on_not_found:)
@environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
@static_environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
nil
end
def check_overflow
raise StackLevelError, "Nesting too deep".freeze if overflow?
end
def overflow?
base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
def internal_error
# raise and catch to set backtrace and cause on exception
raise Liquid::InternalError, 'internal'

View File

@@ -22,5 +22,6 @@
tag_never_closed: "'%{block_name}' tag was never closed"
meta_syntax_error: "Liquid syntax error: #{e.message}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
include: "Argument error in tag 'include' - Illegal template name"

View File

@@ -0,0 +1,18 @@
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = (context.registers[:cached_partials] ||= {})
cached = cached_partials[template_name]
return cached if cached
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name)
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
cached_partials[template_name] = partial
ensure
parse_context.partial = false
end
end
end

View File

@@ -421,6 +421,7 @@ module Liquid
def default(input, default_value = ''.freeze)
if !input || input.respond_to?(:empty?) && input.empty?
Usage.increment("default_filter_received_false_value") if input == false # See https://github.com/Shopify/liquid/issues/1127
default_value
else
input

View File

@@ -16,7 +16,7 @@ module Liquid
end
def render(context)
@variable.render(context)
@variable.render_to_output_buffer(context, '')
end
end

View File

@@ -46,7 +46,12 @@ module Liquid
template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
partial = load_cached_partial(template_name, context)
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
context_variable_name = template_name.split('/'.freeze).last
variable = if @variable_name_expr
@@ -83,35 +88,9 @@ module Liquid
output
end
private
alias_method :parse_context, :options
private :parse_context
def load_cached_partial(template_name, context)
cached_partials = context.registers[:cached_partials] || {}
if cached = cached_partials[template_name]
return cached
end
source = read_template_from_file_system(context)
begin
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
ensure
parse_context.partial = false
end
cached_partials[template_name] = partial
context.registers[:cached_partials] = cached_partials
partial
end
def read_template_from_file_system(context)
file_system = context.registers[:file_system] || Liquid::Template.file_system
file_system.read_template_file(context.evaluate(@template_name_expr))
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[

View File

@@ -23,6 +23,7 @@ module Liquid
def render_to_output_buffer(context, output)
value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1
output << value.to_s
output
end

54
lib/liquid/tags/render.rb Normal file
View File

@@ -0,0 +1,54 @@
module Liquid
class Render < Tag
SYNTAX = /(#{QuotedString})#{QuotedFragment}*/o
attr_reader :template_name_expr, :attributes
def initialize(tag_name, markup, options)
super
raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ SYNTAX
template_name = $1
@template_name_expr = Expression.parse(template_name)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value)
end
end
def render_to_output_buffer(context, output)
# Though we evaluate this here we will only ever parse it as a string literal.
template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.partial = true
@attributes.each do |key, value|
inner_context[key] = context.evaluate(value)
end
partial.render_to_output_buffer(inner_context, output)
output
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.template_name_expr,
] + @node.attributes.values
end
end
end
Template.register_tag('render'.freeze, Render)
end

6
lib/liquid/usage.rb Normal file
View File

@@ -0,0 +1,6 @@
module Liquid
module Usage
def self.increment(name)
end
end
end

View File

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

View File

@@ -30,6 +30,7 @@ class Profiler
@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)

View File

@@ -5,72 +5,72 @@ class LiquidTagTest < Minitest::Test
def test_liquid_tag
assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
{%- liquid
echo array | join: " "
-%}
{%- 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
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
{%- 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
-%}
echo " "
echo double_value
-%}
LIQUID
assert_template_result('abc', <<~LIQUID)
{%- liquid echo "a" -%}
b
{%- liquid echo "c" -%}
{%- 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 error no such tag -%}
LIQUID
assert_match_syntax_error("syntax error (line 7): Unknown tag 'error'", <<~LIQUID)
{{ test }}
{{ test }}
{%-
liquid
for value in array
{%-
liquid
for value in array
error no such tag
endfor
-%}
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
!!! 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
for value in array
echo 'forgot to close the for tag'
-%}
LIQUID
end
@@ -81,24 +81,24 @@ class LiquidTagTest < Minitest::Test
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
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 -%}
{%- 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 %}
{% raw %}{% liquid echo 'test' %}{% endraw %}
LIQUID
end
end

View File

@@ -0,0 +1,149 @@
require 'test_helper'
class RenderTagTest < Minitest::Test
include Liquid
def test_render_with_no_arguments
Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
assert_template_result 'rendered content', '{% render "source" %}'
end
def test_render_tag_looks_for_file_system_in_registers_first
file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
assert_equal 'from register file system',
Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system })
end
def test_render_passes_named_arguments_into_inner_scope
Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
assert_template_result 'My Product', '{% render "product", inner_product: outer_product %}',
'outer_product' => { 'title' => 'My Product' }
end
def test_render_accepts_literals_as_arguments
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
assert_template_result '123', '{% render "snippet", price: 123 %}'
end
def test_render_accepts_multiple_named_arguments
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
assert_template_result '1 2', '{% render "snippet", one: 1, two: 2 %}'
end
def test_render_does_not_inherit_parent_scope_variables
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
assert_template_result '', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}'
end
def test_render_does_not_inherit_variable_with_same_name_as_snippet
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
assert_template_result '', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}"
end
def test_render_sets_the_correct_template_name_for_errors
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :error do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], template.errors.map(&:class)
assert_equal 'snippet', template.errors.first.template_name
end
end
def test_render_sets_the_correct_template_name_for_warnings
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :warn do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
assert_equal 'snippet', context.warnings.first.template_name
end
end
def test_render_does_not_mutate_parent_scope
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
assert_template_result '', "{% render 'snippet' %}{{ inner }}"
end
def test_nested_render_tag
Liquid::Template.file_system = StubFileSystem.new(
'one' => "one {% render 'two' %}",
'two' => 'two'
)
assert_template_result 'one two', "{% render 'one' %}"
end
def test_recursively_rendered_template_does_not_produce_endless_loop
Liquid::Template.file_system = StubFileSystem.new('loop' => '{% render "loop" %}')
assert_raises Liquid::StackLevelError do
Template.parse('{% render "loop" %}').render!
end
end
def test_includes_and_renders_count_towards_the_same_recursion_limit
Liquid::Template.file_system = StubFileSystem.new(
'loop_render' => '{% render "loop_include" %}',
'loop_include' => '{% include "loop_render" %}'
)
assert_raises Liquid::StackLevelError do
Template.parse('{% render "loop_include" %}').render!
end
end
def test_dynamically_choosen_templates_are_not_allowed
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
assert_raises Liquid::SyntaxError do
Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
end
end
def test_include_tag_caches_second_read_of_same_partial
file_system = StubFileSystem.new('snippet' => 'echo')
assert_equal 'echoecho',
Template.parse('{% render "snippet" %}{% render "snippet" %}')
.render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.file_read_count
end
def test_render_tag_doesnt_cache_partials_across_renders
file_system = StubFileSystem.new('snippet' => 'my message')
assert_equal 'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })
assert_equal 1, file_system.file_read_count
assert_equal 'my message',
Template.parse('{% include "snippet" %}').render!({}, registers: { file_system: file_system })
assert_equal 2, file_system.file_read_count
end
def test_render_tag_within_if_statement
Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
assert_template_result 'my message', '{% if true %}{% render "snippet" %}{% endif %}'
end
def test_break_through_render
Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
assert_template_result '1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}'
assert_template_result '112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}'
end
def test_increment_is_isolated_between_renders
Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
assert_template_result '010', '{% increment %}{% increment %}{% render "incr" %}'
end
def test_decrement_is_isolated_between_renders
Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
assert_template_result '-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}'
end
end

View File

@@ -80,7 +80,10 @@ class VariableTest < Minitest::Test
assigns['test'] = 'Tobi'
assert_equal 'Hello Tobi', template.render!(assigns)
assigns.delete('test')
assert_equal "Hello ", template.render!(assigns)
e = assert_raises(RuntimeError) do
template.render!(assigns)
end
assert_equal "Unknown variable 'test'", e.message
end
def test_multiline_variable

View File

@@ -14,7 +14,7 @@ if env_mode = ENV['LIQUID_PARSER_MODE']
end
Liquid::Template.error_mode = mode
if ENV['LIQUID-C'] == '1'
if ENV['LIQUID_C'] == '1'
puts "-- LIQUID C"
require 'liquid/c'
end
@@ -121,3 +121,17 @@ class ErrorDrop < Liquid::Drop
raise Exception, 'exception'
end
end
class StubFileSystem
attr_reader :file_read_count
def initialize(values)
@file_read_count = 0
@values = values
end
def read_template_file(template_path)
@file_read_count += 1
@values.fetch(template_path)
end
end

View File

@@ -468,11 +468,79 @@ class ContextUnitTest < Minitest::Test
assert_equal 'hi filtered', context.apply_global_filter('hi')
end
def test_static_environments_are_read_with_lower_priority_than_environments
context = Context.build(
static_environments: { 'shadowed' => 'static', 'unshadowed' => 'static' },
environments: { 'shadowed' => 'dynamic' }
)
assert_equal 'dynamic', context['shadowed']
assert_equal 'static', context['unshadowed']
end
def test_apply_global_filter_when_no_global_filter_exist
context = Context.new
assert_equal 'hi', context.apply_global_filter('hi')
end
def test_new_isolated_subcontext_does_not_inherit_variables
super_context = Context.new
super_context['my_variable'] = 'some value'
subcontext = super_context.new_isolated_subcontext
assert_nil subcontext['my_variable']
end
def test_new_isolated_subcontext_inherits_static_environment
super_context = Context.build(static_environments: { 'my_environment_value' => 'my value' })
subcontext = super_context.new_isolated_subcontext
assert_equal 'my value', subcontext['my_environment_value']
end
def test_new_isolated_subcontext_inherits_resource_limits
resource_limits = ResourceLimits.new({})
super_context = Context.new({}, {}, {}, false, resource_limits)
subcontext = super_context.new_isolated_subcontext
assert_equal resource_limits, subcontext.resource_limits
end
def test_new_isolated_subcontext_inherits_exception_renderer
super_context = Context.new
super_context.exception_renderer = ->(_e) { 'my exception message' }
subcontext = super_context.new_isolated_subcontext
assert_equal 'my exception message', subcontext.handle_error(Liquid::Error.new)
end
def test_new_isolated_subcontext_does_not_inherit_non_static_registers
registers = {
my_register: :my_value
}
super_context = Context.new({}, {}, registers)
subcontext = super_context.new_isolated_subcontext
assert_nil subcontext.registers[:my_register]
end
def test_new_isolated_subcontext_inherits_static_registers
super_context = Context.build(static_registers: { my_register: :my_value })
subcontext = super_context.new_isolated_subcontext
assert_equal :my_value, subcontext.static_registers[:my_register]
end
def test_new_isolated_subcontext_inherits_filters
my_filter = Module.new do
def my_filter(*)
'my filter result'
end
end
super_context = Context.new
super_context.add_filters([my_filter])
subcontext = super_context.new_isolated_subcontext
template = Template.parse('{{ 123 | my_filter }}')
assert_equal 'my filter result', template.render(subcontext)
end
private
def assert_no_object_allocations

View File

@@ -0,0 +1,91 @@
require 'test_helper'
class PartialCacheUnitTest < Minitest::Test
def test_uses_the_file_system_register_if_present
context = Liquid::Context.build(
registers: {
file_system: StubFileSystem.new('my_partial' => 'my partial body'),
}
)
partial = Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
)
assert_equal 'my partial body', partial.render
end
def test_reads_from_the_file_system_only_once_per_file
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
registers: { file_system: file_system }
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new
)
end
assert_equal 1, file_system.file_read_count
end
def test_cache_state_is_stored_per_context
parse_context = Liquid::ParseContext.new
shared_file_system = StubFileSystem.new(
'my_partial' => 'my shared value'
)
context_one = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
)
context_two = Liquid::Context.build(
registers: {
file_system: shared_file_system,
}
)
2.times do
Liquid::PartialCache.load(
'my_partial',
context: context_one,
parse_context: parse_context
)
end
Liquid::PartialCache.load(
'my_partial',
context: context_two,
parse_context: parse_context
)
assert_equal 2, shared_file_system.file_read_count
end
def test_cache_is_not_broken_when_a_different_parse_context_is_used
file_system = StubFileSystem.new('my_partial' => 'some partial body')
context = Liquid::Context.build(
registers: { file_system: file_system }
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value one')
)
Liquid::PartialCache.load(
'my_partial',
context: context,
parse_context: Liquid::ParseContext.new(my_key: 'value two')
)
# Technically what we care about is that the file was parsed twice,
# but measuring file reads is an OK proxy for this.
assert_equal 1, file_system.file_read_count
end
end