UNPKG

dependency-cruiser

Version:

Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.

157 lines (142 loc) 4.46 kB
/* eslint-disable security/detect-object-injection */ import { dirname } from "node:path/posix"; import { calculateInstability, metricsAreCalculable } from "../module-utl.mjs"; import detectCycles from "../circular.mjs"; import { findFolderByName, getAfferentCouplings, getEfferentCouplings, getParentFolders, object2Array, } from "./utl.mjs"; import { uniq } from "#utl/array-util.mjs"; function upsertCouplings(pAllDependents, pNewDependents) { for (const lNewDependent of pNewDependents) { pAllDependents[lNewDependent] = pAllDependents[lNewDependent] || { count: 0, }; pAllDependents[lNewDependent].count += 1; } } /** * * @param {any} pAllMetrics * @param {import("../../../../types/cruise-result.d.mts").IModule} pModule * @param {string} pDirname * @returns {any} */ function upsertFolderAttributes(pAllMetrics, pModule, pDirname) { pAllMetrics[pDirname] = pAllMetrics[pDirname] || { dependencies: {}, dependents: {}, moduleCount: 0, }; upsertCouplings( pAllMetrics[pDirname].dependents, getAfferentCouplings(pModule, pDirname), ); upsertCouplings( pAllMetrics[pDirname].dependencies, getEfferentCouplings(pModule, pDirname).map( (pDependency) => pDependency.resolved, ), ); pAllMetrics[pDirname].moduleCount += 1; if (pModule.experimentalStats) { if (!Object.hasOwn(pAllMetrics[pDirname], "experimentalStats")) { pAllMetrics[pDirname].experimentalStats = { size: 0, topLevelStatementCount: 0, }; } pAllMetrics[pDirname].experimentalStats.size += pModule.experimentalStats.size; pAllMetrics[pDirname].experimentalStats.topLevelStatementCount += pModule.experimentalStats.topLevelStatementCount; } return pAllMetrics; } function aggregateToFolder(pAllFolders, pModule) { for (const lParentDirectory of getParentFolders(dirname(pModule.source))) { upsertFolderAttributes(pAllFolders, pModule, lParentDirectory); } return pAllFolders; } function sumCounts(pAll, pCurrent) { return pAll + pCurrent.count; } function getFolderLevelCouplings(pCouplingArray) { return uniq( pCouplingArray.map((pCoupling) => dirname(pCoupling.name) === "." ? pCoupling.name : dirname(pCoupling.name), ), ).map((pCoupling) => ({ name: pCoupling })); } function calculateFolderMetrics(pFolder) { const lModuleDependents = object2Array(pFolder.dependents); const lModuleDependencies = object2Array(pFolder.dependencies); // this calculation might look superfluous (why not just .length the dependents // and dependencies?), but it isn't because there can be > 1 relation between // two folders const lAfferentCouplings = lModuleDependents.reduce(sumCounts, 0); const lEfferentCouplings = lModuleDependencies.reduce(sumCounts, 0); return { ...pFolder, afferentCouplings: lAfferentCouplings, efferentCouplings: lEfferentCouplings, instability: calculateInstability(lEfferentCouplings, lAfferentCouplings), dependents: getFolderLevelCouplings(lModuleDependents), dependencies: getFolderLevelCouplings(lModuleDependencies), }; } function deNormalizeInstability(pFolder, _, pAllFolders) { return { ...pFolder, dependencies: pFolder.dependencies.map((pDependency) => { const lFolder = findFolderByName(pAllFolders, pDependency.name) || {}; return { ...pDependency, instability: lFolder.instability >= 0 ? lFolder.instability : 0, }; }), }; } function getSinks(pFolders) { const lKnownFolders = new Set(pFolders.map(({ name }) => name)); const lAllFolders = uniq( pFolders.flatMap(({ dependencies }) => dependencies.map(({ name }) => name), ), ); const lReturnValue = lAllFolders .filter((pFolder) => !lKnownFolders.has(pFolder)) .map((pFolder) => ({ name: pFolder, moduleCount: -1, dependencies: [], dependents: [], })); return lReturnValue; } /** * * @param {import("../../../../types/cruise-result.d.mts").IModule} pModules * @param {import("../../../../types/cruise-result.d.mts").IOptions} pOptions * @returns */ export default function aggregateToFolders(pModules, pOptions = {}) { let lFolders = object2Array( pModules.filter(metricsAreCalculable).reduce(aggregateToFolder, {}), ) .map(calculateFolderMetrics) .map(deNormalizeInstability); lFolders = lFolders.concat(getSinks(lFolders)); return detectCycles(lFolders, { pSourceAttribute: "name", pDependencyName: "name", pSkipAnalysisNotInRules: pOptions.skipAnalysisNotInRules, pRuleSet: pOptions.ruleSet, }); }