dependency-cruiser 10.4.0 → 10.8.0-beta-1
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/dependency-cruise.js +1 -0
- package/package.json +25 -17
- package/src/cli/normalize-cli-options.js +1 -0
- package/src/cli/validate-node-environment.js +2 -2
- package/src/enrich/add-validations.js +3 -2
- package/src/enrich/derive/dependents/index.js +6 -1
- package/src/enrich/derive/folders/get-stability-metrics.js +97 -0
- package/src/enrich/derive/folders/index.js +12 -0
- package/src/enrich/derive/folders/module-utl.js +27 -0
- package/src/enrich/derive/folders/utl.js +23 -0
- package/src/enrich/index.js +2 -0
- package/src/enrich/summarize/summarize-options.js +5 -0
- package/src/extract/transpile/meta.js +1 -0
- package/src/extract/transpile/vue-template-wrap.js +49 -6
- package/src/main/options/normalize.js +3 -3
- package/src/meta.js +2 -1
- package/src/report/anon/anonymize-path.js +2 -1
- package/src/report/anon/random-string.js +5 -10
- package/src/report/index.js +2 -0
- package/src/report/metrics.js +82 -0
- package/src/report/teamcity.js +49 -14
- package/src/schema/configuration.schema.js +4 -0
- package/src/schema/cruise-result.schema.js +21 -0
- package/src/utl/wrap-and-indent.js +2 -2
- package/src/validate/match-dependency-rule.js +2 -0
- package/src/validate/matchers.js +8 -0
- package/types/cruise-result.d.ts +53 -0
- package/types/restrictions.d.ts +5 -0
- package/types/shared-types.d.ts +1 -0
package/bin/dependency-cruise.js
CHANGED
|
@@ -28,6 +28,7 @@ try {
|
|
|
28
28
|
"-T, --output-type <type>",
|
|
29
29
|
"output type; e.g. err, err-html, dot, ddot, archi, flat, baseline or json\n(default: err)"
|
|
30
30
|
)
|
|
31
|
+
.option("-m, --metrics", "calculate stability metrics", false)
|
|
31
32
|
.option(
|
|
32
33
|
"-f, --output-to <file>",
|
|
33
34
|
"file to write output to; - for stdout",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dependency-cruiser",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.8.0-beta-1",
|
|
4
4
|
"description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static analysis",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"acorn-walk": "8.2.0",
|
|
139
139
|
"ajv": "8.6.3",
|
|
140
140
|
"chalk": "4.1.2",
|
|
141
|
-
"commander": "8.
|
|
141
|
+
"commander": "8.3.0",
|
|
142
142
|
"enhanced-resolve": "5.8.3",
|
|
143
143
|
"figures": "^3.2.0",
|
|
144
144
|
"get-stream": "^6.0.1",
|
|
@@ -159,9 +159,10 @@
|
|
|
159
159
|
"@babel/core": "7.15.8",
|
|
160
160
|
"@babel/plugin-transform-modules-commonjs": "7.15.4",
|
|
161
161
|
"@babel/preset-typescript": "7.15.0",
|
|
162
|
-
"@swc/core": "1.2.
|
|
163
|
-
"@typescript-eslint/eslint-plugin": "
|
|
164
|
-
"@typescript-eslint/parser": "
|
|
162
|
+
"@swc/core": "1.2.105",
|
|
163
|
+
"@typescript-eslint/eslint-plugin": "5.2.0",
|
|
164
|
+
"@typescript-eslint/parser": "5.2.0",
|
|
165
|
+
"@vue/compiler-sfc": "3.2.20",
|
|
165
166
|
"c8": "7.10.0",
|
|
166
167
|
"chai": "4.3.4",
|
|
167
168
|
"chai-json-schema": "1.5.1",
|
|
@@ -169,33 +170,34 @@
|
|
|
169
170
|
"eslint": "^7.32.0",
|
|
170
171
|
"eslint-config-moving-meadow": "2.0.9",
|
|
171
172
|
"eslint-config-prettier": "8.3.0",
|
|
172
|
-
"eslint-plugin-budapestian": "
|
|
173
|
-
"eslint-plugin-import": "2.
|
|
173
|
+
"eslint-plugin-budapestian": "3.0.1",
|
|
174
|
+
"eslint-plugin-import": "2.25.2",
|
|
174
175
|
"eslint-plugin-mocha": "9.0.0",
|
|
175
176
|
"eslint-plugin-node": "11.1.0",
|
|
176
177
|
"eslint-plugin-security": "1.4.0",
|
|
177
|
-
"eslint-plugin-unicorn": "
|
|
178
|
+
"eslint-plugin-unicorn": "37.0.1",
|
|
178
179
|
"husky": "^4.3.8",
|
|
179
180
|
"intercept-stdout": "0.1.2",
|
|
180
|
-
"lint-staged": "11.2.
|
|
181
|
-
"mocha": "9.1.
|
|
182
|
-
"normalize-newline": "
|
|
181
|
+
"lint-staged": "11.2.6",
|
|
182
|
+
"mocha": "9.1.3",
|
|
183
|
+
"normalize-newline": "^3.0.0",
|
|
183
184
|
"npm-run-all": "4.1.5",
|
|
184
185
|
"prettier": "2.4.1",
|
|
186
|
+
"proxyquire": "2.1.3",
|
|
185
187
|
"shx": "0.3.3",
|
|
186
|
-
"svelte": "3.
|
|
188
|
+
"svelte": "3.44.0",
|
|
187
189
|
"symlink-dir": "5.0.1",
|
|
188
|
-
"typescript": "4.4.
|
|
190
|
+
"typescript": "4.4.4",
|
|
189
191
|
"upem": "^7.0.0",
|
|
190
192
|
"vue-template-compiler": "2.6.14",
|
|
191
|
-
"yarn": "1.22.
|
|
193
|
+
"yarn": "1.22.17"
|
|
192
194
|
},
|
|
193
195
|
"upem": {
|
|
194
196
|
"policies": [
|
|
195
197
|
{
|
|
196
198
|
"package": "eslint",
|
|
197
199
|
"policy": "wanted",
|
|
198
|
-
"because": "some eslint plugins (
|
|
200
|
+
"because": "some eslint plugins (eslint-plugin-budapestian) are not compatible with eslint 8 yet "
|
|
199
201
|
},
|
|
200
202
|
{
|
|
201
203
|
"package": "figures",
|
|
@@ -212,6 +214,11 @@
|
|
|
212
214
|
"policy": "wanted",
|
|
213
215
|
"because": "version 5 only exports ejs - and we use cjs and don't transpile"
|
|
214
216
|
},
|
|
217
|
+
{
|
|
218
|
+
"package": "normalize-newline",
|
|
219
|
+
"policy": "wanted",
|
|
220
|
+
"because": "version 4 only exports ejs - and we use cjs and don't transpile (this only used in unit tests - but one of'em is a cjs one ...)"
|
|
221
|
+
},
|
|
215
222
|
{
|
|
216
223
|
"package": "wrap-ansi",
|
|
217
224
|
"policy": "wanted",
|
|
@@ -246,7 +253,8 @@
|
|
|
246
253
|
"svelte": ">=3.0.0 <4.0.0",
|
|
247
254
|
"swc": ">=1.0.0 <2.0.0",
|
|
248
255
|
"typescript": ">=2.0.0 <5.0.0",
|
|
249
|
-
"vue-template-compiler": ">=2.0.0 <3.0.0"
|
|
256
|
+
"vue-template-compiler": ">=2.0.0 <3.0.0",
|
|
257
|
+
"@vue/compiler-sfc": ">=3.0.0 <4.0.0"
|
|
250
258
|
},
|
|
251
259
|
"husky": {
|
|
252
260
|
"hooks": {
|
|
@@ -264,7 +272,7 @@
|
|
|
264
272
|
"eslint --fix",
|
|
265
273
|
"prettier --write"
|
|
266
274
|
],
|
|
267
|
-
"test/**/*.{utl,spec}.js": [
|
|
275
|
+
"test/**/*.{utl,spec}.{mjs,js}": [
|
|
268
276
|
"eslint --fix",
|
|
269
277
|
"prettier --write"
|
|
270
278
|
],
|
|
@@ -5,7 +5,7 @@ module.exports = function validateNodeEnvironment(pNodeVersion) {
|
|
|
5
5
|
// not using default parameter here because the check should run
|
|
6
6
|
// run on node 4 as well
|
|
7
7
|
const lNodeVersion = pNodeVersion || process.versions.node;
|
|
8
|
-
const
|
|
8
|
+
const lVersionError = `\nERROR: Your node version (${lNodeVersion}) is not supported. dependency-cruiser
|
|
9
9
|
follows the node.js release cycle and runs on these node versions:
|
|
10
10
|
${engines.node}
|
|
11
11
|
See https://nodejs.org/en/about/releases/ for details.
|
|
@@ -13,6 +13,6 @@ module.exports = function validateNodeEnvironment(pNodeVersion) {
|
|
|
13
13
|
`;
|
|
14
14
|
|
|
15
15
|
if (!satisfies(lNodeVersion, engines.node)) {
|
|
16
|
-
throw new Error(
|
|
16
|
+
throw new Error(lVersionError);
|
|
17
17
|
}
|
|
18
18
|
};
|
|
@@ -20,11 +20,12 @@ function addDependencyViolations(pModule, pDependency, pRuleSet, pValidate) {
|
|
|
20
20
|
* of them added whether or not it is
|
|
21
21
|
* valid and if not which rules were violated
|
|
22
22
|
*/
|
|
23
|
-
module.exports = (pModules, pRuleSet, pValidate) =>
|
|
24
|
-
pModules.map((pModule) => ({
|
|
23
|
+
module.exports = (pModules, pRuleSet, pValidate) => {
|
|
24
|
+
return pModules.map((pModule) => ({
|
|
25
25
|
...pModule,
|
|
26
26
|
...(pValidate ? validate.module(pRuleSet, pModule) : { valid: true }),
|
|
27
27
|
dependencies: pModule.dependencies.map((pDependency) =>
|
|
28
28
|
addDependencyViolations(pModule, pDependency, pRuleSet, pValidate)
|
|
29
29
|
),
|
|
30
30
|
}));
|
|
31
|
+
};
|
|
@@ -11,7 +11,12 @@ function hasDependentsRule(pOptions) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function shouldAddDependents(pOptions) {
|
|
14
|
-
return
|
|
14
|
+
return (
|
|
15
|
+
Boolean(pOptions.forceDeriveDependents) ||
|
|
16
|
+
Boolean(pOptions.metrics) ||
|
|
17
|
+
pOptions.outputType === "metrics" ||
|
|
18
|
+
hasDependentsRule(pOptions)
|
|
19
|
+
);
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
module.exports = function addDependents(pModules, pOptions) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* eslint-disable security/detect-object-injection */
|
|
2
|
+
const path = require("path").posix;
|
|
3
|
+
const { foldersObject2folderArray, getParentFolders } = require("./utl");
|
|
4
|
+
const {
|
|
5
|
+
getAfferentCouplings,
|
|
6
|
+
getEfferentCouplings,
|
|
7
|
+
metricsAreCalculable,
|
|
8
|
+
} = require("./module-utl");
|
|
9
|
+
|
|
10
|
+
function upsertCouplings(pAllDependents, pNewDependents) {
|
|
11
|
+
pNewDependents.forEach((pNewDependent) => {
|
|
12
|
+
pAllDependents[pNewDependent] = pAllDependents[pNewDependent] || {
|
|
13
|
+
count: 0,
|
|
14
|
+
};
|
|
15
|
+
pAllDependents[pNewDependent].count += 1;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function upsertFolderAttributes(pAllMetrics, pModule, pDirname) {
|
|
20
|
+
pAllMetrics[pDirname] = pAllMetrics[pDirname] || {
|
|
21
|
+
dependencies: {},
|
|
22
|
+
dependents: {},
|
|
23
|
+
moduleCount: 0,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
upsertCouplings(
|
|
27
|
+
pAllMetrics[pDirname].dependents,
|
|
28
|
+
getAfferentCouplings(pModule, pDirname)
|
|
29
|
+
);
|
|
30
|
+
upsertCouplings(
|
|
31
|
+
pAllMetrics[pDirname].dependencies,
|
|
32
|
+
getEfferentCouplings(pModule, pDirname).map(
|
|
33
|
+
(pDependency) => pDependency.resolved
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
pAllMetrics[pDirname].moduleCount += 1;
|
|
37
|
+
|
|
38
|
+
return pAllMetrics;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function orderFolderMetrics(pLeftMetric, pRightMetric) {
|
|
42
|
+
// return pLeft.name.localeCompare(pRight.name);
|
|
43
|
+
// For intended use in a table it's probably more useful to sort by
|
|
44
|
+
// instability. Might need to be either configurable or flexible
|
|
45
|
+
// in the output, though
|
|
46
|
+
return pRightMetric.instability - pLeftMetric.instability;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sumCounts(pAll, pCurrent) {
|
|
50
|
+
return pAll + pCurrent.count;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getFolderLevelCouplings(pCouplingArray) {
|
|
54
|
+
return Array.from(
|
|
55
|
+
new Set(
|
|
56
|
+
pCouplingArray.map((pCoupling) =>
|
|
57
|
+
path.dirname(pCoupling.name) === "."
|
|
58
|
+
? pCoupling.name
|
|
59
|
+
: path.dirname(pCoupling.name)
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function calculateFolderMetrics(pFolder) {
|
|
66
|
+
const lModuleDependents = foldersObject2folderArray(pFolder.dependents);
|
|
67
|
+
const lModuleDependencies = foldersObject2folderArray(pFolder.dependencies);
|
|
68
|
+
const lAfferentCouplings = lModuleDependents.reduce(sumCounts, 0);
|
|
69
|
+
const lEfferentCouplings = lModuleDependencies.reduce(sumCounts, 0);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...pFolder,
|
|
73
|
+
afferentCouplings: lAfferentCouplings,
|
|
74
|
+
efferentCouplings: lEfferentCouplings,
|
|
75
|
+
// when both afferentCouplings and efferentCouplings equal 0 instability will
|
|
76
|
+
// yield NaN. Judging Bob Martin's intention, a component with no outgoing
|
|
77
|
+
// dependencies is maximum stable (0)
|
|
78
|
+
instability:
|
|
79
|
+
lEfferentCouplings / (lEfferentCouplings + lAfferentCouplings) || 0,
|
|
80
|
+
dependents: getFolderLevelCouplings(lModuleDependents),
|
|
81
|
+
dependencies: getFolderLevelCouplings(lModuleDependencies),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = function getStabilityMetrics(pModules) {
|
|
86
|
+
return foldersObject2folderArray(
|
|
87
|
+
pModules.filter(metricsAreCalculable).reduce((pAllFolders, pModule) => {
|
|
88
|
+
getParentFolders(path.dirname(pModule.source)).forEach(
|
|
89
|
+
(pParentDirectory) =>
|
|
90
|
+
upsertFolderAttributes(pAllFolders, pModule, pParentDirectory)
|
|
91
|
+
);
|
|
92
|
+
return pAllFolders;
|
|
93
|
+
}, {})
|
|
94
|
+
)
|
|
95
|
+
.map(calculateFolderMetrics)
|
|
96
|
+
.sort(orderFolderMetrics);
|
|
97
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const path = require("path").posix;
|
|
2
|
+
|
|
3
|
+
function getAfferentCouplings(pModule, pDirname) {
|
|
4
|
+
return pModule.dependents.filter(
|
|
5
|
+
(pDependent) => !pDependent.startsWith(pDirname.concat(path.sep))
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getEfferentCouplings(pModule, pDirname) {
|
|
10
|
+
return pModule.dependencies.filter(
|
|
11
|
+
(pDependency) => !pDependency.resolved.startsWith(pDirname.concat(path.sep))
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function metricsAreCalculable(pModule) {
|
|
16
|
+
return (
|
|
17
|
+
!pModule.coreModule &&
|
|
18
|
+
!pModule.couldNotResolve &&
|
|
19
|
+
!pModule.matchesDoNotFollow
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
getAfferentCouplings,
|
|
25
|
+
getEfferentCouplings,
|
|
26
|
+
metricsAreCalculable,
|
|
27
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* eslint-disable security/detect-object-injection */
|
|
2
|
+
function getParentFolders(pPath) {
|
|
3
|
+
let lFragments = pPath.split("/");
|
|
4
|
+
let lReturnValue = [];
|
|
5
|
+
|
|
6
|
+
while (lFragments.length > 0) {
|
|
7
|
+
lReturnValue.push(lFragments.join("/"));
|
|
8
|
+
lFragments.pop();
|
|
9
|
+
}
|
|
10
|
+
return lReturnValue.reverse();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function foldersObject2folderArray(pObject) {
|
|
14
|
+
return Object.keys(pObject).map((pKey) => ({
|
|
15
|
+
name: pKey,
|
|
16
|
+
...pObject[pKey],
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
getParentFolders,
|
|
22
|
+
foldersObject2folderArray,
|
|
23
|
+
};
|
package/src/enrich/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const enrichModules = require("./enrich-modules");
|
|
2
|
+
const deriveFolders = require("./derive/folders");
|
|
2
3
|
const summarize = require("./summarize");
|
|
3
4
|
const clearCaches = require("./clear-caches");
|
|
4
5
|
|
|
@@ -9,6 +10,7 @@ module.exports = function enrich(pModules, pOptions, pFileAndDirectoryArray) {
|
|
|
9
10
|
clearCaches();
|
|
10
11
|
return {
|
|
11
12
|
modules: lModules,
|
|
13
|
+
...deriveFolders(lModules, pOptions),
|
|
12
14
|
summary: summarize(lModules, pOptions, pFileAndDirectoryArray),
|
|
13
15
|
};
|
|
14
16
|
};
|
|
@@ -8,6 +8,7 @@ const SHAREABLE_OPTIONS = [
|
|
|
8
8
|
"externalModuleResolutionStrategy",
|
|
9
9
|
"focus",
|
|
10
10
|
"includeOnly",
|
|
11
|
+
"knownViolations",
|
|
11
12
|
"maxDepth",
|
|
12
13
|
"moduleSystems",
|
|
13
14
|
"outputTo",
|
|
@@ -39,6 +40,10 @@ function makeOptionsPresentable(pOptions) {
|
|
|
39
40
|
(pOption) =>
|
|
40
41
|
pOption !== "exclude" || Object.keys(pOptions.exclude).length > 0
|
|
41
42
|
)
|
|
43
|
+
.filter(
|
|
44
|
+
(pOption) =>
|
|
45
|
+
pOption !== "knownViolations" || pOptions.knownViolations.length > 0
|
|
46
|
+
)
|
|
42
47
|
.reduce((pAll, pOption) => {
|
|
43
48
|
pAll[pOption] = pOptions[pOption];
|
|
44
49
|
return pAll;
|
|
@@ -1,15 +1,58 @@
|
|
|
1
|
+
const isEmpty = require("lodash/isEmpty");
|
|
1
2
|
const _get = require("lodash/get");
|
|
2
3
|
const tryRequire = require("semver-try-require");
|
|
3
4
|
const { supportedTranspilers } = require("../../../src/meta.js");
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/*
|
|
7
|
+
* vue-template-compiler was replaced by @vue/compiler-sfc for Vue3.
|
|
8
|
+
*
|
|
9
|
+
* if your project uses Vue3, then trying to require vue-template-compiler will
|
|
10
|
+
* cause an incompatibility error - so try @vue/compiler-sfc (which is Vue3's
|
|
11
|
+
* version of vue-template-compiler) if the first one fails
|
|
12
|
+
*/
|
|
13
|
+
function getVueTemplateCompiler() {
|
|
14
|
+
let lIsVue3 = false;
|
|
15
|
+
|
|
16
|
+
let lCompiler = tryRequire(
|
|
17
|
+
"vue-template-compiler",
|
|
18
|
+
supportedTranspilers["vue-template-compiler"]
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (lCompiler === false) {
|
|
22
|
+
lCompiler = tryRequire(
|
|
23
|
+
"@vue/compiler-sfc",
|
|
24
|
+
supportedTranspilers["@vue/compiler-sfc"]
|
|
25
|
+
);
|
|
26
|
+
lIsVue3 = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { lCompiler, lIsVue3 };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { lCompiler: vueTemplateCompiler, lIsVue3: isVue3 } =
|
|
33
|
+
getVueTemplateCompiler();
|
|
34
|
+
|
|
35
|
+
function vue3Transpile(pSource) {
|
|
36
|
+
const parsedComponent = vueTemplateCompiler.parse(pSource);
|
|
37
|
+
const errors = _get(parsedComponent, "errors");
|
|
38
|
+
|
|
39
|
+
if (!isEmpty(errors)) {
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return _get(parsedComponent, "descriptor.script.content", "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function vue2Transpile(pSource) {
|
|
47
|
+
return _get(
|
|
48
|
+
vueTemplateCompiler.parseComponent(pSource),
|
|
49
|
+
"script.content",
|
|
50
|
+
""
|
|
51
|
+
);
|
|
52
|
+
}
|
|
9
53
|
|
|
10
54
|
module.exports = {
|
|
11
55
|
isAvailable: () => vueTemplateCompiler !== false,
|
|
12
|
-
|
|
13
56
|
transpile: (pSource) =>
|
|
14
|
-
|
|
57
|
+
isVue3 ? vue3Transpile(pSource) : vue2Transpile(pSource),
|
|
15
58
|
};
|
|
@@ -54,12 +54,12 @@ function normalizeFilterOptions(pOptions, pFilterOptionKeys) {
|
|
|
54
54
|
function normalizeCollapse(pCollapse) {
|
|
55
55
|
let lReturnValue = pCollapse;
|
|
56
56
|
const lOneOrMoreNonSlashes = "[^/]+";
|
|
57
|
-
const
|
|
58
|
-
const
|
|
57
|
+
const lFolderPattern = `${lOneOrMoreNonSlashes}/`;
|
|
58
|
+
const lFolderBelowNodeModules = `node_modules/${lOneOrMoreNonSlashes}`;
|
|
59
59
|
const lSingleDigitRe = /^\d$/;
|
|
60
60
|
|
|
61
61
|
if (typeof pCollapse === "number" || pCollapse.match(lSingleDigitRe)) {
|
|
62
|
-
lReturnValue = `${
|
|
62
|
+
lReturnValue = `${lFolderBelowNodeModules}|^${lFolderPattern.repeat(
|
|
63
63
|
Number.parseInt(pCollapse, 10)
|
|
64
64
|
)}`;
|
|
65
65
|
}
|
package/src/meta.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* generated - don't edit */
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
-
version: "10.
|
|
4
|
+
version: "10.8.0-beta-1",
|
|
5
5
|
engines: {
|
|
6
6
|
node: "^12.20||^14||>=16",
|
|
7
7
|
},
|
|
@@ -14,5 +14,6 @@ module.exports = {
|
|
|
14
14
|
swc: ">=1.0.0 <2.0.0",
|
|
15
15
|
typescript: ">=2.0.0 <5.0.0",
|
|
16
16
|
"vue-template-compiler": ">=2.0.0 <3.0.0",
|
|
17
|
+
"@vue/compiler-sfc": ">=3.0.0 <4.0.0",
|
|
17
18
|
},
|
|
18
19
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const anonymizePathElement = require("./anonymize-path-element");
|
|
2
2
|
|
|
3
3
|
const WHITELIST_RE =
|
|
4
|
-
|
|
4
|
+
// eslint-disable-next-line security/detect-unsafe-regex, unicorn/no-unsafe-regex
|
|
5
|
+
/^(|[.]+|~|bin|apps?|cli|src|libs?|configs?|components?|fixtures?|helpers?|i18n|index\.(jsx?|tsx?|vue|coffee|ls)|_?_?mocks?_?_?|node_modules|packages?|package\.json|scripts?|services?|sources?|specs?|_?_?tests?_?_?|types?|uti?ls?)$/;
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Kind of smartly anonymizes paths, by
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const random = require("lodash/random");
|
|
2
2
|
|
|
3
3
|
const NUMBER = 0;
|
|
4
4
|
const SEPARATOR = 1;
|
|
@@ -30,13 +30,13 @@ function getRandomChar(pChar) {
|
|
|
30
30
|
case SEPARATOR:
|
|
31
31
|
return pChar;
|
|
32
32
|
case NUMBER:
|
|
33
|
-
return
|
|
33
|
+
return random(0, lMaxDecimalChar);
|
|
34
34
|
case UPPERCASE:
|
|
35
35
|
return lLowerCaseChars[
|
|
36
|
-
|
|
36
|
+
random(0, lLowerCaseChars.length - 1)
|
|
37
37
|
].toUpperCase();
|
|
38
38
|
default:
|
|
39
|
-
return lLowerCaseChars[
|
|
39
|
+
return lLowerCaseChars[random(0, lLowerCaseChars.length - 1)];
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
@@ -53,10 +53,5 @@ function getRandomChar(pChar) {
|
|
|
53
53
|
* @return {string} - a random string with above specs
|
|
54
54
|
*/
|
|
55
55
|
module.exports = function getRandomString(pString) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for (let lChar of pString) {
|
|
59
|
-
lReturnValue += getRandomChar(lChar);
|
|
60
|
-
}
|
|
61
|
-
return lReturnValue;
|
|
56
|
+
return Array.from(pString).map(getRandomChar).join("");
|
|
62
57
|
};
|
package/src/report/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const json = require("./json");
|
|
|
13
13
|
const teamcity = require("./teamcity");
|
|
14
14
|
const text = require("./text");
|
|
15
15
|
const baseline = require("./baseline");
|
|
16
|
+
const metrics = require("./metrics");
|
|
16
17
|
const { getExternalPluginReporter } = require("./plugins");
|
|
17
18
|
|
|
18
19
|
const TYPE2REPORTER = {
|
|
@@ -32,6 +33,7 @@ const TYPE2REPORTER = {
|
|
|
32
33
|
teamcity,
|
|
33
34
|
text,
|
|
34
35
|
baseline,
|
|
36
|
+
metrics,
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
/**
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const chalk = require("chalk");
|
|
3
|
+
|
|
4
|
+
const DECIMAL_BASE = 10;
|
|
5
|
+
const METRIC_WIDTH = 4;
|
|
6
|
+
const INSTABILITY_DECIMALS = 2;
|
|
7
|
+
const YADDUM = DECIMAL_BASE ** INSTABILITY_DECIMALS;
|
|
8
|
+
|
|
9
|
+
function transformMetricsToTable(pMetrics) {
|
|
10
|
+
// TODO: should probably use a table module for this (i.e. text-table)
|
|
11
|
+
// to simplify this code; but for this poc not having a dependency (so it's
|
|
12
|
+
// copy-n-pasteable from a gist) is more important
|
|
13
|
+
const lMaxNameWidth = pMetrics
|
|
14
|
+
.map((pMetric) => pMetric.name.length)
|
|
15
|
+
.sort((pLeft, pRight) => pLeft - pRight)
|
|
16
|
+
.pop();
|
|
17
|
+
|
|
18
|
+
return [
|
|
19
|
+
chalk.bold(
|
|
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
|
+
)
|
|
34
|
+
.concat(
|
|
35
|
+
pMetrics.map((pMetric) => {
|
|
36
|
+
return `${pMetric.name.padEnd(
|
|
37
|
+
lMaxNameWidth,
|
|
38
|
+
" "
|
|
39
|
+
)} ${pMetric.moduleCount
|
|
40
|
+
.toString(DECIMAL_BASE)
|
|
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
|
+
})
|
|
51
|
+
)
|
|
52
|
+
.join(os.EOL)
|
|
53
|
+
.concat(os.EOL);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Metrics plugin - to test the waters. If we want to use metrics in other
|
|
58
|
+
* reporters - or use e.g. the Ca/ Ce/ I in rules (e.g. to detect violations
|
|
59
|
+
* of Uncle Bob's variable dependency principle)
|
|
60
|
+
*
|
|
61
|
+
* @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult -
|
|
62
|
+
* the output of a dependency-cruise adhering to dependency-cruiser's
|
|
63
|
+
* cruise result schema
|
|
64
|
+
* @return {import('../../types/dependency-cruiser').IReporterOutput} -
|
|
65
|
+
* output: some metrics on folders and dependencies
|
|
66
|
+
* exitCode: 0
|
|
67
|
+
*/
|
|
68
|
+
module.exports = (pCruiseResult) => {
|
|
69
|
+
if (pCruiseResult.folders) {
|
|
70
|
+
return {
|
|
71
|
+
output: transformMetricsToTable(pCruiseResult.folders),
|
|
72
|
+
exitCode: 0,
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
|
+
return {
|
|
76
|
+
output:
|
|
77
|
+
`${os.EOL}ERROR: The cruise result didn't contain any metrics - re-running the cruise with${os.EOL}` +
|
|
78
|
+
` the '--metrics' command line option should fix that.${os.EOL}${os.EOL}`,
|
|
79
|
+
exitCode: 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
};
|
package/src/report/teamcity.js
CHANGED
|
@@ -45,10 +45,26 @@ function reportAllowedRule(pAllowedRule, pViolations) {
|
|
|
45
45
|
return lReturnValue;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function
|
|
48
|
+
function reportIgnoredRules(pIgnoredCount) {
|
|
49
|
+
let lReturnValue = [];
|
|
50
|
+
|
|
51
|
+
if (pIgnoredCount > 0) {
|
|
52
|
+
lReturnValue = tsm.inspectionType({
|
|
53
|
+
id: "ignored-known-violations",
|
|
54
|
+
name: "ignored-known-violations",
|
|
55
|
+
description:
|
|
56
|
+
"some dependency violations were ignored; run without --ignore-known to see them",
|
|
57
|
+
category: CATEGORY,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return lReturnValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function reportViolatedRules(pRuleSetUsed, pViolations, pIgnoredCount) {
|
|
49
64
|
return reportRules(_get(pRuleSetUsed, "forbidden", []), pViolations)
|
|
50
65
|
.concat(reportAllowedRule(_get(pRuleSetUsed, "allowed", []), pViolations))
|
|
51
|
-
.concat(reportRules(_get(pRuleSetUsed, "required", []), pViolations))
|
|
66
|
+
.concat(reportRules(_get(pRuleSetUsed, "required", []), pViolations))
|
|
67
|
+
.concat(reportIgnoredRules(pIgnoredCount));
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
function determineTo(pViolation) {
|
|
@@ -66,15 +82,31 @@ function bakeViolationMessage(pViolation) {
|
|
|
66
82
|
? pViolation.from
|
|
67
83
|
: `${pViolation.from} -> ${determineTo(pViolation)}`;
|
|
68
84
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
|
|
86
|
+
function reportIgnoredViolation(pIgnoredCount) {
|
|
87
|
+
let lReturnValue = [];
|
|
88
|
+
|
|
89
|
+
if (pIgnoredCount > 0) {
|
|
90
|
+
lReturnValue = tsm.inspection({
|
|
91
|
+
typeId: "ignored-known-violations",
|
|
92
|
+
message: `${pIgnoredCount} known violations ignored. Run without --ignore-known to see them.`,
|
|
93
|
+
SEVERITY: "WARNING",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return lReturnValue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function reportViolations(pViolations, pIgnoredCount) {
|
|
100
|
+
return pViolations
|
|
101
|
+
.map((pViolation) =>
|
|
102
|
+
tsm.inspection({
|
|
103
|
+
typeId: pViolation.rule.name,
|
|
104
|
+
message: bakeViolationMessage(pViolation),
|
|
105
|
+
file: pViolation.from,
|
|
106
|
+
SEVERITY: severity2teamcitySeverity(pViolation.rule.severity),
|
|
107
|
+
})
|
|
108
|
+
)
|
|
109
|
+
.concat(reportIgnoredViolation(pIgnoredCount));
|
|
78
110
|
}
|
|
79
111
|
|
|
80
112
|
/**
|
|
@@ -96,11 +128,14 @@ module.exports = (pResults) => {
|
|
|
96
128
|
tsm.stdout = false;
|
|
97
129
|
|
|
98
130
|
const lRuleSet = _get(pResults, "summary.ruleSetUsed", []);
|
|
99
|
-
const lViolations = _get(pResults, "summary.violations", [])
|
|
131
|
+
const lViolations = _get(pResults, "summary.violations", []).filter(
|
|
132
|
+
(pViolation) => pViolation.rule.severity !== "ignore"
|
|
133
|
+
);
|
|
134
|
+
const lIgnoredCount = _get(pResults, "summary.ignore", 0);
|
|
100
135
|
|
|
101
136
|
return {
|
|
102
|
-
output: reportViolatedRules(lRuleSet, lViolations)
|
|
103
|
-
.concat(reportViolations(lViolations))
|
|
137
|
+
output: reportViolatedRules(lRuleSet, lViolations, lIgnoredCount)
|
|
138
|
+
.concat(reportViolations(lViolations, lIgnoredCount))
|
|
104
139
|
.reduce((pAll, pCurrent) => `${pAll}${pCurrent}\n`, ""),
|
|
105
140
|
exitCode: pResults.summary.error,
|
|
106
141
|
};
|
|
@@ -159,6 +159,10 @@ module.exports = {
|
|
|
159
159
|
type: "array",
|
|
160
160
|
items: { $ref: "#/definitions/DependencyTypeType" },
|
|
161
161
|
},
|
|
162
|
+
dependencyTypesNot: {
|
|
163
|
+
type: "array",
|
|
164
|
+
items: { $ref: "#/definitions/DependencyTypeType" },
|
|
165
|
+
},
|
|
162
166
|
moreThanOneDependencyType: { type: "boolean" },
|
|
163
167
|
license: { $ref: "#/definitions/REAsStringsType" },
|
|
164
168
|
licenseNot: { $ref: "#/definitions/REAsStringsType" },
|
|
@@ -9,6 +9,7 @@ module.exports = {
|
|
|
9
9
|
additionalProperties: false,
|
|
10
10
|
properties: {
|
|
11
11
|
modules: { $ref: "#/definitions/ModulesType" },
|
|
12
|
+
folders: { $ref: "#/definitions/FoldersType" },
|
|
12
13
|
summary: { $ref: "#/definitions/SummaryType" },
|
|
13
14
|
},
|
|
14
15
|
definitions: {
|
|
@@ -158,6 +159,21 @@ module.exports = {
|
|
|
158
159
|
},
|
|
159
160
|
},
|
|
160
161
|
SeverityType: { type: "string", enum: ["error", "warn", "info", "ignore"] },
|
|
162
|
+
FoldersType: { type: "array", items: { $ref: "#/definitions/FolderType" } },
|
|
163
|
+
FolderType: {
|
|
164
|
+
type: "object",
|
|
165
|
+
required: ["name", "moduleCount"],
|
|
166
|
+
additionalProperties: false,
|
|
167
|
+
properties: {
|
|
168
|
+
name: { type: "string" },
|
|
169
|
+
dependents: { type: "array", items: { type: "string" } },
|
|
170
|
+
dependencies: { type: "array", items: { type: "string" } },
|
|
171
|
+
moduleCount: { type: "number" },
|
|
172
|
+
afferentCouplings: { type: "number" },
|
|
173
|
+
efferentCouplings: { type: "number" },
|
|
174
|
+
instability: { type: "number" },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
161
177
|
SummaryType: {
|
|
162
178
|
type: "object",
|
|
163
179
|
required: [
|
|
@@ -331,6 +347,10 @@ module.exports = {
|
|
|
331
347
|
type: "array",
|
|
332
348
|
items: { $ref: "#/definitions/DependencyTypeType" },
|
|
333
349
|
},
|
|
350
|
+
dependencyTypesNot: {
|
|
351
|
+
type: "array",
|
|
352
|
+
items: { $ref: "#/definitions/DependencyTypeType" },
|
|
353
|
+
},
|
|
334
354
|
moreThanOneDependencyType: { type: "boolean" },
|
|
335
355
|
license: { $ref: "#/definitions/REAsStringsType" },
|
|
336
356
|
licenseNot: { $ref: "#/definitions/REAsStringsType" },
|
|
@@ -506,6 +526,7 @@ module.exports = {
|
|
|
506
526
|
"teamcity",
|
|
507
527
|
"anon",
|
|
508
528
|
"text",
|
|
529
|
+
"metrics",
|
|
509
530
|
],
|
|
510
531
|
},
|
|
511
532
|
{ type: "string", pattern: "^plugin:[^:]+$" },
|
|
@@ -4,7 +4,7 @@ const wrapAnsi = require("wrap-ansi");
|
|
|
4
4
|
const DEFAULT_INDENT = 4;
|
|
5
5
|
module.exports = function wrapAndIndent(pString, pIndent = DEFAULT_INDENT) {
|
|
6
6
|
const lDogmaticMaxConsoleWidth = 78;
|
|
7
|
-
const
|
|
7
|
+
const lMaxWidth = lDogmaticMaxConsoleWidth - pIndent;
|
|
8
8
|
|
|
9
|
-
return indentString(wrapAnsi(pString,
|
|
9
|
+
return indentString(wrapAnsi(pString, lMaxWidth), pIndent);
|
|
10
10
|
};
|
|
@@ -32,6 +32,7 @@ function match(pFrom, pTo) {
|
|
|
32
32
|
matchers.toPath(pRule, pTo, lGroups) &&
|
|
33
33
|
matchers.toPathNot(pRule, pTo, lGroups) &&
|
|
34
34
|
matchers.toDependencyTypes(pRule, pTo) &&
|
|
35
|
+
matchers.toDependencyTypesNot(pRule, pTo) &&
|
|
35
36
|
matchesMoreThanOneDependencyType(pRule.to, pTo) &&
|
|
36
37
|
matchers.toLicense(pRule, pTo) &&
|
|
37
38
|
matchers.toLicenseNot(pRule, pTo) &&
|
|
@@ -51,6 +52,7 @@ function match(pFrom, pTo) {
|
|
|
51
52
|
);
|
|
52
53
|
};
|
|
53
54
|
}
|
|
55
|
+
|
|
54
56
|
const isInteresting = (pRule) => !isModuleOnlyRule(pRule);
|
|
55
57
|
|
|
56
58
|
module.exports = {
|
package/src/validate/matchers.js
CHANGED
|
@@ -70,6 +70,13 @@ function toDependencyTypes(pRule, pDependency) {
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function toDependencyTypesNot(pRule, pDependency) {
|
|
74
|
+
return Boolean(
|
|
75
|
+
!pRule.to.dependencyTypesNot ||
|
|
76
|
+
!intersects(pDependency.dependencyTypes, pRule.to.dependencyTypesNot)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
function toLicense(pRule, pDependency) {
|
|
74
81
|
return Boolean(
|
|
75
82
|
!pRule.to.license ||
|
|
@@ -127,6 +134,7 @@ module.exports = {
|
|
|
127
134
|
toPathNot,
|
|
128
135
|
toModulePathNot,
|
|
129
136
|
toDependencyTypes,
|
|
137
|
+
toDependencyTypesNot,
|
|
130
138
|
toLicense,
|
|
131
139
|
toLicenseNot,
|
|
132
140
|
toExoticRequire,
|
package/types/cruise-result.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export interface ICruiseResult {
|
|
|
12
12
|
* A list of modules, with for each module the modules it depends upon
|
|
13
13
|
*/
|
|
14
14
|
modules: IModule[];
|
|
15
|
+
/**
|
|
16
|
+
* A list of folders, as derived from the detected modules, with for each
|
|
17
|
+
* "folder a bunch of metrics (adapted from 'Agile software development:
|
|
18
|
+
* "principles, patterns, and practices' by Robert C Martin (ISBN 0-13-597444-5).
|
|
19
|
+
* "Note: these metrics substitute 'components' and 'classes' from that book
|
|
20
|
+
* "with 'folders' and 'modules'; the closest relatives that work for the most
|
|
21
|
+
* "programming styles in JavaScript (and its derivative languages).
|
|
22
|
+
*/
|
|
23
|
+
folders?: IFolder[];
|
|
15
24
|
/**
|
|
16
25
|
* Data summarizing the found dependencies
|
|
17
26
|
*/
|
|
@@ -393,3 +402,47 @@ export interface IViolation {
|
|
|
393
402
|
*/
|
|
394
403
|
via?: string[];
|
|
395
404
|
}
|
|
405
|
+
|
|
406
|
+
export interface IFolder {
|
|
407
|
+
/**
|
|
408
|
+
* The name of the folder. FOlder names are normalized to posix (so
|
|
409
|
+
* separated by forward slashes e.g.: src/things/morethings)
|
|
410
|
+
*/
|
|
411
|
+
name: string;
|
|
412
|
+
/**
|
|
413
|
+
* List of folders depending on this folder
|
|
414
|
+
*/
|
|
415
|
+
dependents?: string[];
|
|
416
|
+
/**
|
|
417
|
+
* List of folders this folder depends upon
|
|
418
|
+
*/
|
|
419
|
+
dependencies?: string[];
|
|
420
|
+
/**
|
|
421
|
+
* The total number of modules detected in this folder and its sub-folders
|
|
422
|
+
*/
|
|
423
|
+
moduleCount: number;
|
|
424
|
+
/**
|
|
425
|
+
* The number of modules outside this folder that depend on modules
|
|
426
|
+
* within this folder. Only present when dependency-cruiser was
|
|
427
|
+
* "asked to calculate it.
|
|
428
|
+
*/
|
|
429
|
+
afferentCouplings?: number;
|
|
430
|
+
/**
|
|
431
|
+
* The number of modules inside this folder that depend on modules
|
|
432
|
+
* outside this folder. Only present when dependency-cruiser was
|
|
433
|
+
* asked to calculate it.
|
|
434
|
+
*/
|
|
435
|
+
efferentCouplings?: number;
|
|
436
|
+
/**
|
|
437
|
+
* efferentCouplings/ (afferentCouplings + efferentCouplings)
|
|
438
|
+
*
|
|
439
|
+
* A measure for how stable the folder is; ranging between 0
|
|
440
|
+
* (completely stable folder) to 1 (completely instable folder)
|
|
441
|
+
* Note that while 'instability' has a negative connotation it's also
|
|
442
|
+
* (unavoidable in any meaningful system. It's the basis of Martin's
|
|
443
|
+
* variable component stability principle: 'the instability of a folder
|
|
444
|
+
* should be larger than the folders it depends on'. Only present when
|
|
445
|
+
* dependency-cruiser was asked to calculate it.,
|
|
446
|
+
*/
|
|
447
|
+
instability?: number;
|
|
448
|
+
}
|
package/types/restrictions.d.ts
CHANGED
|
@@ -71,6 +71,11 @@ export interface IToRestriction extends IBaseRestrictionType {
|
|
|
71
71
|
* Whether or not to match modules of any of these types (leaving out matches any of them)
|
|
72
72
|
*/
|
|
73
73
|
dependencyTypes?: DependencyType[];
|
|
74
|
+
/**
|
|
75
|
+
* Whether or not to match modules NOT of any of these types (leaving out
|
|
76
|
+
* matches none of them)"
|
|
77
|
+
*/
|
|
78
|
+
dependencyTypesNot?: DependencyType[];
|
|
74
79
|
/**
|
|
75
80
|
* If true matches dependencies with more than one dependency type (e.g. defined in
|
|
76
81
|
* _both_ npm and npm-dev)
|