dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
161 lines (140 loc) • 4.87 kB
JavaScript
import { EOL } from "node:os";
import { styleText } from "node:util";
import {
formatPercentage,
formatViolation as _formatViolation,
} from "./utl/index.mjs";
import { findRuleByName } from "#graph-utl/rule-set.mjs";
import wrapAndIndent from "#utl/wrap-and-indent.mjs";
const SEVERITY2COLOR = new Map([
["error", "red"],
["warn", "yellow"],
["info", "cyan"],
["ignore", "gray"],
]);
const EXTRA_PATH_INFORMATION_INDENT = 6;
function formatMiniDependency(pMiniDependency) {
return EOL.concat(
wrapAndIndent(
pMiniDependency.map(({ name }) => name).join(` → ${EOL}`),
EXTRA_PATH_INFORMATION_INDENT,
),
);
}
function formatModuleViolation(pViolation) {
return styleText("bold", pViolation.from);
}
function formatDependencyViolation(pViolation) {
return `${styleText("bold", pViolation.from)} → ${styleText("bold", pViolation.to)}`;
}
function formatCycleViolation(pViolation) {
return `${styleText("bold", pViolation.from)} → ${formatMiniDependency(pViolation.cycle)}`;
}
function formatReachabilityViolation(pViolation) {
return `${styleText("bold", pViolation.from)} → ${styleText("bold", pViolation.to)}${formatMiniDependency(pViolation.via)}`;
}
function formatInstabilityViolation(pViolation) {
return `${formatDependencyViolation(pViolation)}${EOL}${styleText(
"dim",
wrapAndIndent(
`instability: ${formatPercentage(pViolation.metrics.from.instability)} → ${formatPercentage(pViolation.metrics.to.instability)}`,
EXTRA_PATH_INFORMATION_INDENT,
),
)}`;
}
function formatViolation(pViolation) {
const lViolationType2Formatter = {
module: formatModuleViolation,
dependency: formatDependencyViolation,
cycle: formatCycleViolation,
reachability: formatReachabilityViolation,
instability: formatInstabilityViolation,
};
const lFormattedViolators = _formatViolation(
pViolation,
lViolationType2Formatter,
formatDependencyViolation,
);
return (
`${styleText(
SEVERITY2COLOR.get(pViolation.rule.severity),
pViolation.rule.severity,
)} ${pViolation.rule.name}: ${lFormattedViolators}` +
`${
pViolation.comment
? `${EOL}${styleText("dim", wrapAndIndent(pViolation.comment))}${EOL}`
: ""
}`
);
}
function formatMeta(pMeta) {
return `${pMeta.error} errors, ${pMeta.warn} warnings`;
}
function sumMeta(pMeta) {
return pMeta.error + pMeta.warn + pMeta.info;
}
function formatSummary(pSummary) {
let lMessage = `${EOL}x ${sumMeta(
pSummary,
)} dependency violations (${formatMeta(pSummary)}). ${
pSummary.totalCruised
} modules, ${pSummary.totalDependenciesCruised} dependencies cruised.${EOL}`;
return pSummary.error > 0 ? styleText("red", lMessage) : lMessage;
}
function addExplanation(pRuleSet, pLong) {
return pLong
? (pViolation) => ({
...pViolation,
comment: findRuleByName(pRuleSet, pViolation.rule.name)?.comment ?? "-",
})
: (pViolation) => pViolation;
}
function formatIgnoreWarning(pNumberOfIgnoredViolations) {
if (pNumberOfIgnoredViolations > 0) {
return styleText(
"yellow",
`‼ ${pNumberOfIgnoredViolations} known violations ignored. Run with --no-ignore-known to see them.${EOL}`,
);
}
return "";
}
function report(pResults, pLong) {
const lNonIgnorableViolations = pResults.summary.violations.filter(
(pViolation) => pViolation.rule.severity !== "ignore",
);
if (lNonIgnorableViolations.length === 0) {
return `${EOL}${styleText("green", "✔")} no dependency violations found (${
pResults.summary.totalCruised
} modules, ${
pResults.summary.totalDependenciesCruised
} dependencies cruised)${EOL}${formatIgnoreWarning(
pResults.summary.ignore,
)}${EOL}`;
}
return lNonIgnorableViolations
.reverse()
.map(addExplanation(pResults.summary.ruleSetUsed, pLong))
.reduce((pAll, pThis) => `${pAll} ${formatViolation(pThis)}${EOL}`, EOL)
.concat(formatSummary(pResults.summary))
.concat(formatIgnoreWarning(pResults.summary.ignore))
.concat(EOL);
}
/**
* Returns the results of a cruise in a text only format, reminiscent of how eslint
* prints to stdout:
* - for each violation a message stating the violation name and the to and from
* - a summary with total number of errors and warnings found, and the total
* number of files cruised
* @param {import("../../types/cruise-result.mjs").ICruiseResult} pResults -
* @param {any} pOptions - An object with options;
* {boolean} long - whether or not to include an explanation
* (/ comment) which each violation
* @returns {import("../../types/dependency-cruiser.js").IReporterOutput} - output: the formatted text in a string
* exitCode: the number of errors found
*/
export default function error(pResults, pOptions) {
return {
output: report(pResults, (pOptions || {}).long),
exitCode: pResults.summary.error,
};
}