liquid 4.0.3 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +33 -0
  3. data/README.md +6 -0
  4. data/lib/liquid.rb +17 -5
  5. data/lib/liquid/block.rb +31 -14
  6. data/lib/liquid/block_body.rb +164 -54
  7. data/lib/liquid/condition.rb +39 -18
  8. data/lib/liquid/context.rb +106 -51
  9. data/lib/liquid/document.rb +47 -9
  10. data/lib/liquid/drop.rb +4 -2
  11. data/lib/liquid/errors.rb +20 -18
  12. data/lib/liquid/expression.rb +29 -34
  13. data/lib/liquid/extensions.rb +2 -0
  14. data/lib/liquid/file_system.rb +6 -4
  15. data/lib/liquid/forloop_drop.rb +11 -4
  16. data/lib/liquid/i18n.rb +5 -3
  17. data/lib/liquid/interrupts.rb +3 -1
  18. data/lib/liquid/lexer.rb +30 -23
  19. data/lib/liquid/locales/en.yml +3 -1
  20. data/lib/liquid/parse_context.rb +16 -4
  21. data/lib/liquid/parse_tree_visitor.rb +2 -2
  22. data/lib/liquid/parser.rb +30 -18
  23. data/lib/liquid/parser_switching.rb +17 -3
  24. data/lib/liquid/partial_cache.rb +24 -0
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/profiler/hooks.rb +26 -14
  27. data/lib/liquid/range_lookup.rb +5 -3
  28. data/lib/liquid/register.rb +6 -0
  29. data/lib/liquid/resource_limits.rb +47 -8
  30. data/lib/liquid/standardfilters.rb +63 -44
  31. data/lib/liquid/static_registers.rb +44 -0
  32. data/lib/liquid/strainer_factory.rb +36 -0
  33. data/lib/liquid/strainer_template.rb +53 -0
  34. data/lib/liquid/tablerowloop_drop.rb +6 -4
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +21 -0
  38. data/lib/liquid/tags/assign.rb +24 -10
  39. data/lib/liquid/tags/break.rb +8 -3
  40. data/lib/liquid/tags/capture.rb +11 -8
  41. data/lib/liquid/tags/case.rb +33 -27
  42. data/lib/liquid/tags/comment.rb +5 -3
  43. data/lib/liquid/tags/continue.rb +8 -3
  44. data/lib/liquid/tags/cycle.rb +25 -14
  45. data/lib/liquid/tags/decrement.rb +6 -3
  46. data/lib/liquid/tags/echo.rb +26 -0
  47. data/lib/liquid/tags/for.rb +68 -44
  48. data/lib/liquid/tags/if.rb +35 -23
  49. data/lib/liquid/tags/ifchanged.rb +11 -10
  50. data/lib/liquid/tags/include.rb +34 -47
  51. data/lib/liquid/tags/increment.rb +7 -3
  52. data/lib/liquid/tags/raw.rb +14 -11
  53. data/lib/liquid/tags/render.rb +84 -0
  54. data/lib/liquid/tags/table_row.rb +23 -19
  55. data/lib/liquid/tags/unless.rb +15 -15
  56. data/lib/liquid/template.rb +55 -71
  57. data/lib/liquid/template_factory.rb +9 -0
  58. data/lib/liquid/tokenizer.rb +17 -9
  59. data/lib/liquid/usage.rb +8 -0
  60. data/lib/liquid/utils.rb +5 -3
  61. data/lib/liquid/variable.rb +46 -41
  62. data/lib/liquid/variable_lookup.rb +8 -6
  63. data/lib/liquid/version.rb +2 -1
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -83
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +339 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +118 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +43 -32
  99. data/test/test_helper.rb +75 -22
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +2 -2
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +73 -47
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/lib/liquid/truffle.rb +0 -5
  123. data/test/truffle/truffle_test.rb +0 -9
  124. data/test/unit/context_unit_test.rb +0 -489
  125. data/test/unit/strainer_unit_test.rb +0 -164
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e41c3a6ffee8037dec34a186cf023e2ccb09ac4f94d0c9a812ea02fd50559bf7
4
- data.tar.gz: ff088908363f5a54e5833c64d32b441fb679503340b5f7511de99eb257fd4289
3
+ metadata.gz: 0b1c036e6dd6d55418e6364b008551f5d3ba91f6e7dbc1c0ac3be43b235bd957
4
+ data.tar.gz: a6279802ed388bcffc8980c49bcb0034a0f98de7cd6730bf97f603c07bf13dcd
5
5
  SHA512:
