dependency-cruiser 10.8.0-beta-3 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +1 -1
  3. package/package.json +33 -22
  4. package/src/enrich/derive/metrics/folder.js +1 -2
  5. package/src/enrich/derive/metrics/get-stability-metrics.js +4 -4
  6. package/src/enrich/derive/metrics/module.js +13 -14
  7. package/src/enrich/derive/metrics/utl.js +2 -7
  8. package/src/enrich/derive/utl.js +26 -10
  9. package/src/enrich/enrich-modules.js +1 -1
  10. package/src/enrich/soften-known-violations.js +4 -2
  11. package/src/extract/ast-extractors/swc-dependency-visitor.js +14 -2
  12. package/src/extract/get-dependencies.js +11 -6
  13. package/src/extract/parse/to-typescript-ast.js +24 -8
  14. package/src/extract/utl/get-extension.js +9 -5
  15. package/src/main/options/normalize.js +28 -0
  16. package/src/meta.js +1 -1
  17. package/src/report/dot/dot.template.js +1 -1
  18. package/src/report/dot/index.js +7 -2
  19. package/src/report/dot/module-utl.js +47 -19
  20. package/src/report/dot/prepare-custom-level.js +2 -2
  21. package/src/report/dot/prepare-flat-level.js +2 -2
  22. package/src/report/dot/prepare-folder-level.js +2 -2
  23. package/src/schema/configuration.schema.js +3 -0
  24. package/src/schema/cruise-result.schema.js +4 -0
  25. package/src/validate/match-dependency-rule.js +1 -0
  26. package/src/validate/matchers.js +12 -0
  27. package/types/README.md +1 -0
  28. package/types/baseline-violations.d.ts +1 -1
  29. package/types/cruise-result.d.ts +9 -48
  30. package/types/options.d.ts +5 -0
  31. package/types/reporter-options.d.ts +6 -0
  32. package/types/restrictions.d.ts +12 -0
  33. package/types/rule-summary.d.ts +23 -0
  34. package/types/violations.d.ts +24 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ See [dependency-cruiser releases](https://github.com/sverweij/dependency-cruiser/releases) on GitHub
package/README.md CHANGED
@@ -21,7 +21,7 @@ you can stick on the wall to impress your grandma.
21
21
  ### Install it
22
22
 
23
23
  - `npm install --save-dev dependency-cruiser` to use it as a validator in your project (recommended) or...
24
- - `npm install --global dependency-cruiser` if you just want to to inspect multiple projects.
24
+ - `npm install --global dependency-cruiser` if you just want to inspect multiple projects.
25
25
 
26
26
  ### Show stuff to your grandma
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "10.8.0-beta-3",
3
+ "version": "11.1.0",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -76,7 +76,6 @@
76
76
  "depcruise:all": "node ./bin/dependency-cruise.js src bin test configs types tools --config",
77
77
  "depcruise:baseline": "node ./bin/depcruise-baseline.js src bin test configs types tools",
78
78
  "depcruise:explain": "node ./bin/dependency-cruise.js src bin test configs types tools --output-type err-long --config --progress none",
79
- "depcruise:graph:dev": "node ./bin/dependency-cruise.js bin src --prefix vscode://file/$(pwd)/ --config --output-type dot --progress cli-feedback | dot -T svg | node ./bin/wrap-stream-in-html.js | browser",
80
79
  "depcruise:graph:doc": "npm-run-all depcruise:graph:doc:json --parallel depcruise:graph:doc:fmt-* depcruise:graph:doc:samples",
81
80
  "depcruise:graph:doc:json": "node ./bin/dependency-cruise.js bin src test --config --output-type json --output-to tmp_graph_deps.json --progress",
82
81
  "depcruise:graph:doc:fmt-detail": "./bin/depcruise-fmt.js -T dot -f - tmp_graph_deps.json | dot -T svg | tee doc/real-world-samples/dependency-cruiser-without-node_modules.svg | node bin/wrap-stream-in-html.js > docs/dependency-cruiser-dependency-graph.html",
@@ -88,7 +87,9 @@
88
87
  "depcruise:graph:dot": "node ./bin/dependency-cruise.js bin src --config --output-type dot | dot -T svg > tmp_deps.svg",
89
88
  "depcruise:graph:fdp": "node ./bin/dependency-cruise.js bin src --config --output-type dot | fdp -GK=0.1 -Gsplines=true -T svg > tmp_deps.svg",
90
89
  "depcruise:graph:osage": "node ./bin/dependency-cruise.js bin src --config --output-type dot | osage -Gpack=32 -GpackMode=array2 -T svg > tmp_deps.svg",
91
- "depcruise:report": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config --output-to dependency-violations.html",
90
+ "depcruise:graph:view": "node ./bin/dependency-cruise.js bin src --prefix vscode://file/$(pwd)/ --config configs/.dependency-cruiser-show-metrics-config.json --output-type dot --progress cli-feedback | dot -T svg | node ./bin/wrap-stream-in-html.js | browser",
91
+ "depcruise:report": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to dependency-violations.html",
92
+ "depcruise:report:view": "node ./bin/dependency-cruise.js src bin test configs types --output-type err-html --config configs/.dependency-cruiser-show-metrics-config.json --output-to - | browser",
92
93
  "depcruise:focus": "node ./bin/dependency-cruise.js src bin test configs types --progress --config --output-type text --focus",
93
94
  "lint": "npm-run-all --parallel --aggregate-output lint:eslint lint:prettier:check lint:types",
94
95
  "lint:eslint": "eslint bin/dependency-cruise.js src test configs tools/**/*.mjs --cache --cache-location .cache/eslint/",
@@ -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",
@@ -1,8 +1,7 @@
1
1
  const getStabilityMetrics = require("./get-stability-metrics");
2
- const { shouldDeriveMetrics } = require("./utl");
3
2
 
4
3
  module.exports = function deriveMetrics(pModules, pOptions) {
5
- if (shouldDeriveMetrics(pOptions)) {
4
+ if (pOptions.metrics) {
6
5
  return { folders: getStabilityMetrics(pModules) };
7
6
  }
8
7
  return {};
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable security/detect-object-injection */
2
2
  const path = require("path").posix;
3
- const { foldersObject2folderArray, getParentFolders } = require("./utl");
3
+ const { object2Array, getParentFolders } = require("./utl");
4
4
  const {
5
5
  getAfferentCouplings,
6
6
  getEfferentCouplings,
@@ -63,8 +63,8 @@ function getFolderLevelCouplings(pCouplingArray) {
63
63
  }
64
64
 
65
65
  function calculateFolderMetrics(pFolder) {
66
- const lModuleDependents = foldersObject2folderArray(pFolder.dependents);
67
- const lModuleDependencies = foldersObject2folderArray(pFolder.dependencies);
66
+ const lModuleDependents = object2Array(pFolder.dependents);
67
+ const lModuleDependencies = object2Array(pFolder.dependencies);
68
68
  const lAfferentCouplings = lModuleDependents.reduce(sumCounts, 0);
69
69
  const lEfferentCouplings = lModuleDependencies.reduce(sumCounts, 0);
70
70
 
@@ -83,7 +83,7 @@ function calculateFolderMetrics(pFolder) {
83
83
  }
84
84
 
85
85
  module.exports = function getStabilityMetrics(pModules) {
86
- return foldersObject2folderArray(
86
+ return object2Array(
87
87
  pModules.filter(metricsAreCalculable).reduce((pAllFolders, pModule) => {
88
88
  getParentFolders(path.dirname(pModule.source)).forEach(
89
89
  (pParentDirectory) =>
@@ -1,10 +1,9 @@
1
- // const { findModuleByName, clearCache } = require("../utl");
1
+ const { findModuleByName, clearCache } = require("../utl");
2
2
  const { metricsAreCalculable } = require("./module-utl");
3
- const { shouldDeriveMetrics } = require("./utl");
4
3
 
5
4
  module.exports = function deriveModuleMetrics(pModules, pOptions) {
6
- if (shouldDeriveMetrics(pOptions)) {
7
- return pModules.map((pModule) => ({
5
+ if (pOptions.metrics) {
6
+ const lModules = pModules.map((pModule) => ({
8
7
  ...pModule,
9
8
  ...(metricsAreCalculable(pModule)
10
9
  ? {
@@ -14,16 +13,16 @@ module.exports = function deriveModuleMetrics(pModules, pOptions) {
14
13
  }
15
14
  : {}),
16
15
  }));
17
- // clearCache();
18
- // return lModules.map((pModule) => ({
19
- // ...pModule,
20
- // dependencies: pModule.dependencies.map((pDependency) => ({
21
- // ...pDependency,
22
- // instability:
23
- // (findModuleByName(lModules, pDependency.resolved) || {})
24
- // .instability || 0,
25
- // })),
26
- // }));
16
+ clearCache();
17
+ return lModules.map((pModule) => ({
18
+ ...pModule,
19
+ dependencies: pModule.dependencies.map((pDependency) => ({
20
+ ...pDependency,
21
+ instability:
22
+ (findModuleByName(lModules, pDependency.resolved) || {})
23
+ .instability || 0,
24
+ })),
25
+ }));
27
26
  }
28
27
  return pModules;
29
28
  };
@@ -10,19 +10,14 @@ function getParentFolders(pPath) {
10
10
  return lReturnValue.reverse();
11
11
  }
12
12
 
13
- function foldersObject2folderArray(pObject) {
13
+ function object2Array(pObject) {
14
14
  return Object.keys(pObject).map((pKey) => ({
15
15
  name: pKey,
16
16
  ...pObject[pKey],
17
17
  }));
18
18
  }
19
19
 
20
- function shouldDeriveMetrics(pOptions) {
21
- return pOptions.metrics || pOptions.outputType === "metrics";
22
- }
23
-
24
20
  module.exports = {
25
21
  getParentFolders,
26
- foldersObject2folderArray,
27
- shouldDeriveMetrics,
22
+ object2Array,
28
23
  };
@@ -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
+ };
@@ -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);
@@ -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) {
@@ -21,15 +21,18 @@ function extractFromSwcAST(pOptions, pFileName) {
21
21
  );
22
22
  }
23
23
 
24
- function extractFromTypeScriptAST(pOptions, pFileName) {
24
+ function extractFromTypeScriptAST(pOptions, pFileName, pTranspileOptions) {
25
25
  return extractTypeScriptDeps(
26
- toTypescriptAST.getASTCached(path.join(pOptions.baseDir, pFileName)),
26
+ toTypescriptAST.getASTCached(
27
+ path.join(pOptions.baseDir, pFileName),
28
+ pTranspileOptions
29
+ ),
27
30
  pOptions.exoticRequireStrings
28
31
  );
29
32
  }
30
33
 
31
34
  function isTypeScriptCompatible(pFileName) {
32
- return [".ts", ".tsx", ".js", ".mjs", ".cjs"].includes(
35
+ return [".ts", ".tsx", ".js", ".mjs", ".cjs", ".vue"].includes(
33
36
  path.extname(pFileName)
34
37
  );
35
38
  }
@@ -88,9 +91,11 @@ function extractWithTsc(
88
91
  pFileName,
89
92
  pTranspileOptions
90
93
  ) {
91
- pDependencies = extractFromTypeScriptAST(pCruiseOptions, pFileName).filter(
92
- (pDep) => pCruiseOptions.moduleSystems.includes(pDep.moduleSystem)
93
- );
94
+ pDependencies = extractFromTypeScriptAST(
95
+ pCruiseOptions,
96
+ pFileName,
97
+ pTranspileOptions
98
+ ).filter((pDep) => pCruiseOptions.moduleSystems.includes(pDep.moduleSystem));
94
99
 
95
100
  if (pCruiseOptions.tsPreCompilationDeps === "specify") {
96
101
  pDependencies = detectPreCompilationNess(
@@ -2,21 +2,28 @@ const fs = require("fs");
2
2
  const tryRequire = require("semver-try-require");
3
3
  const _memoize = require("lodash/memoize");
4
4
  const { supportedTranspilers } = require("../../../src/meta.js");
5
+ const transpile = require("../transpile");
6
+ const getExtension = require("../utl/get-extension");
5
7
 
6
8
  const typescript = tryRequire("typescript", supportedTranspilers.typescript);
7
9
 
8
10
  /**
9
11
  * Compiles pTypescriptSource into a (typescript) AST
10
12
  *
11
- * @param {string} pTypescriptSource - the source to compile
12
- * @param {string} [pFileName] - (optional) file name the typescript
13
- * compiler can use in error messages
13
+ * @param {object} pFileRecord Record with source code, an extension and a filename
14
+ * @param {any} [pTranspileOptions] options for the transpiler(s) - a tsconfig or
15
+ * a babel config
14
16
  * @return {object} - a (typescript) AST
15
17
  */
16
- function getASTFromSource(pTypescriptSource, pFileName) {
18
+ function getASTFromSource(pFileRecord, pTranspileOptions) {
19
+ let lSource = pFileRecord.source;
20
+ if (pFileRecord.extension === ".vue") {
21
+ lSource = transpile(pFileRecord, pTranspileOptions);
22
+ }
23
+
17
24
  return typescript.createSourceFile(
18
- pFileName || "$internal-file-name",
19
- pTypescriptSource,
25
+ pFileRecord.filename || "$internal-file-name",
26
+ lSource,
20
27
  typescript.ScriptTarget.Latest,
21
28
  false
22
29
  );
@@ -27,10 +34,19 @@ function getASTFromSource(pTypescriptSource, pFileName) {
27
34
  * AST and returns it
28
35
  *
29
36
  * @param {string} pFileName - the name of the file to compile
37
+ * @param {any} [pTranspileOptions] options for the transpiler(s) - a tsconfig or
38
+ * a babel config
30
39
  * @return {object} - a (typescript) AST
31
40
  */
32
- function getAST(pFileName) {
33
- return getASTFromSource(fs.readFileSync(pFileName, "utf8"), pFileName);
41
+ function getAST(pFileName, pTranspileOptions) {
42
+ return getASTFromSource(
43
+ {
44
+ source: fs.readFileSync(pFileName, "utf8"),
45
+ extension: getExtension(pFileName),
46
+ filename: pFileName,
47
+ },
48
+ pTranspileOptions
49
+ );
34
50
  }
35
51
 
36
52
  const getASTCached = _memoize(getAST);
@@ -5,16 +5,20 @@ const path = require("path");
5
5
  *
6
6
  * Just using path.extname would be fine for most cases,
7
7
  * except for coffeescript, where a markdown extension can
8
- * mean literate coffeescript.
8
+ * mean literate coffeescript, and for typescript where
9
+ * .d.ts and .ts are slightly different beasts
9
10
  *
10
11
  * @param {string} pFileName path to the file to be parsed
11
12
  * @return {string} extension
12
13
  */
13
14
  module.exports = function getExtensions(pFileName) {
14
- let lReturnValue = path.extname(pFileName);
15
+ if (pFileName.endsWith(".d.ts")) {
16
+ return ".d.ts";
17
+ }
15
18
 
16
- if (lReturnValue === ".md") {
17
- return pFileName.endsWith(".coffee.md") ? ".coffee.md" : lReturnValue;
19
+ if (pFileName.endsWith(".coffee.md")) {
20
+ return ".coffee.md";
18
21
  }
19
- return lReturnValue;
22
+
23
+ return path.extname(pFileName);
20
24
  };
@@ -66,6 +66,32 @@ function normalizeCollapse(pCollapse) {
66
66
  return lReturnValue;
67
67
  }
68
68
 
69
+ function hasMetricsRule(pRule) {
70
+ return _has(pRule, "to.moreUnstable");
71
+ }
72
+
73
+ function ruleSetHasMetricsRule(pRuleSet) {
74
+ const lRuleSet = pRuleSet || {};
75
+ return (
76
+ (lRuleSet.forbidden || []).some(hasMetricsRule) ||
77
+ (lRuleSet.allowed || []).some(hasMetricsRule)
78
+ );
79
+ }
80
+
81
+ /**
82
+ * Determines whether (instability) metrics should be calculated
83
+ *
84
+ * @param {import('../../../types/options').ICruiseOptions pOptions
85
+ * @returns Boolean
86
+ */
87
+ function shouldCalculateMetrics(pOptions) {
88
+ return (
89
+ pOptions.metrics ||
90
+ pOptions.outputType === "metrics" ||
91
+ ruleSetHasMetricsRule(pOptions.ruleSet)
92
+ );
93
+ }
94
+
69
95
  /**
70
96
  *
71
97
  * @param {Partial <import('../../../types/options').ICruiseOptions>} pOptions
@@ -101,6 +127,8 @@ function normalizeCruiseOptions(pOptions) {
101
127
  lReturnValue.reporterOptions
102
128
  );
103
129
  }
130
+ lReturnValue.metrics = shouldCalculateMetrics(pOptions);
131
+
104
132
  return lReturnValue;
105
133
  }
106
134
 
package/src/meta.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /* generated - don't edit */
2
2
 
3
3
  module.exports = {
4
- version: "10.8.0-beta-3",
4
+ version: "11.1.0",
5
5
  engines: {
6
6
  node: "^12.20||^14||>=16",
7
7
  },
@@ -1 +1 @@
1
- var Handlebars=require("handlebars/runtime"),template=Handlebars.template,templates=Handlebars.templates=Handlebars.templates||{};templates["dot.template.hbs"]=template({1:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"graphAttrs")||(null!=n?a(n,"graphAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"graphAttrs",hash:{},data:o,loc:{start:{line:2,column:22},end:{line:2,column:38}}}):a)?a:""},3:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"node ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"nodeAttrs")||(null!=n?a(n,"nodeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"nodeAttrs",hash:{},data:o,loc:{start:{line:3,column:27},end:{line:3,column:42}}}):a)?a:"")+"]"},5:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"edge ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"edgeAttrs")||(null!=n?a(n,"edgeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"edgeAttrs",hash:{},data:o,loc:{start:{line:4,column:27},end:{line:4,column:42}}}):a)?a:"")+"]"},7:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},i=null!=(p=c(e,"if").call(u,null!=n?c(n,"folder"):n,{name:"if",hash:{},fn:l.program(8,o,0,a,r),inverse:l.program(20,o,0,a,r),data:o,loc:{start:{line:7,column:4},end:{line:12,column:11}}}))?p:"",s=null!=(s=c(e,"dependencies")||(null!=n?c(n,"dependencies"):n))?s:l.hooks.helperMissing,o={name:"dependencies",hash:{},fn:l.program(22,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:13,column:4},end:{line:15,column:21}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!c(e,"dependencies")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(i+=p),i},8:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" "+(null!=(u=m(e,"each").call(i,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(9,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:4},end:{line:8,column:201}}}))?u:"")+'"'+(null!=(u=typeof(c=null!=(c=m(e,"source")||(null!=n?m(n,"source"):n))?c:s)==p?c.call(i,{name:"source",hash:{},data:o,loc:{start:{line:9,column:9},end:{line:9,column:21}}}):c)?u:"")+'" [label='+(null!=(u=typeof(c=null!=(c=m(e,"label")||(null!=n?m(n,"label"):n))?c:s)==p?c.call(i,{name:"label",hash:{},data:o,loc:{start:{line:9,column:30},end:{line:9,column:41}}}):c)?u:"")+" "+(null!=(u=m(e,"if").call(i,null!=n?m(n,"rule"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:42},end:{line:9,column:87}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:87},end:{line:9,column:121}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:121},end:{line:9,column:162}}}))?u:"")+"]"+(null!=(u=m(e,"each").call(i,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(18,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:163},end:{line:9,column:188}}}))?u:"")+"\n"},9:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'subgraph "cluster_'+(null!=(u=typeof(c=null!=(c=m(e,"aggregateSnippet")||(null!=n?m(n,"aggregateSnippet"):n))?c:s)==p?c.call(i,{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:36},end:{line:8,column:58}}}):c)?u:"")+'" {label="'+(null!=(u=typeof(c=null!=(c=m(e,"snippet")||(null!=n?m(n,"snippet"):n))?c:s)==p?c.call(i,{name:"snippet",hash:{},data:o,loc:{start:{line:8,column:68},end:{line:8,column:81}}}):c)?u:"")+'" '+(null!=(u=m(e,"if").call(i,null!=r[2]?m(r[2],"clustersHaveOwnNode"):r[2],{name:"if",hash:{},fn:l.program(10,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:83},end:{line:8,column:191}}}))?u:"")},10:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'"'+(null!=(a="function"==typeof(a=null!=(a=a(e,"aggregateSnippet")||(null!=n?a(n,"aggregateSnippet"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:117},end:{line:8,column:139}}}):a)?a:"")+'" [width="0.05" shape="point" style="invis"] '},12:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'tooltip="'+(null!=(l=l.lambda(null!=(l=null!=n?a(n,"rule"):n)?a(l,"name"):l,n))?l:"")+'" '},14:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'URL="'+(null!=(a="function"==typeof(a=null!=(a=a(e,"URL")||(null!=n?a(n,"URL"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"URL",hash:{},data:o,loc:{start:{line:9,column:103},end:{line:9,column:112}}}):a)?a:"")+'" '},16:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"themeAttrs")||(null!=n?a(n,"themeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"themeAttrs",hash:{},data:o,loc:{start:{line:9,column:139},end:{line:9,column:155}}}):a)?a:""},18:function(l,n,e,t,o){return" }"},20:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=typeof(c=null!=(c=m(e,"source")||(null!=n?m(n,"source"):n))?c:s)==p?c.call(i,{name:"source",hash:{},data:o,loc:{start:{line:11,column:5},end:{line:11,column:17}}}):c)?u:"")+'" [label='+(null!=(u=typeof(c=null!=(c=m(e,"label")||(null!=n?m(n,"label"):n))?c:s)==p?c.call(i,{name:"label",hash:{},data:o,loc:{start:{line:11,column:26},end:{line:11,column:37}}}):c)?u:"")+" "+(null!=(u=m(e,"if").call(i,null!=n?m(n,"rule"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:38},end:{line:11,column:83}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:83},end:{line:11,column:117}}}))?u:"")+(null!=(u=m(e,"if").call(i,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:117},end:{line:11,column:158}}}))?u:"")+"]\n"},22:function(l,n,e,t,o,a,r){var u,c,i=null!=n?n:l.nullContext||{},s=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=l.lambda(null!=r[1]?s(r[1],"source"):r[1],n))?u:"")+'" -> "'+(null!=(u="function"==typeof(c=null!=(c=s(e,"resolved")||(null!=n?s(n,"resolved"):n))?c:l.hooks.helperMissing)?c.call(i,{name:"resolved",hash:{},data:o,loc:{start:{line:14,column:26},end:{line:14,column:40}}}):c)?u:"")+'"'+(null!=(u=s(e,"if").call(i,null!=n?s(n,"hasExtraAttributes"):n,{name:"if",hash:{},fn:l.program(23,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:41},end:{line:14,column:193}}}))?u:"")+"\n"},23:function(l,n,e,t,o,a,r){var u,c=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" ["+(null!=(u=i(e,"if").call(c,null!=(u=null!=n?i(n,"rule"):n)?i(u,"name"):u,{name:"if",hash:{},fn:l.program(24,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:69},end:{line:14,column:144}}}))?u:"")+(null!=(u=i(e,"if").call(c,null!=n?i(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:144},end:{line:14,column:185}}}))?u:"")+"]"},24:function(l,n,e,t,o){var a=l.lambda,r=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'xlabel="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" tooltip="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" '},compiler:[8,">= 4.3.0"],main:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},i='strict digraph "dependency-cruiser output"{\n '+(null!=(p=c(e,"if").call(u,null!=n?c(n,"graphAttrs"):n,{name:"if",hash:{},fn:l.program(1,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:2,column:4},end:{line:2,column:45}}}))?p:"")+"\n "+(null!=(p=c(e,"if").call(u,null!=n?c(n,"nodeAttrs"):n,{name:"if",hash:{},fn:l.program(3,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:3,column:4},end:{line:3,column:50}}}))?p:"")+"\n "+(null!=(p=c(e,"if").call(u,null!=n?c(n,"edgeAttrs"):n,{name:"if",hash:{},fn:l.program(5,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:4,column:4},end:{line:4,column:50}}}))?p:"")+"\n\n",s=null!=(s=c(e,"modules")||(null!=n?c(n,"modules"):n))?s:l.hooks.helperMissing,o={name:"modules",hash:{},fn:l.program(7,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:6,column:0},end:{line:16,column:12}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!c(e,"modules")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(i+=p),i+"}\n"},useData:!0,useDepths:!0});
1
+ var Handlebars=require("handlebars/runtime"),template=Handlebars.template,templates=Handlebars.templates=Handlebars.templates||{};templates["dot.template.hbs"]=template({1:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"graphAttrs")||(null!=n?a(n,"graphAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"graphAttrs",hash:{},data:o,loc:{start:{line:2,column:22},end:{line:2,column:38}}}):a)?a:""},3:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"node ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"nodeAttrs")||(null!=n?a(n,"nodeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"nodeAttrs",hash:{},data:o,loc:{start:{line:3,column:27},end:{line:3,column:42}}}):a)?a:"")+"]"},5:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return"edge ["+(null!=(a="function"==typeof(a=null!=(a=a(e,"edgeAttrs")||(null!=n?a(n,"edgeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"edgeAttrs",hash:{},data:o,loc:{start:{line:4,column:27},end:{line:4,column:42}}}):a)?a:"")+"]"},7:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},c=null!=(p=i(e,"if").call(u,null!=n?i(n,"folder"):n,{name:"if",hash:{},fn:l.program(8,o,0,a,r),inverse:l.program(18,o,0,a,r),data:o,loc:{start:{line:7,column:4},end:{line:12,column:11}}}))?p:"",s=null!=(s=i(e,"dependencies")||(null!=n?i(n,"dependencies"):n))?s:l.hooks.helperMissing,o={name:"dependencies",hash:{},fn:l.program(20,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:13,column:4},end:{line:15,column:21}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!i(e,"dependencies")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(c+=p),c},8:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" "+(null!=(u=m(e,"each").call(c,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(9,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:4},end:{line:8,column:201}}}))?u:"")+'"'+(null!=(u=typeof(i=null!=(i=m(e,"source")||(null!=n?m(n,"source"):n))?i:s)==p?i.call(c,{name:"source",hash:{},data:o,loc:{start:{line:9,column:9},end:{line:9,column:21}}}):i)?u:"")+'" [label='+(null!=(u=typeof(i=null!=(i=m(e,"label")||(null!=n?m(n,"label"):n))?i:s)==p?i.call(c,{name:"label",hash:{},data:o,loc:{start:{line:9,column:30},end:{line:9,column:41}}}):i)?u:"")+' tooltip="'+(null!=(u=typeof(i=null!=(i=m(e,"tooltip")||(null!=n?m(n,"tooltip"):n))?i:s)==p?i.call(c,{name:"tooltip",hash:{},data:o,loc:{start:{line:9,column:51},end:{line:9,column:64}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:66},end:{line:9,column:100}}}))?u:"")+(null!=(u=m(e,"if").call(c,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:100},end:{line:9,column:141}}}))?u:"")+"]"+(null!=(u=m(e,"each").call(c,null!=n?m(n,"path"):n,{name:"each",hash:{},fn:l.program(16,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:9,column:142},end:{line:9,column:167}}}))?u:"")+"\n"},9:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'subgraph "cluster_'+(null!=(u=typeof(i=null!=(i=m(e,"aggregateSnippet")||(null!=n?m(n,"aggregateSnippet"):n))?i:s)==p?i.call(c,{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:36},end:{line:8,column:58}}}):i)?u:"")+'" {label="'+(null!=(u=typeof(i=null!=(i=m(e,"snippet")||(null!=n?m(n,"snippet"):n))?i:s)==p?i.call(c,{name:"snippet",hash:{},data:o,loc:{start:{line:8,column:68},end:{line:8,column:81}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=r[2]?m(r[2],"clustersHaveOwnNode"):r[2],{name:"if",hash:{},fn:l.program(10,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:8,column:83},end:{line:8,column:191}}}))?u:"")},10:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'"'+(null!=(a="function"==typeof(a=null!=(a=a(e,"aggregateSnippet")||(null!=n?a(n,"aggregateSnippet"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"aggregateSnippet",hash:{},data:o,loc:{start:{line:8,column:117},end:{line:8,column:139}}}):a)?a:"")+'" [width="0.05" shape="point" style="invis"] '},12:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'URL="'+(null!=(a="function"==typeof(a=null!=(a=a(e,"URL")||(null!=n?a(n,"URL"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"URL",hash:{},data:o,loc:{start:{line:9,column:82},end:{line:9,column:91}}}):a)?a:"")+'" '},14:function(l,n,e,t,o){var a=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return null!=(a="function"==typeof(a=null!=(a=a(e,"themeAttrs")||(null!=n?a(n,"themeAttrs"):n))?a:l.hooks.helperMissing)?a.call(null!=n?n:l.nullContext||{},{name:"themeAttrs",hash:{},data:o,loc:{start:{line:9,column:118},end:{line:9,column:134}}}):a)?a:""},16:function(l,n,e,t,o){return" }"},18:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.hooks.helperMissing,p="function",m=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=typeof(i=null!=(i=m(e,"source")||(null!=n?m(n,"source"):n))?i:s)==p?i.call(c,{name:"source",hash:{},data:o,loc:{start:{line:11,column:5},end:{line:11,column:17}}}):i)?u:"")+'" [label='+(null!=(u=typeof(i=null!=(i=m(e,"label")||(null!=n?m(n,"label"):n))?i:s)==p?i.call(c,{name:"label",hash:{},data:o,loc:{start:{line:11,column:26},end:{line:11,column:37}}}):i)?u:"")+' tooltip="'+(null!=(u=typeof(i=null!=(i=m(e,"tooltip")||(null!=n?m(n,"tooltip"):n))?i:s)==p?i.call(c,{name:"tooltip",hash:{},data:o,loc:{start:{line:11,column:47},end:{line:11,column:60}}}):i)?u:"")+'" '+(null!=(u=m(e,"if").call(c,null!=n?m(n,"URL"):n,{name:"if",hash:{},fn:l.program(12,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:62},end:{line:11,column:96}}}))?u:"")+(null!=(u=m(e,"if").call(c,null!=n?m(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:11,column:96},end:{line:11,column:137}}}))?u:"")+"]\n"},20:function(l,n,e,t,o,a,r){var u,i,c=null!=n?n:l.nullContext||{},s=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return' "'+(null!=(u=l.lambda(null!=r[1]?s(r[1],"source"):r[1],n))?u:"")+'" -> "'+(null!=(u="function"==typeof(i=null!=(i=s(e,"resolved")||(null!=n?s(n,"resolved"):n))?i:l.hooks.helperMissing)?i.call(c,{name:"resolved",hash:{},data:o,loc:{start:{line:14,column:26},end:{line:14,column:40}}}):i)?u:"")+'"'+(null!=(u=s(e,"if").call(c,null!=n?s(n,"hasExtraAttributes"):n,{name:"if",hash:{},fn:l.program(21,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:41},end:{line:14,column:193}}}))?u:"")+"\n"},21:function(l,n,e,t,o,a,r){var u,i=null!=n?n:l.nullContext||{},c=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return" ["+(null!=(u=c(e,"if").call(i,null!=(u=null!=n?c(n,"rule"):n)?c(u,"name"):u,{name:"if",hash:{},fn:l.program(22,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:69},end:{line:14,column:144}}}))?u:"")+(null!=(u=c(e,"if").call(i,null!=n?c(n,"themeAttrs"):n,{name:"if",hash:{},fn:l.program(14,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:14,column:144},end:{line:14,column:185}}}))?u:"")+"]"},22:function(l,n,e,t,o){var a=l.lambda,r=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]};return'xlabel="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" tooltip="'+(null!=(l=a(null!=(l=null!=n?r(n,"rule"):n)?r(l,"name"):l,n))?l:"")+'" '},compiler:[8,">= 4.3.0"],main:function(l,n,e,t,o,a,r){var u=null!=n?n:l.nullContext||{},i=l.lookupProperty||function(l,n){if(Object.prototype.hasOwnProperty.call(l,n))return l[n]},c='strict digraph "dependency-cruiser output"{\n '+(null!=(p=i(e,"if").call(u,null!=n?i(n,"graphAttrs"):n,{name:"if",hash:{},fn:l.program(1,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:2,column:4},end:{line:2,column:45}}}))?p:"")+"\n "+(null!=(p=i(e,"if").call(u,null!=n?i(n,"nodeAttrs"):n,{name:"if",hash:{},fn:l.program(3,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:3,column:4},end:{line:3,column:50}}}))?p:"")+"\n "+(null!=(p=i(e,"if").call(u,null!=n?i(n,"edgeAttrs"):n,{name:"if",hash:{},fn:l.program(5,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:4,column:4},end:{line:4,column:50}}}))?p:"")+"\n\n",s=null!=(s=i(e,"modules")||(null!=n?i(n,"modules"):n))?s:l.hooks.helperMissing,o={name:"modules",hash:{},fn:l.program(7,o,0,a,r),inverse:l.noop,data:o,loc:{start:{line:6,column:0},end:{line:16,column:12}}},p="function"==typeof s?s.call(u,o):s;return null!=(p=!i(e,"modules")?l.hooks.blockHelperMissing.call(n,p,o):p)&&(c+=p),c+"}\n"},useData:!0,useDepths:!0});
@@ -17,7 +17,11 @@ const GRANULARITY2FUNCTION = {
17
17
  flat: prepareFlatLevel,
18
18
  };
