dependency-cruiser 11.0.0 → 11.3.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 (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/package.json +42 -32
  4. package/src/cli/format.js +0 -1
  5. package/src/enrich/clear-caches.js +1 -1
  6. package/src/enrich/derive/circular/get-cycle.js +1 -1
  7. package/src/enrich/derive/dependents/get-dependents.js +1 -1
  8. package/src/enrich/derive/{metrics/get-stability-metrics.js → folders/aggregate-to-folders.js} +39 -30
  9. package/src/enrich/derive/folders/index.js +46 -0
  10. package/src/enrich/derive/folders/utl.js +44 -0
  11. package/src/enrich/derive/metrics/get-module-metrics.js +39 -0
  12. package/src/enrich/derive/metrics/index.js +14 -0
  13. package/src/enrich/derive/{utl.js → module-utl.js} +28 -0
  14. package/src/enrich/derive/orphan/is-orphan.js +1 -1
  15. package/src/enrich/derive/reachable/get-path.js +1 -1
  16. package/src/enrich/derive/reachable/index.js +1 -1
  17. package/src/enrich/enrich-modules.js +2 -2
  18. package/src/enrich/index.js +12 -4
  19. package/src/enrich/summarize/get-stats.js +31 -0
  20. package/src/enrich/summarize/index.js +25 -6
  21. package/src/enrich/summarize/summarize-folders.js +38 -0
  22. package/src/enrich/summarize/summarize-modules.js +25 -32
  23. package/src/enrich/summarize/summarize-options.js +13 -9
  24. package/src/extract/ast-extractors/swc-dependency-visitor.js +29 -5
  25. package/src/extract/transpile/vue-template-wrap.js +13 -1
  26. package/src/main/options/normalize.js +28 -0
  27. package/src/main/report-wrap.js +17 -3
  28. package/src/meta.js +1 -1
  29. package/src/report/dot/dot.template.js +1 -1
  30. package/src/report/dot/index.js +7 -2
  31. package/src/report/dot/module-utl.js +47 -19
  32. package/src/report/dot/prepare-custom-level.js +2 -2
  33. package/src/report/dot/prepare-flat-level.js +2 -2
  34. package/src/report/dot/prepare-folder-level.js +2 -2
  35. package/src/report/error-html/error-html.template.js +1 -1
  36. package/src/report/error-html/utl.js +50 -7
  37. package/src/report/error.js +55 -21
  38. package/src/report/teamcity.js +37 -11
  39. package/src/report/utl/index.js +16 -0
  40. package/src/schema/baseline-violations.schema.js +1 -35
  41. package/src/schema/configuration.schema.js +1 -493
  42. package/src/schema/cruise-result.schema.js +1 -642
  43. package/src/utl/regex-util.js +57 -0
  44. package/src/validate/index.js +25 -22
  45. package/src/validate/match-dependency-rule.js +22 -34
  46. package/src/validate/match-folder-dependency-rule.js +19 -0
  47. package/src/validate/match-module-rule.js +4 -3
  48. package/src/validate/matchers.js +58 -48
  49. package/src/validate/rule-classifiers.js +21 -0
  50. package/src/validate/violates-required-rule.js +1 -1
  51. package/types/cruise-result.d.ts +7 -2
  52. package/types/dependency-cruiser.d.ts +2 -3
  53. package/types/options.d.ts +37 -0
  54. package/types/reporter-options.d.ts +6 -0
  55. package/types/restrictions.d.ts +12 -0
  56. package/types/rule-set.d.ts +12 -1
  57. package/types/shared-types.d.ts +9 -0
  58. package/types/violations.d.ts +19 -0
  59. package/src/enrich/derive/metrics/folder.js +0 -9
  60. package/src/enrich/derive/metrics/module-utl.js +0 -27
  61. package/src/enrich/derive/metrics/module.js +0 -29
  62. package/src/enrich/derive/metrics/utl.js +0 -28
  63. package/src/validate/is-module-only-rule.js +0 -16
  64. package/src/validate/utl.js +0 -31
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016-2021 Sander Verweij
3
+ Copyright (c) 2016-2022 Sander Verweij
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -21,7 +21,7 @@ you can stick on the wall to impress your grandma.
21
21
  ### Install it
22
22
 
23
23
  - `npm install --save-dev dependency-cruiser` to use it as a validator in your project (recommended) or...
24
- - `npm install --global dependency-cruiser` if you just want to to inspect multiple projects.
24
+ - `npm install --global dependency-cruiser` if you just want to inspect multiple projects.
25
25
 
26
26
  ### Show stuff to your grandma
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "11.0.0",
3
+ "version": "11.3.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/",
@@ -112,8 +113,11 @@
112
113
  "scm:push:gitlab-mirror:tags": "git push --tags gitlab-mirror",
113
114
  "scm:stage": "git add .",
114
115
  "test": "mocha --timeout 4000 \"test/**/*.spec.{js,mjs,cjs}\"",
116
+ "test:i": "mocha --timeout 4000 \"test/**/*.spec.{js,mjs,cjs}\" -g \"^\\[[I]\\]\"",
117
+ "test:u": "mocha --timeout 4000 \"test/**/*.spec.{js,mjs,cjs}\" --grep \"^\\[[U]\\]\"",
118
+ "test:e": "mocha --timeout 4000 \"test/**/*.spec.{js,mjs,cjs}\" --grep \"^\\[[E]\\]\"",
115
119
  "test:cover": "c8 --check-coverage --statements 99.9 --branches 99.7 --functions 100 --lines 99.9 --exclude \"{bin,configs,doc,docs,coverage,test,tools,webpack.conf.js,tmp*,src/**/*.template.js,src/cli/tools/svg-in-html-snippets/script.snippet.js,src/cli/init-config/get-user-input.js,src/cli/listeners/*/index.js}\" --reporter text-summary --reporter html --reporter json-summary npm test",
116
- "test:glob": "set -f && test \"`bin/dependency-cruise.js test/extract/fixtures/gather-globbing/packages/**/src/**/*.js | grep \"no dependency violations found\"`\" = \"✔ no dependency violations found (6 modules, 0 dependencies cruised)\"",
120
+ "test:glob": "set -f && test \"`bin/dependency-cruise.js test/extract/__mocks__/gather-globbing/packages/**/src/**/*.js | grep \"no dependency violations found\"`\" = \"✔ no dependency violations found (6 modules, 0 dependencies cruised)\"",
117
121
  "test:yarn-pnp": "npm-run-all test:yarn-pnp:cleanup test:yarn-pnp:pack test:yarn-pnp:copy test:yarn-pnp:install test:yarn-pnp:version test:yarn-pnp:run test:yarn-pnp:test test:yarn-pnp:cleanup",
118
122
  "test:yarn-pnp:pack": "npm pack",
119
123
  "test:yarn-pnp:copy": "shx cp -r test/integration/yarn-pnp.template test/integration/yarn-pnp.testing-ground",
@@ -131,12 +135,12 @@
131
135
  "version": "npm-run-all build depcruise:graph:doc scm:stage"
132
136
  },
