dependency-cruiser 11.0.0 → 11.1.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/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ See [dependency-cruiser releases](https://github.com/sverweij/dependency-cruiser/releases) on GitHub
package/README.md CHANGED
@@ -21,7 +21,7 @@ you can stick on the wall to impress your grandma.
21
21
  ### Install it
22
22
 
23
23
  - `npm install --save-dev dependency-cruiser` to use it as a validator in your project (recommended) or...
24
- - `npm install --global dependency-cruiser` if you just want to to inspect multiple projects.
24
+ - `npm install --global dependency-cruiser` if you just want to inspect multiple projects.
25
25
 
26
26
  ### Show stuff to your grandma
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "11.0.0",
3
+ "version": "11.1.0",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -76,7 +76,6 @@
76
76
  "depcruise:all": "node ./bin/dependency-cruise.js src bin test configs types tools --config",
77
77
  "depcruise:baseline": "node ./bin/depcruise-baseline.js src bin test configs types tools",
78
78
  "depcruise:explain": "node ./bin/dependency-cruise.js src bin test configs types tools --output-type err-long --config --progress none",
79
- "depcruise:graph:dev": "node ./bin/dependency-cruise.js bin src --prefix vscode://file/$(pwd)/ --config --output-type dot --progress cli-feedback | dot -T svg | node ./bin/wrap-stream-in-html.js | browser",
80
79
  "depcruise:graph:doc": "npm-run-all depcruise:graph:doc:json --parallel depcruise:graph:doc:fmt-* depcruise:graph:doc:samples",
81
80
  "depcruise:graph:doc:json": "node ./bin/dependency-cruise.js bin src test --config --output-type json --output-to tmp_graph_deps.json --progress",
82
81
  "depcruise:graph:doc:fmt-detail": "./bin/depcruise-fmt.js -T dot -f - tmp_graph_deps.json | dot -T svg | tee doc/real-world-samples/dependency-cruiser-without-node_modules.svg | node bin/wrap-stream-in-html.js > docs/dependency-cruiser-dependency-graph.html",
@@ -88,7 +87,9 @@
88
87
  "depcruise:graph:dot": "node ./bin/dependency-cruise.js bin src --config --output-type dot | dot -T svg > tmp_deps.svg",
89
88
  "depcruise:graph:fdp": "node ./bin/dependency-cruise.js bin src --config --output-type dot | fdp -GK=0.1 -Gsplines=true -T svg > tmp_deps.svg",
90
89
  "depcruise:graph:osage": "node ./bin/dependency-cruise.js bin src --config --output-type dot | osage -Gpack=32 -GpackMode=array2 -T svg > tmp_deps.svg",
91
- "depcruise:report": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config --output-to dependency-violations.html",
90
+ "depcruise:graph:view": "node ./bin/dependency-cruise.js bin src --prefix vscode://file/$(pwd)/ --config configs/.dependency-cruiser-show-metrics-config.json --output-type dot --progress cli-feedback | dot -T svg | node ./bin/wrap-stream-in-html.js | browser",
91
+ "depcruise:report": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to dependency-violations.html",
92
+ "depcruise:report:view": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to - | browser",
92
93
  "depcruise:focus": "node ./bin/dependency-cruise.js src bin test configs types --progress --config --output-type text --focus",
93
94
  "lint": "npm-run-all --parallel --aggregate-output lint:eslint lint:prettier:check lint:types",
94
95
  "lint:eslint": "eslint bin/dependency-cruise.js src test configs tools/**/*.mjs --cache --cache-location .cache/eslint/",
@@ -151,18 +152,18 @@
151
152
  "safe-regex": "2.1.1",
152
153
  "semver": "^7.3.5",
153
154
  "semver-try-require": "^5.0.1",
154
- "teamcity-service-messages": "0.1.11",
155
+ "teamcity-service-messages": "0.1.12",
155
156
  "tsconfig-paths-webpack-plugin": "3.5.2",
156
157
  "wrap-ansi": "^7.0.0"
157
158
  },
158
159
  "devDependencies": {
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",
160
+ "@babel/core": "7.16.5",
161
+ "@babel/plugin-transform-modules-commonjs": "7.16.5",
162
+ "@babel/preset-typescript": "7.16.5",
163
+ "@swc/core": "1.2.120",
164
+ "@typescript-eslint/eslint-plugin": "5.7.0",
165
+ "@typescript-eslint/parser": "5.7.0",
166
+ "@vue/compiler-sfc": "3.2.26",
166
167
  "c8": "7.10.0",
167
168
  "chai": "4.3.4",
168
169
  "chai-json-schema": "1.5.1",
@@ -172,7 +173,7 @@
172
173
  "eslint-config-prettier": "8.3.0",
173
174
  "eslint-plugin-budapestian": "3.0.1",
174
175
  "eslint-plugin-import": "2.25.3",
175
- "eslint-plugin-mocha": "9.0.0",
176
+ "eslint-plugin-mocha": "^9.0.0",
176
177
  "eslint-plugin-node": "11.1.0",
177
178
  "eslint-plugin-security": "1.4.0",
178
179
  "eslint-plugin-unicorn": "39.0.0",
@@ -182,12 +183,12 @@
182
183
  "mocha": "9.1.3",
183
184
  "normalize-newline": "^3.0.0",
184
185
  "npm-run-all": "4.1.5",
185
- "prettier": "2.5.0",
186
+ "prettier": "2.5.1",
186
187
  "proxyquire": "2.1.3",
187
188
  "shx": "0.3.3",
188
- "svelte": "3.44.2",
189
+ "svelte": "3.44.3",
189
190
  "symlink-dir": "5.0.1",
190
- "typescript": "4.5.2",
191
+ "typescript": "4.5.4",
191
192
  "upem": "^7.0.0",
192
193
  "vue-template-compiler": "2.6.14",
193
194
  "yarn": "1.22.17"
@@ -219,6 +220,11 @@
219
220
  "policy": "wanted",
220
221
  "because": "version 5 only exports ejs - and we use cjs and don't transpile"
221
222
  },
