dependency-cruiser 16.6.0 → 16.7.0-beta-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/depcruise-baseline.mjs +1 -1
- package/package.json +6 -6
- package/src/cache/cache.mjs +3 -4
- package/src/cache/content-strategy.mjs +5 -7
- package/src/cache/find-content-changes.mjs +11 -5
- package/src/cache/helpers.mjs +8 -8
- package/src/cache/metadata-strategy.mjs +6 -8
- package/src/cache/options-compatible.mjs +2 -1
- package/src/cli/index.mjs +4 -2
- package/src/cli/init-config/build-config.mjs +13 -9
- package/src/cli/init-config/config-template.mjs +11 -0
- package/src/cli/init-config/index.mjs +10 -6
- package/src/cli/init-config/normalize-init-options.mjs +10 -6
- package/src/cli/listeners/performance-log/format-helpers.mjs +1 -1
- package/src/cli/normalize-cli-options.mjs +1 -0
- package/src/cli/utl/io.mjs +1 -2
- package/src/config-utl/extract-babel-config.mjs +0 -1
- package/src/config-utl/extract-depcruise-options.mjs +8 -4
- package/src/config-utl/extract-known-violations.mjs +6 -6
- package/src/config-utl/extract-webpack-resolve-config.mjs +1 -1
- package/src/enrich/soften-known-violations.mjs +1 -1
- package/src/enrich/summarize/add-rule-set-used.mjs +1 -0
- package/src/enrich/summarize/summarize-modules.mjs +0 -1
- package/src/enrich/summarize/summarize-options.mjs +2 -0
- package/src/extract/extract-dependencies.mjs +16 -11
- package/src/extract/extract-stats.mjs +9 -4
- package/src/extract/gather-initial-sources.mjs +8 -5
- package/src/extract/index.mjs +9 -4
- package/src/extract/resolve/determine-dependency-types.mjs +22 -15
- package/src/extract/resolve/external-module-helpers.mjs +4 -4
- package/src/extract/resolve/index.mjs +11 -5
- package/src/extract/resolve/module-classifiers.mjs +9 -4
- package/src/extract/resolve/resolve-helpers.mjs +3 -3
- package/src/extract/swc/dependency-visitor.mjs +2 -6
- package/src/extract/swc/parse.mjs +8 -4
- package/src/extract/tsc/extract-typescript-deps.mjs +121 -16
- package/src/extract/tsc/extract.mjs +2 -1
- package/src/graph-utl/consolidate-module-dependencies.mjs +8 -4
- package/src/graph-utl/filter-bank.mjs +8 -4
- package/src/graph-utl/indexed-module-graph.mjs +7 -11
- package/src/graph-utl/rule-set.mjs +9 -5
- package/src/main/cruise.mjs +2 -2
- package/src/main/format.mjs +0 -1
- package/src/main/options/assert-validity.mjs +13 -9
- package/src/main/options/defaults.mjs +1 -0
- package/src/main/options/normalize.mjs +23 -13
- package/src/main/report-wrap.mjs +12 -7
- package/src/main/resolve-options/normalize.mjs +10 -5
- package/src/main/rule-set/assert-validity.mjs +5 -2
- package/src/main/rule-set/normalize.mjs +21 -11
- package/src/meta.cjs +1 -1
- package/src/report/anon/anonymize-path.mjs +1 -1
- package/src/report/d2.mjs +2 -2
- package/src/report/dot/index.mjs +3 -3
- package/src/report/dot-webpage/svg-in-html-snippets/script.cjs +4 -4
- package/src/report/markdown.mjs +41 -30
- package/src/report/mermaid.mjs +2 -2
- package/src/report/teamcity.mjs +1 -0
- package/src/schema/baseline-violations.schema.mjs +1 -1
- package/src/schema/configuration.schema.mjs +1 -1
- package/src/schema/cruise-result.schema.mjs +1 -1
- package/src/utl/get-extension.mjs +1 -1
- package/src/utl/try-import.mjs +0 -1
- package/src/utl/try-require.cjs +2 -2
- package/src/validate/index.mjs +6 -2
- package/src/validate/match-dependency-rule.mjs +8 -4
- package/src/validate/match-module-rule-helpers.mjs +16 -10
- package/src/validate/match-module-rule.mjs +8 -5
- package/src/validate/matchers.mjs +1 -3
- package/types/options.d.mts +5 -0
- package/types/shared-types.d.mts +3 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prevent-abbreviations */
|
|
1
2
|
/* eslint-disable max-lines */
|
|
2
3
|
/* eslint-disable no-inline-comments */
|
|
3
4
|
import tryImport from "#utl/try-import.mjs";
|
|
4
5
|
import meta from "#meta.cjs";
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* @import typescript, {Node} from "typescript"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @type {typescript} */
|
|
7
12
|
const typescript = await tryImport(
|
|
8
13
|
"typescript",
|
|
9
14
|
meta.supportedTranspilers.typescript,
|
|
@@ -24,7 +29,7 @@ function isTypeOnlyImport(pStatement) {
|
|
|
24
29
|
function isTypeOnlyExport(pStatement) {
|
|
25
30
|
return (
|
|
26
31
|
// for some reason the isTypeOnly indicator is on _statement_ level
|
|
27
|
-
// and not in exportClause as it is in the importClause ¯\_
|
|
32
|
+
// and not in exportClause as it is in the importClause ¯\_(ツ)_/¯.
|
|
28
33
|
// Also in the case of the omission of an alias the exportClause
|
|
29
34
|
// is not there entirely. So regardless whether there is a
|
|
30
35
|
// pStatement.exportClause or not, we can directly test for the
|
|
@@ -46,7 +51,7 @@ function isTypeOnlyExport(pStatement) {
|
|
|
46
51
|
/**
|
|
47
52
|
* Get all import statements from the top level AST node
|
|
48
53
|
*
|
|
49
|
-
* @param {
|
|
54
|
+
* @param {Node} pAST - the (top-level in this case) AST node
|
|
50
55
|
* @returns {{module: string; moduleSystem: string; exoticallyRequired: boolean; dependencyTypes?: string[];}[]} -
|
|
51
56
|
* all import statements in the (top level) AST node
|
|
52
57
|
*/
|
|
@@ -55,7 +60,7 @@ function extractImports(pAST) {
|
|
|
55
60
|
.filter(
|
|
56
61
|
(pStatement) =>
|
|
57
62
|
typescript.SyntaxKind[pStatement.kind] === "ImportDeclaration" &&
|
|
58
|
-
|
|
63
|
+
pStatement.moduleSpecifier,
|
|
59
64
|
)
|
|
60
65
|
.map((pStatement) => ({
|
|
61
66
|
module: pStatement.moduleSpecifier.text,
|
|
@@ -70,7 +75,7 @@ function extractImports(pAST) {
|
|
|
70
75
|
/**
|
|
71
76
|
* Get all export statements from the top level AST node
|
|
72
77
|
*
|
|
73
|
-
* @param {
|
|
78
|
+
* @param {Node} pAST - the (top-level in this case) AST node
|
|
74
79
|
* @returns {{module: string; moduleSystem: string; exoticallyRequired: boolean; dependencyTypes?: string[];}[]} -
|
|
75
80
|
* all export statements in the (top level) AST node
|
|
76
81
|
*/
|
|
@@ -79,7 +84,7 @@ function extractExports(pAST) {
|
|
|
79
84
|
.filter(
|
|
80
85
|
(pStatement) =>
|
|
81
86
|
typescript.SyntaxKind[pStatement.kind] === "ExportDeclaration" &&
|
|
82
|
-
|
|
87
|
+
pStatement.moduleSpecifier,
|
|
83
88
|
)
|
|
84
89
|
.map((pStatement) => ({
|
|
85
90
|
module: pStatement.moduleSpecifier.text,
|
|
@@ -98,7 +103,7 @@ function extractExports(pAST) {
|
|
|
98
103
|
* Ignores import equals of variables (e.g. import protocol = ts.server.protocol
|
|
99
104
|
* which happens in typescript/lib/protocol.d.ts)
|
|
100
105
|
*
|
|
101
|
-
* @param {
|
|
106
|
+
* @param {Node} pAST - the (top-level in this case) AST node
|
|
102
107
|
* @returns {{module: string, moduleSystem: string;exoticallyRequired: boolean;}[]} - all import equals statements in the
|
|
103
108
|
* (top level) AST node
|
|
104
109
|
*/
|
|
@@ -123,7 +128,7 @@ function extractImportEquals(pAST) {
|
|
|
123
128
|
* might be wise to distinguish the three types of /// directive that
|
|
124
129
|
* can come out of this as the resolution algorithm might differ
|
|
125
130
|
*
|
|
126
|
-
* @param {
|
|
131
|
+
* @param {Node} pAST - typescript syntax tree
|
|
127
132
|
* @returns {{module: string, moduleSystem: string}[]} - 'tripple slash' dependencies
|
|
128
133
|
*/
|
|
129
134
|
function extractTripleSlashDirectives(pAST) {
|
|
@@ -252,7 +257,74 @@ function isTypeImport(pASTNode) {
|
|
|
252
257
|
"FirstTemplateToken")
|
|
253
258
|
);
|
|
254
259
|
}
|
|
255
|
-
|
|
260
|
+
|
|
261
|
+
function extractJSDocImportTags(pJSDocTags) {
|
|
262
|
+
return pJSDocTags
|
|
263
|
+
.filter(
|
|
264
|
+
(pTag) =>
|
|
265
|
+
pTag.tagName.escapedText === "import" &&
|
|
266
|
+
pTag.moduleSpecifier?.kind &&
|
|
267
|
+
typescript.SyntaxKind[pTag.moduleSpecifier.kind] === "StringLiteral" &&
|
|
268
|
+
pTag.moduleSpecifier.text,
|
|
269
|
+
)
|
|
270
|
+
.map((pTag) => ({
|
|
271
|
+
module: pTag.moduleSpecifier.text,
|
|
272
|
+
moduleSystem: "es6",
|
|
273
|
+
exoticallyRequired: false,
|
|
274
|
+
dependencyTypes: ["type-only", "import", "jsdoc", "jsdoc-import-tag"],
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function extractJSDocBracketImports(pJSDocTags) {
|
|
279
|
+
return pJSDocTags
|
|
280
|
+
.filter(
|
|
281
|
+
(pTag) =>
|
|
282
|
+
pTag.tagName.escapedText !== "import" &&
|
|
283
|
+
/* c8 ignore start */
|
|
284
|
+
typescript.SyntaxKind[pTag.typeExpression?.kind ?? -1] ===
|
|
285
|
+
"FirstJSDocNode" &&
|
|
286
|
+
typescript.SyntaxKind[pTag.typeExpression.type?.kind ?? -1] ===
|
|
287
|
+
"LastTypeNode" &&
|
|
288
|
+
typescript.SyntaxKind[pTag.typeExpression.type.argument?.kind ?? -1] ===
|
|
289
|
+
"LiteralType" &&
|
|
290
|
+
typescript.SyntaxKind[
|
|
291
|
+
pTag.typeExpression.type.argument?.literal?.kind ?? -1
|
|
292
|
+
] === "StringLiteral" &&
|
|
293
|
+
/* c8 ignore stop*/
|
|
294
|
+
pTag.typeExpression.type.argument.literal.text,
|
|
295
|
+
)
|
|
296
|
+
.map((pTag) => ({
|
|
297
|
+
module: pTag.typeExpression.type.argument.literal.text,
|
|
298
|
+
moduleSystem: "es6",
|
|
299
|
+
exoticallyRequired: false,
|
|
300
|
+
dependencyTypes: ["type-only", "import", "jsdoc", "jsdoc-bracket-import"],
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function extractJSDocImports(pJSDocNodes) {
|
|
305
|
+
const lJSDocNodesWithTags = pJSDocNodes.filter(
|
|
306
|
+
(pJSDocLine) => pJSDocLine.tags,
|
|
307
|
+
);
|
|
308
|
+
const lJSDocImportTags = lJSDocNodesWithTags.flatMap((pJSDocLine) =>
|
|
309
|
+
extractJSDocImportTags(pJSDocLine.tags),
|
|
310
|
+
);
|
|
311
|
+
const lJSDocBracketImports = lJSDocNodesWithTags.flatMap((pJSDocLine) =>
|
|
312
|
+
extractJSDocBracketImports(pJSDocLine.tags),
|
|
313
|
+
);
|
|
314
|
+
return lJSDocImportTags.concat(lJSDocBracketImports);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Walks the AST and collects all dependencies
|
|
319
|
+
*
|
|
320
|
+
* @param {Node} pASTNode - the AST node to start from
|
|
321
|
+
* @param {string[]} pExoticRequireStrings - exotic require strings to look for
|
|
322
|
+
* @param {boolean} pDetectJSDocImports - whether to detect jsdoc imports
|
|
323
|
+
* @returns {(pASTNode: Node) => void} - the walker function
|
|
324
|
+
*/
|
|
325
|
+
// eslint-disable-next-line max-lines-per-function
|
|
326
|
+
function walk(pResult, pExoticRequireStrings, pDetectJSDocImports) {
|
|
327
|
+
// eslint-disable-next-line max-lines-per-function
|
|
256
328
|
return (pASTNode) => {
|
|
257
329
|
// require('a-string'), require(`a-template-literal`)
|
|
258
330
|
if (isRequireCallExpression(pASTNode)) {
|
|
@@ -298,7 +370,28 @@ function walk(pResult, pExoticRequireStrings) {
|
|
|
298
370
|
dependencyTypes: ["type-import"],
|
|
299
371
|
});
|
|
300
372
|
}
|
|
301
|
-
|
|
373
|
+
|
|
374
|
+
// /** @import thing from './module' */
|
|
375
|
+
// /** @import {thing} from './module' */
|
|
376
|
+
// /** @import * as thing from './module' */
|
|
377
|
+
// see https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag
|
|
378
|
+
//
|
|
379
|
+
// TODO: all the kinds of tags that can have import statements as type declarations
|
|
380
|
+
// (e.g. @type, @param, @returns, @typedef, @property, @prop, @arg, ...)
|
|
381
|
+
// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html
|
|
382
|
+
if (pDetectJSDocImports && pASTNode.jsDoc) {
|
|
383
|
+
const lJSDocImports = extractJSDocImports(pASTNode.jsDoc);
|
|
384
|
+
|
|
385
|
+
// pResult = pResult.concat(lJSDocImports) looks like the more obvious
|
|
386
|
+
// way to do this, but it re-assigns the pResult parameter
|
|
387
|
+
lJSDocImports.forEach((pImport) => {
|
|
388
|
+
pResult.push(pImport);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
typescript.forEachChild(
|
|
392
|
+
pASTNode,
|
|
393
|
+
walk(pResult, pExoticRequireStrings, pDetectJSDocImports),
|
|
394
|
+
);
|
|
302
395
|
};
|
|
303
396
|
}
|
|
304
397
|
|
|
@@ -306,13 +399,19 @@ function walk(pResult, pExoticRequireStrings) {
|
|
|
306
399
|
* returns an array of dependencies that come potentially nested within
|
|
307
400
|
* a source file, like commonJS or dynamic imports
|
|
308
401
|
*
|
|
309
|
-
* @param {
|
|
402
|
+
* @param {Node} pAST - typescript syntax tree
|
|
403
|
+
* @param {string[]} pExoticRequireStrings - exotic require strings to look for
|
|
404
|
+
* @param {boolean} pDetectJSDocImports - whether to detect jsdoc imports
|
|
310
405
|
* @returns {{module: string, moduleSystem: string}[]} - all commonJS dependencies
|
|
311
406
|
*/
|
|
312
|
-
function extractNestedDependencies(
|
|
407
|
+
function extractNestedDependencies(
|
|
408
|
+
pAST,
|
|
409
|
+
pExoticRequireStrings,
|
|
410
|
+
pDetectJSDocImports,
|
|
411
|
+
) {
|
|
313
412
|
let lResult = [];
|
|
314
413
|
|
|
315
|
-
walk(lResult, pExoticRequireStrings)(pAST);
|
|
414
|
+
walk(lResult, pExoticRequireStrings, pDetectJSDocImports)(pAST);
|
|
316
415
|
|
|
317
416
|
return lResult;
|
|
318
417
|
}
|
|
@@ -320,19 +419,25 @@ function extractNestedDependencies(pAST, pExoticRequireStrings) {
|
|
|
320
419
|
/**
|
|
321
420
|
* returns all dependencies in the AST
|
|
322
421
|
*
|
|
323
|
-
* @type {(pTypeScriptAST: (
|
|
422
|
+
* @type {(pTypeScriptAST: (Node), pExoticRequireStrings: string[], pDetectJSDocImports: boolean) => {module: string, moduleSystem: string, dynamic: boolean}[]}
|
|
324
423
|
*/
|
|
325
424
|
export default function extractTypeScriptDependencies(
|
|
326
425
|
pTypeScriptAST,
|
|
327
426
|
pExoticRequireStrings,
|
|
427
|
+
pDetectJSDocImports,
|
|
328
428
|
) {
|
|
329
|
-
|
|
429
|
+
// console.dir(pTypeScriptAST, { depth: 100 });
|
|
430
|
+
return typescript
|
|
330
431
|
? extractImports(pTypeScriptAST)
|
|
331
432
|
.concat(extractExports(pTypeScriptAST))
|
|
332
433
|
.concat(extractImportEquals(pTypeScriptAST))
|
|
333
434
|
.concat(extractTripleSlashDirectives(pTypeScriptAST))
|
|
334
435
|
.concat(
|
|
335
|
-
extractNestedDependencies(
|
|
436
|
+
extractNestedDependencies(
|
|
437
|
+
pTypeScriptAST,
|
|
438
|
+
pExoticRequireStrings,
|
|
439
|
+
pDetectJSDocImports,
|
|
440
|
+
),
|
|
336
441
|
)
|
|
337
442
|
.map((pModule) => ({ dynamic: false, ...pModule }))
|
|
338
443
|
: /* c8 ignore next */ [];
|
|
@@ -13,13 +13,14 @@ export function shouldUse({ tsPreCompilationDeps, parser }, pFileName) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function extract(
|
|
16
|
-
{ baseDir, exoticRequireStrings, moduleSystems },
|
|
16
|
+
{ baseDir, exoticRequireStrings, moduleSystems, detectJSDocImports },
|
|
17
17
|
pFileName,
|
|
18
18
|
pTranspileOptions,
|
|
19
19
|
) {
|
|
20
20
|
return extractTypeScriptDeps(
|
|
21
21
|
getASTCached(join(baseDir, pFileName), pTranspileOptions),
|
|
22
22
|
exoticRequireStrings,
|
|
23
|
+
detectJSDocImports,
|
|
23
24
|
).filter(({ moduleSystem }) => moduleSystems.includes(moduleSystem));
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { compareRules } from "./compare.mjs";
|
|
2
2
|
import { uniq } from "#utl/array-util.mjs";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @import { IDependency, IModule } from "../../types/cruise-result.mjs";
|
|
6
|
+
*/
|
|
7
|
+
|
|
4
8
|
function mergeDependency(pLeftDependency, pRightDependency) {
|
|
5
9
|
return {
|
|
6
10
|
...pLeftDependency,
|
|
@@ -30,8 +34,8 @@ function mergeDependencies(pResolvedName, pDependencies) {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
|
-
* @param {
|
|
34
|
-
* @returns {
|
|
37
|
+
* @param {IDependency[]} pDependencies
|
|
38
|
+
* @returns {IDependency[]}
|
|
35
39
|
*/
|
|
36
40
|
function consolidateDependencies(pDependencies) {
|
|
37
41
|
let lDependencies = structuredClone(pDependencies);
|
|
@@ -51,8 +55,8 @@ function consolidateDependencies(pDependencies) {
|
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/**
|
|
54
|
-
* @param {
|
|
55
|
-
* @returns {
|
|
58
|
+
* @param {IModule} pModule
|
|
59
|
+
* @returns {IModule}
|
|
56
60
|
*/
|
|
57
61
|
export default function consolidateModuleDependencies(pModule) {
|
|
58
62
|
return {
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
dependencyMatchesFilter,
|
|
6
6
|
} from "./match-facade.mjs";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @import { IModule } from "../../types/cruise-result.mjs";
|
|
10
|
+
* @import { IStrictReachesType } from "../../types/strict-filter-types.mjs";
|
|
11
|
+
*/
|
|
12
|
+
|
|
8
13
|
function includeOnly(pModules, pIncludeFilter) {
|
|
9
14
|
return pIncludeFilter.path
|
|
10
15
|
? pModules
|
|
@@ -33,9 +38,9 @@ function exclude(pModules, pExcludeFilter) {
|
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
35
40
|
*
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {
|
|
38
|
-
* @returns {
|
|
41
|
+
* @param {IModule[]} pModules
|
|
42
|
+
* @param {IStrictReachesType} pReachesFilter
|
|
43
|
+
* @returns {IModule[]}
|
|
39
44
|
*/
|
|
40
45
|
function filterReaches(pModules, pReachesFilter) {
|
|
41
46
|
const lModuleNamesToReach = pModules
|
|
@@ -69,7 +74,6 @@ function tagHighlight(pModules, pHighlightFilter) {
|
|
|
69
74
|
}));
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
// eslint-disable-next-line complexity
|
|
73
77
|
export function applyFilters(pModules, pFilters) {
|
|
74
78
|
if (pFilters) {
|
|
75
79
|
let lReturnValue = structuredClone(pModules);
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
/* eslint-disable security/detect-object-injection */
|
|
3
3
|
/**
|
|
4
|
-
* @
|
|
5
|
-
* @
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @typedef {import("../../types/shared-types.d.mts").DependencyType} DependencyType
|
|
9
|
-
* @typedef {import("../../types/shared-types.d.mjs").IMiniDependency} IMiniDependency
|
|
10
|
-
*
|
|
11
|
-
* @typedef {(IDependency|IFolderDependency) & {name:string; dependencyTypes?: DependencyType[]}} IEdge
|
|
4
|
+
* @import { IFolderDependency, IDependency, IFolder, IModule } from "../../types/cruise-result.mjs"
|
|
5
|
+
* @import { DependencyType, IMiniDependency } from "../../types/shared-types.mjs"
|
|
6
|
+
|
|
7
|
+
* @typedef {(IDependency|IFolderDependency) & {name:string; dependencyTypes?: DependencyType[]}} IEdge
|
|
12
8
|
* @typedef {IModule|IFolder} IModuleOrFolder
|
|
13
9
|
* @typedef {IModuleOrFolder & {dependencies: IEdge[]}} IVertex
|
|
14
10
|
*/
|
|
@@ -201,7 +197,7 @@ export default class IndexedModuleGraph {
|
|
|
201
197
|
* @param {Set<string>=} pVisited Technical parameter - best to leave out of direct calls
|
|
202
198
|
* @return {Array<IMiniDependency>} see description above
|
|
203
199
|
*/
|
|
204
|
-
#
|
|
200
|
+
#getCycle(pInitialSource, pCurrentDependency, pVisited) {
|
|
205
201
|
let lVisited = pVisited || new Set();
|
|
206
202
|
const lCurrentVertex = this.findVertexByName(pCurrentDependency.name);
|
|
207
203
|
const lEdges = lCurrentVertex.dependencies.filter(
|
|
@@ -226,7 +222,7 @@ export default class IndexedModuleGraph {
|
|
|
226
222
|
*/
|
|
227
223
|
(pAll, pDependency) => {
|
|
228
224
|
if (!pAll.some((pSome) => pSome.name === pCurrentDependency.name)) {
|
|
229
|
-
const lCycle = this.#
|
|
225
|
+
const lCycle = this.#getCycle(
|
|
230
226
|
pInitialSource,
|
|
231
227
|
pDependency,
|
|
232
228
|
lVisited.add(pDependency.name),
|
|
@@ -266,6 +262,6 @@ export default class IndexedModuleGraph {
|
|
|
266
262
|
if (!lCurrentDependency) {
|
|
267
263
|
return [];
|
|
268
264
|
}
|
|
269
|
-
return this.#
|
|
265
|
+
return this.#getCycle(pInitialSource, lCurrentDependency);
|
|
270
266
|
}
|
|
271
267
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import {IFlattenedRuleSet, IForbiddenRuleType} from '../../types/rule-set.mjs';
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Finds the first rule in the rule set that has name pName,
|
|
3
7
|
* and undefined if no such rule exists/ the rule is an 'allowed'
|
|
@@ -5,9 +9,9 @@
|
|
|
5
9
|
*
|
|
6
10
|
* (this thing probably belongs in a model-like folder and not in utl)
|
|
7
11
|
*
|
|
8
|
-
* @param {
|
|
12
|
+
* @param {IFlattenedRuleSet} pRuleSet - The rule set to search in
|
|
9
13
|
* @param {string} pName - The rule name to look for
|
|
10
|
-
* @return {
|
|
14
|
+
* @return {IForbiddenRuleType|undefined} - a rule (or 'undefined' if nothing found)
|
|
11
15
|
*/
|
|
12
16
|
export function findRuleByName(pRuleSet, pName) {
|
|
13
17
|
const lNamedRules = (pRuleSet?.forbidden ?? []).concat(
|
|
@@ -29,7 +33,7 @@ function ruleHasALicenseLikeAttribute(pRule) {
|
|
|
29
33
|
*
|
|
30
34
|
* Returns false in all other cases
|
|
31
35
|
*
|
|
32
|
-
* @param {
|
|
36
|
+
* @param {IFlattenedRuleSet} pRuleSet
|
|
33
37
|
* @return {boolean}
|
|
34
38
|
*/
|
|
35
39
|
export function ruleSetHasLicenseRule(pRuleSet) {
|
|
@@ -38,9 +42,9 @@ export function ruleSetHasLicenseRule(pRuleSet) {
|
|
|
38
42
|
(pRuleSet?.allowed ?? []).some(ruleHasALicenseLikeAttribute)
|
|
39
43
|
);
|
|
40
44
|
}
|
|
45
|
+
|
|
41
46
|
/**
|
|
42
|
-
*
|
|
43
|
-
* @param {import('../../types/dependency-cruiser').IFlattenedRuleSet} pRuleSet
|
|
47
|
+
* @param {IFlattenedRuleSet} pRuleSet
|
|
44
48
|
* @return {boolean}
|
|
45
49
|
*/
|
|
46
50
|
export function ruleSetHasDeprecationRule(pRuleSet) {
|
package/src/main/cruise.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable no-
|
|
1
|
+
/* eslint-disable no-magic-numbers */
|
|
2
2
|
import { assertCruiseOptionsValid } from "./options/assert-validity.mjs";
|
|
3
3
|
import { normalizeCruiseOptions } from "./options/normalize.mjs";
|
|
4
4
|
import reportWrap from "./report-wrap.mjs";
|
|
@@ -65,7 +65,7 @@ export default async function cruise(
|
|
|
65
65
|
import("#enrich/index.mjs"),
|
|
66
66
|
]);
|
|
67
67
|
|
|
68
|
-
if (
|
|
68
|
+
if (lCruiseOptions.ruleSet) {
|
|
69
69
|
bus.summary("parsing rule set", c(4));
|
|
70
70
|
lCruiseOptions.ruleSet = normalizeRuleSet(
|
|
71
71
|
assertRuleSetValid(lCruiseOptions.ruleSet),
|
package/src/main/format.mjs
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import safeRegex from "safe-regex";
|
|
3
3
|
import { getAvailableReporters } from "#report/index.mjs";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @import { ICruiseOptions, IFormatOptions } from "../../../types/options.mjs";
|
|
7
|
+
*/
|
|
8
|
+
|
|
5
9
|
const MODULE_SYSTEM_LIST_RE = /^(?:(?:cjs|amd|es6|tsd)(?:,|$)){1,4}/gi;
|
|
6
10
|
const VALID_DEPTH_RE = /^\d{1,2}$/g;
|
|
7
11
|
|
|
@@ -30,10 +34,10 @@ function deepMerge(pTarget, pSource) {
|
|
|
30
34
|
|
|
31
35
|
function assertModuleSystemsValid(pModuleSystems) {
|
|
32
36
|
if (
|
|
33
|
-
|
|
37
|
+
pModuleSystems &&
|
|
34
38
|
Array.isArray(pModuleSystems) &&
|
|
35
39
|
!pModuleSystems.every((pModuleSystem) =>
|
|
36
|
-
|
|
40
|
+
pModuleSystem.match(MODULE_SYSTEM_LIST_RE),
|
|
37
41
|
)
|
|
38
42
|
) {
|
|
39
43
|
throw new Error(
|
|
@@ -43,7 +47,7 @@ function assertModuleSystemsValid(pModuleSystems) {
|
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
function assertRegExpSafety(pPattern) {
|
|
46
|
-
if (
|
|
50
|
+
if (pPattern && !safeRegex(pPattern)) {
|
|
47
51
|
throw new Error(
|
|
48
52
|
`The pattern '${pPattern}' will probably run very slowly - cowardly refusing to run.\n`,
|
|
49
53
|
);
|
|
@@ -52,7 +56,7 @@ function assertRegExpSafety(pPattern) {
|
|
|
52
56
|
|
|
53
57
|
function assertOutputTypeValid(pOutputType) {
|
|
54
58
|
if (
|
|
55
|
-
|
|
59
|
+
pOutputType &&
|
|
56
60
|
!getAvailableReporters().includes(pOutputType) &&
|
|
57
61
|
!pOutputType.startsWith("plugin:")
|
|
58
62
|
) {
|
|
@@ -61,7 +65,7 @@ function assertOutputTypeValid(pOutputType) {
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
function assertMaxDepthValid(pDepth) {
|
|
64
|
-
if (
|
|
68
|
+
if (pDepth && !pDepth.toString().match(VALID_DEPTH_RE)) {
|
|
65
69
|
throw new Error(
|
|
66
70
|
`'${pDepth}' is not a valid depth - use an integer between 0 and 99`,
|
|
67
71
|
);
|
|
@@ -73,7 +77,7 @@ function assertFocusDepthValid(pFocusDepth) {
|
|
|
73
77
|
const lMaxFocusDepth = 99;
|
|
74
78
|
|
|
75
79
|
if (
|
|
76
|
-
|
|
80
|
+
pFocusDepth &&
|
|
77
81
|
(Number.isNaN(lFocusDepth) ||
|
|
78
82
|
lFocusDepth < 0 ||
|
|
79
83
|
lFocusDepth > lMaxFocusDepth)
|
|
@@ -96,12 +100,12 @@ function assertPathsSafety(pFilterOption) {
|
|
|
96
100
|
/**
|
|
97
101
|
* @param {any} pOptions
|
|
98
102
|
* @throws {Error}
|
|
99
|
-
* @returns {
|
|
103
|
+
* @returns {ICruiseOptions}
|
|
100
104
|
*/
|
|
101
105
|
export function assertCruiseOptionsValid(pOptions) {
|
|
102
106
|
let lReturnValue = {};
|
|
103
107
|
|
|
104
|
-
if (
|
|
108
|
+
if (pOptions) {
|
|
105
109
|
// necessary because can slip through the cracks when passed as a cli parameter
|
|
106
110
|
assertModuleSystemsValid(pOptions.moduleSystems);
|
|
107
111
|
|
|
@@ -132,7 +136,7 @@ export function assertCruiseOptionsValid(pOptions) {
|
|
|
132
136
|
|
|
133
137
|
/**
|
|
134
138
|
*
|
|
135
|
-
* @param {
|
|
139
|
+
* @param {IFormatOptions} pFormatOptions
|
|
136
140
|
* @throws {Error}
|
|
137
141
|
*/
|
|
138
142
|
export function assertFormatOptionsValid(pFormatOptions) {
|
|
@@ -3,6 +3,14 @@ import { normalizeREProperties } from "../helpers.mjs";
|
|
|
3
3
|
import defaults from "./defaults.mjs";
|
|
4
4
|
import { uniq } from "#utl/array-util.mjs";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @import { ICruiseResult } from "../../../types/cruise-result.mjs";
|
|
8
|
+
* @import { ICruiseOptions, IFormatOptions } from "../../../types/options.mjs";
|
|
9
|
+
* @import { IStrictCruiseOptions, IStrictFormatOptions } from "../../../types/strict-options.mjs";
|
|
10
|
+
* @import { IForbiddenRuleType, IFlattenedRuleSet } from "../../../types/rule-set.mjs";
|
|
11
|
+
* @import { ICacheOptions } from "../../../types/cache-options.mjs";
|
|
12
|
+
*/
|
|
13
|
+
|
|
6
14
|
const DEFAULT_CACHE_FOLDER = "node_modules/.cache/dependency-cruiser";
|
|
7
15
|
const DEFAULT_CACHE_STRATEGY = "metadata";
|
|
8
16
|
|
|
@@ -65,7 +73,7 @@ function normalizeCollapse(pCollapse) {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
function normalizeFocusDepth(pFormatOptions) {
|
|
68
|
-
/** @type
|
|
76
|
+
/** @type {IFormatOptions}*/
|
|
69
77
|
let lFormatOptions = structuredClone(pFormatOptions);
|
|
70
78
|
if (Object.hasOwn(lFormatOptions, "focusDepth")) {
|
|
71
79
|
if (lFormatOptions?.focus) {
|
|
@@ -81,7 +89,7 @@ function normalizeFocusDepth(pFormatOptions) {
|
|
|
81
89
|
|
|
82
90
|
/**
|
|
83
91
|
*
|
|
84
|
-
* @param {
|
|
92
|
+
* @param {IForbiddenRuleType} pRule
|
|
85
93
|
* @returns {boolean}
|
|
86
94
|
*/
|
|
87
95
|
function hasMetricsRule(pRule) {
|
|
@@ -96,7 +104,7 @@ function hasMetricsRule(pRule) {
|
|
|
96
104
|
|
|
97
105
|
/**
|
|
98
106
|
*
|
|
99
|
-
* @param {
|
|
107
|
+
* @param {IFlattenedRuleSet} pRuleSet
|
|
100
108
|
* @returns {boolean}
|
|
101
109
|
*/
|
|
102
110
|
function ruleSetHasMetricsRule(pRuleSet) {
|
|
@@ -109,7 +117,7 @@ function ruleSetHasMetricsRule(pRuleSet) {
|
|
|
109
117
|
|
|
110
118
|
/**
|
|
111
119
|
*
|
|
112
|
-
* @param {
|
|
120
|
+
* @param {ICruiseOptions} pOptions
|
|
113
121
|
* @returns Boolean
|
|
114
122
|
*/
|
|
115
123
|
function reporterShowsMetrics(pOptions) {
|
|
@@ -122,7 +130,7 @@ function reporterShowsMetrics(pOptions) {
|
|
|
122
130
|
/**
|
|
123
131
|
* Determines whether (instability) metrics should be calculated
|
|
124
132
|
*
|
|
125
|
-
* @param {
|
|
133
|
+
* @param {ICruiseOptions} pOptions
|
|
126
134
|
* @returns Boolean
|
|
127
135
|
*/
|
|
128
136
|
function shouldCalculateMetrics(pOptions) {
|
|
@@ -135,8 +143,8 @@ function shouldCalculateMetrics(pOptions) {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
/**
|
|
138
|
-
* @param {string|boolean|Partial<
|
|
139
|
-
* @returns {
|
|
146
|
+
* @param {string|boolean|Partial<ICacheOptions>} pCacheOptions
|
|
147
|
+
* @returns {ICacheOptions}
|
|
140
148
|
*/
|
|
141
149
|
function normalizeCacheOptions(pCacheOptions) {
|
|
142
150
|
let lNormalizedCacheOptions = pCacheOptions;
|
|
@@ -163,12 +171,12 @@ function normalizeCacheOptions(pCacheOptions) {
|
|
|
163
171
|
|
|
164
172
|
/**
|
|
165
173
|
*
|
|
166
|
-
* @param {
|
|
174
|
+
* @param {ICruiseOptions} pOptions
|
|
167
175
|
* @param {string[]} pFileAndDirectoryArray
|
|
168
|
-
* @returns {
|
|
176
|
+
* @returns {IStrictCruiseOptions}
|
|
169
177
|
*/
|
|
170
178
|
export function normalizeCruiseOptions(pOptions, pFileAndDirectoryArray = []) {
|
|
171
|
-
/** @type {
|
|
179
|
+
/** @type {IStrictCruiseOptions} */
|
|
172
180
|
let lReturnValue = {
|
|
173
181
|
baseDir: process.cwd(),
|
|
174
182
|
...defaults,
|
|
@@ -211,14 +219,16 @@ export function normalizeCruiseOptions(pOptions, pFileAndDirectoryArray = []) {
|
|
|
211
219
|
if (lReturnValue.cache) {
|
|
212
220
|
lReturnValue.cache = normalizeCacheOptions(lReturnValue.cache);
|
|
213
221
|
}
|
|
222
|
+
if (lReturnValue.detectJSDocImports) {
|
|
223
|
+
lReturnValue.parser = "tsc";
|
|
224
|
+
}
|
|
214
225
|
|
|
215
226
|
return normalizeFocusDepth(lReturnValue);
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
/**
|
|
219
|
-
*
|
|
220
|
-
* @
|
|
221
|
-
* @returns {import("../../../types/strict-options.js").IStrictFormatOptions}
|
|
230
|
+
* @param {IFormatOptions} pFormatOptions
|
|
231
|
+
* @returns {IStrictFormatOptions}
|
|
222
232
|
*/
|
|
223
233
|
export function normalizeFormatOptions(pFormatOptions) {
|
|
224
234
|
let lFormatOptions = structuredClone(pFormatOptions);
|
package/src/main/report-wrap.mjs
CHANGED
|
@@ -6,10 +6,15 @@ import { compareModules } from "#graph-utl/compare.mjs";
|
|
|
6
6
|
import stripSelfTransitions from "#graph-utl/strip-self-transitions.mjs";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
|
|
9
|
+
* @import { ICruiseResult } from "../../types/cruise-result.mjs";
|
|
10
|
+
* @import { IFormatOptions } from "../../types/options.mjs";
|
|
11
|
+
* @import { IReporterOutput } from "../../types/dependency-cruiser.mjs";
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {ICruiseResult} pResult
|
|
16
|
+
* @param {IFormatOptions} pFormatOptions
|
|
17
|
+
* @returns {ICruiseResult}
|
|
13
18
|
*/
|
|
14
19
|
function reSummarizeResults(pResult, pFormatOptions) {
|
|
15
20
|
let lModules = applyFilters(pResult.modules, pFormatOptions);
|
|
@@ -44,9 +49,9 @@ function getReporterSection(pOutputType) {
|
|
|
44
49
|
|
|
45
50
|
/**
|
|
46
51
|
*
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {
|
|
49
|
-
* @returns {
|
|
52
|
+
* @param {ICruiseResult} pResult result of a previous run of dependency-cruiser
|
|
53
|
+
* @param {IFormatOptions} pFormatOptions
|
|
54
|
+
* @returns {IReporterOutput}
|
|
50
55
|
*/
|
|
51
56
|
export default async function reportWrap(pResult, pFormatOptions) {
|
|
52
57
|
const lReportFunction = await getReporter(pFormatOptions.outputType);
|