19
19
 
20
- function report(pResults, pGranularity, { theme, collapsePattern, filters }) {
20
+ function report(
21
+ pResults,
22
+ pGranularity,
23
+ { theme, collapsePattern, filters, showMetrics }
24
+ ) {
21
25
  const lTheme = theming.normalizeTheme(theme);
22
26
  const lResults = filters
23
27
  ? {
@@ -35,7 +39,8 @@ function report(pResults, pGranularity, { theme, collapsePattern, filters }) {
35
39
  modules: (GRANULARITY2FUNCTION[pGranularity] || prepareCustomLevel)(
36
40
  lResults,
37
41
  lTheme,
38
- collapsePattern
42
+ collapsePattern,
43
+ showMetrics
39
44
  ),
40
45
  });
41
46
  }
@@ -1,6 +1,9 @@
1
1
  const path = require("path").posix;
2
+ const _has = require("lodash/has");
2
3
  const theming = require("./theming");
3
4
 
5
+ const PROTOCOL_PREFIX_RE = /^[a-z]+:\/\//;
6
+
4
7
  function attributizeObject(pObject) {
5
8
  return (
6
9
  Object.keys(pObject)
@@ -12,7 +15,9 @@ function attributizeObject(pObject) {
12
15
 
13
16
  function extractFirstTransgression(pModule) {
14
17
  return {
15
- ...(pModule.rules ? { ...pModule, rule: pModule.rules[0] } : pModule),
18
+ ...(_has(pModule, "rules[0]")
19
+ ? { ...pModule, tooltip: pModule.rules[0].name }
20
+ : pModule),
16
21
  dependencies: pModule.dependencies.map((pDependency) =>
17
22
  pDependency.rules
18
23
  ? {
@@ -57,20 +62,37 @@ function aggregate(pPathSnippet, pCounter, pPathArray) {
57
62
  };
58
63
  }
59
64
 
60
- function folderify(pModule) {
61
- let lAdditions = {};
62
- let lDirectoryName = path.dirname(pModule.source);
65
+ function makeInstabilityString(pModule, pShowMetrics = false) {
66
+ let lInstabilityString = "";
63
67
 
64
- if (lDirectoryName !== ".") {
65
- lAdditions.folder = lDirectoryName;
66
- lAdditions.path = lDirectoryName.split(path.sep).map(aggregate);
68
+ if (pShowMetrics && _has(pModule, "instability") && !pModule.consolidated) {
69
+ lInstabilityString = ` <FONT color="#808080" point-size="8">${Math.round(
70
+ // eslint-disable-next-line no-magic-numbers
71
+ 100 * pModule.instability
72
+ )}</FONT>`;
67
73
  }
74
+ return lInstabilityString;
75
+ }
68
76
 
69
- lAdditions.label = `"${path.basename(pModule.source)}"`;
77
+ function folderify(pShowMetrics) {
78
+ return (pModule) => {
79
+ let lAdditions = {};
80
+ let lDirectoryName = path.dirname(pModule.source);
70
81
 
71
- return {
72
- ...pModule,
73
- ...lAdditions,
82
+ if (lDirectoryName !== ".") {
83
+ lAdditions.folder = lDirectoryName;
84
+ lAdditions.path = lDirectoryName.split(path.sep).map(aggregate);
85
+ }
86
+
87
+ lAdditions.label = `<${path.basename(
88
+ pModule.source
89
+ )}${makeInstabilityString(pModule, pShowMetrics)}>`;
90
+ lAdditions.tooltip = path.basename(pModule.source);
91
+
92
+ return {
93
+ ...pModule,
94
+ ...lAdditions,
95
+ };
74
96
  };
75
97
  }
76
98
 
@@ -87,7 +109,7 @@ function folderify(pModule) {
87
109
  * @return {string} prefix and filename concatenated
88
110
  */
89
111
  function smartURIConcat(pPrefix, pSource) {
90
- if (pPrefix.match(/^[a-z]+:\/\//)) {
112
+ if (pPrefix.match(PROTOCOL_PREFIX_RE)) {
91
113
  return `${pPrefix}${pSource}`;
92
114
  } else {
93
115
  return path.join(pPrefix, pSource);
@@ -103,15 +125,21 @@ function addURL(pPrefix) {
103
125
  });
104
126
  }
105
127
 
106
- function flatLabel(pModule) {
107
- return {
108
- ...pModule,
109
- label: `<${path.dirname(pModule.source)}/<BR/><B>${path.basename(
110
- pModule.source
111
- )}</B>>`,
112
- };
128
+ function makeLabel(pModule, pShowMetrics) {
129
+ return `<${path.dirname(pModule.source)}/<BR/><B>${path.basename(
130
+ pModule.source
131
+ )}</B>${makeInstabilityString(pModule, pShowMetrics)}>`;
113
132
  }
114
133
 
134
+ function flatLabel(pShowMetrics) {
135
+ return (pModule) => {
136
+ return {
137
+ ...pModule,
138
+ label: makeLabel(pModule, pShowMetrics),
139
+ tooltip: path.basename(pModule.source),
140
+ };
141
+ };
142
+ }
115
143
  module.exports = {
116
144
  folderify,
117
145
  applyTheme,
@@ -4,15 +4,15 @@ const compare = require("../../graph-utl/compare");
4
4
  const stripSelfTransitions = require("../../graph-utl/strip-self-transitions");
5
5
  const moduleUtl = require("./module-utl");
6
6
 
7
- module.exports = (pResults, pTheme, pCollapsePattern) => {
7
+ module.exports = (pResults, pTheme, pCollapsePattern, pShowMetrics) => {
8
8
  return (
9
9
  pCollapsePattern
10
10
  ? consolidateToPattern(pResults.modules, pCollapsePattern)
11
11
  : pResults.modules
12
12
  )
13
13
  .sort(compare.modules)
14
+ .map(moduleUtl.folderify(pShowMetrics))
14
15
  .map(moduleUtl.extractFirstTransgression)
15
- .map(moduleUtl.folderify)
16
16
  .map(stripSelfTransitions)
17
17
  .map(moduleUtl.applyTheme(pTheme))
18
18
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -2,10 +2,10 @@ const _get = require("lodash/get");
2
2
  const compare = require("../../graph-utl/compare");
3
3
  const moduleUtl = require("./module-utl");
4
4
 
5
- module.exports = (pResults, pTheme) => {
5
+ module.exports = (pResults, pTheme, _, pShowMetrics) => {
6
6
  return pResults.modules
7
7
  .sort(compare.modules)
8
- .map(moduleUtl.flatLabel)
8
+ .map(moduleUtl.flatLabel(pShowMetrics))
9
9
  .map(moduleUtl.extractFirstTransgression)
10
10
  .map(moduleUtl.applyTheme(pTheme))
11
11
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -4,11 +4,11 @@ const compare = require("../../graph-utl/compare");
4
4
  const stripSelfTransitions = require("../../graph-utl/strip-self-transitions");
5
5
  const moduleUtl = require("./module-utl");
6
6
 
7
- module.exports = (pResults, pTheme) => {
7
+ module.exports = (pResults, pTheme, _, pShowMetrics) => {
8
8
  return consolidateToFolder(pResults.modules)
9
9
  .sort(compare.modules)
10
10
  .map(moduleUtl.extractFirstTransgression)
11
- .map(moduleUtl.folderify)
11
+ .map(moduleUtl.folderify(pShowMetrics))
12
12
  .map(stripSelfTransitions)
13
13
  .map(moduleUtl.applyTheme(pTheme))
14
14
  .map(moduleUtl.addURL(_get(pResults, "summary.optionsUsed.prefix", "")));
@@ -168,6 +168,7 @@ module.exports = {
168
168
  licenseNot: { $ref: "#/definitions/REAsStringsType" },
169
169
  via: { $ref: "#/definitions/REAsStringsType" },
170
170
  viaNot: { $ref: "#/definitions/REAsStringsType" },
171
+ moreUnstable: { type: "boolean" },
171
172
  },
172
173
  },
173
174
  DependentsModuleRestrictionType: {
@@ -348,6 +349,7 @@ module.exports = {
348
349
  },
349
350
  },
350
351
  },
352
+ metrics: { type: "boolean" },
351
353
  baseDir: { type: "string" },
352
354
  },
353
355
  },
@@ -426,6 +428,7 @@ module.exports = {
426
428
  properties: {
427
429
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
428
430
  filters: { $ref: "#/definitions/ReporterFiltersType" },
431
+ showMetrics: { type: "boolean" },
429
432
  theme: { $ref: "#/definitions/DotThemeType" },
430
433
  },
431
434
  },
@@ -127,6 +127,7 @@ module.exports = {
127
127
  type: "array",
128
128
  items: { $ref: "#/definitions/RuleSummaryType" },
129
129
  },
130
+ instability: { type: "number" },
130
131
  },
131
132
  },
132
133
  DependencyTypeType: {
@@ -357,6 +358,7 @@ module.exports = {
357
358
  licenseNot: { $ref: "#/definitions/REAsStringsType" },
358
359
  via: { $ref: "#/definitions/REAsStringsType" },
359
360
  viaNot: { $ref: "#/definitions/REAsStringsType" },
361
+ moreUnstable: { type: "boolean" },
360
362
  },
361
363
  },
362
364
  DependentsModuleRestrictionType: {
@@ -496,6 +498,7 @@ module.exports = {
496
498
  },
497
499
  },
498
500
  },
501
+ metrics: { type: "boolean" },
499
502
  baseDir: { type: "string" },
500
503
  args: { type: "string" },
501
504
  rulesFile: { type: "string" },
@@ -603,6 +606,7 @@ module.exports = {
603
606
  properties: {
604
607
  collapsePattern: { $ref: "#/definitions/REAsStringsType" },
605
608
  filters: { $ref: "#/definitions/ReporterFiltersType" },
609
+ showMetrics: { type: "boolean" },
606
610
  theme: { $ref: "#/definitions/DotThemeType" },
607
611
  },
608
612
  },
@@ -40,6 +40,7 @@ function match(pFrom, pTo) {
40
40
  matchers.toExoticRequireNot(pRule, pTo) &&
41
41
  matchers.toVia(pRule, pTo) &&
42
42
  matchers.toViaNot(pRule, pTo) &&
43
+ matchers.toIsMoreUnstable(pRule, pFrom, pTo) &&
43
44
  // preCompilationOnly is not a mandatory attribute, but if the attribute
44
45
  // is in the rule but not in the dependency there won't be a match
45
46
  // anyway, so we can use the default propertyEquals method regardless
@@ -1,3 +1,4 @@
1
+ const _has = require("lodash/has");
1
2
  const { intersects } = require("../utl/array-util");
2
3
 
3
4
  function fromPath(pRule, pModule) {
@@ -123,6 +124,16 @@ function toVia(pRule, pDependency) {
123
124
  );
124
125
  }
125
126
 
127
+ function toIsMoreUnstable(pRule, pFrom, pTo) {
128
+ if (_has(pRule, "to.moreUnstable")) {
129
+ return (
130
+ (pRule.to.moreUnstable && pFrom.instability < pTo.instability) ||
131
+ (!pRule.to.moreUnstable && pFrom.instability >= pTo.instability)
132
+ );
133
+ }
134
+ return true;
135
+ }
136
+
126
137
  module.exports = {
127
138
  _replaceGroupPlaceholders,
128
139
  fromPath,
@@ -141,4 +152,5 @@ module.exports = {
141
152
  toExoticRequireNot,
142
153
  toVia,
143
154
  toViaNot,
155
+ toIsMoreUnstable,
144
156
  };
@@ -0,0 +1 @@
1
+ ![overview](overview.svg)
@@ -1,3 +1,3 @@
1
- import { IViolation } from "./cruise-result";
1
+ import { IViolation } from "./violations";
2
2
 
3
3
  export type IBaselineViolations = IViolation[];
@@ -1,11 +1,8 @@
1
1
  import { ICruiseOptions } from "./options";
2
2
  import { IFlattenedRuleSet } from "./rule-set";
3
- import {
4
- DependencyType,
5
- ModuleSystemType,
6
- SeverityType,
7
- ProtocolType,
8
- } from "./shared-types";
3
+ import { DependencyType, ModuleSystemType, ProtocolType } from "./shared-types";
4
+ import { IViolation } from "./violations";
5
+ import { IRuleSummary } from "./rule-summary";
9
6
 
10
7
  export interface ICruiseResult {
11
8
  /**
@@ -224,27 +221,11 @@ export interface IDependency {
224
221
  * will be in the 'rule' object at the same level.
225
222
  */
226
223
  valid: boolean;
227
- }
228
-
229
- /**
230
- * If there was a rule violation (valid === false), this object contains the name of the
231
- * rule and severity of violating it.
232
- */
233
- export interface IRuleSummary {
234
224
  /**
235
- * The (short, eslint style) name of the violated rule. Typically something like
236
- * 'no-core-punycode' or 'no-outside-deps'.
225
+ * the (de-normalized) instability of the dependency - also available in
226
+ * the module on the 'to' side of this dependency
237
227
  */
238
- name: string;
239
- /**
240
- * How severe a violation of a rule is. The 'error' severity will make some reporters return
241
- * a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
242
- * use that. The absence of the 'ignore' severity here is by design; ignored rules don't
243
- * show up in the output.
244
- *
245
- * Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
246
- */
247
- severity: SeverityType;
228
+ instability: number;
248
229
  }
249
230
 
250
231
  export interface IReachable {
@@ -389,29 +370,6 @@ export interface IWebpackConfig {
389
370
  */
390
371
  export type WebpackEnvType = { [key: string]: any } | string;
391
372
 
392
- export interface IViolation {
393
- /**
394
- * The violated rule
395
- */
396
- rule: IRuleSummary;
397
- /**
398
- * The from part of the dependency this violation is about
399
- */
400
- from: string;
401
- /**
402
- * The to part of the dependency this violation is about
403
- */
404
- to: string;
405
- /**
406
- * The circular path if the violation is about circularity
407
- */
408
- cycle?: string[];
409
- /**
410
- * The path from the from to the to if the violation is transitive
411
- */
412
- via?: string[];
413
- }
414
-
415
373
  export interface IFolder {
416
374
  /**
417
375
  * The name of the folder. FOlder names are normalized to posix (so
@@ -455,3 +413,6 @@ export interface IFolder {
455
413
  */
456
414
  instability?: number;
457
415
  }
416
+
417
+ export * from "./violations";
418
+ export * from "./rule-summary";
@@ -239,4 +239,9 @@ export interface ICruiseOptions {
239
239
  */
240
240
  type: ProgressType;
241
241
  };
242
+ /**
243
+ * When this flag is set to true, dependency-cruiser will calculate (stability) metrics
244
+ * for all modules and folders. Defaults to false.
245
+ */
246
+ metrics?: boolean;
242
247
  }
@@ -58,6 +58,12 @@ export interface IDotReporterOptions {
58
58
  * goal of the report)
59
59
  */
60
60
  filters?: IReporterFiltersType;
61
+ /**
62
+ * When passed the value 'true', shows instability metrics in the
63
+ * output if dependency-cruiser calculated them. Doesn't show them
64
+ * in all other cases. Defaults to false",
65
+ */
66
+ showMetrics?: boolean;
61
67
  /**
62
68
  * A bunch of criteria to (conditionally) theme the dot output
63
69
  */
@@ -92,6 +92,18 @@ export interface IToRestriction extends IBaseRestrictionType {
92
92
  * licenses. E.g. to flag everyting non MIT use "MIT" here
93
93
  */
94
94
  licenseNot?: string | string[];
95
+ /**
96
+ * When set to true moreUnstable matches for any dependency that has a higher
97
+ * Instability than the module that depends on it. When set to false it matches
98
+ * when the opposite is true; the dependency has an equal or lower Instability.
99
+ *
100
+ * This attribute is useful when you want to check against Robert C. Martin's
101
+ * stable dependency * principle. See online documentation for examples and
102
+ * details.
103
+ *
104
+ * Leave this out when you don't care either way.
105
+ */
106
+ moreUnstable?: boolean;
95
107
  }
96
108
 
97
109
  export interface IReachabilityToRestrictionType extends IBaseRestrictionType {
@@ -0,0 +1,23 @@
1
+ import { SeverityType } from "./shared-types";
2
+
3
+ /**
4
+ * If there was a rule violation (valid === false), this object contains the name of the
5
+ * rule and severity of violating it.
6
+ */
7
+
8
+ export interface IRuleSummary {
9
+ /**
10
+ * The (short, eslint style) name of the violated rule. Typically something like
11
+ * 'no-core-punycode' or 'no-outside-deps'.
12
+ */
13
+ name: string;
14
+ /**
15
+ * How severe a violation of a rule is. The 'error' severity will make some reporters return
16
+ * a non-zero exit code, so if you want e.g. a build to stop when there's a rule violated:
17
+ * use that. The absence of the 'ignore' severity here is by design; ignored rules don't
18
+ * show up in the output.
19
+ *
20
+ * Severity to use when a dependency is not in the 'allowed' set of rules. Defaults to 'warn'
21
+ */
22
+ severity: SeverityType;
23
+ }
@@ -0,0 +1,24 @@
1
+ import { IRuleSummary } from "./rule-summary";
2
+
3
+ export interface IViolation {
4
+ /**
5
+ * The violated rule
6
+ */
7
+ rule: IRuleSummary;
8
+ /**
9
+ * The from part of the dependency this violation is about
10
+ */
11
+ from: string;
12
+ /**
13
+ * The to part of the dependency this violation is about
14
+ */
15
+ to: string;
16
+ /**
17
+ * The circular path if the violation is about circularity
18
+ */
19
+ cycle?: string[];
20
+ /**
21
+ * The path from the from to the to if the violation is transitive
22
+ */
23
+ via?: string[];
24
+ }