UNPKG

dependency-cruiser

Version:

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

192 lines (174 loc) 5.57 kB
import { join, extname, dirname } from "node:path"; import { extract as acornExtract } from "./acorn/extract.mjs"; import { extract as tscExtract, shouldUse as tscShouldUse, } from "./tsc/extract.mjs"; import { extract as swcExtract, shouldUse as swcShouldUse, } from "./swc/extract.mjs"; import resolve from "./resolve/index.mjs"; import { detectPreCompilationNess, extractModuleAttributes, } from "./helpers.mjs"; import { uniqBy, intersects } from "#utl/array-util.mjs"; import { getCachedRegExp } from "#utl/regex-util.mjs"; /** * @import { IDependency } from "../../types/cruise-result.mjs"; * @import { IResolveOptions, IStrictCruiseOptions, ITranspileOptions } from "../../types/dependency-cruiser.mjs"; */ function extractWithTsc(pCruiseOptions, pFileName, pTranspileOptions) { let lDependencies = tscExtract(pCruiseOptions, pFileName, pTranspileOptions); if (pCruiseOptions.tsPreCompilationDeps === "specify") { lDependencies = detectPreCompilationNess( lDependencies, acornExtract(pCruiseOptions, pFileName, pTranspileOptions), ); } return lDependencies; } /** * @param {IStrictCruiseOptions} pCruiseOptions * @param {string} pFileName * @returns {(IStrictCruiseOptions, string, any) => IDependency[]} */ function determineExtractionFunction(pCruiseOptions, pFileName) { let lExtractionFunction = acornExtract; if (tscShouldUse(pCruiseOptions, pFileName)) { lExtractionFunction = extractWithTsc; } else if (swcShouldUse(pCruiseOptions, pFileName)) { lExtractionFunction = swcExtract; } return lExtractionFunction; } /** * @param {IStrictCruiseOptions} pCruiseOptions * @param {string} pFileName * @param {any} pTranspileOptions * @returns {IDependency[]} */ function extractDependencies(pCruiseOptions, pFileName, pTranspileOptions) { /** @type IDependency[] */ let lDependencies = []; if (!pCruiseOptions.extraExtensionsToScan.includes(extname(pFileName))) { const lExtractionFunction = determineExtractionFunction( pCruiseOptions, pFileName, ); lDependencies = lExtractionFunction( pCruiseOptions, pFileName, pTranspileOptions, ); } return lDependencies.map((pDependency) => ({ ...pDependency, ...extractModuleAttributes(pDependency.module), })); } function matchesDoNotFollow({ resolved, dependencyTypes }, pDoNotFollow) { const lMatchesPath = pDoNotFollow.path ? getCachedRegExp(pDoNotFollow.path).test(resolved) : false; const lMatchesDependencyTypes = pDoNotFollow.dependencyTypes ? intersects(dependencyTypes, pDoNotFollow.dependencyTypes) : false; return lMatchesPath || lMatchesDependencyTypes; } function addResolutionAttributes( { baseDir, doNotFollow }, pFileName, pResolveOptions, pTranspileOptions, ) { return function addAttributes(pDependency) { const lResolved = resolve( pDependency, baseDir, join(baseDir, dirname(pFileName)), pResolveOptions, pTranspileOptions, ); const lMatchesDoNotFollow = matchesDoNotFollow(lResolved, doNotFollow); return { ...pDependency, ...lResolved, followable: lResolved.followable && !lMatchesDoNotFollow, matchesDoNotFollow: lMatchesDoNotFollow, }; }; } function matchesPattern(pFullPathToFile, pPattern) { return getCachedRegExp(pPattern).test(pFullPathToFile); } /** * * @param {IDependency} pDependency * @returns {string} */ function getDependencyUniqueKey({ module, moduleSystem, dependencyTypes }) { // c8: dependencyTypes is hardly ever undefined anymore since PR #884, but // we keep the `|| []` in for robustness sake return `${module} ${moduleSystem} ${(dependencyTypes || []).includes( "type-only", )}`; } function compareDeps(pLeft, pRight) { return getDependencyUniqueKey(pLeft).localeCompare( getDependencyUniqueKey(pRight), ); } /** * Returns an array of dependencies present in the given file. Of * each dependency it returns * module - the name of the module as found in the file * resolved - the filename the dependency resides in (including the path * to the current directory or the directory passed as * 'baseDir' in the options) * moduleSystems - the module system(s) * coreModule - a boolean indicating whether it is a (nodejs) core module * * * @param {string} pFileName path to the file * @param {IStrictCruiseOptions} pCruiseOptions cruise options * @param {IResolveOptions} pResolveOptions webpack 'enhanced-resolve' options * @param {ITranspileOptions} pTranspileOptions an object with tsconfig ('typescript project') options * ('flattened' so there's no need for file access on any * 'extends' option in there) * @return {IDependency[]} an array of dependency objects (see above) */ export default function getDependencies( pFileName, pCruiseOptions, pResolveOptions, pTranspileOptions, ) { try { return uniqBy( extractDependencies(pCruiseOptions, pFileName, pTranspileOptions), getDependencyUniqueKey, ) .map( addResolutionAttributes( pCruiseOptions, pFileName, pResolveOptions, pTranspileOptions, ), ) .filter( ({ resolved }) => (!pCruiseOptions?.exclude?.path || !matchesPattern(resolved, pCruiseOptions.exclude.path)) && (!pCruiseOptions?.includeOnly?.path || matchesPattern(resolved, pCruiseOptions.includeOnly.path)), ) .sort(compareDeps); } catch (pError) { throw new Error( `Extracting dependencies ran afoul of...\n\n ${pError.message}\n... in ${pFileName}\n\n`, ); } }