diff --git a/packages/babel-parser/benchmark/many-let-declarations/10-length-binding.mjs b/packages/babel-parser/benchmark/many-let-declarations-within-block/10-length-binding.mjs similarity index 100% rename from packages/babel-parser/benchmark/many-let-declarations/10-length-binding.mjs rename to packages/babel-parser/benchmark/many-let-declarations-within-block/10-length-binding.mjs diff --git a/packages/babel-parser/benchmark/many-let-declarations/1-length-binding.mjs b/packages/babel-parser/benchmark/many-let-declarations/1-length-binding.mjs new file mode 100644 index 0000000000..3fd82c1c10 --- /dev/null +++ b/packages/babel-parser/benchmark/many-let-declarations/1-length-binding.mjs @@ -0,0 +1,34 @@ +import Benchmark from "benchmark"; +import baseline from "@babel-baseline/parser"; +import current from "../../lib/index.js"; +import { report } from "../util.mjs"; + +const suite = new Benchmark.Suite(); +// All codepoints in [0x4e00, 0x9ffc] are valid identifier name per Unicode 13 +function createInput(length) { + if (length > 0x9ffc - 0x4e00) { + throw new Error( + `Length greater than ${ + 0x9ffc - 0x4e00 + } is not supported! Consider modify the \`createInput\`.` + ); + } + let source = ""; + for (let i = 0; i < length; i++) { + source += "let " + String.fromCharCode(0x4e00 + i) + ";"; + } + return source; +} +function benchCases(name, implementation, options) { + for (const length of [256, 512, 1024, 2048]) { + const input = createInput(length); + suite.add(`${name} ${length} length-1 let bindings`, () => { + implementation.parse(input, options); + }); + } +} + +benchCases("baseline", baseline); +benchCases("current", current); + +suite.on("cycle", report).run(); diff --git a/packages/babel-parser/src/plugins/flow/scope.js b/packages/babel-parser/src/plugins/flow/scope.js index eb98bdbcc9..f849c29c00 100644 --- a/packages/babel-parser/src/plugins/flow/scope.js +++ b/packages/babel-parser/src/plugins/flow/scope.js @@ -11,7 +11,7 @@ import * as N from "../../types"; // Reference implementation: https://github.com/facebook/flow/blob/23aeb2a2ef6eb4241ce178fde5d8f17c5f747fb5/src/typing/env.ml#L536-L584 class FlowScope extends Scope { // declare function foo(): type; - declareFunctions: string[] = []; + declareFunctions: Set = new Set(); } export default class FlowScopeHandler extends ScopeHandler { @@ -24,7 +24,7 @@ export default class FlowScopeHandler extends ScopeHandler { if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) { this.checkRedeclarationInScope(scope, name, bindingType, pos); this.maybeExportDefined(scope, name); - scope.declareFunctions.push(name); + scope.declareFunctions.add(name); return; } @@ -40,8 +40,8 @@ export default class FlowScopeHandler extends ScopeHandler { if (bindingType & BIND_FLAGS_FLOW_DECLARE_FN) { return ( - !scope.declareFunctions.includes(name) && - (scope.lexical.includes(name) || scope.functions.includes(name)) + !scope.declareFunctions.has(name) && + (scope.lexical.has(name) || scope.functions.has(name)) ); } @@ -49,7 +49,7 @@ export default class FlowScopeHandler extends ScopeHandler { } checkLocalExport(id: N.Identifier) { - if (this.scopeStack[0].declareFunctions.indexOf(id.name) === -1) { + if (!this.scopeStack[0].declareFunctions.has(id.name)) { super.checkLocalExport(id); } } diff --git a/packages/babel-parser/src/plugins/typescript/scope.js b/packages/babel-parser/src/plugins/typescript/scope.js index 085b90e734..657a3109af 100644 --- a/packages/babel-parser/src/plugins/typescript/scope.js +++ b/packages/babel-parser/src/plugins/typescript/scope.js @@ -14,22 +14,22 @@ import { import * as N from "../../types"; class TypeScriptScope extends Scope { - types: string[] = []; + types: Set = new Set(); // enums (which are also in .types) - enums: string[] = []; + enums: Set = new Set(); // const enums (which are also in .enums and .types) - constEnums: string[] = []; + constEnums: Set = new Set(); // classes (which are also in .lexical) and interface (which are also in .types) - classes: string[] = []; + classes: Set = new Set(); // namespaces and ambient functions (or classes) are too difficult to track, // especially without type analysis. // We need to track them anyway, to avoid "X is not defined" errors // when exporting them. - exportOnlyBindings: string[] = []; + exportOnlyBindings: Set = new Set(); } // See https://github.com/babel/babel/pull/9766#discussion_r268920730 for an @@ -44,7 +44,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler -1) { + if (scope.enums.has(name)) { if (bindingType & BIND_FLAGS_TS_ENUM) { // Enums can be merged with other enums if they are both // const or both non-const. const isConst = !!(bindingType & BIND_FLAGS_TS_CONST_ENUM); - const wasConst = scope.constEnums.indexOf(name) > -1; + const wasConst = scope.constEnums.has(name); return isConst !== wasConst; } return true; } - if (bindingType & BIND_FLAGS_CLASS && scope.classes.indexOf(name) > -1) { - if (scope.lexical.indexOf(name) > -1) { + if (bindingType & BIND_FLAGS_CLASS && scope.classes.has(name)) { + if (scope.lexical.has(name)) { // Classes can be merged with interfaces return !!(bindingType & BIND_KIND_VALUE); } else { @@ -87,7 +87,7 @@ export default class TypeScriptScopeHandler extends ScopeHandler -1) { + if (bindingType & BIND_KIND_TYPE && scope.types.has(name)) { return true; } @@ -95,9 +95,11 @@ export default class TypeScriptScopeHandler extends ScopeHandler = new Set(); + // A set of lexically-declared names in the current lexical scope + lexical: Set = new Set(); + // A set of lexically-declared FunctionDeclaration names in the current lexical scope + functions: Set = new Set(); constructor(flags: ScopeFlags) { this.flags = flags; @@ -104,9 +104,9 @@ export default class ScopeHandler { this.checkRedeclarationInScope(scope, name, bindingType, pos); if (bindingType & BIND_SCOPE_FUNCTION) { - scope.functions.push(name); + scope.functions.add(name); } else { - scope.lexical.push(name); + scope.lexical.add(name); } if (bindingType & BIND_SCOPE_LEXICAL) { @@ -116,7 +116,7 @@ export default class ScopeHandler { for (let i = this.scopeStack.length - 1; i >= 0; --i) { scope = this.scopeStack[i]; this.checkRedeclarationInScope(scope, name, bindingType, pos); - scope.var.push(name); + scope.var.add(name); this.maybeExportDefined(scope, name); if (scope.flags & SCOPE_VAR) break; @@ -153,38 +153,41 @@ export default class ScopeHandler { if (bindingType & BIND_SCOPE_LEXICAL) { return ( - scope.lexical.indexOf(name) > -1 || - scope.functions.indexOf(name) > -1 || - scope.var.indexOf(name) > -1 + scope.lexical.has(name) || + scope.functions.has(name) || + scope.var.has(name) ); } if (bindingType & BIND_SCOPE_FUNCTION) { return ( - scope.lexical.indexOf(name) > -1 || - (!this.treatFunctionsAsVarInScope(scope) && - scope.var.indexOf(name) > -1) + scope.lexical.has(name) || + (!this.treatFunctionsAsVarInScope(scope) && scope.var.has(name)) ); } return ( - (scope.lexical.indexOf(name) > -1 && - !(scope.flags & SCOPE_SIMPLE_CATCH && scope.lexical[0] === name)) || - (!this.treatFunctionsAsVarInScope(scope) && - scope.functions.indexOf(name) > -1) + (scope.lexical.has(name) && + !( + scope.flags & SCOPE_SIMPLE_CATCH && + scope.lexical.values().next().value === name + )) || + (!this.treatFunctionsAsVarInScope(scope) && scope.functions.has(name)) ); } checkLocalExport(id: N.Identifier) { + const { name } = id; + const topLevelScope = this.scopeStack[0]; if ( - this.scopeStack[0].lexical.indexOf(id.name) === -1 && - this.scopeStack[0].var.indexOf(id.name) === -1 && + !topLevelScope.lexical.has(name) && + !topLevelScope.var.has(name) && // In strict mode, scope.functions will always be empty. // Modules are strict by default, but the `scriptMode` option // can overwrite this behavior. - this.scopeStack[0].functions.indexOf(id.name) === -1 + !topLevelScope.functions.has(name) ) { - this.undefinedExports.set(id.name, id.start); + this.undefinedExports.set(name, id.start); } }