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,290 lines (1,196 loc) 199 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const { HookMap, SyncBailHook } = require("tapable"); const NormalModule = require("../NormalModule"); const Parser = require("../Parser"); const WebpackError = require("../WebpackError"); const StackedMap = require("../util/StackedMap"); const { CompilerHintNotationRegExp, createMagicCommentContext, getCommentsInRange, parseCommentOptionsInRange } = require("../util/magicComment"); const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); const { LEGACY_ASSERT_ATTRIBUTES, WebpackParser, buildLineStarts, hasOctalEscape, positionAt } = require("./syntax"); /** @typedef {typeof import("acorn").Parser} AcornParser */ /** @typedef {import("acorn").Options} AcornOptions */ /** @typedef {import("acorn").ecmaVersion} EcmaVersion */ /** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ /** @typedef {import("estree").BinaryExpression} BinaryExpression */ /** @typedef {import("estree").BlockStatement} BlockStatement */ /** @typedef {import("estree").SequenceExpression} SequenceExpression */ /** @typedef {import("estree").CallExpression} CallExpression */ /** @typedef {import("estree").StaticBlock} StaticBlock */ /** @typedef {import("estree").ClassDeclaration} ClassDeclaration */ /** @typedef {import("estree").ForStatement} ForStatement */ /** @typedef {import("estree").SwitchStatement} SwitchStatement */ /** @typedef {import("estree").ClassExpression} ClassExpression */ /** @typedef {import("estree").SourceLocation} SourceLocation */ /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ /** @typedef {import("../Dependency").SourcePosition} SourcePosition */ /** @typedef {import("estree").Comment & { start: number, end: number, loc?: SourceLocation | null }} Comment */ /** @typedef {import("estree").ConditionalExpression} ConditionalExpression */ /** @typedef {import("estree").Declaration} Declaration */ /** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */ /** @typedef {import("estree").PropertyDefinition} PropertyDefinition */ /** @typedef {import("estree").Expression} Expression */ /** @typedef {import("estree").ImportAttribute} ImportAttribute */ /** @typedef {import("estree").ImportDeclaration} ImportDeclaration */ /** @typedef {import("estree").Identifier} Identifier */ /** @typedef {import("estree").VariableDeclaration} VariableDeclaration */ /** @typedef {import("estree").IfStatement} IfStatement */ /** @typedef {import("estree").LabeledStatement} LabeledStatement */ /** @typedef {import("estree").Literal} Literal */ /** @typedef {import("estree").LogicalExpression} LogicalExpression */ /** @typedef {import("estree").ChainExpression} ChainExpression */ /** @typedef {import("estree").MemberExpression} MemberExpression */ /** @typedef {import("estree").YieldExpression} YieldExpression */ /** @typedef {import("estree").MetaProperty} MetaProperty */ /** @typedef {import("estree").Property} Property */ /** @typedef {import("estree").AssignmentPattern} AssignmentPattern */ /** @typedef {import("estree").Pattern} Pattern */ /** @typedef {import("estree").UpdateExpression} UpdateExpression */ /** @typedef {import("estree").ObjectExpression} ObjectExpression */ /** @typedef {import("estree").UnaryExpression} UnaryExpression */ /** @typedef {import("estree").ArrayExpression} ArrayExpression */ /** @typedef {import("estree").ArrayPattern} ArrayPattern */ /** @typedef {import("estree").AwaitExpression} AwaitExpression */ /** @typedef {import("estree").ThisExpression} ThisExpression */ /** @typedef {import("estree").RestElement} RestElement */ /** @typedef {import("estree").ObjectPattern} ObjectPattern */ /** @typedef {import("estree").SwitchCase} SwitchCase */ /** @typedef {import("estree").CatchClause} CatchClause */ /** @typedef {import("estree").VariableDeclarator} VariableDeclarator */ /** @typedef {import("estree").ForInStatement} ForInStatement */ /** @typedef {import("estree").ForOfStatement} ForOfStatement */ /** @typedef {import("estree").ReturnStatement} ReturnStatement */ /** @typedef {import("estree").WithStatement} WithStatement */ /** @typedef {import("estree").ThrowStatement} ThrowStatement */ /** @typedef {import("estree").MethodDefinition} MethodDefinition */ /** @typedef {import("estree").NewExpression} NewExpression */ /** @typedef {import("estree").SpreadElement} SpreadElement */ /** @typedef {import("estree").FunctionExpression} FunctionExpression */ /** @typedef {import("estree").WhileStatement} WhileStatement */ /** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ /** @typedef {import("estree").ExpressionStatement} ExpressionStatement */ /** @typedef {import("estree").ExportAllDeclaration} ExportAllDeclaration */ /** @typedef {import("estree").ExportNamedDeclaration} ExportNamedDeclaration */ /** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */ /** @typedef {import("estree").DoWhileStatement} DoWhileStatement */ /** @typedef {import("estree").TryStatement} TryStatement */ /** @typedef {import("estree").Node} Node */ /** @typedef {import("estree").Program} Program */ /** @typedef {import("estree").Directive} Directive */ /** @typedef {import("estree").Statement} Statement */ /** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */ /** @typedef {import("estree").Super} Super */ /** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression */ /** @typedef {import("estree").TemplateLiteral} TemplateLiteral */ /** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */ /** @typedef {import("estree").MaybeNamedFunctionDeclaration} MaybeNamedFunctionDeclaration */ /** @typedef {import("estree").MaybeNamedClassDeclaration} MaybeNamedClassDeclaration */ /** * Defines the shared type used by this module. * @template T * @typedef {import("tapable").AsArray<T>} AsArray<T> */ /** @typedef {import("../Parser").ParserState} ParserState */ /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ /** @typedef {import("../dependencies/LocalModule")} LocalModule */ /** @typedef {import("../dependencies/HarmonyExportImportedSpecifierDependency").HarmonyStarExportsList} HarmonyStarExportsList */ /** * Defines the known javascript parser state type used by this module. * @typedef {object} KnownJavascriptParserState * @property {Set<string>=} harmonyNamedExports * @property {HarmonyStarExportsList=} harmonyStarExports * @property {number=} lastHarmonyImportOrder * @property {LocalModule[]=} localModules */ /** @typedef {ParserState & KnownJavascriptParserState} JavascriptParserState */ /** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../Module")} Module */ /** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => Members, getMembersOptionals: () => MembersOptionals, getMemberRanges: () => MemberRanges }} GetInfoResult */ /** @typedef {{ consequent?: EXPECTED_OBJECT, alternate?: EXPECTED_OBJECT }} GuardCollection per-branch guard frames pushed onto the parser state guard stack */ /** @typedef {Statement | ModuleDeclaration | Expression | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration} StatementPathItem */ /** @typedef {(ident: string) => void} OnIdentString */ /** @typedef {(ident: string, identifier: Identifier) => void} OnIdent */ /** @typedef {StatementPathItem[]} StatementPath */ /** @typedef {Set<DestructuringAssignmentProperty>} DestructuringAssignmentProperties */ // TODO remove cast when @types/estree has been updated to import phases /** @typedef {import("estree").ImportExpression & { phase?: "defer" | "source" }} ImportExpression */ /** @type {string[]} */ const EMPTY_ARRAY = []; /** * Getter that reverses `arr` in place on first access and caches it. Lighter * than `memoize(() => arr.reverse())` (one closure instead of two) and called * per member expression on hot parse paths. * @template T * @param {T[]} arr array reversed lazily on first access * @returns {() => T[]} getter returning the reversed array */ const lazyReverse = (arr) => { let reversed = false; return () => { if (!reversed) { arr.reverse(); reversed = true; } return arr; }; }; const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01; const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10; const ALLOWED_MEMBER_TYPES_ALL = 0b11; // Shared `() => false` so the members-optionals fallback in walkCallExpression // doesn't allocate a fresh closure per identifier-rooted call. const RETURN_FALSE = () => false; // Shared `() => []` for the member-less identifier/this evaluation results, // replacing three fresh closures per evaluated free variable. const RETURN_EMPTY_ARRAY = () => []; // Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API let parser = /** @type {AcornParser} */ (WebpackParser); /** @typedef {Record<string, string> & { _isLegacyAssert?: boolean }} ImportAttributes */ // Conceptually pairs with ./syntax's LEGACY_ASSERT_ATTRIBUTES marker, but is // pinned here by its public `JavascriptParser.getImportAttributes` typing. /** * Gets import attributes. * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions * @returns {ImportAttributes | undefined} import attributes */ const getImportAttributes = (node) => { if (node.type === "ImportExpression") { if ( node.options && node.options.type === "ObjectExpression" && node.options.properties[0] && node.options.properties[0].type === "Property" && node.options.properties[0].key.type === "Identifier" && (node.options.properties[0].key.name === "with" || node.options.properties[0].key.name === "assert") && node.options.properties[0].value.type === "ObjectExpression" && node.options.properties[0].value.properties.length > 0 ) { const properties = /** @type {Property[]} */ (node.options.properties[0].value.properties); const result = /** @type {ImportAttributes} */ ({}); for (const property of properties) { const key = /** @type {string} */ ( property.key.type === "Identifier" ? property.key.name : /** @type {Literal} */ (property.key).value ); result[key] = /** @type {string} */ (/** @type {Literal} */ (property.value).value); } const key = node.options.properties[0].key.type === "Identifier" ? node.options.properties[0].key.name : /** @type {Literal} */ (node.options.properties[0].key).value; if (key === "assert") { result._isLegacyAssert = true; } return result; } return; } if (node.attributes === undefined || node.attributes.length === 0) { return; } const result = /** @type {ImportAttributes} */ ({}); for (const attribute of node.attributes) { const key = /** @type {string} */ ( attribute.key.type === "Identifier" ? attribute.key.name : attribute.key.value ); result[key] = /** @type {string} */ (attribute.value.value); } if (/** @type {EXPECTED_ANY} */ (node.attributes)[LEGACY_ASSERT_ATTRIBUTES]) { result._isLegacyAssert = true; } return result; }; /** @typedef {typeof VariableInfoFlags.Evaluated | typeof VariableInfoFlags.Free | typeof VariableInfoFlags.Normal | typeof VariableInfoFlags.Tagged} VariableInfoFlagsType */ const VariableInfoFlags = Object.freeze({ Evaluated: 0b000, Free: 0b001, Normal: 0b010, Tagged: 0b100 }); class VariableInfo { /** * Creates an instance of VariableInfo. * @param {ScopeInfo} declaredScope scope in which the variable is declared * @param {string | undefined} name which name the variable use, defined name or free name or tagged name * @param {VariableInfoFlagsType} flags how the variable is created * @param {TagInfo | undefined} tagInfo info about tags */ constructor(declaredScope, name, flags, tagInfo) { /** @type {ScopeInfo} */ this.declaredScope = declaredScope; /** @type {string | undefined} */ this.name = name; /** @type {VariableInfoFlagsType} */ this.flags = flags; /** @type {TagInfo | undefined} */ this.tagInfo = tagInfo; } /** * Checks whether this variable info is free. * @returns {boolean} the variable is free or not */ isFree() { return (this.flags & VariableInfoFlags.Free) > 0; } /** * Checks whether this variable info is tagged. * @returns {boolean} the variable is tagged by tagVariable or not */ isTagged() { return (this.flags & VariableInfoFlags.Tagged) > 0; } } /** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */ /** @typedef {Literal | string | null | undefined} ImportSource */ /** * Defines the internal parse options type used by this module. * @typedef {Omit<ParseOptions, "sourceType" | "ecmaVersion"> & { sourceType: "module" | "script" | "auto" }} InternalParseOptions */ /** * Defines the parse options type used by this module. * @typedef {object} ParseOptions * @property {"module" | "script"} sourceType * @property {EcmaVersion} ecmaVersion * @property {boolean=} locations * @property {boolean=} comments * @property {boolean=} ranges * @property {boolean=} allowHashBang * @property {boolean=} allowReturnOutsideFunction * @property {boolean=} lazyNodes internal: serve `range` lazily and skip acorn's location/range tracking * @property {Comment[]=} lazyComments internal: collect comments here without slicing their text eagerly * @property {boolean=} importPhases enable parsing of the import phase proposals (import defer / import source) * @property {boolean=} moduleFallback internal: for `auto`, let the parser downgrade module->script in place instead of re-parsing */ /** * Defines the parse result type used by this module. * @typedef {object} ParseResult * @property {Program} ast * @property {Comment[]} comments */ /** * Defines the parse function type used by this module. * @typedef {(code: string, options: ParseOptions) => ParseResult} ParseFunction */ /** @typedef {symbol} Tag */ /** @typedef {import("../dependencies/HarmonyImportDependencyParserPlugin").HarmonySettings} HarmonySettings */ /** @typedef {import("../dependencies/ImportParserPlugin").ImportSettings} ImportSettings */ /** @typedef {import("../dependencies/CommonJsImportsParserPlugin").CommonJsImportSettings} CommonJsImportSettings */ /** @typedef {import("../CompatibilityPlugin").CompatibilitySettings} CompatibilitySettings */ /** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */ /** @typedef {HarmonySettings | ImportSettings | CommonJsImportSettings | TopLevelSymbol | CompatibilitySettings} KnownTagData */ /** @typedef {KnownTagData | Record<string, EXPECTED_ANY>} TagData */ /** * Defines the tag info type used by this module. * @typedef {object} TagInfo * @property {Tag} tag * @property {TagData=} data * @property {TagInfo | undefined} next */ /** @typedef {string[]} CalleeMembers */ /** @typedef {string[]} Members */ /** @typedef {boolean[]} MembersOptionals */ /** @typedef {Range[]} MemberRanges */ const SCOPE_INFO_TERMINATED_RETURN = 1; const SCOPE_INFO_TERMINATED_THROW = 2; /** * Defines the scope info type used by this module. * @typedef {object} ScopeInfo * @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions * @property {boolean | "arrow"} topLevelScope * @property {boolean | string} inShorthand * @property {boolean} inTaggedTemplateTag * @property {boolean} inTry * @property {boolean} isStrict * @property {boolean} isAsmJs * @property {undefined | 1 | 2} terminated */ /** @typedef {[number, number]} Range */ /** * Defines the destructuring assignment property type used by this module. * Carries only the range — consumers derive line/column via `getLocation`. * @typedef {object} DestructuringAssignmentProperty * @property {string} id * @property {Range} range * @property {Set<DestructuringAssignmentProperty> | undefined=} pattern * @property {boolean | string} shorthand */ /** * Helper function for joining two ranges into a single range. This is useful * when working with AST nodes, as it allows you to combine the ranges of child nodes * to create the range of the _parent node_. * @param {Range} startRange start range to join * @param {Range} endRange end range to join * @returns {Range} joined range * @example * ```js * const startRange = [0, 5]; * const endRange = [10, 15]; * const joinedRange = joinRanges(startRange, endRange); * console.log(joinedRange); // [0, 15] * ``` */ const joinRanges = (startRange, endRange) => { if (!endRange) return startRange; if (!startRange) return endRange; return [startRange[0], endRange[1]]; }; /** * Helper function used to generate a string representation of a * [member expression](https://github.com/estree/estree/blob/master/es5.md#memberexpression). * @param {string} object object to name * @param {Members} membersReversed reversed list of members * @returns {string} member expression as a string * @example * ```js * const membersReversed = ["property1", "property2", "property3"]; // Members parsed from the AST * const name = objectAndMembersToName("myObject", membersReversed); * * console.log(name); // "myObject.property1.property2.property3" * ``` */ const objectAndMembersToName = (object, membersReversed) => { let name = object; for (let i = membersReversed.length - 1; i >= 0; i--) { name = `${name}.${membersReversed[i]}`; } return name; }; /** * Grabs the name of a given expression and returns it as a string or undefined. Has particular * handling for [Identifiers](https://github.com/estree/estree/blob/master/es5.md#identifier), * [ThisExpressions](https://github.com/estree/estree/blob/master/es5.md#identifier), and * [MetaProperties](https://github.com/estree/estree/blob/master/es2015.md#metaproperty) which is * specifically for handling the `new.target` meta property. * @param {Expression | SpreadElement | Super} expression expression * @returns {string | "this" | undefined} name or variable info */ const getRootName = (expression) => { switch (expression.type) { case "Identifier": return expression.name; case "ThisExpression": return "this"; case "MetaProperty": return `${expression.meta.name}.${expression.property.name}`; default: return undefined; } }; /** * @param {string} type AST node type * @returns {boolean} true for FunctionExpression or ArrowFunctionExpression */ const isFunctionExpression = (type) => type === "FunctionExpression" || type === "ArrowFunctionExpression"; /** * @param {FunctionExpression | ArrowFunctionExpression} fn function * @returns {boolean} true when all params are plain identifiers */ const isSimpleFunction = (fn) => { const params = fn.params; for (let i = 0; i < params.length; i++) { if (params[i].type !== "Identifier") return false; } return true; }; /** @type {ParseOptions} */ const defaultParserOptions = { sourceType: "module", ecmaVersion: "latest", ranges: false, locations: false, comments: false, // https://github.com/tc39/proposal-hashbang allowHashBang: true }; // reused by _parse for every non-custom parse — all fields are reset per parse /** @type {ParseOptions} */ const REUSED_PARSER_OPTIONS = { ...defaultParserOptions }; // Read-only snippet ASTs for `evaluate()`, released with the compilation; // DefinePlugin evaluates the same strings once per usage per module. /** @type {WeakMap<Compilation, Map<string, Program>>} */ const evaluateAstCaches = new WeakMap(); // The inner map is strong while the compilation lives, so cap it const EVALUATE_AST_CACHE_LIMIT = 4096; const CLASS_NAME = "JavascriptParser"; class JavascriptParser extends Parser { /** * Creates an instance of JavascriptParser. * @param {"module" | "script" | "auto"=} sourceType default source type * @param {{ parse?: ParseFunction, typescript?: boolean, importPhases?: boolean, strictModeViolations?: "error" | "warn" | false }=} options parser options */ constructor(sourceType = "auto", options = {}) { super(); this.hooks = Object.freeze({ /** @type {HookMap<SyncBailHook<[UnaryExpression], BasicEvaluatedExpression | null | undefined>>} */ evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])), /** @type {HookMap<SyncBailHook<[Expression | SpreadElement | PrivateIdentifier | Super], BasicEvaluatedExpression | null | undefined>>} */ evaluate: new HookMap(() => new SyncBailHook(["expression"])), /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression | MetaProperty], BasicEvaluatedExpression | null | undefined>>} */ evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])), /** @type {HookMap<SyncBailHook<[Identifier | ThisExpression | MemberExpression], BasicEvaluatedExpression | null | undefined>>} */ evaluateDefinedIdentifier: new HookMap( () => new SyncBailHook(["expression"]) ), /** * @type {HookMap<SyncBailHook<[NewExpression], BasicEvaluatedExpression | null | undefined>>} * @since 5.73.0 */ evaluateNewExpression: new HookMap( () => new SyncBailHook(["expression"]) ), /** * @type {HookMap<SyncBailHook<[CallExpression], BasicEvaluatedExpression | null | undefined>>} * @since 5.73.0 */ evaluateCallExpression: new HookMap( () => new SyncBailHook(["expression"]) ), /** @type {HookMap<SyncBailHook<[CallExpression, BasicEvaluatedExpression], BasicEvaluatedExpression | null | undefined>>} */ evaluateCallExpressionMember: new HookMap( () => new SyncBailHook(["expression", "param"]) ), /** @type {HookMap<SyncBailHook<[Expression | Declaration | PrivateIdentifier | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration, number], boolean | void>>} */ isPure: new HookMap( () => new SyncBailHook(["expression", "commentsStartPosition"]) ), /** @type {SyncBailHook<[Statement | ModuleDeclaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration], boolean | void>} */ preStatement: new SyncBailHook(["statement"]), /** * @type {HookMap<SyncBailHook<[Statement | ModuleDeclaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration], boolean | void>>} * @since 5.109.0 */ preStatementByType: new HookMap(() => new SyncBailHook(["statement"])), /** @type {SyncBailHook<[Statement | ModuleDeclaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration], boolean | void>} */ blockPreStatement: new SyncBailHook(["declaration"]), /** * @type {HookMap<SyncBailHook<[Statement | ModuleDeclaration | MaybeNamedClassDeclaration | MaybeNamedFunctionDeclaration], boolean | void>>} * @since 5.109.0 */ blockPreStatementByType: new HookMap( () => new SyncBailHook(["declaration"]) ), /** @type {SyncBailHook<[Statement | ModuleDeclaration | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration], boolean | void>} */ statement: new SyncBailHook(["statement"]), /** @type {SyncBailHook<[IfStatement], boolean | void>} */ statementIf: new SyncBailHook(["statement"]), /** * @type {SyncBailHook<[Expression], GuardCollection | void>} * @since 5.105.0 */ collectGuards: new SyncBailHook(["expression"]), /** @type {SyncBailHook<[Expression, ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration], boolean | void>} */ classExtendsExpression: new SyncBailHook([ "expression", "classDefinition" ]), /** @type {SyncBailHook<[MethodDefinition | PropertyDefinition | StaticBlock, ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration], boolean | void>} */ classBodyElement: new SyncBailHook(["element", "classDefinition"]), /** * @type {SyncBailHook<[Expression, MethodDefinition | PropertyDefinition, ClassExpression | ClassDeclaration | MaybeNamedClassDeclaration], boolean | void>} * @since 5.36.0 */ classBodyValue: new SyncBailHook([ "expression", "element", "classDefinition" ]), /** @type {HookMap<SyncBailHook<[LabeledStatement], boolean | void>>} */ label: new HookMap(() => new SyncBailHook(["statement"])), /** @type {SyncBailHook<[ImportDeclaration, ImportSource], boolean | void>} */ import: new SyncBailHook(["statement", "source"]), /** @type {SyncBailHook<[ImportDeclaration, ImportSource, string | null, string], boolean | void>} */ importSpecifier: new SyncBailHook([ "statement", "source", "exportName", "identifierName" ]), /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration], boolean | void>} */ export: new SyncBailHook(["statement"]), /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource], boolean | void>} */ exportImport: new SyncBailHook(["statement", "source"]), /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, Declaration], boolean | void>} */ exportDeclaration: new SyncBailHook(["statement", "declaration"]), /** @type {SyncBailHook<[ExportDefaultDeclaration, MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration | Expression], boolean | void>} */ exportExpression: new SyncBailHook(["statement", "node"]), /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, string, string, number | undefined], boolean | void>} */ exportSpecifier: new SyncBailHook([ "statement", "identifierName", "exportName", "index" ]), /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource, string | null, string | null, number | undefined], boolean | void>} */ exportImportSpecifier: new SyncBailHook([ "statement", "source", "identifierName", "exportName", "index" ]), /** @type {SyncBailHook<[VariableDeclarator, VariableDeclaration], boolean | void>} */ preDeclarator: new SyncBailHook(["declarator", "statement"]), /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */ declarator: new SyncBailHook(["declarator", "statement"]), /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */ varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])), /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */ varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])), /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */ varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])), /** * @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} * @since 5.100.0 */ varDeclarationUsing: new HookMap(() => new SyncBailHook(["declaration"])), /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */ varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])), /** @type {HookMap<SyncBailHook<[Identifier], boolean | void>>} */ pattern: new HookMap(() => new SyncBailHook(["pattern"])), /** * @type {SyncBailHook<[Expression], boolean | void>} * @since 5.101.3 */ collectDestructuringAssignmentProperties: new SyncBailHook([ "expression" ]), /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */ canRename: new HookMap(() => new SyncBailHook(["initExpression"])), /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */ rename: new HookMap(() => new SyncBailHook(["initExpression"])), /** @type {HookMap<SyncBailHook<[AssignmentExpression], boolean | void>>} */ assign: new HookMap(() => new SyncBailHook(["expression"])), /** @type {HookMap<SyncBailHook<[AssignmentExpression, Members], boolean | void>>} */ assignMemberChain: new HookMap( () => new SyncBailHook(["expression", "members"]) ), /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */ typeof: new HookMap(() => new SyncBailHook(["expression"])), /** @type {SyncBailHook<[ImportExpression, CallExpression?], boolean | void>} */ importCall: new SyncBailHook(["expression", "importThen"]), /** @type {SyncBailHook<[Expression | ForOfStatement], boolean | void>} */ topLevelAwait: new SyncBailHook(["expression"]), /** @type {HookMap<SyncBailHook<[CallExpression], boolean | void>>} */ call: new HookMap(() => new SyncBailHook(["expression"])), /** Something like "a.b()" */ /** @type {HookMap<SyncBailHook<[CallExpression, Members, MembersOptionals, MemberRanges], boolean | void>>} */ callMemberChain: new HookMap( () => new SyncBailHook([ "expression", "members", "membersOptionals", "memberRanges" ]) ), /** Something like "a.b().c.d" */ /** @type {HookMap<SyncBailHook<[Expression, CalleeMembers, CallExpression, Members, MemberRanges], boolean | void>>} */ memberChainOfCallMemberChain: new HookMap( () => new SyncBailHook([ "expression", "calleeMembers", "callExpression", "members", "memberRanges" ]) ), /** Something like "a.b().c.d()"" */ /** @type {HookMap<SyncBailHook<[CallExpression, CalleeMembers, CallExpression, Members, MemberRanges], boolean | void>>} */ callMemberChainOfCallMemberChain: new HookMap( () => new SyncBailHook([ "expression", "calleeMembers", "innerCallExpression", "members", "memberRanges" ]) ), /** @type {SyncBailHook<[ChainExpression], boolean | void>} */ optionalChaining: new SyncBailHook(["optionalChaining"]), /** @type {HookMap<SyncBailHook<[NewExpression], boolean | void>>} */ new: new HookMap(() => new SyncBailHook(["expression"])), /** * @type {SyncBailHook<[BinaryExpression], boolean | void>} * @since 5.71.0 */ binaryExpression: new SyncBailHook(["binaryExpression"]), /** @type {HookMap<SyncBailHook<[Expression], boolean | void>>} */ expression: new HookMap(() => new SyncBailHook(["expression"])), /** @type {HookMap<SyncBailHook<[MemberExpression, Members, MembersOptionals, MemberRanges], boolean | void>>} */ expressionMemberChain: new HookMap( () => new SyncBailHook([ "expression", "members", "membersOptionals", "memberRanges" ]) ), /** @type {HookMap<SyncBailHook<[MemberExpression, Members], boolean | void>>} */ unhandledExpressionMemberChain: new HookMap( () => new SyncBailHook(["expression", "members"]) ), /** @type {SyncBailHook<[ConditionalExpression], boolean | void>} */ expressionConditionalOperator: new SyncBailHook(["expression"]), /** @type {SyncBailHook<[LogicalExpression], boolean | void>} */ expressionLogicalOperator: new SyncBailHook(["expression"]), /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ program: new SyncBailHook(["ast", "comments"]), /** * @type {SyncBailHook<[ThrowStatement | ReturnStatement], boolean | void>} * @since 5.99.0 */ terminate: new SyncBailHook(["statement"]), /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ finish: new SyncBailHook(["ast", "comments"]), /** * @type {SyncBailHook<[Statement], boolean | void>} * @since 5.99.9 */ unusedStatement: new SyncBailHook(["statement"]) }); this.sourceType = sourceType; this.options = options; /** @type {ScopeInfo} */ this.scope = /** @type {EXPECTED_ANY} */ (undefined); /** @type {JavascriptParserState} */ this.state = /** @type {EXPECTED_ANY} */ (undefined); /** @type {Comment[] | undefined} */ this.comments = undefined; // ASI overrides keyed by statement-end offset: true forces an ASI // position, false forces a real semicolon. Absent offsets are derived // from the source text. Allocated lazily by set/unsetAsiPosition. /** @type {Map<number, boolean> | undefined} */ this.semicolons = undefined; /** @type {StatementPath | undefined} */ this.statementPath = undefined; /** @type {Statement | ModuleDeclaration | Expression | MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration | undefined} */ this.prevStatement = undefined; /** @type {WeakMap<Expression, DestructuringAssignmentProperties> | undefined} */ this.destructuringAssignmentProperties = undefined; /** @type {TagData | undefined} */ this.currentTagData = undefined; // True while parsing a loose module whose code is emitted as strict ESM. this._strictInModuleOutput = false; // Source text of the current parse, for offset → line/column mapping. /** @type {string | undefined} */ this._source = undefined; // Offset of each line's first character, built on first getLocation use. /** @type {number[] | undefined} */ this._lineStarts = undefined; // True while only the parser's own taps sit on evaluate.for("Identifier"); // recomputed per parse, gates the defined-identifier fast paths. this._evalIdentOwnTaps = false; this.magicCommentContext = createMagicCommentContext(); // Reused for enterPatterns on every scope entry to avoid per-scope closures. /** @type {OnIdentString} */ this._defineVariable = (ident) => this.defineVariable(ident); this._initializeEvaluating(); // keyed taps are free for every other member chain, and a shadowed // `arguments` binding never dispatches the free-variable hooks this.hooks.expressionMemberChain .for("arguments") .tap(CLASS_NAME, (expression, members) => { if (this._strictInModuleOutput) { this._checkStrictModeArgumentsMember(members[0], expression); } }); this.hooks.callMemberChain .for("arguments") .tap(CLASS_NAME, (expression, members) => { // longer chains re-enter the member walk, which reports them if (this._strictInModuleOutput && members.length === 1) { this._checkStrictModeArgumentsMember(members[0], expression); } }); } _initializeEvaluating() { this.hooks.evaluate.for("Literal").tap(CLASS_NAME, (_expr) => { const expr = /** @type {Literal} */ (_expr); switch (typeof expr.value) { case "number": return new BasicEvaluatedExpression() .setNumber(expr.value) .setRange(/** @type {Range} */ (expr.range)); case "bigint": return new BasicEvaluatedExpression() .setBigInt(expr.value) .setRange(/** @type {Range} */ (expr.range)); case "string": return new BasicEvaluatedExpression() .setString(expr.value) .setRange(/** @type {Range} */ (expr.range)); case "boolean": return new BasicEvaluatedExpression() .setBoolean(expr.value) .setRange(/** @type {Range} */ (expr.range)); } if (expr.value === null) { return new BasicEvaluatedExpression() .setNull() .setRange(/** @type {Range} */ (expr.range)); } if (expr.value instanceof RegExp) { return new BasicEvaluatedExpression() .setRegExp(expr.value) .setRange(/** @type {Range} */ (expr.range)); } }); this.hooks.evaluate.for("NewExpression").tap(CLASS_NAME, (_expr) => { const expr = /** @type {NewExpression} */ (_expr); const callee = expr.callee; if (callee.type !== "Identifier") return; if (callee.name !== "RegExp") { return this.callHooksForName( this.hooks.evaluateNewExpression, callee.name, expr ); } else if ( expr.arguments.length > 2 || this.getVariableInfo("RegExp") !== "RegExp" ) { return; } /** @type {undefined | string} */ let regExp; const arg1 = expr.arguments[0]; if (arg1) { if (arg1.type === "SpreadElement") return; const evaluatedRegExp = this.evaluateExpression(arg1); if (!evaluatedRegExp) return; regExp = evaluatedRegExp.asString(); if (!regExp) return; } else { return ( new BasicEvaluatedExpression() // eslint-disable-next-line prefer-regex-literals .setRegExp(new RegExp("")) .setRange(/** @type {Range} */ (expr.range)) ); } /** @type {undefined | string} */ let flags; const arg2 = expr.arguments[1]; if (arg2) { if (arg2.type === "SpreadElement") return; const evaluatedFlags = this.evaluateExpression(arg2); if (!evaluatedFlags) return; if (!evaluatedFlags.isUndefined()) { flags = evaluatedFlags.asString(); if ( flags === undefined || !BasicEvaluatedExpression.isValidRegExpFlags(flags) ) { return; } } } return new BasicEvaluatedExpression() .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp)) .setRange(/** @type {Range} */ (expr.range)); }); this.hooks.evaluate.for("LogicalExpression").tap(CLASS_NAME, (_expr) => { const expr = /** @type {LogicalExpression} */ (_expr); const left = this.evaluateExpression(expr.left); let returnRight = false; /** @type {boolean | undefined} */ let allowedRight; if (expr.operator === "&&") { const leftAsBool = left.asBool(); if (leftAsBool === false) { return left.setRange(/** @type {Range} */ (expr.range)); } returnRight = leftAsBool === true; allowedRight = false; } else if (expr.operator === "||") { const leftAsBool = left.asBool(); if (leftAsBool === true) { return left.setRange(/** @type {Range} */ (expr.range)); } returnRight = leftAsBool === false; allowedRight = true; } else if (expr.operator === "??") { const leftAsNullish = left.asNullish(); if (leftAsNullish === false) { return left.setRange(/** @type {Range} */ (expr.range)); } if (leftAsNullish !== true) return; returnRight = true; } else { return; } const right = this.evaluateExpression(expr.right); if (returnRight) { if (left.couldHaveSideEffects()) right.setSideEffects(); return right.setRange(/** @type {Range} */ (expr.range)); } const asBool = right.asBool(); if (allowedRight === true && asBool === true) { return new BasicEvaluatedExpression() .setRange(/** @type {Range} */ (expr.range)) .setTruthy(); } else if (allowedRight === false && asBool === false) { return new BasicEvaluatedExpression() .setRange(/** @type {Range} */ (expr.range)) .setFalsy(); } }); /** * In simple logical cases, we can use valueAsExpression to assist us in evaluating the expression on * either side of a [BinaryExpression](https://github.com/estree/estree/blob/master/es5.md#binaryexpression). * This supports scenarios in webpack like conditionally `import()`'ing modules based on some simple evaluation: * * ```js * if (1 === 3) { * import("./moduleA"); // webpack will auto evaluate this and not import the modules * } * ``` * * Additional scenarios include evaluation of strings inside of dynamic import statements: * * ```js * const foo = "foo"; * const bar = "bar"; * * import("./" + foo + bar); // webpack will auto evaluate this into import("./foobar") * ``` * @param {boolean | number | bigint | string} value the value to convert to an expression * @param {BinaryExpression | UnaryExpression} expr the expression being evaluated * @param {boolean} sideEffects whether the expression has side effects * @returns {BasicEvaluatedExpression | undefined} the evaluated expression * @example * * ```js * const binaryExpr = new BinaryExpression("+", * { type: "Literal", value: 2 }, * { type: "Literal", value: 3 } * ); * * const leftValue = 2; * const rightValue = 3; * * const leftExpr = valueAsExpression(leftValue, binaryExpr.left, false); * const rightExpr = valueAsExpression(rightValue, binaryExpr.right, false); * const result = new BasicEvaluatedExpression() * .setNumber(leftExpr.number + rightExpr.number) * .setRange(binaryExpr.range); * * console.log(result.number); // Output: 5 * ``` */ const valueAsExpression = (value, expr, sideEffects) => { switch (typeof value) { case "boolean": return new BasicEvaluatedExpression() .setBoolean(value) .setSideEffects(sideEffects) .setRange(/** @type {Range} */ (expr.range)); case "number": return new BasicEvaluatedExpression() .setNumber(value) .setSideEffects(sideEffects) .setRange(/** @type {Range} */ (expr.range)); case "bigint": return new BasicEvaluatedExpression() .setBigInt(value) .setSideEffects(sideEffects) .setRange(/** @type {Range} */ (expr.range)); case "string": return new BasicEvaluatedExpression() .setString(value) .setSideEffects(sideEffects) .setRange(/** @type {Range} */ (expr.range)); } }; this.hooks.evaluate.for("BinaryExpression").tap(CLASS_NAME, (_expr) => { const expr = /** @type {BinaryExpression} */ (_expr); /** * Evaluates a binary expression if and only if it is a const operation (e.g. 1 + 2, "a" + "b", etc.). * @template T * @param {(leftOperand: T, rightOperand: T) => boolean | number | bigint | string} operandHandler the handler for the operation (e.g. (a, b) => a + b) * @returns {BasicEvaluatedExpression | undefined} the evaluated expression */ const handleConstOperation = (operandHandler) => { const left = this.evaluateExpression(expr.left); if (!left.isCompileTimeValue()) return; const right = this.evaluateExpression(expr.right); if (!right.isCompileTimeValue()) return; const result = operandHandler( /** @type {T} */ (left.asCompileTimeValue()), /** @type {T} */ (right.asCompileTimeValue()) ); return valueAsExpression( result, expr, left.couldHaveSideEffects() || right.couldHaveSideEffects() ); }; /** * Helper function to determine if two booleans are always different. This is used in `handleStrictEqualityComparison` * to determine if an expressions boolean or nullish conversion is equal or not. * @param {boolean} a first boolean to compare * @param {boolean} b second boolean to compare * @returns {boolean} true if the two booleans are always different, false otherwise */ const isAlwaysDifferent = (a, b) => (a === true && b === false) || (a === false && b === true); /** * Handle template string compare. * @param {BasicEvaluatedExpression} left left * @param {BasicEvaluatedExpression} right right * @param {BasicEvaluatedExpression} res res * @param {boolean} eql true for "===" and false for "!==" * @returns {BasicEvaluatedExpression | undefined} result */ const handleTemplateStringCompare = (left, right, res, eql) => { /** * Returns value. * @param {BasicEvaluatedExpression[]} parts parts * @returns {string} value */ const getPrefix = (parts) => { let value = ""; for (const p of parts) { const v = p.asString(); if (v !== undefined) value += v; else break; } return value; }; /** * Returns value. * @param {BasicEvaluatedExpression[]} parts parts * @returns {string} value */ const getSuffix = (parts) => { let value = ""; for (let i = parts.length - 1; i >= 0; i--) { const v = parts[i].asString(); if (v !== undefined) value = v + value; else break; } return value; }; const leftPrefix = getPrefix( /** @type {BasicEvaluatedExpression[]} */ (left.parts) ); const rightPrefix = getPrefix( /** @type {BasicEvaluatedExpression[]} */ (right.parts) ); const leftSuffix = getSuffix( /** @type {BasicEvaluatedExpression[]} */ (left.parts) ); const rightSuffix = getSuffix( /** @type {BasicEvaluatedExpression[]} */ (right.parts) ); const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length); const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length); const prefixMismatch = lenPrefix > 0 && leftPrefix.slice(0, lenPrefix) !== rightPrefix.slice(0, lenPrefix); const suffixMismatch = lenSuffix > 0 && leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix); if (prefixMismatch || suffixMismatch) { return res .setBoolean(!eql) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } }; /** * Helper function to handle BinaryExpressions using strict equality comparisons (e.g. "===" and "!=="). * @param {boolean} eql true for "===" and false for "!==" * @returns {BasicEvaluatedExpression | undefined} the evaluated expression */ const handleStrictEqualityComparison = (eql) => { const left = this.evaluateExpression(expr.left); const right = this.evaluateExpression(expr.right); const res = new BasicEvaluatedExpression(); res.setRange(/** @type {Range} */ (expr.range)); const leftConst = left.isCompileTimeValue(); const rightConst = right.isCompileTimeValue(); if (leftConst && rightConst) { return res .setBoolean( eql === (left.asCompileTimeValue() === right.asCompileTimeValue()) ) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } if (left.isArray() && right.isArray()) { return res .setBoolean(!eql) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } if (left.isTemplateString() && right.isTemplateString()) { return handleTemplateStringCompare(left, right, res, eql); } const leftPrimitive = left.isPrimitiveType(); const rightPrimitive = right.isPrimitiveType(); if ( // Primitive !== Object or // compile-time object types are never equal to something at runtime (leftPrimitive === false && (leftConst || rightPrimitive === true)) || (rightPrimitive === false && (rightConst || leftPrimitive === true)) || // Different nullish or boolish status also means not equal isAlwaysDifferent( /** @type {boolean} */ (left.asBool()), /** @type {boolean} */ (right.asBool()) ) || isAlwaysDifferent( /** @type {boolean} */ (left.asNullish()), /** @type {boolean} */ (right.asNullish()) ) ) { return res .setBoolean(!eql) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } }; /** * Helper function to handle BinaryExpressions using abstract equality comparisons (e.g. "==" and "!="). * @param {boolean} eql true for "==" and false for "!=" * @returns {BasicEvaluatedExpression | undefined} the evaluated expression */ const handleAbstractEqualityComparison = (eql) => { const left = this.evaluateExpression(expr.left); const right = this.evaluateExpression(expr.right); const res = new BasicEvaluatedExpression(); res.setRange(/** @type {Range} */ (expr.range)); const leftConst = left.isCompileTimeValue(); const rightConst = right.isCompileTimeValue(); if (leftConst && rightConst) { return res .setBoolean( eql === // eslint-disable-next-line eqeqeq (left.asCompileTimeValue() == right.asCompileTimeValue()) ) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } if (left.isArray() && right.isArray()) { return res .setBoolean(!eql) .setSideEffects( left.couldHaveSideEffects() || right.couldHaveSideEffects() ); } if (left.isTemplateString() && right.isTemplateString()) { return handleTemplateStringCompare(left, right, res, eql); } }; if (expr.operator === "+") { const left = this.evaluateExpression(expr.left); const right = this.evaluateExpression(expr.right); const res = new BasicEvaluatedExpression(); if (left.isString()) { if (right.isString()) { res.setString( /** @type {string} */ (left.string) + /** @type {string} */ (right.string) ); } else if (right.isNumber()) { res.setString(/** @type {string} */ (left.string) + right.number); } else if ( right.isWrapped() && right.prefix && right.prefix.isString() ) { // "left" + ("prefix" + inner + "postfix") // => ("leftPrefix" + inner + "postfix") res.setWrapped( new BasicEvaluatedExpression() .setString( /** @type {string} */ (left.string) + /** @type {string} */ (right.prefix.string) ) .setRange( joinRanges( /** @type {Range} */ (left.range), /** @type {Range} */ (right.prefix.range) ) ), right.postfix, right.wrappedInnerExpressions ); } else if (right.isWrapped()) { // "left" + ([null] + inner + "postfix") // => ("left" + inner + "postfix") res.setWrapped(left, right.postfix, right.wrappedInnerExpressions); } else { // "left" + expr // => ("left" + expr + "") res.setWrapped(left, null, [right]); } } else if (left.isNumber()) { if (right.isString()) { res.setString(left.number + /** @type {string} */ (right.string)); } else if (right.isNumber()) { res.setNumber( /** @type {number} */ (left.number) + /** @type {number} */ (right.number) ); } else { return; } } else if (left.isBigInt()) { if (right.isBigInt()) { res.setBigInt( /** @type {bigint} */ (left.bigint) + /** @type {bigint} */ (right.bigint) ); } } else if (left.isWrapped()) { if (left.postfix && left.postfix.isString() && right.isString()) { // ("prefix" + inner + "postfix") + "right" // => ("prefix" + inner + "postfixRight") res.setWrapped( left.prefix, new BasicEvaluatedExpression() .setString( /** @type {string} */ (left.postfix.string) + /** @type {string} */ (right.string) ) .setRange( joinRanges( /** @t