dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
171 lines (150 loc) • 5.2 kB
JavaScript
import tsm from "teamcity-service-messages";
import { formatPercentage, formatViolation } from "./utl/index.mjs";
const CATEGORY = "dependency-cruiser";
const SEVERITY2TEAMCITY_SEVERITY = new Map([
["error", "ERROR"],
["warn", "WARNING"],
["info", "INFO"],
]);
const EOL = "\n";
function severity2teamcitySeverity(pSeverity) {
return SEVERITY2TEAMCITY_SEVERITY.get(pSeverity) || "INFO";
}
function reportRules(pRules, pViolations) {
return pRules
.filter((pRule) =>
pViolations.some((pViolation) => pRule.name === pViolation.rule.name),
)
.map((pRule) =>
tsm.inspectionType({
id: pRule.name,
name: pRule.name,
description: pRule.comment || pRule.name,
category: CATEGORY,
}),
);
}
function reportAllowedRule(pAllowedRule, pViolations) {
let lReturnValue = [];
if (
pAllowedRule.length > 0 &&
pViolations.some((pViolation) => pViolation.rule.name === "not-in-allowed")
) {
lReturnValue = tsm.inspectionType({
id: "not-in-allowed",
name: "not-in-allowed",
description: "dependency is not in the 'allowed' set of rules",
category: CATEGORY,
});
}
return lReturnValue;
}
function reportIgnoredRules(pIgnoredCount) {
let lReturnValue = [];
if (pIgnoredCount > 0) {
lReturnValue = tsm.inspectionType({
id: "ignored-known-violations",
name: "ignored-known-violations",
description:
"some dependency violations were ignored; run with --no-ignore-known to see them",
category: CATEGORY,
});
}
return lReturnValue;
}
function reportViolatedRules(pRuleSetUsed, pViolations, pIgnoredCount) {
return reportRules(pRuleSetUsed?.forbidden ?? [], pViolations)
.concat(reportAllowedRule(pRuleSetUsed?.allowed ?? [], pViolations))
.concat(reportRules(pRuleSetUsed?.required ?? [], pViolations))
.concat(reportIgnoredRules(pIgnoredCount));
}
function formatModuleViolation(pViolation) {
return pViolation.from;
}
function formatDependencyViolation(pViolation) {
return `${pViolation.from} -> ${pViolation.to}`;
}
function formatCycleViolation(pViolation) {
return `${pViolation.from} -> ${pViolation.cycle
.map(({ name }) => name)
.join(" -> ")}`;
}
function formatReachabilityViolation(pViolation) {
return `${formatDependencyViolation(pViolation)} ${pViolation.via
.map(({ name }) => name)
.join(" -> ")}`;
}
function formatInstabilityViolation(pViolation) {
return `${formatDependencyViolation(
pViolation,
)} (instability: ${formatPercentage(
pViolation.metrics.from.instability,
)} -> ${formatPercentage(pViolation.metrics.to.instability)})`;
}
function bakeViolationMessage(pViolation) {
const lViolationType2Formatter = {
module: formatModuleViolation,
dependency: formatDependencyViolation,
cycle: formatCycleViolation,
reachability: formatReachabilityViolation,
instability: formatInstabilityViolation,
};
return formatViolation(
pViolation,
lViolationType2Formatter,
formatDependencyViolation,
);
}
function reportIgnoredViolation(pIgnoredCount) {
let lReturnValue = [];
if (pIgnoredCount > 0) {
lReturnValue = tsm.inspection({
typeId: "ignored-known-violations",
message: `${pIgnoredCount} known violations ignored. Run with --no-ignore-known to see them.`,
SEVERITY: "WARNING",
});
}
return lReturnValue;
}
function reportViolations(pViolations, pIgnoredCount) {
return pViolations
.map((pViolation) =>
tsm.inspection({
typeId: pViolation.rule.name,
message: bakeViolationMessage(pViolation),
file: pViolation.from,
SEVERITY: severity2teamcitySeverity(pViolation.rule.severity),
}),
)
.concat(reportIgnoredViolation(pIgnoredCount));
}
/**
* Returns a bunch of TeamCity service messages:
* - for each violated rule in the passed results: an `inspectionType` with the name and comment of that rule
* - for each violation in the passed results: an `inspection` with the violated rule name and the tos and froms
*
* @param {import("../../types/dependency-cruiser.js").ICruiseResult} pResults
* @returns {import("../../types/dependency-cruiser.js").IReporterOutput}
*/
// eslint-disable-next-line complexity
export default function teamcity(pResults) {
// this is the documented way to get tsm to emit strings
// Alternatively we could've used the 'low level API', which
// involves creating new `Message`s and stringifying those.
// The abstraction of the 'higher level API' makes this
// reporter more easy to implement and maintain, despite
// setting this property directly
tsm.stdout = false;
const lRuleSet = pResults?.summary?.ruleSetUsed ?? [];
const lViolations = (pResults?.summary?.violations ?? []).filter(
(pViolation) => pViolation.rule.severity !== "ignore",
);
const lIgnoredCount = pResults?.summary?.ignore ?? 0;
return {
output:
reportViolatedRules(lRuleSet, lViolations, lIgnoredCount)
.concat(reportViolations(lViolations, lIgnoredCount))
.reduce((pAll, pCurrent) => `${pAll}${pCurrent}\n`, "") || EOL,
exitCode: pResults.summary.error,
};
}