@handlebars/parser 2.2.1 → 2.2.3
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/.github/workflows/plan-release.yml +15 -47
- package/.github/workflows/publish.yml +4 -6
- package/.release-plan.json +11 -3
- package/CHANGELOG.md +34 -0
- package/TROUBLESHOOTING.md +17 -0
- package/dist/cjs/exception.js +4 -2
- package/dist/cjs/helpers.js +42 -17
- package/dist/cjs/index.js +21 -7
- package/dist/cjs/parse.js +41 -11
- package/dist/cjs/parser.js +4 -2
- package/dist/cjs/printer.js +12 -5
- package/dist/cjs/visitor.js +11 -6
- package/dist/cjs/whitespace-control.js +35 -8
- package/dist/esm/helpers.js +13 -3
- package/dist/esm/whitespace-control.js +27 -5
- package/lib/helpers.js +12 -2
- package/lib/whitespace-control.js +32 -6
- package/package.json +4 -4
- package/spec/ast.js +54 -0
- package/tsconfig.json +1 -1
package/lib/helpers.js
CHANGED
|
@@ -164,8 +164,18 @@ export function prepareBlock(
|
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
// Only at the outermost block (where close is the real closing tag, not another
|
|
168
|
+
// chain link) propagate its strip flags through all chained else-if blocks.
|
|
169
|
+
if (inverseAndProgram.chain && !close.chain) {
|
|
170
|
+
const closeStrip = close.strip;
|
|
171
|
+
let innerBlock = inverseAndProgram.program.body[0];
|
|
172
|
+
// Walk the full chain so every block gets the real closing tag's strip flags.
|
|
173
|
+
while (innerBlock) {
|
|
174
|
+
innerBlock.closeStrip = closeStrip;
|
|
175
|
+
innerBlock = (innerBlock.inverse && innerBlock.inverse.chained)
|
|
176
|
+
? innerBlock.inverse.body[0]
|
|
177
|
+
: null;
|
|
178
|
+
}
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
inverseStrip = inverseAndProgram.strip;
|
|
@@ -41,7 +41,9 @@ WhitespaceControl.prototype.Program = function (program) {
|
|
|
41
41
|
// If we are on a standalone node, save the indent info for partials
|
|
42
42
|
if (current.type === 'PartialStatement') {
|
|
43
43
|
// Pull out the whitespace from the final line
|
|
44
|
-
current.indent = /([ \t]
|
|
44
|
+
current.indent = /(?:^|[^ \t])([ \t]+)$/.exec(
|
|
45
|
+
body[i - 1].original
|
|
46
|
+
)[1];
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
}
|
|
@@ -55,7 +57,21 @@ WhitespaceControl.prototype.Program = function (program) {
|
|
|
55
57
|
// Always strip the next node
|
|
56
58
|
omitRight(body, i);
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
// For chained else-if blocks, walk the chain and strip trailing indent
|
|
61
|
+
// from every terminal body so all execution paths lose the close-tag indent.
|
|
62
|
+
let chainNode = current.inverse;
|
|
63
|
+
if (chainNode && chainNode.chained) {
|
|
64
|
+
while (chainNode && chainNode.chained) {
|
|
65
|
+
let lastBlock = chainNode.body[chainNode.body.length - 1];
|
|
66
|
+
omitLeft(lastBlock.program.body);
|
|
67
|
+
chainNode = lastBlock.inverse;
|
|
68
|
+
}
|
|
69
|
+
if (chainNode) {
|
|
70
|
+
omitLeft(chainNode.body);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
omitLeft((current.inverse || current.program).body);
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
77
|
|
|
@@ -158,9 +174,13 @@ function isPrevWhitespace(body, i, isRoot) {
|
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
if (prev.type === 'ContentStatement') {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
177
|
+
// Matches when the trailing whitespace contains a newline (or, for the
|
|
178
|
+
// root's first node, is the whole string). See omitLeft for the anchoring.
|
|
179
|
+
return (
|
|
180
|
+
sibling || !isRoot
|
|
181
|
+
? /(?:^|\S)[^\S\n]*\n\s*$/
|
|
182
|
+
: /^\s*$|(?:^|\S)[^\S\n]*\n\s*$/
|
|
183
|
+
).test(prev.original);
|
|
164
184
|
}
|
|
165
185
|
}
|
|
166
186
|
function isNextWhitespace(body, i, isRoot) {
|
|
@@ -225,7 +245,13 @@ function omitLeft(body, i, multiple) {
|
|
|
225
245
|
|
|
226
246
|
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
|
227
247
|
let original = current.value;
|
|
228
|
-
|
|
248
|
+
// The match must start after a non-whitespace character (or at the start):
|
|
249
|
+
// a plain /\s+$/ is retried at every position of a whitespace run, which
|
|
250
|
+
// takes quadratic time on long runs.
|
|
251
|
+
current.value = current.value.replace(
|
|
252
|
+
multiple ? /(^|\S)\s+$/ : /(^|[^ \t])[ \t]+$/,
|
|
253
|
+
'$1'
|
|
254
|
+
);
|
|
229
255
|
current.leftStripped = current.value !== original;
|
|
230
256
|
return current.leftStripped;
|
|
231
257
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handlebars/parser",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "The parser for the Handlebars language",
|
|
5
5
|
"homepage": "https://github.com/handlebars-lang/handlebars-parser#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"mocha": "^10.7.3",
|
|
47
47
|
"npm-run-all": "^4.1.5",
|
|
48
48
|
"prettier": "^2.1.1",
|
|
49
|
-
"release-plan": "^0.
|
|
49
|
+
"release-plan": "^0.18.0",
|
|
50
50
|
"typescript": "^5.6.3"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
"registry": "https://registry.npmjs.org"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
|
-
"lint": "eslint .",
|
|
61
60
|
"build": "npm-run-all build:parser build:esm build:cjs",
|
|
62
|
-
"build:cjs": "tsc --module
|
|
61
|
+
"build:cjs": "tsc --module commonjs --moduleResolution node --target es5 --outDir dist/cjs",
|
|
63
62
|
"build:esm": "tsc --module es2015 --target es5 --outDir dist/esm",
|
|
64
63
|
"build:jison": "jison -m js src/handlebars.yy src/handlebars.l -o lib/parser.js",
|
|
65
64
|
"build:parser": "npm-run-all build:jison build:parser-suffix",
|
|
66
65
|
"build:parser-suffix": "combine-files lib/parser.js,src/parser-suffix.js lib/parser.js",
|
|
66
|
+
"lint": "eslint .",
|
|
67
67
|
"pretest": "pnpm run build",
|
|
68
68
|
"test": "mocha --inline-diffs spec",
|
|
69
69
|
"pretest:bail": "pnpm run build",
|
package/spec/ast.js
CHANGED
|
@@ -17,6 +17,17 @@ describe('ast', function () {
|
|
|
17
17
|
equals(ast.body[0].value, '');
|
|
18
18
|
equals(ast.body[1].program.body[0].value, 'foo');
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
it('tilde on one else-if does not strip trailing whitespace in later branches', function () {
|
|
22
|
+
let ast = parse('{{#if a}}A{{else if b}}B {{~else if c}}C {{/if}}'),
|
|
23
|
+
block = ast.body[0],
|
|
24
|
+
bBlock = block.inverse.body[0],
|
|
25
|
+
cBlock = bBlock.inverse.body[0];
|
|
26
|
+
|
|
27
|
+
equals(block.program.body[0].value, 'A');
|
|
28
|
+
equals(bBlock.program.body[0].value, 'B'); // tilde strips trailing space from b
|
|
29
|
+
equals(cBlock.program.body[0].value, 'C '); // no tilde on {{/if}}, space preserved
|
|
30
|
+
});
|
|
20
31
|
});
|
|
21
32
|
|
|
22
33
|
describe('parseWithoutProcessing', function () {
|
|
@@ -36,6 +47,35 @@ describe('ast', function () {
|
|
|
36
47
|
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
|
|
37
48
|
});
|
|
38
49
|
});
|
|
50
|
+
|
|
51
|
+
describe('performance', function () {
|
|
52
|
+
// Each template below takes several seconds to parse, and so exceeds the
|
|
53
|
+
// test timeout, if finding trailing whitespace is not linear in its length.
|
|
54
|
+
let spaces = ' '.repeat(100000),
|
|
55
|
+
newlines = '\n'.repeat(100000);
|
|
56
|
+
|
|
57
|
+
it('should strip whitespace in linear time', function () {
|
|
58
|
+
let ast = parse(spaces + 'x' + spaces + '{{~foo}}');
|
|
59
|
+
|
|
60
|
+
equals(ast.body[0].value, spaces + 'x');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should detect standalone lines in linear time', function () {
|
|
64
|
+
let ast = parse(newlines + 'x{{foo}}');
|
|
65
|
+
equals(ast.body[0].value, newlines + 'x');
|
|
66
|
+
|
|
67
|
+
ast = parse('{{foo}}' + newlines + 'x{{foo}}');
|
|
68
|
+
equals(ast.body[1].value, newlines + 'x');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should find partial indentation in linear time', function () {
|
|
72
|
+
let ast = parse(spaces + 'x\n {{> dude}}\n');
|
|
73
|
+
|
|
74
|
+
equals(ast.body[0].value, spaces + 'x\n');
|
|
75
|
+
equals(ast.body[1].indent, ' ');
|
|
76
|
+
equals(ast.body[2].value, '');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
39
79
|
});
|
|
40
80
|
|
|
41
81
|
describe('node details', function () {
|
|
@@ -236,6 +276,20 @@ describe('ast', function () {
|
|
|
236
276
|
|
|
237
277
|
equals(ast.body[2].value, '');
|
|
238
278
|
});
|
|
279
|
+
|
|
280
|
+
it('strips close-tag indent from all chained else-if branches', function () {
|
|
281
|
+
let ast = parse(
|
|
282
|
+
' {{#if a}}\n foo\n {{else if b}}\n bar\n {{else if c}}\n baz\n {{/if}}'
|
|
283
|
+
),
|
|
284
|
+
block = ast.body[1],
|
|
285
|
+
bBlock = block.inverse.body[0],
|
|
286
|
+
cBlock = bBlock.inverse.body[0];
|
|
287
|
+
|
|
288
|
+
equals(ast.body[0].value, '');
|
|
289
|
+
equals(block.program.body[0].value, ' foo\n');
|
|
290
|
+
equals(bBlock.program.body[0].value, ' bar\n');
|
|
291
|
+
equals(cBlock.program.body[0].value, ' baz\n'); // indent before {{/if}} stripped
|
|
292
|
+
});
|
|
239
293
|
});
|
|
240
294
|
describe('partials - parseWithoutProcessing', function () {
|
|
241
295
|
it('simple partial', function () {
|
package/tsconfig.json
CHANGED