UNPKG

dependency-cruiser

Version:

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

220 lines (193 loc) 5.61 kB
/* eslint-disable security/detect-object-injection, no-inline-comments */ import { matchToModulePath, matchToModulePathNot, } from "#validate/matchers.mjs"; import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs"; import { getCachedRegExp, extractGroups } from "#utl/regex-util.mjs"; function isReachableRule(pRule) { return Object.hasOwn(pRule?.to ?? {}, "reachable"); } function getReachableRules(pRuleSet) { return (pRuleSet?.forbidden ?? []) .filter(isReachableRule) .concat((pRuleSet?.allowed ?? []).filter(isReachableRule)) .concat((pRuleSet?.required ?? []).filter(isReachableRule)); } function isModuleInRuleFrom(pRule) { const lRuleFrom = pRule.from ?? pRule.module; if (!lRuleFrom) { return () => false; } const lRuleFromPathRE = lRuleFrom.path ? getCachedRegExp(lRuleFrom.path) : null; const lRuleFromPathNotRE = lRuleFrom.pathNot ? getCachedRegExp(lRuleFrom.pathNot) : null; return (pModule) => { if (lRuleFromPathRE && !lRuleFromPathRE.test(pModule.source)) { return false; } if (lRuleFromPathNotRE && lRuleFromPathNotRE.test(pModule.source)) { return false; } return true; }; } function isModuleInRuleTo(pRule, pModuleTo, pModuleFrom) { const lGroups = pModuleFrom ? extractGroups(pRule.from ?? pRule.module, pModuleFrom.source) : []; return ( matchToModulePath(pRule, pModuleTo, lGroups) && matchToModulePathNot(pRule, pModuleTo, lGroups) ); } function mergeReachableProperties(pModule, pRule, pPath, pModuleFrom) { const lReachables = pModule.reachable || []; const lIndexExistingReachable = lReachables.findIndex( (pReachable) => pReachable.asDefinedInRule === pRule.name, ); const lIsReachable = pPath.length > 0; if (lIndexExistingReachable > -1) { lReachables[lIndexExistingReachable].value = lReachables[lIndexExistingReachable].value || lIsReachable; return lReachables; } return lReachables.concat({ value: lIsReachable, asDefinedInRule: pRule.name, matchedFrom: pModuleFrom, }); } function mergeReachesProperties(pFromModule, pToModule, pRule, pPath) { const lReaches = pFromModule.reaches || []; const lIndexExistingReachable = lReaches.findIndex( (pReachable) => pReachable.asDefinedInRule === pRule.name, ); if (lIndexExistingReachable > -1) { lReaches[lIndexExistingReachable].modules = ( lReaches[lIndexExistingReachable].modules /* c8 ignore next */ || [] ).concat({ source: pToModule.source, via: pPath, }); return lReaches; } return lReaches.concat({ asDefinedInRule: pRule.name, modules: [{ source: pToModule.source, via: pPath }], }); } function shouldAddReaches(pRule, pModule) { return ( (pRule.to.reachable === true || pRule.name === "not-in-allowed") && isModuleInRuleFrom(pRule)(pModule) ); } function hasCapturingGroups(pRule) { const lCapturingGroupPlaceholderRe = /\$[0-9]+/; return ( lCapturingGroupPlaceholderRe.test(pRule?.to?.path ?? "") || lCapturingGroupPlaceholderRe.test(pRule?.to?.pathNot ?? "") ); } function shouldAddReachable(pRule, pModuleTo, pGraph) { let lReturnValue = false; if ( pRule.to.reachable === false || pRule.name === "not-in-allowed" || pRule.module ) { if (hasCapturingGroups(pRule)) { const lModulesFrom = pGraph.filter(isModuleInRuleFrom(pRule)); lReturnValue = lModulesFrom.some((pModuleFrom) => isModuleInRuleTo(pRule, pModuleTo, pModuleFrom), ); } else { lReturnValue = isModuleInRuleTo(pRule, pModuleTo); } } return lReturnValue; } function addReachesToModule(pModule, pGraph, pIndexedGraph, pReachableRule) { for (const lToModule of pGraph) { if ( pModule.source !== lToModule.source && isModuleInRuleTo(pReachableRule, lToModule, pModule) ) { const lPath = pIndexedGraph.getPath(pModule.source, lToModule.source); if (lPath.length > 0) { pModule.reaches = mergeReachesProperties( pModule, lToModule, pReachableRule, lPath, ); } } } return pModule; } function addReachableToModule( pModule, pIndexedGraph, pReachableRule, pFilteredFromModules, ) { let lFound = false; for (const lFromModule of pFilteredFromModules) { if ( !lFound && pModule.source !== lFromModule.source && isModuleInRuleTo(pReachableRule, pModule, lFromModule) ) { const lPath = pIndexedGraph.getPath(lFromModule.source, pModule.source); lFound = lPath.length > 0; pModule.reachable = mergeReachableProperties( pModule, pReachableRule, lPath, lFromModule.source, ); } } return pModule; } function addReachabilityToGraph(pGraph, pIndexedGraph, pReachableRule) { const lFromModules = pGraph.filter(isModuleInRuleFrom(pReachableRule)); return pGraph.map((pModule) => { // mutating pModule intentionally for performance; callers pass a cloned graph let lModule = pModule; if (shouldAddReaches(pReachableRule, lModule)) { lModule = addReachesToModule( lModule, pGraph, pIndexedGraph, pReachableRule, ); } if (shouldAddReachable(pReachableRule, lModule, pGraph)) { lModule = addReachableToModule( lModule, pIndexedGraph, pReachableRule, lFromModules, ); } return lModule; }); } export default function deriveReachables(pGraph, pRuleSet) { const lReachableRules = pRuleSet ? getReachableRules(pRuleSet) : []; if (lReachableRules.length > 0) { const lIndexedGraph = new IndexedModuleGraph(pGraph); let lResultGraph = structuredClone(pGraph); for (const lRule of lReachableRules) { lResultGraph = addReachabilityToGraph(lResultGraph, lIndexedGraph, lRule); } return lResultGraph; } return pGraph; }