dependency-cruiser 10.8.0-beta-1 → 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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/package.json +33 -22
  3. package/src/enrich/derive/metrics/folder.js +8 -0
  4. package/src/enrich/derive/{folders → metrics}/get-stability-metrics.js +4 -4
  5. package/src/enrich/derive/{folders → metrics}/module-utl.js +0 -0
  6. package/src/enrich/derive/metrics/module.js +28 -0
  7. package/src/enrich/derive/{folders → metrics}/utl.js +2 -2
  8. package/src/enrich/derive/utl.js +26 -10
  9. package/src/enrich/enrich-modules.js +5 -0
  10. package/src/enrich/index.js +2 -2
  11. package/src/enrich/soften-known-violations.js +4 -2
  12. package/src/extract/ast-extractors/swc-dependency-visitor.js +14 -2
  13. package/src/main/options/normalize.js +28 -0
  14. package/src/main/report-wrap.js +7 -2
  15. package/src/meta.js +1 -1
  16. package/src/report/dot/dot.template.js +1 -1
  17. package/src/report/dot/index.js +7 -2
  18. package/src/report/dot/module-utl.js +47 -19
  19. package/src/report/dot/prepare-custom-level.js +2 -2
  20. package/src/report/dot/prepare-flat-level.js +2 -2
  21. package/src/report/dot/prepare-folder-level.js +2 -2
  22. package/src/report/metrics.js +96 -47
  23. package/src/schema/configuration.schema.js +22 -0
  24. package/src/schema/cruise-result.schema.js +24 -0
  25. package/src/validate/match-dependency-rule.js +1 -0
  26. package/src/validate/matchers.js +12 -0
  27. package/types/baseline-violations.d.ts +1 -1
  28. package/types/cruise-result.d.ts +18 -48
  29. package/types/options.d.ts +5 -0
  30. package/types/reporter-options.d.ts +23 -0
  31. package/types/restrictions.d.ts +12 -0
  32. package/types/rule-summary.d.ts +23 -0
  33. package/types/violations.d.ts +24 -0
  34. package/src/enrich/derive/folders/index.js +0 -12
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-beta-1",
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/",
@@ -131,13 +132,13 @@
131
132
  "version": "npm-run-all build depcruise:graph:doc scm:stage"
132
133
  },
133
134
  "dependencies": {
134
- "acorn": "8.5.0",
135
+ "acorn": "8.6.0",
135
136
  "acorn-jsx": "5.3.2",
136
137
  "acorn-jsx-walk": "2.0.0",
137
138
  "acorn-loose": "8.2.1",
138
139
  "acorn-walk": "8.2.0",
139
- "ajv": "8.6.3",
140
- "chalk": "4.1.2",
140
+ "ajv": "8.8.2",
141
+ "chalk": "^4.1.2",
141
142
  "commander": "8.3.0",
142
143
  "enhanced-resolve": "5.8.3",
143
144
  "figures": "^3.2.0",
@@ -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
- "tsconfig-paths-webpack-plugin": "3.5.1",
155
+ "teamcity-service-messages": "0.1.12",
156
+ "tsconfig-paths-webpack-plugin": "3.5.2",
156
157
  "wrap-ansi": "^7.0.0"
157
158
  },
158
159
  "devDependencies": {
159
- "@babel/core": "7.15.8",
160
- "@babel/plugin-transform-modules-commonjs": "7.15.4",
161
- "@babel/preset-typescript": "7.15.0",
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",
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",
@@ -171,23 +172,23 @@
171
172
  "eslint-config-moving-meadow": "2.0.9",
172
173
  "eslint-config-prettier": "8.3.0",
173
174
  "eslint-plugin-budapestian": "3.0.1",
174
- "eslint-plugin-import": "2.25.2",
175
- "eslint-plugin-mocha": "9.0.0",
175
+ "eslint-plugin-import": "2.25.3",
176
+ "eslint-plugin-mocha": "^9.0.0",
176
177
  "eslint-plugin-node": "11.1.0",
177
178
  "eslint-plugin-security": "1.4.0",
178
- "eslint-plugin-unicorn": "37.0.1",
179
+ "eslint-plugin-unicorn": "39.0.0",
179
180
  "husky": "^4.3.8",
180
181
  "intercept-stdout": "0.1.2",
181
- "lint-staged": "11.2.6",
182
+ "lint-staged": "12.1.2",
182
183
  "mocha": "9.1.3",
183
184
  "normalize-newline": "^3.0.0",
184
185
  "npm-run-all": "4.1.5",
185
- "prettier": "2.4.1",
186
+ "prettier": "2.5.1",
186
187
  "proxyquire": "2.1.3",
187
188
  "shx": "0.3.3",
188
- "svelte": "3.44.0",
189
+ "svelte": "3.44.3",
189
190
  "symlink-dir": "5.0.1",
190
- "typescript": "4.4.4",
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"
@@ -199,6 +200,11 @@
199
200
  "policy": "wanted",
200
201
  "because": "some eslint plugins (eslint-plugin-budapestian) are not compatible with eslint 8 yet "
201
202
  },
