ember-estree 0.6.1 → 0.6.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/README.md CHANGED
@@ -190,6 +190,220 @@ The [`examples/`](./examples) directory contains ready-to-run integrations:
190
190
  | [`eslint-parser`](./examples/eslint-parser) | Custom ESLint parser that understands `<template>` |
191
191
  | [`zmod`](./examples/zmod) | Codemod toolkit using [zmod](https://github.com/nicolo-ribaudo/zmod) |
192
192
 
193
+ <!-- ast-nodes:start -->
194
+ <!-- Generated by scripts/generate-ast-node-reference.mjs — do not edit by hand. -->
195
+
196
+ ## AST node reference
197
+
198
+ <details>
199
+ <summary><strong>Every AST node</strong> ember-estree may emit (171 total) — grouped by which files they appear in</summary>
200
+
201
+ Generated from `oxc-parser`'s and `@glimmer/syntax`'s visitor-key maps. Re-run `node scripts/generate-ast-node-reference.mjs` after bumping either dependency to keep this in sync.
202
+
203
+ <details>
204
+ <summary><strong>Core ESTree</strong> — in <code>.gjs</code> and <code>.gts</code> (76 nodes)</summary>
205
+
206
+ Standard JavaScript node types. Present in both `.gjs` and `.gts` — TypeScript is a superset of JavaScript, so `.gts` files may contain all of these too.
207
+
208
+ | Node | Child keys |
209
+ | -------------------------- | ---------------------------------------------------------------------------------------------- |
210
+ | `AccessorProperty` | `decorators`, `key`, `typeAnnotation`, `value` |
211
+ | `ArrayExpression` | `elements` |
212
+ | `ArrayPattern` | `decorators`, `elements`, `typeAnnotation` |
213
+ | `ArrowFunctionExpression` | `typeParameters`, `params`, `returnType`, `body` |
214
+ | `AssignmentExpression` | `left`, `right` |
215
+ | `AssignmentPattern` | `decorators`, `left`, `right`, `typeAnnotation` |
216
+ | `AwaitExpression` | `argument` |
217
+ | `BinaryExpression` | `left`, `right` |
218
+ | `BlockStatement` | `body` |
219
+ | `BreakStatement` | `label` |
220
+ | `CallExpression` | `callee`, `typeArguments`, `arguments` |
221
+ | `CatchClause` | `param`, `body` |
222
+ | `ChainExpression` | `expression` |
223
+ | `ClassBody` | `body` |
224
+ | `ClassDeclaration` | `decorators`, `id`, `typeParameters`, `superClass`, `superTypeArguments`, `implements`, `body` |
225
+ | `ClassExpression` | `decorators`, `id`, `typeParameters`, `superClass`, `superTypeArguments`, `implements`, `body` |
226
+ | `ConditionalExpression` | `test`, `consequent`, `alternate` |
227
+ | `ContinueStatement` | `label` |
228
+ | `DebuggerStatement` | _(leaf)_ |
229
+ | `Decorator` | `expression` |
230
+ | `DoWhileStatement` | `body`, `test` |
231
+ | `EmptyStatement` | _(leaf)_ |
232
+ | `ExportAllDeclaration` | `exported`, `source`, `attributes` |
233
+ | `ExportDefaultDeclaration` | `declaration` |
234
+ | `ExportNamedDeclaration` | `declaration`, `specifiers`, `source`, `attributes` |
235
+ | `ExportSpecifier` | `local`, `exported` |
236
+ | `ExpressionStatement` | `expression` |
237
+ | `ForInStatement` | `left`, `right`, `body` |
238
+ | `ForOfStatement` | `left`, `right`, `body` |
239
+ | `ForStatement` | `init`, `test`, `update`, `body` |
240
+ | `FunctionDeclaration` | `id`, `typeParameters`, `params`, `returnType`, `body` |
241
+ | `FunctionExpression` | `id`, `typeParameters`, `params`, `returnType`, `body` |
242
+ | `Identifier` | `decorators`, `typeAnnotation` |
243
+ | `IfStatement` | `test`, `consequent`, `alternate` |
244
+ | `ImportAttribute` | `key`, `value` |
245
+ | `ImportDeclaration` | `specifiers`, `source`, `attributes` |
246
+ | `ImportDefaultSpecifier` | `local` |
247
+ | `ImportExpression` | `source`, `options` |
248
+ | `ImportNamespaceSpecifier` | `local` |
249
+ | `ImportSpecifier` | `imported`, `local` |
250
+ | `LabeledStatement` | `label`, `body` |
251
+ | `Literal` | _(leaf)_ |
252
+ | `LogicalExpression` | `left`, `right` |
253
+ | `MemberExpression` | `object`, `property` |
254
+ | `MetaProperty` | `meta`, `property` |
255
+ | `MethodDefinition` | `decorators`, `key`, `value` |
256
+ | `NewExpression` | `callee`, `typeArguments`, `arguments` |
257
+ | `ObjectExpression` | `properties` |
258
+ | `ObjectPattern` | `decorators`, `properties`, `typeAnnotation` |
259
+ | `ParenthesizedExpression` | `expression` |
260
+ | `PrivateIdentifier` | _(leaf)_ |
261
+ | `Program` | `body` |
262
+ | `Property` | `key`, `value` |
263
+ | `PropertyDefinition` | `decorators`, `key`, `typeAnnotation`, `value` |
264
+ | `RestElement` | `decorators`, `argument`, `typeAnnotation` |
265
+ | `ReturnStatement` | `argument` |
266
+ | `SequenceExpression` | `expressions` |
267
+ | `SpreadElement` | `argument` |
268
+ | `StaticBlock` | `body` |
269
+ | `Super` | _(leaf)_ |
270
+ | `SwitchCase` | `test`, `consequent` |
271
+ | `SwitchStatement` | `discriminant`, `cases` |
272
+ | `TaggedTemplateExpression` | `tag`, `typeArguments`, `quasi` |
273
+ | `TemplateElement` | _(leaf)_ |
274
+ | `TemplateLiteral` | `quasis`, `expressions` |
275
+ | `ThisExpression` | _(leaf)_ |
276
+ | `ThrowStatement` | `argument` |
277
+ | `TryStatement` | `block`, `handler`, `finalizer` |
278
+ | `UnaryExpression` | `argument` |
279
+ | `UpdateExpression` | `argument` |
280
+ | `V8IntrinsicExpression` | `name`, `arguments` |
281
+ | `VariableDeclaration` | `declarations` |
282
+ | `VariableDeclarator` | `id`, `init` |
283
+ | `WhileStatement` | `test`, `body` |
284
+ | `WithStatement` | `object`, `body` |
285
+ | `YieldExpression` | `argument` |
286
+
287
+ </details>
288
+
289
+ <details>
290
+ <summary><strong>TypeScript</strong> — <code>.gts</code> only (74 nodes)</summary>
291
+
292
+ TypeScript-specific nodes. Can only appear in `.gts` files.
293
+
294
+ | Node | Child keys |
295
+ | --------------------------------- | ------------------------------------------------------ |
296
+ | `TSAbstractAccessorProperty` | `decorators`, `key`, `typeAnnotation` |
297
+ | `TSAbstractMethodDefinition` | `key`, `value` |
298
+ | `TSAbstractPropertyDefinition` | `decorators`, `key`, `typeAnnotation` |
299
+ | `TSAnyKeyword` | _(leaf)_ |
300
+ | `TSArrayType` | `elementType` |
301
+ | `TSAsExpression` | `expression`, `typeAnnotation` |
302
+ | `TSBigIntKeyword` | _(leaf)_ |
303
+ | `TSBooleanKeyword` | _(leaf)_ |
304
+ | `TSCallSignatureDeclaration` | `typeParameters`, `params`, `returnType` |
305
+ | `TSClassImplements` | `expression`, `typeArguments` |
306
+ | `TSConditionalType` | `checkType`, `extendsType`, `trueType`, `falseType` |
307
+ | `TSConstructorType` | `typeParameters`, `params`, `returnType` |
308
+ | `TSConstructSignatureDeclaration` | `typeParameters`, `params`, `returnType` |
309
+ | `TSDeclareFunction` | `id`, `typeParameters`, `params`, `returnType`, `body` |
310
+ | `TSEmptyBodyFunctionExpression` | `id`, `typeParameters`, `params`, `returnType` |
311
+ | `TSEnumBody` | `members` |
312
+ | `TSEnumDeclaration` | `id`, `body` |
313
+ | `TSEnumMember` | `id`, `initializer` |
314
+ | `TSExportAssignment` | `expression` |
315
+ | `TSExternalModuleReference` | `expression` |
316
+ | `TSFunctionType` | `typeParameters`, `params`, `returnType` |
317
+ | `TSImportEqualsDeclaration` | `id`, `moduleReference` |
318
+ | `TSImportType` | `source`, `options`, `qualifier`, `typeArguments` |
319
+ | `TSIndexedAccessType` | `objectType`, `indexType` |
320
+ | `TSIndexSignature` | `parameters`, `typeAnnotation` |
321
+ | `TSInferType` | `typeParameter` |
322
+ | `TSInstantiationExpression` | `expression`, `typeArguments` |
323
+ | `TSInterfaceBody` | `body` |
324
+ | `TSInterfaceDeclaration` | `id`, `typeParameters`, `extends`, `body` |
325
+ | `TSInterfaceHeritage` | `expression`, `typeArguments` |
326
+ | `TSIntersectionType` | `types` |
327
+ | `TSIntrinsicKeyword` | _(leaf)_ |
328
+ | `TSJSDocNonNullableType` | `typeAnnotation` |
329
+ | `TSJSDocNullableType` | `typeAnnotation` |
330
+ | `TSJSDocUnknownType` | _(leaf)_ |
331
+ | `TSLiteralType` | `literal` |
332
+ | `TSMappedType` | `key`, `constraint`, `nameType`, `typeAnnotation` |
333
+ | `TSMethodSignature` | `key`, `typeParameters`, `params`, `returnType` |
334
+ | `TSModuleBlock` | `body` |
335
+ | `TSModuleDeclaration` | `id`, `body` |
336
+ | `TSNamedTupleMember` | `label`, `elementType` |
337
+ | `TSNamespaceExportDeclaration` | `id` |
338
+ | `TSNeverKeyword` | _(leaf)_ |
339
+ | `TSNonNullExpression` | `expression` |
340
+ | `TSNullKeyword` | _(leaf)_ |
341
+ | `TSNumberKeyword` | _(leaf)_ |
342
+ | `TSObjectKeyword` | _(leaf)_ |
343
+ | `TSOptionalType` | `typeAnnotation` |
344
+ | `TSParameterProperty` | `decorators`, `parameter` |
345
+ | `TSParenthesizedType` | `typeAnnotation` |
346
+ | `TSPropertySignature` | `key`, `typeAnnotation` |
347
+ | `TSQualifiedName` | `left`, `right` |
348
+ | `TSRestType` | `typeAnnotation` |
349
+ | `TSSatisfiesExpression` | `expression`, `typeAnnotation` |
350
+ | `TSStringKeyword` | _(leaf)_ |
351
+ | `TSSymbolKeyword` | _(leaf)_ |
352
+ | `TSTemplateLiteralType` | `quasis`, `types` |
353
+ | `TSThisType` | _(leaf)_ |
354
+ | `TSTupleType` | `elementTypes` |
355
+ | `TSTypeAliasDeclaration` | `id`, `typeParameters`, `typeAnnotation` |
356
+ | `TSTypeAnnotation` | `typeAnnotation` |
357
+ | `TSTypeAssertion` | `typeAnnotation`, `expression` |
358
+ | `TSTypeLiteral` | `members` |
359
+ | `TSTypeOperator` | `typeAnnotation` |
360
+ | `TSTypeParameter` | `name`, `constraint`, `default` |
361
+ | `TSTypeParameterDeclaration` | `params` |
362
+ | `TSTypeParameterInstantiation` | `params` |
363
+ | `TSTypePredicate` | `parameterName`, `typeAnnotation` |
364
+ | `TSTypeQuery` | `exprName`, `typeArguments` |
365
+ | `TSTypeReference` | `typeName`, `typeArguments` |
366
+ | `TSUndefinedKeyword` | _(leaf)_ |
367
+ | `TSUnionType` | `types` |
368
+ | `TSUnknownKeyword` | _(leaf)_ |
369
+ | `TSVoidKeyword` | _(leaf)_ |
370
+
371
+ </details>
372
+
373
+ <details>
374
+ <summary><strong>Glimmer template</strong> — in <code>.gjs</code> and <code>.gts</code> (21 nodes)</summary>
375
+
376
+ Nodes produced inside `<template>...</template>` regions by `@glimmer/syntax`, prefixed with `Glimmer` when spliced into the ESTree.
377
+
378
+ | Node | Child keys |
379
+ | --------------------------------- | ----------------------------------------------------------------------------- |
380
+ | `GlimmerAttrNode` | `value` |
381
+ | `GlimmerBlock` | `body` |
382
+ | `GlimmerBlockStatement` | `path`, `params`, `hash`, `program`, `inverse` |
383
+ | `GlimmerBooleanLiteral` | _(leaf)_ |
384
+ | `GlimmerCommentStatement` | _(leaf)_ |
385
+ | `GlimmerConcatStatement` | `parts` |
386
+ | `GlimmerElementModifierStatement` | `path`, `params`, `hash` |
387
+ | `GlimmerElementNode` | `attributes`, `modifiers`, `children`, `comments`, `blockParamNodes`, `parts` |
388
+ | `GlimmerHash` | `pairs` |
389
+ | `GlimmerHashPair` | `value` |
390
+ | `GlimmerMustacheCommentStatement` | _(leaf)_ |
391
+ | `GlimmerMustacheStatement` | `path`, `params`, `hash` |
392
+ | `GlimmerNullLiteral` | _(leaf)_ |
393
+ | `GlimmerNumberLiteral` | _(leaf)_ |
394
+ | `GlimmerPathExpression` | _(leaf)_ |
395
+ | `GlimmerProgram` | `body`, `blockParamNodes` |
396
+ | `GlimmerStringLiteral` | _(leaf)_ |
397
+ | `GlimmerSubExpression` | `path`, `params`, `hash` |
398
+ | `GlimmerTemplate` | `body` |
399
+ | `GlimmerTextNode` | _(leaf)_ |
400
+ | `GlimmerUndefinedLiteral` | _(leaf)_ |
401
+
402
+ </details>
403
+
404
+ </details>
405
+ <!-- ast-nodes:end -->
406
+
193
407
  ## License
194
408
 
195
409
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-estree",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "ESTree generator for gjs and gts file used by ember",
5
5
  "keywords": [
6
6
  "AST",
@@ -33,8 +33,7 @@
33
33
  "@glimmer/env": "^0.1.7",
34
34
  "@glimmer/syntax": "^0.95.0",
35
35
  "content-tag": "^4.1.0",
36
- "oxc-parser": "^0.119.0",
37
- "zimmerframe": "^1.1.4"
36
+ "oxc-parser": "^0.119.0"
38
37
  },
39
38
  "devDependencies": {
40
39
  "@tsconfig/node-lts": "^22.0.2",
@@ -45,12 +44,14 @@
45
44
  "publint": "^0.3.18",
46
45
  "release-plan": "^0.18.0",
47
46
  "typescript": "^5.9.3",
48
- "vitest": "^3.2.4"
47
+ "vitest": "^3.2.4",
48
+ "zimmerframe": "^1.1.4"
49
49
  },
50
50
  "scripts": {
51
51
  "bench": "node --expose-gc tests/parser.bench.mjs",
52
52
  "bench:compare": "node scripts/bench-compare.mjs",
53
53
  "bench:summary": "./scripts/local-bench-summary.sh",
54
+ "docs:nodes": "node scripts/generate-ast-node-reference.mjs",
54
55
  "format": "oxfmt",
55
56
  "format:check": "oxfmt --check",
56
57
  "lint": "oxlint && pnpm format:check && publint",
package/src/parse.js CHANGED
@@ -9,12 +9,24 @@
9
9
  * 6. Done
10
10
  */
11
11
 
12
- import { parseSync } from "oxc-parser";
12
+ import { parseSync, visitorKeys as oxcVisitorKeys } from "oxc-parser";
13
13
  import { Preprocessor } from "content-tag";
14
- import { walk } from "zimmerframe";
15
14
 
16
15
  import { processTemplate, DocumentLines, glimmerVisitorKeys, setParent } from "./transforms.js";
17
16
 
17
+ // Base visitor-keys map for the outer-AST walk: oxc-parser's own keys (covers
18
+ // standard ESTree + TS), plus the `File` wrapper we add on the default path,
19
+ // plus Glimmer's keys. Used to iterate only declared child slots instead of
20
+ // every enumerable property on every node.
21
+ //
22
+ // When `options.parser` returns `visitorKeys`, callers merge on top — but if
23
+ // their parser's AST is oxc-compatible, this base is already sufficient.
24
+ const DEFAULT_VISITOR_KEYS = {
25
+ ...oxcVisitorKeys,
26
+ File: ["program"],
27
+ ...glimmerVisitorKeys,
28
+ };
29
+
18
30
  // Swap `oldNode` for `newNode` in whichever slot of `parent` currently holds it.
19
31
  // Used to splice a GlimmerTemplate directly into the outer AST without
20
32
  // allocating new ancestor objects — keeps WeakMap-keyed data (scope manager,
@@ -55,7 +67,9 @@ const PLACEHOLDER_TYPES = new Set([
55
67
  * @param {string} [options.filePath] - File path for language detection
56
68
  * @param {boolean} [options.tokens] - Generate a flat token stream on the AST (needed by ESLint; skipped by default)
57
69
  * @param {boolean} [options.templateOnly] - Parse as raw Glimmer template content (for .hbs)
58
- * @param {function} [options.parser] - Custom JS/TS parser: (placeholderJS) => { ast, scopeManager?, visitorKeys?, services?, ... }
70
+ * @param {function} [options.parser] - Custom JS/TS parser: (placeholderJS) => { ast, scopeManager?, visitorKeys?, services?, ... }.
71
+ * Recommended to return `visitorKeys` describing the parser's AST; when omitted, oxc-parser's
72
+ * keys are used (fine for oxc-compatible ASTs, incomplete for parsers that emit bespoke node types).
59
73
  * @param {object|function} [options.visitors] - Either a map of `{ [Type]: (node, path) => void }`
60
74
  * handlers, or a factory `(outerAst) => handlers` invoked once after parsing (before any
61
75
  * template splicing) to give callers a view of the raw JS/TS tree. Handlers fire on every
@@ -206,49 +220,66 @@ export function toTree(source, options = {}) {
206
220
  return parseResult;
207
221
  }
208
222
 
209
- result.ast = walk(result.ast, null, {
210
- _(node, { next, visit, state }) {
211
- if (hasTemplates && PLACEHOLDER_TYPES.has(node.type)) {
212
- const parseResult = matchPlaceholder(node);
213
- if (parseResult) {
214
- const ast = processPlaceholder(parseResult, node);
215
- // Splice in place: write the GlimmerTemplate directly into the
216
- // parent's slot instead of returning it from the visitor. Returning
217
- // would trigger zimmerframe's apply_mutations, which shallow-clones
218
- // every ancestor up to the root — orphaning any WeakMap-keyed data
219
- // held by custom parsers (scope manager, esTreeNodeToTSNodeMap).
220
- // In-place mutation preserves node identity for all ancestors.
221
- const parent = state?.parentPath?.node ?? null;
222
- if (parent) replaceInParent(parent, node, ast);
223
- setParent(ast, parent);
224
- // Dispatch visitors on the Glimmer subtree. We pass `state` so the
225
- // Glimmer root's parentPath reflects its true JS parent the
226
- // placeholder (TemplateLiteral / StaticBlock) is an internal
227
- // artifact. Not returning anything keeps apply_mutations from
228
- // firing up the ancestor chain.
229
- if (hasVisitors) visit(ast, state);
230
- return;
231
- }
223
+ // Walk the outer AST keyed on visitorKeys — iterating only declared child
224
+ // slots instead of every enumerable property on every node. Custom parsers
225
+ // may supply their own keys; those override the defaults for types they
226
+ // recognise, and Glimmer keys stay on top for the spliced subtrees.
227
+ const allVisitorKeys =
228
+ useCustomParser && result.visitorKeys
229
+ ? { ...DEFAULT_VISITOR_KEYS, ...result.visitorKeys, ...glimmerVisitorKeys }
230
+ : DEFAULT_VISITOR_KEYS;
231
+
232
+ function walkWithKeys(node, parentPath) {
233
+ if (!node || !node.type) return;
234
+
235
+ if (hasTemplates && PLACEHOLDER_TYPES.has(node.type)) {
236
+ const parseResult = matchPlaceholder(node);
237
+ if (parseResult) {
238
+ // Splice in place: write the GlimmerTemplate directly into the parent's
239
+ // slot instead of allocating new ancestor objects. This preserves node
240
+ // identity for every ancestor, which matters for WeakMap-keyed data
241
+ // held by custom parsers (scope manager, esTreeNodeToTSNodeMap).
242
+ const ast = processPlaceholder(parseResult, node);
243
+ const parent = parentPath?.node ?? null;
244
+ if (parent) replaceInParent(parent, node, ast);
245
+ setParent(ast, parent);
246
+ // Recurse into the Glimmer subtree so visitors fire on its nodes too.
247
+ // The Glimmer root's parentPath reflects its true JS parent — the
248
+ // placeholder (TemplateLiteral / StaticBlock) is an internal artifact.
249
+ if (hasVisitors) walkWithKeys(ast, parentPath);
250
+ return;
232
251
  }
252
+ }
233
253
 
234
- const path = {
235
- node,
236
- parent: state?.parentPath?.node ?? null,
237
- parentPath: state?.parentPath ?? null,
238
- };
239
-
240
- if (hasVisitors && !seen.has(node)) {
241
- seen.add(node);
242
- const handler = visitors[node.type];
243
- if (handler) handler(node, path);
244
- if ("blockParams" in node && visitors.GlimmerBlockParams) {
245
- visitors.GlimmerBlockParams(node, path);
254
+ const path = { node, parent: parentPath?.node ?? null, parentPath };
255
+
256
+ if (hasVisitors && !seen.has(node)) {
257
+ seen.add(node);
258
+ const handler = visitors[node.type];
259
+ if (handler) handler(node, path);
260
+ if ("blockParams" in node && visitors.GlimmerBlockParams) {
261
+ visitors.GlimmerBlockParams(node, path);
262
+ }
263
+ }
264
+
265
+ const keys = allVisitorKeys[node.type];
266
+ if (!keys) return;
267
+ for (const key of keys) {
268
+ const child = node[key];
269
+ if (!child) continue;
270
+ if (Array.isArray(child)) {
271
+ for (const item of child) {
272
+ if (item && typeof item === "object" && item.type) {
273
+ walkWithKeys(item, path);
274
+ }
246
275
  }
276
+ } else if (typeof child === "object" && child.type) {
277
+ walkWithKeys(child, path);
247
278
  }
279
+ }
280
+ }
248
281
 
249
- next({ parentPath: path });
250
- },
251
- });
282
+ walkWithKeys(result.ast, null);
252
283
 
253
284
  // Splice template tokens into the AST token stream.
254
285
  //
package/src/transforms.js CHANGED
@@ -256,6 +256,15 @@ export function processTemplate(templateContent, codeLines, options = {}) {
256
256
  removeFromParent(emptyTextNodes);
257
257
 
258
258
  if (generateTokens) {
259
+ // buildTokenStream walks comments and textNodes as sorted intervals via
260
+ // pointer advancement. AST-traversal order doesn't match source order
261
+ // when an element has both attribute-position and body-position
262
+ // MustacheCommentStatements (e.g. `<li {{! a }}>{{! b }}</li>`), so
263
+ // sort here. Without the sort, raw tokens that fall inside an earlier-
264
+ // source-position comment fail the skip check, the same span ends up
265
+ // tokenized twice (raw punctuators *and* a Block), and downstream
266
+ // ESLint token walks can infinite-loop on the non-monotonic stream.
267
+ comments.sort((a, b) => a.range[0] - b.range[0]);
259
268
  ast.tokens = buildTokenStream(
260
269
  tokenize(templateContent, codeLines, offset),
261
270
  comments,