dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
167 lines (146 loc) • 5.09 kB
JavaScript
import tsm from "teamcity-service-messages";
import { formatPercentage, formatViolation } from "./utl/index.mjs";
const CATEGORY = "dependency-cruiser";
const SEVERITY2TEAMCITY_SEVERITY = {
error: "ERROR",
warn: "WARNING",
info: "INFO",
};
function severity2teamcitySeverity(pSeverity) {
// eslint-disable-next-line security/detect-object-injection
return SEVERITY2TEAMCITY_SEVERITY[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.join(" -> ")}`;
}
function formatReachabilityViolation(pViolation) {
return `${formatDependencyViolation(pViolation)} ${pViolation.via.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}
*/
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`, ""),
exitCode: pResults.summary.error,
};
}