ESTree-compatible AST parser for Ember's .gjs and .gts files.
Parses <template> tags into Glimmer AST nodes that are embedded directly in the ESTree, so tools like linters and codemods can work with both the JavaScript/TypeScript and template portions of a single file.
pnpm add ember-estreetoTree returns a File node whose .program is a standard ESTree Program, with any <template> regions represented as Glimmer* AST nodes.
import { toTree } from "ember-estree";
let ast = toTree(`
import Component from "@glimmer/component";
export default class Demo extends Component {
<template>Hello, {{this.name}}!</template>
}
`);
console.log(ast.type); // "File"
console.log(ast.program.body.length); // 2 — ImportDeclaration + ClassDeclarationparse is a lower-level alternative that returns the Program node directly.
import { parse } from "ember-estree";
let program = parse(`const x = <template>hi</template>;`);
console.log(program.type); // "Program"print converts an AST node (ESTree or Glimmer) back to source code.
import { print } from "ember-estree";
print({ type: "Identifier", name: "foo" });
// => "foo"
print({
type: "GlimmerTemplate",
body: [{ type: "GlimmerTextNode", chars: "Hello" }],
});
// => "<template>Hello</template>"print also accepts the File node returned by toTree, printing the whole program with file.comments woven back into the output (each comment is emitted before the nearest node that follows it in the original source). Placement is approximate — a trailing same-line comment becomes a leading comment of the next node — so pair the output with a formatter (e.g. prettier) when exact layout matters.
import { toTree, print } from "ember-estree";
let tree = toTree(`// greet the user\nlet greeting = "hello";`);
print(tree);
// => '// greet the user\nlet greeting = "hello";'statement and statements are tagged templates that build AST from source text — like @babel/template, but gjs/gts-aware: <template> regions become Glimmer* nodes, and TypeScript syntax is understood.
import { statement, statements } from "ember-estree";
statement`import setup from "some-package";`;
// => ImportDeclaration
statements`
const greeting = "hello";
<template>{{greeting}}</template>
`;
// => [VariableDeclaration, GlimmerTemplate]Interpolations may be strings (inserted verbatim), AST nodes (printed), or arrays of either (comma-separated):
let local = "setupInspector";
let callee = { type: "Identifier", name: local };
statement`import ${local} from "some-package";`;
statement`let inspector = ${callee}(this);`;Built nodes carry no source positions, so they can be spliced anywhere into a tree returned by toTree and printed — comment weaving in the host file is unaffected:
import { toTree, print, statement } from "ember-estree";
let tree = toTree(source);
tree.program.body.unshift(statement`import "./styles.css";`);
print(tree);For node kinds that are not statements (class members, object properties, ...), build the smallest statement containing one and pluck it out:
let member = statement`class _ { inspector = setup(this); }`.body.body[0];
// => PropertyDefinitionBoth toTree and parse accept an options object as their second argument.
All options are optional.
| Option | Type | Description |
|---|---|---|
filePath |
string |
Used for language detection. |
tokens |
boolean |
Generate a flat ast.tokens array. Required by ESLint; skipped by default so codemods and type-checkers pay nothing. |
templateOnly |
boolean |
Parse the source as a raw Glimmer template. Use for .hbs files. |
parser |
(placeholderJS: string) => { ast, ... } |
Use a custom JS/TS parser instead of the default oxc-parser. See Custom parser. |
visitors |
VisitorMap or (outerAst) => VisitorMap |
Callbacks fired on every node during traversal — JS/TS and Glimmer — in a single pass. See Visitors. |
Handler signature is (node, path) => void, where path = { node, parent, parentPath } — a linked list that walks all the way back through the JS/TS root, so visitors can locate the enclosing scope or class from within a Glimmer subtree.
Pass tokens: true to populate ast.tokens with a flat, position-sorted array of lexemes spanning the full file — including Glimmer tokens spliced in place of each <template> region. This is what ESLint's SourceCode needs; omit it for codemods or type-checkers that don't use the token stream.
import { toTree } from "ember-estree";
const result = toTree(source, {
tokens: true,
parser: myTsParser,
});
// result.ast.program.tokens now contains JS + Glimmer tokens in source orderFor .hbs files via templateOnly, pass both flags:
toTree(hbsSource, { templateOnly: true, tokens: true });Pass any JS/TS parser that returns an ESTree-compatible AST. ember-estree handles template splicing and Glimmer traversal on top of it.
import { parseSync } from "oxc-parser";
import { toTree } from "ember-estree";
const result = toTree(source, {
parser: (js) => ({
ast: parseSync("input.ts", js).program,
visitorKeys: {
/* ...parser's visitor keys... */
},
}),
});The parser receives a placeholder-JS string (templates replaced with backtick expressions of equal length) and must return at least { ast }. Additional fields like scopeManager, visitorKeys, or services are preserved on the returned result.
Pass visitors to observe or rewrite the tree in a single traversal. Handlers fire on both outer JS/TS nodes and spliced Glimmer subtrees, and a single node is never dispatched twice — safe to relocate nodes mid-walk.
The pseudo-type GlimmerBlockParams fires on any node that carries a blockParams array.
Plain-object form — use when you only need the type → handler map:
import { toTree } from "ember-estree";
const identifiers = [];
toTree(source, {
visitors: {
Identifier: (node) => identifiers.push(node.name),
GlimmerPathExpression: (node) => identifiers.push(node.original),
},
});Factory form — use when you need the outer JS/TS AST up front (for example, to attach state to it before the walk):
import { toTree, print } from "ember-estree";
const ast = toTree(`const world = "🌍"; const X = <template>{{world}}</template>;`, {
visitors: () => ({
Identifier: (node) => (node.name = node.name.toUpperCase()),
GlimmerPathExpression(node) {
node.original = node.original.toUpperCase();
if (node.head) node.head.name = node.original;
},
}),
});
print(ast.program);
// => 'const WORLD = "🌍";\nconst X = <template>{{WORLD}}</template>;'Collecting Glimmer comments into program.comments — useful when adapting the AST for ESLint, which reads comments from the Program node:
const ast = toTree(source, {
visitors: (outerAst) => {
outerAst.program.comments = [...(outerAst.comments ?? [])];
const push = (node) => outerAst.program.comments.push(node);
return {
GlimmerCommentStatement: push,
GlimmerMustacheCommentStatement: push,
};
},
});Removing nodes mid-traversal — siblings are splice-safe:
toTree(source, {
visitors: () => ({
GlimmerMustacheCommentStatement(node, path) {
const siblings = path.parent?.body ?? path.parent?.children;
const idx = siblings?.indexOf(node) ?? -1;
if (idx >= 0) siblings.splice(idx, 1);
},
}),
});The examples/ directory contains ready-to-run integrations:
| Example | Description |
|---|---|
eslint-parser |
Custom ESLint parser that understands <template> |
zmod |
Codemod toolkit using zmod |
Every AST node ember-estree may emit (171 total) — grouped by which files they appear in
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.
Core ESTree — in .gjs and .gts (76 nodes)
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.
| Node | Child keys |
|---|---|
AccessorProperty |
decorators, key, typeAnnotation, value |
ArrayExpression |
elements |
ArrayPattern |
decorators, elements, typeAnnotation |
ArrowFunctionExpression |
typeParameters, params, returnType, body |
AssignmentExpression |
left, right |
AssignmentPattern |
decorators, left, right, typeAnnotation |
AwaitExpression |
argument |
BinaryExpression |
left, right |
BlockStatement |
body |
BreakStatement |
label |
CallExpression |
callee, typeArguments, arguments |
CatchClause |
param, body |
ChainExpression |
expression |
ClassBody |
body |
ClassDeclaration |
decorators, id, typeParameters, superClass, superTypeArguments, implements, body |
ClassExpression |
decorators, id, typeParameters, superClass, superTypeArguments, implements, body |
ConditionalExpression |
test, consequent, alternate |
ContinueStatement |
label |
DebuggerStatement |
(leaf) |
Decorator |
expression |
DoWhileStatement |
body, test |
EmptyStatement |
(leaf) |
ExportAllDeclaration |
exported, source, attributes |
ExportDefaultDeclaration |
declaration |
ExportNamedDeclaration |
declaration, specifiers, source, attributes |
ExportSpecifier |
local, exported |
ExpressionStatement |
expression |
ForInStatement |
left, right, body |
ForOfStatement |
left, right, body |
ForStatement |
init, test, update, body |
FunctionDeclaration |
id, typeParameters, params, returnType, body |
FunctionExpression |
id, typeParameters, params, returnType, body |
Identifier |
decorators, typeAnnotation |
IfStatement |
test, consequent, alternate |
ImportAttribute |
key, value |
ImportDeclaration |
specifiers, source, attributes |
ImportDefaultSpecifier |
local |
ImportExpression |
source, options |
ImportNamespaceSpecifier |
local |
ImportSpecifier |
imported, local |
LabeledStatement |
label, body |
Literal |
(leaf) |
LogicalExpression |
left, right |
MemberExpression |
object, property |
MetaProperty |
meta, property |
MethodDefinition |
decorators, key, value |
NewExpression |
callee, typeArguments, arguments |
ObjectExpression |
properties |
ObjectPattern |
decorators, properties, typeAnnotation |
ParenthesizedExpression |
expression |
PrivateIdentifier |
(leaf) |
Program |
body |
Property |
key, value |
PropertyDefinition |
decorators, key, typeAnnotation, value |
RestElement |
decorators, argument, typeAnnotation |
ReturnStatement |
argument |
SequenceExpression |
expressions |
SpreadElement |
argument |
StaticBlock |
body |
Super |
(leaf) |
SwitchCase |
test, consequent |
SwitchStatement |
discriminant, cases |
TaggedTemplateExpression |
tag, typeArguments, quasi |
TemplateElement |
(leaf) |
TemplateLiteral |
quasis, expressions |
ThisExpression |
(leaf) |
ThrowStatement |
argument |
TryStatement |
block, handler, finalizer |
UnaryExpression |
argument |
UpdateExpression |
argument |
V8IntrinsicExpression |
name, arguments |
VariableDeclaration |
declarations |
VariableDeclarator |
id, init |
WhileStatement |
test, body |
WithStatement |
object, body |
YieldExpression |
argument |
TypeScript — .gts only (74 nodes)
TypeScript-specific nodes. Can only appear in .gts files.
| Node | Child keys |
|---|---|
TSAbstractAccessorProperty |
decorators, key, typeAnnotation |
TSAbstractMethodDefinition |
key, value |
TSAbstractPropertyDefinition |
decorators, key, typeAnnotation |
TSAnyKeyword |
(leaf) |
TSArrayType |
elementType |
TSAsExpression |
expression, typeAnnotation |
TSBigIntKeyword |
(leaf) |
TSBooleanKeyword |
(leaf) |
TSCallSignatureDeclaration |
typeParameters, params, returnType |
TSClassImplements |
expression, typeArguments |
TSConditionalType |
checkType, extendsType, trueType, falseType |
TSConstructorType |
typeParameters, params, returnType |
TSConstructSignatureDeclaration |
typeParameters, params, returnType |
TSDeclareFunction |
id, typeParameters, params, returnType, body |
TSEmptyBodyFunctionExpression |
id, typeParameters, params, returnType |
TSEnumBody |
members |
TSEnumDeclaration |
id, body |
TSEnumMember |
id, initializer |
TSExportAssignment |
expression |
TSExternalModuleReference |
expression |
TSFunctionType |
typeParameters, params, returnType |
TSImportEqualsDeclaration |
id, moduleReference |
TSImportType |
source, options, qualifier, typeArguments |
TSIndexedAccessType |
objectType, indexType |
TSIndexSignature |
parameters, typeAnnotation |
TSInferType |
typeParameter |
TSInstantiationExpression |
expression, typeArguments |
TSInterfaceBody |
body |
TSInterfaceDeclaration |
id, typeParameters, extends, body |
TSInterfaceHeritage |
expression, typeArguments |
TSIntersectionType |
types |
TSIntrinsicKeyword |
(leaf) |
TSJSDocNonNullableType |
typeAnnotation |
TSJSDocNullableType |
typeAnnotation |
TSJSDocUnknownType |
(leaf) |
TSLiteralType |
literal |
TSMappedType |
key, constraint, nameType, typeAnnotation |
TSMethodSignature |
key, typeParameters, params, returnType |
TSModuleBlock |
body |
TSModuleDeclaration |
id, body |
TSNamedTupleMember |
label, elementType |
TSNamespaceExportDeclaration |
id |
TSNeverKeyword |
(leaf) |
TSNonNullExpression |
expression |
TSNullKeyword |
(leaf) |
TSNumberKeyword |
(leaf) |
TSObjectKeyword |
(leaf) |
TSOptionalType |
typeAnnotation |
TSParameterProperty |
decorators, parameter |
TSParenthesizedType |
typeAnnotation |
TSPropertySignature |
key, typeAnnotation |
TSQualifiedName |
left, right |
TSRestType |
typeAnnotation |
TSSatisfiesExpression |
expression, typeAnnotation |
TSStringKeyword |
(leaf) |
TSSymbolKeyword |
(leaf) |
TSTemplateLiteralType |
quasis, types |
TSThisType |
(leaf) |
TSTupleType |
elementTypes |
TSTypeAliasDeclaration |
id, typeParameters, typeAnnotation |
TSTypeAnnotation |
typeAnnotation |
TSTypeAssertion |
typeAnnotation, expression |
TSTypeLiteral |
members |
TSTypeOperator |
typeAnnotation |
TSTypeParameter |
name, constraint, default |
TSTypeParameterDeclaration |
params |
TSTypeParameterInstantiation |
params |
TSTypePredicate |
parameterName, typeAnnotation |
TSTypeQuery |
exprName, typeArguments |
TSTypeReference |
typeName, typeArguments |
TSUndefinedKeyword |
(leaf) |
TSUnionType |
types |
TSUnknownKeyword |
(leaf) |
TSVoidKeyword |
(leaf) |
Glimmer template — in .gjs and .gts (21 nodes)
Nodes produced inside <template>...</template> regions by @glimmer/syntax, prefixed with Glimmer when spliced into the ESTree.
| Node | Child keys |
|---|---|
GlimmerAttrNode |
value |
GlimmerBlock |
body |
GlimmerBlockStatement |
path, params, hash, program, inverse |
GlimmerBooleanLiteral |
(leaf) |
GlimmerCommentStatement |
(leaf) |
GlimmerConcatStatement |
parts |
GlimmerElementModifierStatement |
path, params, hash |
GlimmerElementNode |
attributes, modifiers, children, comments, blockParamNodes, parts |
GlimmerHash |
pairs |
GlimmerHashPair |
value |
GlimmerMustacheCommentStatement |
(leaf) |
GlimmerMustacheStatement |
path, params, hash |
GlimmerNullLiteral |
(leaf) |
GlimmerNumberLiteral |
(leaf) |
GlimmerPathExpression |
(leaf) |
GlimmerProgram |
body, blockParamNodes |
GlimmerStringLiteral |
(leaf) |
GlimmerSubExpression |
path, params, hash |
GlimmerTemplate |
body |
GlimmerTextNode |
(leaf) |
GlimmerUndefinedLiteral |
(leaf) |
MIT