203
+ {
204
+ "package": "chalk",
205
+ "policy": "wanted",
206
+ "because": "version 5 only exports ejs - and we use cjs and don't transpile"
207
+ },
202
208
  {
203
209
  "package": "figures",
204
210
  "policy": "wanted",
@@ -214,6 +220,11 @@
214
220
  "policy": "wanted",
215
221
  "because": "version 5 only exports ejs - and we use cjs and don't transpile"
216
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
+ },
217
228
  {
218
229
  "package": "normalize-newline",
219
230
  "policy": "wanted",
@@ -0,0 +1,8 @@
1
+ const getStabilityMetrics = require("./get-stability-metrics");
2
+
3
+ module.exports = function deriveMetrics(pModules, pOptions) {
4
+ if (pOptions.metrics) {
5
+ return { folders: getStabilityMetrics(pModules) };
6
+ }
7
+ return {};
8
+ };
@@ -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) =>
@@ -0,0 +1,28 @@
1
+ const { findModuleByName, clearCache } = require("../utl");
2
+ const { metricsAreCalculable } = require("./module-utl");
3
+
4
+ module.exports = function deriveModuleMetrics(pModules, pOptions) {
5
+ if (pOptions.metrics) {
6
+ const lModules = pModules.map((pModule) => ({
7
+ ...pModule,
8
+ ...(metricsAreCalculable(pModule)
9
+ ? {
10
+ instability:
11
+ pModule.dependencies.length /
12
+ (pModule.dependents.length + pModule.dependencies.length) || 0,
13
+ }
14
+ : {}),
15
+ }));
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
+ }));
26
+ }
27
+ return pModules;
28
+ };
@@ -10,7 +10,7 @@ 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],
@@ -19,5 +19,5 @@ function foldersObject2folderArray(pObject) {
19
19
 
20
20
  module.exports = {
21
21
  getParentFolders,
22
- foldersObject2folderArray,
22
+ object2Array,
23
23
  };
@@ -1,14 +1,26 @@
1
- const _memoize = require("lodash/memoize");
1
+ let gIndexedGraph = null;
2
2
 
3
- function bareFindModuleByName(pGraph, pSource) {
4
- return pGraph.find((pNode) => pNode.source === pSource);
3
+ /**
4
+ * Returns the module with attribute pSource, when it exists in the pModuleGraph.
5
+ * Returns undefined when it doesn't.
6
+ *
7
+ * This function uses an indexed cache for efficiency reasons. If you need to
8
+ * call this function consecutively for different module graphs, you can clear
9
+ * this cache with the clearCache function from this module.
10
+ *
11
+ * @param {import('../../../types/cruise-result').IModule[]} pModuleGraph
12
+ * @param {string} pSource
13
+ * @returns {import('../../../types/cruise-result').IModule | undefined}
14
+ */
15
+ function findModuleByName(pModuleGraph, pSource) {
16
+ if (!gIndexedGraph) {
17
+ gIndexedGraph = new Map(
18
+ pModuleGraph.map((pModule) => [pModule.source, pModule])
19
+ );
20
+ }
21
+ return gIndexedGraph.get(pSource);
5
22
  }
6
23
 
7
- const findModuleByName = _memoize(
8
- bareFindModuleByName,
9
- (_pGraph, pSource) => pSource
10
- );
11
-
12
24
  function isDependent(pResolvedName) {
13
25
  return (pModule) =>
14
26
  pModule.dependencies.some(
@@ -17,7 +29,11 @@ function isDependent(pResolvedName) {
17
29
  }
18
30
 
19
31
  function clearCache() {
20
- findModuleByName.cache.clear();
32
+ gIndexedGraph = null;
21
33
  }
22
34
 
23
- module.exports = { findModuleByName, clearCache, isDependent };
35
+ module.exports = {
36
+ findModuleByName,
37
+ clearCache,
38
+ isDependent,
39
+ };
@@ -8,6 +8,7 @@ const addDependents = require("./derive/dependents");
8
8
  const deriveReachable = require("./derive/reachable");
9
9
  const addValidations = require("./add-validations");
10
10
  const softenKnownViolations = require("./soften-known-violations");
11
+ const deriveModuleMetrics = require("./derive/metrics/module");
11
12
 
12
13
  module.exports = function enrichModules(pModules, pOptions) {
13
14
  bus.emit("progress", "analyzing: cycles", { level: busLogLevels.INFO });
@@ -18,6 +19,10 @@ module.exports = function enrichModules(pModules, pOptions) {
18
19
  lModules = deriveOrphans(lModules);
19
20
  bus.emit("progress", "analyzing: reachables", { level: busLogLevels.INFO });
20
21
  lModules = deriveReachable(lModules, pOptions.ruleSet);
22
+ bus.emit("progress", "analyzing: module metrics", {
23
+ level: busLogLevels.INFO,
24
+ });
25
+ lModules = deriveModuleMetrics(lModules, pOptions);
21
26
  bus.emit("progress", "analyzing: add focus (if any)", {
22
27
  level: busLogLevels.INFO,
23
28
  });
@@ -1,5 +1,5 @@
1
1
  const enrichModules = require("./enrich-modules");
2
- const deriveFolders = require("./derive/folders");
2
+ const deriveFolderMetrics = require("./derive/metrics/folder.js");
3
3
  const summarize = require("./summarize");
4
4
  const clearCaches = require("./clear-caches");
5
5
 
@@ -10,7 +10,7 @@ module.exports = function enrich(pModules, pOptions, pFileAndDirectoryArray) {
10
10
  clearCaches();
11
11
  return {
12
12
  modules: lModules,
13
- ...deriveFolders(lModules, pOptions),
13
+ ...deriveFolderMetrics(lModules, pOptions),
14
14
  summary: summarize(lModules, pOptions, pFileAndDirectoryArray),
15
15
  };
16
16
  };
@@ -78,7 +78,8 @@ function softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity) {
78
78
  pRule,
79
79
  pModule.source,
80
80
  pKnownViolations.filter(
81
- (pKnownError) => pKnownError.from === pKnownError.to
81
+ (pKnownError) =>
82
+ pKnownError.from === pKnownError.to && !pKnownError.cycle
82
83
  ),
83
84
  pSoftenedSeverity
84
85
  )
@@ -93,7 +94,8 @@ function softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity) {
93
94
  pDependency,
94
95
  pModule.source,
95
96
  pKnownViolations.filter(
96
- (pKnownError) => pKnownError.from !== pKnownError.to
97
+ (pKnownError) =>
98
+ pKnownError.from !== pKnownError.to || pKnownError.cycle
97
99
  ),
98
100
  pSoftenedSeverity
99
101
  )
@@ -119,7 +119,13 @@ if (VisitorModule) {
119
119
  // also include the same method, but with the correct spelling.
120
120
  visitExportAllDeclration(pNode) {
121
121
  this.pushImportExportSource(pNode);
122
- return super.visitExportAllDeclration(pNode);
122
+ /* c8 ignore start */
123
+ if (super.visitExportAllDeclration) {
124
+ return super.visitExportAllDeclration(pNode);
125
+ } else {
126
+ /* c8 ignore stop */
127
+ return super.visitExportAllDeclaration(pNode);
128
+ }
123
129
  }
124
130
 
125
131
  /* c8 ignore start */
@@ -131,7 +137,13 @@ if (VisitorModule) {
131
137
  // same spelling error as the above - same solution
132
138
  visitExportNamedDeclration(pNode) {
133
139
  this.pushImportExportSource(pNode);
134
- return super.visitExportNamedDeclration(pNode);
140
+ /* c8 ignore start */
141
+ if (super.visitExportNamedDeclration) {
142
+ return super.visitExportNamedDeclration(pNode);
143
+ } else {
144
+ /* c8 ignore stop */
145
+ return super.visitExportNamedDeclaration(pNode);
146
+ }
135
147
  }
136
148
  /* c8 ignore start */
137
149
  visitExportNamedDeclaration(pNode) {
@@ -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
 
@@ -28,13 +28,18 @@ function reSummarizeResults(pResult, pFormatOptions) {
28
28
 
29
29
  module.exports = function reportWrap(pResult, pFormatOptions) {
30
30
  const lReportFunction = report.getReporter(pFormatOptions.outputType);
31
+ const lReportOptions = _get(
32
+ pResult,
33
+ `summary.optionsUsed.reporterOptions.${pFormatOptions.outputType}`,
34
+ {}
35
+ );
31
36
 
32
37
  return lReportFunction(
33
38
  reSummarizeResults(pResult, pFormatOptions),
34
39
  // passing format options here so reporters that read collapse patterns
35
40
  // from the result take the one passed in the format options instead
36
41
  _has(pFormatOptions, "collapse")
37
- ? { collapsePattern: pFormatOptions.collapse }
38
- : {}
42
+ ? { ...lReportOptions, collapsePattern: pFormatOptions.collapse }
43
+ : lReportOptions
39
44
  );
40
45
  };
package/src/meta.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* generated - don't edit */
2
2
 
3
3
  module.exports = {
4
- version: "10.8.0-beta-1",
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", "")));
@@ -1,62 +1,110 @@
1
- const os = require("os");
1
+ const { EOL } = require("os");
2
2
  const chalk = require("chalk");
3
3
 
4
4
  const DECIMAL_BASE = 10;
5
5
  const METRIC_WIDTH = 4;
6
6
  const INSTABILITY_DECIMALS = 2;
7
7
  const YADDUM = DECIMAL_BASE ** INSTABILITY_DECIMALS;
8
+ const COMPONENT_HEADER = "name";
8
9
 
9
- function transformMetricsToTable(pMetrics) {
10
+ function getHeader(pMaxNameWidth) {
11
+ return `${COMPONENT_HEADER.padEnd(pMaxNameWidth)} ${"N".padStart(
12
+ METRIC_WIDTH + 1
13
+ )} ${"Ca".padStart(METRIC_WIDTH + 1)} ${"Ce".padStart(
14
+ METRIC_WIDTH + 1
15
+ )} ${"I".padEnd(METRIC_WIDTH + 1)}`;
16
+ }
17
+
18
+ function getDemarcationLine(pMaxNameWidth) {
19
+ return `${"-".repeat(pMaxNameWidth)} ${"-".repeat(
20
+ METRIC_WIDTH + 1
21
+ )} ${"-".repeat(METRIC_WIDTH + 1)} ${"-".repeat(
22
+ METRIC_WIDTH + 1
23
+ )} ${"-".repeat(METRIC_WIDTH + 1)}`;
24
+ }
25
+
26
+ function getMetricsTable(pMetrics, pMaxNameWidth) {
27
+ return pMetrics.map(
28
+ ({
29
+ name,
30
+ moduleCount,
31
+ afferentCouplings,
32
+ efferentCouplings,
33
+ instability,
34
+ }) =>
35
+ `${name.padEnd(pMaxNameWidth, " ")} ${moduleCount
36
+ .toString(DECIMAL_BASE)
37
+ .padStart(METRIC_WIDTH)} ${afferentCouplings
38
+ .toString(DECIMAL_BASE)
39
+ .padStart(METRIC_WIDTH)} ${efferentCouplings
40
+ .toString(DECIMAL_BASE)
41
+ .padStart(METRIC_WIDTH)} ${(Math.round(YADDUM * instability) / YADDUM)
42
+ .toString(DECIMAL_BASE)
43
+ .padEnd(METRIC_WIDTH)}`
44
+ );
45
+ }
46
+
47
+ function metricifyModule({ source, dependents, dependencies, instability }) {
48
+ return {
49
+ name: source,
50
+ moduleCount: 1,
51
+ afferentCouplings: dependents.length,
52
+ efferentCouplings: dependencies.length,
53
+ instability,
54
+ };
55
+ }
56
+
57
+ function metricsAreCalculable(pModule) {
58
+ return Object.prototype.hasOwnProperty.call(pModule, "instability");
59
+ }
60
+
61
+ function orderByNumber(pAttributeName) {
62
+ // eslint-disable-next-line security/detect-object-injection
63
+ return (pLeft, pRight) => pRight[pAttributeName] - pLeft[pAttributeName];
64
+ }
65
+
66
+ function orderByString(pAttributeName) {
67
+ return (pLeft, pRight) =>
68
+ // eslint-disable-next-line security/detect-object-injection
69
+ pLeft[pAttributeName].localeCompare(pRight[pAttributeName]);
70
+ }
71
+
72
+ function transformMetricsToTable(
73
+ { modules, folders },
74
+ { orderBy, hideFolders, hideModules }
75
+ ) {
10
76
  // TODO: should probably use a table module for this (i.e. text-table)
11
- // to simplify this code; 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)
77
+ // to simplify this code
78
+ let lComponents = [];
79
+ lComponents = lComponents.concat(hideFolders ? [] : folders);
80
+ lComponents = lComponents.concat(
81
+ hideModules ? [] : modules.filter(metricsAreCalculable).map(metricifyModule)
82
+ );
83
+ const lMaxNameWidth = lComponents
84
+ .map(({ name }) => name.length)
85
+ .concat(COMPONENT_HEADER.length)
15
86
  .sort((pLeft, pRight) => pLeft - pRight)
16
87
  .pop();
17
88
 
18
- return [
19
- 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
- )
89
+ return [chalk.bold(getHeader(lMaxNameWidth))]
90
+ .concat(getDemarcationLine(lMaxNameWidth))
34
91
  .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
- })
92
+ getMetricsTable(
93
+ lComponents
94
+ .sort(orderByString("name"))
95
+ .sort(orderByNumber(orderBy || "instability")),
96
+ lMaxNameWidth
97
+ )
51
98
  )
52
- .join(os.EOL)
53
- .concat(os.EOL);
99
+ .join(EOL)
100
+ .concat(EOL);
54
101
  }
55
102
 
56
103
  /**
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)
104
+ * returns stability metrics of modules & folders in an ascii table
105
+ *
106
+ * Potential future features:
107
+ * - additional output formats (csv?, html?)
60
108
  *
61
109
  * @param {import('../../types/dependency-cruiser').ICruiseResult} pCruiseResult -
62
110
  * the output of a dependency-cruise adhering to dependency-cruiser's
@@ -65,17 +113,18 @@ function transformMetricsToTable(pMetrics) {
65
113
  * output: some metrics on folders and dependencies
66
114
  * exitCode: 0
67
115
  */
68
- module.exports = (pCruiseResult) => {
116
+ module.exports = (pCruiseResult, pReporterOptions) => {
117
+ const lReporterOptions = pReporterOptions || {};
69
118
  if (pCruiseResult.folders) {
70
119
  return {
71
- output: transformMetricsToTable(pCruiseResult.folders),
120
+ output: transformMetricsToTable(pCruiseResult, lReporterOptions),
72
121
  exitCode: 0,
73
122
  };
74
123
  } else {
75
124
  return {
76
125
  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}`,
126
+ `${EOL}ERROR: The cruise result didn't contain any metrics - re-running the cruise with${EOL}` +
127
+ ` the '--metrics' command line option should fix that.${EOL}${EOL}`,
79
128
  exitCode: 1,
80
129
  };
81
130
  }
@@ -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
  },
@@ -394,6 +396,7 @@ module.exports = {
394
396
  dot: { $ref: "#/definitions/DotReporterOptionsType" },
395
397
  ddot: { $ref: "#/definitions/DotReporterOptionsType" },
396
398
  flat: { $ref: "#/definitions/DotReporterOptionsType" },
399
+ metrics: { $ref: "#/definitions/MetricsReporterOptionsType" },
397
400
  },
398
401
  },
399
402
  AnonReporterOptionsType: {
@@ -401,12 +404,31 @@ module.exports = {
401
404
  additionalProperties: false,
402
405
  properties: { wordlist: { type: "array", items: { type: "string" } } },
403
406
  },
407
+ MetricsReporterOptionsType: {
408
+ type: "object",
409
+ additionalProperties: false,
410
+ properties: {
411
+ orderBy: {
412
+ type: "string",
413
+ enum: [
414
+ "instability",
415
+ "moduleCount",
416
+ "afferentCouplings",
417
+ "efferentCouplings",
418
+ "name",
419
+ ],
420
+ },
421
+ hideModules: { type: "boolean" },
422
+ hideFolders: { type: "boolean" },
423
+ },
424
+ },
404
425
  DotReporterOptionsType: {
405
426
  type: "object",
406
427
  additionalProperties: false,
407
428
  properties: {
408
429
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
409
430
  filters: { $ref: "#/definitions/ReporterFiltersType" },
431
+ showMetrics: { type: "boolean" },
410
432
  theme: { $ref: "#/definitions/DotThemeType" },
411
433
  },
412
434
  },
@@ -47,6 +47,7 @@ module.exports = {
47
47
  items: { $ref: "#/definitions/RuleSummaryType" },
48
48
  },
49
49
  consolidated: { type: "boolean" },
50
+ instability: { type: "number" },
50
51
  },
51
52
  },
52
53
  ReachableType: {
@@ -126,6 +127,7 @@ module.exports = {
126
127
  type: "array",
127
128
  items: { $ref: "#/definitions/RuleSummaryType" },
128
129
  },
130
+ instability: { type: "number" },
129
131
  },
130
132
  },
131
133
  DependencyTypeType: {
@@ -356,6 +358,7 @@ module.exports = {
356
358
  licenseNot: { $ref: "#/definitions/REAsStringsType" },
357
359
  via: { $ref: "#/definitions/REAsStringsType" },
358
360
  viaNot: { $ref: "#/definitions/REAsStringsType" },
361
+ moreUnstable: { type: "boolean" },
359
362
  },
360
363
  },
361
364
  DependentsModuleRestrictionType: {
@@ -495,6 +498,7 @@ module.exports = {
495
498
  },
496
499
  },
497
500
  },
501
+ metrics: { type: "boolean" },
498
502
  baseDir: { type: "string" },
499
503
  args: { type: "string" },
500
504
  rulesFile: { type: "string" },
@@ -570,6 +574,7 @@ module.exports = {
570
574
  dot: { $ref: "#/definitions/DotReporterOptionsType" },
571
575
  ddot: { $ref: "#/definitions/DotReporterOptionsType" },
572
576
  flat: { $ref: "#/definitions/DotReporterOptionsType" },
577
+ metrics: { $ref: "#/definitions/MetricsReporterOptionsType" },
573
578
  },
574
579
  },
575
580
  AnonReporterOptionsType: {
@@ -577,12 +582,31 @@ module.exports = {
577
582
  additionalProperties: false,
578
583
  properties: { wordlist: { type: "array", items: { type: "string" } } },
579
584
  },
585
+ MetricsReporterOptionsType: {
586
+ type: "object",
587
+ additionalProperties: false,
588
+ properties: {
589
+ orderBy: {
590
+ type: "string",
591
+ enum: [
592
+ "instability",
593
+ "moduleCount",
594
+ "afferentCouplings",
595
+ "efferentCouplings",
596
+ "name",
597
+ ],
598
+ },
599
+ hideModules: { type: "boolean" },
600
+ hideFolders: { type: "boolean" },
601
+ },
602
+ },
580
603
  DotReporterOptionsType: {
581
604
  type: "object",
582
605
  additionalProperties: false,
583
606
  properties: {
584
607
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
585
608
  filters: { $ref: "#/definitions/ReporterFiltersType" },
609
+ showMetrics: { type: "boolean" },
586
610
  theme: { $ref: "#/definitions/DotThemeType" },
587
611
  },
588
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
  /**
@@ -106,6 +103,15 @@ export interface IModule {
106
103
  * purposes - it will not be present after a regular cruise.
107
104
  */
108
105
  consolidated?: boolean;
106
+ /**
107
+ * "number of dependents/ (number of dependents + number of dependencies)
108
+ * A measure for how stable the module is; ranging between 0 (completely
109
+ * stable module) to 1 (completely instable module). Derived from Uncle
110
+ * Bob's instability metric - but applied to a single module instead of
111
+ * to a group of them. This attribute is only present when dependency-cruiser
112
+ * was asked to calculate metrics.
113
+ */
114
+ instability?: number;
109
115
  }
110
116
 
111
117
  export interface IDependency {
@@ -215,27 +221,11 @@ export interface IDependency {
215
221
  * will be in the 'rule' object at the same level.
216
222
  */
217
223
  valid: boolean;
218
- }
219
-
220
- /**
221
- * If there was a rule violation (valid === false), this object contains the name of the
222
- * rule and severity of violating it.
223
- */
224
- export interface IRuleSummary {
225
224
  /**
226
- * The (short, eslint style) name of the violated rule. Typically something like
227
- * '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
228
227
  */
229
- name: string;
230
- /**
231
- * How severe a violation of a rule is. The 'error' severity will make some reporters return
232
- * a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
233
- * use that. The absence of the 'ignore' severity here is by design; ignored rules don't
234
- * show up in the output.
235
- *
236
- * Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
237
- */
238
- severity: SeverityType;
228
+ instability: number;
239
229
  }
240
230
 
241
231
  export interface IReachable {
@@ -380,29 +370,6 @@ export interface IWebpackConfig {
380
370
  */
381
371
  export type WebpackEnvType = { [key: string]: any } | string;
382
372
 
383
- export interface IViolation {
384
- /**
385
- * The violated rule
386
- */
387
- rule: IRuleSummary;
388
- /**
389
- * The from part of the dependency this violation is about
390
- */
391
- from: string;
392
- /**
393
- * The to part of the dependency this violation is about
394
- */
395
- to: string;
396
- /**
397
- * The circular path if the violation is about circularity
398
- */
399
- cycle?: string[];
400
- /**
401
- * The path from the from to the to if the violation is transitive
402
- */
403
- via?: string[];
404
- }
405
-
406
373
  export interface IFolder {
407
374
  /**
408
375
  * The name of the folder. FOlder names are normalized to posix (so
@@ -446,3 +413,6 @@ export interface IFolder {
446
413
  */
447
414
  instability?: number;
448
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
  }
@@ -21,6 +21,10 @@ export interface IReporterOptions {
21
21
  * Options to tweak the output of the flat /fdot reporter
22
22
  */
23
23
  flat?: IDotReporterOptions;
24
+ /**
25
+ * Options to tweak the output of the metrics reporter
26
+ */
27
+ metrics?: IMetricsReporterOptions;
24
28
  }
25
29
 
26
30
  export interface IReporterFiltersType {
@@ -54,6 +58,12 @@ export interface IDotReporterOptions {
54
58
  * goal of the report)
55
59
  */
56
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;
57
67
  /**
58
68
  * A bunch of criteria to (conditionally) theme the dot output
59
69
  */
@@ -96,3 +106,16 @@ export interface IDotThemeEntry {
96
106
  criteria: any;
97
107
  attributes: any;
98
108
  }
109
+
110
+ export interface IMetricsReporterOptions {
111
+ hideModules?: boolean;
112
+ hideFolders?: boolean;
113
+ oderBy?: MetricsOrderByType;
114
+ }
115
+
116
+ export type MetricsOrderByType =
117
+ | "instability"
118
+ | "moduleCount"
119
+ | "afferentCouplings"
120
+ | "efferentCouplings"
121
+ | "name";
@@ -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
+ }
@@ -1,12 +0,0 @@
1
- const getStabilityMetrics = require("./get-stability-metrics");
2
-
3
- function shouldDeriveFolders(pOptions) {
4
- return pOptions.metrics || pOptions.outputType === "metrics";
5
- }
6
-
7
- module.exports = function deriveFolders(pModules, pOptions) {
8
- if (shouldDeriveFolders(pOptions)) {
9
- return { folders: getStabilityMetrics(pModules) };
10
- }
11
- return {};
12
- };