223
+ {
224
+ "package": "eslint-plugin-mocha",
225
+ "policy": "wanted",
226
+ "because": "version 10 dropped support for node 12, which we still do support"
227
+ },
222
228
  {
223
229
  "package": "normalize-newline",
224
230
  "policy": "wanted",
@@ -1,8 +1,7 @@
1
1
  const getStabilityMetrics = require("./get-stability-metrics");
2
- const { shouldDeriveMetrics } = require("./utl");
3
2
 
4
3
  module.exports = function deriveMetrics(pModules, pOptions) {
5
- if (shouldDeriveMetrics(pOptions)) {
4
+ if (pOptions.metrics) {
6
5
  return { folders: getStabilityMetrics(pModules) };
7
6
  }
8
7
  return {};
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable security/detect-object-injection */
2
2
  const path = require("path").posix;
3
- const { foldersObject2folderArray, getParentFolders } = require("./utl");
3
+ const { object2Array, getParentFolders } = require("./utl");
4
4
  const {
5
5
  getAfferentCouplings,
6
6
  getEfferentCouplings,
@@ -63,8 +63,8 @@ function getFolderLevelCouplings(pCouplingArray) {
63
63
  }
64
64
 
65
65
  function calculateFolderMetrics(pFolder) {
66
- const lModuleDependents = foldersObject2folderArray(pFolder.dependents);
67
- const lModuleDependencies = foldersObject2folderArray(pFolder.dependencies);
66
+ const lModuleDependents = object2Array(pFolder.dependents);
67
+ const lModuleDependencies = object2Array(pFolder.dependencies);
68
68
  const lAfferentCouplings = lModuleDependents.reduce(sumCounts, 0);
69
69
  const lEfferentCouplings = lModuleDependencies.reduce(sumCounts, 0);
70
70
 
@@ -83,7 +83,7 @@ function calculateFolderMetrics(pFolder) {
83
83
  }
84
84
 
85
85
  module.exports = function getStabilityMetrics(pModules) {
86
- return foldersObject2folderArray(
86
+ return object2Array(
87
87
  pModules.filter(metricsAreCalculable).reduce((pAllFolders, pModule) => {
88
88
  getParentFolders(path.dirname(pModule.source)).forEach(
89
89
  (pParentDirectory) =>
@@ -1,10 +1,9 @@
1
- // const { findModuleByName, clearCache } = require("../utl");
1
+ const { findModuleByName, clearCache } = require("../utl");
2
2
  const { metricsAreCalculable } = require("./module-utl");
3
- const { shouldDeriveMetrics } = require("./utl");
4
3
 
5
4
  module.exports = function deriveModuleMetrics(pModules, pOptions) {
6
- if (shouldDeriveMetrics(pOptions)) {
7
- return pModules.map((pModule) => ({
5
+ if (pOptions.metrics) {
6
+ const lModules = pModules.map((pModule) => ({
8
7
  ...pModule,
9
8
  ...(metricsAreCalculable(pModule)
10
9
  ? {
@@ -14,16 +13,16 @@ module.exports = function deriveModuleMetrics(pModules, pOptions) {
14
13
  }
15
14
  : {}),
16
15
  }));
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
- // }));
16
+ clearCache();
17
+ return lModules.map((pModule) => ({
18
+ ...pModule,
19
+ dependencies: pModule.dependencies.map((pDependency) => ({
20
+ ...pDependency,
21
+ instability:
22
+ (findModuleByName(lModules, pDependency.resolved) || {})
23
+ .instability || 0,
24
+ })),
25
+ }));
27
26
  }
28
27
  return pModules;
29
28
  };
@@ -10,19 +10,14 @@ function getParentFolders(pPath) {
10
10
  return lReturnValue.reverse();
11
11
  }
12
12
 
13
- function foldersObject2folderArray(pObject) {
13
+ function object2Array(pObject) {
14
14
  return Object.keys(pObject).map((pKey) => ({
15
15
  name: pKey,
16
16
  ...pObject[pKey],
17
17
  }));
18
18
  }
19
19
 
20
- function shouldDeriveMetrics(pOptions) {
21
- return pOptions.metrics || pOptions.outputType === "metrics";
22
- }
23
-
24
20
  module.exports = {
25
21
  getParentFolders,
26
- foldersObject2folderArray,
27
- shouldDeriveMetrics,
22
+ object2Array,
28
23
  };
@@ -19,7 +19,7 @@ module.exports = function enrichModules(pModules, pOptions) {
19
19
  lModules = deriveOrphans(lModules);
20
20
  bus.emit("progress", "analyzing: reachables", { level: busLogLevels.INFO });
21
21
  lModules = deriveReachable(lModules, pOptions.ruleSet);
22
- bus.emit("progress", "analyzing: calculating module metrics", {
22
+ bus.emit("progress", "analyzing: module metrics", {
23
23
  level: busLogLevels.INFO,
24
24
  });
25
25
  lModules = deriveModuleMetrics(lModules, pOptions);
@@ -66,6 +66,32 @@ function normalizeCollapse(pCollapse) {
66
66
  return lReturnValue;
67
67
  }
68
68
 
69
+ function hasMetricsRule(pRule) {
70
+ return _has(pRule, "to.moreUnstable");
71
+ }
72
+
73
+ function ruleSetHasMetricsRule(pRuleSet) {
74
+ const lRuleSet = pRuleSet || {};
75
+ return (
76
+ (lRuleSet.forbidden || []).some(hasMetricsRule) ||
77
+ (lRuleSet.allowed || []).some(hasMetricsRule)
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Determines whether (instability) metrics should be calculated
83
+ *
84
+ * @param {import('../../../types/options').ICruiseOptions pOptions
85
+ * @returns Boolean
86
+ */
87
+ function shouldCalculateMetrics(pOptions) {
88
+ return (
89
+ pOptions.metrics ||
90
+ pOptions.outputType === "metrics" ||
91
+ ruleSetHasMetricsRule(pOptions.ruleSet)
92
+ );
93
+ }
94
+
69
95
  /**
70
96
  *
71
97
  * @param {Partial <import('../../../types/options').ICruiseOptions>} pOptions
@@ -101,6 +127,8 @@ function normalizeCruiseOptions(pOptions) {
101
127
  lReturnValue.reporterOptions
102
128
  );
103
129
  }
130
+ lReturnValue.metrics = shouldCalculateMetrics(pOptions);
131
+
104
132
  return lReturnValue;
105
133
  }
106
134
 
package/src/meta.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* generated - don't edit */
2
2
 
3
3
  module.exports = {
4
- version: "11.0.0",
4
+ version: "11.1.0",
5
5
  engines: {
6
6
  node: "^12.20||^14||>=16",
7
7
  },
@@ -1 +1 @@
1
- var Handlebars=require("handlebars/runtime"),template=Handlebars.template,templates=Handlebars.templates=Handlebars.templates||{};templates["dot.template.hbs"]=template({1:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"graphAttrs")||(null!=n?a(n,"graphAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"graphAttrs",hash:{},data:o,loc:{start:{line:2,column:22},end:{line:2,column:38}}}):a)?a:""},3:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"node ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"nodeAttrs")||(null!=n?a(n,"nodeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"nodeAttrs",hash:{},data:o,loc:{start:{line:3,column:27},end:{line:3,column:42}}}):a)?a:"")+"]"},5:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"edge ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"edgeAttrs")||(null!=n?a(n,"edgeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"edgeAttrs",hash:{},data:o,loc:{start:{line:4,column:27},end:{line:4,column:42}}}):a)?a:"")+"]"},7:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},i=null!=(p=c(e,"if").call(u,null!=n?c(n,"folder"):n,{name:"if",hash:{},fn:l.program(8,o,0,a,r),inverse:l.program(20,o,0,a,r),data:o,loc:{start:{line:7,column:4},end:{line:12,column:11}}}))?p:"",s=null!=(s=c(e,"dependencies")||(null!=n?c(n,"dependencies"):n))?s:l.hooks.helperMissing,o={name:"dependencies",hash:{},fn:l.program(22,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:13,column:4},end:{line:15,column:21}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!c(e,"dependencies")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(i+=p),i},8:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" "+(null!=(u=m(e,"each").call(i,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(9,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:4},end:{line:8,column:201}}}))?u:"")+'"'+(null!=(u=typeof(c=null!=(c=m(e,"source")||(null!=n?m(n,"source"):n))?c:s)==p?c.call(i,{name:"source",hash:{},data:o,loc:{start:{line:9,column:9},end:{line:9,column:21}}}):c)?u:"")+'" [label='+(null!=(u=typeof(c=null!=(c=m(e,"label")||(null!=n?m(n,"label"):n))?c:s)==p?c.call(i,{name:"label",hash:{},data:o,loc:{start:{line:9,column:30},end:{line:9,column:41}}}):c)?u:"")+" "+(null!=(u=m(e,"if").call(i,null!=n?m(n,"rule"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:42},end:{line:9,column:87}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:87},end:{line:9,column:121}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:121},end:{line:9,column:162}}}))?u:"")+"]"+(null!=(u=m(e,"each").call(i,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(18,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:163},end:{line:9,column:188}}}))?u:"")+"\n"},9:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'subgraph "cluster_'+(null!=(u=typeof(c=null!=(c=m(e,"aggregateSnippet")||(null!=n?m(n,"aggregateSnippet"):n))?c:s)==p?c.call(i,{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:36},end:{line:8,column:58}}}):c)?u:"")+'" {label="'+(null!=(u=typeof(c=null!=(c=m(e,"snippet")||(null!=n?m(n,"snippet"):n))?c:s)==p?c.call(i,{name:"snippet",hash:{},data:o,loc:{start:{line:8,column:68},end:{line:8,column:81}}}):c)?u:"")+'" '+(null!=(u=m(e,"if").call(i,null!=r[2]?m(r[2],"clustersHaveOwnNode"):r[2],{name:"if",hash:{},fn:l.program(10,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:83},end:{line:8,column:191}}}))?u:"")},10:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'"'+(null!=(a="function"==typeof(a=null!=(a=a(e,"aggregateSnippet")||(null!=n?a(n,"aggregateSnippet"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:117},end:{line:8,column:139}}}):a)?a:"")+'" [width="0.05" shape="point" style="invis"] '},12:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'tooltip="'+(null!=(l=l.lambda(null!=(l=null!=n?a(n,"rule"):n)?a(l,"name"):l,n))?l:"")+'" '},14:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'URL="'+(null!=(a="function"==typeof(a=null!=(a=a(e,"URL")||(null!=n?a(n,"URL"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"URL",hash:{},data:o,loc:{start:{line:9,column:103},end:{line:9,column:112}}}):a)?a:"")+'" '},16:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"themeAttrs")||(null!=n?a(n,"themeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"themeAttrs",hash:{},data:o,loc:{start:{line:9,column:139},end:{line:9,column:155}}}):a)?a:""},18:function(l,n,e,t,o){return" }"},20:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=typeof(c=null!=(c=m(e,"source")||(null!=n?m(n,"source"):n))?c:s)==p?c.call(i,{name:"source",hash:{},data:o,loc:{start:{line:11,column:5},end:{line:11,column:17}}}):c)?u:"")+'" [label='+(null!=(u=typeof(c=null!=(c=m(e,"label")||(null!=n?m(n,"label"):n))?c:s)==p?c.call(i,{name:"label",hash:{},data:o,loc:{start:{line:11,column:26},end:{line:11,column:37}}}):c)?u:"")+" "+(null!=(u=m(e,"if").call(i,null!=n?m(n,"rule"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:38},end:{line:11,column:83}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:83},end:{line:11,column:117}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:117},end:{line:11,column:158}}}))?u:"")+"]\n"},22:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=l.lambda(null!=r[1]?s(r[1],"source"):r[1],n))?u:"")+'" -> "'+(null!=(u="function"==typeof(c=null!=(c=s(e,"resolved")||(null!=n?s(n,"resolved"):n))?c:l.hooks.helperMissing)?c.call(i,{name:"resolved",hash:{},data:o,loc:{start:{line:14,column:26},end:{line:14,column:40}}}):c)?u:"")+'"'+(null!=(u=s(e,"if").call(i,null!=n?s(n,"hasExtraAttributes"):n,{name:"if",hash:{},fn:l.program(23,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:41},end:{line:14,column:193}}}))?u:"")+"\n"},23:function(l,n,e,t,o,a,r){var u,c=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" ["+(null!=(u=i(e,"if").call(c,null!=(u=null!=n?i(n,"rule"):n)?i(u,"name"):u,{name:"if",hash:{},fn:l.program(24,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:69},end:{line:14,column:144}}}))?u:"")+(null!=(u=i(e,"if").call(c,null!=n?i(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:144},end:{line:14,column:185}}}))?u:"")+"]"},24:function(l,n,e,t,o){var a=l.lambda,r=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'xlabel="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" tooltip="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" '},compiler:[8,">= 4.3.0"],main:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},i='strict digraph "dependency-cruiser output"{\n '+(null!=(p=c(e,"if").call(u,null!=n?c(n,"graphAttrs"):n,{name:"if",hash:{},fn:l.program(1,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:2,column:4},end:{line:2,column:45}}}))?p:"")+"\n "+(null!=(p=c(e,"if").call(u,null!=n?c(n,"nodeAttrs"):n,{name:"if",hash:{},fn:l.program(3,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:3,column:4},end:{line:3,column:50}}}))?p:"")+"\n "+(null!=(p=c(e,"if").call(u,null!=n?c(n,"edgeAttrs"):n,{name:"if",hash:{},fn:l.program(5,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:4,column:4},end:{line:4,column:50}}}))?p:"")+"\n\n",s=null!=(s=c(e,"modules")||(null!=n?c(n,"modules"):n))?s:l.hooks.helperMissing,o={name:"modules",hash:{},fn:l.program(7,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:6,column:0},end:{line:16,column:12}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!c(e,"modules")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(i+=p),i+"}\n"},useData:!0,useDepths:!0});
1
+ var Handlebars=require("handlebars/runtime"),template=Handlebars.template,templates=Handlebars.templates=Handlebars.templates||{};templates["dot.template.hbs"]=template({1:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"graphAttrs")||(null!=n?a(n,"graphAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"graphAttrs",hash:{},data:o,loc:{start:{line:2,column:22},end:{line:2,column:38}}}):a)?a:""},3:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"node ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"nodeAttrs")||(null!=n?a(n,"nodeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"nodeAttrs",hash:{},data:o,loc:{start:{line:3,column:27},end:{line:3,column:42}}}):a)?a:"")+"]"},5:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"edge ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"edgeAttrs")||(null!=n?a(n,"edgeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"edgeAttrs",hash:{},data:o,loc:{start:{line:4,column:27},end:{line:4,column:42}}}):a)?a:"")+"]"},7:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},c=null!=(p=i(e,"if").call(u,null!=n?i(n,"folder"):n,{name:"if",hash:{},fn:l.program(8,o,0,a,r),inverse:l.program(18,o,0,a,r),data:o,loc:{start:{line:7,column:4},end:{line:12,column:11}}}))?p:"",s=null!=(s=i(e,"dependencies")||(null!=n?i(n,"dependencies"):n))?s:l.hooks.helperMissing,o={name:"dependencies",hash:{},fn:l.program(20,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:13,column:4},end:{line:15,column:21}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!i(e,"dependencies")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(c+=p),c},8:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" "+(null!=(u=m(e,"each").call(c,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(9,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:4},end:{line:8,column:201}}}))?u:"")+'"'+(null!=(u=typeof(i=null!=(i=m(e,"source")||(null!=n?m(n,"source"):n))?i:s)==p?i.call(c,{name:"source",hash:{},data:o,loc:{start:{line:9,column:9},end:{line:9,column:21}}}):i)?u:"")+'" [label='+(null!=(u=typeof(i=null!=(i=m(e,"label")||(null!=n?m(n,"label"):n))?i:s)==p?i.call(c,{name:"label",hash:{},data:o,loc:{start:{line:9,column:30},end:{line:9,column:41}}}):i)?u:"")+' tooltip="'+(null!=(u=typeof(i=null!=(i=m(e,"tooltip")||(null!=n?m(n,"tooltip"):n))?i:s)==p?i.call(c,{name:"tooltip",hash:{},data:o,loc:{start:{line:9,column:51},end:{line:9,column:64}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:66},end:{line:9,column:100}}}))?u:"")+(null!=(u=m(e,"if").call(c,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:100},end:{line:9,column:141}}}))?u:"")+"]"+(null!=(u=m(e,"each").call(c,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:142},end:{line:9,column:167}}}))?u:"")+"\n"},9:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'subgraph "cluster_'+(null!=(u=typeof(i=null!=(i=m(e,"aggregateSnippet")||(null!=n?m(n,"aggregateSnippet"):n))?i:s)==p?i.call(c,{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:36},end:{line:8,column:58}}}):i)?u:"")+'" {label="'+(null!=(u=typeof(i=null!=(i=m(e,"snippet")||(null!=n?m(n,"snippet"):n))?i:s)==p?i.call(c,{name:"snippet",hash:{},data:o,loc:{start:{line:8,column:68},end:{line:8,column:81}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=r[2]?m(r[2],"clustersHaveOwnNode"):r[2],{name:"if",hash:{},fn:l.program(10,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:83},end:{line:8,column:191}}}))?u:"")},10:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'"'+(null!=(a="function"==typeof(a=null!=(a=a(e,"aggregateSnippet")||(null!=n?a(n,"aggregateSnippet"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:117},end:{line:8,column:139}}}):a)?a:"")+'" [width="0.05" shape="point" style="invis"] '},12:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'URL="'+(null!=(a="function"==typeof(a=null!=(a=a(e,"URL")||(null!=n?a(n,"URL"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"URL",hash:{},data:o,loc:{start:{line:9,column:82},end:{line:9,column:91}}}):a)?a:"")+'" '},14:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"themeAttrs")||(null!=n?a(n,"themeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"themeAttrs",hash:{},data:o,loc:{start:{line:9,column:118},end:{line:9,column:134}}}):a)?a:""},16:function(l,n,e,t,o){return" }"},18:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=typeof(i=null!=(i=m(e,"source")||(null!=n?m(n,"source"):n))?i:s)==p?i.call(c,{name:"source",hash:{},data:o,loc:{start:{line:11,column:5},end:{line:11,column:17}}}):i)?u:"")+'" [label='+(null!=(u=typeof(i=null!=(i=m(e,"label")||(null!=n?m(n,"label"):n))?i:s)==p?i.call(c,{name:"label",hash:{},data:o,loc:{start:{line:11,column:26},end:{line:11,column:37}}}):i)?u:"")+' tooltip="'+(null!=(u=typeof(i=null!=(i=m(e,"tooltip")||(null!=n?m(n,"tooltip"):n))?i:s)==p?i.call(c,{name:"tooltip",hash:{},data:o,loc:{start:{line:11,column:47},end:{line:11,column:60}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:62},end:{line:11,column:96}}}))?u:"")+(null!=(u=m(e,"if").call(c,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:96},end:{line:11,column:137}}}))?u:"")+"]\n"},20:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=l.lambda(null!=r[1]?s(r[1],"source"):r[1],n))?u:"")+'" -> "'+(null!=(u="function"==typeof(i=null!=(i=s(e,"resolved")||(null!=n?s(n,"resolved"):n))?i:l.hooks.helperMissing)?i.call(c,{name:"resolved",hash:{},data:o,loc:{start:{line:14,column:26},end:{line:14,column:40}}}):i)?u:"")+'"'+(null!=(u=s(e,"if").call(c,null!=n?s(n,"hasExtraAttributes"):n,{name:"if",hash:{},fn:l.program(21,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:41},end:{line:14,column:193}}}))?u:"")+"\n"},21:function(l,n,e,t,o,a,r){var u,i=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" ["+(null!=(u=c(e,"if").call(i,null!=(u=null!=n?c(n,"rule"):n)?c(u,"name"):u,{name:"if",hash:{},fn:l.program(22,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:69},end:{line:14,column:144}}}))?u:"")+(null!=(u=c(e,"if").call(i,null!=n?c(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:144},end:{line:14,column:185}}}))?u:"")+"]"},22:function(l,n,e,t,o){var a=l.lambda,r=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'xlabel="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" tooltip="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" '},compiler:[8,">= 4.3.0"],main:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},c='strict digraph "dependency-cruiser output"{\n '+(null!=(p=i(e,"if").call(u,null!=n?i(n,"graphAttrs"):n,{name:"if",hash:{},fn:l.program(1,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:2,column:4},end:{line:2,column:45}}}))?p:"")+"\n "+(null!=(p=i(e,"if").call(u,null!=n?i(n,"nodeAttrs"):n,{name:"if",hash:{},fn:l.program(3,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:3,column:4},end:{line:3,column:50}}}))?p:"")+"\n "+(null!=(p=i(e,"if").call(u,null!=n?i(n,"edgeAttrs"):n,{name:"if",hash:{},fn:l.program(5,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:4,column:4},end:{line:4,column:50}}}))?p:"")+"\n\n",s=null!=(s=i(e,"modules")||(null!=n?i(n,"modules"):n))?s:l.hooks.helperMissing,o={name:"modules",hash:{},fn:l.program(7,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:6,column:0},end:{line:16,column:12}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!i(e,"modules")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(c+=p),c+"}\n"},useData:!0,useDepths:!0});
@@ -17,7 +17,11 @@ const GRANULARITY2FUNCTION = {
17
17
  flat: prepareFlatLevel,
18
18
  };
