Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(types): Increase type coverage for CommonJsExportsParserPlugin #17046

Merged
merged 2 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/dependencies/CommonJsDependencyHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@

const RuntimeGlobals = require("../RuntimeGlobals");

/** @typedef {import("../Module")} Module */
/** @typedef {"exports" | "module.exports" | "this" | "Object.defineProperty(exports)" | "Object.defineProperty(module.exports)" | "Object.defineProperty(this)"} CommonJSDependencyBaseKeywords */

/**
* @param {CommonJSDependencyBaseKeywords} depBase commonjs dependency base
* @param {Module} module module
* @param {Set<string>} runtimeRequirements runtime requirements
* @returns {[string, string]} type and base
*/
exports.handleDependencyBase = (depBase, module, runtimeRequirements) => {
let base = undefined;
let type;
Expand Down
68 changes: 65 additions & 3 deletions lib/dependencies/CommonJsExportsParserPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,36 @@ const DynamicExports = require("./DynamicExports");
const HarmonyExports = require("./HarmonyExports");
const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");

/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
/** @typedef {import("estree").CallExpression} CallExpression */
/** @typedef {import("estree").Expression} ExpressionNode */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("estree").Super} Super */

/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */

/**
* This function takes a generic expression and detects whether it is an ObjectExpression.
* This is used in the context of parsing CommonJS exports to get the value of the property descriptor
* when the `exports` object is assigned to `Object.defineProperty`.
*
* In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore
* webpack has to detect this case and get the value key of the property descriptor. See the following example
* for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57
*
* This would be an example of a CommonJS module that exports an object with a property descriptor:
* ```js
* Object.defineProperty(exports, "__esModule", { value: true });
* exports.foo = void 0;
* exports.foo = "bar";
* ```
*
* @param {TODO} expr expression
TheLarkInn marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Expression} returns the value of property descriptor
*/
const getValueOfPropertyDescription = expr => {
if (expr.type !== "ObjectExpression") return;
for (const property of expr.properties) {
Expand All @@ -31,6 +56,15 @@ const getValueOfPropertyDescription = expr => {
}
};

/**
* The purpose of this function is to check whether an expression is a truthy literal or not. This is
* useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
* values like `null` and `false`. However, exports should only be created if the exported value is truthy.
*
* @param {Expression} expr expression being checked
* @returns {boolean} true, when the expression is a truthy literal
*
*/
const isTruthyLiteral = expr => {
switch (expr.type) {
case "Literal":
Expand All @@ -41,6 +75,14 @@ const isTruthyLiteral = expr => {
return false;
};

/**
* The purpose of this function is to check whether an expression is a falsy literal or not. This is
* useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy
* values like `null` and `false`. However, exports should only be created if the exported value is truthy.
*
* @param {Expression} expr expression being checked
* @returns {boolean} true, when the expression is a falsy literal
*/
const isFalsyLiteral = expr => {
switch (expr.type) {
case "Literal":
Expand Down Expand Up @@ -97,6 +139,13 @@ class CommonJsExportsParserPlugin {
const enableStructuredExports = () => {
DynamicExports.enable(parser.state);
};

/**
* @param {boolean} topLevel true, when the export is on top level
* @param {string[]} members members of the export
* @param {Expression} valueExpr expression for the value
* @returns {void}
*/
const checkNamespace = (topLevel, members, valueExpr) => {
if (!DynamicExports.isEnabled(parser.state)) return;
if (members.length > 0 && members[0] === "__esModule") {
Expand Down Expand Up @@ -126,6 +175,13 @@ class CommonJsExportsParserPlugin {
.tap("CommonJsPlugin", evaluateToString("object"));

// exporting //

/**
* @param {AssignmentExpression} expr expression
* @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
* @param {string[]} members members of the export
* @returns {boolean} true, when the expression was handled
*/
const handleAssignExport = (expr, base, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
// Handle reexporting
Expand Down Expand Up @@ -192,9 +248,7 @@ class CommonJsExportsParserPlugin {
parser.hooks.call
.for("Object.defineProperty")
.tap("CommonJsExportsParserPlugin", expression => {
const expr = /** @type {import("estree").CallExpression} */ (
expression
);
const expr = /** @type {CallExpression} */ (expression);
if (!parser.isStatementLevelExpression(expr)) return;
if (expr.arguments.length !== 3) return;
if (expr.arguments[0].type === "SpreadElement") return;
Expand Down Expand Up @@ -233,6 +287,14 @@ class CommonJsExportsParserPlugin {
});

// Self reference //

/**
* @param {Expression | Super} expr expression
* @param {CommonJSDependencyBaseKeywords} base commonjs base keywords
* @param {string[]} members members of the export
* @param {CallExpression} call call expression
* @returns {boolean} true, when the expression was handled
*/
const handleAccessExport = (expr, base, members, call = undefined) => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (members.length === 0) {
Expand Down