@handlebars/parser 2.1.0 → 2.2.1

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.
package/spec/utils.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { parse, print } from '../dist/esm/index.js';
2
+
1
3
  let AssertError;
2
4
  if (Error.captureStackTrace) {
3
5
  AssertError = function AssertError(message, caller) {
@@ -15,20 +17,79 @@ if (Error.captureStackTrace) {
15
17
  }
16
18
 
17
19
  /**
18
- * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
20
+ * @todo Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
19
21
  * @see https://www.chaijs.com/api/bdd/
20
22
  */
21
- export function equals(a, b, msg) {
22
- if (a !== b) {
23
- throw new AssertError(
24
- "'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
23
+ export function equals(actual, expected, msg) {
24
+ if (actual !== expected) {
25
+ const error = new AssertError(
26
+ `\n Actual: ${actual} Expected: ${expected}` +
27
+ (msg ? `\n${msg}` : ''),
25
28
  equals
26
29
  );
30
+ error.expected = expected;
31
+ error.actual = actual;
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ export function equalsAst(source, expected, options) {
37
+ const msg = typeof options === 'string' ? options : options?.msg;
38
+ const parserOptions =
39
+ typeof options === 'string' ? undefined : options?.options;
40
+ const ast = astFor(source, parserOptions);
41
+ const padding = ` `.repeat(8);
42
+
43
+ if (ast !== `${expected}\n`) {
44
+ let sourceMsg = `${padding}Source: ${source}`;
45
+ if (parserOptions) {
46
+ let formattedOptions = printOptions(parserOptions)
47
+ .split('\n')
48
+ .join(`\n${padding}`);
49
+
50
+ sourceMsg += `\n${padding}Options: ${formattedOptions}`;
51
+ }
52
+ const error = new AssertError(
53
+ `\n${sourceMsg}${msg ? `\n${msg}` : ''}\n`,
54
+ equalsAst
55
+ );
56
+
57
+ error.expected = expected;
58
+ error.actual = ast;
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ function printOptions(options) {
64
+ if (!options) {
65
+ return '';
27
66
  }
28
- };
67
+
68
+ let outOptions = {};
69
+
70
+ if (options.srcName) {
71
+ outOptions.srcName = options.srcName;
72
+ }
73
+ if (options.syntax) {
74
+ outOptions.syntax = {};
75
+
76
+ if (options.syntax.hash) {
77
+ outOptions.syntax.hash = `{function ${
78
+ options.syntax.hash.name ?? 'anonymous'
79
+ }}`;
80
+ }
81
+ if (options.syntax.square) {
82
+ outOptions.syntax.square = `{function ${
83
+ options.syntax.square.name ?? 'anonymous'
84
+ }}`;
85
+ }
86
+ }
87
+
88
+ return JSON.stringify(outOptions, null, 2);
89
+ }
29
90
 
30
91
  /**
31
- * @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
92
+ * @todo Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
32
93
  * @see https://www.chaijs.com/api/bdd/#method_throw
33
94
  */
34
95
  export function shouldThrow(callback, type, msg) {
@@ -58,4 +119,8 @@ export function shouldThrow(callback, type, msg) {
58
119
  if (failed) {
59
120
  throw new AssertError('It failed to throw', shouldThrow);
60
121
  }
61
- };
122
+ }
123
+ function astFor(template, options = {}) {
124
+ let ast = parse(template, options);
125
+ return print(ast);
126
+ }
package/spec/visitor.js CHANGED
@@ -1,8 +1,8 @@
1
- import { Visitor, parse, print, Exception } from '../dist/esm';
2
- import { equals, shouldThrow } from './utils';
1
+ import { Visitor, parse, print, Exception } from '../dist/esm/index.js';
2
+ import { equals, shouldThrow } from './utils.js';
3
3
 
4
- describe('Visitor', function() {
5
- it('should provide coverage', function() {
4
+ describe('Visitor', function () {
5
+ it('should provide coverage', function () {
6
6
  // Simply run the thing and make sure it does not fail and that all of the
7
7
  // stub methods are executed
8
8
  let visitor = new Visitor();
@@ -16,16 +16,16 @@ describe('Visitor', function() {
16
16
  visitor.accept(parse('{{* bar }}'));
17
17
  });
18
18
 
19
- it('should traverse to stubs', function() {
19
+ it('should traverse to stubs', function () {
20
20
  let visitor = new Visitor();
21
21
 
22
- visitor.StringLiteral = function(string) {
22
+ visitor.StringLiteral = function (string) {
23
23
  equals(string.value, '2');
24
24
  };
25
- visitor.NumberLiteral = function(number) {
25
+ visitor.NumberLiteral = function (number) {
26
26
  equals(number.value, 1);
27
27
  };
28
- visitor.BooleanLiteral = function(bool) {
28
+ visitor.BooleanLiteral = function (bool) {
29
29
  equals(bool.value, true);
30
30
 
31
31
  equals(this.parents.length, 3);
@@ -33,13 +33,13 @@ describe('Visitor', function() {
33
33
  equals(this.parents[1].type, 'BlockStatement');
34
34
  equals(this.parents[2].type, 'Program');
35
35
  };
36
- visitor.PathExpression = function(id) {
36
+ visitor.PathExpression = function (id) {
37
37
  equals(/(foo\.)?bar$/.test(id.original), true);
38
38
  };
39
- visitor.ContentStatement = function(content) {
39
+ visitor.ContentStatement = function (content) {
40
40
  equals(content.value, ' ');
41
41
  };
42
- visitor.CommentStatement = function(comment) {
42
+ visitor.CommentStatement = function (comment) {
43
43
  equals(comment.value, 'comment');
44
44
  };
45
45
 
@@ -50,53 +50,47 @@ describe('Visitor', function() {
50
50
  );
51
51
  });
52
52
 
53
- describe('mutating', function() {
54
- describe('fields', function() {
55
- it('should replace value', function() {
53
+ describe('mutating', function () {
54
+ describe('fields', function () {
55
+ it('should replace value', function () {
56
56
  let visitor = new Visitor();
57
57
 
58
58
  visitor.mutating = true;
59
- visitor.StringLiteral = function(string) {
59
+ visitor.StringLiteral = function (string) {
60
60
  return { type: 'NumberLiteral', value: 42, loc: string.loc };
61
61
  };
62
62
 
63
63
  let ast = parse('{{foo foo="foo"}}');
64
64
  visitor.accept(ast);
65
- equals(
66
- print(ast),
67
- '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
68
- );
65
+ equals(print(ast), '{{ p%foo HASH{foo=n%42} }}\n');
69
66
  });
70
- it('should treat undefined resonse as identity', function() {
67
+ it('should treat undefined resonse as identity', function () {
71
68
  let visitor = new Visitor();
72
69
  visitor.mutating = true;
73
70
 
74
71
  let ast = parse('{{foo foo=42}}');
75
72
  visitor.accept(ast);
76
- equals(
77
- print(ast),
78
- '{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
79
- );
73
+ equals(print(ast), '{{ p%foo HASH{foo=n%42} }}\n');
80
74
  });
81
- it('should remove false responses', function() {
75
+ it('should remove false responses', function () {
82
76
  let visitor = new Visitor();
83
77
 
84
78
  visitor.mutating = true;
85
- visitor.Hash = function() {
79
+ visitor.Hash = function () {
86
80
  return false;
87
81
  };
88
82
 
89
83
  let ast = parse('{{foo foo=42}}');
90
84
  visitor.accept(ast);
91
- equals(print(ast), '{{ PATH:foo [] }}\n');
85
+ equals(print(ast), '{{ p%foo }}\n');
92
86
  });
93
- it('should throw when removing required values', function() {
87
+ it('should throw when removing required values', function () {
94
88
  shouldThrow(
95
- function() {
89
+ function () {
96
90
  let visitor = new Visitor();
97
91
 
98
92
  visitor.mutating = true;
99
- visitor.PathExpression = function() {
93
+ visitor.PathExpression = function () {
100
94
  return false;
101
95
  };
102
96
 
@@ -107,13 +101,13 @@ describe('Visitor', function() {
107
101
  'MustacheStatement requires path'
108
102
  );
109
103
  });
110
- it('should throw when returning non-node responses', function() {
104
+ it('should throw when returning non-node responses', function () {
111
105
  shouldThrow(
112
- function() {
106
+ function () {
113
107
  let visitor = new Visitor();
114
108
 
115
109
  visitor.mutating = true;
116
- visitor.PathExpression = function() {
110
+ visitor.PathExpression = function () {
117
111
  return {};
118
112
  };
119
113
 
@@ -125,38 +119,38 @@ describe('Visitor', function() {
125
119
  );
126
120
  });
127
121
  });
128
- describe('arrays', function() {
129
- it('should replace value', function() {
122
+ describe('arrays', function () {
123
+ it('should replace value', function () {
130
124
  let visitor = new Visitor();
131
125
 
132
126
  visitor.mutating = true;
133
- visitor.StringLiteral = function(string) {
127
+ visitor.StringLiteral = function (string) {
134
128
  return { type: 'NumberLiteral', value: 42, loc: string.locInfo };
135
129
  };
136
130
 
137
131
  let ast = parse('{{foo "foo"}}');
138
132
  visitor.accept(ast);
139
- equals(print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
133
+ equals(print(ast), '{{ p%foo [n%42] }}\n');
140
134
  });
141
- it('should treat undefined resonse as identity', function() {
135
+ it('should treat undefined resonse as identity', function () {
142
136
  let visitor = new Visitor();
143
137
  visitor.mutating = true;
144
138
 
145
139
  let ast = parse('{{foo 42}}');
146
140
  visitor.accept(ast);
147
- equals(print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
141
+ equals(print(ast), '{{ p%foo [n%42] }}\n');
148
142
  });
149
- it('should remove false responses', function() {
143
+ it('should remove false responses', function () {
150
144
  let visitor = new Visitor();
151
145
 
152
146
  visitor.mutating = true;
153
- visitor.NumberLiteral = function() {
147
+ visitor.NumberLiteral = function () {
154
148
  return false;
155
149
  };
156
150
 
157
151
  let ast = parse('{{foo 42}}');
158
152
  visitor.accept(ast);
159
- equals(print(ast), '{{ PATH:foo [] }}\n');
153
+ equals(print(ast), '{{ p%foo }}\n');
160
154
  });
161
155
  });
162
156
  });
package/src/handlebars.l CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- %x mu emu com raw
2
+ %x mu emu com raw escl
3
3
 
4
4
  %{
5
5
 
@@ -12,8 +12,8 @@ function strip(start, end) {
12
12
  LEFT_STRIP "~"
13
13
  RIGHT_STRIP "~"
14
14
 
15
- LOOKAHEAD [=~}\s\/.)|]
16
- LITERAL_LOOKAHEAD [~}\s)]
15
+ LOOKAHEAD [=~}\s\/.)\]|]
16
+ LITERAL_LOOKAHEAD [~}\s)\]]
17
17
 
18
18
  /*
19
19
  ID is the inverse of control characters.
@@ -73,6 +73,18 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
73
73
  <mu>"(" return 'OPEN_SEXPR';
74
74
  <mu>")" return 'CLOSE_SEXPR';
75
75
 
76
+ <mu>"[" {
77
+ if (yy.syntax.square === 'string') {
78
+ this.unput(yytext);
79
+ // escaped literal
80
+ this.begin('escl');
81
+ } else {
82
+ return 'OPEN_ARRAY';
83
+ }
84
+ }
85
+ <mu>"]" return 'CLOSE_ARRAY';
86
+
87
+
76
88
  <mu>"{{{{" { return 'OPEN_RAW_BLOCK'; }
77
89
  <mu>"}}}}" {
78
90
  this.popState();
@@ -103,6 +115,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
103
115
  <mu>"=" return 'EQUALS';
104
116
  <mu>".." return 'ID';
105
117
  <mu>"."/{LOOKAHEAD} return 'ID';
118
+ <mu>".#" return 'PRIVATE_SEP';
106
119
  <mu>[\/.] return 'SEP';
107
120
  <mu>\s+ // ignore whitespace
108
121
  <mu>"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED';
@@ -120,7 +133,12 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
120
133
 
121
134
  <mu>{ID} return 'ID';
122
135
 
123
- <mu>'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID';
136
+ <escl>'['('\\]'|[^\]])*']' {
137
+ yytext = yytext.replace(/\\([\\\]])/g,'$1');
138
+ this.popState();
139
+ return 'ID';
140
+ }
141
+
124
142
  <mu>. return 'INVALID';
125
143
 
126
144
  <INITIAL,mu><<EOF>> return 'EOF';
package/src/handlebars.yy CHANGED
@@ -85,7 +85,8 @@ closeBlock
85
85
  mustache
86
86
  // Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
87
87
  // This also allows for handler unification as all mustache node instances can utilize the same handler
88
- : OPEN expr expr* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
88
+ : OPEN hash CLOSE -> yy.prepareMustache(yy.syntax.hash($2, yy.locInfo(@$), { yy, syntax: 'expr' }), [], undefined, $1, yy.stripFlags($1, $3), @$)
89
+ | OPEN expr expr* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
89
90
  | OPEN_UNESCAPED expr expr* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
90
91
  ;
91
92
 
@@ -111,11 +112,18 @@ openPartialBlock
111
112
 
112
113
  expr
113
114
  : helperName -> $1
115
+ | exprHead -> $1
116
+ ;
117
+
118
+ exprHead
119
+ : arrayLiteral -> $1
114
120
  | sexpr -> $1
115
121
  ;
116
122
 
123
+
117
124
  sexpr
118
- : OPEN_SEXPR expr expr* hash? CLOSE_SEXPR {
125
+ : OPEN_SEXPR hash CLOSE_SEXPR -> yy.syntax.hash($2, yy.locInfo(@$), { yy, syntax: 'expr' })
126
+ | OPEN_SEXPR expr expr* hash? CLOSE_SEXPR {
119
127
  $$ = {
120
128
  type: 'SubExpression',
121
129
  path: $2,
@@ -133,6 +141,10 @@ hashSegment
133
141
  : ID EQUALS expr -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)}
134
142
  ;
135
143
 
144
+ arrayLiteral
145
+ : OPEN_ARRAY expr* CLOSE_ARRAY -> yy.syntax.square($2, yy.locInfo(@$), { yy, syntax: 'expr' })
146
+ ;
147
+
136
148
  blockParams
137
149
  : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2)
138
150
  ;
@@ -151,12 +163,17 @@ dataName
151
163
  : DATA pathSegments -> yy.preparePath(true, false, $2, @$)
152
164
  ;
153
165
 
166
+ sep
167
+ : SEP -> $1
168
+ | PRIVATE_SEP -> $1
169
+ ;
170
+
154
171
  path
155
- : sexpr SEP pathSegments -> yy.preparePath(false, $1, $3, @$)
172
+ : exprHead sep pathSegments -> yy.preparePath(false, $1, $3, @$)
156
173
  | pathSegments -> yy.preparePath(false, false, $1, @$)
157
174
  ;
158
175
 
159
176
  pathSegments
160
- : pathSegments SEP ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; }
177
+ : pathSegments sep ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; }
161
178
  | ID -> [{part: yy.id($1), original: $1}]
162
179
  ;
package/tsconfig.json CHANGED
@@ -8,18 +8,24 @@
8
8
  "baseUrl": "lib",
9
9
  "rootDir": "lib",
10
10
  "esModuleInterop": true,
11
- "moduleResolution": "node",
12
-
11
+ "moduleResolution": "bundler",
12
+ "verbatimModuleSyntax": true,
13
13
  // Enhance Strictness
14
14
  "strict": true,
15
15
  "suppressImplicitAnyIndexErrors": false,
16
16
  "noUnusedLocals": true,
17
17
  "noUnusedParameters": true,
18
18
  "noImplicitReturns": true,
19
-
20
19
  "newLine": "LF",
21
20
  "allowJs": true
22
21
  },
23
- "include": ["lib/**/*.js"],
24
- "exclude": ["dist", "node_modules", ".vscode"]
25
- }
22
+ "include": [
23
+ "lib/**/*.js",
24
+ "lib/**/*.d.ts"
25
+ ],
26
+ "exclude": [
27
+ "dist",
28
+ "node_modules",
29
+ ".vscode"
30
+ ]
31
+ }