@handlebars/parser 2.0.0 → 2.2.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.
package/lib/helpers.js CHANGED
@@ -1,4 +1,4 @@
1
- import Exception from './exception';
1
+ import Exception from './exception.js';
2
2
 
3
3
  function validateClose(open, close) {
4
4
  close = close.path ? close.path.original : close;
@@ -17,11 +17,11 @@ export function SourceLocation(source, locInfo) {
17
17
  this.source = source;
18
18
  this.start = {
19
19
  line: locInfo.first_line,
20
- column: locInfo.first_column
20
+ column: locInfo.first_column,
21
21
  };
22
22
  this.end = {
23
23
  line: locInfo.last_line,
24
- column: locInfo.last_column
24
+ column: locInfo.last_column,
25
25
  };
26
26
  }
27
27
 
@@ -36,7 +36,7 @@ export function id(token) {
36
36
  export function stripFlags(open, close) {
37
37
  return {
38
38
  open: open.charAt(2) === '~',
39
- close: close.charAt(close.length - 3) === '~'
39
+ close: close.charAt(close.length - 3) === '~',
40
40
  };
41
41
  }
42
42
 
@@ -44,38 +44,56 @@ export function stripComment(comment) {
44
44
  return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
45
45
  }
46
46
 
47
- export function preparePath(data, parts, loc) {
47
+ export function preparePath(data, sexpr, parts, loc) {
48
48
  loc = this.locInfo(loc);
49
49
 
50
- let original = data ? '@' : '',
51
- dig = [],
52
- depth = 0;
50
+ let original;
51
+
52
+ if (data) {
53
+ original = '@';
54
+ } else if (sexpr) {
55
+ original = sexpr.original + '.';
56
+ } else {
57
+ original = '';
58
+ }
59
+
60
+ let tail = [];
61
+ let depth = 0;
53
62
 
54
63
  for (let i = 0, l = parts.length; i < l; i++) {
55
- let part = parts[i].part,
56
- // If we have [] syntax then we do not treat path references as operators,
57
- // i.e. foo.[this] resolves to approximately context.foo['this']
58
- isLiteral = parts[i].original !== part;
59
- original += (parts[i].separator || '') + part;
64
+ let part = parts[i].part;
65
+ // If we have [] syntax then we do not treat path references as operators,
66
+ // i.e. foo.[this] resolves to approximately context.foo['this']
67
+ let isLiteral = parts[i].original !== part;
68
+ let separator = parts[i].separator;
69
+
70
+ let partPrefix = separator === '.#' ? '#' : '';
71
+
72
+ original += (separator || '') + part;
60
73
 
61
74
  if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
62
- if (dig.length > 0) {
75
+ if (tail.length > 0) {
63
76
  throw new Exception('Invalid path: ' + original, { loc });
64
77
  } else if (part === '..') {
65
78
  depth++;
66
79
  }
67
80
  } else {
68
- dig.push(part);
81
+ tail.push(`${partPrefix}${part}`);
69
82
  }
70
83
  }
71
84
 
85
+ let head = sexpr || tail.shift();
86
+
72
87
  return {
73
88
  type: 'PathExpression',
89
+ this: original.startsWith('this.'),
74
90
  data,
75
91
  depth,
76
- parts: dig,
92
+ head,
93
+ tail,
94
+ parts: head ? [head, ...tail] : tail,
77
95
  original,
78
- loc
96
+ loc,
79
97
  };
80
98
  }
81
99
 
@@ -92,7 +110,7 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
92
110
  hash,
93
111
  escaped,
94
112
  strip,
95
- loc: this.locInfo(locInfo)
113
+ loc: this.locInfo(locInfo),
96
114
  };
97
115
  }
98
116
 
@@ -104,7 +122,7 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
104
122
  type: 'Program',
105
123
  body: contents,
106
124
  strip: {},
107
- loc: locInfo
125
+ loc: locInfo,
108
126
  };
109
127
 
110
128
  return {
@@ -116,7 +134,7 @@ export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
116
134
  openStrip: {},
117
135
  inverseStrip: {},
118
136
  closeStrip: {},
119
- loc: locInfo
137
+ loc: locInfo,
120
138
  };
121
139
  }
122
140
 
@@ -170,7 +188,7 @@ export function prepareBlock(
170
188
  openStrip: openBlock.strip,
171
189
  inverseStrip,
172
190
  closeStrip: close && close.strip,
173
- loc: this.locInfo(locInfo)
191
+ loc: this.locInfo(locInfo),
174
192
  };
