-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create EntryFilesAnalyzer class to analyze a set of entry files (#258)
* feat: create EntryFilesAnalyzer class to analyze a set of entry files asynchronously, yielding analysis reports * fix: return early if existing extension is not allowed * refactor: avoid useless asignantions in tests files * fix: fixtures must end with 1 blank line * test: asserts all reports have been yielded at the end of tests * fix: JsDoc for EntryFilesAnalyzer.analyze should have string|Url union * types: add api types * fix: import order * fix: typo in mjs module, it is an externalDep * fix: typo in .cjs .js modules, it is an externalDep
- Loading branch information
1 parent
f1095a1
commit 55f52fa
Showing
20 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Import Node.js Dependencies | ||
import fs from "node:fs/promises"; | ||
import path from "node:path"; | ||
|
||
// Import Internal Dependencies | ||
import { AstAnalyser } from "./AstAnalyser.js"; | ||
|
||
const kDefaultExtensions = ["js", "cjs", "mjs", "node"]; | ||
|
||
export class EntryFilesAnalyser { | ||
/** | ||
* @constructor | ||
* @param {object} [options={}] | ||
* @param {AstAnalyser} [options.astAnalyzer=new AstAnalyser()] | ||
* @param {function} [options.loadExtensions] | ||
*/ | ||
constructor(options = {}) { | ||
this.astAnalyzer = options.astAnalyzer ?? new AstAnalyser(); | ||
this.allowedExtensions = options.loadExtensions | ||
? options.loadExtensions(kDefaultExtensions) | ||
: kDefaultExtensions; | ||
} | ||
|
||
/** | ||
* Asynchronously analyze a set of entry files yielding analysis reports. | ||
* | ||
* @param {(string | URL)[]} entryFiles | ||
* @yields {Object} - Yields an object containing the analysis report for each file. | ||
*/ | ||
async* analyse(entryFiles) { | ||
this.analyzedDeps = new Set(); | ||
|
||
for (const file of entryFiles) { | ||
yield* this.#analyzeFile(file); | ||
} | ||
} | ||
|
||
async* #analyzeFile(file) { | ||
const filePath = file instanceof URL ? file.pathname : file; | ||
const report = await this.astAnalyzer.analyseFile(file); | ||
|
||
yield { url: filePath, ...report }; | ||
|
||
if (!report.ok) { | ||
return; | ||
} | ||
|
||
yield* this.#analyzeDeps(report.dependencies, path.dirname(filePath)); | ||
} | ||
|
||
async* #analyzeDeps(deps, basePath) { | ||
for (const [name] of deps) { | ||
const depPath = await this.#getInternalDepPath(name, basePath); | ||
if (depPath && !this.analyzedDeps.has(depPath)) { | ||
this.analyzedDeps.add(depPath); | ||
|
||
yield* this.#analyzeFile(depPath); | ||
} | ||
} | ||
} | ||
|
||
async #getInternalDepPath(name, basePath) { | ||
const depPath = path.join(basePath, name); | ||
const existingExt = path.extname(name); | ||
if (existingExt !== "") { | ||
if (!this.allowedExtensions.includes(existingExt.slice(1))) { | ||
return null; | ||
} | ||
|
||
if (await this.#fileExists(depPath)) { | ||
return depPath; | ||
} | ||
} | ||
|
||
for (const ext of this.allowedExtensions) { | ||
const depPathWithExt = `${depPath}.${ext}`; | ||
if (await this.#fileExists(depPathWithExt)) { | ||
return depPathWithExt; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
async #fileExists(path) { | ||
try { | ||
await fs.access(path, fs.constants.F_OK); | ||
|
||
return true; | ||
} | ||
catch (error) { | ||
if (error.code !== "ENOENT") { | ||
throw error; | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Import Node.js Dependencies | ||
import { describe, it } from "node:test"; | ||
import assert from "node:assert"; | ||
|
||
// Import Internal Dependencies | ||
import { EntryFilesAnalyser } from "../src/EntryFilesAnalyser.js"; | ||
import { AstAnalyser } from "../src/AstAnalyser.js"; | ||
|
||
const FIXTURE_URL = new URL("fixtures/entryFiles/", import.meta.url); | ||
|
||
describe("EntryFilesAnalyser", () => { | ||
it("should analyze internal dependencies recursively", async(t) => { | ||
const entryFilesAnalyser = new EntryFilesAnalyser(); | ||
const entryUrl = new URL("entry.js", FIXTURE_URL); | ||
const deepEntryUrl = new URL("deps/deepEntry.js", FIXTURE_URL); | ||
|
||
t.mock.method(AstAnalyser.prototype, "analyseFile"); | ||
const generator = entryFilesAnalyser.analyse([entryUrl, deepEntryUrl]); | ||
|
||
// First entry | ||
await assertReport(generator, entryUrl, true); | ||
await assertReport(generator, new URL("deps/dep1.js", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("shared.js", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("deps/dep2.js", FIXTURE_URL), true); | ||
|
||
// Second entry | ||
await assertReport(generator, deepEntryUrl, true); | ||
await assertReport(generator, new URL("deps/dep3.js", FIXTURE_URL), true); | ||
|
||
await assertAllReportsYielded(generator); | ||
|
||
// Check that shared dependencies are not analyzed several times | ||
const calls = AstAnalyser.prototype.analyseFile.mock.calls; | ||
assert.strictEqual(calls.length, 6); | ||
}); | ||
|
||
it("should detect internal deps that failed to be analyzed", async() => { | ||
const entryFilesAnalyser = new EntryFilesAnalyser(); | ||
const entryUrl = new URL("entryWithInvalidDep.js", FIXTURE_URL); | ||
|
||
const generator = entryFilesAnalyser.analyse([entryUrl]); | ||
|
||
await assertReport(generator, entryUrl, true); | ||
|
||
const invalidDepReport = await generator.next(); | ||
assert.ok(!invalidDepReport.value.ok); | ||
assert.strictEqual(invalidDepReport.value.url, new URL("deps/invalidDep.js", FIXTURE_URL).pathname); | ||
assert.strictEqual(invalidDepReport.value.warnings[0].kind, "parsing-error"); | ||
|
||
await assertReport(generator, new URL("deps/dep1.js", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("shared.js", FIXTURE_URL), true); | ||
|
||
await assertAllReportsYielded(generator); | ||
}); | ||
|
||
it("should extends default extensions", async() => { | ||
const entryFilesAnalyser = new EntryFilesAnalyser({ | ||
loadExtensions: (exts) => [...exts, "jsx"] | ||
}); | ||
const entryUrl = new URL("entryWithVariousDepExtensions.js", FIXTURE_URL); | ||
const generator = entryFilesAnalyser.analyse([entryUrl]); | ||
|
||
await assertReport(generator, entryUrl, true); | ||
await assertReport(generator, new URL("deps/default.js", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("deps/default.cjs", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("deps/default.mjs", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("deps/default.node", FIXTURE_URL), true); | ||
await assertReport(generator, new URL("deps/default.jsx", FIXTURE_URL), true); | ||
|
||
await assertAllReportsYielded(generator); | ||
}); | ||
|
||
it("should override default extensions", async() => { | ||
const entryFilesAnalyser = new EntryFilesAnalyser({ | ||
loadExtensions: () => ["jsx"] | ||
}); | ||
const entryUrl = new URL("entryWithVariousDepExtensions.js", FIXTURE_URL); | ||
const generator = entryFilesAnalyser.analyse([entryUrl]); | ||
|
||
await assertReport(generator, entryUrl, true); | ||
await assertReport(generator, new URL("deps/default.jsx", FIXTURE_URL), true); | ||
|
||
await assertAllReportsYielded(generator); | ||
}); | ||
|
||
async function assertReport(generator, expectedUrl, expectedOk) { | ||
const report = await generator.next(); | ||
assert.strictEqual(report.value.url, expectedUrl.pathname); | ||
assert.strictEqual(report.value.ok, expectedOk); | ||
} | ||
|
||
async function assertAllReportsYielded(generator) { | ||
assert.strictEqual((await generator.next()).value, undefined); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require("./dep1"); | ||
require("./dep2"); | ||
require("./dep3"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require('externalDep') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require('externalDep') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import React from 'react' | ||
|
||
export default function Foo() { | ||
return ( | ||
<div></div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import externalDep from 'externalDep'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('some/addon'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require("../shared"); | ||
require("externalDep"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require("../shared"); | ||
require("../shared.js"); | ||
require("externalDep"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require("../shared"); | ||
require("../shared.js"); | ||
require("externalDep"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@invalidJs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require("externalDep"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require("./deps/dep1"); | ||
require("./deps/dep2.js"); // keep extension for testing purpose |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require("./deps/invalidDep"); | ||
require("./deps/dep1"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require("./deps/dep1.js"); | ||
require("./deps/dep1"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
require("./deps/default.js"); | ||
require("./deps/default.cjs"); | ||
require("./deps/default.mjs"); | ||
require("./deps/default.node"); | ||
require("./deps/default.jsx"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require("externalDep"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters