UNPKG

dependency-cruiser-fork

Version:

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

226 lines (204 loc) 7.63 kB
const path = require("path"); const _get = require("lodash/get"); const _uniqBy = require("lodash/uniqBy"); const { intersects } = require("../utl/array-util"); const resolve = require("./resolve"); const extractES6Deps = require("./ast-extractors/extract-es6-deps"); const extractCommonJSDeps = require("./ast-extractors/extract-cjs-deps"); const extractAMDDeps = require("./ast-extractors/extract-amd-deps"); const extractTypeScriptDeps = require("./ast-extractors/extract-typescript-deps"); const extractExportedFunctions = require("./ast-extractors/extract-exported-functions"); const extractCallExpressions = require("./ast-extractors/extract-call-expressions"); const toJavascriptAST = require("./parse/to-javascript-ast"); const toTypescriptAST = require("./parse/to-typescript-ast"); const detectPreCompilationNess = require("./utl/detect-pre-compilation-ness"); function extractFromTypeScriptAST(pOptions, pFileName) { return extractTypeScriptDeps( toTypescriptAST.getASTCached(path.join(pOptions.baseDir, pFileName)), pOptions.exoticRequireStrings ); } function shouldUseTSC(pOptions, pFileName) { return ( toTypescriptAST.isAvailable() && path.extname(pFileName).startsWith(".ts") && pOptions.tsPreCompilationDeps ); } function extractFromJavaScriptAST(pOptions, pFileName, pTranspileOptions) { let lDependencies = []; let lFunctionDeclarations = []; let lCallExpressions = []; const lAST = toJavascriptAST.getASTCached( path.join(pOptions.baseDir, pFileName), pTranspileOptions ); if (pOptions.moduleSystems.includes("cjs")) { extractCommonJSDeps( lAST, lDependencies, "cjs", pOptions.exoticRequireStrings ); } if (pOptions.moduleSystems.includes("es6")) { extractES6Deps(lAST, lDependencies); } if (pOptions.moduleSystems.includes("amd")) { extractAMDDeps(lAST, lDependencies, pOptions.exoticRequireStrings); } // 提取出当前模块导出的函数声明信息 extractExportedFunctions(lAST, lFunctionDeclarations); // 提取出当前模块的函数调用 extractCallExpressions(lAST, lCallExpressions, lDependencies); return { lDependencies, lFunctionDeclarations, lCallExpressions, }; } function extractDependencies(pCruiseOptions, pFileName, pTranspileOptions) { let lDependencies = []; let lFunctionDeclarations = []; let lCallExpressions = []; if (shouldUseTSC(pCruiseOptions, pFileName)) { lDependencies = extractFromTypeScriptAST( pCruiseOptions, pFileName ).filter((pDep) => pCruiseOptions.moduleSystems.includes(pDep.moduleSystem) ); if (pCruiseOptions.tsPreCompilationDeps === "specify") { const extractObject = extractFromJavaScriptAST( pCruiseOptions, pFileName, pTranspileOptions ); lDependencies = detectPreCompilationNess( lDependencies, extractObject.lDependencies ); lFunctionDeclarations = extractObject.lFunctionDeclarations; lCallExpressions = extractObject.lCallExpressions; } } else { const extractObject = extractFromJavaScriptAST( pCruiseOptions, pFileName, pTranspileOptions ); lDependencies = extractObject.lDependencies; lFunctionDeclarations = extractObject.lFunctionDeclarations; lCallExpressions = extractObject.lCallExpressions; } return { lDependencies, lFunctionDeclarations, lCallExpressions, }; } function matchesDoNotFollow(pResolved, pDoNotFollow) { const lMatchesPath = Boolean(pDoNotFollow.path) ? RegExp(pDoNotFollow.path, "g").test(pResolved.resolved) : false; const lMatchesDependencyTypes = Boolean(pDoNotFollow.dependencyTypes) ? intersects(pResolved.dependencyTypes, pDoNotFollow.dependencyTypes) : false; return lMatchesPath || lMatchesDependencyTypes; } function addResolutionAttributes(pOptions, pFileName, pResolveOptions) { return (pDependency) => { const lResolved = resolve( pDependency, pOptions.baseDir, path.join(pOptions.baseDir, path.dirname(pFileName)), pResolveOptions ); const lMatchesDoNotFollow = matchesDoNotFollow( lResolved, pOptions.doNotFollow ); return { ...lResolved, ...pDependency, followable: lResolved.followable && !lMatchesDoNotFollow, matchesDoNotFollow: lMatchesDoNotFollow, }; }; } function matchesPattern(pFullPathToFile, pPattern) { return RegExp(pPattern, "g").test(pFullPathToFile); } function getDependencyUniqueKey(pDependency) { return `${pDependency.module} ${pDependency.moduleSystem}`; } 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 {object} pCruiseOptions an object with one or more of these properties: * - baseDir - the directory to consider as the * base for all files * Default: the current working directory * - moduleSystems - an array of module systems to * consider. * Default: ["cjs", "es6", "amd"] * - exclude - a regular expression string * with a pattern of modules to exclude * (e.g. "(node_modules)"). Default: none * - preserveSymlinks - don't resolve symlinks. * @param {object} pResolveOptions an object with webpack 'enhanced-resolve' options * @param {any} pTranspileOptions an object with tsconfig ('typescript project') options * ('flattened' so there's no need for file access on any * 'extends' option in there) * @return {array} an array of dependency objects (see above) */ module.exports = ( pFileName, pCruiseOptions, pResolveOptions, pTranspileOptions ) => { try { const extractObject = extractDependencies( pCruiseOptions, pFileName, pTranspileOptions ); const dependencies = _uniqBy( extractObject.lDependencies, getDependencyUniqueKey ) .sort(compareDeps) .map(addResolutionAttributes(pCruiseOptions, pFileName, pResolveOptions)) .filter( (pDep) => (!_get(pCruiseOptions, "exclude.path") || !matchesPattern(pDep.resolved, pCruiseOptions.exclude.path)) && (!_get(pCruiseOptions, "includeOnly.path") || matchesPattern(pDep.resolved, pCruiseOptions.includeOnly.path)) ); return { dependencies, functionDeclarations: extractObject.lFunctionDeclarations, callExpressions: extractObject.lCallExpressions, }; } catch (pError) { throw new Error( `Extracting dependencies ran afoul of...\n\n ${pError.message}\n... in ${pFileName}\n\n` ); } };