133
137
  "dependencies": {
134
- "acorn": "8.6.0",
138
+ "acorn": "8.7.0",
135
139
  "acorn-jsx": "5.3.2",
136
140
  "acorn-jsx-walk": "2.0.0",
137
- "acorn-loose": "8.2.1",
141
+ "acorn-loose": "8.3.0",
138
142
  "acorn-walk": "8.2.0",
139
- "ajv": "8.8.2",
143
+ "ajv": "8.9.0",
140
144
  "chalk": "^4.1.2",
141
145
  "commander": "8.3.0",
142
146
  "enhanced-resolve": "5.8.3",
@@ -151,19 +155,19 @@
151
155
  "safe-regex": "2.1.1",
152
156
  "semver": "^7.3.5",
153
157
  "semver-try-require": "^5.0.1",
154
- "teamcity-service-messages": "0.1.11",
158
+ "teamcity-service-messages": "0.1.12",
155
159
  "tsconfig-paths-webpack-plugin": "3.5.2",
156
160
  "wrap-ansi": "^7.0.0"
157
161
  },
158
162
  "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",
166
- "c8": "7.10.0",
163
+ "@babel/core": "7.16.12",
164
+ "@babel/plugin-transform-modules-commonjs": "7.16.8",
165
+ "@babel/preset-typescript": "7.16.7",
166
+ "@swc/core": "1.2.133",
167
+ "@typescript-eslint/eslint-plugin": "5.10.0",
168
+ "@typescript-eslint/parser": "5.10.0",
169
+ "@vue/compiler-sfc": "3.2.28",
170
+ "c8": "7.11.0",
167
171
  "chai": "4.3.4",
