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