feat(packages/codegen)!: printSync return an object (#25720)

`oxc-codegen`'s current API requires passing in a `SourceMapGenerator` as an option to generate source maps. This is currently really slow, and I imagine we'll want to generate source maps internally (perhaps in Rust), and return a source map string from `printSync` instead.

Alter `printSync`'s return type now, to support that without a breaking change later.

- Before: `function printSync(node: ESTree.Node, options?: Options): string`
- After: `function printSync(node: ESTree.Node, options?: Options): { code: string }`

Later on, it can return `{ code: string, map: string }` and that won't break existing users.
This commit is contained in:
overlookmotel committed 2026-08-15 15:53:54 +00:00
1 parent 1cbbcb7c3e
commit 365274ea16
6 files changed
+23 -21

No files matched your search

+4 -4
View File
@@ -26,7 +26,7 @@ import { printSync } from "oxc-codegen";
import { parseSync } from "oxc-parser";
const { program } = parseSync("input.js", "const answer=6*7");
const code = printSync(program);
const { code } = printSync(program);
console.log(code);
// const answer = 6 * 7;
@@ -57,7 +57,7 @@ const program = {
],
};
console.log(printSync(program));
console.log(printSync(program).code);
// console.log("Hello!");
```
@@ -68,7 +68,7 @@ Set `ts` when the AST can contain TypeScript nodes. For TSX, set both `ts` and `
```js
const { program } = parseSync("component.tsx", "const Box = <T,>(value: T) => <div>{value}</div>");
const code = printSync(program, {
const { code } = printSync(program, {
ts: true,
jsx: true,
});
@@ -79,7 +79,7 @@ const code = printSync(program, {
### `printSync(node, options?)`
```ts
function printSync(node: Node, options?: Options): string;
function printSync(node: Node, options?: Options): { code: string };
```
Prints a complete `Program` or a single statement and returns the generated source code.
+2 -2
View File
@@ -59,9 +59,9 @@ const printers: (PrintModule["printSync"] | null)[] = [null, null, null, null];
*
* @param node - AST node to print, a `Program` or a single statement
* @param options - Printing options (optional)
* @returns Generated code
* @returns Object holding the generated code
*/
export function printSync(node: ESTree.Node, options?: Options): string {
export function printSync(node: ESTree.Node, options?: Options): { code: string } {
// The printer is built 4 times, over whether the AST may contain TypeScript and whether
// source mappings are wanted. This picks the build the options call for and loads it on first use,
// so a caller printing only JavaScript never pays for the TypeScript printers.
+5 -3
View File
@@ -23,11 +23,13 @@ import type * as ESTree from "../../../../npm/oxc-types/types.d.ts";
* One of these exists per build. The package entry point picks the build and calls it -
* callers of the package never reach this directly.
*
* The result is an object so that further outputs (a source map) can be added without a breaking change.
*
* @param state - Created by the entry point, so that all 4 builds share one class and so one object shape
* @param options - The same options `state` was created from, for the parts only this build acts on
* @returns Generated code
* @returns Object holding the generated code
*/
export function printSync(node: ESTree.Node, state: State, options: Options): string {
export function printSync(node: ESTree.Node, state: State, options: Options): { code: string } {
if (node.type === "Program") {
printProgram(node, state);
} else {
@@ -45,7 +47,7 @@ export function printSync(node: ESTree.Node, state: State, options: Options): st
emitMappings(state, options.sourceMap);
}
return state.output;
return { code: state.output };
}
export type { Mapping, Options, Position, SourceMapGenerator } from "./options.ts";
+8 -8
View File
@@ -101,7 +101,7 @@ type Case = [name: string, ast: ESTree.Node, output: string];
*/
function checkCases(cases: Case[]): void {
test.each(cases)("%s", (_name, ast, output) => {
expect(printSync(ast)).toBe(output);
expect(printSync(ast).code).toBe(output);
});
}
@@ -111,11 +111,11 @@ describe("starting indent level", () => {
const ast = e(id("x"));
test("indents from a valid level", () => {
expect(printSync(ast, { startingIndentLevel: 1 })).toBe("\tx;\n");
expect(printSync(ast, { startingIndentLevel: 1 }).code).toBe("\tx;\n");
});
test("accepts the maximum level", () => {
expect(printSync(ast, { startingIndentLevel: 1000 })).toBe(`${"\t".repeat(1000)}x;\n`);
expect(printSync(ast, { startingIndentLevel: 1000 }).code).toBe(`${"\t".repeat(1000)}x;\n`);
});
test.each([
@@ -240,7 +240,7 @@ describe("strings", () => {
PARSE_OPTIONS,
);
if (errors.length > 0) throw new Error(`fixture parse failed: ${errors[0].message}`);
expect(printSync(parsed)).toBe("const value = `before <\\/ScRiPt> after`;\n");
expect(printSync(parsed).code).toBe("const value = `before <\\/ScRiPt> after`;\n");
});
});
@@ -333,7 +333,7 @@ describe("directives", () => {
])("%s", (_name, source, output) => {
const { program: parsed, errors } = parseSync("fixture.js", source, PARSE_OPTIONS);
if (errors.length > 0) throw new Error(`fixture parse failed: ${errors[0].message}`);
expect(printSync(parsed)).toBe(output);
expect(printSync(parsed).code).toBe(output);
});
});
@@ -376,7 +376,7 @@ describe("export default declarations", () => {
])("%s", (_name, source, output) => {
const { program: parsed, errors } = parseSync("fixture.ts", source, PARSE_OPTIONS);
if (errors.length > 0) throw new Error(`fixture parse failed: ${errors[0].message}`);
expect(printSync(parsed, { ts: true })).toBe(output);
expect(printSync(parsed, { ts: true }).code).toBe(output);
});
});
@@ -416,12 +416,12 @@ describe("JSX strings", () => {
};
// From `raw`, as a parser supplies it
expect(printSync(parse(), { jsx: true })).toBe(output);
expect(printSync(parse(), { jsx: true }).code).toBe(output);
// From `value`, which is where a change in the parser would show
const fromValue = parse();
dropJsxRaw(fromValue);
expect(printSync(fromValue, { jsx: true })).toBe(output);
expect(printSync(fromValue, { jsx: true }).code).toBe(output);
});
});
+3 -3
View File
@@ -235,8 +235,8 @@ for (const fixture of FIXTURES) {
// Both builds through the public API - `printSync` picks the maps build when given a
// `sourceMap`, and the no-maps build when not
const withMaps = printSync(program, { jsx, ts, sourceMap: collector });
const withoutMaps = printSync(program, { jsx, ts });
const withMaps = printSync(program, { jsx, ts, sourceMap: collector }).code;
const withoutMaps = printSync(program, { jsx, ts }).code;
printed = {
withMaps,
@@ -364,7 +364,7 @@ describe("indent option", () => {
const out = printSync(program, {
indent: indent as string | undefined,
sourceMap: collector,
});
}).code;
const expectedIndent = typeof indent === "string" && /^[ \t]+$/.test(indent) ? indent : "\t";
const lines = out.split("\n");
+1 -1
View File
@@ -97,7 +97,7 @@ export function checkFixture(
// rather than a printer one, and saying so is more use than a diff of printed output.
expect(errors, "Rust parsed this fixture cleanly but `oxc-parser` did not").toEqual([]);
const actual = printSync(program, { ts, jsx });
const actual = printSync(program, { ts, jsx }).code;
expect(actual, `preserveParens: ${preserveParens}`).toBe(expected);
checked = true;
}