UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

1,426 lines (1,357 loc) 141 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Alexander Akait @alexander-akait */ "use strict"; // cspell:ignore yuku binop prec Prec const { Parser: BaseParser, tokTypes } = require("acorn"); // acorn exports its token-context table but leaves it out of its public types const tokContexts = /** @type {Record<string, unknown>} */ ( /** @type {{ tokContexts: Record<string, unknown> }} */ (/** @type {unknown} */ (require("acorn"))).tokContexts ); /** @typedef {{ token: string, isExpr: boolean, preserveSpace?: boolean, override?: unknown }} TokContextShim acorn TokContext fields read by the owned tokenizer */ // acorn's token contexts used by the inlined finishToken context updates const CTX_B_STAT = /** @type {TokContextShim} */ (tokContexts.b_stat); const CTX_B_EXPR = /** @type {TokContextShim} */ (tokContexts.b_expr); const CTX_P_STAT = /** @type {TokContextShim} */ (tokContexts.p_stat); const CTX_P_EXPR = /** @type {TokContextShim} */ (tokContexts.p_expr); const CTX_F_STAT = /** @type {TokContextShim} */ (tokContexts.f_stat); const CTX_F_EXPR = /** @type {TokContextShim} */ (tokContexts.f_expr); // acorn exports its keyword→TokenType map but leaves it out of its public // types; used by the word-classification lookups below. const keywordTypes = /** @type {Record<string, TokenType>} */ ( /** @type {{ keywordTypes: Record<string, TokenType> }} */ (/** @type {unknown} */ (require("acorn"))).keywordTypes ); /** @typedef {import("acorn").Options} Options */ /** @typedef {import("acorn").Position} Position */ /** @typedef {import("acorn").Node} Node */ /** @typedef {import("acorn").Identifier} Identifier */ /** @typedef {import("acorn").ImportAttribute} ImportAttribute */ /** @typedef {import("acorn").ImportDefaultSpecifier} ImportDefaultSpecifier */ /** @typedef {import("acorn").ImportExpression} ImportExpression */ /** @typedef {import("acorn").Expression} Expression */ /** @typedef {import("acorn").ImportSpecifier | import("acorn").ImportDefaultSpecifier | import("acorn").ImportNamespaceSpecifier} AnyImportSpecifier */ /** @typedef {import("acorn").TokenType} TokenType */ /** @typedef {TokenType & { beforeExpr: boolean, isAssign?: boolean, prefix?: boolean, postfix?: boolean, binop: number | null, updateContext?: (prevType: TokenType) => void }} TokenTypeInternal acorn's internal TokenType fields, absent from its public types */ /** @typedef {import("estree").SourceLocation} SourceLocation */ /** @typedef {[number, number]} Range */ /** @typedef {"defer" | "source"} ImportPhase */ /** @typedef {import("estree").Comment & { start: number, end: number }} CollectedComment comment as JavascriptParser exposes it */ // Symbol-keyed so they stay out of for-in, Object.keys and JSON.stringify // over AST nodes. const kSource = Symbol("source"); const kRange = Symbol("range"); const kText = Symbol("text"); const kTextStart = Symbol("text start"); // Marks import attributes parsed from the legacy `assert {...}` syntax. const LEGACY_ASSERT_ATTRIBUTES = Symbol("assert"); // acorn's binding types and scope flags, stable across acorn 8 const BIND_VAR = 1; const BIND_LEXICAL = 2; const SCOPE_TOP = 1; const SCOPE_SIMPLE_CATCH = 32; // SCOPE_TOP | SCOPE_FUNCTION | SCOPE_CLASS_STATIC_BLOCK const SCOPE_VAR = 0b100000011; // ASCII identifier-continuation chars ($ 0-9 A-Z _ a-z); css/html-style // Uint8Array table so the tokenizer fast path is one load per char const IDENT_CHAR = new Uint8Array(128); IDENT_CHAR[36] = 1; IDENT_CHAR[95] = 1; for (let i = 48; i <= 57; i++) IDENT_CHAR[i] = 1; for (let i = 65; i <= 90; i++) IDENT_CHAR[i] = 1; for (let i = 97; i <= 122; i++) IDENT_CHAR[i] = 1; // ASCII identifier-start chars (IDENT_CHAR minus 0-9), for token dispatch in // the owned `nextToken` loop. const IDENT_START = new Uint8Array(128); IDENT_START[36] = 1; IDENT_START[95] = 1; for (let i = 65; i <= 90; i++) IDENT_START[i] = 1; for (let i = 97; i <= 122; i++) IDENT_START[i] = 1; // Single-char punctuators that acorn's `getTokenFromCode` reads as just // `++pos; finishToken(type)` (no value, no operator state machine). Dispatching // them from `nextToken`'s char table skips the extra `getTokenFromCode` call and // its switch for the commonest tokens in JS ( ) { } [ ] ; , : — `0` is "not a // simple punctuator" since token types are truthy objects. const SIMPLE_PUNCT = Array.from({ length: 128 }); SIMPLE_PUNCT[40] = tokTypes.parenL; SIMPLE_PUNCT[41] = tokTypes.parenR; SIMPLE_PUNCT[59] = tokTypes.semi; SIMPLE_PUNCT[44] = tokTypes.comma; SIMPLE_PUNCT[91] = tokTypes.bracketL; SIMPLE_PUNCT[93] = tokTypes.bracketR; SIMPLE_PUNCT[123] = tokTypes.braceL; SIMPLE_PUNCT[125] = tokTypes.braceR; SIMPLE_PUNCT[58] = tokTypes.colon; // Char classification for the owned `nextToken` (yuku's ws_class): one table // load steers both the whitespace skip loop and the token dispatch. Token // classes sort below CLS_SPACE so the skip loop exits on a single compare. const CLS_OTHER = 0; const CLS_IDENT = 1; const CLS_PUNCT = 2; const CLS_DOT = 3; const CLS_EQ = 4; const CLS_UNICODE = 5; const CLS_SPACE = 6; const CLS_NEWLINE = 7; const CLS_SLASH = 8; // Full `charCodeAt` range so the scan loop needs no `code > 127` branch per // character: every non-ASCII code unit classifies as CLS_UNICODE (which sorts // below CLS_SPACE, so the loop exits on the same single compare) and the // dispatch delegates it to acorn's unicode-aware paths. const CHAR_CLASS = new Uint8Array(0x10000).fill(CLS_UNICODE, 128); for (let i = 0; i < 128; i++) { if (IDENT_START[i] === 1 || i === 92) CHAR_CLASS[i] = CLS_IDENT; else if (SIMPLE_PUNCT[i] !== undefined) CHAR_CLASS[i] = CLS_PUNCT; } CHAR_CLASS[46] = CLS_DOT; CHAR_CLASS[61] = CLS_EQ; CHAR_CLASS[32] = CLS_SPACE; CHAR_CLASS[9] = CLS_SPACE; CHAR_CLASS[11] = CLS_SPACE; CHAR_CLASS[12] = CLS_SPACE; CHAR_CLASS[10] = CLS_NEWLINE; CHAR_CLASS[13] = CLS_NEWLINE; CHAR_CLASS[47] = CLS_SLASH; /** * Drop-in replacement for acorn's `Node` that materializes `loc` and `range` * on first access instead of allocating them during parsing. Most nodes never * get either read, which saves three objects and an array per node. */ class LazyLocNode { /** * @param {number} pos start offset */ constructor(pos) { this.type = ""; this.start = pos; this.end = 0; } /** * Memoized in a symbol slot — a plain store is far cheaper than making the * property own via defineProperty, and the slot stays invisible to for-in, * Object.keys and JSON.stringify. No `loc` is served at all — locations * are derived from offsets via `JavascriptParser#getLocation`. * @returns {Range} source range */ get range() { const cached = this[kRange]; if (cached !== undefined) return cached; /** @type {Range} */ const range = [this.start, this.end]; if (this.end > 0) this[kRange] = range; return range; } /** * @param {Range} value source range */ set range(value) { this[kRange] = value; } } /** * Single-shape `Identifier`, the most common node: all fields are assigned in * one constructor, so every instance is born on its final hidden class instead * of transitioning through acorn's start-empty-then-mutate construction. */ class IdentifierNode { /** * @param {number} start start offset * @param {number} end end offset * @param {string} name identifier name */ constructor(start, end, name) { this.type = "Identifier"; this.start = start; this.end = end; this.name = name; } } /** * Single-shape `Literal`; `bigint` and `regex` stay post-construction * additions since both are rare. */ class LiteralNode { /** * @param {number} start start offset * @param {number} end end offset * @param {unknown} value literal value * @param {string} raw literal source text */ constructor(start, end, value, raw) { this.type = "Literal"; this.start = start; this.end = end; this.value = value; this.raw = raw; } } /** * Single-shape `MemberExpression`. `optional` is a real field on every * instance since webpack always parses with `ecmaVersion >= 11`. */ class MemberExpressionNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} object object expression * @param {Node} property property node * @param {boolean} computed whether the access is computed (`a[b]`) * @param {boolean} optional whether the access is optional (`a?.b`) */ constructor(start, end, object, property, computed, optional) { this.type = "MemberExpression"; this.start = start; this.end = end; this.object = object; this.property = property; this.computed = computed; this.optional = optional; } } /** * Single-shape `CallExpression`; `optional` as in `MemberExpressionNode`. */ class CallExpressionNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} callee callee expression * @param {Node[]} args call arguments * @param {boolean} optional whether the call is optional (`a?.()`) */ constructor(start, end, callee, args, optional) { this.type = "CallExpression"; this.start = start; this.end = end; this.callee = callee; this.arguments = args; this.optional = optional; } } /** * Single-shape `ThisExpression`. */ class ThisNode { /** * @param {number} start start offset * @param {number} end end offset */ constructor(start, end) { this.type = "ThisExpression"; this.start = start; this.end = end; } } /** * Single-shape `BinaryExpression`/`LogicalExpression` — identical field sets, * so both node types share one hidden class. */ class BinaryNode { /** * @param {number} start start offset * @param {number} end end offset * @param {"BinaryExpression" | "LogicalExpression"} type node type * @param {Expression} left left operand * @param {string} operator operator text * @param {Expression} right right operand */ constructor(start, end, type, left, operator, right) { this.type = type; this.start = start; this.end = end; this.left = left; this.operator = operator; this.right = right; } } /** * Single-shape `AssignmentExpression`. */ class AssignmentNode { /** * @param {number} start start offset * @param {number} end end offset * @param {string} operator assignment operator text * @param {Node} left assignment target * @param {Expression} right assigned value */ constructor(start, end, operator, left, right) { this.type = "AssignmentExpression"; this.start = start; this.end = end; this.operator = operator; this.left = left; this.right = right; } } /** * Single-shape `UnaryExpression`/`UpdateExpression` — identical field sets, * so both node types share one hidden class. */ class UnaryNode { /** * @param {number} start start offset * @param {number} end end offset * @param {"UnaryExpression" | "UpdateExpression"} type node type * @param {string} operator operator text * @param {boolean} prefix whether the operator is prefixed * @param {Expression} argument operand */ constructor(start, end, type, operator, prefix, argument) { this.type = type; this.start = start; this.end = end; this.operator = operator; this.prefix = prefix; this.argument = argument; } } /** * Single-shape `VariableDeclaration` (statement position; `for` heads keep the * generic node since their caller finishes them). */ class VariableDeclarationNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Node[]} declarations declarators * @param {string} kind declaration kind (`var`/`let`/`const`/`using`) */ constructor(start, end, declarations, kind) { this.type = "VariableDeclaration"; this.start = start; this.end = end; this.declarations = declarations; this.kind = kind; } } /** * Single-shape `VariableDeclarator`. */ class VariableDeclaratorNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Node} id binding target * @param {Expression | null} init initializer */ constructor(start, end, id, init) { this.type = "VariableDeclarator"; this.start = start; this.end = end; this.id = id; this.init = init; } } /** * Single-shape `ExpressionStatement`. */ class ExpressionStatementNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} expression the statement's expression */ constructor(start, end, expression) { this.type = "ExpressionStatement"; this.start = start; this.end = end; this.expression = expression; } } /** * Single-shape `BlockStatement`. */ class BlockStatementNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Node[]} body statements */ constructor(start, end, body) { this.type = "BlockStatement"; this.start = start; this.end = end; this.body = body; } } /** * Single-shape `IfStatement`. */ class IfStatementNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} test condition * @param {Node} consequent then-branch * @param {Node | null} alternate else-branch */ constructor(start, end, test, consequent, alternate) { this.type = "IfStatement"; this.start = start; this.end = end; this.test = test; this.consequent = consequent; this.alternate = alternate; } } /** * Single-shape `ReturnStatement`. */ class ReturnStatementNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression | null} argument returned expression */ constructor(start, end, argument) { this.type = "ReturnStatement"; this.start = start; this.end = end; this.argument = argument; } } /** * Single-shape `ConditionalExpression`. */ class ConditionalExpressionNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} test condition * @param {Expression} consequent then-value * @param {Expression} alternate else-value */ constructor(start, end, test, consequent, alternate) { this.type = "ConditionalExpression"; this.start = start; this.end = end; this.test = test; this.consequent = consequent; this.alternate = alternate; } } /** * Single-shape `NewExpression`. */ class NewExpressionNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression} callee constructed expression * @param {Expression[]} args constructor arguments */ constructor(start, end, callee, args) { this.type = "NewExpression"; this.start = start; this.end = end; this.callee = callee; this.arguments = args; } } /** * Single-shape `ArrayExpression`. */ class ArrayExpressionNode { /** * @param {number} start start offset * @param {number} end end offset * @param {(Expression | null)[]} elements array elements (`null` for holes) */ constructor(start, end, elements) { this.type = "ArrayExpression"; this.start = start; this.end = end; this.elements = elements; } } /** * Single-shape `TemplateLiteral`. */ class TemplateLiteralNode { /** * @param {number} start start offset * @param {number} end end offset * @param {Expression[]} expressions substitution expressions * @param {Node[]} quasis template chunks */ constructor(start, end, expressions, quasis) { this.type = "TemplateLiteral"; this.start = start; this.end = end; this.expressions = expressions; this.quasis = quasis; } } /** * Single-shape `TemplateElement`. */ class TemplateElementNode { /** * @param {number} start start offset * @param {number} end end offset * @param {{ raw: string, cooked: string | null }} value chunk text * @param {boolean} tail whether this is the closing chunk */ constructor(start, end, value, tail) { this.type = "TemplateElement"; this.start = start; this.end = end; this.value = value; this.tail = tail; } } /** * Single-shape `ObjectExpression`/`ObjectPattern` — identical field sets, so * both node types share one hidden class. */ class ObjectNode { /** * @param {number} start start offset * @param {number} end end offset * @param {"ObjectExpression" | "ObjectPattern"} type node type * @param {Node[]} properties properties */ constructor(start, end, type, properties) { this.type = type; this.start = start; this.end = end; this.properties = properties; } } /** * Pre-shaped `Property`: acorn fills property nodes through shared * subroutines (`parsePropertyName`/`parsePropertyValue`), so instead of * rebuilding that flow the fields are all declared up-front and acorn's * writes land in existing slots — one hidden class, no transitions (yuku's * decoder emits `Property` with this fixed shape). Every non-throwing acorn * branch assigns `computed`, `key`, `value` and `kind`; `finishNode` sets * `type` and `end`. */ class PropertyNode { /** * @param {number} start start offset */ constructor(start) { this.type = ""; this.start = start; this.end = 0; this.method = false; this.shorthand = false; this.computed = false; /** @type {Node | null} */ this.key = null; /** @type {Node | null} */ this.value = null; this.kind = ""; } } /** * Single-shape `SpreadElement`/`RestElement` — identical field sets, so both * node types share one hidden class. */ class RestSpreadNode { /** * @param {number} start start offset * @param {number} end end offset * @param {"SpreadElement" | "RestElement"} type node type * @param {Node} argument spread/rest argument */ constructor(start, end, type, argument) { this.type = type; this.start = start; this.end = end; this.argument = argument; } } // Shared zero-length arguments array for `new X` without parens, mirroring // acorn's module-level `empty`. /** @type {Expression[]} */ const EMPTY_NEW_ARGS = []; /** * Mirror of acorn's module-level `isLocalVariableAccess`. * @param {Node} node checked node * @returns {boolean} whether the node reads a local variable */ const isLocalVariableAccess = (node) => node.type === "Identifier" || (node.type === "ParenthesizedExpression" && isLocalVariableAccess( /** @type {Node} */ ( /** @type {Node & { expression?: Node }} */ (node).expression ) )); /** * Mirror of acorn's module-level `isPrivateFieldAccess`. * @param {Node} node checked node * @returns {boolean} whether the node accesses a private field */ const isPrivateFieldAccess = (node) => (node.type === "MemberExpression" && /** @type {Node} */ ( /** @type {Node & { property?: Node }} */ (node).property ).type === "PrivateIdentifier") || (node.type === "ChainExpression" && isPrivateFieldAccess( /** @type {Node} */ ( /** @type {Node & { expression?: Node }} */ (node).expression ) )) || (node.type === "ParenthesizedExpression" && isPrivateFieldAccess( /** @type {Node} */ ( /** @type {Node & { expression?: Node }} */ (node).expression ) )); // the dedicated node classes serve `range` exactly like LazyLocNode for (const NodeClass of [ IdentifierNode, LiteralNode, MemberExpressionNode, CallExpressionNode, ThisNode, BinaryNode, AssignmentNode, UnaryNode, VariableDeclarationNode, VariableDeclaratorNode, ExpressionStatementNode, BlockStatementNode, IfStatementNode, ReturnStatementNode, ConditionalExpressionNode, NewExpressionNode, ArrayExpressionNode, TemplateLiteralNode, TemplateElementNode, ObjectNode, PropertyNode, RestSpreadNode ]) { for (const key of ["range"]) { Object.defineProperty( NodeClass.prototype, key, /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(LazyLocNode.prototype, key)) ); } } /** * Comment collected without slicing its text out of the source: only magic * comments and pure annotations ever get their text read, so the slice is * deferred to the first `value` access and memoized like `loc`. */ class LazyComment { /** * @param {boolean} block whether this is a block comment * @param {number} textStart offset right after the comment opener * @param {number} start start offset * @param {number} end end offset * @param {string} source full source text for the lazy `value` slice */ constructor(block, textStart, start, end, source) { /** @type {"Block" | "Line"} */ this.type = block ? "Block" : "Line"; this.start = start; this.end = end; /** @type {Range} */ this.range = [start, end]; this[kSource] = source; this[kTextStart] = textStart; } /** * @returns {string} comment text without the delimiters */ get value() { const cached = this[kText]; if (cached !== undefined) return cached; return (this[kText] = this[kSource].slice( this[kTextStart], this.type === "Block" ? this.end - 2 : this.end )); } /** * @param {string} value comment text */ set value(value) { this[kText] = value; } } /** * Replaces acorn's array-backed `Scope`: membership checks in `declareName` * are `indexOf` there, which goes quadratic on files with thousands of * bindings per scope (bundled or minified inputs). The three Sets are * allocated lazily — most scopes declare into only one (module `functions` is * always empty), so ~⅔ of the Sets are never needed. */ class Scope { /** * @param {number} flags scope flags */ constructor(flags) { this.flags = flags; /** @type {Set<string> | undefined} */ this.var = undefined; /** @type {Set<string> | undefined} */ this.lexical = undefined; /** @type {Set<string> | undefined} */ this.functions = undefined; // first lexically-declared name; stands in for acorn's `lexical[0]` // (the catch parameter of a simple catch scope) /** @type {string | undefined} */ this.firstLexical = undefined; } } /** * Acorn's methods and state used by `WebpackParser` but missing from its * public types, plus `WebpackParser`'s own fields, so overridden methods can * declare `this` precisely. * @typedef {import("acorn").Parser & { * type: TokenType, * value: unknown, * start: number, * startLoc?: Position, * containsEsc: boolean, * exprAllowed: boolean, * options: Options, * end: number, * lastTokEnd: number, * canInsertSemicolon: () => boolean, * nextToken: () => void, * next: (ignoreEscapeSequenceInKeyword?: boolean) => void, * eat: (type: TokenType) => boolean, * expect: (type: TokenType) => void, * afterTrailingComma: (type: TokenType, notNext?: boolean) => boolean, * unexpected: (pos?: number) => never, * raise: (pos: number, message: string) => never, * raiseRecoverable: (pos: number, message: string) => void, * isContextual: (name: string) => boolean, * parseIdent: (liberal?: boolean) => Identifier, * parseLiteral: (value: unknown) => Node, * awaitIdentPos: number, * lastTokStart: number, * yieldPos: number, * awaitPos: number, * parseExpression: () => Expression, * parseSpread: (refDestructuringErrors?: DestructuringErrorsShim | null) => Node, * braceIsBlock: (prevType: TokenType) => boolean, * _gapHasNewline: () => boolean, * parseExprList: (close: TokenType, allowTrailingComma: boolean, allowEmpty: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression[], * parsePrivateIdent: () => Node, * parseTemplate: (opts: { isTagged: boolean }) => Node, * shouldParseAsyncArrow: () => boolean, * parseSubscriptAsyncArrow: (startPos: number, startLoc: Position | undefined, exprList: Expression[], forInit: boolean | string) => Expression, * checkPatternErrors: (refDestructuringErrors: DestructuringErrorsShim, isAssign: boolean) => void, * checkYieldAwaitInDefaultParams: () => void, * checkExpressionErrors: (refDestructuringErrors?: DestructuringErrorsShim | null, andThrow?: boolean) => boolean, * parseSubscript: (base: Expression, startPos: number, startLoc: Position | undefined, noCalls: boolean | undefined, maybeAsyncArrow: boolean, optionalChained: boolean, forInit: boolean | string) => Expression, * parseExprAtom: (refDestructuringErrors?: DestructuringErrorsShim | null, forInit?: boolean | string, forNew?: boolean) => Expression, * buildBinary: (startPos: number, startLoc: Position | undefined, left: Expression, right: Expression, op: string, logical: boolean) => Expression, * parseMaybeAssign: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null, afterLeftParse?: (this: unknown, left: Expression, startPos: number, startLoc?: Position) => Expression) => Expression, * parseMaybeConditional: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression, * parseMaybeUnary: (refDestructuringErrors: DestructuringErrorsShim | null, sawUnary: boolean, incDec: boolean, forInit?: boolean | string) => Expression, * parseExprSubscripts: (refDestructuringErrors?: DestructuringErrorsShim | null, forInit?: boolean | string) => Expression, * parseAwait: (forInit?: boolean | string) => Expression, * canAwait: boolean, * privateNameStack: unknown[], * semicolon: () => void, * exitScope: () => void, * parseStatement: (context: string | null, topLevel?: boolean, exports?: unknown) => Node, * parseBindingAtom: () => Node, * parseVarStatement: (node: Node, kind: string, allowMissingInitializer?: boolean) => Node, * parseVar: (node: Node, isFor: boolean, kind: string, allowMissingInitializer?: boolean) => Node, * parseExpressionStatement: (node: Node, expr: Expression) => Node, * parseParenExpression: () => Expression, * parseIfStatement: (node: Node) => Node, * parseReturnStatement: (node: Node) => Node, * insertSemicolon: () => boolean, * allowReturn: boolean, * allowNewDotTarget: boolean, * parseExprOps: (forInit?: boolean | string, refDestructuringErrors?: DestructuringErrorsShim | null) => Expression, * parseExprOp: (left: Expression, leftStartPos: number, leftStartLoc: Position | undefined, minPrec: number, forInit?: boolean | string) => Expression, * _deStack: DestructuringErrorsShim[], * _deDepth: number, * _ecmaVersion: number, * _noLocations: boolean, * _validRegexpFlags: string, * _propHashFastPath: boolean, * _propHashStack: { proto: boolean }[], * _propHashDepth: number, * _acquireDestructuringErrors: () => DestructuringErrorsShim, * _releaseDestructuringErrors: () => void, * parseSubscripts: (base: Expression, startPos: number, startLoc: Position | undefined, noCalls?: boolean, forInit?: boolean | string) => Expression, * parseNew: () => Expression, * parseTemplateElement: (opts: { isTagged: boolean }) => Node, * parseBlock: (createNewLexicalScope?: boolean, node?: Node, exitStrict?: boolean) => Node, * parseYield: (forInit?: boolean | string) => Expression, * toAssignable: (node: Node, isBinding?: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node, * checkLValPattern: (expr: Node, bindingType?: number, checkClashes?: unknown) => void, * checkUnreserved: (ref: Identifier) => void, * enterScope: (flags: number) => void, * readRegexp: () => void, * potentialArrowAt: number, * potentialArrowInForAwait: boolean, * overrideContext: (tokenCtx: unknown) => void, * parseFunction: (node: Node, statement: number, allowExpressionBody?: boolean, isAsync?: boolean, forInit?: boolean | string) => Expression, * parseArrowExpression: (node: Node, params: Node[], isAsync: boolean, forInit?: boolean | string) => Expression, * _subscriptFastPath: boolean, * checkLValSimple: (expr: Node, bindingType?: number) => void, * startNode: () => Node, * startNodeAt: (pos: number, loc?: Position) => Node, * finishNode: (node: Node, type: string) => Node, * readWord1: () => string, * readWord: () => void, * readToken: (code: number) => void, * getTokenFromCode: (code: number) => void, * fullCharCodeAtPos: () => number, * skipSpace: () => void, * skipLineComment: (startSkip: number) => void, * skipBlockComment: () => void, * readString: (quote: number) => void, * readNumber: (startsWithDot: boolean) => void, * readRadixNumber: (radix: number) => void, * readTmplToken: () => void, * finishToken: (type: TokenType, value?: unknown) => void, * context: TokContextShim[], * pos: number, * input: string, * scopeStack: Scope[], * currentScope: () => Scope, * currentThisScope: () => Scope, * currentVarScope: () => Scope, * keywords: RegExp, * reservedWords: RegExp, * reservedWordsStrict: RegExp, * reservedWordsStrictBind: RegExp, * strict: boolean, * inGenerator: boolean, * inGeneratorContext: () => boolean, * inAsync: boolean, * inClassStaticBlock: boolean, * _wordLookups: WordLookups, * treatFunctionsAsVar: boolean, * treatFunctionsAsVarInScope: (scope: Scope) => boolean, * inModule: boolean, * undefinedExports: Record<string, Node>, * parseObj: (isPattern: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node, * parseProperty: (isPattern: boolean, refDestructuringErrors?: DestructuringErrorsShim | null) => Node, * parsePropertyName: (prop: Node) => Node, * parsePropertyValue: (prop: Node, isPattern: boolean, isGenerator: boolean, isAsync: boolean, startPos: number | undefined, startLoc: Position | undefined, refDestructuringErrors: DestructuringErrorsShim | null | undefined, containsEsc: boolean) => void, * isAsyncProp: (prop: Node) => boolean, * checkPropClash: (prop: Node, propHash: Record<string, unknown>, refDestructuringErrors?: DestructuringErrorsShim | null) => void, * parseImport: (node: Node) => Node, * parseExport: (node: Node, exports: unknown) => Node, * parseImportSpecifiers: () => AnyImportSpecifier[], * parseImportAttribute: () => ImportAttribute, * parseExprImport: (forNew: boolean) => Expression, * parseImportMeta: (node: Node) => Expression, * parseDynamicImport: (node: Node) => Expression, * _lazy: boolean, * _importPhase: ImportPhase | null, * _importPhasesEnabled: boolean, * _lazyComments: CollectedComment[] | undefined, * _newlineBefore: 0 | 1 | 2, * _fullTokenFastPath: boolean, * _stmtFastPath: boolean, * isLet: (context?: string | null) => boolean, * parseLabeledStatement: (node: Node, maybeName: string, expr: Identifier, context: string | null) => Node, * _parseVarInto: (declarations: Node[], isFor: boolean, kind: string, allowMissingInitializer?: boolean) => void, * _parseVarStatementAt: (start: number, kind: string, allowMissingInitializer?: boolean) => Node, * _parseIfStatementAt: (start: number) => Node, * _parseReturnStatementAt: (start: number) => Node, * _parseExpressionStatementAt: (start: number, expr: Expression) => Node, * _moduleFallback: boolean, * _moduleSyntaxSeen: boolean, * _tryModuleFallback: () => boolean, * }} ParserInternals */ // internal methods are absent from acorn's types, so super calls do not // type-check; call through a typed view of the base prototype instead const base = /** @type {ParserInternals} */ ( /** @type {unknown} */ (BaseParser.prototype) ); /** * Acorn's internal destructuring-errors record; the class itself is not * exported. Owned methods must create records with the same hidden class the * rest of the expression parser reads, or every record field access there * turns polymorphic. * @typedef {{ shorthandAssign: number, trailingComma: number, parenthesizedAssign: number, parenthesizedBind: number, doubleProto: number }} DestructuringErrorsShim */ // Capture the class at module load: parse one expression through a probe // whose `checkExpressionErrors` sees the record the base parser created. /** @type {{ new (): DestructuringErrorsShim } | null} */ const DestructuringErrorsClass = (() => { /** @type {{ new (): DestructuringErrorsShim } | null} */ let captured = null; class Probe extends BaseParser { /** * @param {DestructuringErrorsShim | null} refDestructuringErrors record to inspect * @param {boolean=} andThrow whether to throw on error * @returns {boolean} whether an error position was set */ checkExpressionErrors(refDestructuringErrors, andThrow) { if (refDestructuringErrors) { captured = /** @type {{ new (): DestructuringErrorsShim }} */ (refDestructuringErrors.constructor); } return /** @type {ParserInternals} */ ( /** @type {unknown} */ (base) ).checkExpressionErrors.call(this, refDestructuringErrors, andThrow); } } Probe.parse("a", { ecmaVersion: 2020 }); // cast: the closure assignment above is invisible to control-flow analysis return /** @type {{ new (): DestructuringErrorsShim } | null} */ (captured); })(); /** * @returns {DestructuringErrorsShim} fresh destructuring-errors record on acorn's own class (plain-object fallback if the capture ever fails) */ const createDestructuringErrors = () => { const DestructuringErrors = DestructuringErrorsClass; return DestructuringErrors !== null ? new DestructuringErrors() : { shorthandAssign: -1, trailingComma: -1, parenthesizedAssign: -1, parenthesizedBind: -1, doubleProto: -1 }; }; /** * Reserved-word classification for `checkUnreserved`'s single lookup: * `1` keyword, `2` reserved in sloppy and strict mode, `3` reserved in strict * mode only. * @typedef {1 | 2 | 3} ReservedKind */ /** * @typedef {object} WordLookups * @property {Map<string, TokenType>} keywords keyword name → token type * @property {Map<string, ReservedKind>} reservedKinds identifier name → reserved kind * @property {number} reservedMaxLen longest key in `reservedKinds` * @property {{ test: (name: string) => boolean }} reservedBindTest strict-mode binding check, a Set-backed stand-in for acorn's `reservedWordsStrictBind` regexp */ // One entry per distinct keyword/reserved-word set; webpack parses with a // single option set, making this effectively a one-time build shared across // every parse. /** @type {Map<string, WordLookups>} */ const wordLookupsCache = new Map(); /** * @param {string} word interned candidate * @param {string} input source code * @param {number} start word start offset * @param {number} len word length (already known equal to `word.length`) * @returns {boolean} whether the source span spells `word` */ const sameWord = (word, input, start, len) => { for (let i = 0; i < len; i++) { if (word.charCodeAt(i) !== input.charCodeAt(start + i)) return false; } return true; }; // Direct-mapped identifier cache for `readWord1`: one slot per hash, verified // by char compare, overwritten on collision. Shared across parses — hits are // content-checked, so a stale entry is merely a miss. Only words short enough // to be flat V8 strings (never slices retaining their whole source) are stored. const WORD_CACHE_MASK = 0x1fff; /** @type {(string | null)[]} */ const WORD_CACHE = Array.from({ length: WORD_CACHE_MASK + 1 }, () => null); const WORD_CACHE_MAX_LEN = 12; // Multi-char operator strings for `finishOp` (`=>`, `===`, `&&=`, …), keyed by // their char codes packed 7 bits apart — collision-free for ASCII operators up // to acorn's maximum of 4 chars (`>>>=`), so the set stays ~40 entries. /** @type {Map<number, string>} */ const OP_CACHE = new Map(); // Sticky mirrors of acorn's `skipWhiteSpace` / string-literal / `lineBreak` // regexes for the owned `strictDirective` (they scan at an offset, no slice). const STRICT_SKIP_WS = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g; const STRICT_LITERAL = /(?:'((?:\\[^]|[^'\\])*?)'|"((?:\\[^]|[^"\\])*?)")/y; const STRICT_LINE_BREAK = /\r\n?|\n|\u2028|\u2029/; /** * @param {RegExp} re acorn `wordsRegexp` output (`^(?:a|b|c)$`) * @returns {Set<string>} the alternation's words */ const wordsRegexpToSet = (re) => { const match = /^\^\(\?:(.*)\)\$$/.exec(re.source); const body = match ? match[1] : ""; return new Set(body ? body.split("|") : []); }; // One-entry identity memo in front of the string-keyed cache: acorn's // `wordsRegexp` interns its regexps, so identity captures the whole word set, // and builds construct thousands of parsers with one option set — this makes // the per-construction lookup three compares instead of a long key concat. /** @type {RegExp | undefined} */ let lastKeywordsRe; /** @type {RegExp | undefined} */ let lastReservedRe; /** @type {RegExp | undefined} */ let lastReservedStrictRe; /** @type {WordLookups | undefined} */ let lastWordLookups; /** * Mirrors acorn's `keywords` / `reservedWords` / `reservedWordsStrict` regexps * as Map/Set lookups. Membership is the hot per-word test in `readWord` and * `checkUnreserved`, and a hash lookup beats an anchored alternation regexp. * @param {ParserInternals} parser parser instance * @returns {WordLookups} lookups for this parser's keyword set */ const getWordLookups = (parser) => { if ( parser.keywords === lastKeywordsRe && parser.reservedWords === lastReservedRe && parser.reservedWordsStrict === lastReservedStrictRe ) { return /** @type {WordLookups} */ (lastWordLookups); } // module vs script share a keyword set but differ in reserved words, so the // key must cover all three regexps const key = `${parser.keywords.source}\n${parser.reservedWords.source}\n${parser.reservedWordsStrict.source}`; lastKeywordsRe = parser.keywords; lastReservedRe = parser.reservedWords; lastReservedStrictRe = parser.reservedWordsStrict; const cached = wordLookupsCache.get(key); if (cached !== undefined) { lastWordLookups = cached; return cached; } /** @type {Map<string, TokenType>} */ const keywords = new Map(); // acorn's keyword regexp is a subset of keywordTypes for the ecmaVersion for (const name of Object.keys(keywordTypes)) { if (parser.keywords.test(name)) keywords.set(name, keywordTypes[name]); } const reserved = wordsRegexpToSet(parser.reservedWords); /** @type {Map<string, ReservedKind>} */ const reservedKinds = new Map(); for (const name of reserved) reservedKinds.set(name, 2); for (const name of wordsRegexpToSet(parser.reservedWordsStrict)) { if (!reserved.has(name)) reservedKinds.set(name, 3); } // keyword classification wins, matching acorn's keyword-first check for (const name of keywords.keys()) reservedKinds.set(name, 1); const reservedBind = wordsRegexpToSet(parser.reservedWordsStrictBind); let reservedMaxLen = 0; for (const name of reservedKinds.keys()) { if (name.length > reservedMaxLen) reservedMaxLen = name.length; } /** @type {WordLookups} */ const lookups = { keywords, reservedKinds, reservedMaxLen, reservedBindTest: { test: (name) => reservedBind.has(name) } }; wordLookupsCache.set(key, lookups); lastWordLookups = lookups; return lookups; }; /** * webpack's parser: acorn plus lazy `range` (no `loc` at all), Set-based scopes, * tokenizer fast paths, import attributes and import phases (with acorn's * `!forNew` guard, unlike the former `acorn-import-phases` package). */ class WebpackParser extends BaseParser { /** * @param {Options & { lazyNodes?: boolean, lazyComments?: CollectedComment[], importPhases?: boolean, moduleFallback?: boolean }} options options * @param {string} input source code * @param {number=} startPos start position */ constructor(options, input, startPos) { const lazy = options.lazyNodes === true; // JavascriptParser._parse pre-disables acorn's tracking, so the // defensive copy only runs for direct callers if (lazy && (options.locations || options.ranges)) { options = { ...options, locations: false, ranges: false }; } super(options, input, startPos); // acorn sets this.keywords/reservedWords in its constructor; parsing // (and thus readWord) only starts later in parse(), so this is ready this._wordLookups = getWordLookups( /** @type {ParserInternals} */ (/** @type {unknown} */ (this)) ); // acorn only calls `.test()` on reservedWordsStrictBind (in // checkLValSimple); swap its regexp for the Set-backed check /** @type {{ reservedWordsStrictBind: { test: (name: string) => boolean } }} */ (/** @type {unknown} */ (this)).reservedWordsStrictBind = this._wordLookups.reservedBindTest; // per-token option probes cached once: acorn normalizes options in // `getOptions` before the constructor body runs and never mutates them const normalizedOptions = /** @type {ParserInternals} */ ( /** @type {unknown} */ (this) ).options; this._ecmaVersion = /** @type {number} */ (normalizedOptions.ecmaVersion); this._noLocations = !normalizedOptions.locations; // lazy mode: nodes get only offsets, gating the owned tokenizer and // statement fast paths this._lazy = lazy; // lazy comment collection must not race a user-provided onComment /** @type {CollectedComment[] | undefined} */ this._lazyComments = lazy && !options.onComment ? options.lazyComments : undefined; // acorn skips a hashbang inside its constructor, before `_lazyComments` // above exists — reconstruct the comment the override missed if ( this._lazyComments !== undefined && !startPos && this.options.allowHashBang && input.startsWith("#!") ) { this._lazyComments.push( new LazyComment( false, 2, 0, /** @type {ParserInternals} */ (/** @type {unknown} */ (this)).pos, input ) ); } /** @type {ImportPhase | null} */ this._importPhase = null; this._importPhasesEnabled = options.importPhases === true; // auto source type: parse as module first, downgrade to script in place // (instead of a second full parse) when script-only syntax is hit this._moduleFallback = options.moduleFallback === true; // set once a module-only construct is parsed; blocks the downgrade this._moduleSyntaxSeen = false; // the owned parseSubscript assumes optional chaining exists (it bakes // `optional` into the node shape), so gate it on the normalized version this._subscriptFastPath = lazy && this._ecmaVersion >= 11; // the owned getTokenFromCode bakes in every ES2021 operator (?., ??=, // &&=, ...), so it needs at least that version this._fullTokenFastPath = lazy && this._ecmaVersion >= 12; // whether the gap before the current token holds a line terminator: // 0 no, 1 yes, 2 unknown (canInsertSemicolon then scans the gap) /** @type {0 | 1 | 2} */ this._newlineBefore = 2; // LIFO pool for call-scoped destructuring-errors records; depth resets // implicitly since a raise aborts the whole parse /** @type {DestructuringErrorsShim[]} */ this._deStack = []; this._deDepth = 0; // LIFO pool for `parseObj`'s prop-clash records: acorn's ES6+ // `checkPropClash` only ever touches `.proto`, so one record per nesting // depth suffices; an overriding subclass gets the fresh `{}` acorn expects this._propHashFastPath = /** @type {ParserInternals} */ (/** @type {unknown} */ (this)) .checkPropClash === base.checkPropClash; /** @type {{ proto: boolean }[]} */ this._propHashStack = []; this._propHashDepth = 0; // `readRegexp`'s flag whitelist depends only on the ecmaVersion this._validRegexpFlags = `gim${this._ecmaVersion >= 6 ? "uy" : ""}${ this._ecmaVersion >= 9 ? "s" : "" }${this._ecmaVersion >= 13 ? "d" : ""}${this._ecmaVersion >= 15 ? "v" : ""}`; // the owned parseStatement inlines these methods, so a parser plugin // overriding any of them turns the statement fast path off const proto = WebpackParser.prototype; this._stmtFastPath = lazy && this.parseVarStatement === proto.parseVarStatement && this.parseVar === proto.parseVar && this.parseIfStatement === proto.parseIfStatement && this.parseReturnStatement === proto.parseReturnStatement && this.parseExpressionStatement === proto.parseExpressionStatement; } /** * Fetches a destructuring-errors record from the pool: acorn allocates one * per expression parse and drops it at the end of the call, so strictly * call-scoped users can reuse records instead. Pair every acquire with a * `_releaseDestructuringErrors` on each non-throwing exit. * @returns {DestructuringErrorsShim} reset record * @this {ParserInternals} */ _acquireDestructuringErrors() { const stack = this._deStack; const depth = this._deDepth++; const cached = stack[depth]; if (cached !== undefined) { cached.shorthandAssign = cached.trailingComma = cached.parenthesizedAssign = cached.parenthesizedBind = cached.doubleProto = -1; return cached; } return (stack[depth] = createDestructuringErrors()); } /** * @returns {void} * @this {ParserInternals} */ _releaseDestructuringErrors() { this._deDepth--; } // ----- tokenizer fast paths ----- /** * Owned per-token loop: acorn's `nextToken` chains `skipSpace` → * `fullCharCodeAtPos` → `readToken` → `isIdentifierStart` with a dead * `locations` check at each step. For the common lazy, non-template context * this folds whitespace and comment skipping and the ASCII token dispatch * into one function so nothing re-enters acorn's per-step option checks. * Template/`preserveSpace` contexts and non-lazy mode use acorn's tokenizer. * @returns {void} * @this {ParserInternals} */ nextToken() { const context = this.context; const curContext = context[context.length - 1]; if ( !this._lazy || !curContext || curContext.preserveSpace || curContext.override ) { this._newlineBefore = 2; return base.nextToken.call(this); } const input = this.input; const len = input.length; let pos = this.pos; // line terminators are flagged while skipping (yuku's // `line_terminator_before`), so ASI checks need no gap re-scan. A // delegated path (acorn's html-comment handling) may have consumed part // of the gap before re-entering — start at "unknown" then. /** @type {0 | 1 | 2} */ let newline = pos === this.lastTokEnd ? 0 : 2; // one CHAR_CLASS load classifies each char for both the skip loop and // the token dispatch below (yuku's ws_class/ident/punct tables in one) let code = 0; let cls = CLS_OTHER; while (pos < len) { code = input.charCodeAt(pos); cls = CHAR_CLASS[code]; if (cls < CLS_SPACE) { if (cls === CLS_UNICODE) { // unicode whitespace / line terminators: acorn consumes the rest this.pos = pos; base.skipSpace.call(this); pos = this.pos; if (newline === 0) newline = 2; code = pos < len ? input.charCodeAt(pos) : 0; cls = CHAR_CLASS[code]; } break; } if (cls === CLS_SPACE) { // space, tab, VT, FF (no CRLF/line bookkeeping in lazy mode) pos++; } else if (cls === CLS_NEWLINE) { newline = 1; pos++; } else { const next = input.charCodeAt(pos + 1); if (next === 42) { this.pos = pos; this.skipBlockComment(); pos = this.pos; // the comment body may hold a line terminator if (newline === 0) newline = 2; } else if (next === 47) { this.pos = pos; this.skipLineComment(2); pos = this.pos; } else { // a division/regexp token, not a comment cls = CLS_OTHER; break; } } } this._newlineBefore = newline; this.pos = pos; this.start = pos; if (pos >= len) return this.finishToken(tokTypes.eof); switch (cls) { case CLS_IDENT: return this.readWord(); case CLS_PUNCT: this.pos = pos + 1; return this.finishToken(/** @type {TokenType} */ (SIMPLE_PUNCT[code])); case CLS_DOT: { // `.` not starting `.5` or `...`: skip readToken_dot's re-dispatch const next = input.charCodeAt(pos + 1); if ((next < 48 || next > 57) && next !== 46) { this.pos = pos + 1; return this.finishToken(tokTypes.dot); } return this.getTokenFromCode(code); } case CLS_EQ: { // `=` not starting `==` or `=>`: skip readToken_eq_excl + finishOp slice const next = input.charCodeAt(pos + 1); if (next !== 61 && next !== 62) { this.pos = pos + 1; return this.finishToken(tokTypes.eq, "="); } return this.getTokenFromCode(code); } case CLS_UNICODE: return this.readToken(this.fullCharCodeAtPos()); default: return this.getTokenFromCode(code); } } /** * Lazy-mode `finishToken`: acorn probes `options.locations` for a dead * `endLoc` write on every token and reaches `updateContext` through an extra * method call. Skip the probe and inline acorn's `updateContext` body — this * runs once per token. Other modes use acorn's. * @param {TokenType} type token type * @param {unknown=} value token value * @returns {void} * @this {ParserInternals} */ finishToken(type, value) { if (!this._lazy) { return base.finishToken.call(this, type, value); } this.end = this.pos; const prevType = this.type; this.type = type; this.value = value; const internal = /** @type {TokenTypeInternal} */ (type); // acorn's updateContext, inlined: keyword-after-dot forbids an expression, // else the token type's own context hook runs, else `exprAllowed` follows // the type's `beforeExpr` (the branch that makes `/` after a value divide) if (type === tokTypes.name) { // name.updateContext, inlined for the commonest token: only `of` / // `yield` (outside a `.` access, ES6+) can re-allow an expression this.exprAllowed = ((value === "of" && !this.exprAllowed) || (value === "yield" && this.inGeneratorContext())) && prevType !== tokTypes.dot && this._ecmaVersion >= 6; } else if (type.keyword && prevType === tokTypes.dot) { this.exprAllowed = false; } else if (type === tokTypes.parenR || type === tokTypes.braceR) { // parenR/braceR.updateContext, inlined const context = this.context; if (context.length === 1) { this.exprAllowed = true; } else { let out = /** @type {TokContextShim} */ (context.pop()); if ( out === CTX_B_STAT && /** @type {TokContextShim} */ (context[context.length - 1]).token === "function" ) { out = /** @type {TokContextShim} */ (context.pop()); } this.exprAllowed = !out.isExpr; } } else if (type === tokTypes.braceL) { // braceL.updateContext, inlined this.context.push(this.braceIsBlock(prevType) ? CTX_B_STAT : CTX_B_EXPR); this.exprAllowed = true; } else if (type === tokTypes.parenL) { // pa