123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- import { parse, print } from '../dist/esm';
- import { equals, shouldThrow } from './utils';
- describe('parser', function() {
- function astFor(template) {
- let ast = parse(template);
- return print(ast);
- }
- it('parses simple mustaches', function() {
- equals(astFor('{{123}}'), '{{ NUMBER{123} [] }}\n');
- equals(astFor('{{"foo"}}'), '{{ "foo" [] }}\n');
- equals(astFor('{{false}}'), '{{ BOOLEAN{false} [] }}\n');
- equals(astFor('{{true}}'), '{{ BOOLEAN{true} [] }}\n');
- equals(astFor('{{foo}}'), '{{ PATH:foo [] }}\n');
- equals(astFor('{{foo?}}'), '{{ PATH:foo? [] }}\n');
- equals(astFor('{{foo_}}'), '{{ PATH:foo_ [] }}\n');
- equals(astFor('{{foo-}}'), '{{ PATH:foo- [] }}\n');
- equals(astFor('{{foo:}}'), '{{ PATH:foo: [] }}\n');
- });
- it('parses simple mustaches with data', function() {
- equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n');
- });
- it('parses simple mustaches with data paths', function() {
- equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n');
- });
- it('parses mustaches with paths', function() {
- equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n');
- });
- it('parses mustaches with this/foo', function() {
- equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n');
- });
- it('parses mustaches with - in a path', function() {
- equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
- });
- it('parses mustaches with escaped [] in a path', function() {
- equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n');
- });
- it('parses escaped \\\\ in path', function() {
- equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n');
- });
- it('parses mustaches with parameters', function() {
- equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
- });
- it('parses mustaches with string parameters', function() {
- equals(astFor('{{foo bar "baz" }}'), '{{ PATH:foo [PATH:bar, "baz"] }}\n');
- });
- it('parses mustaches with NUMBER parameters', function() {
- equals(astFor('{{foo 1}}'), '{{ PATH:foo [NUMBER{1}] }}\n');
- });
- it('parses mustaches with BOOLEAN parameters', function() {
- equals(astFor('{{foo true}}'), '{{ PATH:foo [BOOLEAN{true}] }}\n');
- equals(astFor('{{foo false}}'), '{{ PATH:foo [BOOLEAN{false}] }}\n');
- });
- it('parses mustaches with undefined and null paths', function() {
- equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n');
- equals(astFor('{{null}}'), '{{ NULL [] }}\n');
- });
- it('parses mustaches with undefined and null parameters', function() {
- equals(
- astFor('{{foo undefined null}}'),
- '{{ PATH:foo [UNDEFINED, NULL] }}\n'
- );
- });
- it('parses mustaches with DATA parameters', function() {
- equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
- });
- it('parses mustaches with hash arguments', function() {
- equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
- equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
- equals(
- astFor('{{foo bar=true}}'),
- '{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
- );
- equals(
- astFor('{{foo bar=false}}'),
- '{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
- );
- equals(
- astFor('{{foo bar=@baz}}'),
- '{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
- );
- equals(
- astFor('{{foo bar=baz bat=bam}}'),
- '{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
- );
- equals(
- astFor('{{foo bar=baz bat="bam"}}'),
- '{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
- );
- equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
- equals(
- astFor('{{foo omg bar=baz bat="bam"}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
- );
- equals(
- astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
- '{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
- );
- });
- it('parses contents followed by a mustache', function() {
- equals(
- astFor('foo bar {{baz}}'),
- "CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
- );
- });
- it('parses a partial', function() {
- equals(astFor('{{> foo }}'), '{{> PARTIAL:foo }}\n');
- equals(astFor('{{> "foo" }}'), '{{> PARTIAL:foo }}\n');
- equals(astFor('{{> 1 }}'), '{{> PARTIAL:1 }}\n');
- });
- it('parses a partial with context', function() {
- equals(astFor('{{> foo bar}}'), '{{> PARTIAL:foo PATH:bar }}\n');
- });
- it('parses a partial with hash', function() {
- equals(
- astFor('{{> foo bar=bat}}'),
- '{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
- );
- });
- it('parses a partial with context and hash', function() {
- equals(
- astFor('{{> foo bar bat=baz}}'),
- '{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
- );
- });
- it('parses a partial with a complex name', function() {
- equals(
- astFor('{{> shared/partial?.bar}}'),
- '{{> PARTIAL:shared/partial?.bar }}\n'
- );
- });
- it('parsers partial blocks', function() {
- equals(
- astFor('{{#> foo}}bar{{/foo}}'),
- "{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
- );
- });
- it('should handle parser block mismatch', function() {
- shouldThrow(
- function() {
- astFor('{{#> goodbyes}}{{/hellos}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
- });
- it('parsers partial blocks with arguments', function() {
- equals(
- astFor('{{#> foo context hash=value}}bar{{/foo}}'),
- "{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
- );
- });
- it('parses a comment', function() {
- equals(
- astFor('{{! this is a comment }}'),
- "{{! ' this is a comment ' }}\n"
- );
- });
- it('parses a multi-line comment', function() {
- equals(
- astFor('{{!\nthis is a multi-line comment\n}}'),
- "{{! '\nthis is a multi-line comment\n' }}\n"
- );
- });
- it('parses an inverse section', function() {
- equals(
- astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
- });
- it('parses an inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
- });
- it('parses multiple inverse sections', function() {
- equals(
- astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
- );
- });
- it('parses empty blocks', function() {
- equals(astFor('{{#foo}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n');
- });
- it('parses empty blocks with empty inverse section', function() {
- equals(
- astFor('{{#foo}}{{^}}{{/foo}}'),
- 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
- );
- });
- it('parses empty blocks with empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}}{{else}}{{/foo}}'),
- 'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
- );
- });
- it('parses non-empty blocks with empty inverse section', function() {
- equals(
- astFor('{{#foo}} bar {{^}}{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
- );
- });
- it('parses non-empty blocks with empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}} bar {{else}}{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
- );
- });
- it('parses empty blocks with non-empty inverse section', function() {
- equals(
- astFor('{{#foo}}{{^}} bar {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
- );
- });
- it('parses empty blocks with non-empty inverse (else-style) section', function() {
- equals(
- astFor('{{#foo}}{{else}} bar {{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
- );
- });
- it('parses a standalone inverse section', function() {
- equals(
- astFor('{{^foo}}bar{{/foo}}'),
- "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
- );
- });
- it('throws on old inverse section', function() {
- shouldThrow(function() {
- astFor('{{else foo}}bar{{/foo}}');
- }, Error);
- });
- it('parses block with block params', function() {
- equals(
- astFor('{{#foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
- });
- it('parses mustaches with sub-expressions as the callable', function() {
- equals(
- astFor('{{(my-helper foo)}}'),
- '{{ PATH:my-helper [PATH:foo] [] }}\n'
- );
- });
- it('parses mustaches with sub-expressions as the callable (with args)', function() {
- equals(
- astFor('{{(my-helper foo) bar}}'),
- '{{ PATH:my-helper [PATH:foo] [PATH:bar] }}\n'
- );
- });
- it('parses sub-expressions with a sub-expression as the callable', function() {
- equals(
- astFor('{{((my-helper foo))}}'),
- '{{ PATH:my-helper [PATH:foo] [] [] }}\n'
- );
- });
- it('parses sub-expressions with a sub-expression as the callable (with args)', function() {
- equals(
- astFor('{{((my-helper foo) bar)}}'),
- '{{ PATH:my-helper [PATH:foo] [PATH:bar] [] }}\n'
- );
- });
- it('parses arguments with a sub-expression as the callable (with args)', function() {
- equals(
- astFor('{{my-helper ((foo) bar) baz=((foo bar))}}'),
- '{{ PATH:my-helper [PATH:foo [] [PATH:bar]] HASH{baz=PATH:foo [PATH:bar] []} }}\n'
- );
- });
- it('parses inverse block with block params', function() {
- equals(
- astFor('{{^foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
- });
- it('parses chained inverse block with block params', function() {
- equals(
- astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
- "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
- );
- });
- it("raises if there's a Parse error", function() {
- shouldThrow(
- function() {
- astFor('foo{{^}}bar');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{foo}');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{foo &}}');
- },
- Error,
- /Parse error on line 1/
- );
- shouldThrow(
- function() {
- astFor('{{#goodbyes}}{{/hellos}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
- shouldThrow(
- function() {
- astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
- },
- Error,
- /goodbyes doesn't match hellos/
- );
- });
- it('should handle invalid paths', function() {
- shouldThrow(
- function() {
- astFor('{{foo/../bar}}');
- },
- Error,
- /Invalid path: foo\/\.\. - 1:2/
- );
- shouldThrow(
- function() {
- astFor('{{foo/./bar}}');
- },
- Error,
- /Invalid path: foo\/\. - 1:2/
- );
- shouldThrow(
- function() {
- astFor('{{foo/this/bar}}');
- },
- Error,
- /Invalid path: foo\/this - 1:2/
- );
- });
- it('knows how to report the correct line number in errors', function() {
- shouldThrow(
- function() {
- astFor('hello\nmy\n{{foo}');
- },
- Error,
- /Parse error on line 3/
- );
- shouldThrow(
- function() {
- astFor('hello\n\nmy\n\n{{foo}');
- },
- Error,
- /Parse error on line 5/
- );
- });
- it('knows how to report the correct line number in errors when the first character is a newline', function() {
- shouldThrow(
- function() {
- astFor('\n\nhello\n\nmy\n\n{{foo}');
- },
- Error,
- /Parse error on line 7/
- );
- });
- describe('externally compiled AST', function() {
- it('can pass through an already-compiled AST', function() {
- equals(
- astFor({
- type: 'Program',
- body: [{ type: 'ContentStatement', value: 'Hello' }]
- }),
- "CONTENT[ 'Hello' ]\n"
- );
- });
- });
- describe('directives', function() {
- it('should parse block directives', function() {
- equals(
- astFor('{{#* foo}}{{/foo}}'),
- 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
- );
- });
- it('should parse directives', function() {
- equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
- });
- it('should fail if directives have inverse', function() {
- shouldThrow(
- function() {
- astFor('{{#* foo}}{{^}}{{/foo}}');
- },
- Error,
- /Unexpected inverse/
- );
- });
- });
- it('GH1024 - should track program location properly', function() {
- let p = parse(
- '\n' +
- ' {{#if foo}}\n' +
- ' {{bar}}\n' +
- ' {{else}} {{baz}}\n' +
- '\n' +
- ' {{/if}}\n' +
- ' '
- );
- // We really need a deep equals but for now this should be stable...
- equals(
- JSON.stringify(p.loc),
- JSON.stringify({
- start: { line: 1, column: 0 },
- end: { line: 7, column: 4 }
- })
- );
- equals(
- JSON.stringify(p.body[1].program.loc),
- JSON.stringify({
- start: { line: 2, column: 13 },
- end: { line: 4, column: 7 }
- })
- );
- equals(
- JSON.stringify(p.body[1].inverse.loc),
- JSON.stringify({
- start: { line: 4, column: 15 },
- end: { line: 6, column: 5 }
- })
- );
- });
- });
|