dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
151 lines (129 loc) • 4 kB
JavaScript
/* eslint-disable security/detect-object-injection */
import safeRegex from "safe-regex";
import { getAvailableReporters } from "#report/index.mjs";
/**
* @import { ICruiseOptions, IFormatOptions } from "../../../types/options.mjs";
*/
const MODULE_SYSTEM_LIST_RE = /^(?:(?:cjs|amd|es6|tsd)(?:,|$)){1,4}/i;
const VALID_DEPTH_RE = /^\d{1,2}$/;
function isObject(pObject) {
return (
typeof pObject === "object" && !Array.isArray(pObject) && pObject !== null
);
}
function deepMerge(pTarget, pSource) {
const lOutput = structuredClone(pTarget);
for (const lKey in pSource) {
if (isObject(pSource[lKey])) {
if (lKey in pTarget) {
lOutput[lKey] = deepMerge(pTarget[lKey], pSource[lKey]);
} else {
Object.assign(lOutput, { [lKey]: pSource[lKey] });
}
} else {
Object.assign(lOutput, { [lKey]: pSource[lKey] });
}
}
return lOutput;
}
function assertModuleSystemsValid(pModuleSystems) {
if (
pModuleSystems &&
Array.isArray(pModuleSystems) &&
!pModuleSystems.every((pModuleSystem) =>
MODULE_SYSTEM_LIST_RE.test(pModuleSystem),
)
) {
throw new Error(
`Invalid module system list: '${pModuleSystems.join(", ")}'\n`,
);
}
}
function assertRegExpSafety(pPattern) {
if (pPattern && !safeRegex(pPattern)) {
throw new Error(
`The pattern '${pPattern}' will probably run very slowly - cowardly refusing to run.\n`,
);
}
}
function assertOutputTypeValid(pOutputType) {
if (
pOutputType &&
!getAvailableReporters().includes(pOutputType) &&
!pOutputType.startsWith("plugin:")
) {
throw new Error(`'${pOutputType}' is not a valid output type.\n`);
}
}
function assertMaxDepthValid(pDepth) {
if (pDepth && !VALID_DEPTH_RE.test(pDepth.toString())) {
throw new Error(
`'${pDepth}' is not a valid depth - use an integer between 0 and 99`,
);
}
}
function assertFocusDepthValid(pFocusDepth) {
const lFocusDepth = Number.parseInt(pFocusDepth, 10);
const lMaxFocusDepth = 99;
if (
pFocusDepth &&
(Number.isNaN(lFocusDepth) ||
lFocusDepth < 0 ||
lFocusDepth > lMaxFocusDepth)
) {
throw new Error(
`'${pFocusDepth}' is not a valid focus depth - use an integer between 0 and ${lMaxFocusDepth}`,
);
}
}
function assertPathsSafety(pFilterOption) {
if (typeof pFilterOption === "string") {
assertRegExpSafety(pFilterOption);
}
assertRegExpSafety(pFilterOption?.path ?? "");
assertRegExpSafety(pFilterOption?.pathNot ?? "");
}
/**
* @param {any} pOptions
* @throws {Error}
* @returns {ICruiseOptions}
*/
export function assertCruiseOptionsValid(pOptions) {
let lReturnValue = {};
if (pOptions) {
// necessary because can slip through the cracks when passed as a cli parameter
assertModuleSystemsValid(pOptions.moduleSystems);
// necessary because this safety check can't be done in json schema (a.f.a.i.k.)
assertPathsSafety(pOptions.doNotFollow);
assertPathsSafety(pOptions.exclude);
assertRegExpSafety(pOptions.includeOnly);
assertRegExpSafety(pOptions.focus);
assertRegExpSafety(pOptions.reaches);
assertRegExpSafety(pOptions.highlight);
assertRegExpSafety(pOptions.collapse);
// necessary because not in the config schema
assertOutputTypeValid(pOptions.outputType);
// necessary because not found a way to do this properly in JSON schema
assertMaxDepthValid(pOptions.maxDepth);
assertFocusDepthValid(pOptions.focusDepth);
if (pOptions?.ruleSet?.options) {
lReturnValue = assertCruiseOptionsValid(pOptions.ruleSet.options);
}
return deepMerge(lReturnValue, pOptions);
}
return lReturnValue;
}
/**
*
* @param {IFormatOptions} pFormatOptions
* @throws {Error}
*/
export function assertFormatOptionsValid(pFormatOptions) {
assertPathsSafety(pFormatOptions.exclude);
assertPathsSafety(pFormatOptions.focus);
assertPathsSafety(pFormatOptions.reaches);
assertPathsSafety(pFormatOptions.includeOnly);
assertRegExpSafety(pFormatOptions.collapse);
assertOutputTypeValid(pFormatOptions.outputType);
assertFocusDepthValid(pFormatOptions.focusDepth);
}