UNPKG

dependency-cruiser

Version:

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

283 lines (252 loc) 7.13 kB
import { readdirSync, statSync, readFileSync } from "node:fs"; import { join, relative } from "node:path/posix"; import ignore from "ignore"; import pathToPosix from "./path-to-posix.mjs"; /** * @typedef {(pString:string, pIndex: number, pArray: string[]) => boolean} FilterFunctionType */ /** * @typedef {{directoryName: string; ignoreMatcher: import("ignore").Ignore}} IgnoreRuleType */ /** * @param {string} pFullPathToFile * @param {string} pBaseDirectory * @returns {boolean} */ function fileIsDirectory(pFullPathToFile, pBaseDirectory) { const lStat = statSync(join(pBaseDirectory, pFullPathToFile), { throwIfNoEntry: false, }); return lStat?.isDirectory() ?? false; } /** * @param {string} pFileName * @returns {string} */ function readIgnoreFile(pFileName) { try { return readFileSync(pFileName, "utf8"); } catch { return ""; } } /** * @param {string} pDirectoryName * @param {string} pBaseDirectory * @returns {string} */ function normalizeDirectoryName(pDirectoryName, pBaseDirectory) { return pathToPosix( relative(pBaseDirectory, join(pBaseDirectory, pDirectoryName)), ); } /** * @param {string} pDirectoryName * @param {string} pIgnoreFileContents * @param {string[]=} pAdditionalIgnorePatterns * @returns {IgnoreRuleType} */ function createIgnoreRule( pDirectoryName, pIgnoreFileContents, pAdditionalIgnorePatterns = [], ) { return { directoryName: pDirectoryName, ignoreMatcher: ignore() .add(pIgnoreFileContents) .add(pAdditionalIgnorePatterns), }; } /** * @param {string} pDirectoryName * @param {string} pBaseDirectory * @param {{ignoreFileContents?: string}} pOptions * @returns {IgnoreRuleType} */ function createIgnoreRuleForDirectory( pDirectoryName, pBaseDirectory, pOptions, ) { const lIgnoreFileContents = pOptions.ignoreFileContents === undefined ? readIgnoreFile(join(pBaseDirectory, pDirectoryName, ".gitignore")) : pOptions.ignoreFileContents; return createIgnoreRule(pDirectoryName, lIgnoreFileContents); } /** * @param {string[]} pAncestorDirectoryNames * @param {string} pBaseDirectory * @returns {IgnoreRuleType[]} */ function createIgnoreRulesFromDirectoryNames( pAncestorDirectoryNames, pBaseDirectory, ) { return pAncestorDirectoryNames.map((pAncestorDirectoryName) => createIgnoreRuleForDirectory(pAncestorDirectoryName, pBaseDirectory, {}), ); } /** * @param {string} pDirectoryName * @param {string} pBaseDirectory * @returns {IgnoreRuleType[]} */ function createIgnoreRulesBeforeDirectory(pDirectoryName, pBaseDirectory) { const lNormalizedDirectoryName = normalizeDirectoryName( pDirectoryName, pBaseDirectory, ); if (lNormalizedDirectoryName === "") { return []; } const lDirectorySegments = lNormalizedDirectoryName.split("/"); const lAncestorDirectoryNames = [""]; let lCurrentDirectoryName = ""; for (const lDirectorySegment of lDirectorySegments.slice(0, -1)) { lCurrentDirectoryName = lCurrentDirectoryName ? join(lCurrentDirectoryName, lDirectorySegment) : lDirectorySegment; lAncestorDirectoryNames.push(pathToPosix(lCurrentDirectoryName)); } return createIgnoreRulesFromDirectoryNames( lAncestorDirectoryNames, pBaseDirectory, ); } /** * @param {string} pFilePath * @param {IgnoreRuleType[]} pIgnoreRules * @param {IgnoreRuleType} pMandatoryIgnoreRule * @returns {boolean} */ function fileShouldBeKept(pFilePath, pIgnoreRules, pMandatoryIgnoreRule) { let lFileIsIgnored = false; for (const lIgnoreRule of pIgnoreRules) { const lDirectoryName = lIgnoreRule.directoryName; const lIgnoreMatcher = lIgnoreRule.ignoreMatcher; let lRelativePath = null; if (lDirectoryName === "") { lRelativePath = pFilePath; } else if (pFilePath.startsWith(`${lDirectoryName}/`)) { lRelativePath = pFilePath.slice(lDirectoryName.length + 1); } if (lRelativePath !== null) { const { ignored: lIgnored, unignored: lUnignored } = lIgnoreMatcher.test(lRelativePath); if (lIgnored) { lFileIsIgnored = true; } if (lUnignored) { lFileIsIgnored = false; } } } const { ignored: lIgnored, unignored: lUnignored } = pMandatoryIgnoreRule.ignoreMatcher.test(pFilePath); if (lIgnored) { lFileIsIgnored = true; } if (lUnignored) { lFileIsIgnored = false; } return !lFileIsIgnored; } /** * @type FilterFunctionType */ function identityFilter(_pString, _pIndex, _pArray) { return true; } /** * @param {string} pDirectoryName * @param {{baseDir: string; ignoreRules: IgnoreRuleType[]; mandatoryIgnoreRule: IgnoreRuleType; startDirectoryName: string; startDirectoryIgnoreFileContents?: string; excludeFilterFn: FilterFunctionType; includeOnlyFilterFn: FilterFunctionType}} * pOptions * @returns {string[]} */ function walk( pDirectoryName, { baseDir, ignoreRules, mandatoryIgnoreRule, startDirectoryName, startDirectoryIgnoreFileContents, excludeFilterFn, includeOnlyFilterFn, }, ) { const lCurrentDirectoryName = normalizeDirectoryName(pDirectoryName, baseDir); const lCurrentIgnoreRules = ignoreRules.concat( createIgnoreRuleForDirectory(lCurrentDirectoryName, baseDir, { ...(lCurrentDirectoryName === startDirectoryName && startDirectoryIgnoreFileContents !== undefined ? { ignoreFileContents: startDirectoryIgnoreFileContents } : {}), }), ); const lFilesInCurrentDirectory = readdirSync(join(baseDir, pDirectoryName)) .map((pFileName) => join(pDirectoryName, pFileName)) .filter((pFilePath) => fileShouldBeKept(pFilePath, lCurrentIgnoreRules, mandatoryIgnoreRule), ) .filter(excludeFilterFn) .filter(includeOnlyFilterFn); const lFiles = []; for (const lFile of lFilesInCurrentDirectory) { if (fileIsDirectory(lFile, baseDir)) { lFiles.push( ...walk(lFile, { baseDir, ignoreRules: lCurrentIgnoreRules, mandatoryIgnoreRule, startDirectoryName, startDirectoryIgnoreFileContents, excludeFilterFn, includeOnlyFilterFn, }), ); } else { lFiles.push(pathToPosix(lFile)); } } return lFiles; } /** * @param {string} pDirectoryName * @param {{baseDir: string; ignoreFileContents?: string; additionalIgnorePatterns?: string[]; excludeFilterFn?: FilterFunctionType; includeOnlyFilterFn?: FilterFunctionType}} * pOptions * @returns {string[]} */ export default function findAllFiles( pDirectoryName, { baseDir, ignoreFileContents, additionalIgnorePatterns, excludeFilterFn, includeOnlyFilterFn, }, ) { const lAdditionalIgnorePatterns = additionalIgnorePatterns ?? [".git"]; const lStartDirectoryName = normalizeDirectoryName(pDirectoryName, baseDir); const lMandatoryIgnoreRule = createIgnoreRule( "", "", lAdditionalIgnorePatterns, ); const lIgnoreRules = createIgnoreRulesBeforeDirectory( pDirectoryName, baseDir, ); return walk(pDirectoryName, { baseDir, ignoreRules: lIgnoreRules, mandatoryIgnoreRule: lMandatoryIgnoreRule, startDirectoryName: lStartDirectoryName, startDirectoryIgnoreFileContents: ignoreFileContents, excludeFilterFn: excludeFilterFn ?? identityFilter, includeOnlyFilterFn: includeOnlyFilterFn ?? identityFilter, }); }