ember-estree 0.4.0 → 0.4.2
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/package.json +5 -1
- package/src/parse.js +3 -0
- package/src/transforms.js +83 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-estree",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "ESTree generator for gjs and gts file used by ember",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"AST",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@tsconfig/node-lts": "^22.0.2",
|
|
41
|
+
"mitata": "^1.0.34",
|
|
41
42
|
"oxfmt": "^0.40.0",
|
|
42
43
|
"oxlint": "^1.55.0",
|
|
43
44
|
"publint": "^0.3.18",
|
|
@@ -48,6 +49,9 @@
|
|
|
48
49
|
"scripts": {
|
|
49
50
|
"format": "oxfmt",
|
|
50
51
|
"format:check": "oxfmt --check",
|
|
52
|
+
"bench": "node --expose-gc tests/parser.bench.mjs",
|
|
53
|
+
"bench:compare": "node scripts/bench-compare.mjs",
|
|
54
|
+
"bench:summary": "./scripts/local-bench-summary.sh",
|
|
51
55
|
"lint": "oxlint && pnpm format:check && publint",
|
|
52
56
|
"lint:fix": "oxlint --fix && oxfmt",
|
|
53
57
|
"test": "vitest run"
|
package/src/parse.js
CHANGED
|
@@ -58,6 +58,9 @@ export function toTree(source, options = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
} else {
|
|
60
60
|
let filename = options.filePath || "input.ts";
|
|
61
|
+
if (filename.includes(".gts")) {
|
|
62
|
+
filename = filename.replace(/\.gts$/, ".ts");
|
|
63
|
+
}
|
|
61
64
|
let oxcResult = parseSync(filename, js);
|
|
62
65
|
result = {
|
|
63
66
|
ast: {
|
package/src/transforms.js
CHANGED
|
@@ -54,46 +54,15 @@ export const glimmerVisitorKeys = (() => {
|
|
|
54
54
|
|
|
55
55
|
// ── Internal helpers ──────────────────────────────────────────────────
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
allNodes.push(node);
|
|
64
|
-
if (node.type === "CommentStatement" || node.type === "MustacheCommentStatement") {
|
|
65
|
-
comments.push(node);
|
|
66
|
-
}
|
|
67
|
-
if (node.type === "TextNode") {
|
|
68
|
-
node.value = node.chars;
|
|
69
|
-
if (node.value.trim().length !== 0 || (parent && parent.type === "AttrNode")) {
|
|
70
|
-
textNodes.push(node);
|
|
71
|
-
} else {
|
|
72
|
-
emptyTextNodes.push(node);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const keys = rawGlimmerVisitorKeys[node.type];
|
|
76
|
-
if (!keys) return;
|
|
77
|
-
for (const key of keys) {
|
|
78
|
-
const child = node[key];
|
|
79
|
-
if (!child) continue;
|
|
80
|
-
if (Array.isArray(child)) {
|
|
81
|
-
for (const item of child) {
|
|
82
|
-
if (item && typeof item === "object" && item.type) {
|
|
83
|
-
collectNodes(item, node, allNodes, comments, textNodes, emptyTextNodes);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} else if (typeof child === "object" && child.type) {
|
|
87
|
-
collectNodes(child, node, allNodes, comments, textNodes, emptyTextNodes);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Reusable descriptor — shadows prototype getters with own properties.
|
|
93
|
-
// @glimmer/syntax nodes have getter chains that cause esrecurse infinite recursion.
|
|
57
|
+
// @glimmer/syntax nodes use prototype getters that form circular chains,
|
|
58
|
+
// crashing traversers like esrecurse. We snapshot configurable getters:
|
|
59
|
+
// ElementNode: tag, blockParams, selfClosing
|
|
60
|
+
// PathExpression: original
|
|
61
|
+
// VarHead: name, original
|
|
62
|
+
// Block: blockParams
|
|
94
63
|
const _desc = { value: undefined, configurable: true, enumerable: true, writable: true };
|
|
95
|
-
function defOwn(obj, key
|
|
96
|
-
_desc.value =
|
|
64
|
+
function defOwn(obj, key) {
|
|
65
|
+
_desc.value = obj[key];
|
|
97
66
|
Object.defineProperty(obj, key, _desc);
|
|
98
67
|
}
|
|
99
68
|
|
|
@@ -194,6 +163,10 @@ function buildTokenStream(rawTokens, comments, textNodes) {
|
|
|
194
163
|
/**
|
|
195
164
|
* Parse and transform a Glimmer template into an ESTree-compatible AST.
|
|
196
165
|
* Internal — consumed by toTree.
|
|
166
|
+
*
|
|
167
|
+
* Single recursive pass: collect, categorize, snapshot getters, fix
|
|
168
|
+
* positions, create parts/blockParamNodes, nullify empty hashes, and
|
|
169
|
+
* prefix types. No separate collect-then-transform loop.
|
|
197
170
|
*/
|
|
198
171
|
export function processTemplate(
|
|
199
172
|
templateContent,
|
|
@@ -202,7 +175,7 @@ export function processTemplate(
|
|
|
202
175
|
{ includeParentLinks = true } = {},
|
|
203
176
|
) {
|
|
204
177
|
const offset = templateRange[0];
|
|
205
|
-
const docLines = new DocumentLines(templateContent);
|
|
178
|
+
const docLines = offset === 0 ? codeLines : new DocumentLines(templateContent);
|
|
206
179
|
|
|
207
180
|
const toFileRange = (loc) => [
|
|
208
181
|
offset + docLines.positionToOffset(loc.start),
|
|
@@ -218,27 +191,67 @@ export function processTemplate(
|
|
|
218
191
|
const comments = [];
|
|
219
192
|
const textNodes = [];
|
|
220
193
|
const emptyTextNodes = [];
|
|
221
|
-
collectNodes(ast, null, allNodes, comments, textNodes, emptyTextNodes);
|
|
222
194
|
|
|
223
|
-
|
|
195
|
+
// Single recursive pass over the glimmer AST. Processes each node
|
|
196
|
+
// fully (getters, positions, parts, blockParams) then recurses into
|
|
197
|
+
// children using raw visitor keys. Type prefixing happens inline
|
|
198
|
+
// AFTER recursing (so children see the original type during lookup).
|
|
199
|
+
function visit(n, parent) {
|
|
200
|
+
n.parent = parent;
|
|
201
|
+
allNodes.push(n);
|
|
202
|
+
|
|
203
|
+
// Categorize
|
|
204
|
+
if (n.type === "CommentStatement" || n.type === "MustacheCommentStatement") {
|
|
205
|
+
comments.push(n);
|
|
206
|
+
}
|
|
207
|
+
if (n.type === "TextNode") {
|
|
208
|
+
n.value = n.chars;
|
|
209
|
+
if (n.value.trim().length !== 0 || (parent && parent.type === "AttrNode")) {
|
|
210
|
+
textNodes.push(n);
|
|
211
|
+
} else {
|
|
212
|
+
emptyTextNodes.push(n);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Snapshot configurable prototype getters
|
|
217
|
+
switch (n.type) {
|
|
218
|
+
case "ElementNode":
|
|
219
|
+
defOwn(n, "tag");
|
|
220
|
+
defOwn(n, "blockParams");
|
|
221
|
+
defOwn(n, "selfClosing");
|
|
222
|
+
if (n.path?.head) {
|
|
223
|
+
defOwn(n.path.head, "name");
|
|
224
|
+
defOwn(n.path.head, "original");
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case "PathExpression":
|
|
228
|
+
defOwn(n, "original");
|
|
229
|
+
if (n.head) {
|
|
230
|
+
defOwn(n.head, "name");
|
|
231
|
+
defOwn(n.head, "original");
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
case "Block":
|
|
235
|
+
defOwn(n, "blockParams");
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Fix positions
|
|
224
240
|
if (n.type === "PathExpression") {
|
|
225
241
|
n.head.range = toFileRange(n.head.loc);
|
|
226
242
|
n.head.start = n.head.range[0];
|
|
227
243
|
n.head.end = n.head.range[1];
|
|
228
244
|
n.head.loc = toFileLoc(n.head.range);
|
|
229
245
|
}
|
|
230
|
-
|
|
231
246
|
n.range = n.type === "Template" ? [...templateRange] : toFileRange(n.loc);
|
|
232
247
|
n.start = n.range[0];
|
|
233
248
|
n.end = n.range[1];
|
|
234
249
|
n.loc = toFileLoc(n.range);
|
|
235
250
|
|
|
251
|
+
// Create parts for ElementNode
|
|
236
252
|
if (n.type === "ElementNode") {
|
|
237
|
-
defOwn(n, "tag", n.tag);
|
|
238
253
|
n.name = n.tag;
|
|
239
254
|
const p = n.path.head;
|
|
240
|
-
defOwn(p, "name", p.name);
|
|
241
|
-
defOwn(p, "original", p.original);
|
|
242
255
|
const partRange = toFileRange(p.loc);
|
|
243
256
|
n.parts = [
|
|
244
257
|
{
|
|
@@ -254,11 +267,10 @@ export function processTemplate(
|
|
|
254
267
|
];
|
|
255
268
|
}
|
|
256
269
|
|
|
270
|
+
// Create blockParamNodes
|
|
257
271
|
if ("blockParams" in n && Array.isArray(n.blockParams)) {
|
|
258
272
|
if (n.params && n.params.length === n.blockParams.length) {
|
|
259
273
|
n.blockParamNodes = n.params.map((p) => {
|
|
260
|
-
defOwn(p, "name", p.name);
|
|
261
|
-
defOwn(p, "original", p.original);
|
|
262
274
|
const range = toFileRange(p.loc);
|
|
263
275
|
return {
|
|
264
276
|
type: "GlimmerBlockParam",
|
|
@@ -272,9 +284,9 @@ export function processTemplate(
|
|
|
272
284
|
};
|
|
273
285
|
});
|
|
274
286
|
} else {
|
|
275
|
-
n.blockParamNodes = n.blockParams.map((
|
|
287
|
+
n.blockParamNodes = n.blockParams.map((bpName) => ({
|
|
276
288
|
type: "GlimmerBlockParam",
|
|
277
|
-
name,
|
|
289
|
+
name: bpName,
|
|
278
290
|
parent: n,
|
|
279
291
|
range: [n.range[0], n.range[1]],
|
|
280
292
|
start: n.range[0],
|
|
@@ -284,26 +296,38 @@ export function processTemplate(
|
|
|
284
296
|
}
|
|
285
297
|
}
|
|
286
298
|
|
|
299
|
+
// Nullify empty hashes
|
|
287
300
|
if (
|
|
288
301
|
(n.type === "MustacheStatement" ||
|
|
289
302
|
n.type === "BlockStatement" ||
|
|
290
303
|
n.type === "SubExpression") &&
|
|
291
|
-
n.hash
|
|
292
|
-
n.hash.pairs &&
|
|
293
|
-
n.hash.pairs.length === 0
|
|
304
|
+
n.hash?.pairs?.length === 0
|
|
294
305
|
) {
|
|
295
306
|
n.hash = null;
|
|
296
307
|
}
|
|
297
308
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
// Recurse into children BEFORE prefixing type (visitor keys use original type)
|
|
310
|
+
const keys = rawGlimmerVisitorKeys[n.type];
|
|
311
|
+
if (keys) {
|
|
312
|
+
for (const key of keys) {
|
|
313
|
+
const child = n[key];
|
|
314
|
+
if (!child) continue;
|
|
315
|
+
if (Array.isArray(child)) {
|
|
316
|
+
for (const item of child) {
|
|
317
|
+
if (item && typeof item === "object" && item.type) visit(item, n);
|
|
318
|
+
}
|
|
319
|
+
} else if (typeof child === "object" && child.type) {
|
|
320
|
+
visit(child, n);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
304
323
|
}
|
|
324
|
+
|
|
325
|
+
// Prefix type after children are visited
|
|
326
|
+
n.type = `Glimmer${n.type}`;
|
|
305
327
|
}
|
|
306
328
|
|
|
329
|
+
visit(ast, null);
|
|
330
|
+
|
|
307
331
|
removeFromParent(emptyTextNodes);
|
|
308
332
|
removeFromParent(comments);
|
|
309
333
|
for (const comment of comments) {
|