dependency-cruiser 17.3.3-beta-4 → 17.3.4-beta-1

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.3-beta-4",
3
+ "version": "17.3.4-beta-1",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -149,7 +149,7 @@
149
149
  "acorn-loose": "8.5.2",
150
150
  "acorn-walk": "8.3.4",
151
151
  "commander": "14.0.2",
152
- "enhanced-resolve": "5.18.3",
152
+ "enhanced-resolve": "5.18.4",
153
153
  "ignore": "7.0.5",
154
154
  "interpret": "3.1.1",
155
155
  "is-installed-globally": "1.0.0",
@@ -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
@@ -11,10 +11,10 @@ import { DEFAULT_CONFIG_FILE_NAME } from "../defaults.mjs";
11
11
  const LIKELY_SOURCE_FOLDERS = ["src", "lib", "app", "bin", "sources"];
12
12
  const LIKELY_TEST_FOLDERS = ["test", "spec", "tests", "specs", "bdd"];
13
13
  const LIKELY_PACKAGES_FOLDERS = ["packages"];
14
- const TSCONFIG_CANDIDATE_PATTERN = /.*tsconfig.*\.json$/gi;
15
- const JSCONFIG_CANDIDATE_PATTERN = /.*jsconfig.*\.json$/gi;
16
- const WEBPACK_CANDIDATE_PATTERN = /.*webpack.*\.(c?js|json5?|ts|ya?ml)$/gi;
17
- const BABEL_CONFIG_CANDIDATE_PATTERN = /^\.babelrc$|.*babel.*\.json/gi;
14
+ const TSCONFIG_CANDIDATE_PATTERN = /.*tsconfig.*\.json$/i;
15
+ const JSCONFIG_CANDIDATE_PATTERN = /.*jsconfig.*\.json$/i;
16
+ const WEBPACK_CANDIDATE_PATTERN = /.*webpack.*\.(c?js|json5?|ts|ya?ml)$/i;
17
+ const BABEL_CONFIG_CANDIDATE_PATTERN = /^\.babelrc$|.*babel.*\.json/i;
18
18
 
19
19
  /**
20
20
  * Read the package manifest ('package.json') and return it as a javascript object
@@ -93,7 +93,7 @@ function getMatchingFileNames(pPattern, pFolderName = process.cwd()) {
93
93
  return readdirSync(pFolderName, "utf8").filter(
94
94
  (pFileName) =>
95
95
  statSync(join(pFolderName, pFileName)).isFile() &&
96
- pFileName.match(pPattern),
96
+ pPattern.test(pFileName),
97
97
  );
98
98
  }
99
99
 
@@ -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,12 @@
1
- import { isDependent } from "./module-utl.mjs";
2
-
3
1
  /** @import { IFlattenedRuleSet } from "../../../types/rule-set.mjs" */
4
2
 
5
3
  function isDependentsRule(pRule) {
6
4
  // 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
5
+ // the 'metrics' flag in options, so not going to repeat that here
8
6
 
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.
7
+ // dependents are used in the orphans analysis. However, there is a fall back
8
+ // where it does its own analysis which is faster on itself, so not going
9
+ // to repeat that check here either.
11
10
  return (
12
11
  /* c8 ignore start */
13
12
  Object.hasOwn(pRule?.module ?? {}, "numberOfDependentsLessThan") ||
@@ -16,10 +15,10 @@ function isDependentsRule(pRule) {
16
15
  );
17
16
  }
18
17
 
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))
18
+ export function getDependents(pModule, pModulesWithDependencySet) {
19
+ // perf O(n) as dependency-lookups are O(1)
20
+ return pModulesWithDependencySet
21
+ .filter(({ dependencies }) => dependencies.has(pModule.source))
23
22
  .map((pDependentModule) => pDependentModule.source);
24
23
  }
25
24
 