168
172
  "chai-json-schema": "1.5.1",
169
173
  "coffeescript": "2.6.1",
@@ -171,23 +175,23 @@
171
175
  "eslint-config-moving-meadow": "2.0.9",
172
176
  "eslint-config-prettier": "8.3.0",
173
177
  "eslint-plugin-budapestian": "3.0.1",
174
- "eslint-plugin-import": "2.25.3",
175
- "eslint-plugin-mocha": "9.0.0",
178
+ "eslint-plugin-import": "2.25.4",
179
+ "eslint-plugin-mocha": "^9.0.0",
176
180
  "eslint-plugin-node": "11.1.0",
177
181
  "eslint-plugin-security": "1.4.0",
178
- "eslint-plugin-unicorn": "39.0.0",
182
+ "eslint-plugin-unicorn": "40.1.0",
179
183
  "husky": "^4.3.8",
180
184
  "intercept-stdout": "0.1.2",
181
- "lint-staged": "12.1.2",
182
- "mocha": "9.1.3",
185
+ "lint-staged": "12.3.0",
186
+ "mocha": "9.1.4",
183
187
  "normalize-newline": "^3.0.0",
184
188
  "npm-run-all": "4.1.5",
185
- "prettier": "2.5.0",
189
+ "prettier": "2.5.1",
186
190
  "proxyquire": "2.1.3",
187
- "shx": "0.3.3",
188
- "svelte": "3.44.2",
191
+ "shx": "0.3.4",
192
+ "svelte": "3.46.2",
189
193
  "symlink-dir": "5.0.1",
190
- "typescript": "4.5.2",
194
+ "typescript": "4.5.5",
191
195
  "upem": "^7.0.0",
192
196
  "vue-template-compiler": "2.6.14",
193
197
  "yarn": "1.22.17"
@@ -219,6 +223,11 @@
219
223
  "policy": "wanted",
220
224
  "because": "version 5 only exports ejs - and we use cjs and don't transpile"
221
225
  },
226
+ {
227
+ "package": "eslint-plugin-mocha",
228
+ "policy": "wanted",
229
+ "because": "version 10 dropped support for node 12, which we still do support"
230
+ },
222
231
  {
223
232
  "package": "normalize-newline",
224
233
  "policy": "wanted",
@@ -241,10 +250,11 @@
241
250
  "src/**/*.template.js",
242
251
  "src/cli/tools/svg-in-html-snippets/script.snippet.js",
243
252
  "test/integration/**",
244
- "test/*/fixtures/**",
245
- "test/*/*/fixtures/**",
246
- "test/*/mocks/**",
247
- "test/*/*/mocks/**",
253
+ "test/*/__fixtures__/**",
254
+ "test/*/*/__fixtures__/**",
255
+ "test/*/*/*/__fixtures__/**",
256
+ "test/*/__mocks__/**",
257
+ "test/*/*/__mocks__/**",
248
258
  "types/**"
249
259
  ],