175
193
  }
176
194
 
@@ -185,12 +203,12 @@ export function prepareProgram(statements, loc) {
185
203
  source: firstLoc.source,
186
204
  start: {
187
205
  line: firstLoc.start.line,
188
- column: firstLoc.start.column
206
+ column: firstLoc.start.column,
189
207
  },
190
208
  end: {
191
209
  line: lastLoc.end.line,
192
- column: lastLoc.end.column
193
- }
210
+ column: lastLoc.end.column,
211
+ },
194
212
  };
195
213
  }
196
214
  }
@@ -199,7 +217,7 @@ export function prepareProgram(statements, loc) {
199
217
  type: 'Program',
200
218
  body: statements,
201
219
  strip: {},
202
- loc: loc
220
+ loc: loc,
203
221
  };
204
222
  }
205
223
 
@@ -214,6 +232,6 @@ export function preparePartialBlock(open, program, close, locInfo) {
214
232
  program,
215
233
  openStrip: open.strip,
216
234
  closeStrip: close && close.strip,
217
- loc: this.locInfo(locInfo)
235
+ loc: this.locInfo(locInfo),
218
236
  };
219
237
  }
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { default as Visitor } from './visitor';
2
- export { default as WhitespaceControl } from './whitespace-control';
3
- export { default as parser } from './parser';
4
- export { default as Exception } from './exception';
5
- export { print, PrintVisitor } from './printer';
6
- export { parse, parseWithoutProcessing } from './parse';
1
+ export { default as Visitor } from './visitor.js';
2
+ export { default as WhitespaceControl } from './whitespace-control.js';
3
+ export { default as parser } from './parser.js';
4
+ export { default as Exception } from './exception.js';
5
+ export { print, PrintVisitor } from './printer.js';
6
+ export { parse, parseWithoutProcessing } from './parse.js';
package/lib/parse.js CHANGED
@@ -1,6 +1,6 @@
1
- import parser from './parser';
2
- import WhitespaceControl from './whitespace-control';
3
- import * as Helpers from './helpers';
1
+ import parser from './parser.js';
2
+ import WhitespaceControl from './whitespace-control.js';
3
+ import * as Helpers from './helpers.js';
4
4
 
5
5
  let baseHelpers = {};
6
6
 
