Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some minor parser performance improvements for ts #16461

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion Gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ function createWorker(useWorker) {
return require("./babel-worker.cjs");
}
const worker = new JestWorker(require.resolve("./babel-worker.cjs"), {
enableWorkerThreads: true,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is 0.3s faster on my local machine. :)

numWorkers,
exposedMethods: ["transform"],
});
Expand Down Expand Up @@ -942,7 +943,10 @@ gulp.task(
);

function watch() {
gulp.watch(defaultSourcesGlob, gulp.task("build-no-bundle-watch"));
gulp.watch(
defaultSourcesGlob,
gulp.series("build-no-bundle-watch", "build-cjs-bundles")
);
gulp.watch(babelStandalonePluginConfigGlob, gulp.task("generate-standalone"));
gulp.watch(buildTypingsWatchGlob, gulp.task("generate-type-helpers"));
gulp.watch(
Expand Down
32 changes: 24 additions & 8 deletions benchmark/babel-parser/real-case-ts/bench.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Benchmark from "benchmark";
import baseline from "@babel-baseline/parser";
import current from "@babel/parser";
import { report } from "../../util.mjs";
import { readFileSync } from "fs";
import { copyFileSync, readFileSync, rmSync } from "fs";
// eslint-disable-next-line import/no-extraneous-dependencies
import { commonJS } from "$repo-utils";
import { Benchmark } from "../../util.mjs";

const suite = new Benchmark.Suite();
const { require } = commonJS(import.meta.url);

const benchmark = new Benchmark();

function createInput(length) {
return readFileSync(new URL("ts-parser.txt", import.meta.url), {
Expand All @@ -19,7 +20,7 @@ const inputs = [1].map(length => ({

function benchCases(name, implementation, options) {
for (const input of inputs) {
suite.add(`${name} ${input.tag} typescript parser.ts`, () => {
benchmark.add(`${name} ${input.tag} typescript parser.ts`, () => {
implementation(input.body, {
sourceType: "module",
plugins: ["typescript"],
Expand All @@ -28,10 +29,25 @@ function benchCases(name, implementation, options) {
});
}
}
copyFileSync(
require.resolve("@babel-baseline/parser"),
"./parser-baseline.cjs"
);
copyFileSync(
"../../../packages/babel-parser/lib/index.js",
"./parser-current.cjs"
);

const baseline = require("./parser-baseline.cjs");
const current = require("./parser-current.cjs");

Comment on lines +32 to +42
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to do this otherwise I would get unstable benchmarks for current and I don't understand why.
I suspect that might have something to do with it, but it's still weird. It shouldn't cause any effect.
image

benchCases("current", current.parse);
benchCases("baseline", baseline.parse);
benchCases("current", current.parse);
benchCases("baseline", baseline.parse);

suite.on("cycle", report).run();
benchmark.suite.on("complete", () => {
rmSync("./parser-current.cjs");
rmSync("./parser-baseline.cjs");
});
benchmark.run();
12 changes: 6 additions & 6 deletions benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"private": true,
"type": "module",
"devDependencies": {
"@babel-baseline/core": "npm:@babel/core@7.23.2",
"@babel-baseline/generator": "npm:@babel/generator@7.23.0",
"@babel-baseline/helper-compilation-targets": "npm:@babel/helper-compilation-targets@7.22.15",
"@babel-baseline/core": "npm:@babel/core@7.24.4",
"@babel-baseline/generator": "npm:@babel/generator@7.24.4",
"@babel-baseline/helper-compilation-targets": "npm:@babel/helper-compilation-targets@7.23.6",
"@babel-baseline/helper-validator-identifier": "npm:@babel/[email protected]",
"@babel-baseline/parser": "npm:@babel/parser@7.23.0",
"@babel-baseline/traverse": "npm:@babel/traverse@7.23.2",
"@babel-baseline/types": "npm:@babel/types@7.23.0",
"@babel-baseline/parser": "npm:@babel/parser@7.24.4",
"@babel-baseline/traverse": "npm:@babel/traverse@7.24.1",
"@babel-baseline/types": "npm:@babel/types@7.24.0",
"@babel/core": "workspace:^",
"@babel/generator": "workspace:^",
"@babel/helper-compilation-targets": "workspace:^",
Expand Down
42 changes: 27 additions & 15 deletions packages/babel-parser/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import type { Options } from "./options.ts";
import {
hasPlugin,
validatePlugins,
mixinPluginNames,
mixinPlugins,
type PluginList,
} from "./plugin-utils.ts";
import type {
PluginConfig as ParserPlugin,
FlowPluginOptions,
RecordAndTuplePluginOptions,
PipelineOperatorPluginOptions,
} from "./typings.ts";
import Parser from "./parser/index.ts";
import Parser, { type PluginsMap } from "./parser/index.ts";

import type { ExportedTokenType } from "./tokenizer/types.ts";
import {
Expand Down Expand Up @@ -92,33 +90,47 @@ export const tokTypes = generateExportedTokenTypes(internalTokenTypes);

function getParser(options: Options | undefined | null, input: string): Parser {
let cls = Parser;
const pluginsMap: PluginsMap = new Map();
if (options?.plugins) {
validatePlugins(options.plugins);
cls = getParserClass(options.plugins);
for (const plugin of options.plugins) {
let name, opts;
if (typeof plugin === "string") {
name = plugin;
} else {
[name, opts] = plugin;
}
if (!pluginsMap.has(name)) {
pluginsMap.set(name, opts || {});
}
}
validatePlugins(pluginsMap);
cls = getParserClass(pluginsMap);
}

return new cls(options, input);
return new cls(options, input, pluginsMap);
}

const parserClassCache: { [key: string]: { new (...args: any): Parser } } = {};
const parserClassCache = new Map<string, { new (...args: any): Parser }>();

/** Get a Parser class with plugins applied. */
function getParserClass(pluginsFromOptions: PluginList): {
function getParserClass(pluginsMap: Map<string, any>): {
new (...args: any): Parser;
} {
const pluginList = mixinPluginNames.filter(name =>
hasPlugin(pluginsFromOptions, name),
);

const key = pluginList.join("/");
let cls = parserClassCache[key];
const pluginList = [];
for (const name of mixinPluginNames) {
if (pluginsMap.has(name)) {
pluginList.push(name);
}
}
const key = pluginList.join("|");
let cls = parserClassCache.get(key);
if (!cls) {
cls = Parser;
for (const plugin of pluginList) {
// @ts-expect-error todo(flow->ts)
cls = mixinPlugins[plugin](cls);
}
parserClassCache[key] = cls;
parserClassCache.set(key, cls);
}
return cls;
}
Expand Down
24 changes: 17 additions & 7 deletions packages/babel-parser/src/parse-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface ParseErrorSpecification<ErrorDetails> {

// We should consider removing this as it now just contains the same
// information as `loc.index`.
// pos: number;
pos: number;
}

export type ParseError<ErrorDetails> = SyntaxError &
Expand Down Expand Up @@ -68,13 +68,23 @@ function defineHidden(obj: object, key: string, value: unknown) {

function toParseErrorConstructor<ErrorDetails extends object>({
toMessage,
...properties
code,
reasonCode,
syntaxPlugin,
}: ParseErrorCredentials<ErrorDetails>): ParseErrorConstructor<ErrorDetails> {
const hasMissingPlugin =
reasonCode === "MissingPlugin" || reasonCode === "MissingOneOfPlugins";
return function constructor(loc: Position, details: ErrorDetails) {
const error = new SyntaxError();
Object.assign(error, properties, { loc, pos: loc.index });
if ("missingPlugin" in details) {
Object.assign(error, { missingPlugin: details.missingPlugin });
const error: ParseError<ErrorDetails> = new SyntaxError() as any;

error.code = code as ParseErrorCode;
error.reasonCode = reasonCode;
error.loc = loc;
error.pos = loc.index;

error.syntaxPlugin = syntaxPlugin;
if (hasMissingPlugin) {
error.missingPlugin = (details as any).missingPlugin;
}

type Overrides = {
Expand Down Expand Up @@ -103,7 +113,7 @@ function toParseErrorConstructor<ErrorDetails extends object>({
},
});

return error as ParseError<ErrorDetails>;
return error;
};
}

Expand Down
24 changes: 11 additions & 13 deletions packages/babel-parser/src/parser/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,7 @@ export default abstract class ExpressionParser extends LValParser {

this.next();
node.right = this.parseMaybeAssign();
this.checkLVal(left, {
in: this.finishNode(node, "AssignmentExpression"),
});
this.checkLVal(left, this.finishNode(node, "AssignmentExpression"));
// @ts-expect-error todo(flow->ts) improve node types
return node;
} else if (ownExpressionErrors) {
Expand Down Expand Up @@ -676,9 +674,10 @@ export default abstract class ExpressionParser extends LValParser {
): N.Expression {
if (update) {
const updateExpressionNode = node as Undone<N.UpdateExpression>;
this.checkLVal(updateExpressionNode.argument, {
in: this.finishNode(updateExpressionNode, "UpdateExpression"),
});
this.checkLVal(
updateExpressionNode.argument,
this.finishNode(updateExpressionNode, "UpdateExpression"),
);
return node;
}

Expand All @@ -691,9 +690,7 @@ export default abstract class ExpressionParser extends LValParser {
node.prefix = false;
node.argument = expr;
this.next();
this.checkLVal(expr, {
in: (expr = this.finishNode(node, "UpdateExpression")),
});
this.checkLVal(expr, (expr = this.finishNode(node, "UpdateExpression")));
}
return expr;
}
Expand Down Expand Up @@ -2664,12 +2661,13 @@ export default abstract class ExpressionParser extends LValParser {
// 1. https://tc39.es/ecma262/#prod-FormalParameters
const formalParameters = { type: "FormalParameters" } as const;
for (const param of node.params) {
this.checkLVal(param, {
in: formalParameters,
binding: BindingFlag.TYPE_VAR,
this.checkLVal(
param,
formalParameters,
BindingFlag.TYPE_VAR,
checkClashes,
strictModeChanged,
});
);
}
}

Expand Down
18 changes: 6 additions & 12 deletions packages/babel-parser/src/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Options } from "../options.ts";
import type * as N from "../types.ts";
import type { PluginList } from "../plugin-utils.ts";
import { getOptions } from "../options.ts";
import StatementParser from "./statement.ts";
import ScopeHandler from "../util/scope.ts";
Expand All @@ -19,13 +18,17 @@ export default class Parser extends StatementParser {
// node: N.JSXOpeningElement,
// ): N.JSXOpeningElement;

constructor(options: Options | undefined | null, input: string) {
constructor(
options: Options | undefined | null,
input: string,
pluginsMap: PluginsMap,
) {
options = getOptions(options);
super(options, input);

this.options = options;
this.initializeScopes();
this.plugins = pluginsMap(this.options.plugins);
this.plugins = pluginsMap;
this.filename = options.sourceFilename;
}

Expand All @@ -48,12 +51,3 @@ export default class Parser extends StatementParser {
return file as N.File;
}
}

function pluginsMap(plugins: PluginList): PluginsMap {
const pluginMap: PluginsMap = new Map();
for (const plugin of plugins) {
const [name, options] = Array.isArray(plugin) ? plugin : [plugin, {}];
if (!pluginMap.has(name)) pluginMap.set(name, options || {});
}
return pluginMap;
}