@@ -39,9 +38,19 @@ export default function addDependents(
39
38
  { skipAnalysisNotInRules, metrics, ruleSet },
40
39
  ) {
41
40
  if (!skipAnalysisNotInRules || metrics || hasDependentsRule(ruleSet)) {
41
+ // creating this optimized structure here might seem overkill, but on
42
+ // large graphs it pays off significantly - creation is a few centiseconds
43
+ // but it cuts analysis in half on large graphs (and even on smaller
44
+ // graphs it's a win, even though just a few milliseconds)
45
+ const lModulesWithDependencySet = pModules.map((pModule) => ({
46
+ source: pModule.source,
47
+ dependencies: new Set(
48
+ pModule.dependencies.map((pDependency) => pDependency.resolved),
49
+ ),
50
+ }));
42
51
  return pModules.map((pModule) => ({
43
52
  ...pModule,
44
- dependents: getDependents(pModule, pModules),
53
+ dependents: getDependents(pModule, lModulesWithDependencySet),
45
54
  }));
46
55
  }
47
56
  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
  }
@@ -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,
@@ -14,6 +14,7 @@ import {
14
14
  extractModuleAttributes,
15
15
  } from "./helpers.mjs";
16
16
  import { uniqBy, intersects } from "#utl/array-util.mjs";
17
+ import { getCachedRegExp } from "#utl/regex-util.mjs";
17
18
 
18
19
  /**
19
20
  * @import { IDependency } from "../../types/cruise-result.mjs";
@@ -79,7 +80,7 @@ function extractDependencies(pCruiseOptions, pFileName, pTranspileOptions) {
79
80
 
80
81
  function matchesDoNotFollow({ resolved, dependencyTypes }, pDoNotFollow) {
81
82
  const lMatchesPath = pDoNotFollow.path
82
- ? RegExp(pDoNotFollow.path, "g").test(resolved)
83
+ ? getCachedRegExp(pDoNotFollow.path).test(resolved)
83
84
  : false;
84
85
  const lMatchesDependencyTypes = pDoNotFollow.dependencyTypes
85
86
  ? intersects(dependencyTypes, pDoNotFollow.dependencyTypes)
@@ -114,7 +115,7 @@ function addResolutionAttributes(
114
115
  }
115
116
 
116
117
  function matchesPattern(pFullPathToFile, pPattern) {
117
- return RegExp(pPattern, "g").test(pFullPathToFile);
118
+ return getCachedRegExp(pPattern).test(pFullPathToFile);
118
119
  }
119
120
 
120
121
  /**
@@ -166,7 +167,6 @@ export default function getDependencies(
166
167
  extractDependencies(pCruiseOptions, pFileName, pTranspileOptions),
167
168
  getDependencyUniqueKey,
168
169
  )
169
- .sort(compareDeps)
170
170
  .map(
171
171
  addResolutionAttributes(
172
172
  pCruiseOptions,
@@ -181,7 +181,8 @@ export default function getDependencies(
181
181
  !matchesPattern(resolved, pCruiseOptions.exclude.path)) &&
182
182
  (!pCruiseOptions?.includeOnly?.path ||
183
183
  matchesPattern(resolved, pCruiseOptions.includeOnly.path)),
184
- );
184
+ )
185
+ .sort(compareDeps);
185
186
  } catch (pError) {
186
187
  throw new Error(
187
188
  `Extracting dependencies ran afoul of...\n\n ${pError.message}\n... in ${pFileName}\n\n`,
@@ -80,7 +80,6 @@ function extractFileDirectoryArray(
80
80
  const lResult = [];
81
81
  for (const lFilename of lInitialSources) {
82
82
  if (!lVisited.has(lFilename)) {
83
- lVisited.add(lFilename);
84
83
  lResult.push(
85
84
  ...extractRecursive(
86
85
  lFilename,
@@ -96,15 +95,7 @@ function extractFileDirectoryArray(
96
95
  return lResult;
97
96
  }
98
97
 
99
- function isNotFollowable({ followable }) {
100
- return !followable;
101
- }
102
-
103
- function notInFromListAlready(pFromList) {
104
- return ({ resolved }) => !pFromList.some(({ source }) => source === resolved);
105
- }
106
-
107
- function toDependencyToSource(pToListItem) {
98
+ function toDependencyAsModule(pToListItem) {
108
99
  return {
109
100
  source: pToListItem.resolved,
110
101
  followable: pToListItem.followable,
@@ -116,18 +107,20 @@ function toDependencyToSource(pToListItem) {
116
107
  };
117
108
  }
118
109
 
119
- function complete(pAll, pFromListItem) {
120
- return pAll
121
- .concat(pFromListItem)
110
+ function complete(pModules, pModule) {
111
+ return pModules
112
+ .concat(pModule)
122
113
  .concat(
123
- pFromListItem.dependencies
124
- .filter(isNotFollowable)
125
- .filter(notInFromListAlready(pAll))
126
- .map(toDependencyToSource),
114
+ pModule.dependencies
115
+ .filter(
116
+ ({ followable, resolved }) =>
117
+ !followable && !pModules.some(({ source }) => source === resolved),
118
+ )
119
+ .map(toDependencyAsModule),
127
120
  );
128
121
  }
129
122
 
130
- function filterExcludedDynamicDependencies(pModule, pExclude) {
123
+ function removeExcludedDynamicDependencies(pModule, pExclude) {
131
124
  // no need to do the 'path' thing as that was addressed in extractFileDirectoryArray already
132
125
  return {
133
126
  ...pModule,
@@ -164,7 +157,7 @@ export default function extract(
164
157
  )
165
158
  .reduce(complete, [])
166
159
  .map((pModule) =>
167
- filterExcludedDynamicDependencies(pModule, pCruiseOptions.exclude),
160
+ removeExcludedDynamicDependencies(pModule, pCruiseOptions.exclude),
168
161
  );
169
162
  pResolveOptions.fileSystem.purge();
170
163
  clearCaches();
@@ -15,13 +15,16 @@ const typescript = await tryImport(
15
15
  meta.supportedTranspilers.typescript,
16
16
  );
17
17
 
18
- const INTERESTING_NODE_KINDS = new Set([
19
- typescript.SyntaxKind.CallExpression,
20
- typescript.SyntaxKind.ExportDeclaration,
21
- typescript.SyntaxKind.ImportDeclaration,
22
- typescript.SyntaxKind.ImportEqualsDeclaration,
23
- typescript.SyntaxKind.LastTypeNode,
24
- ]);
18
+ const INTERESTING_NODE_KINDS = typescript
19
+ ? new Set([
20
+ typescript.SyntaxKind.CallExpression,
21
+ typescript.SyntaxKind.ExportDeclaration,
22
+ typescript.SyntaxKind.ImportDeclaration,
23
+ typescript.SyntaxKind.ImportEqualsDeclaration,
24
+ typescript.SyntaxKind.LastTypeNode,
25
+ ])
26
+ : /* c8 ignore next 1 */
27
+ new Set();
25
28
 
