Skip to content

Commit c4be7e8

Browse files
committed
fix(core): detect webpack-bundled CommonJS default exports
1 parent c6c2e37 commit c4be7e8

4 files changed

Lines changed: 159 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@arethetypeswrong/core": patch
3+
---
4+
5+
Detect default exports in webpack-bundled CommonJS libraries.

packages/core/src/internal/getProbableExports.ts

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface Export {
88
}
99

1010
export function getProbableExports(sourceFile: ts.SourceFile): Export[] {
11-
return getEsbuildBabelSwcExports(sourceFile) ?? [];
11+
return getEsbuildBabelSwcExports(sourceFile) ?? getWebpackBootstrapExports(sourceFile) ?? [];
1212
}
1313

1414
function getEsbuildBabelSwcExports(sourceFile: ts.SourceFile): Export[] | undefined {
@@ -93,3 +93,112 @@ function isEsbuildExportFunction(decl: ts.Declaration | undefined) {
9393
function isProbablyMinified(text: string): boolean {
9494
return minifiedVariableAssignmentPattern.test(text);
9595
}
96+
97+
function getWebpackBootstrapExports(sourceFile: ts.SourceFile): Export[] | undefined {
98+
for (const statement of sourceFile.statements) {
99+
if (!ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression)) {
100+
continue;
101+
}
102+
const assignment = statement.expression;
103+
if (assignment.operatorToken.kind !== ts.SyntaxKind.EqualsToken || !isModuleExports(assignment.left)) {
104+
continue;
105+
}
106+
107+
const bootstrapCall = ts.skipParentheses(assignment.right);
108+
if (!ts.isCallExpression(bootstrapCall)) {
109+
continue;
110+
}
111+
112+
const bootstrap = ts.skipParentheses(bootstrapCall.expression);
113+
if (
114+
!ts.isFunctionExpression(bootstrap) ||
115+
!ts.isBlock(bootstrap.body) ||
116+
bootstrapCall.arguments.length !== 1 ||
117+
!ts.isArrayLiteralExpression(bootstrapCall.arguments[0])
118+
) {
119+
continue;
120+
}
121+
122+
const entryModuleId = getWebpackEntryModuleId(bootstrap.body);
123+
if (entryModuleId === undefined) {
124+
continue;
125+
}
126+
127+
const entryModule = bootstrapCall.arguments[0].elements[entryModuleId];
128+
if (!entryModule || !ts.isFunctionExpression(entryModule)) {
129+
continue;
130+
}
131+
132+
const exportsParameterName = entryModule.parameters[1]?.name;
133+
if (!exportsParameterName || !ts.isIdentifier(exportsParameterName)) {
134+
continue;
135+
}
136+
const exportsParameterText = exportsParameterName.text;
137+
138+
const exports: Export[] = [];
139+
visit(entryModule.body);
140+
return exports;
141+
142+
function visit(node: ts.Node) {
143+
if (
144+
ts.isBinaryExpression(node) &&
145+
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
146+
ts.isAccessExpression(node.left) &&
147+
ts.isIdentifier(node.left.expression) &&
148+
node.left.expression.text === exportsParameterText
149+
) {
150+
const name = getNameOfAccessExpression(node.left);
151+
if (name !== undefined) {
152+
exports.push({ name, node });
153+
}
154+
}
155+
ts.forEachChild(node, visit);
156+
}
157+
}
158+
}
159+
160+
function getWebpackEntryModuleId(body: ts.Block): number | undefined {
161+
for (const statement of body.statements) {
162+
if (!ts.isReturnStatement(statement) || !statement.expression) {
163+
continue;
164+
}
165+
166+
return getWebpackEntryModuleIdFromExpression(statement.expression);
167+
}
168+
}
169+
170+
function getWebpackEntryModuleIdFromExpression(expression: ts.Expression): number | undefined {
171+
expression = ts.skipParentheses(expression);
172+
if (ts.isCallExpression(expression) && expression.arguments.length === 1) {
173+
return getNumericValue(expression.arguments[0]);
174+
}
175+
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.CommaToken) {
176+
return getWebpackEntryModuleIdFromExpression(expression.right);
177+
}
178+
}
179+
180+
function getNumericValue(node: ts.Expression): number | undefined {
181+
node = ts.skipParentheses(node);
182+
if (ts.isNumericLiteral(node)) {
183+
return Number(node.text);
184+
}
185+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
186+
return getNumericValue(node.right);
187+
}
188+
}
189+
190+
function isModuleExports(node: ts.Expression) {
191+
return (
192+
ts.isAccessExpression(node) &&
193+
ts.isIdentifier(node.expression) &&
194+
node.expression.text === "module" &&
195+
getNameOfAccessExpression(node) === "exports"
196+
);
197+
}
198+
199+
function getNameOfAccessExpression(accessExpression: ts.AccessExpression): string | undefined {
200+
const node = ts.getNameOfAccessExpression(accessExpression);
201+
if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) {
202+
return node.text;
203+
}
204+
}

packages/core/test/getProbableExports.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,26 @@ _export(exports, {
7575
var _pluginHelper = require("@bbob/plugin-helper");
7676
var _parse = /*#__PURE__*/ _interopRequireWildcard(require("./parse"));`,
7777
},
78+
79+
webpack_cjs_default: {
80+
expected: ["__esModule", "default"],
81+
file: `module.exports = function(e) {
82+
var r = {};
83+
function t(n) {
84+
if (r[n]) return r[n].exports;
85+
var o = r[n] = { exports: {} };
86+
e[n].call(o.exports, o, o.exports, t);
87+
return o.exports;
88+
}
89+
return t.m = e, t.c = r, t(t.s = 0);
90+
}([
91+
function(e, r, t) {
92+
"use strict";
93+
r.__esModule = true;
94+
r.default = function accept() {};
95+
}
96+
]);`,
97+
},
7898
};
7999

80100
describe("getProbableExports", () => {

packages/core/test/problems/exportDefaultDisagreement.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,30 @@ var index_default = { a, b };`,
230230
);
231231
});
232232

233+
test("ignores bundled CommonJS default export with aliased exports object", () => {
234+
assert(
235+
isOk(
236+
`export default function accept(): boolean;`,
237+
`module.exports = function(e) {
238+
var r = {};
239+
function t(n) {
240+
if (r[n]) return r[n].exports;
241+
var o = r[n] = { exports: {} };
242+
e[n].call(o.exports, o, o.exports, t);
243+
return o.exports;
244+
}
245+
return t(0);
246+
}([
247+
function(e, r, t) {
248+
"use strict";
249+
r.__esModule = true;
250+
r.default = function accept() {};
251+
}
252+
]);`,
253+
),
254+
);
255+
});
256+
233257
test("types have single default export, JS has default and other named exports", () => {
234258
assert(
235259
isOk(

0 commit comments

Comments
 (0)