diff --git a/packages/babel-parser/benchmark/many-identifiers/11-length.bench.mjs b/packages/babel-parser/benchmark/many-identifiers/11-length.bench.mjs new file mode 100644 index 000000000000..67d3c259280a --- /dev/null +++ b/packages/babel-parser/benchmark/many-identifiers/11-length.bench.mjs @@ -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(); diff --git a/packages/babel-parser/benchmark/many-identifiers/await.mjs b/packages/babel-parser/benchmark/many-identifiers/await.mjs new file mode 100644 index 000000000000..9bf634ecebd7 --- /dev/null +++ b/packages/babel-parser/benchmark/many-identifiers/await.mjs @@ -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(); diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index eb2ddaa9e14e..c1f6aa6d24be 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -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; diff --git a/packages/babel-parser/src/util/identifier.js b/packages/babel-parser/src/util/identifier.js index 8831967edb85..d9674d50ffc9 100644 --- a/packages/babel-parser/src/util/identifier.js +++ b/packages/babel-parser/src/util/identifier.js @@ -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); +} diff --git a/packages/babel-parser/src/util/scope.js b/packages/babel-parser/src/util/scope.js index 03c01f57d844..d9dbe44f0e4c 100644 --- a/packages/babel-parser/src/util/scope.js +++ b/packages/babel-parser/src/util/scope.js @@ -49,22 +49,26 @@ export default class ScopeHandler { } 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 { } // $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; } } }