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.
433 lines
12 KiB
TypeScript
433 lines
12 KiB
TypeScript
// Classes.
|
|
|
|
import { typeAssertIs } from "../asserts.ts";
|
|
import { printPropertyKey } from "./binding_pattern.ts";
|
|
import {
|
|
CAT_CLOSE_BRACKET,
|
|
CAT_IDENT,
|
|
CAT_OP_UN_NOT,
|
|
CAT_OTHER,
|
|
CAT_QUESTION,
|
|
CAT_START_OF_STMT,
|
|
debugAssertLastFresh,
|
|
markWithMap,
|
|
write,
|
|
writeNoLast,
|
|
writeWithMap,
|
|
writeWithMapEnd,
|
|
writeWithMapNoLast,
|
|
} from "./write.ts";
|
|
import { printExpression } from "./expression.ts";
|
|
import { printFunctionBody, printParenParams } from "./function.ts";
|
|
import { printSpaceBeforeIdentifier } from "./space.ts";
|
|
import { printIndent } from "./indent.ts";
|
|
import { CTX_NONE } from "./operators.ts";
|
|
import { PREC_CALL, PREC_COMMA, PREC_LOWEST, PREC_POSTFIX } from "./precedence.ts";
|
|
import { printStatement } from "./statement.ts";
|
|
import {
|
|
printTSIndexSignature,
|
|
printTypeAnnotation,
|
|
printTypeArguments,
|
|
printTypeParameters,
|
|
} from "./typescript.ts";
|
|
|
|
import type { State } from "../state.ts";
|
|
import type {
|
|
AccessorPropertyNode,
|
|
ClassBodyNode,
|
|
MethodDefinitionNode,
|
|
PropertyDefinitionNode,
|
|
} from "./types.ts";
|
|
import type * as ESTree from "../../../../npm/oxc-types/types.d.ts";
|
|
|
|
/**
|
|
* Print a class declaration or expression, decorators and heritage clauses included.
|
|
*
|
|
* A class expression is parenthesized where the statement or an `export default` starts with it,
|
|
* as `printFunction` does for functions.
|
|
*/
|
|
export function printClass(node: ESTree.Class, state: State): void {
|
|
let wrap = false;
|
|
if (node.type === "ClassExpression") {
|
|
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);
|
|
|
|
const { decorators } = node;
|
|
if (decorators != null && decorators.length > 0) printDecorators(decorators, state);
|
|
|
|
printSpaceBeforeIdentifier(state);
|
|
|
|
const declare = TS && node.declare;
|
|
const abstract = TS && node.abstract;
|
|
|
|
// The node's mapping goes on whichever of these is written first
|
|
if (declare) writeWithMap(state, "declare ", CAT_OTHER, node);
|
|
if (abstract) {
|
|
if (declare) {
|
|
write(state, "abstract ", CAT_OTHER);
|
|
} else {
|
|
writeWithMap(state, "abstract ", CAT_OTHER, node);
|
|
}
|
|
}
|
|
if (declare || abstract) {
|
|
write(state, "class", CAT_IDENT);
|
|
} else {
|
|
writeWithMap(state, "class", CAT_IDENT, node);
|
|
}
|
|
|
|
if (node.id != null) {
|
|
write(state, " ", CAT_OTHER);
|
|
writeWithMap(state, node.id.name, CAT_IDENT, node.id);
|
|
}
|
|
|
|
if (TS) printTypeParameters(node.typeParameters, state);
|
|
|
|
if (node.superClass != null) {
|
|
write(state, " extends ", CAT_OTHER);
|
|
printExpression(node.superClass, state, PREC_POSTFIX, CTX_NONE);
|
|
if (TS) printTypeArguments(node.superTypeArguments, state);
|
|
}
|
|
|
|
const { implements: implementsClauses } = node;
|
|
if (TS && implementsClauses != null && implementsClauses.length > 0) {
|
|
write(state, " implements ", CAT_OTHER);
|
|
|
|
const { length } = implementsClauses;
|
|
for (let i = 0; i < length; i++) {
|
|
if (i > 0) write(state, ", ", CAT_OTHER);
|
|
const clause = implementsClauses[i];
|
|
printExpression(clause.expression, state, PREC_CALL, CTX_NONE);
|
|
printTypeArguments(clause.typeArguments, state);
|
|
}
|
|
}
|
|
|
|
write(state, " ", CAT_OTHER);
|
|
|
|
printClassBody(node.body, state);
|
|
|
|
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
|
|
}
|
|
|
|
/**
|
|
* Print a run of decorators separated by spaces, parenthesizing any whose expression needs it.
|
|
*/
|
|
export function printDecorators(decorators: ESTree.Decorator[], state: State): void {
|
|
const { length } = decorators;
|
|
for (let i = 0; i < length; i++) {
|
|
const decorator = decorators[i];
|
|
|
|
writeWithMap(state, "@", CAT_OTHER, decorator);
|
|
|
|
const { expression } = decorator;
|
|
const wrap = decoratorNeedsWrap(expression);
|
|
if (wrap) write(state, "(", CAT_OTHER);
|
|
printExpression(expression, state, PREC_LOWEST, CTX_NONE);
|
|
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
|
|
|
|
write(state, " ", CAT_OTHER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Whether a decorator's expression has to be parenthesized.
|
|
*
|
|
* The grammar allows a name, a chain of plain member accesses, and a call on either, and nothing
|
|
* else - so a computed access needs parentheses even though a static one does not.
|
|
*/
|
|
function decoratorNeedsWrap(expr: ESTree.Expression): boolean {
|
|
for (;;) {
|
|
switch (expr.type) {
|
|
case "Identifier":
|
|
return false;
|
|
case "MemberExpression":
|
|
// `@a.b` / `@this.b` / `@x!.y` need no parens; computed access does
|
|
return expr.computed;
|
|
case "CallExpression":
|
|
expr = expr.callee;
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print the members of a class in braces, empty ones tight.
|
|
*/
|
|
function printClassBody(node: ClassBodyNode, state: State): void {
|
|
const { body } = node;
|
|
const { length } = body;
|
|
if (length === 0) {
|
|
writeWithMapNoLast(state, "{", node);
|
|
writeWithMapEnd(state, "}", CAT_OTHER, node);
|
|
return;
|
|
}
|
|
|
|
writeWithMap(state, "{\n", CAT_OTHER, node);
|
|
|
|
state.indentLevel++;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
printIndent(state);
|
|
|
|
const element = body[i];
|
|
switch (element.type) {
|
|
case "MethodDefinition":
|
|
/* IF TS */
|
|
case "TSAbstractMethodDefinition":
|
|
/* END_IF */
|
|
printMethodDefinition(element, state);
|
|
write(state, "\n", CAT_OTHER);
|
|
break;
|
|
case "PropertyDefinition":
|
|
/* IF TS */
|
|
case "TSAbstractPropertyDefinition":
|
|
/* END_IF */
|
|
printPropertyDefinition(element, state);
|
|
write(state, ";\n", CAT_OTHER);
|
|
break;
|
|
case "StaticBlock":
|
|
printStaticBlock(element, state);
|
|
write(state, "\n", CAT_OTHER);
|
|
break;
|
|
case "AccessorProperty":
|
|
/* IF TS */
|
|
case "TSAbstractAccessorProperty":
|
|
/* END_IF */
|
|
printAccessorProperty(element, state);
|
|
write(state, ";\n", CAT_OTHER);
|
|
break;
|
|
/* IF TS */
|
|
case "TSIndexSignature":
|
|
printTSIndexSignature(element, state);
|
|
write(state, ";\n", CAT_OTHER);
|
|
break;
|
|
/* END_IF */
|
|
default:
|
|
throw new Error(`Unknown class element type: ${element.type}`);
|
|
}
|
|
}
|
|
|
|
state.indentLevel--;
|
|
|
|
printIndent(state);
|
|
writeWithMapEnd(state, "}", CAT_OTHER, node);
|
|
}
|
|
|
|
/**
|
|
* Print a method, including getters, setters, constructors and their modifiers.
|
|
*/
|
|
function printMethodDefinition(node: MethodDefinitionNode, state: State): void {
|
|
markWithMap(state, node);
|
|
|
|
const { decorators } = node;
|
|
if (decorators != null && decorators.length > 0) printDecorators(decorators, state);
|
|
|
|
if (TS && node.accessibility != null) {
|
|
printSpaceBeforeIdentifier(state);
|
|
writeNoLast(state, node.accessibility);
|
|
write(state, " ", CAT_OTHER);
|
|
}
|
|
|
|
if (TS && (node.type === "TSAbstractMethodDefinition" || node.abstract)) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "abstract ", CAT_OTHER);
|
|
}
|
|
|
|
if (node.static) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "static ", CAT_OTHER);
|
|
}
|
|
if (TS && node.override) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "override ", CAT_OTHER);
|
|
}
|
|
|
|
const { kind } = node;
|
|
if (kind === "get") {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "get ", CAT_OTHER);
|
|
} else if (kind === "set") {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "set ", CAT_OTHER);
|
|
}
|
|
|
|
const func = node.value;
|
|
if (func.async) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "async ", CAT_OTHER);
|
|
}
|
|
if (func.generator) write(state, "*", CAT_OTHER);
|
|
|
|
if (node.computed) {
|
|
write(state, "[", CAT_OTHER);
|
|
typeAssertIs<ESTree.Expression>(node.key);
|
|
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
|
|
write(state, "]", CAT_CLOSE_BRACKET);
|
|
} else {
|
|
printPropertyKey(node.key, state);
|
|
}
|
|
|
|
if (TS && node.optional) write(state, "?", CAT_QUESTION);
|
|
|
|
if (TS) printTypeParameters(func.typeParameters, state);
|
|
|
|
printParenParams(func.params, state);
|
|
|
|
if (TS && func.returnType != null) {
|
|
printTypeAnnotation(func.returnType, state);
|
|
}
|
|
|
|
if (func.body != null) {
|
|
write(state, " ", CAT_OTHER);
|
|
printFunctionBody(func.body, state);
|
|
} else {
|
|
write(state, ";", CAT_OTHER);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a class field, with its modifiers and initializer.
|
|
*/
|
|
function printPropertyDefinition(node: PropertyDefinitionNode, state: State): void {
|
|
markWithMap(state, node);
|
|
|
|
const { decorators } = node;
|
|
if (decorators != null && decorators.length > 0) printDecorators(decorators, state);
|
|
|
|
if (TS && node.declare) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "declare ", CAT_OTHER);
|
|
}
|
|
|
|
if (TS && node.accessibility != null) {
|
|
printSpaceBeforeIdentifier(state);
|
|
writeNoLast(state, node.accessibility);
|
|
write(state, " ", CAT_OTHER);
|
|
}
|
|
|
|
if (TS && (node.type === "TSAbstractPropertyDefinition" || node.abstract)) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "abstract ", CAT_OTHER);
|
|
}
|
|
|
|
if (node.static) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "static ", CAT_OTHER);
|
|
}
|
|
if (TS && node.override) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "override ", CAT_OTHER);
|
|
}
|
|
if (TS && node.readonly) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "readonly ", CAT_OTHER);
|
|
}
|
|
|
|
if (node.computed) {
|
|
write(state, "[", CAT_OTHER);
|
|
typeAssertIs<ESTree.Expression>(node.key);
|
|
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
|
|
write(state, "]", CAT_CLOSE_BRACKET);
|
|
} else {
|
|
printPropertyKey(node.key, state);
|
|
}
|
|
|
|
if (TS && node.optional) write(state, "?", CAT_QUESTION);
|
|
if (TS && node.definite) write(state, "!", CAT_OP_UN_NOT);
|
|
|
|
if (TS && node.typeAnnotation != null) printTypeAnnotation(node.typeAnnotation, state);
|
|
|
|
if (node.value != null) {
|
|
write(state, " = ", CAT_OTHER);
|
|
printExpression(node.value, state, PREC_COMMA, CTX_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print a `static { … }` block.
|
|
*
|
|
* Its body has no directive prologue, unlike a function body, so a string statement at the top of
|
|
* one needs no protection from being read back as a directive.
|
|
*/
|
|
function printStaticBlock(node: ESTree.StaticBlock, state: State): void {
|
|
printSpaceBeforeIdentifier(state);
|
|
|
|
writeWithMap(state, "static ", CAT_OTHER, node);
|
|
|
|
const { body } = node;
|
|
const { length } = body;
|
|
if (length === 0) {
|
|
writeWithMapNoLast(state, "{", node);
|
|
writeWithMapEnd(state, "}", CAT_OTHER, node);
|
|
return;
|
|
}
|
|
|
|
writeWithMap(state, "{\n", CAT_OTHER, node);
|
|
|
|
state.indentLevel++;
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
printStatement(body[i], state);
|
|
}
|
|
|
|
state.indentLevel--;
|
|
|
|
printIndent(state);
|
|
writeWithMapEnd(state, "}", CAT_OTHER, node);
|
|
}
|
|
|
|
/**
|
|
* Print an `accessor` field.
|
|
*/
|
|
function printAccessorProperty(node: AccessorPropertyNode, state: State): void {
|
|
markWithMap(state, node);
|
|
|
|
const { decorators } = node;
|
|
if (decorators != null && decorators.length > 0) printDecorators(decorators, state);
|
|
|
|
if (TS && (node.type === "TSAbstractAccessorProperty" || node.abstract)) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "abstract ", CAT_OTHER);
|
|
}
|
|
|
|
if (TS && node.accessibility != null) {
|
|
printSpaceBeforeIdentifier(state);
|
|
writeNoLast(state, node.accessibility);
|
|
write(state, " ", CAT_OTHER);
|
|
}
|
|
|
|
if (node.static) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "static ", CAT_OTHER);
|
|
}
|
|
if (TS && node.override) {
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "override ", CAT_OTHER);
|
|
}
|
|
|
|
printSpaceBeforeIdentifier(state);
|
|
write(state, "accessor", CAT_IDENT);
|
|
|
|
if (node.computed) {
|
|
write(state, " [", CAT_OTHER);
|
|
typeAssertIs<ESTree.Expression>(node.key);
|
|
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
|
|
write(state, "]", CAT_CLOSE_BRACKET);
|
|
} else {
|
|
write(state, " ", CAT_OTHER);
|
|
printPropertyKey(node.key, state);
|
|
}
|
|
|
|
if (TS && node.definite) write(state, "!", CAT_OP_UN_NOT);
|
|
if (TS && node.typeAnnotation != null) printTypeAnnotation(node.typeAnnotation, state);
|
|
|
|
if (node.value != null) {
|
|
write(state, " = ", CAT_OTHER);
|
|
printExpression(node.value, state, PREC_COMMA, CTX_NONE);
|
|
}
|
|
}
|