dependency-cruiser 17.3.4 → 17.3.5-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "17.3.4",
3
+ "version": "17.3.5-beta-2",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -21,6 +21,7 @@ export default class MetaDataStrategy {
21
21
  /**
22
22
  * @param {string} _pDirectory
23
23
  * @param {ICruiseResult} _pCachedCruiseResult
24
+ * @param {IStrictCruiseOptions} pCruiseOptions
24
25
  * @param {Object} pOptions
25
26
  * @param {Set<string>} pOptions.extensions
26
27
  * @param {Set<changeType>=} pOptions.interestingChangeTypes
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable security/detect-object-injection */
2
+ import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs";
2
3
 
3
4
  /** @import { IFlattenedRuleSet } from "../../../types/rule-set.mjs" */
4
5
 
@@ -44,29 +45,33 @@ function addCircularityCheckToDependency(
44
45
  }
45
46
 
46
47
  /**
47
- * Runs through all dependencies of all pNodes, for each of them determines
48
+ * Runs through all dependencies of all pModulesOrFolders, for each of them determines
48
49
  * whether it's (part of a) circular (relationship) and returns the
49
50
  * dependencies with that added.
50
51
  */
51
52
  export default function detectAndAddCycles(
52
- pNodes,
53
- pIndexedNodes,
53
+ pModulesOrFolders,
54
54
  { pSourceAttribute, pDependencyName, pSkipAnalysisNotInRules, pRuleSet },
55
55
  ) {
56
56
  if (!pSkipAnalysisNotInRules || hasCycleRule(pRuleSet)) {
57
- return pNodes.map((pModule) => ({
58
- ...pModule,
59
- dependencies: pModule.dependencies.map((pToDep) =>
57
+ const lIndexedGraph = new IndexedModuleGraph(
58
+ pModulesOrFolders,
59
+ pSourceAttribute,
60
+ );
61
+
62
+ return pModulesOrFolders.map((pModuleOrFolder) => ({
63
+ ...pModuleOrFolder,
64
+ dependencies: pModuleOrFolder.dependencies.map((pToDep) =>
60
65
  addCircularityCheckToDependency(
61
- pIndexedNodes,
62
- pModule[pSourceAttribute],
66
+ lIndexedGraph,
67
+ pModuleOrFolder[pSourceAttribute],
63
68
  pToDep,
64
69
  pDependencyName,
65
70
  ),
66
71
  ),
67
72
  }));
68
73
  }
69
- return pNodes.map((pModule) => ({
74
+ return pModulesOrFolders.map((pModule) => ({
70
75
  ...pModule,
71
76
  dependencies: pModule.dependencies.map((pToDep) => ({
72
77
  ...pToDep,
@@ -1,13 +1,14 @@
1
- import { isDependent } from "./module-utl.mjs";
2
-
3
1
  /** @import { IFlattenedRuleSet } from "../../../types/rule-set.mjs" */
4
2
 
3
+ import ModuleGraphWithDependencySet from "#graph-utl/module-graph-with-dependency-set.mjs";
4
+
5
5
  function isDependentsRule(pRule) {
6
6
  // used in folder rules and when moreUnstable is in the 'to' => governed by
7
- // the 'metrics' flag in options, sot not going to repeat that here
7
+ // the 'metrics' flag in options, so not going to repeat that here
8
8
 
9
- // dependents are used in the orphans analsys. However, there is a fall back
10
- // where it does its own analysis, so not going to repeat that check here.
9
+ // dependents are used in the orphans analysis. However, there is a fall back
10
+ // where it does its own analysis which is faster on itself, so not going
11
+ // to repeat that check here either.
11
12
  return (
12
13
  /* c8 ignore start */
13
14
  Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsLessThan") ||
@@ -16,13 +17,6 @@ function isDependentsRule(pRule) {
16
17
  );
17
18
  }
18
19
 
19
- export function getDependents(pModule, pModules) {
20
- // perf between O(n) in an unconnected graph and O(n^2) in a fully connected one
21
- return pModules
22
- .filter(isDependent(pModule.source))
23
- .map((pDependentModule) => pDependentModule.source);
24
- }
25
-
26
20
  /**
27
21
  * @param {IFlattenedRuleSet} pRuleSet
28
22
  * @returns {boolean}
@@ -39,9 +33,17 @@ export default function addDependents(
39
33
  { skipAnalysisNotInRules, metrics, ruleSet },
40
34
  ) {
41
35
  if (!skipAnalysisNotInRules || metrics || hasDependentsRule(ruleSet)) {
36
+ // creating this optimized structure here might seem overkill, but on
37
+ // large graphs it pays off significantly - creation is a few centiseconds
38
+ // but it cuts analysis in half on large graphs (and even on smaller
39
+ // graphs it's a win, even though just a few milliseconds)
40
+ const lModulesWithDependencySet = new ModuleGraphWithDependencySet(
41
+ pModules,
42
+ );
43
+
42
44
  return pModules.map((pModule) => ({
43
45
  ...pModule,
44
- dependents: getDependents(pModule, pModules),
46
+ dependents: lModulesWithDependencySet.getDependents(pModule),
45
47
  }));
46
48
  }
47
49
  return pModules;
@@ -9,7 +9,6 @@ import {
9
9
  getParentFolders,
10
10
  object2Array,
11
11
  } from "./utl.mjs";
12
- import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs";
13
12
  import { uniq } from "#utl/array-util.mjs";
14
13
 
15
14
  function upsertCouplings(pAllDependents, pNewDependents) {
@@ -148,7 +147,7 @@ export default function aggregateToFolders(pModules, pOptions = {}) {
148
147
  .map(deNormalizeInstability);
149
148
  lFolders = lFolders.concat(getSinks(lFolders));
150
149
 
151
- return detectCycles(lFolders, new IndexedModuleGraph(lFolders, "name"), {
150
+ return detectCycles(lFolders, {
152
151
  pSourceAttribute: "name",
153
152
  pDependencyName: "name",
154
153
  pSkipAnalysisNotInRules: pOptions.skipAnalysisNotInRules,
@@ -1,5 +1,4 @@
1
1
  import { calculateInstability, metricsAreCalculable } from "../module-utl.mjs";
2
- import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs";
3
2
 
4
3
  export function addInstabilityMetric(pModule) {
5
4
  return {
@@ -15,25 +14,23 @@ export function addInstabilityMetric(pModule) {
15
14
  };
16
15
  }
17
16
 
18
- function addInstabilityToDependency(pAllModules) {
19
- const lIndexedModules = new IndexedModuleGraph(pAllModules);
17
+ function addInstabilityToDependency(pIndexedModules) {
20
18
  return (pDependency) => ({
21
19
  ...pDependency,
22
20
  instability:
23
- (lIndexedModules.findVertexByName(pDependency.resolved) || {})
21
+ (pIndexedModules.findVertexByName(pDependency.resolved) || {})
24
22
  .instability || 0,
25
23
  });
26
24
  }
27
25
 
28
26
  export function deNormalizeInstabilityMetricsToDependencies(
29
27
  pModule,
30
- _,
31
- pAllModules,
28
+ pIndexedModules,
32
29
  ) {
33
30
  return {
34
31
  ...pModule,
35
32
  dependencies: pModule.dependencies.map(
36
- addInstabilityToDependency(pAllModules),
33
+ addInstabilityToDependency(pIndexedModules),
37
34
  ),
38
35
  };
39
36
  }
@@ -2,12 +2,16 @@ import {
2
2
  addInstabilityMetric,
3
3
  deNormalizeInstabilityMetricsToDependencies,
4
4
  } from "./get-module-metrics.mjs";
5
+ import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs";
5
6
 
6
7
  export default function deriveModulesMetrics(pModules, pOptions) {
7
8
  if (pOptions.metrics) {
8
- return pModules
9
- .map(addInstabilityMetric)
10
- .map(deNormalizeInstabilityMetricsToDependencies);
9
+ const lModules = pModules.map(addInstabilityMetric);
10
+ const lIndexedModules = new IndexedModuleGraph(lModules);
11
+
12
+ return lModules.map((pModule) =>
13
+ deNormalizeInstabilityMetricsToDependencies(pModule, lIndexedModules),
14
+ );
11
15
  }
12
16
  return pModules;
13
17
  }
@@ -1,10 +1,3 @@
1
- export function isDependent(pResolvedName) {
2
- return (pModule) =>
3
- pModule.dependencies.some(
4
- (pDependency) => pDependency.resolved === pResolvedName,
5
- );
6
- }
7
-
8
1
  export function metricsAreCalculable(pModule) {
9
2
  return (
10
3
  !pModule.coreModule &&
@@ -1,4 +1,5 @@
1
1
  import isOrphan from "./is-orphan.mjs";
2
+ import ModuleGraphWithDependencySet from "#graph-utl/module-graph-with-dependency-set.mjs";
2
3
 
3
4
  /** @import { IFlattenedRuleSet } from "../../../../types/rule-set.mjs" */
4
5
 
@@ -25,9 +26,19 @@ export default function deriveOrphans(
25
26
  { skipAnalysisNotInRules, ruleSet },
26
27
  ) {
27
28
  if (!skipAnalysisNotInRules || hasOrphanRule(ruleSet)) {
29
+ // just like the dependents derivation, creating this data structure
30
+ // might seem overkill, but it can save effort here as well, even though
31
+ // isOrphan has early exits the dependents derivation doesn't:
32
+ // - when the module already has dependencies (quick length check)
33
+ // - when dependents are already calculated (quick length check as well). If
34
+ // they are, the lModulesWithDependencySet is created for naught.
35
+ const lModulesWithDependencySet = new ModuleGraphWithDependencySet(
36
+ pModules,
37
+ );
38
+
28
39
  return pModules.map((pModule) => ({
29
40
  ...pModule,
30
- orphan: isOrphan(pModule, pModules),
41
+ orphan: isOrphan(pModule, lModulesWithDependencySet),
31
42
  }));
32
43
  }
33
44
  return pModules;
@@ -1,6 +1,11 @@
1
- import { isDependent } from "../module-utl.mjs";
2
-
3
- export default function isOrphan(pModule, pGraph) {
1
+ /**
2
+ * returns true if the given module has no dependencies and no dependents
3
+ *
4
+ * @param {import("../../../../types/dependency-cruiser.mjs").IModule} pModule
5
+ * @param {import("#graph-utl/module-graph-with-dependency-set.mjs").default} pModulesWithDependencySet
6
+ * @returns {boolean}
7
+ */
8
+ export default function isOrphan(pModule, pModulesWithDependencySet) {
4
9
  if (pModule.dependencies.length > 0) {
5
10
  return false;
6
11
  }
@@ -10,5 +15,5 @@ export default function isOrphan(pModule, pGraph) {
10
15
  return pModule.dependents.length === 0;
11
16
  }
12
17
  // ... otherwise calculate them
13
- return !pGraph.some(isDependent(pModule.source));
18
+ return !pModulesWithDependencySet.moduleHasDependents(pModule);
14
19
  }
@@ -5,7 +5,6 @@ import deriveReachable from "./derive/reachable.mjs";
5
5
  import addValidations from "./add-validations.mjs";
6
6
  import softenKnownViolations from "./soften-known-violations.mjs";
7
7
  import deriveModuleMetrics from "./derive/metrics/index.mjs";
8
- import IndexedModuleGraph from "#graph-utl/indexed-module-graph.mjs";
9
8
  import addFocus from "#graph-utl/add-focus.mjs";
10
9
  import { bus } from "#utl/bus.mjs";
11
10
 
@@ -18,8 +17,8 @@ import { bus } from "#utl/bus.mjs";
18
17
  */
19
18
  export default function enrichModules(pModules, pOptions) {
20
19
  bus.info("analyze: cycles");
21
- const lIndexedModules = new IndexedModuleGraph(pModules);
22
- let lModules = deriveCycles(pModules, lIndexedModules, {
20
+
21
+ let lModules = deriveCycles(pModules, {
23
22
  pSourceAttribute: "source",
24
23
  pDependencyName: "resolved",
25
24
  pSkipAnalysisNotInRules: pOptions.skipAnalysisNotInRules,
@@ -0,0 +1,41 @@
1
+ export default class ModuleGraphWithDependencySet {
2
+ /** @type {Array<{source: string, dependencies: Set<string>}>} */
3
+ #modulesWithDependencySet;
4
+ /**
5
+ * Creates a module graph optimized for querying dependents
6
+ * @constructor
7
+ * @param {import("../../types/dependency-cruiser.mjs").IModule[]} pModules
8
+ */
9
+ constructor(pModules) {
10
+ this.#modulesWithDependencySet = pModules.map((pModule) => ({
11
+ source: pModule.source,
12
+ dependencies: new Set(
13
+ pModule.dependencies.map((pDependency) => pDependency.resolved),
14
+ ),
15
+ }));
16
+ }
17
+
18
+ /**
19
+ * Returns true if the given module has dependents in the graph, false otherwise
20
+ *
21
+ * @param {import("../../types/dependency-cruiser.mjs").IModule} pModule
22
+ * @returns {boolean}
23
+ */
24
+ moduleHasDependents(pModule) {
25
+ return this.#modulesWithDependencySet.some(({ dependencies }) =>
26
+ dependencies.has(pModule.source),
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Returns an array of module source paths that depend on the given module
32
+ *
33
+ * @param {import("../../types/dependency-cruiser.mjs").IModule} pModule
34
+ * @returns {Array<string>}
35
+ */
36
+ getDependents(pModule) {
37
+ return this.#modulesWithDependencySet
38
+ .filter(({ dependencies }) => dependencies.has(pModule.source))
39
+ .map(({ source }) => source);
40
+ }
41
+ }
package/src/meta.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /* generated - don't edit */
2
2
  module.exports = {
3
- version: "17.3.4",
3
+ version: "17.3.5-beta-2",
4
4
  engines: {
5
5
  node: "^20.12||^22||>=24",
6
6
  },