Skip to content

Commit 5cc6031

Browse files
authored
Merge pull request #272 from NodeSecure/fix-entry-files-analyser
Fix entry files analyser
2 parents 5fcd7a5 + 777ed54 commit 5cc6031

8 files changed

+124
-93
lines changed

src/EntryFilesAnalyser.js

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Import Node.js Dependencies
22
import fs from "node:fs/promises";
33
import path from "node:path";
4+
import { fileURLToPath } from "node:url";
45

56
// Import Internal Dependencies
67
import { AstAnalyser } from "./AstAnalyser.js";
78

9+
// CONSTANTS
810
const kDefaultExtensions = ["js", "cjs", "mjs", "node"];
911

1012
export class EntryFilesAnalyser {
@@ -16,9 +18,11 @@ export class EntryFilesAnalyser {
1618
*/
1719
constructor(options = {}) {
1820
this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser();
19-
this.allowedExtensions = options.loadExtensions
21+
const rawAllowedExtensions = options.loadExtensions
2022
? options.loadExtensions(kDefaultExtensions)
2123
: kDefaultExtensions;
24+
25+
this.allowedExtensions = new Set(rawAllowedExtensions);
2226
}
2327

2428
/**
@@ -36,7 +40,7 @@ export class EntryFilesAnalyser {
3640
}
3741

3842
async* #analyzeFile(file) {
39-
const filePath = file instanceof URL ? file.pathname : file;
43+
const filePath = file instanceof URL ? fileURLToPath(file) : file;
4044
const report = await this.astAnalyzer.analyseFile(file);
4145

4246
yield { url: filePath, ...report };
@@ -45,12 +49,16 @@ export class EntryFilesAnalyser {
4549
return;
4650
}
4751

48-
yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath));
52+
yield* this.#analyzeDeps(
53+
report.dependencies,
54+
path.dirname(filePath)
55+
);
4956
}
5057

5158
async* #analyzeDeps(deps, basePath) {
5259
for (const [name] of deps) {
5360
const depPath = await this.#getInternalDepPath(name, basePath);
61+
5462
if (depPath && !this.analyzedDeps.has(depPath)) {
5563
this.analyzedDeps.add(depPath);
5664

@@ -62,20 +70,25 @@ export class EntryFilesAnalyser {
6270
async #getInternalDepPath(name, basePath) {
6371
const depPath = path.join(basePath, name);
6472
const existingExt = path.extname(name);
65-
if (existingExt !== "") {
66-
if (!this.allowedExtensions.includes(existingExt.slice(1))) {
67-
return null;
68-
}
6973

70-
if (await this.#fileExists(depPath)) {
71-
return depPath;
74+
if (existingExt === "") {
75+
for (const ext of this.allowedExtensions) {
76+
const depPathWithExt = `${depPath}.${ext}`;
77+
78+
const fileExist = await this.#fileExists(depPathWithExt);
79+
if (fileExist) {
80+
return depPathWithExt;
81+
}
7282
}
7383
}
84+
else {
85+
if (!this.allowedExtensions.has(existingExt.slice(1))) {
86+
return null;
87+
}
7488

75-
for (const ext of this.allowedExtensions) {
76-
const depPathWithExt = `${depPath}.${ext}`;
77-
if (await this.#fileExists(depPathWithExt)) {
78-
return depPathWithExt;
89+
const fileExist = await this.#fileExists(depPath);
90+
if (fileExist) {
91+
return depPath;
7992
}
8093
}
8194

@@ -84,7 +97,7 @@ export class EntryFilesAnalyser {
8497

8598
async #fileExists(path) {
8699
try {
87-
await fs.access(path, fs.constants.F_OK);
100+
await fs.access(path, fs.constants.R_OK);
88101

89102
return true;
90103
}

test/AstAnalyser.spec.js

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
/* eslint-disable max-nested-callbacks */
12
// Import Node.js Dependencies
23
import { describe, it } from "node:test";
34
import assert from "node:assert";
45
import { readFileSync } from "node:fs";
56

67
// Import Internal Dependencies
7-
import { AstAnalyser } from "../src/AstAnalyser.js";
8-
import { JsSourceParser } from "../src/JsSourceParser.js";
8+
import { AstAnalyser, JsSourceParser } from "../index.js";
99
import { SourceFile } from "../src/SourceFile.js";
1010
import {
1111
customProbes,
@@ -255,7 +255,7 @@ describe("AstAnalyser", (t) => {
255255
});
256256

257257
describe("analyseFile", () => {
258-
it("remove the packageName from the dependencies list", async () => {
258+
it("remove the packageName from the dependencies list", async() => {
259259
const result = await getAnalyser().analyseFile(
260260
new URL("depName.js", FIXTURE_URL),
261261
{ module: false, packageName: "foobar" }
@@ -268,7 +268,7 @@ describe("AstAnalyser", (t) => {
268268
);
269269
});
270270

271-
it("should fail with a parsing error", async () => {
271+
it("should fail with a parsing error", async() => {
272272
const result = await getAnalyser().analyseFile(
273273
new URL("parsingError.js", FIXTURE_URL),
274274
{ module: false, packageName: "foobar" }
@@ -286,18 +286,18 @@ describe("AstAnalyser", (t) => {
286286
const url = new URL("depName.js", FIXTURE_URL);
287287

288288
describe("initialize", () => {
289-
it("should throw if initialize is not a function", async () => {
289+
it("should throw if initialize is not a function", async() => {
290290
const res = await analyser.analyseFile(
291291
url, {
292-
initialize: "foo"
293-
});
292+
initialize: "foo"
293+
});
294294

295295
assert.strictEqual(res.ok, false);
296296
assert.strictEqual(res.warnings[0].value, "options.initialize must be a function");
297297
assert.strictEqual(res.warnings[0].kind, "parsing-error");
298298
});
299299

300-
it("should call the initialize function", async (t) => {
300+
it("should call the initialize function", async(t) => {
301301
const initialize = t.mock.fn();
302302

303303
await analyser.analyseFile(url, {
@@ -307,7 +307,7 @@ describe("AstAnalyser", (t) => {
307307
assert.strictEqual(initialize.mock.callCount(), 1);
308308
});
309309

310-
it("should pass the source file as first argument", async (t) => {
310+
it("should pass the source file as first argument", async(t) => {
311311
const initialize = t.mock.fn();
312312

313313
await analyser.analyseFile(url, {
@@ -319,18 +319,18 @@ describe("AstAnalyser", (t) => {
319319
});
320320

321321
describe("finalize", () => {
322-
it("should throw if finalize is not a function", async () => {
322+
it("should throw if finalize is not a function", async() => {
323323
const res = await analyser.analyseFile(
324324
url, {
325-
finalize: "foo"
326-
});
325+
finalize: "foo"
326+
});
327327

328328
assert.strictEqual(res.ok, false);
329329
assert.strictEqual(res.warnings[0].value, "options.finalize must be a function");
330330
assert.strictEqual(res.warnings[0].kind, "parsing-error");
331331
});
332332

333-
it("should call the finalize function", async (t) => {
333+
it("should call the finalize function", async(t) => {
334334
const finalize = t.mock.fn();
335335

336336
await analyser.analyseFile(url, {
@@ -340,7 +340,7 @@ describe("AstAnalyser", (t) => {
340340
assert.strictEqual(finalize.mock.callCount(), 1);
341341
});
342342

343-
it("should pass the source file as first argument", async (t) => {
343+
it("should pass the source file as first argument", async(t) => {
344344
const finalize = t.mock.fn();
345345

346346
await analyser.analyseFile(url, {
@@ -352,7 +352,7 @@ describe("AstAnalyser", (t) => {
352352
});
353353

354354

355-
it("intialize should be called before finalize", async () => {
355+
it("intialize should be called before finalize", async() => {
356356
const calls = [];
357357

358358
await analyser.analyseFile(url, {
@@ -411,8 +411,8 @@ describe("AstAnalyser", (t) => {
411411
it("should remove multiple HTML comments", () => {
412412
const preparedSource = getAnalyser().prepareSource(
413413
"<!-- const yo = 5; -->\nconst yo = 'foo'\n<!-- const yo = 5; -->", {
414-
removeHTMLComments: true
415-
});
414+
removeHTMLComments: true
415+
});
416416

417417
assert.strictEqual(preparedSource, "\nconst yo = 'foo'\n");
418418
});

test/Deobfuscator.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { walk } from "estree-walker";
77

88
// Import Internal Dependencies
99
import { Deobfuscator } from "../src/Deobfuscator.js";
10-
import { JsSourceParser } from "../src/JsSourceParser.js";
10+
import { JsSourceParser } from "../index.js";
1111

1212
describe("Deobfuscator", () => {
1313
describe("identifiers and counters", () => {

test/EntryFilesAnalyser.spec.js

Lines changed: 75 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Import Node.js Dependencies
22
import { describe, it } from "node:test";
33
import assert from "node:assert";
4+
import { fileURLToPath } from "node:url";
45

56
// Import Internal Dependencies
6-
import { EntryFilesAnalyser } from "../src/EntryFilesAnalyser.js";
7-
import { AstAnalyser } from "../src/AstAnalyser.js";
7+
import { EntryFilesAnalyser, AstAnalyser } from "../index.js";
88

99
const FIXTURE_URL = new URL("fixtures/entryFiles/", import.meta.url);
1010

@@ -15,19 +15,24 @@ describe("EntryFilesAnalyser", () => {
1515
const deepEntryUrl = new URL("deps/deepEntry.js", FIXTURE_URL);
1616

1717
t.mock.method(AstAnalyser.prototype, "analyseFile");
18-
const generator = entryFilesAnalyser.analyse([entryUrl, deepEntryUrl]);
1918

20-
// First entry
21-
await assertReport(generator, entryUrl);
22-
await assertReport(generator, new URL("deps/dep1.js", FIXTURE_URL));
23-
await assertReport(generator, new URL("shared.js", FIXTURE_URL));
24-
await assertReport(generator, new URL("deps/dep2.js", FIXTURE_URL));
25-
26-
// Second entry
27-
await assertReport(generator, deepEntryUrl);
28-
await assertReport(generator, new URL("deps/dep3.js", FIXTURE_URL));
29-
30-
await assertAllReportsYielded(generator);
19+
const generator = entryFilesAnalyser.analyse([
20+
entryUrl,
21+
deepEntryUrl
22+
]);
23+
const reports = await fromAsync(generator);
24+
25+
assert.deepEqual(
26+
reports.map((report) => report.url),
27+
[
28+
entryUrl,
29+
new URL("deps/dep1.js", FIXTURE_URL),
30+
new URL("shared.js", FIXTURE_URL),
31+
new URL("deps/dep2.js", FIXTURE_URL),
32+
deepEntryUrl,
33+
new URL("deps/dep3.js", FIXTURE_URL)
34+
].map((url) => fileURLToPath(url))
35+
);
3136

3237
// Check that shared dependencies are not analyzed several times
3338
const calls = AstAnalyser.prototype.analyseFile.mock.calls;
@@ -39,62 +44,79 @@ describe("EntryFilesAnalyser", () => {
3944
const entryUrl = new URL("entryWithInvalidDep.js", FIXTURE_URL);
4045

4146
const generator = entryFilesAnalyser.analyse([entryUrl]);
42-
43-
await assertReport(generator, entryUrl);
44-
45-
const invalidDepReport = await generator.next();
46-
assert.ok(!invalidDepReport.value.ok);
47-
assert.strictEqual(invalidDepReport.value.url, new URL("deps/invalidDep.js", FIXTURE_URL).pathname);
48-
assert.strictEqual(invalidDepReport.value.warnings[0].kind, "parsing-error");
49-
50-
await assertReport(generator, new URL("deps/dep1.js", FIXTURE_URL));
51-
await assertReport(generator, new URL("shared.js", FIXTURE_URL));
52-
53-
await assertAllReportsYielded(generator);
47+
const reports = await fromAsync(generator);
48+
49+
assert.deepEqual(
50+
reports.map((report) => report.url),
51+
[
52+
entryUrl,
53+
new URL("deps/invalidDep.js", FIXTURE_URL),
54+
new URL("deps/dep1.js", FIXTURE_URL),
55+
new URL("shared.js", FIXTURE_URL)
56+
].map((url) => fileURLToPath(url))
57+
);
58+
59+
const invalidReports = reports.filter((report) => !report.ok);
60+
assert.strictEqual(invalidReports.length, 1);
61+
assert.strictEqual(
62+
invalidReports[0].warnings[0].kind,
63+
"parsing-error"
64+
);
5465
});
5566

5667
it("should extends default extensions", async() => {
5768
const entryFilesAnalyser = new EntryFilesAnalyser({
5869
loadExtensions: (exts) => [...exts, "jsx"]
5970
});
6071
const entryUrl = new URL("entryWithVariousDepExtensions.js", FIXTURE_URL);
61-
const generator = entryFilesAnalyser.analyse([entryUrl]);
6272

63-
await assertReport(generator, entryUrl);
64-
await assertReport(generator, new URL("deps/default.js", FIXTURE_URL));
65-
await assertReport(generator, new URL("deps/default.cjs", FIXTURE_URL));
66-
await assertReport(generator, new URL("deps/dep.cjs", FIXTURE_URL));
67-
await assertReport(generator, new URL("deps/default.mjs", FIXTURE_URL));
68-
await assertReport(generator, new URL("deps/dep.mjs", FIXTURE_URL));
69-
await assertReport(generator, new URL("deps/default.node", FIXTURE_URL));
70-
await assertReport(generator, new URL("deps/dep.node", FIXTURE_URL));
71-
await assertReport(generator, new URL("deps/default.jsx", FIXTURE_URL));
72-
await assertReport(generator, new URL("deps/dep.jsx", FIXTURE_URL));
73-
74-
await assertAllReportsYielded(generator);
73+
const generator = entryFilesAnalyser.analyse([entryUrl]);
74+
const reports = await fromAsync(generator);
75+
76+
assert.deepEqual(
77+
reports.map((report) => report.url),
78+
[
79+
entryUrl,
80+
new URL("deps/default.js", FIXTURE_URL),
81+
new URL("deps/default.cjs", FIXTURE_URL),
82+
new URL("deps/dep.cjs", FIXTURE_URL),
83+
new URL("deps/default.mjs", FIXTURE_URL),
84+
new URL("deps/dep.mjs", FIXTURE_URL),
85+
new URL("deps/default.node", FIXTURE_URL),
86+
new URL("deps/dep.node", FIXTURE_URL),
87+
new URL("deps/default.jsx", FIXTURE_URL),
88+
new URL("deps/dep.jsx", FIXTURE_URL)
89+
].map((url) => fileURLToPath(url))
90+
);
7591
});
7692

7793
it("should override default extensions", async() => {
7894
const entryFilesAnalyser = new EntryFilesAnalyser({
7995
loadExtensions: () => ["jsx"]
8096
});
8197
const entryUrl = new URL("entryWithVariousDepExtensions.js", FIXTURE_URL);
82-
const generator = entryFilesAnalyser.analyse([entryUrl]);
83-
84-
await assertReport(generator, entryUrl);
85-
await assertReport(generator, new URL("deps/default.jsx", FIXTURE_URL));
86-
await assertReport(generator, new URL("deps/dep.jsx", FIXTURE_URL));
8798

88-
await assertAllReportsYielded(generator);
99+
const generator = entryFilesAnalyser.analyse([entryUrl]);
100+
const reports = await fromAsync(generator);
101+
102+
assert.deepEqual(
103+
reports.map((report) => report.url),
104+
[
105+
entryUrl,
106+
new URL("deps/default.jsx", FIXTURE_URL),
107+
new URL("deps/dep.jsx", FIXTURE_URL)
108+
].map((url) => fileURLToPath(url))
109+
);
89110
});
111+
});
90112

91-
async function assertReport(generator, expectedUrl) {
92-
const report = await generator.next();
93-
assert.strictEqual(report.value.url, expectedUrl.pathname);
94-
assert.ok(report.value.ok);
95-
}
113+
// TODO: replace with Array.fromAsync when droping Node.js 20
114+
async function fromAsync(asyncIter) {
115+
const items = [];
96116

97-
async function assertAllReportsYielded(generator) {
98-
assert.strictEqual((await generator.next()).value, undefined);
117+
for await (const item of asyncIter) {
118+
items.push(item);
99119
}
100-
});
120+
121+
return items;
122+
}

0 commit comments

Comments
 (0)