dependency-cruiser 18.2.0-beta-2 → 18.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.
@@ -1,5 +1,5 @@
1
1
  import { readdirSync, statSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
2
+ import { join, relative } from "node:path/posix";
3
3
  import ignore from "ignore";
4
4
  import pathToPosix from "./path-to-posix.mjs";
5
5
 
@@ -7,6 +7,10 @@ import pathToPosix from "./path-to-posix.mjs";
7
7
  * @typedef {(pString:string, pIndex: number, pArray: string[]) => boolean} FilterFunctionType
8
8
  */
9
9
 
10
+ /**
11
+ * @typedef {{directoryName: string; ignoreMatcher: import("ignore").Ignore}} IgnoreRuleType
12
+ */
13
+
10
14
  /**
11
15
  * @param {string} pFullPathToFile
12
16
  * @param {string} pBaseDirectory
@@ -19,18 +23,200 @@ function fileIsDirectory(pFullPathToFile, pBaseDirectory) {
19
23
  return lStat?.isDirectory() ?? false;
20
24
  }
21
25
 
26
+ /**
27
+ * @param {string} pFileName
28
+ * @returns {string}
29
+ */
30
+ function readIgnoreFile(pFileName) {
31
+ try {
32
+ return readFileSync(pFileName, "utf8");
33
+ } catch {
34
+ return "";
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @param {string} pDirectoryName
40
+ * @param {string} pBaseDirectory
41
+ * @returns {string}
42
+ */
43
+ function normalizeDirectoryName(pDirectoryName, pBaseDirectory) {
44
+ return pathToPosix(
45
+ relative(pBaseDirectory, join(pBaseDirectory, pDirectoryName)),
46
+ );
47
+ }
48
+
49
+ /**
50
+ * @param {string} pDirectoryName
51
+ * @param {string} pIgnoreFileContents
52
+ * @param {string[]=} pAdditionalIgnorePatterns
53
+ * @returns {IgnoreRuleType}
54
+ */
55
+ function createIgnoreRule(
56
+ pDirectoryName,
57
+ pIgnoreFileContents,
58
+ pAdditionalIgnorePatterns = [],
59
+ ) {
60
+ return {
61
+ directoryName: pDirectoryName,
62
+ ignoreMatcher: ignore()
63
+ .add(pIgnoreFileContents)
64
+ .add(pAdditionalIgnorePatterns),
65
+ };
66
+ }
67
+
22
68
  /**
23
69
  * @param {string} pDirectoryName
24
- * @param {{baseDir: string; ignoreFilterFn: FilterFunctionType; excludeFilterFn: FilterFunctionType; includeOnlyFilterFn: FilterFunctionType}} pOptions
70
+ * @param {string} pBaseDirectory
71
+ * @param {{ignoreFileContents?: string}} pOptions
72
+ * @returns {IgnoreRuleType}
73
+ */
74
+ function createIgnoreRuleForDirectory(
75
+ pDirectoryName,
76
+ pBaseDirectory,
77
+ pOptions,
78
+ ) {
79
+ const lIgnoreFileContents =
80
+ pOptions.ignoreFileContents === undefined
81
+ ? readIgnoreFile(join(pBaseDirectory, pDirectoryName, ".gitignore"))
82
+ : pOptions.ignoreFileContents;
83
+
84
+ return createIgnoreRule(pDirectoryName, lIgnoreFileContents);
85
+ }
86
+
87
+ /**
88
+ * @param {string[]} pAncestorDirectoryNames
89
+ * @param {string} pBaseDirectory
90
+ * @returns {IgnoreRuleType[]}
91
+ */
92
+ function createIgnoreRulesFromDirectoryNames(
93
+ pAncestorDirectoryNames,
94
+ pBaseDirectory,
95
+ ) {
96
+ return pAncestorDirectoryNames.map((pAncestorDirectoryName) =>
97
+ createIgnoreRuleForDirectory(pAncestorDirectoryName, pBaseDirectory, {}),
98
+ );
99
+ }
100
+
101
+ /**
102
+ * @param {string} pDirectoryName
103
+ * @param {string} pBaseDirectory
104
+ * @returns {IgnoreRuleType[]}
105
+ */
106
+ function createIgnoreRulesBeforeDirectory(pDirectoryName, pBaseDirectory) {
107
+ const lNormalizedDirectoryName = normalizeDirectoryName(
108
+ pDirectoryName,
109
+ pBaseDirectory,
110
+ );
111
+
112
+ if (lNormalizedDirectoryName === "") {
113
+ return [];
114
+ }
115
+
116
+ const lDirectorySegments = lNormalizedDirectoryName.split("/");
117
+ const lAncestorDirectoryNames = [""];
118
+ let lCurrentDirectoryName = "";
119
+
120
+ for (const lDirectorySegment of lDirectorySegments.slice(0, -1)) {
121
+ lCurrentDirectoryName = lCurrentDirectoryName
122
+ ? join(lCurrentDirectoryName, lDirectorySegment)
123
+ : lDirectorySegment;
124
+ lAncestorDirectoryNames.push(pathToPosix(lCurrentDirectoryName));
125
+ }
126
+
127
+ return createIgnoreRulesFromDirectoryNames(
128
+ lAncestorDirectoryNames,
129
+ pBaseDirectory,
130
+ );
131
+ }
132
+
133
+ /**
134
+ * @param {string} pFilePath
135
+ * @param {IgnoreRuleType[]} pIgnoreRules
136
+ * @param {IgnoreRuleType} pMandatoryIgnoreRule
137
+ * @returns {boolean}
138
+ */
139
+ function fileShouldBeKept(pFilePath, pIgnoreRules, pMandatoryIgnoreRule) {
140
+ let lFileIsIgnored = false;
141
+
142
+ for (const lIgnoreRule of pIgnoreRules) {
143
+ const lDirectoryName = lIgnoreRule.directoryName;
144
+ const lIgnoreMatcher = lIgnoreRule.ignoreMatcher;
145
+ let lRelativePath = null;
146
+
147
+ if (lDirectoryName === "") {
148
+ lRelativePath = pFilePath;
149
+ } else if (pFilePath.startsWith(`${lDirectoryName}/`)) {
150
+ lRelativePath = pFilePath.slice(lDirectoryName.length + 1);
151
+ }
152
+
153
+ if (lRelativePath !== null) {
154
+ const { ignored: lIgnored, unignored: lUnignored } =
155
+ lIgnoreMatcher.test(lRelativePath);
156
+
157
+ if (lIgnored) {
158
+ lFileIsIgnored = true;
159
+ }
160
+ if (lUnignored) {
161
+ lFileIsIgnored = false;
162
+ }
163
+ }
164
+ }
165
+
166
+ const { ignored: lIgnored, unignored: lUnignored } =
167
+ pMandatoryIgnoreRule.ignoreMatcher.test(pFilePath);
168
+
169
+ if (lIgnored) {
170
+ lFileIsIgnored = true;
171
+ }
172
+ if (lUnignored) {
173
+ lFileIsIgnored = false;
174
+ }
175
+
176
+ return !lFileIsIgnored;
177
+ }
178
+
179
+ /**
180
+ * @type FilterFunctionType
181
+ */
182
+
183
+ function identityFilter(_pString, _pIndex, _pArray) {
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * @param {string} pDirectoryName
189
+ * @param {{baseDir: string; ignoreRules: IgnoreRuleType[]; mandatoryIgnoreRule: IgnoreRuleType; startDirectoryName: string; startDirectoryIgnoreFileContents?: string; excludeFilterFn: FilterFunctionType; includeOnlyFilterFn: FilterFunctionType}}
190
+ * pOptions
25
191
  * @returns {string[]}
26
192
  */
27
193
  function walk(
28
194
  pDirectoryName,
29
- { baseDir, ignoreFilterFn, excludeFilterFn, includeOnlyFilterFn },
195
+ {
196
+ baseDir,
197
+ ignoreRules,
198
+ mandatoryIgnoreRule,
199
+ startDirectoryName,
200
+ startDirectoryIgnoreFileContents,
201
+ excludeFilterFn,
202
+ includeOnlyFilterFn,
203
+ },
30
204
  ) {
205
+ const lCurrentDirectoryName = normalizeDirectoryName(pDirectoryName, baseDir);
206
+ const lCurrentIgnoreRules = ignoreRules.concat(
207
+ createIgnoreRuleForDirectory(lCurrentDirectoryName, baseDir, {
208
+ ...(lCurrentDirectoryName === startDirectoryName &&
209
+ startDirectoryIgnoreFileContents !== undefined
210
+ ? { ignoreFileContents: startDirectoryIgnoreFileContents }
211
+ : {}),
212
+ }),
213
+ );
214
+
31
215
  const lFilesInCurrentDirectory = readdirSync(join(baseDir, pDirectoryName))
32
216
  .map((pFileName) => join(pDirectoryName, pFileName))
33
- .filter(ignoreFilterFn)
217
+ .filter((pFilePath) =>
218
+ fileShouldBeKept(pFilePath, lCurrentIgnoreRules, mandatoryIgnoreRule),
219
+ )
34
220
  .filter(excludeFilterFn)
35
221
  .filter(includeOnlyFilterFn);
36
222
 
@@ -40,7 +226,10 @@ function walk(
40
226
  lFiles.push(
41
227
  ...walk(lFile, {
42
228
  baseDir,
43
- ignoreFilterFn,
229
+ ignoreRules: lCurrentIgnoreRules,
230
+ mandatoryIgnoreRule,
231
+ startDirectoryName,
232
+ startDirectoryIgnoreFileContents,
44
233
  excludeFilterFn,
45
234
  includeOnlyFilterFn,
46
235
  }),
@@ -53,25 +242,10 @@ function walk(
53
242
  return lFiles;
54
243
  }
55
244
 
56
- function readIgnoreFile(pFileName) {
57
- try {
58
- return readFileSync(pFileName, "utf8");
59
- } catch {
60
- return "";
61
- }
62
- }
63
-
64
- /**
65
- * @type FilterFunctionType
66
- */
67
-
68
- function identityFilter(_pString, _pIndex, _pArray) {
69
- return true;
70
- }
71
-
72
245
  /**
73
246
  * @param {string} pDirectoryName
74
- * @param {{baseDir: string; ignoreFileContents?: string; additionalIgnorePatterns?: string[]; excludeFilterFn?: FilterFunctionType; includeOnlyFilterFn?: FilterFunctionType}} pOptions
247
+ * @param {{baseDir: string; ignoreFileContents?: string; additionalIgnorePatterns?: string[]; excludeFilterFn?: FilterFunctionType; includeOnlyFilterFn?: FilterFunctionType}}
248
+ * pOptions
75
249
  * @returns {string[]}
76
250
  */
77
251
  export default function findAllFiles(
@@ -84,17 +258,24 @@ export default function findAllFiles(
84
258
  includeOnlyFilterFn,
85
259
  },
86
260
  ) {
87
- const lIgnoreFileContents =
88
- ignoreFileContents ?? readIgnoreFile(join(baseDir, ".gitignore"));
89
261
  const lAdditionalIgnorePatterns = additionalIgnorePatterns ?? [".git"];
90
- const lIgnoreFilterFunction = ignore()
91
- .add(lIgnoreFileContents)
92
- .add(lAdditionalIgnorePatterns)
93
- .createFilter();
262
+ const lStartDirectoryName = normalizeDirectoryName(pDirectoryName, baseDir);
263
+ const lMandatoryIgnoreRule = createIgnoreRule(
264
+ "",
265
+ "",
266
+ lAdditionalIgnorePatterns,
267
+ );
268
+ const lIgnoreRules = createIgnoreRulesBeforeDirectory(
269
+ pDirectoryName,
270
+ baseDir,
271
+ );
94
272
 
95
273
  return walk(pDirectoryName, {
96
274
  baseDir,
97
- ignoreFilterFn: lIgnoreFilterFunction,
275
+ ignoreRules: lIgnoreRules,
276
+ mandatoryIgnoreRule: lMandatoryIgnoreRule,
277
+ startDirectoryName: lStartDirectoryName,
278
+ startDirectoryIgnoreFileContents: ignoreFileContents,
98
279
  excludeFilterFn: excludeFilterFn ?? identityFilter,
99
280
  includeOnlyFilterFn: includeOnlyFilterFn ?? identityFilter,
100
281
  });
@@ -14,7 +14,7 @@ function getVersion(pModuleName) {
14
14
  // The 'proper' way to do this would be with a dynamic import with an
15
15
  // import assertion. Because it's 'experimental' since node 16 and prints
16
16
  // an ugly warning on stderr since node 19 we'll be using the require
17
- // hack below in stead. Code would otherwise have been:
17
+ // hack below instead. Code would otherwise have been:
18
18
  //
19
19
  // const lManifest = await import(
20
20
  // // @ts-expect-error TS2345 extractRootModuleName can return either a string or
@@ -26,6 +26,12 @@ export interface IReporterOutput {
26
26
  * a string.
27
27
  */
28
28
  output: ICruiseResult | string;
29
+ /**
30
+ * Textual meta information to show in a side-channel (like stderr). E.g.
31
+ * the baseline reporter returns the numerical diffs between the current
32
+ * and the new baseline.
33
+ */
34
+ meta?: string;
29
35
  /**
30
36
  * The exit code - reporters can return a non-zero value when they find
31
37
  * errors here. api consumers (like a cli) can use this to return a
@@ -16,6 +16,7 @@ export type ExternalModuleResolutionStrategyType = "node_modules" | "yarn-pnp";
16
16
  export type ProgressType =
17
17
  "cli-feedback" | "performance-log" | "ndjson" | "none";
18
18
  export type ParserType = "acorn" | "tsc" | "swc";
19
+ export type BaselineModeType = "full" | "shrink-only";
19
20
 
20
21
  export interface ITsConfig {
21
22
  fileName?: string;
@@ -109,6 +110,11 @@ export interface ICruiseOptions {
109
110
  * as 'highlighted' in its output
110
111
  */
111
112
  highlight?: string | string[] | IHighlightType;
113
+ /*
114
+ * whether or not to ignore known violations (whether passed in a separate
115
+ * file or directly in knownViolations)
116
+ */
117
+ ignoreKnown?: boolean;
112
118
  /*
113
119
  * baseline of known validations. Typically you'd specify these in a file called
114
120
  * .dependency-cruiser-known-violations.json (which you'd generate with the --outputType
@@ -234,7 +240,7 @@ export interface ICruiseOptions {
234
240
  *
235
241
  * Hasn't had any effect on dependency-cruiser's behaviour since a few major
236
242
  * versions ago. If there's a need to manipulate this use the `skipAnalysisNotInRules`
237
- * option in stead.
243
+ * option instead.
238
244
  *
239
245
  * Previously documented behavior:
240
246
  * > When true includes denormalized dependents in the cruise-result, even
@@ -424,6 +430,16 @@ export interface ICruiseOptions {
424
430
  * When caching is switched on the default cache folder is 'node_modules/.cache/dependency-cruiser/'
425
431
  */
426
432
  cache?: boolean | string | Partial<ICacheOptions>;
433
+
434
+ baseline?: {
435
+ /**
436
+ * How to update the baseline;
437
+ * 'full' - replaces all violations with the current set (the default behaviour)
438
+ * 'shrink-only' - keeps existing violations, removes fixed violations, but
439
+ * does not add new ones.
440
+ */
441
+ mode?: BaselineModeType;
442
+ };
427
443
  }
428
444
 
429
445
  export interface IFormatOptions {
@@ -84,13 +84,13 @@ export interface IToRestriction extends IBaseRestrictionType {
84
84
  * _only_ modules that don't satisfy this regular expression. E.g. to disallow all cycles,
85
85
  * except when they go through one specific module. Typically to temporarily
86
86
  * allow some cycles until they're removed.
87
- * @deprecated use viaOnly.pathNot in stead
87
+ * @deprecated use viaOnly.pathNot instead
88
88
  */
89
89
  viaNot?: string | string[];
90
90
  /**
91
91
  * "For circular dependencies - whether or not to match cycles that include
92
92
  * _some_ modules that don't satisfy this regular expression.
93
- * @deprecated use via.pathNot in stead
93
+ * @deprecated use via.pathNot instead
94
94
  */
95
95
  viaSomeNot?: string | string[];
96
96
  /**
@@ -3,26 +3,29 @@ export type ModuleSystemType = "cjs" | "amd" | "es6" | "tsd";
3
3
 
4
4
  // cruise options, dependency-cruiser
5
5
  export type OutputType =
6
- | "json"
7
- | "html"
8
- | "dot"
9
- | "cdot"
6
+ | "anon"
10
7
  | "archi"
8
+ | "azure-devops"
9
+ | "baseline"
10
+ | "cdot"
11
+ | "csv"
12
+ | "d2"
11
13
  | "ddot"
14
+ | "dot-webpage"
15
+ | "dot"
16
+ | "err-html"
17
+ | "err-long"
18
+ | "err"
12
19
  | "fdot"
13
20
  | "flat"
14
- | "csv"
15
- | "err"
16
- | "err-long"
17
- | "err-html"
18
- | "teamcity"
19
- | "anon"
20
- | "text"
21
- | "metrics"
21
+ | "html"
22
+ | "json"
22
23
  | "markdown"
23
24
  | "mermaid"
24
- | "d2"
25
+ | "metrics"
25
26
  | "null"
27
+ | "teamcity"
28
+ | "text"
26
29
  // for plugins: string. TODO: research whether it's possible to
27
30
  // tie this down to the `^plugin:[^:]+-reporter-plugin.[cm]?js$` regex
28
31
  | (string & {}); // autocompletion hack