19
19
 
20
- function report(pResults, pGranularity, { theme, collapsePattern, filters }) {
20
+ function report(
21
+ pResults,
22
+ pGranularity,
23
+ { theme, collapsePattern, filters, showMetrics }
24
+ ) {
21
25
  const lTheme = theming.normalizeTheme(theme);
22
26
  const lResults = filters
23
27
  ? {
@@ -35,7 +39,8 @@ function report(pResults, pGranularity, { theme, collapsePattern, filters }) {
35
39
  modules: (GRANULARITY2FUNCTION[pGranularity] || prepareCustomLevel)(
36
40
  lResults,
37
41
  lTheme,
38
- collapsePattern
42
+ collapsePattern,
43
+ showMetrics
39
44
  ),
40
45
  });
41
46
  }
@@ -1,6 +1,9 @@
1
1
  const path = require("path").posix;
2
+ const _has = require("lodash/has");
2
3
  const theming = require("./theming");
3
4
 
5
+ const PROTOCOL_PREFIX_RE = /^[a-z]+:\/\//;
6
+
4
7
  function attributizeObject(pObject) {
5
8
  return (
6
9
  Object.keys(pObject)
@@ -12,7 +15,9 @@ function attributizeObject(pObject) {
12
15
 
13
16
  function extractFirstTransgression(pModule) {
14
17
  return {
15
- ...(pModule.rules ? { ...pModule, rule: pModule.rules[0] } : pModule),
18
+ ...(_has(pModule, "rules[0]")
19
+ ? { ...pModule, tooltip: pModule.rules[0].name }
20
+ : pModule),
16
21
  dependencies: pModule.dependencies.map((pDependency) =>
17
22
  pDependency.rules
18
23
  ? {
@@ -57,20 +62,37 @@ function aggregate(pPathSnippet, pCounter, pPathArray) {
57
62
  };
58
63
  }
59
64
 
60
- function folderify(pModule) {
61
- let lAdditions = {};
62
- let lDirectoryName = path.dirname(pModule.source);
65
+ function makeInstabilityString(pModule, pShowMetrics = false) {
66
+ let lInstabilityString = "";
63
67
 
64
- if (lDirectoryName !== ".") {
65
- lAdditions.folder = lDirectoryName;
66
- lAdditions.path = lDirectoryName.split(path.sep).map(aggregate);
68
+ if (pShowMetrics && _has(pModule, "instability") && !pModule.consolidated) {
69
+ lInstabilityString = ` <FONT color="#808080" point-size="8">${Math.round(
70
+ // eslint-disable-next-line no-magic-numbers
71
+ 100 * pModule.instability
72
+ )}</FONT>`;
67
73
  }
74
+ return lInstabilityString;
75
+ }
68
76
 
69
- lAdditions.label = `"${path.basename(pModule.source)}"`;
77
+ function folderify(pShowMetrics) {
78
+ return (pModule) => {
79
+ let lAdditions = {};
80
+ let lDirectoryName = path.dirname(pModule.source);
70
81
 
71
- return {
72
- ...pModule,
73
- ...lAdditions,
82
+ if (lDirectoryName !== ".") {
83
+ lAdditions.folder = lDirectoryName;
84
+ lAdditions.path = lDirectoryName.split(path.sep).map(aggregate);
85
+ }
86
+
87
+ lAdditions.label = `<${path.basename(
88
+ pModule.source
89
+ )}${makeInstabilityString(pModule, pShowMetrics)}>`;
90
+ lAdditions.tooltip = path.basename(pModule.source);
91
+
92
+ return {
93
+ ...pModule,
94
+ ...lAdditions,
95
+ };
74
96
  };
75
97
  }
76
98
 
@@ -87,7 +109,7 @@ function folderify(pModule) {
87
109
  * @return {string} prefix and filename concatenated
88
110
  */
89
111
  function smartURIConcat(pPrefix, pSource) {
90
- if (pPrefix.match(/^[a-z]+:\/\//)) {
112
+ if (pPrefix.match(PROTOCOL_PREFIX_RE)) {
91
113
  return `${pPrefix}${pSource}`;
92
114
  } else {
93
115
  return path.join(pPrefix, pSource);
@@ -103,15 +125,21 @@ function addURL(pPrefix) {
103
125
  });
104
126
  }
105
127
 
106
- function flatLabel(pModule) {
107
- return {
108
- ...pModule,
109
- label: `<${path.dirname(pModule.source)}/<BR/><B>${path.basename(
110
- pModule.source
111
- )}</B>>`,
112
- };
128
+ function makeLabel(pModule, pShowMetrics) {
129
+ return `<${path.dirname(pModule.source)}/<BR/><B>${path.basename(
130
+ pModule.source
131
+ )}</B>${makeInstabilityString(pModule, pShowMetrics)}>`;
113
132
  }
114
133
 
134
+ function flatLabel(pShowMetrics) {
135
+ return (pModule) => {
136
+ return {
137
+ ...pModule,
138
+ label: makeLabel(pModule, pShowMetrics),
139
+ tooltip: path.basename(pModule.source),
140
+ };
141
+ };
142
+ }
115
143
  module.exports = {
116
144
  folderify,
117
145
  applyTheme,
@@ -4,15 +4,15 @@ const compare = require("../../graph-utl/compare");
4
4
  const stripSelfTransitions = require("../../graph-utl/strip-self-transitions");
5
5
  const moduleUtl = require("./module-utl");
6
6
 
7
- module.exports = (pResults, pTheme, pCollapsePattern) => {
7
+ module.exports = (pResults, pTheme, pCollapsePattern, pShowMetrics) => {
8
8
  return (
9
9
  pCollapsePattern
10
10
  ? consolidateToPattern(pResults.modules, pCollapsePattern)
11
11
  : pResults.modules
12
12
  )
13
13
  .sort(compare.modules)
14
+ .map(moduleUtl.folderify(pShowMetrics))
14
15
  .map(moduleUtl.extractFirstTransgression)
15
- .map(moduleUtl.folderify)
16
16
  .map(stripSelfTransitions)
17
17
  .map(moduleUtl.applyTheme(pTheme))
18
18
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -2,10 +2,10 @@ const _get = require("lodash/get");
2
2
  const compare = require("../../graph-utl/compare");
3
3
  const moduleUtl = require("./module-utl");
4
4
 
5
- module.exports = (pResults, pTheme) => {
5
+ module.exports = (pResults, pTheme, _, pShowMetrics) => {
6
6
  return pResults.modules
7
7
  .sort(compare.modules)
8
- .map(moduleUtl.flatLabel)
8
+ .map(moduleUtl.flatLabel(pShowMetrics))
9
9
  .map(moduleUtl.extractFirstTransgression)
10
10
  .map(moduleUtl.applyTheme(pTheme))
11
11
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -4,11 +4,11 @@ const compare = require("../../graph-utl/compare");
4
4
  const stripSelfTransitions = require("../../graph-utl/strip-self-transitions");
5
5
  const moduleUtl = require("./module-utl");
6
6
 
7
- module.exports = (pResults, pTheme) => {
7
+ module.exports = (pResults, pTheme, _, pShowMetrics) => {
8
8
  return consolidateToFolder(pResults.modules)
9
9
  .sort(compare.modules)
10
10
  .map(moduleUtl.extractFirstTransgression)
11
- .map(moduleUtl.folderify)
11
+ .map(moduleUtl.folderify(pShowMetrics))
12
12
  .map(stripSelfTransitions)
13
13
  .map(moduleUtl.applyTheme(pTheme))
14
14
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -168,6 +168,7 @@ module.exports = {
168
168
  licenseNot: { $ref: "#/definitions/REAsStringsType" },
169
169
  via: { $ref: "#/definitions/REAsStringsType" },
170
170
  viaNot: { $ref: "#/definitions/REAsStringsType" },
171
+ moreUnstable: { type: "boolean" },
171
172
  },
172
173
  },
173
174
  DependentsModuleRestrictionType: {
@@ -348,6 +349,7 @@ module.exports = {
348
349
  },
349
350
  },
350
351
  },
352
+ metrics: { type: "boolean" },
351
353
  baseDir: { type: "string" },
352
354
  },
353
355
  },
@@ -426,6 +428,7 @@ module.exports = {
426
428
  properties: {
427
429
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
428
430
  filters: { $ref: "#/definitions/ReporterFiltersType" },
431
+ showMetrics: { type: "boolean" },
429
432
  theme: { $ref: "#/definitions/DotThemeType" },
430
433
  },
431
434
  },
@@ -127,6 +127,7 @@ module.exports = {
127
127
  type: "array",
128
128
  items: { $ref: "#/definitions/RuleSummaryType" },
129
129
  },
130
+ instability: { type: "number" },
130
131
  },
131
132
  },
132
133
  DependencyTypeType: {
@@ -357,6 +358,7 @@ module.exports = {
357
358
  licenseNot: { $ref: "#/definitions/REAsStringsType" },
358
359
  via: { $ref: "#/definitions/REAsStringsType" },
359
360
  viaNot: { $ref: "#/definitions/REAsStringsType" },
361
+ moreUnstable: { type: "boolean" },
360
362
  },
361
363
  },
362
364
  DependentsModuleRestrictionType: {
@@ -496,6 +498,7 @@ module.exports = {
496
498
  },
497
499
  },
498
500
  },
501
+ metrics: { type: "boolean" },
499
502
  baseDir: { type: "string" },
500
503
  args: { type: "string" },
501
504
  rulesFile: { type: "string" },
@@ -603,6 +606,7 @@ module.exports = {
603
606
  properties: {
604
607
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
605
608
  filters: { $ref: "#/definitions/ReporterFiltersType" },
609
+ showMetrics: { type: "boolean" },
606
610
  theme: { $ref: "#/definitions/DotThemeType" },
607
611
  },
608
612
  },
@@ -40,6 +40,7 @@ function match(pFrom, pTo) {
40
40
  matchers.toExoticRequireNot(pRule, pTo) &&
41
41
  matchers.toVia(pRule, pTo) &&
42
42
  matchers.toViaNot(pRule, pTo) &&
43
+ matchers.toIsMoreUnstable(pRule, pFrom, pTo) &&
43
44
  // preCompilationOnly is not a mandatory attribute, but if the attribute
44
45
  // is in the rule but not in the dependency there won't be a match
45
46
  // anyway, so we can use the default propertyEquals method regardless
@@ -1,3 +1,4 @@
1
+ const _has = require("lodash/has");
1
2
  const { intersects } = require("../utl/array-util");
2
3
 
3
4
  function fromPath(pRule, pModule) {
@@ -123,6 +124,16 @@ function toVia(pRule, pDependency) {
123
124
  );
124
125
  }
125
126
 
127
+ function toIsMoreUnstable(pRule, pFrom, pTo) {
128
+ if (_has(pRule, "to.moreUnstable")) {
129
+ return (
130
+ (pRule.to.moreUnstable && pFrom.instability < pTo.instability) ||
131
+ (!pRule.to.moreUnstable && pFrom.instability >= pTo.instability)
132
+ );
133
+ }
134
+ return true;
135
+ }
136
+
126
137
  module.exports = {
127
138
  _replaceGroupPlaceholders,
128
139
  fromPath,
@@ -141,4 +152,5 @@ module.exports = {
141
152
  toExoticRequireNot,
142
153
  toVia,
143
154
  toViaNot,
155
+ toIsMoreUnstable,
144
156
  };
@@ -0,0 +1 @@
1
+ ![overview](overview.svg)
@@ -221,6 +221,11 @@ export interface IDependency {
221
221
  * will be in the 'rule' object at the same level.
222
222
  */
223
223
  valid: boolean;
224
+ /**
225
+ * the (de-normalized) instability of the dependency - also available in
226
+ * the module on the 'to' side of this dependency
227
+ */
228
+ instability: number;
224
229
  }
225
230
 
226
231
  export interface IReachable {
@@ -239,4 +239,9 @@ export interface ICruiseOptions {
239
239
  */
240
240
  type: ProgressType;
241
241
  };
242
+ /**
243
+ * When this flag is set to true, dependency-cruiser will calculate (stability) metrics
244
+ * for all modules and folders. Defaults to false.
245
+ */
246
+ metrics?: boolean;
242
247
  }
@@ -58,6 +58,12 @@ export interface IDotReporterOptions {
58
58
  * goal of the report)
59
59
  */
60
60
  filters?: IReporterFiltersType;
61
+ /**
62
+ * When passed the value 'true', shows instability metrics in the
63
+ * output if dependency-cruiser calculated them. Doesn't show them
64
+ * in all other cases. Defaults to false",
65
+ */
66
+ showMetrics?: boolean;
61
67
  /**
62
68
  * A bunch of criteria to (conditionally) theme the dot output
63
69
  */
@@ -92,6 +92,18 @@ export interface IToRestriction extends IBaseRestrictionType {
92
92
  * licenses. E.g. to flag everyting non MIT use "MIT" here
93
93
  */
94
94
  licenseNot?: string | string[];
95
+ /**
96
+ * When set to true moreUnstable matches for any dependency that has a higher
97
+ * Instability than the module that depends on it. When set to false it matches
98
+ * when the opposite is true; the dependency has an equal or lower Instability.
99
+ *
100
+ * This attribute is useful when you want to check against Robert C. Martin's
101
+ * stable dependency * principle. See online documentation for examples and
102
+ * details.
103
+ *
104
+ * Leave this out when you don't care either way.
105
+ */
106
+ moreUnstable?: boolean;
95
107
  }
96
108
 
97
109
  export interface IReachabilityToRestrictionType extends IBaseRestrictionType {