Files
oxc-project__oxc/packages/codegen/src-js/print/function.ts
overlookmotel a4478e960a feat(codegen): add oxc-codegen package (#25488)
Add `oxc-codegen` package - a printer written in TS.

### Features

- Prints JS, JSX, TS, TSX.
- Matches `oxc_codegen` exactly on all Test262, TypeScript, Acorn-JSX fixtures.
- Very fast.

### Deficiencies

- Pretty-printing only (no compact/minified output).
- No support for printing comments.
- Source map support is semi-there, but largely untested, and poor API (design doc has some ideas on how to improve it).

### This PR adds

- The printer code.
- Conformance testing infra.
- Further tests for edge cases.
- README.
- Design doc - how it differs from `oxc_codegen` and why, what produces the perf.

### This PR doesn't add

- Not integrated into CI or release pipeline.

### Notes

This started as a slop port of `oxc_codegen`. I've iterated on that base, cleaned it up a lot, and at least skimmed through all of it. I think it's in fairly decent shape.

The design doc is I think worth a read. It explains the rationale for the choices, in particular the "1 codebase, 4 builds" approach.

I've put it in a new top-level directory called `packages`. It's not an app, so didn't seem to me to fit alongside `apps/oxlint` and `apps/oxfmt`. It's pure JS, so not like e.g. `napi/parser`. And it felt too heavyweight to put into `npm` dir. But maybe there's a better place for it.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-12 13:41:31 +00:00

168 lines
5.1 KiB
TypeScript

// Functions.
import { typeAssertIs } from "../asserts.ts";
import { printBindingPattern } from "./binding_pattern.ts";
import {
CAT_IDENT,
CAT_OTHER,
CAT_START_OF_STMT,
debugAssertLastFresh,
write,
writeNoLast,
writeWithMap,
} from "./write.ts";
import { printDecorators } from "./class.ts";
import { printSpaceBeforeIdentifier } from "./space.ts";
import { printIndent } from "./indent.ts";
import { printDirectivesAndStatements } from "./statement.ts";
import { printTypeAnnotation, printTypeParameters } from "./typescript.ts";
import type { State } from "../state.ts";
import type * as ESTree from "../../../../npm/oxc-types/types.d.ts";
/**
* Print a function declaration or expression, from `async` through to the closing brace of its body.
*
* A function expression is parenthesized where the statement or an `export default` starts with it,
* since it would otherwise be read as a declaration.
*/
export function printFunction(node: ESTree.Function, state: State): void {
let wrap = false;
if (node.type === "FunctionExpression") {
debugAssertLastFresh(state);
// `CAT_START_OF_STMT` or `CAT_START_OF_DEFAULT_EXPORT`, which are adjacent - see `write.ts`
wrap = (state.last | 1) === CAT_START_OF_STMT;
}
if (wrap) write(state, "(", CAT_OTHER);
printSpaceBeforeIdentifier(state);
// The node's mapping goes on whichever of these is written first
const declare = TS && node.declare;
if (declare) {
writeWithMap(state, "declare ", CAT_OTHER, node);
write(state, node.async ? "async function" : "function", CAT_IDENT);
} else {
writeWithMap(state, node.async ? "async function" : "function", CAT_IDENT, node);
}
if (node.generator) write(state, "* ", CAT_OTHER);
if (node.id != null) {
printSpaceBeforeIdentifier(state);
writeWithMap(state, node.id.name, CAT_IDENT, node.id);
}
if (TS) printTypeParameters(node.typeParameters, state);
printParenParams(node.params, state);
if (TS && node.returnType != null) printTypeAnnotation(node.returnType, state);
if (node.body != null) {
write(state, " ", CAT_OTHER);
printFunctionBody(node.body, state);
} else {
write(state, ";", CAT_OTHER);
}
if (wrap) write(state, ")", CAT_OTHER);
}
/**
* Print a parameter list in parentheses.
*/
export function printParenParams(params: ESTree.ParamPattern[], state: State): void {
// `(params)`, as a single write when there are none
if (params.length === 0) {
write(state, "()", CAT_OTHER);
return;
}
write(state, "(", CAT_OTHER);
printParams(params, state);
write(state, ")", CAT_OTHER);
}
/**
* Print the parameters themselves, without the parentheses around them.
*
* TypeScript parameter properties carry the modifiers which make them a property as well as a
* parameter, and decorators can appear on either kind.
*/
function printParams(params: ESTree.ParamPattern[], state: State): void {
const { length } = params;
for (let i = 0; i < length; i++) {
if (i > 0) write(state, ", ", CAT_OTHER);
const param = params[i];
const { decorators } = param;
if (decorators != null && decorators.length > 0) printDecorators(decorators, state);
if (TS && param.type === "TSParameterProperty") {
if (param.accessibility != null) {
printSpaceBeforeIdentifier(state);
writeNoLast(state, param.accessibility);
write(state, " ", CAT_OTHER);
}
if (param.override) {
printSpaceBeforeIdentifier(state);
write(state, "override ", CAT_OTHER);
}
if (param.readonly) {
printSpaceBeforeIdentifier(state);
write(state, "readonly ", CAT_OTHER);
}
printBindingPattern(param.parameter, state);
} else {
// The `TS &&` above defeats narrowing; `TSParameterProperty` was excluded by the check
typeAssertIs<Exclude<typeof param, ESTree.TSParameterProperty>>(param);
printBindingPattern(param, state);
}
}
}
/**
* Print a function body in braces, empty ones tight.
*
* A function body has a directive prologue, so it goes through the same printer as a program does,
* rather than through the block statement printer.
*/
export function printFunctionBody(body: ESTree.FunctionBody, state: State): void {
// `body` is a BlockStatement holding directives + statements.
const statements = body.body;
if (statements.length === 0) {
writeWithMap(state, "{}", CAT_OTHER, body);
return;
}
writeWithMap(state, "{\n", CAT_OTHER, body);
state.indentLevel++;
printDirectivesAndStatements(statements, state);
state.indentLevel--;
printIndent(state);
write(state, "}", CAT_OTHER);
}
/**
* Print an arrow function's parameter list along with its `=>`.
*
* Oxc keeps the parentheses even around a lone parameter, so there is no single-parameter form.
*/
export function printParenParamsArrow(params: ESTree.ParamPattern[], state: State): void {
// `(params) => `, as a single write when there are none
if (params.length === 0) {
write(state, "() => ", CAT_OTHER);
return;
}
write(state, "(", CAT_OTHER);
printParams(params, state);
write(state, ") => ", CAT_OTHER);
}