perf: Improve builders of @babel/types (#16870)

* perf

* improve

* Update packages/babel-types/src/definitions/index.ts

Co-authored-by: Nicolò Ribaudo <[email protected]>

* review

* review

* review

---------

Co-authored-by: Nicolò Ribaudo <[email protected]>
This commit is contained in:
liuxingbaoyu
2025-03-22 22:38:24 +01:00
committed by GitHub
co-authored by Nicolò Ribaudo
parent 737cf3089b
commit 1e5452d448
8 changed files with 84 additions and 31 deletions
@@ -0,0 +1,20 @@
import { Benchmark, baselineTypes, currentTypes } from "../../util.mjs";
const bench = new Benchmark();
function benchCases(implementations, num) {
for (const version in implementations) {
const t = implementations[version];
const blockStatement = t.blockStatement;
const stmts = new Array(num).fill(t.emptyStatement());
bench.add(`${version} builder ${num}`, () => {
blockStatement(stmts);
});
}
}
benchCases({ baselineTypes, currentTypes }, 1);
benchCases({ baselineTypes, currentTypes }, 128);
bench.run();
+1
View File
@@ -77,6 +77,7 @@ export default [
requireConfigFile: false,
babelOptions: {
babelrc: false,
browserslistConfigFile: false,
configFile: false,
// Todo: Remove the parserOpts here after the proposal gets stage 4.
parserOpts: {
+2 -2
View File
@@ -545,7 +545,7 @@ defineType("Identifier", {
validate:
process.env.BABEL_8_BREAKING || process.env.BABEL_TYPES_8_BREAKING
? function (parent, key, node) {
const match = /\.(\w+)$/.exec(key);
const match = /\.(\w+)$/.exec(key.toString());
if (!match) return;
const [, parentKey] = match;
@@ -1025,7 +1025,7 @@ defineType("RestElement", {
validate:
process.env.BABEL_8_BREAKING || process.env.BABEL_TYPES_8_BREAKING
? function (parent: t.ArrayPattern | t.ObjectPattern, key) {
const match = /(\w+)\[(\d+)\]/.exec(key);
const match = /(\w+)\[(\d+)\]/.exec(key.toString());
if (!match) throw new Error("Internal Babel error: malformed key.");
const [, listKey, index] = match as unknown as [
@@ -12,6 +12,7 @@ import {
BUILDER_KEYS,
DEPRECATED_KEYS,
NODE_PARENT_VALIDATIONS,
allExpandedTypes,
} from "./utils.ts";
import {
PLACEHOLDERS,
@@ -27,6 +28,17 @@ import { DEPRECATED_ALIASES } from "./deprecated-aliases.ts";
FLIPPED_ALIAS_KEYS[DEPRECATED_ALIASES[deprecatedAlias]];
});
for (const { types, set } of allExpandedTypes) {
for (const type of types) {
const aliases = FLIPPED_ALIAS_KEYS[type];
if (aliases) {
aliases.forEach(set.add, set);
} else {
set.add(type);
}
}
}
const TYPES: Array<string> = [].concat(
Object.keys(VISITOR_KEYS),
Object.keys(FLIPPED_ALIAS_KEYS),
+46 -16
View File
@@ -51,7 +51,7 @@ export type Validator = (
| { shapeOf: { [x: string]: FieldOptions } }
| object
) &
((node: t.Node, key: string, val: any) => void);
((node: t.Node, key: string | { toString(): string }, val: any) => void);
export type FieldOptions = {
default?: string | number | boolean | [];
@@ -96,11 +96,21 @@ export function assertEach(callback: Validator): Validator {
function validator(node: t.Node, key: string, val: any) {
if (!Array.isArray(val)) return;
for (let i = 0; i < val.length; i++) {
const subkey = `${key}[${i}]`;
let i = 0;
// We lazily concatenate strings here for performance reasons.
// Concatenating the strings is expensive because we are actually concatenating a string and a number,
// so V8 cannot just create a "rope string" but has to allocate memory for the string resulting from the number
// This string is very rarely used, only in error paths, so we can skip the concatenation cost in most cases
const subKey = {
toString() {
return `${key}[${i}]`;
},
};
for (; i < val.length; i++) {
const v = val[i];
callback(node, subkey, v);
childValidator(node, subkey, v);
callback(node, subKey, v);
childValidator(node, subKey, v);
}
}
validator.each = callback;
@@ -123,13 +133,31 @@ export function assertOneOf(...values: Array<any>): Validator {
return validate;
}
export const allExpandedTypes: {
types: NodeTypes[];
set: Set<string>;
}[] = [];
export function assertNodeType(...types: NodeTypes[]): Validator {
const expandedTypes = new Set<string>();
allExpandedTypes.push({ types, set: expandedTypes });
function validate(node: t.Node, key: string, val: any) {
for (const type of types) {
if (is(type, val)) {
const valType = val?.type;
if (valType != null) {
if (expandedTypes.has(valType)) {
validateChild(node, key, val);
return;
}
if (valType === "Placeholder") {
for (const type of types) {
if (is(type, val)) {
validateChild(node, key, val);
return;
}
}
}
}
throw new TypeError(
@@ -137,7 +165,7 @@ export function assertNodeType(...types: NodeTypes[]): Validator {
node.type
} expected node to be of a type ${JSON.stringify(
types,
)} but instead got ${JSON.stringify(val?.type)}`,
)} but instead got ${JSON.stringify(valType)}`,
);
}
@@ -150,8 +178,9 @@ export function assertNodeOrValueType(
...types: (NodeTypes | PrimitiveTypes)[]
): Validator {
function validate(node: t.Node, key: string, val: any) {
const primitiveType = getType(val);
for (const type of types) {
if (getType(val) === type || is(type, val)) {
if (primitiveType === type || is(type, val)) {
validateChild(node, key, val);
return;
}
@@ -173,13 +202,13 @@ export function assertNodeOrValueType(
export function assertValueType(type: PrimitiveTypes): Validator {
function validate(node: t.Node, key: string, val: any) {
const valid = getType(val) === type;
if (!valid) {
throw new TypeError(
`Property ${key} expected type of ${type} but got ${getType(val)}`,
);
if (getType(val) === type) {
return;
}
throw new TypeError(
`Property ${key} expected type of ${type} but got ${getType(val)}`,
);
}
validate.type = type;
@@ -188,9 +217,10 @@ export function assertValueType(type: PrimitiveTypes): Validator {
}
export function assertShape(shape: { [x: string]: FieldOptions }): Validator {
const keys = Object.keys(shape);
function validate(node: t.Node, key: string, val: any) {
const errors = [];
for (const property of Object.keys(shape)) {
for (const property of keys) {
try {
validateField(node, property, val[property], shape[property]);
} catch (error) {
@@ -11,11 +11,7 @@ export default function isPlaceholderType(
const aliases: Array<string> | undefined =
PLACEHOLDERS_ALIAS[placeholderType];
if (aliases) {
for (const alias of aliases) {
if (targetType === alias) return true;
}
}
if (aliases?.includes(targetType)) return true;
return false;
}
@@ -26,13 +26,7 @@ export default function isType(nodeType: string, targetType: string): boolean {
if (ALIAS_KEYS[targetType]) return false;
const aliases: Array<string> | undefined = FLIPPED_ALIAS_KEYS[targetType];
if (aliases) {
if (aliases[0] === nodeType) return true;
for (const alias of aliases) {
if (nodeType === alias) return true;
}
}
if (aliases?.includes(nodeType)) return true;
return false;
}
@@ -53,7 +53,7 @@ export function validateField(
export function validateChild(
node: t.Node | undefined | null,
key: string,
key: string | { toString(): string },
val?: unknown,
) {
const type = (val as t.Node)?.type;