diff options
Diffstat (limited to 'node_modules/webpack/lib/dependencies/CommonJsImportsParserPlugin.js')
| -rw-r--r-- | node_modules/webpack/lib/dependencies/CommonJsImportsParserPlugin.js | 796 |
1 files changed, 796 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/dependencies/CommonJsImportsParserPlugin.js b/node_modules/webpack/lib/dependencies/CommonJsImportsParserPlugin.js new file mode 100644 index 00000000..0e64ef17 --- /dev/null +++ b/node_modules/webpack/lib/dependencies/CommonJsImportsParserPlugin.js @@ -0,0 +1,796 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { fileURLToPath } = require("url"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const WebpackError = require("../WebpackError"); +const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); +const { + evaluateToIdentifier, + evaluateToString, + expressionIsUnsupported, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); +const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); +const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); +const CommonJsRequireDependency = require("./CommonJsRequireDependency"); +const ConstDependency = require("./ConstDependency"); +const ContextDependencyHelpers = require("./ContextDependencyHelpers"); +const LocalModuleDependency = require("./LocalModuleDependency"); +const { getLocalModule } = require("./LocalModulesHelpers"); +const RequireHeaderDependency = require("./RequireHeaderDependency"); +const RequireResolveContextDependency = require("./RequireResolveContextDependency"); +const RequireResolveDependency = require("./RequireResolveDependency"); +const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").NewExpression} NewExpression */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const createRequireSpecifierTag = Symbol("createRequire"); +const createdRequireIdentifierTag = Symbol("createRequire()"); + +class CommonJsImportsParserPlugin { + /** + * @param {JavascriptParserOptions} options parser options + */ + constructor(options) { + this.options = options; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const options = this.options; + + const getContext = () => { + if (parser.currentTagData) { + const { context } = parser.currentTagData; + return context; + } + }; + + //#region metadata + /** + * @param {TODO} expression expression + * @param {() => string[]} getMembers get members + */ + const tapRequireExpression = (expression, getMembers) => { + parser.hooks.typeof + .for(expression) + .tap( + "CommonJsImportsParserPlugin", + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for(expression) + .tap("CommonJsImportsParserPlugin", evaluateToString("function")); + parser.hooks.evaluateIdentifier + .for(expression) + .tap( + "CommonJsImportsParserPlugin", + evaluateToIdentifier(expression, "require", getMembers, true) + ); + }; + /** + * @param {string | symbol} tag tag + */ + const tapRequireExpressionTag = tag => { + parser.hooks.typeof + .for(tag) + .tap( + "CommonJsImportsParserPlugin", + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for(tag) + .tap("CommonJsImportsParserPlugin", evaluateToString("function")); + }; + tapRequireExpression("require", () => []); + tapRequireExpression("require.resolve", () => ["resolve"]); + tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]); + //#endregion + + // Weird stuff // + parser.hooks.assign + .for("require") + .tap("CommonJsImportsParserPlugin", expr => { + // to not leak to global "require", we need to define a local require here. + const dep = new ConstDependency("var require;", 0); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + //#region Unsupported + parser.hooks.expression + .for("require.main") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "require.main is not supported by webpack." + ) + ); + parser.hooks.call + .for("require.main.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "require.main.require is not supported by webpack." + ) + ); + parser.hooks.expression + .for("module.parent.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "module.parent.require is not supported by webpack." + ) + ); + parser.hooks.call + .for("module.parent.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "module.parent.require is not supported by webpack." + ) + ); + //#endregion + + //#region Renaming + /** + * @param {Expression} expr expression + * @returns {boolean} true when set undefined + */ + const defineUndefined = expr => { + // To avoid "not defined" error, replace the value with undefined + const dep = new ConstDependency( + "undefined", + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return false; + }; + parser.hooks.canRename + .for("require") + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.rename + .for("require") + .tap("CommonJsImportsParserPlugin", defineUndefined); + //#endregion + + //#region Inspection + const requireCache = toConstantDependency( + parser, + RuntimeGlobals.moduleCache, + [ + RuntimeGlobals.moduleCache, + RuntimeGlobals.moduleId, + RuntimeGlobals.moduleLoaded + ] + ); + + parser.hooks.expression + .for("require.cache") + .tap("CommonJsImportsParserPlugin", requireCache); + //#endregion + + //#region Require as expression + /** + * @param {Expression} expr expression + * @returns {boolean} true when handled + */ + const requireAsExpressionHandler = expr => { + const dep = new CommonJsRequireContextDependency( + { + request: options.unknownContextRequest, + recursive: options.unknownContextRecursive, + regExp: options.unknownContextRegExp, + mode: "sync" + }, + /** @type {Range} */ (expr.range), + undefined, + parser.scope.inShorthand, + getContext() + ); + dep.critical = + options.unknownContextCritical && + "require function is used in a way in which dependencies cannot be statically extracted"; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = !!parser.scope.inTry; + parser.state.current.addDependency(dep); + return true; + }; + parser.hooks.expression + .for("require") + .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); + //#endregion + + //#region Require + /** + * @param {CallExpression | NewExpression} expr expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | void} true when handled + */ + const processRequireItem = (expr, param) => { + if (param.isString()) { + const dep = new CommonJsRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (param.range), + getContext() + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = !!parser.scope.inTry; + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression | NewExpression} expr expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | void} true when handled + */ + const processRequireContext = (expr, param) => { + const dep = ContextDependencyHelpers.create( + CommonJsRequireContextDependency, + /** @type {Range} */ (expr.range), + param, + expr, + options, + { + category: "commonjs" + }, + parser, + undefined, + getContext() + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = !!parser.scope.inTry; + parser.state.current.addDependency(dep); + return true; + }; + /** + * @param {boolean} callNew true, when require is called with new + * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler + */ + const createRequireHandler = callNew => expr => { + if (options.commonjsMagicComments) { + const { options: requireOptions, errors: commentErrors } = + parser.parseCommentOptions(/** @type {Range} */ (expr.range)); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + if (requireOptions) { + if (requireOptions.webpackIgnore !== undefined) { + if (typeof requireOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + // Do not instrument `require()` if `webpackIgnore` is `true` + if (requireOptions.webpackIgnore) { + return true; + } + } + } + } + } + + if (expr.arguments.length !== 1) return; + let localModule; + const param = parser.evaluateExpression(expr.arguments[0]); + if (param.isConditional()) { + let isExpression = false; + for (const p of /** @type {BasicEvaluatedExpression[]} */ ( + param.options + )) { + const result = processRequireItem(expr, p); + if (result === undefined) { + isExpression = true; + } + } + if (!isExpression) { + const dep = new RequireHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + if ( + param.isString() && + (localModule = getLocalModule( + parser.state, + /** @type {string} */ (param.string) + )) + ) { + localModule.flagUsed(); + const dep = new LocalModuleDependency( + localModule, + /** @type {Range} */ (expr.range), + callNew + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } else { + const result = processRequireItem(expr, param); + if (result === undefined) { + processRequireContext(expr, param); + } else { + const dep = new RequireHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } + return true; + } + }; + parser.hooks.call + .for("require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + parser.hooks.new + .for("require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); + parser.hooks.call + .for("module.require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + parser.hooks.new + .for("module.require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); + //#endregion + + //#region Require with property access + /** + * @param {Expression} expr expression + * @param {string[]} calleeMembers callee members + * @param {CallExpression} callExpr call expression + * @param {string[]} members members + * @param {Range[]} memberRanges member ranges + * @returns {boolean | void} true when handled + */ + const chainHandler = ( + expr, + calleeMembers, + callExpr, + members, + memberRanges + ) => { + if (callExpr.arguments.length !== 1) return; + const param = parser.evaluateExpression(callExpr.arguments[0]); + if ( + param.isString() && + !getLocalModule(parser.state, /** @type {string} */ (param.string)) + ) { + const dep = new CommonJsFullRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range), + members, + /** @type {Range[]} */ memberRanges + ); + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.optional = !!parser.scope.inTry; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression} expr expression + * @param {string[]} calleeMembers callee members + * @param {CallExpression} callExpr call expression + * @param {string[]} members members + * @param {Range[]} memberRanges member ranges + * @returns {boolean | void} true when handled + */ + const callChainHandler = ( + expr, + calleeMembers, + callExpr, + members, + memberRanges + ) => { + if (callExpr.arguments.length !== 1) return; + const param = parser.evaluateExpression(callExpr.arguments[0]); + if ( + param.isString() && + !getLocalModule(parser.state, /** @type {string} */ (param.string)) + ) { + const dep = new CommonJsFullRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.callee.range), + members, + /** @type {Range[]} */ memberRanges + ); + dep.call = true; + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.optional = !!parser.scope.inTry; + dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc); + parser.state.current.addDependency(dep); + parser.walkExpressions(expr.arguments); + return true; + } + }; + parser.hooks.memberChainOfCallMemberChain + .for("require") + .tap("CommonJsImportsParserPlugin", chainHandler); + parser.hooks.memberChainOfCallMemberChain + .for("module.require") + .tap("CommonJsImportsParserPlugin", chainHandler); + parser.hooks.callMemberChainOfCallMemberChain + .for("require") + .tap("CommonJsImportsParserPlugin", callChainHandler); + parser.hooks.callMemberChainOfCallMemberChain + .for("module.require") + .tap("CommonJsImportsParserPlugin", callChainHandler); + //#endregion + + //#region Require.resolve + /** + * @param {CallExpression} expr call expression + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolve = (expr, weak) => { + if (expr.arguments.length !== 1) return; + const param = parser.evaluateExpression(expr.arguments[0]); + if (param.isConditional()) { + for (const option of /** @type {BasicEvaluatedExpression[]} */ ( + param.options + )) { + const result = processResolveItem(expr, option, weak); + if (result === undefined) { + processResolveContext(expr, option, weak); + } + } + const dep = new RequireResolveHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } else { + const result = processResolveItem(expr, param, weak); + if (result === undefined) { + processResolveContext(expr, param, weak); + } + const dep = new RequireResolveHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + }; + /** + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolveItem = (expr, param, weak) => { + if (param.isString()) { + const dep = new RequireResolveDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (param.range), + getContext() + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = !!parser.scope.inTry; + dep.weak = weak; + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolveContext = (expr, param, weak) => { + const dep = ContextDependencyHelpers.create( + RequireResolveContextDependency, + /** @type {Range} */ (param.range), + param, + expr, + options, + { + category: "commonjs", + mode: weak ? "weak" : "sync" + }, + parser, + getContext() + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = !!parser.scope.inTry; + parser.state.current.addDependency(dep); + return true; + }; + + parser.hooks.call + .for("require.resolve") + .tap("CommonJsImportsParserPlugin", expr => { + return processResolve(expr, false); + }); + parser.hooks.call + .for("require.resolveWeak") + .tap("CommonJsImportsParserPlugin", expr => { + return processResolve(expr, true); + }); + //#endregion + + //#region Create require + + if (!options.createRequire) return; + + /** @type {ImportSource[]} */ + let moduleName = []; + /** @type {string | undefined} */ + let specifierName; + + if (options.createRequire === true) { + moduleName = ["module", "node:module"]; + specifierName = "createRequire"; + } else { + let moduleName; + const match = /^(.*) from (.*)$/.exec(options.createRequire); + if (match) { + [, specifierName, moduleName] = match; + } + if (!specifierName || !moduleName) { + const err = new WebpackError( + `Parsing javascript parser option "createRequire" failed, got ${JSON.stringify( + options.createRequire + )}` + ); + err.details = + 'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module'; + throw err; + } + } + + tapRequireExpressionTag(createdRequireIdentifierTag); + tapRequireExpressionTag(createRequireSpecifierTag); + parser.hooks.evaluateCallExpression + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", expr => { + const context = parseCreateRequireArguments(expr); + if (context === undefined) return; + const ident = parser.evaluatedVariable({ + tag: createdRequireIdentifierTag, + data: { context }, + next: undefined + }); + return new BasicEvaluatedExpression() + .setIdentifier( + /** @type {TODO} */ (ident), + /** @type {TODO} */ (ident), + () => [] + ) + .setSideEffects(false) + .setRange(/** @type {Range} */ (expr.range)); + }); + parser.hooks.unhandledExpressionMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => { + return expressionIsUnsupported( + parser, + `createRequire().${members.join(".")} is not supported by webpack.` + )(expr); + }); + parser.hooks.canRename + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.canRename + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.rename + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", defineUndefined); + parser.hooks.expression + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); + parser.hooks.call + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + /** + * @param {CallExpression} expr call expression + * @returns {string | void} context + */ + const parseCreateRequireArguments = expr => { + const args = expr.arguments; + if (args.length !== 1) { + const err = new WebpackError( + "module.createRequire supports only one argument." + ); + err.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addWarning(err); + return; + } + const arg = args[0]; + const evaluated = parser.evaluateExpression(arg); + if (!evaluated.isString()) { + const err = new WebpackError( + "module.createRequire failed parsing argument." + ); + err.loc = /** @type {DependencyLocation} */ (arg.loc); + parser.state.module.addWarning(err); + return; + } + const ctx = /** @type {string} */ (evaluated.string).startsWith("file://") + ? fileURLToPath(/** @type {string} */ (evaluated.string)) + : /** @type {string} */ (evaluated.string); + // argument always should be a filename + return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\")); + }; + + parser.hooks.import.tap( + { + name: "CommonJsImportsParserPlugin", + stage: -10 + }, + (statement, source) => { + if ( + !moduleName.includes(source) || + statement.specifiers.length !== 1 || + statement.specifiers[0].type !== "ImportSpecifier" || + statement.specifiers[0].imported.type !== "Identifier" || + statement.specifiers[0].imported.name !== specifierName + ) + return; + // clear for 'import { createRequire as x } from "module"' + // if any other specifier was used import module + const clearDep = new ConstDependency( + parser.isAsiPosition(/** @type {Range} */ (statement.range)[0]) + ? ";" + : "", + /** @type {Range} */ (statement.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); + parser.state.module.addPresentationalDependency(clearDep); + parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]); + return true; + } + ); + parser.hooks.importSpecifier.tap( + { + name: "CommonJsImportsParserPlugin", + stage: -10 + }, + (statement, source, id, name) => { + if (!moduleName.includes(source) || id !== specifierName) return; + parser.tagVariable(name, createRequireSpecifierTag); + return true; + } + ); + parser.hooks.preDeclarator.tap( + "CommonJsImportsParserPlugin", + declarator => { + if ( + declarator.id.type !== "Identifier" || + !declarator.init || + declarator.init.type !== "CallExpression" || + declarator.init.callee.type !== "Identifier" + ) + return; + const variableInfo = + /** @type {TODO} */ + (parser.getVariableInfo(declarator.init.callee.name)); + if ( + variableInfo && + variableInfo.tagInfo && + variableInfo.tagInfo.tag === createRequireSpecifierTag + ) { + const context = parseCreateRequireArguments(declarator.init); + if (context === undefined) return; + parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, { + name: declarator.id.name, + context + }); + return true; + } + } + ); + + parser.hooks.memberChainOfCallMemberChain + .for(createRequireSpecifierTag) + .tap( + "CommonJsImportsParserPlugin", + (expr, calleeMembers, callExpr, members) => { + if ( + calleeMembers.length !== 0 || + members.length !== 1 || + members[0] !== "cache" + ) + return; + // createRequire().cache + const context = parseCreateRequireArguments(callExpr); + if (context === undefined) return; + return requireCache(expr); + } + ); + parser.hooks.callMemberChainOfCallMemberChain + .for(createRequireSpecifierTag) + .tap( + "CommonJsImportsParserPlugin", + (expr, calleeMembers, innerCallExpression, members) => { + if ( + calleeMembers.length !== 0 || + members.length !== 1 || + members[0] !== "resolve" + ) + return; + // createRequire().resolve() + return processResolve(expr, false); + } + ); + parser.hooks.expressionMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => { + // require.cache + if (members.length === 1 && members[0] === "cache") { + return requireCache(expr); + } + }); + parser.hooks.callMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => { + // require.resolve() + if (members.length === 1 && members[0] === "resolve") { + return processResolve(expr, false); + } + }); + parser.hooks.call + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", expr => { + const clearDep = new ConstDependency( + "/* createRequire() */ undefined", + /** @type {Range} */ (expr.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(clearDep); + return true; + }); + //#endregion + } +} +module.exports = CommonJsImportsParserPlugin; |
