| | |
| | | getImportAttributes |
| | | } = require("../javascript/JavascriptParser"); |
| | | const InnerGraph = require("../optimize/InnerGraph"); |
| | | const AppendOnlyStackedSet = require("../util/AppendOnlyStackedSet"); |
| | | const ConstDependency = require("./ConstDependency"); |
| | | const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); |
| | | const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); |
| | | const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency"); |
| | | const HarmonyExports = require("./HarmonyExports"); |
| | | const { ExportPresenceModes } = require("./HarmonyImportDependency"); |
| | | const { |
| | | ExportPresenceModes, |
| | | getNonOptionalPart |
| | | } = require("./HarmonyImportDependency"); |
| | | const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); |
| | | const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); |
| | | const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase"); |
| | | |
| | | /** @typedef {import("estree").Expression} Expression */ |
| | | /** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */ |
| | | /** @typedef {import("estree").Identifier} Identifier */ |
| | | /** @typedef {import("estree").MemberExpression} MemberExpression */ |
| | | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ |
| | |
| | | /** @typedef {import("../javascript/JavascriptParser").Members} Members */ |
| | | /** @typedef {import("../javascript/JavascriptParser").MembersOptionals} MembersOptionals */ |
| | | /** @typedef {import("./HarmonyImportDependency").Ids} Ids */ |
| | | /** @typedef {import("./HarmonyImportDependency").ExportPresenceMode} ExportPresenceMode */ |
| | | /** @typedef {import("./ImportPhase").ImportPhaseType} ImportPhaseType */ |
| | | |
| | | /** |
| | | * Defines the harmony specifier guards type used by this module. |
| | | * @typedef {object} HarmonySpecifierGuards |
| | | * @property {AppendOnlyStackedSet<string> | undefined} guards |
| | | */ |
| | | |
| | | /** @typedef {Map<string, Set<string>>} Guards Map of import root to guarded member keys */ |
| | | |
| | | const harmonySpecifierTag = Symbol("harmony import"); |
| | | const harmonySpecifierGuardTag = Symbol("harmony import guard"); |
| | | |
| | | /** |
| | | * Defines the harmony settings type used by this module. |
| | | * @typedef {object} HarmonySettings |
| | | * @property {Ids} ids |
| | | * @property {string} source |
| | |
| | | |
| | | const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin"; |
| | | |
| | | /** |
| | | * Gets in operator harmony import info. |
| | | * @param {JavascriptParser} parser the parser |
| | | * @param {PrivateIdentifier | Expression} left left expression |
| | | * @param {Expression} right right expression |
| | | * @returns {{ leftPart: string, members: Members, settings: HarmonySettings } | undefined} info |
| | | */ |
| | | const getInOperatorHarmonyImportInfo = (parser, left, right) => { |
| | | const leftPartEvaluated = parser.evaluateExpression(left); |
| | | if (leftPartEvaluated.couldHaveSideEffects()) return; |
| | | /** @type {string | undefined} */ |
| | | const leftPart = leftPartEvaluated.asString(); |
| | | if (!leftPart) return; |
| | | |
| | | const rightPart = parser.evaluateExpression(right); |
| | | if (!rightPart.isIdentifier()) return; |
| | | |
| | | const rootInfo = rightPart.rootInfo; |
| | | const root = |
| | | typeof rootInfo === "string" |
| | | ? rootInfo |
| | | : rootInfo instanceof VariableInfo |
| | | ? rootInfo.name |
| | | : undefined; |
| | | if (!root) return; |
| | | |
| | | const settings = /** @type {HarmonySettings | undefined} */ ( |
| | | parser.getTagData(root, harmonySpecifierTag) |
| | | ); |
| | | if (!settings) { |
| | | return; |
| | | } |
| | | |
| | | return { |
| | | leftPart, |
| | | members: /** @type {(() => Members)} */ (rightPart.getMembers)(), |
| | | settings |
| | | }; |
| | | }; |
| | | |
| | | module.exports = class HarmonyImportDependencyParserPlugin { |
| | | /** |
| | | * Creates an instance of HarmonyImportDependencyParserPlugin. |
| | | * @param {JavascriptParserOptions} options options |
| | | */ |
| | | constructor(options) { |
| | | this.options = options; |
| | | this.exportPresenceMode = |
| | | options.importExportsPresence !== undefined |
| | | ? ExportPresenceModes.fromUserOption(options.importExportsPresence) |
| | | : options.exportsPresence !== undefined |
| | | ? ExportPresenceModes.fromUserOption(options.exportsPresence) |
| | | : options.strictExportPresence |
| | | ? ExportPresenceModes.ERROR |
| | | : ExportPresenceModes.AUTO; |
| | | /** @type {ExportPresenceMode} */ |
| | | this.exportPresenceMode = ExportPresenceModes.resolveFromOptions( |
| | | options.importExportsPresence, |
| | | options |
| | | ); |
| | | this.strictThisContextOnImports = options.strictThisContextOnImports; |
| | | } |
| | | |
| | | /** |
| | | * Gets export presence mode. |
| | | * @param {JavascriptParser} parser the parser |
| | | * @param {HarmonySettings} settings settings |
| | | * @param {Ids} ids ids |
| | | * @returns {ExportPresenceMode} exportPresenceMode |
| | | */ |
| | | getExportPresenceMode(parser, settings, ids) { |
| | | // Guards only apply to namespace imports |
| | | if (settings.ids.length) return this.exportPresenceMode; |
| | | |
| | | const harmonySettings = /** @type {HarmonySettings=} */ ( |
| | | parser.currentTagData |
| | | ); |
| | | if (!harmonySettings) return this.exportPresenceMode; |
| | | |
| | | const data = /** @type {HarmonySpecifierGuards=} */ ( |
| | | parser.getTagData(harmonySettings.name, harmonySpecifierGuardTag) |
| | | ); |
| | | |
| | | if (data && data.guards && data.guards.has(ids[0])) { |
| | | return ExportPresenceModes.NONE; |
| | | } |
| | | |
| | | return this.exportPresenceMode; |
| | | } |
| | | |
| | | /** |
| | | * Applies the plugin by registering its hooks on the compiler. |
| | | * @param {JavascriptParser} parser the parser |
| | | * @returns {void} |
| | | */ |
| | | apply(parser) { |
| | | const { exportPresenceMode } = this; |
| | | |
| | | const getImportPhase = createGetImportPhase(this.options.deferImport); |
| | | const getImportPhase = createGetImportPhase( |
| | | this.options.deferImport, |
| | | this.options.sourceImport |
| | | ); |
| | | |
| | | /** |
| | | * @param {Members} members members |
| | | * @param {MembersOptionals} membersOptionals members Optionals |
| | | * @returns {Ids} a non optional part |
| | | */ |
| | | function getNonOptionalPart(members, membersOptionals) { |
| | | let i = 0; |
| | | while (i < members.length && membersOptionals[i] === false) i++; |
| | | return i !== members.length ? members.slice(0, i) : members; |
| | | } |
| | | |
| | | /** |
| | | * Gets non optional member chain. |
| | | * @param {MemberExpression} node member expression |
| | | * @param {number} count count |
| | | * @returns {Expression} member expression |
| | |
| | | ); |
| | | parser.hooks.binaryExpression.tap(PLUGIN_NAME, (expression) => { |
| | | if (expression.operator !== "in") return; |
| | | const info = getInOperatorHarmonyImportInfo( |
| | | parser, |
| | | expression.left, |
| | | expression.right |
| | | ); |
| | | if (!info) return; |
| | | |
| | | const leftPartEvaluated = parser.evaluateExpression(expression.left); |
| | | if (leftPartEvaluated.couldHaveSideEffects()) return; |
| | | /** @type {string | undefined} */ |
| | | const leftPart = leftPartEvaluated.asString(); |
| | | if (!leftPart) return; |
| | | |
| | | const rightPart = parser.evaluateExpression(expression.right); |
| | | if (!rightPart.isIdentifier()) return; |
| | | |
| | | const rootInfo = rightPart.rootInfo; |
| | | if ( |
| | | typeof rootInfo === "string" || |
| | | !rootInfo || |
| | | !rootInfo.tagInfo || |
| | | rootInfo.tagInfo.tag !== harmonySpecifierTag |
| | | ) { |
| | | return; |
| | | } |
| | | const settings = |
| | | /** @type {HarmonySettings} */ |
| | | (rootInfo.tagInfo.data); |
| | | const members = |
| | | /** @type {(() => Members)} */ |
| | | (rightPart.getMembers)(); |
| | | const { leftPart, members, settings } = info; |
| | | const dep = new HarmonyEvaluatedImportSpecifierDependency( |
| | | settings.source, |
| | | settings.sourceOrder, |
| | |
| | | .for(harmonySpecifierTag) |
| | | .tap(PLUGIN_NAME, (expr) => { |
| | | const settings = /** @type {HarmonySettings} */ (parser.currentTagData); |
| | | |
| | | const dep = new HarmonyImportSpecifierDependency( |
| | | settings.source, |
| | | settings.sourceOrder, |
| | |
| | | settings.name, |
| | | /** @type {Range} */ |
| | | (expr.range), |
| | | exportPresenceMode, |
| | | this.exportPresenceMode, |
| | | settings.phase, |
| | | settings.attributes, |
| | | [] |
| | |
| | | settings.name, |
| | | /** @type {Range} */ |
| | | (expr.range), |
| | | exportPresenceMode, |
| | | this.getExportPresenceMode(parser, settings, ids), |
| | | settings.phase, |
| | | settings.attributes, |
| | | ranges |
| | |
| | | ids, |
| | | settings.name, |
| | | /** @type {Range} */ (expr.range), |
| | | exportPresenceMode, |
| | | this.getExportPresenceMode(parser, settings, ids), |
| | | settings.phase, |
| | | settings.attributes, |
| | | ranges |
| | |
| | | parser.state.module.addDependency(dep); |
| | | } |
| | | }); |
| | | |
| | | /** |
| | | * Processes the provided guard. |
| | | * @param {Guards} guards guards |
| | | * @param {() => void} walk walk callback |
| | | * @returns {void} |
| | | */ |
| | | const withGuards = (guards, walk) => { |
| | | const applyGuards = () => { |
| | | /** @type {(() => void)[]} */ |
| | | const restoreFns = []; |
| | | |
| | | for (const [rootName, members] of guards) { |
| | | const previous = parser.getVariableInfo(rootName); |
| | | const exist = /** @type {HarmonySpecifierGuards=} */ ( |
| | | parser.getTagData(rootName, harmonySpecifierGuardTag) |
| | | ); |
| | | |
| | | const mergedGuards = |
| | | exist && exist.guards |
| | | ? exist.guards.createChild() |
| | | : new AppendOnlyStackedSet(); |
| | | |
| | | for (const memberKey of members) mergedGuards.add(memberKey); |
| | | parser.tagVariable(rootName, harmonySpecifierGuardTag, { |
| | | guards: mergedGuards |
| | | }); |
| | | restoreFns.push(() => { |
| | | parser.setVariable(rootName, previous); |
| | | }); |
| | | } |
| | | |
| | | return () => { |
| | | for (const restore of restoreFns) { |
| | | restore(); |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const restore = applyGuards(); |
| | | try { |
| | | walk(); |
| | | } finally { |
| | | restore(); |
| | | } |
| | | }; |
| | | |
| | | if (this.exportPresenceMode !== ExportPresenceModes.NONE) { |
| | | parser.hooks.collectGuards.tap(PLUGIN_NAME, (expression) => { |
| | | if (parser.scope.isAsmJs) return; |
| | | /** @type {Guards} */ |
| | | const guards = new Map(); |
| | | |
| | | /** |
| | | * Processes the provided expression. |
| | | * @param {Expression} expression expression |
| | | * @param {boolean} needTruthy need to be truthy |
| | | */ |
| | | const collect = (expression, needTruthy) => { |
| | | if ( |
| | | expression.type === "UnaryExpression" && |
| | | expression.operator === "!" |
| | | ) { |
| | | collect(expression.argument, !needTruthy); |
| | | return; |
| | | } else if (expression.type === "LogicalExpression" && needTruthy) { |
| | | if (expression.operator === "&&") { |
| | | collect(expression.left, true); |
| | | collect(expression.right, true); |
| | | } else if (expression.operator === "||") { |
| | | const leftEvaluation = parser.evaluateExpression(expression.left); |
| | | const leftBool = leftEvaluation.asBool(); |
| | | if (leftBool === false) { |
| | | collect(expression.right, true); |
| | | } |
| | | } else if (expression.operator === "??") { |
| | | const leftEvaluation = parser.evaluateExpression(expression.left); |
| | | const leftNullish = leftEvaluation.asNullish(); |
| | | if (leftNullish === true) { |
| | | collect(expression.right, true); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | if (!needTruthy) return; |
| | | |
| | | // Direct `"x" in ns` guards |
| | | if ( |
| | | expression.type === "BinaryExpression" && |
| | | expression.operator === "in" |
| | | ) { |
| | | if (expression.right.type !== "Identifier") { |
| | | return; |
| | | } |
| | | const info = getInOperatorHarmonyImportInfo( |
| | | parser, |
| | | expression.left, |
| | | expression.right |
| | | ); |
| | | if (!info) return; |
| | | |
| | | const { settings, leftPart, members } = info; |
| | | // Only direct namespace guards |
| | | if (members.length > 0) return; |
| | | const guarded = guards.get(settings.name); |
| | | if (guarded) { |
| | | guarded.add(leftPart); |
| | | return; |
| | | } |
| | | |
| | | guards.set(settings.name, new Set([leftPart])); |
| | | } |
| | | }; |
| | | |
| | | collect(expression, true); |
| | | |
| | | if (guards.size === 0) return; |
| | | return (walk) => { |
| | | withGuards(guards, walk); |
| | | }; |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | module.exports.harmonySpecifierGuardTag = harmonySpecifierGuardTag; |
| | | module.exports.harmonySpecifierTag = harmonySpecifierTag; |