dependency-cruiser 10.8.0-beta-1 → 11.0.0
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/package.json +23 -18
- package/src/enrich/derive/metrics/folder.js +9 -0
- package/src/enrich/derive/{folders → metrics}/get-stability-metrics.js +0 -0
- package/src/enrich/derive/{folders → metrics}/module-utl.js +0 -0
- package/src/enrich/derive/metrics/module.js +29 -0
- package/src/enrich/derive/{folders → metrics}/utl.js +5 -0
- package/src/enrich/derive/utl.js +26 -10
- package/src/enrich/enrich-modules.js +5 -0
- package/src/enrich/index.js +2 -2
- package/src/enrich/soften-known-violations.js +4 -2
- package/src/extract/ast-extractors/swc-dependency-visitor.js +14 -2
- package/src/extract/get-dependencies.js +11 -6
- package/src/extract/parse/to-typescript-ast.js +24 -8
- package/src/extract/utl/get-extension.js +9 -5
- package/src/main/report-wrap.js +7 -2
- package/src/meta.js +1 -1
- package/src/report/metrics.js +96 -47
- package/src/schema/configuration.schema.js +19 -0
- package/src/schema/cruise-result.schema.js +20 -0
- package/types/baseline-violations.d.ts +1 -1
- package/types/cruise-result.d.ts +15 -50
- package/types/reporter-options.d.ts +17 -0
- package/types/rule-summary.d.ts +23 -0
- package/types/violations.d.ts +24 -0
- package/src/enrich/derive/folders/index.js +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dependency-cruiser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static analysis",
|
|
@@ -131,13 +131,13 @@
|
|
|
131
131
|
"version": "npm-run-all build depcruise:graph:doc scm:stage"
|
|
132
132
|
},
|
|
133
133
|
"dependencies": {
|
|
134
|
-
"acorn": "8.
|
|
134
|
+
"acorn": "8.6.0",
|
|
135
135
|
"acorn-jsx": "5.3.2",
|
|
136
136
|
"acorn-jsx-walk": "2.0.0",
|
|
137
137
|
"acorn-loose": "8.2.1",
|
|
138
138
|
"acorn-walk": "8.2.0",
|
|
139
|
-
"ajv": "8.
|
|
140
|
-
"chalk": "4.1.2",
|
|
139
|
+
"ajv": "8.8.2",
|
|
140
|
+
"chalk": "^4.1.2",
|
|
141
141
|
"commander": "8.3.0",
|
|
142
142
|
"enhanced-resolve": "5.8.3",
|
|
143
143
|
"figures": "^3.2.0",
|
|
@@ -152,17 +152,17 @@
|
|
|
152
152
|
"semver": "^7.3.5",
|
|
153
153
|
"semver-try-require": "^5.0.1",
|
|
154
154
|
"teamcity-service-messages": "0.1.11",
|
|
155
|
-
"tsconfig-paths-webpack-plugin": "3.5.
|
|
155
|
+
"tsconfig-paths-webpack-plugin": "3.5.2",
|
|
156
156
|
"wrap-ansi": "^7.0.0"
|
|
157
157
|
},
|
|
158
158
|
"devDependencies": {
|
|
159
|
-
"@babel/core": "7.
|
|
160
|
-
"@babel/plugin-transform-modules-commonjs": "7.
|
|
161
|
-
"@babel/preset-typescript": "7.
|
|
162
|
-
"@swc/core": "1.2.
|
|
163
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
164
|
-
"@typescript-eslint/parser": "5.
|
|
165
|
-
"@vue/compiler-sfc": "3.2.
|
|
159
|
+
"@babel/core": "7.16.0",
|
|
160
|
+
"@babel/plugin-transform-modules-commonjs": "7.16.0",
|
|
161
|
+
"@babel/preset-typescript": "7.16.0",
|
|
162
|
+
"@swc/core": "1.2.113",
|
|
163
|
+
"@typescript-eslint/eslint-plugin": "5.4.0",
|
|
164
|
+
"@typescript-eslint/parser": "5.4.0",
|
|
165
|
+
"@vue/compiler-sfc": "3.2.23",
|
|
166
166
|
"c8": "7.10.0",
|
|
167
167
|
"chai": "4.3.4",
|
|
168
168
|
"chai-json-schema": "1.5.1",
|
|
@@ -171,23 +171,23 @@
|
|
|
171
171
|
"eslint-config-moving-meadow": "2.0.9",
|
|
172
172
|
"eslint-config-prettier": "8.3.0",
|
|
173
173
|
"eslint-plugin-budapestian": "3.0.1",
|
|
174
|
-
"eslint-plugin-import": "2.25.
|
|
174
|
+
"eslint-plugin-import": "2.25.3",
|
|
175
175
|
"eslint-plugin-mocha": "9.0.0",
|
|
176
176
|
"eslint-plugin-node": "11.1.0",
|
|
177
177
|
"eslint-plugin-security": "1.4.0",
|
|
178
|
-
"eslint-plugin-unicorn": "
|
|
178
|
+
"eslint-plugin-unicorn": "39.0.0",
|
|
179
179
|
"husky": "^4.3.8",
|
|
180
180
|
"intercept-stdout": "0.1.2",
|
|
181
|
-
"lint-staged": "
|
|
181
|
+
"lint-staged": "12.1.2",
|
|
182
182
|
"mocha": "9.1.3",
|
|
183
183
|
"normalize-newline": "^3.0.0",
|
|
184
184
|
"npm-run-all": "4.1.5",
|
|
185
|
-
"prettier": "2.
|
|
185
|
+
"prettier": "2.5.0",
|
|
186
186
|
"proxyquire": "2.1.3",
|
|
187
187
|
"shx": "0.3.3",
|
|
188
|
-
"svelte": "3.44.
|
|
188
|
+
"svelte": "3.44.2",
|
|
189
189
|
"symlink-dir": "5.0.1",
|
|
190
|
-
"typescript": "4.
|
|
190
|
+
"typescript": "4.5.2",
|
|
191
191
|
"upem": "^7.0.0",
|
|
192
192
|
"vue-template-compiler": "2.6.14",
|
|
193
193
|
"yarn": "1.22.17"
|
|
@@ -199,6 +199,11 @@
|
|
|
199
199
|
"policy": "wanted",
|
|
200
200
|
"because": "some eslint plugins (eslint-plugin-budapestian) are not compatible with eslint 8 yet "
|
|
201
201
|
},
|
|
202
|
+
{
|
|
203
|
+
"package": "chalk",
|
|
204
|
+
"policy": "wanted",
|
|
205
|
+
"because": "version 5 only exports ejs - and we use cjs and don't transpile"
|
|
206
|
+
},
|
|
202
207
|
{
|
|
203
208
|
"package": "figures",
|
|
204
209
|
"policy": "wanted",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const getStabilityMetrics = require("./get-stability-metrics");
|
|
2
|
+
const { shouldDeriveMetrics } = require("./utl");
|
|
3
|
+
|
|
4
|
+
module.exports = function deriveMetrics(pModules, pOptions) {
|
|
5
|
+
if (shouldDeriveMetrics(pOptions)) {
|
|
6
|
+
return { folders: getStabilityMetrics(pModules) };
|
|
7
|
+
}
|
|
8
|
+
return {};
|
|
9
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// const { findModuleByName, clearCache } = require("../utl");
|
|
2
|
+
const { metricsAreCalculable } = require("./module-utl");
|
|
3
|
+
const { shouldDeriveMetrics } = require("./utl");
|
|
4
|
+
|
|
5
|
+
module.exports = function deriveModuleMetrics(pModules, pOptions) {
|
|
6
|
+
if (shouldDeriveMetrics(pOptions)) {
|
|
7
|
+
return pModules.map((pModule) => ({
|
|
8
|
+
...pModule,
|
|
9
|
+
...(metricsAreCalculable(pModule)
|
|
10
|
+
? {
|
|
11
|
+
instability:
|
|
12
|
+
pModule.dependencies.length /
|
|
13
|
+
(pModule.dependents.length + pModule.dependencies.length) || 0,
|
|
14
|
+
}
|
|
15
|
+
: {}),
|
|
16
|
+
}));
|
|
17
|
+
// clearCache();
|
|
18
|
+
// return lModules.map((pModule) => ({
|
|
19
|
+
// ...pModule,
|
|
20
|
+
// dependencies: pModule.dependencies.map((pDependency) => ({
|
|
21
|
+
// ...pDependency,
|
|
22
|
+
// instability:
|
|
23
|
+
// (findModuleByName(lModules, pDependency.resolved) || {})
|
|
24
|
+
// .instability || 0,
|
|
25
|
+
// })),
|
|
26
|
+
// }));
|
|
27
|
+
}
|
|
28
|
+
return pModules;
|
|
29
|
+
};
|
|
@@ -17,7 +17,12 @@ function foldersObject2folderArray(pObject) {
|
|
|
17
17
|
}));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function shouldDeriveMetrics(pOptions) {
|
|
21
|
+
return pOptions.metrics || pOptions.outputType === "metrics";
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
module.exports = {
|
|
21
25
|
getParentFolders,
|
|
22
26
|
foldersObject2folderArray,
|
|
27
|
+
shouldDeriveMetrics,
|
|
23
28
|
};
|
package/src/enrich/derive/utl.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
let gIndexedGraph = null;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Returns the module with attribute pSource, when it exists in the pModuleGraph.
|
|
5
|
+
* Returns undefined when it doesn't.
|
|
6
|
+
*
|
|
7
|
+
* This function uses an indexed cache for efficiency reasons. If you need to
|
|
8
|
+
* call this function consecutively for different module graphs, you can clear
|
|
9
|
+
* this cache with the clearCache function from this module.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('../../../types/cruise-result').IModule[]} pModuleGraph
|
|
12
|
+
* @param {string} pSource
|
|
13
|
+
* @returns {import('../../../types/cruise-result').IModule | undefined}
|
|
14
|
+
*/
|
|
15
|
+
function findModuleByName(pModuleGraph, pSource) {
|
|
16
|
+
if (!gIndexedGraph) {
|
|
17
|
+
gIndexedGraph = new Map(
|
|
18
|
+
pModuleGraph.map((pModule) => [pModule.source, pModule])
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return gIndexedGraph.get(pSource);
|
|
5
22
|
}
|
|
6
23
|
|
|
7
|
-
const findModuleByName = _memoize(
|
|
8
|
-
bareFindModuleByName,
|
|
9
|
-
(_pGraph, pSource) => pSource
|
|
10
|
-
);
|
|
11
|
-
|
|
12
24
|
function isDependent(pResolvedName) {
|
|
13
25
|
return (pModule) =>
|
|
14
26
|
pModule.dependencies.some(
|
|
@@ -17,7 +29,11 @@ function isDependent(pResolvedName) {
|
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
function clearCache() {
|
|
20
|
-
|
|
32
|
+
gIndexedGraph = null;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
module.exports = {
|
|
35
|
+
module.exports = {
|
|
36
|
+
findModuleByName,
|
|
37
|
+
clearCache,
|
|
38
|
+
isDependent,
|
|
39
|
+
};
|
|
@@ -8,6 +8,7 @@ const addDependents = require("./derive/dependents");
|
|
|
8
8
|
const deriveReachable = require("./derive/reachable");
|
|
9
9
|
const addValidations = require("./add-validations");
|
|
10
10
|
const softenKnownViolations = require("./soften-known-violations");
|
|
11
|
+
const deriveModuleMetrics = require("./derive/metrics/module");
|
|
11
12
|
|
|
12
13
|
module.exports = function enrichModules(pModules, pOptions) {
|
|
13
14
|
bus.emit("progress", "analyzing: cycles", { level: busLogLevels.INFO });
|
|
@@ -18,6 +19,10 @@ module.exports = function enrichModules(pModules, pOptions) {
|
|
|
18
19
|
lModules = deriveOrphans(lModules);
|
|
19
20
|
bus.emit("progress", "analyzing: reachables", { level: busLogLevels.INFO });
|
|
20
21
|
lModules = deriveReachable(lModules, pOptions.ruleSet);
|
|
22
|
+
bus.emit("progress", "analyzing: calculating module metrics", {
|
|
23
|
+
level: busLogLevels.INFO,
|
|
24
|
+
});
|
|
25
|
+
lModules = deriveModuleMetrics(lModules, pOptions);
|
|
21
26
|
bus.emit("progress", "analyzing: add focus (if any)", {
|
|
22
27
|
level: busLogLevels.INFO,
|
|
23
28
|
});
|
package/src/enrich/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const enrichModules = require("./enrich-modules");
|
|
2
|
-
const
|
|
2
|
+
const deriveFolderMetrics = require("./derive/metrics/folder.js");
|
|
3
3
|
const summarize = require("./summarize");
|
|
4
4
|
const clearCaches = require("./clear-caches");
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ module.exports = function enrich(pModules, pOptions, pFileAndDirectoryArray) {
|
|
|
10
10
|
clearCaches();
|
|
11
11
|
return {
|
|
12
12
|
modules: lModules,
|
|
13
|
-
...
|
|
13
|
+
...deriveFolderMetrics(lModules, pOptions),
|
|
14
14
|
summary: summarize(lModules, pOptions, pFileAndDirectoryArray),
|
|
15
15
|
};
|
|
16
16
|
};
|
|
@@ -78,7 +78,8 @@ function softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity) {
|
|
|
78
78
|
pRule,
|
|
79
79
|
pModule.source,
|
|
80
80
|
pKnownViolations.filter(
|
|
81
|
-
(pKnownError) =>
|
|
81
|
+
(pKnownError) =>
|
|
82
|
+
pKnownError.from === pKnownError.to && !pKnownError.cycle
|
|
82
83
|
),
|
|
83
84
|
pSoftenedSeverity
|
|
84
85
|
)
|
|
@@ -93,7 +94,8 @@ function softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity) {
|
|
|
93
94
|
pDependency,
|
|
94
95
|
pModule.source,
|
|
95
96
|
pKnownViolations.filter(
|
|
96
|
-
(pKnownError) =>
|
|
97
|
+
(pKnownError) =>
|
|
98
|
+
pKnownError.from !== pKnownError.to || pKnownError.cycle
|
|
97
99
|
),
|
|
98
100
|
pSoftenedSeverity
|
|
99
101
|
)
|
|
@@ -119,7 +119,13 @@ if (VisitorModule) {
|
|
|
119
119
|
// also include the same method, but with the correct spelling.
|
|
120
120
|
visitExportAllDeclration(pNode) {
|
|
121
121
|
this.pushImportExportSource(pNode);
|
|
122
|
-
|
|
122
|
+
/* c8 ignore start */
|
|
123
|
+
if (super.visitExportAllDeclration) {
|
|
124
|
+
return super.visitExportAllDeclration(pNode);
|
|
125
|
+
} else {
|
|
126
|
+
/* c8 ignore stop */
|
|
127
|
+
return super.visitExportAllDeclaration(pNode);
|
|
128
|
+
}
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
/* c8 ignore start */
|
|
@@ -131,7 +137,13 @@ if (VisitorModule) {
|
|
|
131
137
|
// same spelling error as the above - same solution
|
|
132
138
|
visitExportNamedDeclration(pNode) {
|
|
133
139
|
this.pushImportExportSource(pNode);
|
|
134
|
-
|
|
140
|
+
/* c8 ignore start */
|
|
141
|
+
if (super.visitExportNamedDeclration) {
|
|
142
|
+
return super.visitExportNamedDeclration(pNode);
|
|
143
|
+
} else {
|
|
144
|
+
/* c8 ignore stop */
|
|
145
|
+
return super.visitExportNamedDeclaration(pNode);
|
|
146
|
+
}
|
|
135
147
|
}
|
|
136
148
|
/* c8 ignore start */
|
|
137
149
|
visitExportNamedDeclaration(pNode) {
|
|
@@ -21,15 +21,18 @@ function extractFromSwcAST(pOptions, pFileName) {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function extractFromTypeScriptAST(pOptions, pFileName) {
|
|
24
|
+
function extractFromTypeScriptAST(pOptions, pFileName, pTranspileOptions) {
|
|
25
25
|
return extractTypeScriptDeps(
|
|
26
|
-
toTypescriptAST.getASTCached(
|
|
26
|
+
toTypescriptAST.getASTCached(
|
|
27
|
+
path.join(pOptions.baseDir, pFileName),
|
|
28
|
+
pTranspileOptions
|
|
29
|
+
),
|
|
27
30
|
pOptions.exoticRequireStrings
|
|
28
31
|
);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
function isTypeScriptCompatible(pFileName) {
|
|
32
|
-
return [".ts", ".tsx", ".js", ".mjs", ".cjs"].includes(
|
|
35
|
+
return [".ts", ".tsx", ".js", ".mjs", ".cjs", ".vue"].includes(
|
|
33
36
|
path.extname(pFileName)
|
|
34
37
|
);
|
|
35
38
|
}
|
|
@@ -88,9 +91,11 @@ function extractWithTsc(
|
|
|
88
91
|
pFileName,
|
|
89
92
|
pTranspileOptions
|
|
90
93
|
) {
|
|
91
|
-
pDependencies = extractFromTypeScriptAST(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
pDependencies = extractFromTypeScriptAST(
|
|
95
|
+
pCruiseOptions,
|
|
96
|
+
pFileName,
|
|
97
|
+
pTranspileOptions
|
|
98
|
+
).filter((pDep) => pCruiseOptions.moduleSystems.includes(pDep.moduleSystem));
|
|
94
99
|
|
|
95
100
|
if (pCruiseOptions.tsPreCompilationDeps === "specify") {
|
|
96
101
|
pDependencies = detectPreCompilationNess(
|
|
@@ -2,21 +2,28 @@ const fs = require("fs");
|
|
|
2
2
|
const tryRequire = require("semver-try-require");
|
|
3
3
|
const _memoize = require("lodash/memoize");
|
|
4
4
|
const { supportedTranspilers } = require("../../../src/meta.js");
|
|
5
|
+
const transpile = require("../transpile");
|
|
6
|
+
const getExtension = require("../utl/get-extension");
|
|
5
7
|
|
|
6
8
|
const typescript = tryRequire("typescript", supportedTranspilers.typescript);
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Compiles pTypescriptSource into a (typescript) AST
|
|
10
12
|
*
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {
|
|
13
|
-
*
|
|
13
|
+
* @param {object} pFileRecord Record with source code, an extension and a filename
|
|
14
|
+
* @param {any} [pTranspileOptions] options for the transpiler(s) - a tsconfig or
|
|
15
|
+
* a babel config
|
|
14
16
|
* @return {object} - a (typescript) AST
|
|
15
17
|
*/
|
|
16
|
-
function getASTFromSource(
|
|
18
|
+
function getASTFromSource(pFileRecord, pTranspileOptions) {
|
|
19
|
+
let lSource = pFileRecord.source;
|
|
20
|
+
if (pFileRecord.extension === ".vue") {
|
|
21
|
+
lSource = transpile(pFileRecord, pTranspileOptions);
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
return typescript.createSourceFile(
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
pFileRecord.filename || "$internal-file-name",
|
|
26
|
+
lSource,
|
|
20
27
|
typescript.ScriptTarget.Latest,
|
|
21
28
|
false
|
|
22
29
|
);
|
|
@@ -27,10 +34,19 @@ function getASTFromSource(pTypescriptSource, pFileName) {
|
|
|
27
34
|
* AST and returns it
|
|
28
35
|
*
|
|
29
36
|
* @param {string} pFileName - the name of the file to compile
|
|
37
|
+
* @param {any} [pTranspileOptions] options for the transpiler(s) - a tsconfig or
|
|
38
|
+
* a babel config
|
|
30
39
|
* @return {object} - a (typescript) AST
|
|
31
40
|
*/
|
|
32
|
-
function getAST(pFileName) {
|
|
33
|
-
return getASTFromSource(
|
|
41
|
+
function getAST(pFileName, pTranspileOptions) {
|
|
42
|
+
return getASTFromSource(
|
|
43
|
+
{
|
|
44
|
+
source: fs.readFileSync(pFileName, "utf8"),
|
|
45
|
+
extension: getExtension(pFileName),
|
|
46
|
+
filename: pFileName,
|
|
47
|
+
},
|
|
48
|
+
pTranspileOptions
|
|
49
|
+
);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
const getASTCached = _memoize(getAST);
|
|
@@ -5,16 +5,20 @@ const path = require("path");
|
|
|
5
5
|
*
|
|
6
6
|
* Just using path.extname would be fine for most cases,
|
|
7
7
|
* except for coffeescript, where a markdown extension can
|
|
8
|
-
* mean literate coffeescript
|
|
8
|
+
* mean literate coffeescript, and for typescript where
|
|
9
|
+
* .d.ts and .ts are slightly different beasts
|
|
9
10
|
*
|
|
10
11
|
* @param {string} pFileName path to the file to be parsed
|
|
11
12
|
* @return {string} extension
|
|
12
13
|
*/
|
|
13
14
|
module.exports = function getExtensions(pFileName) {
|
|
14
|
-
|
|
15
|
+
if (pFileName.endsWith(".d.ts")) {
|
|
16
|
+
return ".d.ts";
|
|
17
|
+
}
|
|
15
18
|
|
|
16
|
-
if (
|
|
17
|
-
return
|
|
19
|
+
if (pFileName.endsWith(".coffee.md")) {
|
|
20
|
+
return ".coffee.md";
|
|
18
21
|
}
|
|
19
|
-
|
|
22
|
+
|
|
23
|
+
return path.extname(pFileName);
|
|
20
24
|
};
|
package/src/main/report-wrap.js
CHANGED
|
@@ -28,13 +28,18 @@ function reSummarizeResults(pResult, pFormatOptions) {
|
|
|
28
28
|
|
|
29
29
|
module.exports = function reportWrap(pResult, pFormatOptions) {
|
|
30
30
|
const lReportFunction = report.getReporter(pFormatOptions.outputType);
|
|
31
|
+
const lReportOptions = _get(
|
|
32
|
+
pResult,
|
|
33
|
+
`summary.optionsUsed.reporterOptions.${pFormatOptions.outputType}`,
|
|
34
|
+
{}
|
|
35
|
+
);
|
|
31
36
|
|
|
32
37
|
return lReportFunction(
|
|
33
38
|
reSummarizeResults(pResult, pFormatOptions),
|
|
34
39
|
// passing format options here so reporters that read collapse patterns
|
|
35
40
|
// from the result take the one passed in the format options instead
|
|
36
41
|
_has(pFormatOptions, "collapse")
|
|
37
|
-
? { collapsePattern: pFormatOptions.collapse }
|
|
38
|
-
:
|
|
42
|
+
? { ...lReportOptions, collapsePattern: pFormatOptions.collapse }
|
|
43
|
+
: lReportOptions
|
|
39
44
|
);
|
|
40
45
|
};
|
package/src/meta.js
CHANGED
package/src/report/metrics.js
CHANGED
|
@@ -1,62 +1,110 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { EOL } = require("os");
|
|
2
2
|
const chalk = require("chalk");
|
|
3
3
|
|
|
4
4
|
const DECIMAL_BASE = 10;
|
|
5
5
|
const METRIC_WIDTH = 4;
|
|
6
6
|
const INSTABILITY_DECIMALS = 2;
|
|
7
7
|
const YADDUM = DECIMAL_BASE ** INSTABILITY_DECIMALS;
|
|
8
|
+
const COMPONENT_HEADER = "name";
|
|
8
9
|
|
|
9
|
-
function
|
|
10
|
+
function getHeader(pMaxNameWidth) {
|
|
11
|
+
return `${COMPONENT_HEADER.padEnd(pMaxNameWidth)} ${"N".padStart(
|
|
12
|
+
METRIC_WIDTH + 1
|
|
13
|
+
)} ${"Ca".padStart(METRIC_WIDTH + 1)} ${"Ce".padStart(
|
|
14
|
+
METRIC_WIDTH + 1
|
|
15
|
+
)} ${"I".padEnd(METRIC_WIDTH + 1)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getDemarcationLine(pMaxNameWidth) {
|
|
19
|
+
return `${"-".repeat(pMaxNameWidth)} ${"-".repeat(
|
|
20
|
+
METRIC_WIDTH + 1
|
|
21
|
+
)} ${"-".repeat(METRIC_WIDTH + 1)} ${"-".repeat(
|
|
22
|
+
METRIC_WIDTH + 1
|
|
23
|
+
)} ${"-".repeat(METRIC_WIDTH + 1)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getMetricsTable(pMetrics, pMaxNameWidth) {
|
|
27
|
+
return pMetrics.map(
|
|
28
|
+
({
|
|
29
|
+
name,
|
|
30
|
+
moduleCount,
|
|
31
|
+
afferentCouplings,
|
|
32
|
+
efferentCouplings,
|
|
33
|
+
instability,
|
|
34
|
+
}) =>
|
|
35
|
+
`${name.padEnd(pMaxNameWidth, " ")} ${moduleCount
|
|
36
|
+
.toString(DECIMAL_BASE)
|
|
37
|
+
.padStart(METRIC_WIDTH)} ${afferentCouplings
|
|
38
|
+
.toString(DECIMAL_BASE)
|
|
39
|
+
.padStart(METRIC_WIDTH)} ${efferentCouplings
|
|
40
|
+
.toString(DECIMAL_BASE)
|
|
41
|
+
.padStart(METRIC_WIDTH)} ${(Math.round(YADDUM * instability) / YADDUM)
|
|
42
|
+
.toString(DECIMAL_BASE)
|
|
43
|
+
.padEnd(METRIC_WIDTH)}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function metricifyModule({ source, dependents, dependencies, instability }) {
|
|
48
|
+
return {
|
|
49
|
+
name: source,
|
|
50
|
+
moduleCount: 1,
|
|
51
|
+
afferentCouplings: dependents.length,
|
|
52
|
+
efferentCouplings: dependencies.length,
|
|
53
|
+
instability,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function metricsAreCalculable(pModule) {
|
|
58
|
+
return Object.prototype.hasOwnProperty.call(pModule, "instability");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function orderByNumber(pAttributeName) {
|
|
62
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
63
|
+
return (pLeft, pRight) => pRight[pAttributeName] - pLeft[pAttributeName];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function orderByString(pAttributeName) {
|
|
67
|
+
return (pLeft, pRight) =>
|
|
68
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
69
|
+
pLeft[pAttributeName].localeCompare(pRight[pAttributeName]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function transformMetricsToTable(
|
|
73
|
+
{ modules, folders },
|
|
74
|
+
{ orderBy, hideFolders, hideModules }
|
|
75
|
+
) {
|
|
10
76
|
// TODO: should probably use a table module for this (i.e. text-table)
|
|
11
|
-
// to simplify this code
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
77
|
+
// to simplify this code
|
|
78
|
+
let lComponents = [];
|
|
79
|
+
lComponents = lComponents.concat(hideFolders ? [] : folders);
|
|
80
|
+
lComponents = lComponents.concat(
|
|
81
|
+
hideModules ? [] : modules.filter(metricsAreCalculable).map(metricifyModule)
|
|
82
|
+
);
|
|
83
|
+
const lMaxNameWidth = lComponents
|
|
84
|
+
.map(({ name }) => name.length)
|
|
85
|
+
.concat(COMPONENT_HEADER.length)
|
|
15
86
|
.sort((pLeft, pRight) => pLeft - pRight)
|
|
16
87
|
.pop();
|
|
17
88
|
|
|
18
|
-
return [
|
|
19
|
-
|
|
20
|
-
`${"folder".padEnd(lMaxNameWidth)} ${"N".padStart(
|
|
21
|
-
METRIC_WIDTH + 1
|
|
22
|
-
)} ${"Ca".padStart(METRIC_WIDTH + 1)} ${"Ce".padStart(
|
|
23
|
-
METRIC_WIDTH + 1
|
|
24
|
-
)} ${"I".padEnd(METRIC_WIDTH + 1)}`
|
|
25
|
-
),
|
|
26
|
-
]
|
|
27
|
-
.concat(
|
|
28
|
-
`${"-".repeat(lMaxNameWidth)} ${"-".repeat(
|
|
29
|
-
METRIC_WIDTH + 1
|
|
30
|
-
)} ${"-".repeat(METRIC_WIDTH + 1)} ${"-".repeat(
|
|
31
|
-
METRIC_WIDTH + 1
|
|
32
|
-
)} ${"-".repeat(METRIC_WIDTH + 1)}`
|
|
33
|
-
)
|
|
89
|
+
return [chalk.bold(getHeader(lMaxNameWidth))]
|
|
90
|
+
.concat(getDemarcationLine(lMaxNameWidth))
|
|
34
91
|
.concat(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.padStart(METRIC_WIDTH)} ${pMetric.afferentCouplings
|
|
42
|
-
.toString(DECIMAL_BASE)
|
|
43
|
-
.padStart(METRIC_WIDTH)} ${pMetric.efferentCouplings
|
|
44
|
-
.toString(DECIMAL_BASE)
|
|
45
|
-
.padStart(METRIC_WIDTH)} ${(
|
|
46
|
-
Math.round(YADDUM * pMetric.instability) / YADDUM
|
|
47
|
-
)
|
|
48
|
-
.toString(DECIMAL_BASE)
|
|
49
|
-
.padEnd(METRIC_WIDTH)}`;
|
|
50
|
-
})
|
|
92
|
+
getMetricsTable(
|
|
93
|
+
lComponents
|
|
94
|
+
.sort(orderByString("name"))
|
|
95
|
+
.sort(orderByNumber(orderBy || "instability")),
|
|
96
|
+
lMaxNameWidth
|
|
97
|
+
)
|
|
51
98
|
)
|
|
52
|
-
.join(
|
|
53
|
-
.concat(
|
|
99
|
+
.join(EOL)
|
|
100
|
+
.concat(EOL);
|
|
54
101
|
}
|
|
55
102
|
|
|
56
103
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
104
|
+
* returns stability metrics of modules & folders in an ascii table
|
|
105
|
+
*
|
|
106
|
+
* Potential future features:
|
|
107
|
+
* - additional output formats (csv?, html?)
|
|
60
108
|
*
|
|
61
109
|
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult -
|
|
62
110
|
* the output of a dependency-cruise adhering to dependency-cruiser's
|
|
@@ -65,17 +113,18 @@ function transformMetricsToTable(pMetrics) {
|
|
|
65
113
|
* output: some metrics on folders and dependencies
|
|
66
114
|
* exitCode: 0
|
|
67
115
|
*/
|
|
68
|
-
module.exports = (pCruiseResult) => {
|
|
116
|
+
module.exports = (pCruiseResult, pReporterOptions) => {
|
|
117
|
+
const lReporterOptions = pReporterOptions || {};
|
|
69
118
|
if (pCruiseResult.folders) {
|
|
70
119
|
return {
|
|
71
|
-
output: transformMetricsToTable(pCruiseResult
|
|
120
|
+
output: transformMetricsToTable(pCruiseResult, lReporterOptions),
|
|
72
121
|
exitCode: 0,
|
|
73
122
|
};
|
|
74
123
|
} else {
|
|
75
124
|
return {
|
|
76
125
|
output:
|
|
77
|
-
`${
|
|
78
|
-
` the '--metrics' command line option should fix that.${
|
|
126
|
+
`${EOL}ERROR: The cruise result didn't contain any metrics - re-running the cruise with${EOL}` +
|
|
127
|
+
` the '--metrics' command line option should fix that.${EOL}${EOL}`,
|
|
79
128
|
exitCode: 1,
|
|
80
129
|
};
|
|
81
130
|
}
|
|
@@ -394,6 +394,7 @@ module.exports = {
|
|
|
394
394
|
dot: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
395
395
|
ddot: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
396
396
|
flat: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
397
|
+
metrics: { $ref: "#/definitions/MetricsReporterOptionsType" },
|
|
397
398
|
},
|
|
398
399
|
},
|
|
399
400
|
AnonReporterOptionsType: {
|
|
@@ -401,6 +402,24 @@ module.exports = {
|
|
|
401
402
|
additionalProperties: false,
|
|
402
403
|
properties: { wordlist: { type: "array", items: { type: "string" } } },
|
|
403
404
|
},
|
|
405
|
+
MetricsReporterOptionsType: {
|
|
406
|
+
type: "object",
|
|
407
|
+
additionalProperties: false,
|
|
408
|
+
properties: {
|
|
409
|
+
orderBy: {
|
|
410
|
+
type: "string",
|
|
411
|
+
enum: [
|
|
412
|
+
"instability",
|
|
413
|
+
"moduleCount",
|
|
414
|
+
"afferentCouplings",
|
|
415
|
+
"efferentCouplings",
|
|
416
|
+
"name",
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
hideModules: { type: "boolean" },
|
|
420
|
+
hideFolders: { type: "boolean" },
|
|
421
|
+
},
|
|
422
|
+
},
|
|
404
423
|
DotReporterOptionsType: {
|
|
405
424
|
type: "object",
|
|
406
425
|
additionalProperties: false,
|
|
@@ -47,6 +47,7 @@ module.exports = {
|
|
|
47
47
|
items: { $ref: "#/definitions/RuleSummaryType" },
|
|
48
48
|
},
|
|
49
49
|
consolidated: { type: "boolean" },
|
|
50
|
+
instability: { type: "number" },
|
|
50
51
|
},
|
|
51
52
|
},
|
|
52
53
|
ReachableType: {
|
|
@@ -570,6 +571,7 @@ module.exports = {
|
|
|
570
571
|
dot: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
571
572
|
ddot: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
572
573
|
flat: { $ref: "#/definitions/DotReporterOptionsType" },
|
|
574
|
+
metrics: { $ref: "#/definitions/MetricsReporterOptionsType" },
|
|
573
575
|
},
|
|
574
576
|
},
|
|
575
577
|
AnonReporterOptionsType: {
|
|
@@ -577,6 +579,24 @@ module.exports = {
|
|
|
577
579
|
additionalProperties: false,
|
|
578
580
|
properties: { wordlist: { type: "array", items: { type: "string" } } },
|
|
579
581
|
},
|
|
582
|
+
MetricsReporterOptionsType: {
|
|
583
|
+
type: "object",
|
|
584
|
+
additionalProperties: false,
|
|
585
|
+
properties: {
|
|
586
|
+
orderBy: {
|
|
587
|
+
type: "string",
|
|
588
|
+
enum: [
|
|
589
|
+
"instability",
|
|
590
|
+
"moduleCount",
|
|
591
|
+
"afferentCouplings",
|
|
592
|
+
"efferentCouplings",
|
|
593
|
+
"name",
|
|
594
|
+
],
|
|
595
|
+
},
|
|
596
|
+
hideModules: { type: "boolean" },
|
|
597
|
+
hideFolders: { type: "boolean" },
|
|
598
|
+
},
|
|
599
|
+
},
|
|
580
600
|
DotReporterOptionsType: {
|
|
581
601
|
type: "object",
|
|
582
602
|
additionalProperties: false,
|
package/types/cruise-result.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { ICruiseOptions } from "./options";
|
|
2
2
|
import { IFlattenedRuleSet } from "./rule-set";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
SeverityType,
|
|
7
|
-
ProtocolType,
|
|
8
|
-
} from "./shared-types";
|
|
3
|
+
import { DependencyType, ModuleSystemType, ProtocolType } from "./shared-types";
|
|
4
|
+
import { IViolation } from "./violations";
|
|
5
|
+
import { IRuleSummary } from "./rule-summary";
|
|
9
6
|
|
|
10
7
|
export interface ICruiseResult {
|
|
11
8
|
/**
|
|
@@ -106,6 +103,15 @@ export interface IModule {
|
|
|
106
103
|
* purposes - it will not be present after a regular cruise.
|
|
107
104
|
*/
|
|
108
105
|
consolidated?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* "number of dependents/ (number of dependents + number of dependencies)
|
|
108
|
+
* A measure for how stable the module is; ranging between 0 (completely
|
|
109
|
+
* stable module) to 1 (completely instable module). Derived from Uncle
|
|
110
|
+
* Bob's instability metric - but applied to a single module instead of
|
|
111
|
+
* to a group of them. This attribute is only present when dependency-cruiser
|
|
112
|
+
* was asked to calculate metrics.
|
|
113
|
+
*/
|
|
114
|
+
instability?: number;
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
export interface IDependency {
|
|
@@ -217,27 +223,6 @@ export interface IDependency {
|
|
|
217
223
|
valid: boolean;
|
|
218
224
|
}
|
|
219
225
|
|
|
220
|
-
/**
|
|
221
|
-
* If there was a rule violation (valid === false), this object contains the name of the
|
|
222
|
-
* rule and severity of violating it.
|
|
223
|
-
*/
|
|
224
|
-
export interface IRuleSummary {
|
|
225
|
-
/**
|
|
226
|
-
* The (short, eslint style) name of the violated rule. Typically something like
|
|
227
|
-
* 'no-core-punycode' or 'no-outside-deps'.
|
|
228
|
-
*/
|
|
229
|
-
name: string;
|
|
230
|
-
/**
|
|
231
|
-
* How severe a violation of a rule is. The 'error' severity will make some reporters return
|
|
232
|
-
* a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
|
|
233
|
-
* use that. The absence of the 'ignore' severity here is by design; ignored rules don't
|
|
234
|
-
* show up in the output.
|
|
235
|
-
*
|
|
236
|
-
* Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
|
|
237
|
-
*/
|
|
238
|
-
severity: SeverityType;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
226
|
export interface IReachable {
|
|
242
227
|
/**
|
|
243
228
|
* The name of the rule where the reachability was defined
|
|
@@ -380,29 +365,6 @@ export interface IWebpackConfig {
|
|
|
380
365
|
*/
|
|
381
366
|
export type WebpackEnvType = { [key: string]: any } | string;
|
|
382
367
|
|
|
383
|
-
export interface IViolation {
|
|
384
|
-
/**
|
|
385
|
-
* The violated rule
|
|
386
|
-
*/
|
|
387
|
-
rule: IRuleSummary;
|
|
388
|
-
/**
|
|
389
|
-
* The from part of the dependency this violation is about
|
|
390
|
-
*/
|
|
391
|
-
from: string;
|
|
392
|
-
/**
|
|
393
|
-
* The to part of the dependency this violation is about
|
|
394
|
-
*/
|
|
395
|
-
to: string;
|
|
396
|
-
/**
|
|
397
|
-
* The circular path if the violation is about circularity
|
|
398
|
-
*/
|
|
399
|
-
cycle?: string[];
|
|
400
|
-
/**
|
|
401
|
-
* The path from the from to the to if the violation is transitive
|
|
402
|
-
*/
|
|
403
|
-
via?: string[];
|
|
404
|
-
}
|
|
405
|
-
|
|
406
368
|
export interface IFolder {
|
|
407
369
|
/**
|
|
408
370
|
* The name of the folder. FOlder names are normalized to posix (so
|
|
@@ -446,3 +408,6 @@ export interface IFolder {
|
|
|
446
408
|
*/
|
|
447
409
|
instability?: number;
|
|
448
410
|
}
|
|
411
|
+
|
|
412
|
+
export * from "./violations";
|
|
413
|
+
export * from "./rule-summary";
|
|
@@ -21,6 +21,10 @@ export interface IReporterOptions {
|
|
|
21
21
|
* Options to tweak the output of the flat /fdot reporter
|
|
22
22
|
*/
|
|
23
23
|
flat?: IDotReporterOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Options to tweak the output of the metrics reporter
|
|
26
|
+
*/
|
|
27
|
+
metrics?: IMetricsReporterOptions;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export interface IReporterFiltersType {
|
|
@@ -96,3 +100,16 @@ export interface IDotThemeEntry {
|
|
|
96
100
|
criteria: any;
|
|
97
101
|
attributes: any;
|
|
98
102
|
}
|
|
103
|
+
|
|
104
|
+
export interface IMetricsReporterOptions {
|
|
105
|
+
hideModules?: boolean;
|
|
106
|
+
hideFolders?: boolean;
|
|
107
|
+
oderBy?: MetricsOrderByType;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type MetricsOrderByType =
|
|
111
|
+
| "instability"
|
|
112
|
+
| "moduleCount"
|
|
113
|
+
| "afferentCouplings"
|
|
114
|
+
| "efferentCouplings"
|
|
115
|
+
| "name";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SeverityType } from "./shared-types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* If there was a rule violation (valid === false), this object contains the name of the
|
|
5
|
+
* rule and severity of violating it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface IRuleSummary {
|
|
9
|
+
/**
|
|
10
|
+
* The (short, eslint style) name of the violated rule. Typically something like
|
|
11
|
+
* 'no-core-punycode' or 'no-outside-deps'.
|
|
12
|
+
*/
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* How severe a violation of a rule is. The 'error' severity will make some reporters return
|
|
16
|
+
* a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
|
|
17
|
+
* use that. The absence of the 'ignore' severity here is by design; ignored rules don't
|
|
18
|
+
* show up in the output.
|
|
19
|
+
*
|
|
20
|
+
* Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
|
|
21
|
+
*/
|
|
22
|
+
severity: SeverityType;
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IRuleSummary } from "./rule-summary";
|
|
2
|
+
|
|
3
|
+
export interface IViolation {
|
|
4
|
+
/**
|
|
5
|
+
* The violated rule
|
|
6
|
+
*/
|
|
7
|
+
rule: IRuleSummary;
|
|
8
|
+
/**
|
|
9
|
+
* The from part of the dependency this violation is about
|
|
10
|
+
*/
|
|
11
|
+
from: string;
|
|
12
|
+
/**
|
|
13
|
+
* The to part of the dependency this violation is about
|
|
14
|
+
*/
|
|
15
|
+
to: string;
|
|
16
|
+
/**
|
|
17
|
+
* The circular path if the violation is about circularity
|
|
18
|
+
*/
|
|
19
|
+
cycle?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* The path from the from to the to if the violation is transitive
|
|
22
|
+
*/
|
|
23
|
+
via?: string[];
|
|
24
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const getStabilityMetrics = require("./get-stability-metrics");
|
|
2
|
-
|
|
3
|
-
function shouldDeriveFolders(pOptions) {
|
|
4
|
-
return pOptions.metrics || pOptions.outputType === "metrics";
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
module.exports = function deriveFolders(pModules, pOptions) {
|
|
8
|
-
if (shouldDeriveFolders(pOptions)) {
|
|
9
|
-
return { folders: getStabilityMetrics(pModules) };
|
|
10
|
-
}
|
|
11
|
-
return {};
|
|
12
|
-
};
|