250
260
  "engines": {
@@ -277,13 +287,13 @@
277
287
  "eslint --fix",
278
288
  "prettier --write"
279
289
  ],
280
- "test/**/*.{utl,spec}.{mjs,js}": [
290
+ "test/**/*.{utl,spec}.{mjs,cjs}": [
281
291
  "eslint --fix",
282
292
  "prettier --write"
283
293
  ],
284
- "*.d.ts": [
294
+ "types/**/*.d.ts": [
285
295
  "eslint --config types/.eslintrc.json --fix",
286
- "prettier --write --ignore-path .prettierignore"
296
+ "prettier --write"
287
297
  ]
288
298
  }
289
299
  }
package/src/cli/format.js CHANGED
@@ -6,7 +6,6 @@ const io = require("./utl/io");
6
6
 
7
7
  const KNOWN_FMT_OPTIONS = [
8
8
  "collapse",
9
- "doNotFollow",
10
9
  "exclude",
11
10
  "focus",
12
11
  "help",
@@ -1,4 +1,4 @@
1
- const utl = require("./derive/utl");
1
+ const utl = require("./derive/module-utl");
2
2
 
3
3
  module.exports = function clearCaches() {
4
4
  utl.clearCache();
@@ -1,4 +1,4 @@
1
- const { findModuleByName } = require("../utl");
1
+ const { findModuleByName } = require("../module-utl");
2
2
  /* about the absence of checks whether attributes/ objects actually
3
3
  * exist:
4
4
  * - it saves CPU cycles to the effect of being ~30% faster than with the
@@ -1,4 +1,4 @@
1
- const { isDependent } = require("../utl");
1
+ const { isDependent } = require("../module-utl");
2
2
 
3
3
  module.exports = function getDependents(pModule, pModules) {
4
4
  // perf between O(n) in an unconnected graph and O(n^2) in a fully connected one
@@ -1,11 +1,12 @@
1
1
  /* eslint-disable security/detect-object-injection */
2
2
  const path = require("path").posix;
3
- const { foldersObject2folderArray, getParentFolders } = require("./utl");
3
+ const { calculateInstability, metricsAreCalculable } = require("../module-utl");
4
4
  const {
5
5
  getAfferentCouplings,
6
6
  getEfferentCouplings,
7
- metricsAreCalculable,
8
- } = require("./module-utl");
7
+ getParentFolders,
8
+ object2Array,
9
+ } = require("./utl");
9
10
 
10
11
  function upsertCouplings(pAllDependents, pNewDependents) {
11
12
  pNewDependents.forEach((pNewDependent) => {
@@ -34,16 +35,14 @@ function upsertFolderAttributes(pAllMetrics, pModule, pDirname) {
34
35
  )
35
36
  );
36
37
  pAllMetrics[pDirname].moduleCount += 1;
37
-
38
38
  return pAllMetrics;
39
39
  }
40
40
 
41
- function orderFolderMetrics(pLeftMetric, pRightMetric) {
42
- // return pLeft.name.localeCompare(pRight.name);
43
- // For intended use in a table it's probably more useful to sort by
44
- // instability. Might need to be either configurable or flexible
45
- // in the output, though
46
- return pRightMetric.instability - pLeftMetric.instability;
41
+ function aggregateToFolder(pAllFolders, pModule) {
42
+ getParentFolders(path.dirname(pModule.source)).forEach((pParentDirectory) =>
43
+ upsertFolderAttributes(pAllFolders, pModule, pParentDirectory)
44
+ );
45
+ return pAllFolders;
47
46
  }
48
47
 
49
48
  function sumCounts(pAll, pCurrent) {
@@ -59,12 +58,15 @@ function getFolderLevelCouplings(pCouplingArray) {
59
58
  : path.dirname(pCoupling.name)
60
59
  )
61
60
  )
62
- );
61
+ ).map((pCoupling) => ({ name: pCoupling }));
63
62
  }
