perf(packages/codegen): remove lastWasPostfixClose (#25888)

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.
This commit is contained in:
overlookmotel committed 2026-08-19 09:20:28 +00:00
1 parent 8e2b3bebcc
commit 53ff08068d
11 files changed
+111 -106

No files matched your search

@@ -2,7 +2,7 @@
import { typeAssertIs } from "../asserts.ts";
import { printPropertyKey } from "./binding_pattern.ts";
import { CAT_IDENT, CAT_OTHER, write, writeWithMap } from "./write.ts";
import { CAT_CLOSE_BRACKET, CAT_IDENT, CAT_OTHER, write, writeWithMap } from "./write.ts";
import { printExpression, printMemberExpression } from "./expression.ts";
import { printSpaceBeforeIdentifier } from "./space.ts";
import { CTX_NONE } from "./operators.ts";
@@ -97,7 +97,7 @@ function printAssignmentTargetProperty(node: ESTree.AssignmentTargetProperty, st
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(key);
printExpression(key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(key, state);
}
@@ -159,5 +159,5 @@ function printArrayAssignmentTarget(node: ESTree.ArrayAssignmentTarget, state: S
printAssignmentTarget(rest.argument, state);
}
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
}
+2 -2
View File
@@ -1,7 +1,7 @@
// Binary/logical expressions (port of `binary_expr_visitor.rs`).
import { typeAssertIs } from "../asserts.ts";
import { CAT_OTHER, write } from "./write.ts";
import { CAT_CLOSE_BRACKET, CAT_OTHER, write } from "./write.ts";
import { printPrivateInExpression, printExpression } from "./expression.ts";
import { BIN_PRECEDENCE, CTX_FORBID_IN, PADDED_BIN_OPERATORS } from "./operators.ts";
import { withoutParens } from "./parens.ts";
@@ -181,5 +181,5 @@ function binVisitRightAndFinish(v: BinaryVisitor, state: State): void {
write(state, PADDED_BIN_OPERATORS[v.operator], CAT_OTHER);
// Any `ParenthesizedExpression` wrapper is kept, for the same reason as on the left operand
printExpression(v.e.right, state, v.rightPrecedence, v.ctx);
if (v.wrap) write(state, ")", CAT_OTHER);
if (v.wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
@@ -2,6 +2,7 @@
import { typeAssertIs } from "../asserts.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_OTHER,
CAT_QUESTION,
@@ -120,7 +121,7 @@ function printBindingProperty(node: ESTree.BindingProperty, state: State): void
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(key);
printExpression(key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(key, state);
}
@@ -193,5 +194,5 @@ function printArrayBindingPattern(node: ESTree.ArrayPattern, state: State): void
printBindingPattern(rest, state);
}
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
}
+6 -5
View File
@@ -3,6 +3,7 @@
import { typeAssertIs } from "../asserts.ts";
import { printPropertyKey } from "./binding_pattern.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_OP_UN_NOT,
CAT_OTHER,
@@ -107,7 +108,7 @@ export function printClass(node: ESTree.Class, state: State): void {
printClassBody(node.body, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -124,7 +125,7 @@ export function printDecorators(decorators: ESTree.Decorator[], state: State): v
const wrap = decoratorNeedsWrap(expression);
if (wrap) write(state, "(", CAT_OTHER);
printExpression(expression, state, PREC_LOWEST, CTX_NONE);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
write(state, " ", CAT_OTHER);
}
@@ -265,7 +266,7 @@ function printMethodDefinition(node: MethodDefinitionNode, state: State): void {
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(node.key);
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(node.key, state);
}
@@ -330,7 +331,7 @@ function printPropertyDefinition(node: PropertyDefinitionNode, state: State): vo
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(node.key);
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(node.key, state);
}
@@ -415,7 +416,7 @@ function printAccessorProperty(node: AccessorPropertyNode, state: State): void {
write(state, " [", CAT_OTHER);
typeAssertIs<ESTree.Expression>(node.key);
printExpression(node.key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
write(state, " ", CAT_OTHER);
printPropertyKey(node.key, state);
+27 -25
View File
@@ -5,6 +5,7 @@ import { printAssignmentTarget } from "./assignment_target.ts";
import { printBinaryish } from "./binary.ts";
import { printPropertyKey } from "./binding_pattern.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_INT_DIGIT,
CAT_LT,
@@ -195,7 +196,7 @@ export function printExpression(
if (inner.type === "FunctionExpression" || inner.type === "ArrowFunctionExpression") {
write(state, "(", CAT_OTHER);
printExpression(inner, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
if (SOURCEMAPS && precedence === PREC_POSTFIX) {
markWithMapAfter(state, inner);
const wrappers: ESTree.ParenthesizedExpression[] = [];
@@ -243,7 +244,8 @@ export function printExpression(
// Rust adds an exclusive-end mapping after a postfix operand which ended in `)` or `]`,
// so the punctuation of a following call/member/tag chain resolves to the operand,
// rather than one character to its left
if (SOURCEMAPS && precedence === PREC_POSTFIX && state.lastWasPostfixClose) {
debugAssertLastFresh(state);
if (SOURCEMAPS && precedence === PREC_POSTFIX && state.last === CAT_CLOSE_BRACKET) {
markWithMapAfter(state, node);
}
}
@@ -269,13 +271,13 @@ export function printMemberExpression(
if (wrap) write(state, "(", CAT_OTHER);
printExpression(object, state, PREC_POSTFIX, ctx & CTX_FORBID_CALL);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
if (node.optional) write(state, "?.", CAT_OTHER);
write(state, "[", CAT_OTHER);
printExpression(node.property, state, PREC_LOWEST, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printExpression(object, state, PREC_POSTFIX, ctx & CTX_FORBID_CALL);
@@ -336,7 +338,7 @@ function printCallExpression(
printArguments(node, node.arguments, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -352,7 +354,7 @@ function printArguments(
const { length } = args;
if (length === 0) {
writeNoLast(state, "(");
writeWithMapEnd(state, ")", CAT_OTHER, node);
writeWithMapEnd(state, ")", CAT_CLOSE_BRACKET, node);
return;
}
@@ -370,7 +372,7 @@ function printArguments(
}
}
writeWithMapEnd(state, ")", CAT_OTHER, node);
writeWithMapEnd(state, ")", CAT_CLOSE_BRACKET, node);
}
/**
@@ -394,7 +396,7 @@ export function printPrivateInExpression(
write(state, " in ", CAT_OTHER);
printExpression(node.right, state, PREC_EQUALS, CTX_FORBID_IN);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -437,7 +439,7 @@ function printObjectExpression(node: ESTree.ObjectExpression, state: State): voi
writeWithMapEnd(state, "}", CAT_OTHER, node);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -476,7 +478,7 @@ function printObjectProperty(node: ESTree.ObjectPropertyKind, state: State): voi
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(key);
printExpression(key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(key, state);
}
@@ -539,7 +541,7 @@ function printObjectProperty(node: ESTree.ObjectPropertyKind, state: State): voi
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(key);
printExpression(key, state, PREC_COMMA, CTX_NONE);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printPropertyKey(key, state);
}
@@ -594,7 +596,7 @@ function printArrayExpression(node: ESTree.ArrayExpression, state: State): void
printIndent(state);
}
writeWithMapEnd(state, "]", CAT_OTHER, node);
writeWithMapEnd(state, "]", CAT_CLOSE_BRACKET, node);
}
/**
@@ -627,7 +629,7 @@ function printAssignmentExpression(
write(state, PADDED_ASSIGN_OPERATORS[node.operator], CAT_OTHER);
printExpression(node.right, state, PREC_COMMA, ctx);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -660,7 +662,7 @@ function printUpdateExpression(
write(state, node.operator, operatorCode);
}
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -701,9 +703,9 @@ function printUnaryExpression(
if (isDeleteInfinity) write(state, "(0, ", CAT_OTHER);
printExpression(node.argument, state, PREC_EXPONENTIATION, ctx);
if (isDeleteInfinity) write(state, ")", CAT_OTHER);
if (isDeleteInfinity) write(state, ")", CAT_CLOSE_BRACKET);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -747,7 +749,7 @@ function printConditionalExpression(
write(state, " : ", CAT_OTHER);
printExpression(node.alternate, state, PREC_YIELD, innerCtx);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -772,7 +774,7 @@ function printSequenceExpression(
printExpression(expressions[i], state, PREC_LOWEST, innerCtx);
}
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -818,7 +820,7 @@ function printArrowFunctionExpression(
printExpression(body, state, PREC_COMMA, bodyCtx);
}
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -837,7 +839,7 @@ function printNewExpression(node: ESTree.NewExpression, state: State, precedence
if (TS) printTypeArguments(node.typeArguments, state);
printArguments(node, node.arguments, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -901,7 +903,7 @@ function printAwaitExpression(
writeWithMap(state, "await ", CAT_OTHER, node);
printExpression(node.argument, state, PREC_EXPONENTIATION, ctx);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -926,7 +928,7 @@ function printYieldExpression(
printExpression(node.argument, state, PREC_YIELD, CTX_NONE);
}
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -958,9 +960,9 @@ function printImportExpression(
write(state, ", ", CAT_OTHER);
printExpression(node.options, state, PREC_COMMA, CTX_NONE);
}
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -981,7 +983,7 @@ function printChainExpression(
if (wrap) {
write(state, "(", CAT_OTHER);
printExpression(node.expression, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
} else {
printExpression(node.expression, state, precedence, ctx);
}
+4 -3
View File
@@ -3,6 +3,7 @@
import { typeAssertIs } from "../asserts.ts";
import { printBindingPattern } from "./binding_pattern.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_OTHER,
CAT_START_OF_STMT,
@@ -70,7 +71,7 @@ export function printFunction(node: ESTree.Function, state: State): void {
write(state, ";", CAT_OTHER);
}
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -79,13 +80,13 @@ export function printFunction(node: ESTree.Function, state: State): void {
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);
write(state, "()", CAT_CLOSE_BRACKET);
return;
}
write(state, "(", CAT_OTHER);
printParams(params, state);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
}
/**
+4 -3
View File
@@ -2,6 +2,7 @@
import { typeAssertIs } from "../asserts.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_INT_DIGIT,
CAT_OP_UN_NEG,
@@ -126,7 +127,7 @@ function printNumericLiteral(
}
writeWithMap(state, "Infinity", CAT_IDENT, node);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
} else if (Object.is(value, 0)) {
// +0 exactly - `Object.is` rejects `-0`, which the negative paths below print as `-0`
printSpaceBeforeIdentifier(state);
@@ -134,7 +135,7 @@ function printNumericLiteral(
} else if (precedence >= PREC_PREFIX) {
writeNoLast(state, "(-");
printNonNegativeFloat(state, -value, node);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
} else {
printSpaceBeforeOperator(state, CAT_OP_UN_NEG);
writeNoLast(state, "-");
@@ -193,7 +194,7 @@ function printBigIntLiteral(node: ESTree.BigIntLiteral, state: State, precedence
if (value.startsWith("-") && precedence >= PREC_PREFIX) {
writeWithMapNoLast(state, "(", node);
writeNoLast(state, value);
write(state, "n)", CAT_OTHER);
write(state, "n)", CAT_CLOSE_BRACKET);
} else {
writeWithMapNoLast(state, value, node);
write(state, "n", CAT_IDENT);
+10 -9
View File
@@ -4,6 +4,7 @@ import { typeAssertIs } from "../asserts.ts";
import { printAssignmentTarget } from "./assignment_target.ts";
import { printBindingPattern } from "./binding_pattern.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_OP_UN_NOT,
CAT_OTHER,
@@ -252,7 +253,7 @@ export function printStatement(node: ESTree.Statement | UnknownNode, state: Stat
printSpaceBeforeIdentifier(state);
writeWithMap(state, "with(", CAT_OTHER, node);
printExpression(node.object, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
printBody(node.body, state);
break;
case "DebuggerStatement":
@@ -433,7 +434,7 @@ function printIf(node: ESTree.IfStatement, state: State): void {
writeWithMapEnd(state, "}", CAT_OTHER, consequent);
write(state, alternate != null ? " " : "\n", CAT_OTHER);
} else {
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
printBody(consequent, state);
if (alternate != null) printIndent(state);
}
@@ -521,7 +522,7 @@ function printTryStatement(node: ESTree.TryStatement, state: State): void {
if (handler.param != null) {
write(state, " (", CAT_OTHER);
printBindingPattern(handler.param, state);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
}
write(state, " ", CAT_OTHER);
@@ -611,7 +612,7 @@ function printWhileStatement(node: ESTree.WhileStatement, state: State): void {
writeWithMap(state, "while (", CAT_OTHER, node);
printExpression(node.test, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
printBody(node.body, state);
}
@@ -683,9 +684,9 @@ function printForStatement(node: ESTree.ForStatement, state: State): void {
if (update != null) {
write(state, "; ", CAT_OTHER);
printExpression(update, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
} else {
write(state, ";)", CAT_OTHER);
write(state, ";)", CAT_CLOSE_BRACKET);
}
printBody(node.body, state);
@@ -710,7 +711,7 @@ function printForInStatement(node: ESTree.ForInStatement, state: State): void {
write(state, " in ", CAT_OTHER);
printExpression(node.right, state, PREC_LOWEST, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
printBody(node.body, state);
}
@@ -744,12 +745,12 @@ function printForOfStatement(node: ESTree.ForOfStatement, state: State): void {
(!node.await && bare.type === "Identifier" && bare.name === "async");
if (wrap) write(state, "(", CAT_OTHER);
printAssignmentTarget(left, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
write(state, " of ", CAT_OTHER);
printExpression(node.right, state, PREC_COMMA, CTX_NONE);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
printBody(node.body, state);
}
+21 -20
View File
@@ -2,6 +2,7 @@
import { typeAssertIs } from "../asserts.ts";
import {
CAT_CLOSE_BRACKET,
CAT_IDENT,
CAT_LT,
CAT_OP_UN_NOT,
@@ -56,7 +57,7 @@ export function printTSAsOrSatisfiesExpression(
write(state, node.type === "TSAsExpression" ? " as " : " satisfies ", CAT_OTHER);
printTSType(node.typeAnnotation, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -147,8 +148,8 @@ function printTSType(
const wrap = parenthesizeTypeOfPostfixType(node.elementType);
if (wrap) write(state, "(", CAT_OTHER);
printTSType(node.elementType, state);
if (wrap) write(state, ")", CAT_OTHER);
write(state, "[]", CAT_OTHER);
if (wrap) writeNoLast(state, ")");
write(state, "[]", CAT_CLOSE_BRACKET);
break;
}
case "TSTypeLiteral":
@@ -172,7 +173,7 @@ function printTSType(
if (i > 0) write(state, ", ", CAT_OTHER);
printTSTupleElement(elementTypes[i], state);
}
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
break;
}
case "TSConditionalType":
@@ -186,10 +187,10 @@ function printTSType(
const wrap = parenthesizeTypeOfPostfixType(node.objectType);
if (wrap) write(state, "(", CAT_OTHER);
printTSType(node.objectType, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
write(state, "[", CAT_OTHER);
printTSType(node.indexType, state);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
break;
}
case "TSMappedType":
@@ -227,7 +228,7 @@ function printTSType(
case "TSParenthesizedType":
write(state, "(", CAT_OTHER);
printTSType(node.typeAnnotation, state);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
break;
case "TSNamedTupleMember":
printTSTupleElement(node, state);
@@ -349,7 +350,7 @@ function printTSUnionType(node: ESTree.TSUnionType, state: State): void {
const wrap = parenthesizeTypeOfUnionType(types[i]);
if (wrap) write(state, "(", CAT_OTHER);
printTSType(types[i], state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
}
@@ -389,7 +390,7 @@ function printTSIntersectionType(node: ESTree.TSIntersectionType, state: State):
const wrap = parenthesizeTypeOfIntersectionType(types[i]);
if (wrap) write(state, "(", CAT_OTHER);
printTSType(types[i], state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
}
@@ -465,7 +466,7 @@ function printTSSignature(node: ESTree.TSSignature | UnknownNode, state: State,
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(node.key);
printExpression(node.key, state, PREC_COMMA, ctx);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printSignatureKey(node.key, state, ctx);
}
@@ -486,7 +487,7 @@ function printTSSignature(node: ESTree.TSSignature | UnknownNode, state: State,
write(state, "[", CAT_OTHER);
typeAssertIs<ESTree.Expression>(node.key);
printExpression(node.key, state, PREC_COMMA, ctx);
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
} else {
printSignatureKey(node.key, state, ctx);
}
@@ -682,7 +683,7 @@ function printTSTupleElement(node: ESTree.TSTupleElement, state: State): void {
const wrap = parenthesizeTypeOfPostfixType(node.typeAnnotation);
if (wrap) write(state, "(", CAT_OTHER);
printTSType(node.typeAnnotation, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
write(state, "?", CAT_QUESTION);
break;
}
@@ -715,7 +716,7 @@ function printTSConditionalType(node: ESTree.TSConditionalType, state: State): v
if (checkWrap) write(state, "(", CAT_OTHER);
printTSType(checkType, state);
if (checkWrap) write(state, ")", CAT_OTHER);
if (checkWrap) write(state, ")", CAT_CLOSE_BRACKET);
write(state, " extends ", CAT_OTHER);
@@ -723,7 +724,7 @@ function printTSConditionalType(node: ESTree.TSConditionalType, state: State): v
if (extendsWrapped) write(state, "(", CAT_OTHER);
printTSType(extendsType, state);
if (extendsWrapped) write(state, ")", CAT_OTHER);
if (extendsWrapped) write(state, ")", CAT_CLOSE_BRACKET);
write(state, " ? ", CAT_OTHER);
printTSType(node.trueType, state);
@@ -802,7 +803,7 @@ function printTSTypeOperator(node: ESTree.TSTypeOperator, state: State): void {
if (wrap) write(state, "(", CAT_OTHER);
printTSType(ty, state);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -857,7 +858,7 @@ function printTSImportType(node: ESTree.TSImportType, state: State): void {
printExpression(node.options, state, PREC_LOWEST, CTX_TYPESCRIPT);
}
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
if (node.qualifier != null) {
write(state, ".", CAT_OTHER);
@@ -910,7 +911,7 @@ export function printTSTypeAssertion(
write(state, ">", CAT_OTHER);
printExpression(node.expression, state, PREC_EXPONENTIATION, ctx);
if (wrap) write(state, ")", CAT_OTHER);
if (wrap) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -1054,7 +1055,7 @@ export function printTSTypeAliasDeclaration(
const needsParens = isLeftmostIntrinsicReference(node.typeAnnotation);
if (needsParens) write(state, "(", CAT_OTHER);
printTSType(node.typeAnnotation, state);
if (needsParens) write(state, ")", CAT_OTHER);
if (needsParens) write(state, ")", CAT_CLOSE_BRACKET);
}
/**
@@ -1159,7 +1160,7 @@ function printTSEnumMember(node: ESTree.TSEnumMember, state: State): void {
write(state, "[", CAT_OTHER);
printExpression(id, state, PREC_COMMA, CTX_NONE);
}
write(state, "]", CAT_OTHER);
write(state, "]", CAT_CLOSE_BRACKET);
}
if (node.initializer != null) {
@@ -1189,7 +1190,7 @@ export function printTSImportEqualsDeclaration(
if (ref.type === "TSExternalModuleReference") {
write(state, "require(", CAT_OTHER);
printString(state, ref.expression.value, ref.expression);
write(state, ")", CAT_OTHER);
write(state, ")", CAT_CLOSE_BRACKET);
} else {
printTSTypeName(ref, state);
}
+31 -29
View File
@@ -39,21 +39,22 @@ import type { State } from "../state.ts";
// 1 CAT_INT_DIGIT Numeric literal of plain digits (`0 .toExponential()`)
// 2 CAT_REGEX_SLASH Regex closed with no flags
//
// Group 3 to 9: Checked individually
// Group 3 to 10: Checked individually
// 3 CAT_OTHER Anything else not covered by another category - punctuation, whitespace
// 4 CAT_LT `<`
// 5 CAT_QUESTION `?`
// 6 CAT_START_OF_DEFAULT_EXPORT `export default` expression is about to be printed
// 7 CAT_START_OF_STMT Expression statement's expression is about to be printed
// 8 CAT_START_OF_ARROW_EXPR Concise arrow body is about to be printed
// 9 CAT_OP_UN_NOT `!`
// 9 CAT_CLOSE_BRACKET `)` or `]`
// 10 CAT_OP_UN_NOT `!`
//
// Group 10 to 14: Operators `printSpaceBeforeOperatorSlow` needs to tell apart (`last >= CAT_OP_UN_NOT_AFTER_LT`)
// 10 CAT_OP_UN_NOT_AFTER_LT `!` written straight after a `<`
// 11 CAT_OP_UN_PLUS `+`
// 12 CAT_OP_UPD_INC `++`
// 13 CAT_OP_UN_NEG `-`
// 14 CAT_OP_UPD_DEC `--`
// Group 11 to 15: Operators `printSpaceBeforeOperatorSlow` needs to tell apart (`last >= CAT_OP_UN_NOT_AFTER_LT`)
// 11 CAT_OP_UN_NOT_AFTER_LT `!` written straight after a `<`
// 12 CAT_OP_UN_PLUS `+`
// 13 CAT_OP_UPD_INC `++`
// 14 CAT_OP_UN_NEG `-`
// 15 CAT_OP_UPD_DEC `--`
/**
* A category code. One of the `CAT_*` constants below, and nothing else -
@@ -69,6 +70,7 @@ export type Category =
| typeof CAT_START_OF_STMT
| typeof CAT_START_OF_ARROW_EXPR
| typeof CAT_START_OF_DEFAULT_EXPORT
| typeof CAT_CLOSE_BRACKET
| typeof CAT_OP_UN_NOT
| typeof CAT_OP_UN_NOT_AFTER_LT
| typeof CAT_OP_UN_PLUS
@@ -132,6 +134,18 @@ export const CAT_START_OF_STMT = 7;
*/
export const CAT_START_OF_ARROW_EXPR = 8;
/**
* `)` or `]`, the only two characters a postfix operand can end with.
*
* The one reader is the source map hook at the end of `printExpression`, which mirrors Rust's
* exclusive-end mapping after such an operand. It never asks which of the two was written,
* so one code serves for both.
*
* Neither space check cares about either character, so it sits between the marks and the operator range,
* where both range compares read it as "nothing to separate".
*/
export const CAT_CLOSE_BRACKET = 9;
/**
* `!`, written anywhere other than straight after a `<` - both the unary operator and TS's
* postfix `!` (non-null assertion, definite assignment), which postfix position keeps off a `<`.
@@ -139,7 +153,7 @@ export const CAT_START_OF_ARROW_EXPR = 8;
* An operator code, but deliberately below the range `printSpaceBeforeOperator` gates on -
* no following operator merges with a plain `!`, so storing this never costs the slow path a call.
*/
export const CAT_OP_UN_NOT = 9;
export const CAT_OP_UN_NOT = 10;
/**
* `!` written immediately after a `<`, which is the `<!--` hazard.
@@ -148,19 +162,19 @@ export const CAT_OP_UN_NOT = 9;
* The first of the operators `printSpaceBeforeOperator` gates on - writing one of these
* is what records it, so no separate field tracks which operator came last.
*/
export const CAT_OP_UN_NOT_AFTER_LT = 10;
export const CAT_OP_UN_NOT_AFTER_LT = 11;
/** `+`, which must not merge with a following `+` or `++`. */
export const CAT_OP_UN_PLUS = 11;
export const CAT_OP_UN_PLUS = 12;
/** `++`, which must not follow a `+` without a space. */
export const CAT_OP_UPD_INC = 12;
export const CAT_OP_UPD_INC = 13;
/** `-`, which must not merge with a following `-` or `--`. */
export const CAT_OP_UN_NEG = 13;
export const CAT_OP_UN_NEG = 14;
/** `--`, which must not follow a `-`, nor the `!` of a `<!`. */
export const CAT_OP_UPD_DEC = 14;
export const CAT_OP_UPD_DEC = 15;
/**
* Every category, in numbering order.
@@ -178,6 +192,7 @@ export const ALL_CATEGORIES: Category[] = [
CAT_START_OF_DEFAULT_EXPORT,
CAT_START_OF_STMT,
CAT_START_OF_ARROW_EXPR,
CAT_CLOSE_BRACKET,
CAT_OP_UN_NOT,
CAT_OP_UN_NOT_AFTER_LT,
CAT_OP_UN_PLUS,
@@ -235,7 +250,6 @@ export function write(state: State, code: string, last: Category): void {
debugAssertCategoryMatches(state, code, last);
state.last = last;
updatePostfixClose(state, code);
state.output += code;
if (DEBUG) {
@@ -264,7 +278,6 @@ export function writeWithMap(state: State, code: string, last: Category, node: M
recordSourceMapping(state, node, LOCATION_NAMED);
state.last = last;
updatePostfixClose(state, code);
state.output += code;
if (DEBUG) {
@@ -286,7 +299,6 @@ export function writeWithMap(state: State, code: string, last: Category, node: M
* @param code - Text to append, which unlike `write` may be empty
*/
export function writeNoLast(state: State, code: string): void {
updatePostfixClose(state, code);
state.output += code;
if (DEBUG) {
@@ -309,7 +321,6 @@ export function writeNoLast(state: State, code: string): void {
export function writeWithMapNoLast(state: State, code: string, node: MappableNode): void {
recordSourceMapping(state, node, LOCATION_NAMED);
updatePostfixClose(state, code);
state.output += code;
if (DEBUG) {
@@ -335,7 +346,6 @@ export function writeWithMapEnd(
recordSourceMapping(state, node, LOCATION_END_MINUS_ONE);
state.last = last;
updatePostfixClose(state, code);
state.output += code;
if (DEBUG) {
@@ -573,16 +583,6 @@ function isDefinitelyIdentifierBoundary(code: number): boolean {
);
}
/**
* Track the final byte category Rust's postfix source-map hook checks, without reading `output`.
*/
function updatePostfixClose(state: State, code: string): void {
if (SOURCEMAPS && code.length > 0) {
const last = code.charCodeAt(code.length - 1);
state.lastWasPostfixClose = last === 41 || last === 93; // `)` or `]`
}
}
/**
* Assert that `state.last` still describes what was written last.
*
@@ -649,6 +649,8 @@ function debugAssertCategoryMatches(state: State, code: string, last: Category):
ok = last === CAT_QUESTION;
} else if (ch === "/") {
ok = last === CAT_REGEX_SLASH;
} else if (ch === ")" || ch === "]") {
ok = last === CAT_CLOSE_BRACKET;
} else {
// The final character, whole - for an astral character `ch` is only its low surrogate,
// which no Unicode property matches
-5
View File
@@ -68,10 +68,6 @@ export class State {
// means for spacing.
declare last: Category;
// Whether the last write ended in `)` or `]`. Only maps builds read this, to mirror Rust's
// trailing mapping for a postfix operand before chained punctuation.
declare lastWasPostfixClose: boolean;
// `true` between a `writeNoLast` and the write which follows it,
// i.e. while `last` describes something other than what was written last.
// Only used in debug builds. See `debugAssertLastFresh`.
@@ -133,7 +129,6 @@ export class State {
// directly would flatten V8's rope representation on every append-then-read (quadratic),
// which is why the category is tracked rather than derived.
this.last = CAT_OTHER;
this.lastWasPostfixClose = false;
// Debug-only fields for checking `last` is correct on both writes and reads
if (DEBUG) {