Faster checkReservedWord (#13386)
* perf: faster parser scope check * perf: early return for identifier length > 10 * perf: early return for normal identifier names * chore: add benchmark * Update packages/babel-parser/src/parser/expression.js
This commit is contained in:
parent
ae3f5d905a
commit
cbad50ac1d
@ -0,0 +1,23 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "../../lib/index-main.js";
|
||||
import current from "../../lib/index.js";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
function createInput(length) {
|
||||
return "abcde12345z;".repeat(length);
|
||||
}
|
||||
current.parse("a");
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [64, 128, 256, 512]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} length-11 identifiers`, () => {
|
||||
implementation.parse(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline);
|
||||
benchCases("current", current);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
23
packages/babel-parser/benchmark/many-identifiers/await.mjs
Normal file
23
packages/babel-parser/benchmark/many-identifiers/await.mjs
Normal file
@ -0,0 +1,23 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "../../lib/index-main.js";
|
||||
import current from "../../lib/index.js";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
function createInput(length) {
|
||||
return "await;".repeat(length);
|
||||
}
|
||||
current.parse("a");
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [64, 128, 256, 512]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} await identifier`, () => {
|
||||
implementation.parse(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline);
|
||||
benchCases("current", current);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
@ -28,6 +28,7 @@ import {
|
||||
isStrictReservedWord,
|
||||
isStrictBindReservedWord,
|
||||
isIdentifierStart,
|
||||
canBeReservedWord,
|
||||
} from "../util/identifier";
|
||||
import type { Pos } from "../util/location";
|
||||
import { Position } from "../util/location";
|
||||
@ -2358,13 +2359,14 @@ export default class ExpressionParser extends LValParser {
|
||||
// `class` and `function` keywords push function-type token context into this.context.
|
||||
// But there is no chance to pop the context if the keyword is consumed
|
||||
// as an identifier such as a property name.
|
||||
const curContext = this.curContext();
|
||||
if (
|
||||
(type === tt._class || type === tt._function) &&
|
||||
(curContext === ct.functionStatement ||
|
||||
curContext === ct.functionExpression)
|
||||
) {
|
||||
this.state.context.pop();
|
||||
if (type === tt._class || type === tt._function) {
|
||||
const curContext = this.curContext();
|
||||
if (
|
||||
curContext === ct.functionStatement ||
|
||||
curContext === ct.functionExpression
|
||||
) {
|
||||
this.state.context.pop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw this.unexpected();
|
||||
@ -2389,12 +2391,22 @@ export default class ExpressionParser extends LValParser {
|
||||
checkKeywords: boolean,
|
||||
isBinding: boolean,
|
||||
): void {
|
||||
if (this.prodParam.hasYield && word === "yield") {
|
||||
this.raise(startLoc, Errors.YieldBindingIdentifier);
|
||||
// Every JavaScript reserved word is 10 characters or less.
|
||||
if (word.length > 10) {
|
||||
return;
|
||||
}
|
||||
// Most identifiers are not reservedWord-like, they don't need special
|
||||
// treatments afterward, which very likely ends up throwing errors
|
||||
if (!canBeReservedWord(word)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (word === "await") {
|
||||
if (word === "yield") {
|
||||
if (this.prodParam.hasYield) {
|
||||
this.raise(startLoc, Errors.YieldBindingIdentifier);
|
||||
return;
|
||||
}
|
||||
} else if (word === "await") {
|
||||
if (this.prodParam.hasAwait) {
|
||||
this.raise(startLoc, Errors.AwaitBindingIdentifier);
|
||||
return;
|
||||
@ -2407,16 +2419,13 @@ export default class ExpressionParser extends LValParser {
|
||||
Errors.AwaitBindingIdentifier,
|
||||
);
|
||||
}
|
||||
} else if (word === "arguments") {
|
||||
if (this.scope.inClassAndNotInNonArrowFunction) {
|
||||
this.raise(startLoc, Errors.ArgumentsInClass);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.scope.inClass &&
|
||||
!this.scope.inNonArrowFunction &&
|
||||
word === "arguments"
|
||||
) {
|
||||
this.raise(startLoc, Errors.ArgumentsInClass);
|
||||
return;
|
||||
}
|
||||
if (checkKeywords && isKeyword(word)) {
|
||||
this.raise(startLoc, Errors.UnexpectedKeyword, word);
|
||||
return;
|
||||
|
||||
@ -21,3 +21,66 @@ export const keywordRelationalOperator = /^in(stanceof)?$/;
|
||||
export function isIteratorStart(current: number, next: number): boolean {
|
||||
return current === charCodes.atSign && next === charCodes.atSign;
|
||||
}
|
||||
|
||||
// This is the comprehensive set of JavaScript reserved words
|
||||
// If a word is in this set, it could be a reserved word,
|
||||
// depending on sourceType/strictMode/binding info. In other words
|
||||
// if a word is not in this set, it is not a reserved word under
|
||||
// any circumstance.
|
||||
const reservedWordLikeSet = new Set([
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"continue",
|
||||
"debugger",
|
||||
"default",
|
||||
"do",
|
||||
"else",
|
||||
"finally",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"return",
|
||||
"switch",
|
||||
"throw",
|
||||
"try",
|
||||
"var",
|
||||
"const",
|
||||
"while",
|
||||
"with",
|
||||
"new",
|
||||
"this",
|
||||
"super",
|
||||
"class",
|
||||
"extends",
|
||||
"export",
|
||||
"import",
|
||||
"null",
|
||||
"true",
|
||||
"false",
|
||||
"in",
|
||||
"instanceof",
|
||||
"typeof",
|
||||
"void",
|
||||
"delete",
|
||||
// strict
|
||||
"implements",
|
||||
"interface",
|
||||
"let",
|
||||
"package",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"static",
|
||||
"yield",
|
||||
// strictBind
|
||||
"eval",
|
||||
"arguments",
|
||||
// reservedWorkLike
|
||||
"enum",
|
||||
"await",
|
||||
]);
|
||||
|
||||
export function canBeReservedWord(word: string): boolean {
|
||||
return reservedWordLikeSet.has(word);
|
||||
}
|
||||
|
||||
@ -49,22 +49,26 @@ export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
}
|
||||
|
||||
get inFunction() {
|
||||
return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0;
|
||||
return (this.currentVarScopeFlags() & SCOPE_FUNCTION) > 0;
|
||||
}
|
||||
get allowSuper() {
|
||||
return (this.currentThisScope().flags & SCOPE_SUPER) > 0;
|
||||
return (this.currentThisScopeFlags() & SCOPE_SUPER) > 0;
|
||||
}
|
||||
get allowDirectSuper() {
|
||||
return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0;
|
||||
return (this.currentThisScopeFlags() & SCOPE_DIRECT_SUPER) > 0;
|
||||
}
|
||||
get inClass() {
|
||||
return (this.currentThisScope().flags & SCOPE_CLASS) > 0;
|
||||
return (this.currentThisScopeFlags() & SCOPE_CLASS) > 0;
|
||||
}
|
||||
get inClassAndNotInNonArrowFunction() {
|
||||
const flags = this.currentThisScopeFlags();
|
||||
return (flags & SCOPE_CLASS) > 0 && (flags & SCOPE_FUNCTION) === 0;
|
||||
}
|
||||
get inStaticBlock() {
|
||||
return (this.currentThisScope().flags & SCOPE_STATIC_BLOCK) > 0;
|
||||
return (this.currentThisScopeFlags() & SCOPE_STATIC_BLOCK) > 0;
|
||||
}
|
||||
get inNonArrowFunction() {
|
||||
return (this.currentThisScope().flags & SCOPE_FUNCTION) > 0;
|
||||
return (this.currentThisScopeFlags() & SCOPE_FUNCTION) > 0;
|
||||
}
|
||||
get treatFunctionsAsVar() {
|
||||
return this.treatFunctionsAsVarInScope(this.currentScope());
|
||||
@ -189,25 +193,22 @@ export default class ScopeHandler<IScope: Scope = Scope> {
|
||||
}
|
||||
|
||||
// $FlowIgnore
|
||||
currentVarScope(): IScope {
|
||||
currentVarScopeFlags(): ScopeFlags {
|
||||
for (let i = this.scopeStack.length - 1; ; i--) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (scope.flags & SCOPE_VAR) {
|
||||
return scope;
|
||||
const { flags } = this.scopeStack[i];
|
||||
if (flags & SCOPE_VAR) {
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Could be useful for `arguments`, `this`, `new.target`, `super()`, `super.property`, and `super[property]`.
|
||||
// $FlowIgnore
|
||||
currentThisScope(): IScope {
|
||||
currentThisScopeFlags(): ScopeFlags {
|
||||
for (let i = this.scopeStack.length - 1; ; i--) {
|
||||
const scope = this.scopeStack[i];
|
||||
if (
|
||||
(scope.flags & SCOPE_VAR || scope.flags & SCOPE_CLASS) &&
|
||||
!(scope.flags & SCOPE_ARROW)
|
||||
) {
|
||||
return scope;
|
||||
const { flags } = this.scopeStack[i];
|
||||
if (flags & (SCOPE_VAR | SCOPE_CLASS) && !(flags & SCOPE_ARROW)) {
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user