Skip to content

Commit f04cfb1

Browse files
fix: Report.isOneLineRequire should be true if single line LogicalExpression assignment (#205)
--------- Co-authored-by: Thomas.G <gentilhomme.thomas@gmail.com>
1 parent 9d85fa2 commit f04cfb1

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import isMinified from "is-minified-code";
1010
import { SourceFile } from "./src/SourceFile.js";
1111
import { SourceParser } from "./src/SourceParser.js";
1212
import { warnings } from "./src/warnings.js";
13+
import { isOneLineExpressionExport } from "./src/utils.js";
1314

1415
export function runASTAnalysis(
1516
str,
@@ -46,7 +47,7 @@ export function runASTAnalysis(
4647
return {
4748
...source.getResult(isMinified),
4849
dependencies: source.dependencies,
49-
isOneLineRequire: body.length <= 1 && source.dependencies.size <= 1
50+
isOneLineRequire: isOneLineExpressionExport(body)
5051
};
5152
}
5253

src/utils.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,58 @@ export function isUnsafeCallee(node) {
3333
return [false, null];
3434
}
3535

36+
export function isOneLineExpressionExport(body) {
37+
if (body.length > 1) {
38+
return false;
39+
}
40+
41+
if (body[0].type !== "ExpressionStatement") {
42+
return false;
43+
}
44+
45+
if (body[0].expression.type !== "AssignmentExpression") {
46+
return false;
47+
}
48+
49+
return exportAssignmentHasRequireLeave(body[0].expression.right);
50+
}
51+
52+
export function exportAssignmentHasRequireLeave(expr) {
53+
if (expr.type === "LogicalExpression") {
54+
return atLeastOneBranchHasRequireLeave(expr.left, expr.right);
55+
}
56+
57+
if (expr.type === "ConditionalExpression") {
58+
return atLeastOneBranchHasRequireLeave(expr.consequent, expr.alternate);
59+
}
60+
61+
if (expr.type === "CallExpression") {
62+
return getCallExpressionIdentifier(expr) === "require";
63+
}
64+
65+
if (expr.type === "MemberExpression") {
66+
let rootMember = expr.object;
67+
while (rootMember.type === "MemberExpression") {
68+
rootMember = rootMember.object;
69+
}
70+
71+
if (rootMember.type !== "CallExpression") {
72+
return false;
73+
}
74+
75+
return getCallExpressionIdentifier(rootMember) === "require";
76+
}
77+
78+
return false;
79+
}
80+
81+
function atLeastOneBranchHasRequireLeave(left, right) {
82+
return [
83+
exportAssignmentHasRequireLeave(left),
84+
exportAssignmentHasRequireLeave(right)
85+
].some((hasRequire) => hasRequire);
86+
}
87+
3688
export function rootLocation() {
3789
return { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
3890
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Import Node.js Dependencies
2+
import { test } from "node:test";
3+
import assert from "node:assert";
4+
5+
// Import Internal Dependencies
6+
import { runASTAnalysis } from "../../index.js";
7+
8+
const validTestCases = [
9+
["module.exports = require('fs') || require('constants');", ["fs", "constants"]],
10+
["module.exports = require('constants') ? require('fs') : require('foo');", ["constants", "fs", "foo"]],
11+
12+
// should have at least one branch has a `require` callee
13+
["module.exports = require('constants') || {};", ["constants"]],
14+
["module.exports = {} || require('constants');", ["constants"]],
15+
["module.exports = require('constants') ? require('fs') : {};", ["constants", "fs"]],
16+
["module.exports = require('constants') ? {} : require('fs');", ["constants", "fs"]],
17+
18+
// should apply to nested conditions
19+
["module.exports = (require('constants') || {}) || (require('foo') || {});", ["constants", "foo"]],
20+
["module.exports = require('constants') ? (require('fs') || {}) : ({} || require('foo'));", ["constants", "fs", "foo"]],
21+
["module.exports = require('constants') ? ({} || require('fs')) : (require('foo') || {});", ["constants", "fs", "foo"]],
22+
["module.exports = require('constants') ? (require('fs') ? {} : require('bar')) : {};", ["constants", "fs", "bar"]],
23+
["module.exports = require('constants') ? {} : (require('fs') ? {} : require('bar'));", ["constants", "fs", "bar"]],
24+
25+
// test condition that are not `require` callees, here `notRequire('someModule')`, are ignored
26+
["module.exports = notRequire('someModule') ? require('constants') : require('foo');",
27+
["constants", "foo"]
28+
],
29+
["module.exports = ok ? (notRequire('someModule') ? require('constants') : require('foo')) : {};",
30+
["constants", "foo"]
31+
],
32+
["module.exports = ok ? {} : (notRequire('someModule') ? require('constants') : require('foo'));",
33+
["constants", "foo"]
34+
]
35+
];
36+
37+
test("it should return isOneLineRequire true given a single line CJS export with a valid assignment", () => {
38+
validTestCases.forEach((test) => {
39+
const [source, modules] = test;
40+
const { dependencies, isOneLineRequire } = runASTAnalysis(source);
41+
42+
assert.ok(isOneLineRequire);
43+
assert.deepEqual([...dependencies.keys()], modules);
44+
});
45+
});
46+
47+
const invalidTestCases = [
48+
// should have at least one `require` callee
49+
["module.exports = notRequire('foo') || {};", []],
50+
["module.exports = {} || notRequire('foo');", []],
51+
["module.exports = require('constants') ? {} : {};", ["constants"]],
52+
53+
// same behavior should apply to nested conditions
54+
["module.exports = (notRequire('foo') || {}) || (notRequire('foo') || {});", []],
55+
["module.exports = require('constants') ? (notRequire('foo') || {}) : (notRequire('foo') || {});", ["constants"]],
56+
["module.exports = require('constants') ? (notRequire('foo') || {}) : (notRequire('foo') || {});", ["constants"]],
57+
["module.exports = require('constants') ? (require('constants') ? {} : {}) : (require('constants') ? {} : {});", ["constants"]]
58+
];
59+
60+
test("it should return isOneLineRequire false given a single line CJS export with illegal callees", () => {
61+
invalidTestCases.forEach((test) => {
62+
const [source, modules] = test;
63+
const { dependencies, isOneLineRequire } = runASTAnalysis(source);
64+
65+
assert.ok(isOneLineRequire === false);
66+
assert.deepEqual([...dependencies.keys()], modules);
67+
});
68+
});

0 commit comments

Comments
 (0)