From c4be7e8a41da3318038879fb70b8b98239509f40 Mon Sep 17 00:00:00 2001 From: morgan-coded <256248948+morgan-coded@users.noreply.github.com> Date: Sun, 28 Jun 2026 16:05:35 -0500 Subject: [PATCH] fix(core): detect webpack-bundled CommonJS default exports --- .changeset/gentle-bundles-detect.md | 5 + .../core/src/internal/getProbableExports.ts | 111 +++++++++++++++++- packages/core/test/getProbableExports.test.ts | 20 ++++ .../exportDefaultDisagreement.test.ts | 24 ++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 .changeset/gentle-bundles-detect.md diff --git a/.changeset/gentle-bundles-detect.md b/.changeset/gentle-bundles-detect.md new file mode 100644 index 0000000..3eec2d8 --- /dev/null +++ b/.changeset/gentle-bundles-detect.md @@ -0,0 +1,5 @@ +--- +"@arethetypeswrong/core": patch +--- + +Detect default exports in webpack-bundled CommonJS libraries. diff --git a/packages/core/src/internal/getProbableExports.ts b/packages/core/src/internal/getProbableExports.ts index cdbbb27..caf2efc 100644 --- a/packages/core/src/internal/getProbableExports.ts +++ b/packages/core/src/internal/getProbableExports.ts @@ -8,7 +8,7 @@ export interface Export { } export function getProbableExports(sourceFile: ts.SourceFile): Export[] { - return getEsbuildBabelSwcExports(sourceFile) ?? []; + return getEsbuildBabelSwcExports(sourceFile) ?? getWebpackBootstrapExports(sourceFile) ?? []; } function getEsbuildBabelSwcExports(sourceFile: ts.SourceFile): Export[] | undefined { @@ -93,3 +93,112 @@ function isEsbuildExportFunction(decl: ts.Declaration | undefined) { function isProbablyMinified(text: string): boolean { return minifiedVariableAssignmentPattern.test(text); } + +function getWebpackBootstrapExports(sourceFile: ts.SourceFile): Export[] | undefined { + for (const statement of sourceFile.statements) { + if (!ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression)) { + continue; + } + const assignment = statement.expression; + if (assignment.operatorToken.kind !== ts.SyntaxKind.EqualsToken || !isModuleExports(assignment.left)) { + continue; + } + + const bootstrapCall = ts.skipParentheses(assignment.right); + if (!ts.isCallExpression(bootstrapCall)) { + continue; + } + + const bootstrap = ts.skipParentheses(bootstrapCall.expression); + if ( + !ts.isFunctionExpression(bootstrap) || + !ts.isBlock(bootstrap.body) || + bootstrapCall.arguments.length !== 1 || + !ts.isArrayLiteralExpression(bootstrapCall.arguments[0]) + ) { + continue; + } + + const entryModuleId = getWebpackEntryModuleId(bootstrap.body); + if (entryModuleId === undefined) { + continue; + } + + const entryModule = bootstrapCall.arguments[0].elements[entryModuleId]; + if (!entryModule || !ts.isFunctionExpression(entryModule)) { + continue; + } + + const exportsParameterName = entryModule.parameters[1]?.name; + if (!exportsParameterName || !ts.isIdentifier(exportsParameterName)) { + continue; + } + const exportsParameterText = exportsParameterName.text; + + const exports: Export[] = []; + visit(entryModule.body); + return exports; + + function visit(node: ts.Node) { + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isAccessExpression(node.left) && + ts.isIdentifier(node.left.expression) && + node.left.expression.text === exportsParameterText + ) { + const name = getNameOfAccessExpression(node.left); + if (name !== undefined) { + exports.push({ name, node }); + } + } + ts.forEachChild(node, visit); + } + } +} + +function getWebpackEntryModuleId(body: ts.Block): number | undefined { + for (const statement of body.statements) { + if (!ts.isReturnStatement(statement) || !statement.expression) { + continue; + } + + return getWebpackEntryModuleIdFromExpression(statement.expression); + } +} + +function getWebpackEntryModuleIdFromExpression(expression: ts.Expression): number | undefined { + expression = ts.skipParentheses(expression); + if (ts.isCallExpression(expression) && expression.arguments.length === 1) { + return getNumericValue(expression.arguments[0]); + } + if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.CommaToken) { + return getWebpackEntryModuleIdFromExpression(expression.right); + } +} + +function getNumericValue(node: ts.Expression): number | undefined { + node = ts.skipParentheses(node); + if (ts.isNumericLiteral(node)) { + return Number(node.text); + } + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) { + return getNumericValue(node.right); + } +} + +function isModuleExports(node: ts.Expression) { + return ( + ts.isAccessExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === "module" && + getNameOfAccessExpression(node) === "exports" + ); +} + +function getNameOfAccessExpression(accessExpression: ts.AccessExpression): string | undefined { + const node = ts.getNameOfAccessExpression(accessExpression); + if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) { + return node.text; + } +} diff --git a/packages/core/test/getProbableExports.test.ts b/packages/core/test/getProbableExports.test.ts index dc0f6f9..ea50888 100644 --- a/packages/core/test/getProbableExports.test.ts +++ b/packages/core/test/getProbableExports.test.ts @@ -75,6 +75,26 @@ _export(exports, { var _pluginHelper = require("@bbob/plugin-helper"); var _parse = /*#__PURE__*/ _interopRequireWildcard(require("./parse"));`, }, + + webpack_cjs_default: { + expected: ["__esModule", "default"], + file: `module.exports = function(e) { + var r = {}; + function t(n) { + if (r[n]) return r[n].exports; + var o = r[n] = { exports: {} }; + e[n].call(o.exports, o, o.exports, t); + return o.exports; + } + return t.m = e, t.c = r, t(t.s = 0); +}([ + function(e, r, t) { + "use strict"; + r.__esModule = true; + r.default = function accept() {}; + } +]);`, + }, }; describe("getProbableExports", () => { diff --git a/packages/core/test/problems/exportDefaultDisagreement.test.ts b/packages/core/test/problems/exportDefaultDisagreement.test.ts index f2c1af0..cba312e 100644 --- a/packages/core/test/problems/exportDefaultDisagreement.test.ts +++ b/packages/core/test/problems/exportDefaultDisagreement.test.ts @@ -230,6 +230,30 @@ var index_default = { a, b };`, ); }); + test("ignores bundled CommonJS default export with aliased exports object", () => { + assert( + isOk( + `export default function accept(): boolean;`, + `module.exports = function(e) { + var r = {}; + function t(n) { + if (r[n]) return r[n].exports; + var o = r[n] = { exports: {} }; + e[n].call(o.exports, o, o.exports, t); + return o.exports; + } + return t(0); + }([ + function(e, r, t) { + "use strict"; + r.__esModule = true; + r.default = function accept() {}; + } + ]);`, + ), + ); + }); + test("types have single default export, JS has default and other named exports", () => { assert( isOk(