Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,25 @@ export function toTree(source, options = {}) {
function walkWithKeys(node, parentPath) {
if (!node || !node.type) return;

const path = { node, parent: parentPath?.node ?? null, parentPath };

if (hasTemplates && PLACEHOLDER_TYPES.has(node.type)) {
const parseResult = matchPlaceholder(node);
if (parseResult) {
if (parseResult && node.type === "ExportDefaultDeclaration") {
// `export default <template>...</template>`: keep the export wrapper
// and splice its declaration, so the tree (and `print`) still carry
// the `export default`.
const ast = processPlaceholder(parseResult, node.declaration);
node.declaration = ast;
setParent(ast, node);
if (hasVisitors && !seen.has(node)) {
seen.add(node);
const handler = visitors[node.type];
if (handler) handler(node, path);
walkWithKeys(ast, path);
}
return;
} else if (parseResult) {
// Splice in place: write the GlimmerTemplate directly into the parent's
// slot instead of allocating new ancestor objects. This preserves node
// identity for every ancestor, which matters for WeakMap-keyed data
Expand All @@ -250,14 +266,12 @@ export function toTree(source, options = {}) {
setParent(ast, parent);
// Recurse into the Glimmer subtree so visitors fire on its nodes too.
// The Glimmer root's parentPath reflects its true JS parent — the
// placeholder (UnaryExpression / StaticBlock) is an internal artifact.
// placeholder (`void` expression / StaticBlock) is an internal artifact.
if (hasVisitors) walkWithKeys(ast, parentPath);
return;
}
}

const path = { node, parent: parentPath?.node ?? null, parentPath };

if (hasVisitors && !seen.has(node)) {
seen.add(node);
const handler = visitors[node.type];
Expand Down
8 changes: 8 additions & 0 deletions tests/parse.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ describe("parse", () => {
]);
});

it("keeps the export default wrapper around a default-exported template", () => {
const source = `export default <template>hi</template>;\n`;
const ast = parse(source);

expect(ast.program.body.map((node) => node.type)).toEqual(["ExportDefaultDeclaration"]);
expect(ast.program.body[0].declaration.type).toBe("GlimmerTemplate");
});

it("handles multiple classes with templates", () => {
const source = `class A extends Component {
<template><div>A</div></template>
Expand Down
5 changes: 5 additions & 0 deletions tests/print-file.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ describe("print(File)", () => {
`);
});

it("round-trips a default-exported template", () => {
const source = `export default <template>hi</template>;`;
expect(print(toTree(source))).toBe("export default <template>hi</template>");
});

it("prints an empty File", () => {
const tree = toTree(``, { filePath: "m.js" });

Expand Down
Loading