26
29
  function isTypeOnlyImport(pStatement) {
27
30
  return (
@@ -6,8 +6,8 @@ import { getAvailableReporters } from "#report/index.mjs";
6
6
  * @import { ICruiseOptions, IFormatOptions } from "../../../types/options.mjs";
7
7
  */
8
8
 
9
- const MODULE_SYSTEM_LIST_RE = /^(?:(?:cjs|amd|es6|tsd)(?:,|$)){1,4}/gi;
10
- const VALID_DEPTH_RE = /^\d{1,2}$/g;
9
+ const MODULE_SYSTEM_LIST_RE = /^(?:(?:cjs|amd|es6|tsd)(?:,|$)){1,4}/i;
10
+ const VALID_DEPTH_RE = /^\d{1,2}$/;
11
11
 
12
12
  function isObject(pObject) {
13
13
  return (
@@ -37,7 +37,7 @@ function assertModuleSystemsValid(pModuleSystems) {
37
37
  pModuleSystems &&
38
38
  Array.isArray(pModuleSystems) &&
39
39
  !pModuleSystems.every((pModuleSystem) =>
40
- pModuleSystem.match(MODULE_SYSTEM_LIST_RE),
40
+ MODULE_SYSTEM_LIST_RE.test(pModuleSystem),
41
41
  )
42
42
  ) {
43
43
  throw new Error(
@@ -65,7 +65,7 @@ function assertOutputTypeValid(pOutputType) {
65
65
  }
66
66
 
67
67
  function assertMaxDepthValid(pDepth) {
68
- if (pDepth && !pDepth.toString().match(VALID_DEPTH_RE)) {
68
+ if (pDepth && !VALID_DEPTH_RE.test(pDepth.toString())) {
69
69
  throw new Error(
70
70
  `'${pDepth}' is not a valid depth - use an integer between 0 and 99`,
71
71
  );
package/src/meta.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /* generated - don't edit */
2
2
  module.exports = {
3
- version: "17.3.3-beta-4",
3
+ version: "17.3.4-beta-1",
4
4
  engines: {
5
5
  node: "^20.12||^22||>=24",
6
6
  },
@@ -34,7 +34,7 @@ function replaceFromWordList(pPathElement, pWordList, pCached) {
34
34
  export function anonymizePathElement(
35
35
  pPathElement,
36
36
  pWordList = [],
37
- pWhiteListRE = /^$/g,
37
+ pWhiteListRE = /^$/,
38
38
  pCached = true,
39
39
  ) {
40
40
  return pWhiteListRE.test(pPathElement)
@@ -151,8 +151,7 @@ function sanitizeWordList(pWordList) {
151
151
  return pWordList
152
152
  .map((pString) => pString.replaceAll(/[^a-zA-Z-]/g, "_"))
153
153
  .filter(
154
- (pString) =>
155
- /^[a-zA-Z-_]+$/g.test(pString) && !WHITELIST_RE.test(pString),
154
+ (pString) => /^[a-zA-Z-_]+$/.test(pString) && !WHITELIST_RE.test(pString),
156
155
  );
157
156
  }
158
157
 
@@ -1,15 +1,12 @@
1
1
  import DEFAULT_THEME from "./default-theme.mjs";
2
2
  import { attributizeObject } from "./module-utl.mjs";
3
3
  import { has, get } from "#utl/object-util.mjs";
4
-
5
- function matchesRE(pValue, pRE) {
6
- const lMatchResult = pValue.match && pValue.match(pRE);
7
-
8
- return lMatchResult && lMatchResult.length > 0;
9
- }
4
+ import { getCachedRegExp } from "#utl/regex-util.mjs";
10
5
 
11
6
  function matchesCriterion(pModuleKey, pCriterion) {
12
- return pModuleKey === pCriterion || matchesRE(pModuleKey, pCriterion);
7
+ return (
8
+ pModuleKey === pCriterion || getCachedRegExp(pCriterion).test(pModuleKey)
9
+ );
13
10
  }
14
11
 
15
12
  function moduleOrDependencyMatchesCriteria(pSchemeEntry, pModule) {
@@ -1,5 +1,5 @@
1
- const LOCAL_MODULE_RE = /^[.]{1,2}($|\/.*)/g;
2
- const ABSOLUTE_MODULE_RE = /^\/.*/g;
1
+ const LOCAL_MODULE_RE = /^[.]{1,2}($|\/.*)/;
2
+ const ABSOLUTE_MODULE_RE = /^\/.*/;
3
3
 
4
4
  const PACKAGE_RE = "[^/]+";
5
5
  const SCOPED_PACKAGE_RE = "@[^/]+(/[^/]+)";
@@ -13,8 +13,8 @@ const ROOT_MODULE_RE = new RegExp(`^(${SCOPED_PACKAGE_RE}|${PACKAGE_RE})`, "g");
13
13
  */
14
14
  module.exports = function extractRootModuleName(pModuleName) {
15
15
  if (
16
- pModuleName.match(LOCAL_MODULE_RE) ||
17
- pModuleName.match(ABSOLUTE_MODULE_RE)
16
+ LOCAL_MODULE_RE.test(pModuleName) ||
17
+ ABSOLUTE_MODULE_RE.test(pModuleName)
18
18
  ) {
19
19
  return pModuleName;
20
20
  } else {