dependency-cruiser 10.8.0 → 10.9.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/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": "10.8.0",
3
+ "version": "10.9.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: "10.8.0",
4
+ version: "10.9.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
  };
@@ -1,3 +1,3 @@
1
- import { IViolation } from "./cruise-result";
1
+ import { IViolation } from "./violations";
2
2
 
3
3
  export type IBaselineViolations = IViolation[];
@@ -1,11 +1,8 @@
1
1
  import { ICruiseOptions } from "./options";
2
2
  import { IFlattenedRuleSet } from "./rule-set";
3
- import {
4
- DependencyType,
5
- ModuleSystemType,
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
  /**
@@ -224,27 +221,11 @@ export interface IDependency {
224
221
  * will be in the 'rule' object at the same level.
225
222
  */
226
223
  valid: boolean;
227
- }
228
-
229
- /**
230
- * If there was a rule violation (valid === false), this object contains the name of the
231
- * rule and severity of violating it.
232
- */
233
- export interface IRuleSummary {
234
224
  /**
235
- * The (short, eslint style) name of the violated rule. Typically something like
236
- * 'no-core-punycode' or 'no-outside-deps'.
225
+ * the (de-normalized) instability of the dependency - also available in
226
+ * the module on the 'to' side of this dependency
237
227
  */
238
- name: string;
239
- /**
240
- * How severe a violation of a rule is. The 'error' severity will make some reporters return
241
- * a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
242
- * use that. The absence of the 'ignore' severity here is by design; ignored rules don't
243
- * show up in the output.
244
- *
245
- * Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
246
- */
247
- severity: SeverityType;
228
+ instability: number;
248
229
  }
249
230
 
250
231
  export interface IReachable {
@@ -389,29 +370,6 @@ export interface IWebpackConfig {
389
370
  */
390
371
  export type WebpackEnvType = { [key: string]: any } | string;
391
372
 
392
- export interface IViolation {
393
- /**
394
- * The violated rule
395
- */
396
- rule: IRuleSummary;
397
- /**
398
- * The from part of the dependency this violation is about
399
- */
400
- from: string;
401
- /**
402
- * The to part of the dependency this violation is about
403
- */
404
- to: string;
405
- /**
406
- * The circular path if the violation is about circularity
407
- */
408
- cycle?: string[];
409
- /**
410
- * The path from the from to the to if the violation is transitive
411
- */
412
- via?: string[];
413
- }
414
-
415
373
  export interface IFolder {
416
374
  /**
417
375
  * The name of the folder. FOlder names are normalized to posix (so
@@ -455,3 +413,6 @@ export interface IFolder {
455
413
  */
456
414
  instability?: number;
457
415
  }
416
+
417
+ export * from "./violations";
418
+ export * from "./rule-summary";
@@ -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 {
@@ -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
+ }