UNPKG

18.9 kBJavaScriptView Raw
1(function() {
2 var expect, lib, nodes, parser;
3
4 if(typeof require != 'undefined') {
5 expect = require('expect.js');
6 lib = require('../src/lib');
7 nodes = require('../src/nodes');
8 parser = require('../src/parser');
9 }
10 else {
11 expect = window.expect;
12 lib = nunjucks.require('lib');
13 nodes = nunjucks.require('nodes');
14 parser = nunjucks.require('parser');
15 }
16
17 function _isAST(node1, node2) {
18 // Compare ASTs
19 // TODO: Clean this up (seriously, really)
20
21 expect(node1.typename).to.be(node2.typename);
22
23 var children1 = (node1.children && node1.children.length) || 'null';
24 var children2 = (node2.children && node2.children.length) || 'null';
25
26 if(node2 instanceof nodes.NodeList) {
27 var lit = ': num-children: ';
28 var sig2 = (node2.typename + lit + node2.children.length);
29
30 expect(node1.children).to.be.ok();
31 var sig1 = (node1.typename + lit + node1.children.length);
32
33 expect(sig1).to.be(sig2);
34
35 for(var i=0, l=node2.children.length; i<l; i++) {
36 _isAST(node1.children[i], node2.children[i]);
37 }
38 }
39 else {
40 node2.iterFields(function(value, field) {
41 var ofield = node1[field];
42
43 if(value instanceof nodes.Node) {
44 _isAST(ofield, value);
45 }
46 else if(lib.isArray(ofield) && lib.isArray(value)) {
47 expect('num-children: ' + ofield.length).to.be('num-children: ' + value.length);
48
49 lib.each(ofield, function(v, i) {
50 if(ofield[i] instanceof nodes.Node) {
51 _isAST(ofield[i], value[i]);
52 }
53 else if(ofield[i] !== null && value[i] !== null) {
54 expect(ofield[i]).to.be(value[i]);
55 }
56 });
57 }
58 else if((ofield !== null || value !== null) &&
59 (ofield !== undefined || value !== undefined)) {
60 if(ofield === null) {
61 throw new Error(value + ' expected for "' + field +
62 '", null found');
63 }
64
65 if(value === null) {
66 throw new Error(ofield + ' expected to be null for "' +
67 field + '"');
68 }
69
70 // We want good errors and tracebacks, so test on
71 // whichever object exists
72 if(!ofield) {
73 expect(value).to.be(ofield);
74 }
75 else {
76 expect(ofield).to.be(value);
77 }
78 }
79 });
80 }
81 }
82
83 function isAST(node1, ast) {
84 // Compare the ASTs, the second one is an AST literal so transform
85 // it into a real one
86 return _isAST(node1, toNodes(ast));
87 }
88
89 // We'll be doing a lot of AST comparisons, so this defines a kind
90 // of "AST literal" that you can specify with arrays. This
91 // transforms it into a real AST.
92 function toNodes(ast) {
93 if(!(ast && lib.isArray(ast))) {
94 return ast;
95 }
96
97 var type = ast[0];
98 var F = function() {};
99 F.prototype = type.prototype;
100
101 var dummy = new F();
102
103 if(dummy instanceof nodes.NodeList) {
104 return new type(0, 0, lib.map(ast.slice(1), toNodes));
105 }
106 else if(dummy instanceof nodes.CallExtension) {
107 return new type(ast[1], ast[2], ast[3] ? toNodes(ast[3]) : ast[3],
108 lib.isArray(ast[4]) ? lib.map(ast[4], toNodes) : ast[4]);
109 }
110 else {
111 return new type(0, 0,
112 toNodes(ast[1]),
113 toNodes(ast[2]),
114 toNodes(ast[3]),
115 toNodes(ast[4]),
116 toNodes(ast[5]));
117 }
118 }
119
120 describe('parser', function() {
121 it('should parse basic types', function() {
122 isAST(parser.parse('{{ 1 }}'),
123 [nodes.Root,
124 [nodes.Output,
125 [nodes.Literal, 1]]]);
126
127 isAST(parser.parse('{{ 4.567 }}'),
128 [nodes.Root,
129 [nodes.Output,
130 [nodes.Literal, 4.567]]]);
131
132 isAST(parser.parse('{{ "foo" }}'),
133 [nodes.Root,
134 [nodes.Output,
135 [nodes.Literal, 'foo']]]);
136
137 isAST(parser.parse("{{ 'foo' }}"),
138 [nodes.Root,
139 [nodes.Output,
140 [nodes.Literal, 'foo']]]);
141
142 isAST(parser.parse("{{ true }}"),
143 [nodes.Root,
144 [nodes.Output,
145 [nodes.Literal, true]]]);
146
147 isAST(parser.parse("{{ false }}"),
148 [nodes.Root,
149 [nodes.Output,
150 [nodes.Literal, false]]]);
151
152 isAST(parser.parse("{{ foo }}"),
153 [nodes.Root,
154 [nodes.Output,
155 [nodes.Symbol, 'foo']]]);
156 });
157
158 it('should parse aggregate types', function() {
159 isAST(parser.parse("{{ [1,2,3] }}"),
160 [nodes.Root,
161 [nodes.Output,
162 [nodes.Array,
163 [nodes.Literal, 1],
164 [nodes.Literal, 2],
165 [nodes.Literal, 3]]]]);
166
167 isAST(parser.parse("{{ (1,2,3) }}"),
168 [nodes.Root,
169 [nodes.Output,
170 [nodes.Group,
171 [nodes.Literal, 1],
172 [nodes.Literal, 2],
173 [nodes.Literal, 3]]]]);
174
175 isAST(parser.parse("{{ {foo: 1, 'two': 2} }}"),
176 [nodes.Root,
177 [nodes.Output,
178 [nodes.Dict,
179 [nodes.Pair,
180 [nodes.Symbol, 'foo'],
181 [nodes.Literal, 1]],
182 [nodes.Pair,
183 [nodes.Literal, 'two'],
184 [nodes.Literal, 2]]]]]);
185 });
186
187 it('should parse variables', function() {
188 isAST(parser.parse('hello {{ foo }}, how are you'),
189 [nodes.Root,
190 [nodes.Output, [nodes.TemplateData, 'hello ']],
191 [nodes.Output, [nodes.Symbol, 'foo']],
192 [nodes.Output, [nodes.TemplateData, ', how are you']]]);
193 });
194
195 it('should parse blocks', function() {
196 var n = parser.parse('want some {% if hungry %}pizza{% else %}' +
197 'water{% endif %}?');
198 expect(n.children[1].typename).to.be('If');
199
200 n = parser.parse('{% block foo %}stuff{% endblock %}');
201 expect(n.children[0].typename).to.be('Block');
202
203 n = parser.parse('{% extends "test.html" %}stuff');
204 expect(n.children[0].typename).to.be('Extends');
205
206 n = parser.parse('{% include "test.html" %}');
207 expect(n.children[0].typename).to.be('Include');
208 });
209
210 it('should parse filters', function() {
211 isAST(parser.parse('{{ foo | bar }}'),
212 [nodes.Root,
213 [nodes.Output,
214 [nodes.Filter,
215 [nodes.Symbol, 'bar'],
216 [nodes.NodeList,
217 [nodes.Symbol, 'foo']]]]]);
218
219 isAST(parser.parse('{{ foo | bar | baz }}'),
220 [nodes.Root,
221 [nodes.Output,
222 [nodes.Filter,
223 [nodes.Symbol, 'baz'],
224 [nodes.NodeList,
225 [nodes.Filter,
226 [nodes.Symbol, 'bar'],
227 [nodes.NodeList,
228 [nodes.Symbol, 'foo']]]]]]]);
229
230 isAST(parser.parse('{{ foo | bar(3) }}'),
231 [nodes.Root,
232 [nodes.Output,
233 [nodes.Filter,
234 [nodes.Symbol, 'bar'],
235 [nodes.NodeList,
236 [nodes.Symbol, 'foo'],
237 [nodes.Literal, 3]]]]]);
238 });
239
240 it('should parse macro definitions', function() {
241 var ast = parser.parse('{% macro foo(bar, baz="foobar") %}' +
242 'This is a macro' +
243 '{% endmacro %}');
244 isAST(ast,
245 [nodes.Root,
246 [nodes.Macro,
247 [nodes.Symbol, 'foo'],
248 [nodes.NodeList,
249 [nodes.Symbol, 'bar'],
250 [nodes.KeywordArgs,
251 [nodes.Pair,
252 [nodes.Symbol, 'baz'], [nodes.Literal, 'foobar']]]],
253 [nodes.NodeList,
254 [nodes.Output,
255 [nodes.TemplateData, 'This is a macro']]]]]);
256 });
257
258 it('should parse raw', function() {
259 isAST(parser.parse('{% raw %}hello {{ {% %} }}{% endraw %}'),
260 [nodes.Root,
261 [nodes.Output,
262 [nodes.TemplateData, 'hello {{ {% %} }}']]]);
263 });
264
265 it('should parse keyword and non-keyword arguments', function() {
266 isAST(parser.parse('{{ foo("bar", falalalala, baz="foobar") }}'),
267 [nodes.Root,
268 [nodes.Output,
269 [nodes.FunCall,
270 [nodes.Symbol, 'foo'],
271 [nodes.NodeList,
272 [nodes.Literal, 'bar'],
273 [nodes.Symbol, 'falalalala'],
274 [nodes.KeywordArgs,
275 [nodes.Pair,
276 [nodes.Symbol, 'baz'],
277 [nodes.Literal, 'foobar']]]]]]]);
278 });
279
280 it('should parse imports', function() {
281 isAST(parser.parse('{% import "foo/bar.html" as baz %}'),
282 [nodes.Root,
283 [nodes.Import,
284 [nodes.Literal, 'foo/bar.html'],
285 [nodes.Symbol, 'baz']]]);
286
287 isAST(parser.parse('{% from "foo/bar.html" import baz, ' +
288 ' foobar as foobarbaz %}'),
289 [nodes.Root,
290 [nodes.FromImport,
291 [nodes.Literal, "foo/bar.html"],
292 [nodes.NodeList,
293 [nodes.Symbol, 'baz'],
294 [nodes.Pair,
295 [nodes.Symbol, 'foobar'],
296 [nodes.Symbol, 'foobarbaz']]]]]);
297 });
298
299 it('should parse whitespace control', function() {
300 // Every start/end tag with "-" should trim the whitespace
301 // before or after it
302
303 isAST(parser.parse('{% if x %}\n hi \n{% endif %}'),
304 [nodes.Root,
305 [nodes.If,
306 [nodes.Symbol, 'x'],
307 [nodes.NodeList,
308 [nodes.Output,
309 [nodes.TemplateData, '\n hi \n']]]]]);
310
311 isAST(parser.parse('{% if x -%}\n hi \n{% endif %}'),
312 [nodes.Root,
313 [nodes.If,
314 [nodes.Symbol, 'x'],
315 [nodes.NodeList,
316 [nodes.Output,
317 [nodes.TemplateData, 'hi \n']]]]]);
318
319 isAST(parser.parse('{% if x %}\n hi \n{%- endif %}'),
320 [nodes.Root,
321 [nodes.If,
322 [nodes.Symbol, 'x'],
323 [nodes.NodeList,
324 [nodes.Output,
325 [nodes.TemplateData, '\n hi']]]]]);
326
327 isAST(parser.parse('{% if x -%}\n hi \n{%- endif %}'),
328 [nodes.Root,
329 [nodes.If,
330 [nodes.Symbol, 'x'],
331 [nodes.NodeList,
332 [nodes.Output,
333 [nodes.TemplateData, 'hi']]]]]);
334
335 isAST(parser.parse('poop \n{%- if x -%}\n hi \n{%- endif %}'),
336 [nodes.Root,
337 [nodes.Output,
338 [nodes.TemplateData, 'poop']],
339 [nodes.If,
340 [nodes.Symbol, 'x'],
341 [nodes.NodeList,
342 [nodes.Output,
343 [nodes.TemplateData, 'hi']]]]]);
344
345 // The from statement required a special case so make sure to
346 // test it
347 isAST(parser.parse('{% from x import y %}\n hi \n'),
348 [nodes.Root,
349 [nodes.FromImport,
350 [nodes.Symbol, 'x'],
351 [nodes.NodeList,
352 [nodes.Symbol, 'y']]],
353 [nodes.Output,
354 [nodes.TemplateData, '\n hi \n']]]);
355
356 isAST(parser.parse('{% from x import y -%}\n hi \n'),
357 [nodes.Root,
358 [nodes.FromImport,
359 [nodes.Symbol, 'x'],
360 [nodes.NodeList,
361 [nodes.Symbol, 'y']]],
362 [nodes.Output,
363 [nodes.TemplateData, 'hi \n']]]);
364 });
365
366 it('should throw errors', function() {
367 expect(function() {
368 parser.parse('hello {{ foo');
369 }).to.throwException(/expected variable end/);
370
371 expect(function() {
372 parser.parse('hello {% if');
373 }).to.throwException(/expected expression/);
374
375 expect(function() {
376 parser.parse('hello {% if sdf zxc');
377 }).to.throwException(/expected block end/);
378
379 expect(function() {
380 parser.parse('{% include "foo %}');
381 }).to.throwException(/expected block end/);
382
383 expect(function() {
384 parser.parse('hello {% if sdf %} data');
385 }).to.throwException(/expected endif, else, or endif/);
386
387 expect(function() {
388 parser.parse('hello {% block sdf %} data');
389 }).to.throwException(/expected endblock/);
390
391 expect(function() {
392 parser.parse('hello {% bar %} dsfsdf');
393 }).to.throwException(/unknown block tag/);
394
395 expect(function() {
396 parser.parse('{{ foo(bar baz) }}');
397 }).to.throwException(/expected comma after expression/);
398
399 expect(function() {
400 parser.parse('{% import "foo" %}');
401 }).to.throwException(/expected "as" keyword/);
402
403 expect(function() {
404 parser.parse('{% from "foo" %}');
405 }).to.throwException(/expected import/);
406
407 expect(function() {
408 parser.parse('{% from "foo" import bar baz %}');
409 }).to.throwException(/expected comma/);
410
411 expect(function() {
412 parser.parse('{% from "foo" import _bar %}');
413 }).to.throwException(/names starting with an underscore cannot be imported/);
414 });
415
416 it('should parse custom tags', function() {
417
418 function testtagExtension() {
419 this.tags = ['testtag'];
420
421 /* normally this is automatically done by Environment */
422 this._name = 'testtagExtension';
423
424 this.parse = function(parser, nodes) {
425 var begun = parser.peekToken();
426 parser.advanceAfterBlockEnd();
427 return new nodes.CallExtension(this, 'foo');
428 };
429 }
430
431 function testblocktagExtension() {
432 this.tags = ['testblocktag'];
433 this._name = 'testblocktagExtension';
434
435 this.parse = function(parser, nodes) {
436 var begun = parser.peekToken();
437 parser.advanceAfterBlockEnd();
438
439 var content = parser.parseUntilBlocks('endtestblocktag');
440 var tag = new nodes.CallExtension(this, 'bar', null, [1, content]);
441 parser.advanceAfterBlockEnd();
442
443 return tag;
444 };
445 }
446
447 function testargsExtension() {
448 this.tags = ['testargs'];
449 this._name = 'testargsExtension';
450
451 this.parse = function(parser, nodes, tokens) {
452 var begun = parser.peekToken();
453 var args = null;
454
455 // Skip the name
456 parser.nextToken();
457
458 args = parser.parseSignature(true);
459 parser.advanceAfterBlockEnd(begun.value);
460
461 return new nodes.CallExtension(this, 'biz', args);
462 };
463 }
464
465 var extensions = [new testtagExtension(),
466 new testblocktagExtension(),
467 new testargsExtension()];
468
469 isAST(parser.parse('{% testtag %}', extensions),
470 [nodes.Root,
471 [nodes.CallExtension, extensions[0], 'foo', undefined, undefined]]);
472
473 isAST(parser.parse('{% testblocktag %}sdfd{% endtestblocktag %}',
474 extensions),
475 [nodes.Root,
476 [nodes.CallExtension, extensions[1], 'bar', null,
477 [1, [nodes.NodeList,
478 [nodes.Output,
479 [nodes.TemplateData, "sdfd"]]]]]]);
480
481 isAST(parser.parse('{% testblocktag %}{{ 123 }}{% endtestblocktag %}',
482 extensions),
483 [nodes.Root,
484 [nodes.CallExtension, extensions[1], 'bar', null,
485 [1, [nodes.NodeList,
486 [nodes.Output,
487 [nodes.Literal, 123]]]]]]);
488
489 isAST(parser.parse('{% testargs(123, "abc", foo="bar") %}', extensions),
490 [nodes.Root,
491 [nodes.CallExtension, extensions[2], 'biz',
492
493 // The only arg is the list of run-time arguments
494 // coming from the template
495 [nodes.NodeList,
496 [nodes.Literal, 123],
497 [nodes.Literal, "abc"],
498 [nodes.KeywordArgs,
499 [nodes.Pair,
500 [nodes.Symbol, "foo"],
501 [nodes.Literal, "bar"]]]]]]);
502
503 isAST(parser.parse('{% testargs %}', extensions),
504 [nodes.Root,
505 [nodes.CallExtension, extensions[2], 'biz', null]]);
506 });
507 });
508})();