6
- metadata.gz: 6b726a02e92f8d9fac005e07034aeee32dd2eae3a2b342fb4e9e648dc79da4c086de92e1e1a24bf52054a4a2b0a74af17f89fdd6c3cbbb2982e4b5b5971a23c2
7
- data.tar.gz: e875f98a0b30f914ec1703781bfb443e737ea11675829c3e98fca96524dfe30a72fb9adf59c7855f85eda8014c2e6a9850d911b22cb96a63d2eac52e91273a42
6
+ metadata.gz: 0c5094a47d46c8de3ac8ac632dbec8b813c1e47af834c0037ed9b868f5a2dde4da8dfdd143ff34c41505c1425524f1879ca909525676028cadd9900cbd028e63
7
+ data.tar.gz: 7195f154c81283e8b7d99a07b82a2d9976b41be761efc528945f79ac0d77c2ff9c7632b3455f871b6db0471979c35e633f87e1c33900e4f9898012f836fd1496
data/History.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.0.0 / 2021-01-06
4
+
5
+ ### Features
6
+ * Add new `{% render %}` tag (#1122) [Samuel Doiron]
7
+ * Add support for `as` in `{% render %}` and `{% include %}` (#1181) [Mike Angell]
8
+ * Add `{% liquid %}` and `{% echo %}` tags (#1086) [Justin Li]
9
+ * Add [usage tracking](README.md#usage-tracking) [Mike Angell]
10
+ * Add `Tag.disable_tags` for disabling tags that prepend `Tag::Disableable` at render time (#1162, #1274, #1275) [Mike Angell]
11
+ * Support using a profiler for multiple renders (#1365, #1366) [Dylan Thacker-Smith]
12
+
13
+ ### Fixes
14
+ * Fix catastrophic backtracking in `RANGES_REGEX` regular expression (#1357) [Dylan Thacker-Smith]
15
+ * Make sure the for tag's limit and offset are integers (#1094) [David Cornu]
16
+ * Invokable methods for enumerable reject include (#1151) [Thierry Joyal]
17
+ * Allow `default` filter to handle `false` as value (#1144) [Mike Angell]
18
+ * Fix render length resource limit so it doesn't multiply nested output (#1285) [Dylan Thacker-Smith]
19
+ * Fix duplication of text in raw tags (#1304) [Peter Zhu]
20
+ * Fix strict parsing of find variable with a name expression (#1317) [Dylan Thacker-Smith]
21
+ * Use monotonic time to measure durations in Liquid::Profiler (#1362) [Dylan Thacker-Smith]
22
+
23
+ ### Breaking Changes
24
+ * Require Ruby >= 2.5 (#1131, #1310) [Mike Angell, Dylan Thacker-Smith]
25
+ * Remove support for taint checking (#1268) [Dylan Thacker-Smith]
26
+ * Split Strainer class into StrainerFactory and StrainerTemplate (#1208) [Thierry Joyal]
27
+ * Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
28
+ * Handle `BlockBody#blank?` at parse time (#1287) [Dylan Thacker-Smith]
29
+ * Pass the tag markup and tokenizer to `Document#unknown_tag` (#1290) [Dylan Thacker-Smith]
30
+ * And several internal changes
31
+
32
+ ### Performance Improvements
33
+ * Reduce allocations (#1073, #1091, #1115, #1099, #1117, #1141, #1322, #1341) [Richard Monette, Florian Weingarten, Ashwin Maroli]
34
+ * Improve resources limits performance (#1093, #1323) [Florian Weingarten, Dylan Thacker-Smith]
35
+
3
36
  ## 4.0.3 / 2019-03-12
4
37
 
5
38
  ### Fixed
data/README.md CHANGED
@@ -106,3 +106,9 @@ template = Liquid::Template.parse("{{x}} {{y}}")
106
106
  template.render!({ 'x' => 1}, { strict_variables: true })
107
107
  #=> Liquid::UndefinedVariable: Liquid error: undefined variable y
108
108
  ```
109
+
110
+ ### Usage tracking
111
+
112
+ 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.
113
+
114
+ 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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright (c) 2005 Tobias Luetke
2
4
  #
3
5
  # Permission is hereby granted, free of charge, to any person obtaining
@@ -21,10 +23,10 @@
21
23
 
22
24
  module Liquid
23
25
  FilterSeparator = /\|/
24
- ArgumentSeparator = ','.freeze
25
- FilterArgumentSeparator = ':'.freeze
26
- VariableAttributeSeparator = '.'.freeze
27
- WhitespaceControl = '-'.freeze
26
+ ArgumentSeparator = ','
27
+ FilterArgumentSeparator = ':'
28
+ VariableAttributeSeparator = '.'
29
+ WhitespaceControl = '-'
28
30
  TagStart = /\{\%/
29
31
  TagEnd = /\%\}/
30
32
  VariableSignature = /\(?[\w\-\.\[\]]\)?/
@@ -40,6 +42,8 @@ module Liquid
40
42
  TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/om
41
43
  VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/o
42
44
 
45
+ RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
46
+
43
47
  singleton_class.send(:attr_accessor, :cache_classes)
44
48
  self.cache_classes = true
45
49
  end
@@ -55,11 +59,14 @@ require 'liquid/forloop_drop'
55
59
  require 'liquid/extensions'
56
60
  require 'liquid/errors'
57
61
  require 'liquid/interrupts'
58
- require 'liquid/strainer'
62
+ require 'liquid/strainer_factory'
63
+ require 'liquid/strainer_template'
59
64
  require 'liquid/expression'
60
65
  require 'liquid/context'
61
66
  require 'liquid/parser_switching'
62
67
  require 'liquid/tag'
68
+ require 'liquid/tag/disabler'
69
+ require 'liquid/tag/disableable'
63
70
  require 'liquid/block'
64
71
  require 'liquid/block_body'
65
72
  require 'liquid/document'
@@ -74,6 +81,11 @@ require 'liquid/condition'
74
81
  require 'liquid/utils'
75
82
  require 'liquid/tokenizer'
76
83
  require 'liquid/parse_context'
84
+ require 'liquid/partial_cache'
85
+ require 'liquid/usage'
86
+ require 'liquid/register'
87
+ require 'liquid/static_registers'
88
+ require 'liquid/template_factory'
77
89
 
78
90
  # Load all the tags of the standard library
79
91
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Block < Tag
3
5
  MAX_DEPTH = 100
@@ -8,11 +10,13 @@ module Liquid
8
10
  end
9
11
 
10
12
  def parse(tokens)
11
- @body = BlockBody.new
13
+ @body = new_body
12
14
  while parse_body(@body, tokens)
13
15
  end
16
+ @body.freeze
14
17
  end
15
18
 
19
+ # For backwards compatibility
16
20
  def render(context)
17
21
  @body.render(context)
18
22
  end
@@ -25,20 +29,29 @@ module Liquid
25
29
  @body.nodelist
26
30
  end
27
31
 
28
- def unknown_tag(tag, _params, _tokens)
29
- if tag == 'else'.freeze
30
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_else".freeze,
31
- block_name: block_name))
32
- elsif tag.start_with?('end'.freeze)
33
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.invalid_delimiter".freeze,
32
+ def unknown_tag(tag_name, _markup, _tokenizer)
33
+ Block.raise_unknown_tag(tag_name, block_name, block_delimiter, parse_context)
34
+ end
35
+
36
+ # @api private
37
+ def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
38
+ if tag == 'else'
39
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
40
+ block_name: block_name)
41
+ elsif tag.start_with?('end')
42
+ raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
34
43
  tag: tag,
35
44
  block_name: block_name,
36
- block_delimiter: block_delimiter))
45
+ block_delimiter: block_delimiter)
37
46
  else
38
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
47
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
39
48
  end
40
49
  end
41
50
 
51
+ def raise_tag_never_closed(block_name)
52
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_never_closed", block_name: block_name)
53
+ end
54
+
42
55
  def block_name
43
56
  @tag_name
44
57
  end
@@ -47,11 +60,17 @@ module Liquid
47
60
  @block_delimiter ||= "end#{block_name}"
48
61
  end
49
62
 
50
- protected
63
+ private
64
+
65
+ # @api public
66
+ def new_body
67
+ parse_context.new_block_body
68
+ end
51
69
 
70
+ # @api public
52
71
  def parse_body(body, tokens)
53
72
  if parse_context.depth >= MAX_DEPTH
54
- raise StackLevelError, "Nesting too deep".freeze
73
+ raise StackLevelError, "Nesting too deep"
55
74
  end
56
75
  parse_context.depth += 1
57
76
  begin
@@ -59,9 +78,7 @@ module Liquid
59
78
  @blank &&= body.blank?
60
79
 
61
80
  return false if end_tag_name == block_delimiter
62
- unless end_tag_name
63
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
64
- end
81
+ raise_tag_never_closed(block_name) unless end_tag_name
65
82
 
66
83
  # this tag is not registered with the system
67
84
  # pass it to the current block for special handling or error reporting
@@ -1,32 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
1
5
  module Liquid
2
6
  class BlockBody
3
- FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
4
- ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
7
+ LiquidTagToken = /\A\s*(\w+)\s*(.*?)\z/o
8
+ FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(\w+)(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
9
+ ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
5
10
  WhitespaceOrNothing = /\A\s*\z/
6
- TAGSTART = "{%".freeze
7
- VARSTART = "{{".freeze
11
+ TAGSTART = "{%"
12
+ VARSTART = "{{"
8
13
 
9
14
  attr_reader :nodelist
10
15
 
11
16
  def initialize
12
17
  @nodelist = []
13
- @blank = true
18
+ @blank = true
14
19
  end
15
20
 
16
- def parse(tokenizer, parse_context)
21
+ def parse(tokenizer, parse_context, &block)
22
+ raise FrozenError, "can't modify frozen Liquid::BlockBody" if frozen?
23
+
17
24
  parse_context.line_number = tokenizer.line_number
18
- while token = tokenizer.shift
25
+
26
+ if tokenizer.for_liquid_tag
27
+ parse_for_liquid_tag(tokenizer, parse_context, &block)
28
+ else
29
+ parse_for_document(tokenizer, parse_context, &block)
30
+ end
31
+ end
32
+
33
+ def freeze
34
+ @nodelist.freeze
35
+ super
36
+ end
37
+
38
+ private def parse_for_liquid_tag(tokenizer, parse_context)
39
+ while (token = tokenizer.shift)
40
+ unless token.empty? || token =~ WhitespaceOrNothing
41
+ unless token =~ LiquidTagToken
42
+ # line isn't empty but didn't match tag syntax, yield and let the
43
+ # caller raise a syntax error
44
+ return yield token, token
45
+ end
46
+ tag_name = Regexp.last_match(1)
47
+ markup = Regexp.last_match(2)
48
+ unless (tag = registered_tags[tag_name])
49
+ # end parsing if we reach an unknown tag and let the caller decide
50
+ # determine how to proceed
51
+ return yield tag_name, markup
52
+ end
53
+ new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
54
+ @blank &&= new_tag.blank?
55
+ @nodelist << new_tag
56
+ end
57
+ parse_context.line_number = tokenizer.line_number
58
+ end
59
+
60
+ yield nil, nil
61
+ end
62
+
63
+ # @api private
64
+ def self.unknown_tag_in_liquid_tag(tag, parse_context)
65
+ Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
66
+ end
67
+
68
+ # @api private
69
+ def self.raise_missing_tag_terminator(token, parse_context)
70
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
71
+ end
72
+
73
+ # @api private
74
+ def self.raise_missing_variable_terminator(token, parse_context)
75
+ raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
76
+ end
77
+
78
+ # @api private
79
+ def self.render_node(context, output, node)
80
+ node.render_to_output_buffer(context, output)
81
+ rescue => exc
82
+ blank_tag = !node.instance_of?(Variable) && node.blank?
83
+ rescue_render_node(context, output, node.line_number, exc, blank_tag)
84
+ end
85
+
86
+ # @api private
87
+ def self.rescue_render_node(context, output, line_number, exc, blank_tag)
88
+ case exc
89
+ when MemoryError
90
+ raise
91
+ when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
92
+ context.handle_error(exc, line_number)
93
+ else
94
+ error_message = context.handle_error(exc, line_number)
95
+ unless blank_tag # conditional for backwards compatibility
96
+ output << error_message
97
+ end
98
+ end
99
+ end
100
+
101
+ private def parse_liquid_tag(markup, parse_context)
102
+ liquid_tag_tokenizer = Tokenizer.new(markup, line_number: parse_context.line_number, for_liquid_tag: true)
103
+ parse_for_liquid_tag(liquid_tag_tokenizer, parse_context) do |end_tag_name, _end_tag_markup|
104
+ if end_tag_name
105
+ BlockBody.unknown_tag_in_liquid_tag(end_tag_name, parse_context)
106
+ end
107
+ end
108
+ end
109
+
110
+ private def parse_for_document(tokenizer, parse_context)
111
+ while (token = tokenizer.shift)
19
112
  next if token.empty?
20
113
  case
21
114
  when token.start_with?(TAGSTART)
22
115
  whitespace_handler(token, parse_context)
23
116
  unless token =~ FullToken
24
- raise_missing_tag_terminator(token, parse_context)
117
+ BlockBody.raise_missing_tag_terminator(token, parse_context)
118
+ end
119
+ tag_name = Regexp.last_match(2)
120
+ markup = Regexp.last_match(4)
121
+
122
+ if parse_context.line_number
123
+ # newlines inside the tag should increase the line number,
124
+ # particularly important for multiline {% liquid %} tags
125
+ parse_context.line_number += Regexp.last_match(1).count("\n") + Regexp.last_match(3).count("\n")
126
+ end
127
+
128
+ if tag_name == 'liquid'
129
+ parse_liquid_tag(markup, parse_context)
130
+ next
25
131
  end
26
- tag_name = $1
27
- markup = $2
28
- # fetch the tag from registered blocks
29
- unless tag = registered_tags[tag_name]
132
+
133
+ unless (tag = registered_tags[tag_name])
30
134
  # end parsing if we reach an unknown tag and let the caller decide
31
135
  # determine how to proceed
32
136
  return yield tag_name, markup
@@ -55,8 +159,12 @@ module Liquid
55
159
  def whitespace_handler(token, parse_context)
56
160
  if token[2] == WhitespaceControl
57
161
  previous_token = @nodelist.last
58
- if previous_token.is_a? String
162
+ if previous_token.is_a?(String)
163
+ first_byte = previous_token.getbyte(0)
59
164
  previous_token.rstrip!
165
+ if previous_token.empty? && parse_context[:bug_compatible_whitespace_trimming] && first_byte
166
+ previous_token << first_byte
167
+ end
60
168
  end
61
169
  end
62
170
  parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
@@ -66,58 +174,58 @@ module Liquid
66
174
  @blank
67
175
  end
68
176
 
177
+ # Remove blank strings in the block body for a control flow tag (e.g. `if`, `for`, `case`, `unless`)
178
+ # with a blank body.
179
+ #
180
+ # For example, in a conditional assignment like the following
181
+ #
182
+ # ```
183
+ # {% if size > max_size %}
184
+ # {% assign size = max_size %}
185
+ # {% endif %}
186
+ # ```
187
+ #
188
+ # we assume the intention wasn't to output the blank spaces in the `if` tag's block body, so this method
189
+ # will remove them to reduce the render output size.
190
+ #
191
+ # Note that it is now preferred to use the `liquid` tag for this use case.
192
+ def remove_blank_strings
193
+ raise "remove_blank_strings only support being called on a blank block body" unless @blank
194
+ @nodelist.reject! { |node| node.instance_of?(String) }
195
+ end
196
+
69
197
  def render(context)
70
- output = []
71
- context.resource_limits.render_score += @nodelist.length
198
+ render_to_output_buffer(context, +'')
199
+ end
200
+
201
+ def render_to_output_buffer(context, output)
202
+ freeze unless frozen?
203
+
204
+ context.resource_limits.increment_render_score(@nodelist.length)
72
205
 
73
206
  idx = 0
74
- while node = @nodelist[idx]
75
- case node
76
- when String
77
- check_resources(context, node)
207
+ while (node = @nodelist[idx])
208
+ if node.instance_of?(String)
78
209
  output << node
79
- when Variable
80
- render_node_to_output(node, output, context)
81
- when Block
82
- render_node_to_output(node, output, context, node.blank?)
83
- break if context.interrupt? # might have happened in a for-block
84
- when Continue, Break
210
+ else
211
+ render_node(context, output, node)
85
212
  # If we get an Interrupt that means the block must stop processing. An
86
213
  # Interrupt is any command that stops block execution such as {% break %}
87
- # or {% continue %}
88
- context.push_interrupt(node.interrupt)
89
- break
90
- else # Other non-Block tags
91
- render_node_to_output(node, output, context)
92
- break if context.interrupt? # might have happened through an include
214
+ # or {% continue %}. These tags may also occur through Block or Include tags.
215
+ break if context.interrupt? # might have happened in a for-block
93
216
  end
94
217
  idx += 1
218
+
219
+ context.resource_limits.increment_write_score(output)
95
220
  end
96
221
 
97
- output.join
222
+ output
98
223
  end
99
224
 
100
225
  private
101
226
 
102
- def render_node_to_output(node, output, context, skip_output = false)
103
- node_output = node.render(context)
104
- node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
105
- check_resources(context, node_output)
106
- output << node_output unless skip_output
107
- rescue MemoryError => e
108
- raise e
109
- rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
110
- context.handle_error(e, node.line_number)
111
- output << nil
112
- rescue ::StandardError => e
113
- line_number = node.is_a?(String) ? nil : node.line_number
114
- output << context.handle_error(e, line_number)
115
- end
116
-
117
- def check_resources(context, node_output)
118
- context.resource_limits.render_length += node_output.length
119
- return unless context.resource_limits.reached?
120
- raise MemoryError.new("Memory limits exceeded".freeze)
227
+ def render_node(context, output, node)
228
+ BlockBody.render_node(context, output, node)
121
229
  end
122
230
 
123
231
  def create_variable(token, parse_context)
@@ -125,15 +233,17 @@ module Liquid
125
233
  markup = content.first
126
234
  return Variable.new(markup, parse_context)
127
235
  end
128
- raise_missing_variable_terminator(token, parse_context)
236
+ BlockBody.raise_missing_variable_terminator(token, parse_context)
129
237
  end
130
238
 
239
+ # @deprecated Use {.raise_missing_tag_terminator} instead
131
240
  def raise_missing_tag_terminator(token, parse_context)
132
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect))
241
+ BlockBody.raise_missing_tag_terminator(token, parse_context)
133
242
  end
134
243
 
244
+ # @deprecated Use {.raise_missing_variable_terminator} instead
135
245
  def raise_missing_variable_terminator(token, parse_context)
136
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
246
+ BlockBody.raise_missing_variable_terminator(token, parse_context)
137
247
  end
138
248
 
139
249
  def registered_tags