64
63
 
65
64
  function calculateFolderMetrics(pFolder) {
66
- const lModuleDependents = foldersObject2folderArray(pFolder.dependents);
67
- const lModuleDependencies = foldersObject2folderArray(pFolder.dependencies);
65
+ const lModuleDependents = object2Array(pFolder.dependents);
66
+ const lModuleDependencies = object2Array(pFolder.dependencies);
67
+ // this calculation might look superfluous (why not just .length the dependents
68
+ // and dependencies?), but it isn't because there can be > 1 relation between
69
+ // two folders
68
70
  const lAfferentCouplings = lModuleDependents.reduce(sumCounts, 0);
69
71
  const lEfferentCouplings = lModuleDependencies.reduce(sumCounts, 0);
70
72
 
@@ -72,26 +74,33 @@ function calculateFolderMetrics(pFolder) {
72
74
  ...pFolder,
73
75
  afferentCouplings: lAfferentCouplings,
74
76
  efferentCouplings: lEfferentCouplings,
75
- // when both afferentCouplings and efferentCouplings equal 0 instability will
76
- // yield NaN. Judging Bob Martin's intention, a component with no outgoing
77
- // dependencies is maximum stable (0)
78
- instability:
79
- lEfferentCouplings / (lEfferentCouplings + lAfferentCouplings) || 0,
77
+ instability: calculateInstability(lEfferentCouplings, lAfferentCouplings),
80
78
  dependents: getFolderLevelCouplings(lModuleDependents),
81
79
  dependencies: getFolderLevelCouplings(lModuleDependencies),
82
80
  };
83
81
  }
84
82
 
