liquid 4.0.3 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -0
  3. data/README.md +10 -4
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +169 -57
  6. data/lib/liquid/condition.rb +48 -21
  7. data/lib/liquid/context.rb +111 -52
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +28 -32
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +54 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +8 -5
  19. data/lib/liquid/parse_context.rb +20 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +13 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +551 -114
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +64 -5
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +28 -6
  36. data/lib/liquid/tags/assign.rb +36 -18
  37. data/lib/liquid/tags/break.rb +16 -3
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +61 -27
  40. data/lib/liquid/tags/comment.rb +18 -3
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +37 -25
  43. data/lib/liquid/tags/decrement.rb +22 -20
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +90 -87
  46. data/lib/liquid/tags/if.rb +50 -32
  47. data/lib/liquid/tags/ifchanged.rb +11 -10
  48. data/lib/liquid/tags/include.rb +49 -60
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +25 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +45 -19
  54. data/lib/liquid/tags/unless.rb +38 -19
  55. data/lib/liquid/template.rb +52 -72
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +18 -10
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +13 -3
  60. data/lib/liquid/variable.rb +49 -44
  61. data/lib/liquid/variable_lookup.rb +18 -10
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +18 -6
  64. metadata +20 -108
  65. data/lib/liquid/strainer.rb +0 -66
  66. data/lib/liquid/truffle.rb +0 -5
  67. data/test/fixtures/en_locale.yml +0 -9
  68. data/test/integration/assign_test.rb +0 -48
  69. data/test/integration/blank_test.rb +0 -106
  70. data/test/integration/block_test.rb +0 -12
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/document_test.rb +0 -19
  74. data/test/integration/drop_test.rb +0 -273
  75. data/test/integration/error_handling_test.rb +0 -260
  76. data/test/integration/filter_test.rb +0 -178
  77. data/test/integration/hash_ordering_test.rb +0 -23
  78. data/test/integration/output_test.rb +0 -123
  79. data/test/integration/parse_tree_visitor_test.rb +0 -247
  80. data/test/integration/parsing_quirks_test.rb +0 -122
  81. data/test/integration/render_profiling_test.rb +0 -154
  82. data/test/integration/security_test.rb +0 -80
  83. data/test/integration/standard_filter_test.rb +0 -776
  84. data/test/integration/tags/break_tag_test.rb +0 -15
  85. data/test/integration/tags/continue_tag_test.rb +0 -15
  86. data/test/integration/tags/for_tag_test.rb +0 -410
  87. data/test/integration/tags/if_else_tag_test.rb +0 -188
  88. data/test/integration/tags/include_tag_test.rb +0 -253
  89. data/test/integration/tags/increment_tag_test.rb +0 -23
  90. data/test/integration/tags/raw_tag_test.rb +0 -31
  91. data/test/integration/tags/standard_tag_test.rb +0 -296
  92. data/test/integration/tags/statements_test.rb +0 -111
  93. data/test/integration/tags/table_row_test.rb +0 -64
  94. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  95. data/test/integration/template_test.rb +0 -332
  96. data/test/integration/trim_mode_test.rb +0 -529
  97. data/test/integration/variable_test.rb +0 -96
  98. data/test/test_helper.rb +0 -116
  99. data/test/truffle/truffle_test.rb +0 -9
  100. data/test/unit/block_unit_test.rb +0 -58
  101. data/test/unit/condition_unit_test.rb +0 -166
  102. data/test/unit/context_unit_test.rb +0 -489
  103. data/test/unit/file_system_unit_test.rb +0 -35
  104. data/test/unit/i18n_unit_test.rb +0 -37
  105. data/test/unit/lexer_unit_test.rb +0 -51
  106. data/test/unit/parser_unit_test.rb +0 -82
  107. data/test/unit/regexp_unit_test.rb +0 -44
  108. data/test/unit/strainer_unit_test.rb +0 -164
  109. data/test/unit/tag_unit_test.rb +0 -21
  110. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  111. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  112. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  113. data/test/unit/template_unit_test.rb +0 -78
  114. data/test/unit/tokenizer_unit_test.rb +0 -55
  115. data/test/unit/variable_unit_test.rb +0 -162
