mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
53ff08068d
Follow-on after #25585. Previously `printExpression` checked whether last character written was a `)` or `]` by consulting `lastWasPostfixClose`. Every write kept `lastWasPostfixClose` up to date by reading the last character that was written (`updatePostfixClose`). It's statically known everywhere that writes a `)` or `]`, so instead add a category `CAT_CLOSE_BRACKET`, and write that as `state.last` using the existing machinery. `printExpression` then does its check with `state.last === CAT_CLOSE_BRACKET`. This has a large effect on perf because checking the last character that was written in `updatePostfixClose` was expensive and on a hot path. Benchmark without sourcemap generation: | Fixture | Bytes | Before | After | Faster by | |:---| ---:| ---:| ---:| ---:| | `RadixUIAdoptionSection.jsx` | 2,518 | 0\.01221ms | 0\.00716ms | \+41.4% | | `react.development.js` | 72,141 | 0\.38326ms | 0\.25377ms | \+33.8% | | `binder.ts` | 193,077 | 0\.6824ms | 0\.6458ms | \+5.4% | | `App.tsx` | 415,340 | 1\.8782ms | 1\.7846ms | \+5.0% | | `lodash.js` | 544,096 | 1\.6802ms | 1\.0902ms | \+35.1% | | `kitchen-sink.tsx` | 732,222 | 5\.2654ms | 4\.7936ms | \+9.0% | | `antd.js` | 6,683,633 | 32\.0742ms | 22\.4061ms | \+30.1% | With sourcemap generation included: | Fixture | Bytes | Before | After | Faster by | |:---| ---:| ---:| ---:| ---:| | `RadixUIAdoptionSection.jsx` | 2,518 | 0\.02126ms | 0\.01616ms | \+24.0% | | `react.development.js` | 72,141 | 0\.52684ms | 0\.39368ms | \+25.3% | | `binder.ts` | 193,077 | 1\.0708ms | 1\.0335ms | \+3.5% | | `App.tsx` | 415,340 | 3\.8221ms | 3\.7476ms | \+2.0% | | `lodash.js` | 544,096 | 2\.9815ms | 2\.4294ms | \+18.5% | | `kitchen-sink.tsx` | 732,222 | 9\.8293ms | 9\.5644ms | \+2.7% | | `antd.js` | 6,683,633 | 54\.3088ms | 44\.3745ms | \+18.3% | The main reason for the large impact on JS/JSX fixtures is that the JS build gets poisoned by JSX text. Most strings that `updatePostfixClose` sees are interned strings (source literals like `"function "`) but in JSX files it was also seeing dynamic strings (JSX text) which turned it polymorphic. Once it's gone polymorphic, it never goes back, affecting all following JS fixtures (which use the JS build). Removing the string accesses removes this cost.
185 lines
5.6 KiB
TypeScript
185 lines
5.6 KiB
TypeScript
// Functions.
|
|
|
|
import { typeAssertIs } from "../asserts.ts";
|
|
import { printBindingPattern } from "./binding_pattern.ts";
|
|
import {
|
|
CAT_CLOSE_BRACKET,
|
|
CAT_IDENT,
|
|
CAT_OTHER,
|
|
CAT_START_OF_STMT,
|
|
debugAssertLastFresh,
|
|
markWithMapNoName,
|
|
write,
|
|
writeNoLast,
|
|
writeWithMap,
|
|
writeWithMapEnd,
|
|
writeWithMapNoLast,
|
|
} 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_CLOSE_BRACKET);
|
|
}
|
|
|
|
/**
|
|
* 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_CLOSE_BRACKET);
|
|
return;
|
|
}
|
|
|
|
write(state, "(", CAT_OTHER);
|
|
printParams(params, state);
|
|
write(state, ")", CAT_CLOSE_BRACKET);
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
|
|
// Oxc stores TypeScript's special `this` parameter separately from formal parameters
|
|
// and prints it without a source mapping.
|
|
if (TS && param.type === "Identifier" && param.name === "this") {
|
|
write(state, "this", CAT_IDENT);
|
|
if (param.typeAnnotation != null) printTypeAnnotation(param.typeAnnotation, state);
|
|
continue;
|
|
}
|
|
|
|
const { decorators } = param;
|
|
if (decorators != null && decorators.length > 0) {
|
|
printDecorators(decorators, state);
|
|
} else {
|
|
markWithMapNoName(state, param);
|
|
}
|
|
|
|
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) {
|
|
writeWithMapNoLast(state, "{", body);
|
|
writeWithMapEnd(state, "}", CAT_OTHER, body);
|
|
return;
|
|
}
|
|
|
|
writeWithMap(state, "{\n", CAT_OTHER, body);
|
|
state.indentLevel++;
|
|
printDirectivesAndStatements(statements, state);
|
|
state.indentLevel--;
|
|
printIndent(state);
|
|
|
|
writeWithMapEnd(state, "}", CAT_OTHER, body);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|