85
- module.exports = function getStabilityMetrics(pModules) {
86
- return foldersObject2folderArray(
87
- pModules.filter(metricsAreCalculable).reduce((pAllFolders, pModule) => {
88
- getParentFolders(path.dirname(pModule.source)).forEach(
89
- (pParentDirectory) =>
90
- upsertFolderAttributes(pAllFolders, pModule, pParentDirectory)
91
- );
92
- return pAllFolders;
93
- }, {})
94
- )
95
- .map(calculateFolderMetrics)
96
- .sort(orderFolderMetrics);
83
+ function findFolderByName(pAllFolders, pName) {
84
+ return pAllFolders.find((pFolder) => pFolder.name === pName);
85
+ }
86
+
87
+ function denormalizeInstability(pFolder, _, pAllFolders) {
88
+ return {
89
+ ...pFolder,
90
+ dependencies: pFolder.dependencies.map((pDependency) => {
91
+ const lFolder = findFolderByName(pAllFolders, pDependency.name) || {};
92
+ return {
93
+ ...pDependency,
94
+ instability: lFolder.instability >= 0 ? lFolder.instability : 0,
95
+ };
96
+ }),
97
+ };
98
+ }
99
+
100
+ module.exports = function aggregateToFolders(pModules) {
101
+ const lFolders = object2Array(
102
+ pModules.filter(metricsAreCalculable).reduce(aggregateToFolder, {})
103
+ ).map(calculateFolderMetrics);
104
+
105
+ return lFolders.map(denormalizeInstability);
97
106
  };
@@ -0,0 +1,46 @@
1
+ const validate = require("../../../validate");
2
+ const aggregateToFolders = require("./aggregate-to-folders");
3
+
4
+ /**
5
+ *
6
+ * @param {*} pFolder
7
+ * @param {import('../../../../types/dependency-cruiser').IOptions} pOptions
8
+ * @returns
9
+ */
10
+ function validateFolderDependency(pFolder, pOptions) {
11
+ return (pDependency) => {
12
+ return {
13
+ ...pDependency,
14
+ ...validate.folder(pOptions.ruleSet || {}, pFolder, pDependency),
15
+ };
16
+ };
17
+ }
18
+
19
+ function addFolderDependencyViolations(pOptions) {
20
+ return (pFolder) => {
21
+ return {
22
+ ...pFolder,
23
+ dependencies: pFolder.dependencies.map(
24
+ validateFolderDependency(pFolder, pOptions)
25
+ ),
26
+ };
27
+ };
28
+ }
29
+
30
+ /**
31
+ *
32
+ * @param {import('../../../../types/dependency-cruiser').IModule[]} pModules
33
+ * @param {import('../../../../types/dependency-cruiser').IOptions} pOptions
34
+ * @returns {any}
35
+ */
36
+ module.exports = function deriveFolderMetrics(pModules, pOptions) {
37
+ let lReturnValue = {};
38
+ if (pOptions.metrics) {
39
+ lReturnValue = {
40
+ folders: aggregateToFolders(pModules).map(
41
+ addFolderDependencyViolations(pOptions)
42
+ ),
43
+ };
44
+ }
45
+ return lReturnValue;
46
+ };
@@ -0,0 +1,44 @@
1
+ /* eslint-disable security/detect-object-injection */
2
+ const path = require("path").posix;
3
+
4
+ function getAfferentCouplings(pModule, pDirname) {
5
+ return pModule.dependents.filter(
6
+ (pDependent) => !pDependent.startsWith(pDirname.concat(path.sep))
7
+ );
8
+ }
9
+
10
+ function getEfferentCouplings(pModule, pDirname) {
11
+ return pModule.dependencies.filter(
12
+ (pDependency) => !pDependency.resolved.startsWith(pDirname.concat(path.sep))
13
+ );
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param {string} pPath
19
+ * @returns string[]
20
+ */
21
+ function getParentFolders(pPath) {
22
+ let lFragments = pPath.split("/");
23
+ let lReturnValue = [];
24
+
25
+ while (lFragments.length > 0) {
26
+ lReturnValue.push(lFragments.join("/"));
27
+ lFragments.pop();
28
+ }
29
+ return lReturnValue.reverse();
30
+ }
31
+
32
+ function object2Array(pObject) {
33
+ return Object.keys(pObject).map((pKey) => ({
34
+ name: pKey,
35
+ ...pObject[pKey],
36
+ }));
37
+ }
38
+
39
+ module.exports = {
40
+ getAfferentCouplings,
41
+ getEfferentCouplings,
42
+ getParentFolders,
43
+ object2Array,
44
+ };
@@ -0,0 +1,39 @@
1
+ const { findModuleByName } = require("../module-utl");
2
+ const { calculateInstability, metricsAreCalculable } = require("../module-utl");
3
+
4
+ function addInstabilityMetric(pModule) {
5
+ return {
6
+ ...pModule,
7
+ ...(metricsAreCalculable(pModule)
8
+ ? {
9
+ instability: calculateInstability(
10
+ pModule.dependencies.length,
11
+ pModule.dependents.length
12
+ ),
13
+ }
14
+ : {}),
15
+ };
16
+ }
17
+
18
+ function addInstabilityToDependency(pAllModules) {
19
+ return (pDependency) => ({
20
+ ...pDependency,
21
+ instability:
22
+ (findModuleByName(pAllModules, pDependency.resolved) || {}).instability ||
23
+ 0,
24
+ });
25
+ }
26
+
27
+ function deNormalizeInstabilityMetricsToDependencies(pModule, _, pAllModules) {
28
+ return {
29
+ ...pModule,
30
+ dependencies: pModule.dependencies.map(
31
+ addInstabilityToDependency(pAllModules)
32
+ ),
33
+ };
34
+ }
35
+
36
+ module.exports = {
37
+ addInstabilityMetric,
38
+ deNormalizeInstabilityMetricsToDependencies,
39
+ };
@@ -0,0 +1,14 @@
1
+ const { clearCache } = require("../module-utl");
2
+ const {
3
+ addInstabilityMetric,
4
+ deNormalizeInstabilityMetricsToDependencies,
5
+ } = require("./get-module-metrics");
6
+
7
+ module.exports = function deriveModulesMetrics(pModules, pOptions) {
8
+ if (pOptions.metrics) {
9
+ const lModules = pModules.map(addInstabilityMetric);
10
+ clearCache();
11
+ return lModules.map(deNormalizeInstabilityMetricsToDependencies);
12
+ }
13
+ return pModules;
14
+ };
@@ -28,6 +28,32 @@ function isDependent(pResolvedName) {
28
28
  );
29
29
  }
30
30
 
31
+ function metricsAreCalculable(pModule) {
32
+ return (
33
+ !pModule.coreModule &&
34
+ !pModule.couldNotResolve &&
35
+ !pModule.matchesDoNotFollow
36
+ );
37
+ }
38
+
39
+ /**
40
+ * returns the Instability of a component given the number of incoming (afferent)
41
+ * and outgoign (efferent) connections ('couplings')
42
+ *
43
+ * @param {number} pEfferentCouplingCount
44
+ * @param {number} pAfferentCouplingCount
45
+ * @returns number
46
+ */
47
+ function calculateInstability(pEfferentCouplingCount, pAfferentCouplingCount) {
48
+ // when both afferentCouplings and efferentCouplings equal 0 instability will
49
+ // yield NaN. Judging Bob Martin's intention, a component with no outgoing
50
+ // dependencies is maximum stable (0)
51
+ return (
52
+ pEfferentCouplingCount /
53
+ (pEfferentCouplingCount + pAfferentCouplingCount) || 0
54
+ );
55
+ }
56
+
31
57
  function clearCache() {
32
58
  gIndexedGraph = null;
33
59
  }
@@ -36,4 +62,6 @@ module.exports = {
36
62
  findModuleByName,
37
63
  clearCache,
38
64
  isDependent,
65
+ metricsAreCalculable,
66
+ calculateInstability,
39
67
  };
@@ -1,4 +1,4 @@
1
- const { isDependent } = require("../utl");
1
+ const { isDependent } = require("../module-utl");
2
2
 
3
3
  module.exports = (pModule, pGraph) => {
4
4
  if (pModule.dependencies.length > 0) {
@@ -1,4 +1,4 @@
1
- const { findModuleByName } = require("../utl");
1
+ const { findModuleByName } = require("../module-utl");
2
2
 
3
3
  function getPath(pGraph, pFrom, pTo, pVisited = new Set()) {
4
4
  let lReturnValue = [];
@@ -4,7 +4,7 @@ const _clone = require("lodash/clone");
4
4
  const _get = require("lodash/get");
5
5
  const _has = require("lodash/has");
6
6
  const matchers = require("../../../validate/matchers");
7
- const { extractGroups } = require("../../../validate/utl");
7
+ const { extractGroups } = require("../../../utl/regex-util");
8
8
  const getPath = require("./get-path");
9
9
 
10
10
  function getReachableRules(pRuleSet) {
@@ -8,7 +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
+ const deriveModuleMetrics = require("./derive/metrics");
12
12
 
13
13
  module.exports = function enrichModules(pModules, pOptions) {
14
14
  bus.emit("progress", "analyzing: cycles", { level: busLogLevels.INFO });
@@ -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);
@@ -1,16 +1,24 @@
1
1
  const enrichModules = require("./enrich-modules");
2
- const deriveFolderMetrics = require("./derive/metrics/folder.js");
2
+ const aggregateToFolders = require("./derive/folders");
3
3
  const summarize = require("./summarize");
4
4
  const clearCaches = require("./clear-caches");
5
5
 
6
6
  module.exports = function enrich(pModules, pOptions, pFileAndDirectoryArray) {
7
7
  clearCaches();
8
8
  const lModules = enrichModules(pModules, pOptions);
9
+ const lFolders = aggregateToFolders(lModules, pOptions);
9
10
 
10
11
  clearCaches();
11
- return {
12
+ const lReturnValue = {
12
13
  modules: lModules,
13
- ...deriveFolderMetrics(lModules, pOptions),
14
- summary: summarize(lModules, pOptions, pFileAndDirectoryArray),
14
+ ...lFolders,
15
+ summary: summarize(
16
+ lModules,
17
+ pOptions,
18
+ pFileAndDirectoryArray,
19
+ lFolders.folders
20
+ ),
15
21
  };
22
+
23
+ return lReturnValue;
16
24
  };
@@ -0,0 +1,31 @@
1
+ function getViolationStats(pViolations) {
2
+ return pViolations.reduce(
3
+ (pAll, pThis) => {
4
+ pAll[pThis.rule.severity] += 1;
5
+ return pAll;
6
+ },
7
+ {
8
+ error: 0,
9
+ warn: 0,
10
+ info: 0,
11
+ ignore: 0,
12
+ }
13
+ );
14
+ }
15
+
16
+ function getModulesCruised(pModules) {
17
+ return pModules.length;
18
+ }
19
+
20
+ function getDependenciesCruised(pModules) {
21
+ return pModules.reduce(
22
+ (pAll, pModule) => pAll + pModule.dependencies.length,
23
+ 0
24
+ );
25
+ }
26
+
27
+ module.exports = {
28
+ getViolationStats,
29
+ getModulesCruised,
30
+ getDependenciesCruised,
31
+ };
@@ -1,6 +1,12 @@
1
1
  const addRuleSetUsed = require("./add-rule-set-used");
2
2
  const summarizeModules = require("./summarize-modules");
3
+ const summarizeFolders = require("./summarize-folders");
3
4
  const summarizeOptions = require("./summarize-options");
5
+ const {
6
+ getViolationStats,
7
+ getModulesCruised,
8
+ getDependenciesCruised,
9
+ } = require("./get-stats");
4
10
 
5
11
  /**
6
12
  *
@@ -8,17 +14,30 @@ const summarizeOptions = require("./summarize-options");
8
14
  * cruised modules that have been enriched with mandatory attributes &
9
15
  * have been validated against rules as passed in the options
10
16
  * @param {import("../../../types/options").ICruiseOptions} pOptions -
11
- *
12
17
  * @param {string[]} pFileDirectoryArray -
13
18
  * the files/ directories originally passed to be cruised
19
+ * @param {import("../../../types/dependency-cruiser").IFolder[]} pFolders -
20
+ * the pModules collapsed to folders, with their own metrics & deps
14
21
  *
15
22
  * @returns {import("../../../types/cruise-result").ISummary} -
16
23
  * a summary of the found modules, dependencies and any violations
17
24
  */
18
- module.exports = function makeSummary(pModules, pOptions, pFileDirectoryArray) {
19
- return Object.assign(
20
- summarizeModules(pModules, pOptions.ruleSet),
21
- summarizeOptions(pFileDirectoryArray, pOptions),
22
- pOptions.ruleSet ? { ruleSetUsed: addRuleSetUsed(pOptions) } : {}
25
+ module.exports = function summarize(
26
+ pModules,
27
+ pOptions,
28
+ pFileDirectoryArray,
29
+ pFolders
30
+ ) {
31
+ const lViolations = summarizeModules(pModules, pOptions.ruleSet).concat(
32
+ summarizeFolders(pFolders || [], pOptions.ruleSet)
23
33
  );
34
+
35
+ return {
36
+ violations: lViolations,
37
+ ...getViolationStats(lViolations),
38
+ totalCruised: getModulesCruised(pModules),
39
+ totalDependenciesCruised: getDependenciesCruised(pModules),
40
+ ...summarizeOptions(pFileDirectoryArray, pOptions),
41
+ ...(pOptions.ruleSet ? { ruleSetUsed: addRuleSetUsed(pOptions) } : {}),
42
+ };
24
43
  };