dependency-cruiser-fork
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
226 lines (204 loc) • 7.63 kB
JavaScript
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`
);
}
};