@@ -1,273 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ContextDrop < Liquid::Drop
4
- def scopes
5
- @context.scopes.size
6
- end
7
-
8
- def scopes_as_array
9
- (1..@context.scopes.size).to_a
10
- end
11
-
12
- def loop_pos
13
- @context['forloop.index']
14
- end
15
-
16
- def liquid_method_missing(method)
17
- @context[method]
18
- end
19
- end
20
-
21
- class ProductDrop < Liquid::Drop
22
- class TextDrop < Liquid::Drop
23
- def array
24
- ['text1', 'text2']
25
- end
26
-
27
- def text
28
- 'text1'
29
- end
30
- end
31
-
32
- class CatchallDrop < Liquid::Drop
33
- def liquid_method_missing(method)
34
- 'catchall_method: ' << method.to_s
35
- end
36
- end
37
-
38
- def texts
39
- TextDrop.new
40
- end
41
-
42
- def catchall
43
- CatchallDrop.new
44
- end
45
-
46
- def context
47
- ContextDrop.new
48
- end
49
-
50
- def user_input
51
- "foo".taint
52
- end
53
-
54
- protected
55
-
56
- def callmenot
57
- "protected"
58
- end
59
- end
60
-
61
- class EnumerableDrop < Liquid::Drop
62
- def liquid_method_missing(method)
63
- method
64
- end
65
-
66
- def size
67
- 3
68
- end
69
-
70
- def first
71
- 1
72
- end
73
-
74
- def count
75
- 3
76
- end
77
-
78
- def min
79
- 1
80
- end
81
-
82
- def max
83
- 3
84
- end
85
-
86
- def each
87
- yield 1
88
- yield 2
89
- yield 3
90
- end
91
- end
92
-
93
- class RealEnumerableDrop < Liquid::Drop
94
- include Enumerable
95
-
96
- def liquid_method_missing(method)
97
- method
98
- end
99
-
100
- def each
101
- yield 1
102
- yield 2
103
- yield 3
104
- end
105
- end
106
-
107
- class DropsTest < Minitest::Test
108
- include Liquid
109
-
110
- def test_product_drop
111
- tpl = Liquid::Template.parse(' ')
112
- assert_equal ' ', tpl.render!('product' => ProductDrop.new)
113
- end
114
-
115
- def test_rendering_raises_on_tainted_attr
116
- with_taint_mode(:error) do
117
- tpl = Liquid::Template.parse('{{ product.user_input }}')
118
- assert_raises TaintedError do
119
- tpl.render!('product' => ProductDrop.new)
120
- end
121
- end
122
- end
123
-
124
- def test_rendering_warns_on_tainted_attr
125
- with_taint_mode(:warn) do
126
- tpl = Liquid::Template.parse('{{ product.user_input }}')
127
- context = Context.new('product' => ProductDrop.new)
128
- tpl.render!(context)
129
- assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
130
- assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
131
- end
132
- end
133
-
134
- def test_rendering_doesnt_raise_on_escaped_tainted_attr
135
- with_taint_mode(:error) do
136
- tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
137
- tpl.render!('product' => ProductDrop.new)
138
- end
139
- end
140
-
141
- def test_drop_does_only_respond_to_whitelisted_methods
142
- assert_equal "", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new)
143
- assert_equal "", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new)
144
- assert_equal "", Liquid::Template.parse("{{ product.whatever }}").render!('product' => ProductDrop.new)
145
- assert_equal "", Liquid::Template.parse('{{ product | map: "inspect" }}').render!('product' => ProductDrop.new)
146
- assert_equal "", Liquid::Template.parse('{{ product | map: "pretty_inspect" }}').render!('product' => ProductDrop.new)
147
- assert_equal "", Liquid::Template.parse('{{ product | map: "whatever" }}').render!('product' => ProductDrop.new)
148
- end
149
-
150
- def test_drops_respond_to_to_liquid
151
- assert_equal "text1", Liquid::Template.parse("{{ product.to_liquid.texts.text }}").render!('product' => ProductDrop.new)
152
- assert_equal "text1", Liquid::Template.parse('{{ product | map: "to_liquid" | map: "texts" | map: "text" }}').render!('product' => ProductDrop.new)
153
- end
154
-
155
- def test_text_drop
156
- output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
157
- assert_equal ' text1 ', output
158
- end
159
-
160
- def test_catchall_unknown_method
161
- output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
162
- assert_equal ' catchall_method: unknown ', output
163
- end
164
-
165
- def test_catchall_integer_argument_drop
166
- output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
167
- assert_equal ' catchall_method: 8 ', output
168
- end
169
-
170
- def test_text_array_drop
171
- output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
172
- assert_equal ' text1 text2 ', output
173
- end
174
-
175
- def test_context_drop
176
- output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
177
- assert_equal ' carrot ', output
178
- end
179
-
180
- def test_nested_context_drop
181
- output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
182
- assert_equal ' monkey ', output
183
- end
184
-
185
- def test_protected
186
- output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
187
- assert_equal ' ', output
188
- end
189
-
190
- def test_object_methods_not_allowed
191
- [:dup, :clone, :singleton_class, :eval, :class_eval, :inspect].each do |method|
192
- output = Liquid::Template.parse(" {{ product.#{method} }} ").render!('product' => ProductDrop.new)
193
- assert_equal ' ', output
194
- end
195
- end
196
-
197
- def test_scope
198
- assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
199
- assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
200
- assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
201
- end
202
-
203
- def test_scope_though_proc
204
- assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
205
- assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
206
- 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])
207
- end
208
-
209
- def test_scope_with_assigns
210
- assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
211
- assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
212
- assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
213
- assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
214
- end
215
-
216
- def test_scope_from_tags
217
- assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
218
- 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])
219
- 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])
220
- end
221
-
222
- def test_access_context_from_drop
223
- assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
224
- end
225
-
226
- def test_enumerable_drop
227
- assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
228
- end
229
-
230
- def test_enumerable_drop_size
231
- assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
232
- end
233
-
234
- def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
235
- ["select", "each", "map", "cycle"].each do |method|
236
- assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
237
- assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
238
- assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
239
- assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
240
- end
241
- end
242
-
243
- def test_some_enumerable_methods_still_get_invoked
244
- [ :count, :max ].each do |method|
245
- assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
246
- assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
247
- assert_equal "3", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
248
- assert_equal "3", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
249
- end
250
-
251
- assert_equal "yes", Liquid::Template.parse("{% if collection contains 3 %}yes{% endif %}").render!('collection' => RealEnumerableDrop.new)
252
-
253
- [ :min, :first ].each do |method|
254
- assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => RealEnumerableDrop.new)
255
- assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => RealEnumerableDrop.new)
256
- assert_equal "1", Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
257
- assert_equal "1", Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
258
- end
259
- end
260
-
261
- def test_empty_string_value_access
262
- assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => '')
263
- end
264
-
265
- def test_nil_value_access
266
- assert_equal '', Liquid::Template.parse('{{ product[value] }}').render!('product' => ProductDrop.new, 'value' => nil)
267
- end
268
-
269
- def test_default_to_s_on_drops
270
- assert_equal 'ProductDrop', Liquid::Template.parse("{{ product }}").render!('product' => ProductDrop.new)
271
- assert_equal 'EnumerableDrop', Liquid::Template.parse('{{ collection }}').render!('collection' => EnumerableDrop.new)
272
- end
273
- end # DropsTest
@@ -1,260 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ErrorHandlingTest < Minitest::Test
4
- include Liquid
5
-
6
- def test_templates_parsed_with_line_numbers_renders_them_in_errors
7
- template = <<-LIQUID
8
- Hello,
9
-
10
- {{ errors.standard_error }} will raise a standard error.
11
-
12
- Bla bla test.
13
-
14
- {{ errors.syntax_error }} will raise a syntax error.
15
-
16
- This is an argument error: {{ errors.argument_error }}
17
-
18
- Bla.
19
- LIQUID
20
-
21
- expected = <<-TEXT
22
- Hello,
23
-
24
- Liquid error (line 3): standard error will raise a standard error.
25
-
26
- Bla bla test.
27
-
28
- Liquid syntax error (line 7): syntax error will raise a syntax error.
29
-
30
- This is an argument error: Liquid error (line 9): argument error
31
-
32
- Bla.
33
- TEXT
34
-
35
- output = Liquid::Template.parse(template, line_numbers: true).render('errors' => ErrorDrop.new)
36
- assert_equal expected, output
37
- end
38
-
39
- def test_standard_error
40
- template = Liquid::Template.parse(' {{ errors.standard_error }} ')
41
- assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
42
-
43
- assert_equal 1, template.errors.size
44
- assert_equal StandardError, template.errors.first.class
45
- end
46
-
47
- def test_syntax
48
- template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
49
- assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
50
-
51
- assert_equal 1, template.errors.size
52
- assert_equal SyntaxError, template.errors.first.class
53
- end
54
-
55
- def test_argument
56
- template = Liquid::Template.parse(' {{ errors.argument_error }} ')
57
- assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
58
-
59
- assert_equal 1, template.errors.size
60
- assert_equal ArgumentError, template.errors.first.class
61
- end
62
-
63
- def test_missing_endtag_parse_time_error
64
- assert_raises(Liquid::SyntaxError) do
65
- Liquid::Template.parse(' {% for a in b %} ... ')
66
- end
67
- end
68
-
69
- def test_unrecognized_operator
70
- with_error_mode(:strict) do
71
- assert_raises(SyntaxError) do
72
- Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
73
- end
74
- end
75
- end
76
-
77
- def test_lax_unrecognized_operator
78
- template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
79
- assert_equal ' Liquid error: Unknown operator =! ', template.render
80
- assert_equal 1, template.errors.size
81
- assert_equal Liquid::ArgumentError, template.errors.first.class
82
- end
83
-
84
- def test_with_line_numbers_adds_numbers_to_parser_errors
85
- err = assert_raises(SyntaxError) do
86
- Liquid::Template.parse(%q(
87
- foobar
88
-
89
- {% "cat" | foobar %}
90
-
91
- bla
92
- ),
93
- line_numbers: true
94
- )
95
- end
96
-
97
- assert_match(/Liquid syntax error \(line 4\)/, err.message)
98
- end
99
-
100
- def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
101
- err = assert_raises(SyntaxError) do
102
- Liquid::Template.parse(%q(
103
- foobar
104
-
105
- {%- "cat" | foobar -%}
106
-
107
- bla
108
- ),
109
- line_numbers: true
110
- )
111
- end
112
-
113
- assert_match(/Liquid syntax error \(line 4\)/, err.message)
114
- end
115
-
116
- def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
117
- template = Liquid::Template.parse('
118
- foobar
119
-
120
- {% if 1 =! 2 %}ok{% endif %}
121
-
122
- bla
123
- ',
124
- error_mode: :warn,
125
- line_numbers: true
126
- )
127
-
128
- assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
129
- template.warnings.map(&:message)
130
- end
131
-
132
- def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
133
- err = assert_raises(SyntaxError) do
134
- Liquid::Template.parse('
135
- foobar
136
-
137
- {% if 1 =! 2 %}ok{% endif %}
138
-
139
- bla
140
- ',
141
- error_mode: :strict,
142
- line_numbers: true
143
- )
144
- end
145
-
146
- assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
147
- end
148
-
149
- def test_syntax_errors_in_nested_blocks_have_correct_line_number
150
- err = assert_raises(SyntaxError) do
151
- Liquid::Template.parse('
152
- foobar
153
-
154
- {% if 1 != 2 %}
155
- {% foo %}
156
- {% endif %}
157
-
158
- bla
159
- ',
160
- line_numbers: true
161
- )
162
- end
163
-
164
- assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
165
- end
166
-
167
- def test_strict_error_messages
168
- err = assert_raises(SyntaxError) do
169
- Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
170
- end
171
- assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
172
-
173
- err = assert_raises(SyntaxError) do
174
- Liquid::Template.parse('{{%%%}}', error_mode: :strict)
175
- end
176
- assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
177
- end
178
-
179
- def test_warnings
180
- template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
181
- assert_equal 3, template.warnings.size
182
- assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
183
- assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
184
- assert_equal 'Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].to_s(false)
185
- assert_equal '', template.render
186
- end
187
-
188
- def test_warning_line_numbers
189
- template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
190
- assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
191
- assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
192
- assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
193
- assert_equal 3, template.warnings.size
194
- assert_equal [1, 2, 3], template.warnings.map(&:line_number)
195
- end
196
-
197
- # Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
198
- def test_exceptions_propagate
199
- assert_raises Exception do
200
- template = Liquid::Template.parse('{{ errors.exception }}')
201
- template.render('errors' => ErrorDrop.new)
202
- end
203
- end
204
-
205
- def test_default_exception_renderer_with_internal_error
206
- template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
207
-
208
- output = template.render({ 'errors' => ErrorDrop.new })
209
-
210
- assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
211
- assert_equal [Liquid::InternalError], template.errors.map(&:class)
212
- end
213
-
214
- def test_setting_default_exception_renderer
215
- old_exception_renderer = Liquid::Template.default_exception_renderer
216
- exceptions = []
217
- Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
218
- template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
219
-
220
- output = template.render({ 'errors' => ErrorDrop.new })
221
-
222
- assert_equal 'This is a runtime error: ', output
223
- assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
224
- ensure
225
- Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
226
- end
227
-
228
- def test_exception_renderer_exposing_non_liquid_error
229
- template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
230
- exceptions = []
231
- handler = ->(e) { exceptions << e; e.cause }
232
-
233
- output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
234
-
235
- assert_equal 'This is a runtime error: runtime error', output
236
- assert_equal [Liquid::InternalError], exceptions.map(&:class)
237
- assert_equal exceptions, template.errors
238
- assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
239
- end
240
-
241
- class TestFileSystem
242
- def read_template_file(template_path)
243
- "{{ errors.argument_error }}"
244
- end
245
- end
246
-
247
- def test_included_template_name_with_line_numbers
248
- old_file_system = Liquid::Template.file_system
249
-
250
- begin
251
- Liquid::Template.file_system = TestFileSystem.new
252
- template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
253
- page = template.render('errors' => ErrorDrop.new)
254
- ensure
255
- Liquid::Template.file_system = old_file_system
256
- end
257
- assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
258
- assert_equal "product", template.errors.first.template_name
259
- end
260
- end