UNPKG

@nodesecure/js-x-ray

Version:
149 lines 5.41 kB
// Import Node.js Dependencies import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; // Import Third-party Dependencies import { DiGraph } from "digraph-js"; // Import Internal Dependencies import { AstAnalyser } from "./AstAnalyser.js"; import { TsSourceParser } from "./parsers/TsSourceParser.js"; import { JsSourceParser } from "./parsers/JsSourceParser.js"; // CONSTANTS const kDefaultExtensions = [ ...Array.from(JsSourceParser.FileExtensions).map((ext) => ext.slice(1)), ...Array.from(TsSourceParser.FileExtensions).map((ext) => ext.slice(1)), "node" ]; export class EntryFilesAnalyser { static Parsers = { js: new JsSourceParser(), ts: new TsSourceParser() }; #rootPath = null; astAnalyzer; allowedExtensions; dependencies; ignoreENOENT; constructor(options = {}) { const { astAnalyzer = new AstAnalyser(), loadExtensions, rootPath = null, ignoreENOENT = false } = options; this.astAnalyzer = astAnalyzer; const rawAllowedExtensions = loadExtensions ? loadExtensions(kDefaultExtensions) : kDefaultExtensions; this.allowedExtensions = new Set(rawAllowedExtensions); this.#rootPath = rootPath === null ? null : fileURLToPathExtended(rootPath); this.ignoreENOENT = ignoreENOENT; } async *analyse(entryFiles, options = {}) { this.dependencies = new DiGraph(); for (const entryFile of new Set(entryFiles)) { const normalizedEntryFile = this.#normalizeAndCleanEntryFile(entryFile); if (this.ignoreENOENT && !await this.#fileExists(normalizedEntryFile)) { return; } yield* this.#analyseFile(normalizedEntryFile, this.#getRelativeFilePath(normalizedEntryFile), options); } } #normalizeAndCleanEntryFile(file) { let normalizedEntryFile = path.normalize(fileURLToPathExtended(file)); if (this.#rootPath !== null && !path.isAbsolute(normalizedEntryFile)) { normalizedEntryFile = path.join(this.#rootPath, normalizedEntryFile); } return normalizedEntryFile; } #getRelativeFilePath(file) { return this.#rootPath ? path.relative(this.#rootPath, file) : file; } #getParserFromFileExtension(file) { const fileExtension = path.extname(file); if (JsSourceParser.FileExtensions.has(fileExtension)) { return EntryFilesAnalyser.Parsers.js; } else if (TsSourceParser.FileExtensions.has(fileExtension)) { return EntryFilesAnalyser.Parsers.ts; } return void 0; } async *#analyseFile(file, relativeFile, options) { // Skip declaration files as they are not meant to be analysed if (file.includes("d.ts")) { return; } this.dependencies.addVertex({ id: relativeFile, adjacentTo: [], body: {} }); const report = await this.astAnalyzer.analyseFile(file, { ...options, customParser: this.#getParserFromFileExtension(file) }); yield { file: relativeFile, ...report }; const dependencySet = this.astAnalyzer.getCollectableSet("dependency"); if (!report.ok || typeof dependencySet === "undefined") { return; } for (const name of dependencySet.values()) { const depFile = await this.#getInternalDepPath(path.join(path.dirname(file), name)); if (depFile === null) { continue; } const depRelativeFile = this.#getRelativeFilePath(depFile); if (!this.dependencies.hasVertex(depRelativeFile)) { this.dependencies.addVertex({ id: depRelativeFile, adjacentTo: [], body: {} }); yield* this.#analyseFile(depFile, depRelativeFile, options); } this.dependencies.addEdge({ from: relativeFile, to: depRelativeFile }); } } async #getInternalDepPath(filePath) { const fileExtension = path.extname(filePath); if (fileExtension === "") { for (const ext of this.allowedExtensions) { const depPathWithExt = `${filePath}.${ext}`; const fileExist = await this.#fileExists(depPathWithExt); if (fileExist) { return depPathWithExt; } } } else { if (!this.allowedExtensions.has(fileExtension.slice(1))) { return null; } const fileExist = await this.#fileExists(filePath); if (fileExist) { return filePath; } } return null; } async #fileExists(filePath) { try { await fs.access(filePath, fs.constants.R_OK); return true; } catch (error) { if (error.code !== "ENOENT") { throw error; } return false; } } } function fileURLToPathExtended(file) { return file instanceof URL ? fileURLToPath(file) : file; } //# sourceMappingURL=EntryFilesAnalyser.js.map