@@ -19,13 +19,50 @@ export function parseWithoutProcessing(input, options) {
19
19
  parser.yy = baseHelpers;
20
20
 
21
21
  // Altering the shared object here, but this is ok as parser is a sync operation
22
- parser.yy.locInfo = function(locInfo) {
22
+ parser.yy.locInfo = function (locInfo) {
23
23
  return new Helpers.SourceLocation(options && options.srcName, locInfo);
24
24
  };
25
25
 
26
- let ast = parser.parse(input);
26
+ let squareSyntax;
27
27
 
28
- return ast;
28
+ if (typeof options?.syntax?.square === 'function') {
29
+ squareSyntax = options.syntax.square;
30
+ } else if (options?.syntax?.square === 'node') {
31
+ squareSyntax = arrayLiteralNode;
32
+ } else {
33
+ squareSyntax = 'string';
34
+ }
35
+
36
+ let hashSyntax;
37
+
38
+ if (typeof options?.syntax?.hash === 'function') {
39
+ hashSyntax = options.syntax.hash;
40
+ } else {
41
+ hashSyntax = hashLiteralNode;
42
+ }
43
+
44
+ parser.yy.syntax = {
45
+ square: squareSyntax,
46
+ hash: hashSyntax,
47
+ };
48
+
49
+ return parser.parse(input);
50
+ }
51
+
52
+ function arrayLiteralNode(array, loc) {
53
+ return {
54
+ type: 'ArrayLiteral',
55
+ items: array,
56
+ loc,
57
+ };
58
+ }
59
+
60
+ function hashLiteralNode(hash, loc) {
61
+ return {
62
+ type: 'HashLiteral',
63
+ pairs: hash.pairs,
64
+ loc,
65
+ };
29
66
  }
30
67
 
31
68
  export function parse(input, options) {
package/lib/printer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable new-cap */
2
- import Visitor from './visitor';
2
+ import Visitor from './visitor.js';
3
3
 
4
4
  export function print(ast) {
5
5
  return new PrintVisitor().accept(ast);
@@ -11,7 +11,7 @@ export function PrintVisitor() {
11
11
 
12
12
  PrintVisitor.prototype = new Visitor();
13
13
 
14
- PrintVisitor.prototype.pad = function(string) {
14
+ PrintVisitor.prototype.pad = function (string) {
15
15
  let out = '';
16
16
 
17
17
  for (let i = 0, l = this.padding; i < l; i++) {
@@ -22,7 +22,7 @@ PrintVisitor.prototype.pad = function(string) {
22
22
  return out;
23
23
  };
24
24
 
25
- PrintVisitor.prototype.Program = function(program) {
25
+ PrintVisitor.prototype.Program = function (program) {
26
26
  let out = '',
27
27
  body = program.body,
28
28
  i,
@@ -46,47 +46,50 @@ PrintVisitor.prototype.Program = function(program) {
46
46
  return out;
47
47
  };
48
48
 
49
- PrintVisitor.prototype.MustacheStatement = function(mustache) {
50
- return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
49
+ PrintVisitor.prototype.MustacheStatement = function (mustache) {
50
+ if (mustache.params.length > 0 || mustache.hash) {
51
+ return this.pad('{{ ' + this.callBody(mustache) + ' }}');
52
+ } else {
53
+ return this.pad('{{ ' + this.accept(mustache.path) + ' }}');
54
+ }
51
55
  };
52
- PrintVisitor.prototype.Decorator = function(mustache) {
53
- return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
56
+ PrintVisitor.prototype.Decorator = function (mustache) {
57
+ return this.pad('{{ DIRECTIVE ' + this.callBody(mustache) + ' }}');
54
58
  };
55
59
 
56
- PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
57
- block
58
- ) {
59
- let out = '';
60
+ PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock =
61
+ function (block) {
62
+ let out = '';
60
63
 
61
- out += this.pad(
62
- (block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
63
- );
64
- this.padding++;
65
- out += this.pad(this.SubExpression(block));
66
- if (block.program) {
67
- out += this.pad('PROGRAM:');
64
+ out += this.pad(
65
+ (block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
66
+ );
68
67
  this.padding++;
69
- out += this.accept(block.program);
70
- this.padding--;
71
- }
72
- if (block.inverse) {
68
+ out += this.pad(this.callBody(block));
73
69
  if (block.program) {
70
+ out += this.pad('PROGRAM:');
74
71
  this.padding++;
72
+ out += this.accept(block.program);
73
+ this.padding--;
75
74
  }
76
- out += this.pad('{{^}}');
77
- this.padding++;
78
- out += this.accept(block.inverse);
79
- this.padding--;
80
- if (block.program) {
75
+ if (block.inverse) {
76
+ if (block.program) {
77
+ this.padding++;
78
+ }
79
+ out += this.pad('{{^}}');
80
+ this.padding++;
81
+ out += this.accept(block.inverse);
81
82
  this.padding--;
83
+ if (block.program) {
84
+ this.padding--;
85
+ }
82
86
  }
83
- }
84
- this.padding--;
87
+ this.padding--;
85
88
 
86
- return out;
87
- };
89
+ return out;
90
+ };
88
91
 
89
- PrintVisitor.prototype.PartialStatement = function(partial) {
92
+ PrintVisitor.prototype.PartialStatement = function (partial) {
90
93
  let content = 'PARTIAL:' + partial.name.original;
91
94
  if (partial.params[0]) {
92
95
  content += ' ' + this.accept(partial.params[0]);
@@ -96,7 +99,7 @@ PrintVisitor.prototype.PartialStatement = function(partial) {
96
99
  }
97
100
  return this.pad('{{> ' + content + ' }}');
98
101
  };
99
- PrintVisitor.prototype.PartialBlockStatement = function(partial) {
102
+ PrintVisitor.prototype.PartialBlockStatement = function (partial) {
100
103
  let content = 'PARTIAL BLOCK:' + partial.name.original;
101
104
  if (partial.params[0]) {
102
105
  content += ' ' + this.accept(partial.params[0]);
@@ -113,16 +116,20 @@ PrintVisitor.prototype.PartialBlockStatement = function(partial) {
113
116
  return this.pad('{{> ' + content + ' }}');
114
117
  };
115
118
 
116
- PrintVisitor.prototype.ContentStatement = function(content) {
119
+ PrintVisitor.prototype.ContentStatement = function (content) {
117
120
  return this.pad("CONTENT[ '" + content.value + "' ]");
118
121
  };
119
122
 
120
- PrintVisitor.prototype.CommentStatement = function(comment) {
123
+ PrintVisitor.prototype.CommentStatement = function (comment) {
121
124
  return this.pad("{{! '" + comment.value + "' }}");
122
125
  };
123
126
 
124
- PrintVisitor.prototype.SubExpression = function(sexpr) {
125
- let params = sexpr.params,
127
+ PrintVisitor.prototype.SubExpression = function (sexpr) {
128
+ return `(${this.callBody(sexpr)})`;
129
+ };
130
+
131
+ PrintVisitor.prototype.callBody = function (callExpr) {
132
+ let params = callExpr.params,
126
133
  paramStrings = [],
127
134
  hash;
128
135
 
@@ -130,49 +137,75 @@ PrintVisitor.prototype.SubExpression = function(sexpr) {
130
137
  paramStrings.push(this.accept(params[i]));
131
138
  }
132
139
 
133
- params = '[' + paramStrings.join(', ') + ']';
140
+ params =
141
+ paramStrings.length === 0 ? '' : ' [' + paramStrings.join(', ') + ']';
134
142
 
135
- hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
143
+ hash = callExpr.hash ? ' ' + this.accept(callExpr.hash) : '';
136
144
 
137
- return this.accept(sexpr.path) + ' ' + params + hash;
145
+ return this.accept(callExpr.path) + params + hash;
138
146
  };
139
147
 
140
- PrintVisitor.prototype.PathExpression = function(id) {
141
- let path = id.parts.join('/');
142
- return (id.data ? '@' : '') + 'PATH:' + path;
148
+ PrintVisitor.prototype.PathExpression = function (id) {
149
+ let head =
150
+ typeof id.head === 'string' ? id.head : `[${this.accept(id.head)}]`;
151
+ let path = [head, ...id.tail].join('/');
152
+ return 'p%' + prefix(id) + path;
143
153
  };
144
154
 
145
- PrintVisitor.prototype.StringLiteral = function(string) {
155
+ function prefix(path) {
156
+ if (path.data) {
157
+ return '@';
158
+ } else if (path.this) {
159
+ return 'this.';
160
+ } else {
161
+ return '';
162
+ }
163
+ }
164
+
165
+ PrintVisitor.prototype.StringLiteral = function (string) {
146
166
  return '"' + string.value + '"';
147
167
  };
148
168
 
149
- PrintVisitor.prototype.NumberLiteral = function(number) {
150
- return 'NUMBER{' + number.value + '}';
169
+ PrintVisitor.prototype.NumberLiteral = function (number) {
170
+ return 'n%' + number.value;
151
171
  };
152
172
 
153
- PrintVisitor.prototype.BooleanLiteral = function(bool) {
154
- return 'BOOLEAN{' + bool.value + '}';
173
+ PrintVisitor.prototype.BooleanLiteral = function (bool) {
174
+ return 'b%' + bool.value;
155
175
  };
156
176
 
157
- PrintVisitor.prototype.UndefinedLiteral = function() {
177
+ PrintVisitor.prototype.UndefinedLiteral = function () {
158
178
  return 'UNDEFINED';
159
179
  };
160
180
 
161
- PrintVisitor.prototype.NullLiteral = function() {
181
+ PrintVisitor.prototype.NullLiteral = function () {
162
182
  return 'NULL';
163
183
  };
164
184
 
165
- PrintVisitor.prototype.Hash = function(hash) {
185
+ PrintVisitor.prototype.ArrayLiteral = function (array) {
186
+ return `Array[${array.items.map((item) => this.accept(item)).join(', ')}]`;
187
+ };
188
+
189
+ PrintVisitor.prototype.HashLiteral = function (hash) {
190
+ return `Hash{${this.hashPairs(hash)}}`;
191
+ };
192
+
193
+ PrintVisitor.prototype.Hash = function (hash) {
194
+ return `HASH{${this.hashPairs(hash)}}`;
195
+ };
196
+
197
+ PrintVisitor.prototype.hashPairs = function (hash) {
166
198
  let pairs = hash.pairs,
167
199
  joinedPairs = [];
168
200
 
169
201
  for (let i = 0, l = pairs.length; i < l; i++) {
170
- joinedPairs.push(this.accept(pairs[i]));
202
+ joinedPairs.push(this.HashPair(pairs[i]));
171
203
  }
172
204
 
173
- return 'HASH{' + joinedPairs.join(', ') + '}';
205
+ return joinedPairs.join(' ');
174
206
  };
175
- PrintVisitor.prototype.HashPair = function(pair) {
207
+
208
+ PrintVisitor.prototype.HashPair = function (pair) {
176
209
  return pair.key + '=' + this.accept(pair.value);
177
210
  };
178
211
  /* eslint-enable new-cap */
package/lib/visitor.js CHANGED
@@ -1,4 +1,4 @@
1
- import Exception from './exception';
1
+ import Exception from './exception.js';
2
2
 
3
3
  function Visitor() {
4
4
  this.parents = [];
@@ -9,7 +9,7 @@ Visitor.prototype = {
9
9
  mutating: false,
10
10
 
11
11
  // Visits a given value. If mutating, will replace the value if necessary.
12
- acceptKey: function(node, name) {
12
+ acceptKey: function (node, name) {
13
13
  let value = this.accept(node[name]);
14
14
  if (this.mutating) {
15
15
  // Hacky sanity check: This may have a few false positives for type for the helper
@@ -30,7 +30,7 @@ Visitor.prototype = {
30
30
 
31
31
  // Performs an accept operation with added sanity check to ensure
32
32
  // required keys are not removed.
33
- acceptRequired: function(node, name) {
33
+ acceptRequired: function (node, name) {
34
34
  this.acceptKey(node, name);
35
35
 
36
36
  if (!node[name]) {
@@ -38,9 +38,9 @@ Visitor.prototype = {
38
38
  }
39
39
  },
40
40
 
41
- // Traverses a given array. If mutating, empty respnses will be removed
41
+ // Traverses a given array. If mutating, empty responses will be removed
42
42
  // for child elements.
43
- acceptArray: function(array) {
43
+ acceptArray: function (array) {
44
44
  for (let i = 0, l = array.length; i < l; i++) {
45
45
  this.acceptKey(array, i);
46
46
 
@@ -52,7 +52,7 @@ Visitor.prototype = {
52
52
  }
53
53
  },
54
54
 
55
- accept: function(object) {
55
+ accept: function (object) {
56
56
  if (!object) {
57
57
  return;
58
58
  }
@@ -78,7 +78,7 @@ Visitor.prototype = {
78
78
  }
79
79
  },
80
80
 
81
- Program: function(program) {
81
+ Program: function (program) {
82
82
  this.acceptArray(program.body);
83
83
  },
84
84
 
@@ -89,31 +89,31 @@ Visitor.prototype = {
89
89
  DecoratorBlock: visitBlock,
90
90
 
91
91
  PartialStatement: visitPartial,
92
- PartialBlockStatement: function(partial) {
92
+ PartialBlockStatement: function (partial) {
93
93
  visitPartial.call(this, partial);
94
94
 
95
95
  this.acceptKey(partial, 'program');
96
96
  },
97
97
 
98
- ContentStatement: function(/* content */) {},
99
- CommentStatement: function(/* comment */) {},
98
+ ContentStatement: function (/* content */) {},
99
+ CommentStatement: function (/* comment */) {},
100
100
 
101
101
  SubExpression: visitSubExpression,
102
102
 
103
- PathExpression: function(/* path */) {},
103
+ PathExpression: function (/* path */) {},
104
104
 
105
- StringLiteral: function(/* string */) {},
106
- NumberLiteral: function(/* number */) {},
107
- BooleanLiteral: function(/* bool */) {},
108
- UndefinedLiteral: function(/* literal */) {},
109
- NullLiteral: function(/* literal */) {},
105
+ StringLiteral: function (/* string */) {},
106
+ NumberLiteral: function (/* number */) {},
107
+ BooleanLiteral: function (/* bool */) {},
108
+ UndefinedLiteral: function (/* literal */) {},
109
+ NullLiteral: function (/* literal */) {},
110
110
 
111
- Hash: function(hash) {
111
+ Hash: function (hash) {
112
112
  this.acceptArray(hash.pairs);
113
113
  },
114
- HashPair: function(pair) {
114
+ HashPair: function (pair) {
115
115
  this.acceptRequired(pair, 'value');
116
- }
116
+ },
117
117
  };
118
118
 
119
119
  function visitSubExpression(mustache) {