dependency-cruiser 18.2.0 → 18.3.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/README.md CHANGED
@@ -30,7 +30,7 @@ pnpm add -D dependency-cruiser
30
30
  ### ... and generate a config
31
31
 
32
32
  ```shell
33
- npx depcruise --init
33
+ npx dependency-cruiser --init
34
34
  ```
35
35
 
36
36
  This will look around in your environment a bit, ask you some questions and create
@@ -56,7 +56,7 @@ cruiser with output type `dot` and run _GraphViz dot_[^3] on the result. In
56
56
  a one liner:
57
57
 
58
58
  ```shell
59
- npx depcruise src --include-only "^src" --output-type dot | dot -T svg > dependency-graph.svg
59
+ npx dependency-cruiser src --include-only "^src" --output-type dot | dot -T svg > dependency-graph.svg
60
60
  ```
61
61
 
62
62
  > <details>
@@ -67,7 +67,7 @@ npx depcruise src --include-only "^src" --output-type dot | dot -T svg > depende
67
67
  > configuration file:
68
68
  >
69
69
  > ```shell
70
- > npx depcruise src --include-only "^src" --config --output-type dot | dot -T svg > dependency-graph.svg
70
+ > npx dependency-cruiser src --include-only "^src" --config --output-type dot | dot -T svg > dependency-graph.svg
71
71
  > ```
72
72
 
73
73
  </details>
@@ -90,7 +90,7 @@ npx depcruise src --include-only "^src" --output-type dot | dot -T svg > depende
90
90
 
91
91
  #### Declare some rules
92
92
 
93
- When you ran `depcruise --init` above, the command also added some rules
93
+ When you ran `dependency-cruiser --init` above, the command also added some rules
94
94
  to `.dependency-cruiser.js` that make sense in most projects, like detecting
95
95
  **circular dependencies**, dependencies **missing** in package.json, **orphans**,
96
96
  and production code relying on dev- or optionalDependencies.
@@ -120,7 +120,7 @@ Sample rule:
120
120
  #### Report them
121
121
 
122
122
  ```sh
123
- npx depcruise src
123
+ npx dependency-cruiser src
124
124
  ```
125
125
 
126
126
  > <details>
@@ -131,7 +131,7 @@ npx depcruise src
131
131
  > configuration file:
132
132
  >
133
133
  > ```shell
134
- > npx depcruise --config .dependency-cruiser.js src
134
+ > npx dependency-cruiser --config .dependency-cruiser.js src
135
135
  > ```
136
136
 
137
137
  </details>
@@ -10,14 +10,17 @@ function showHelp() {
10
10
  .write(`Usage: depcruise-baseline [options] <files-or-directories...>
11
11
 
12
12
  Writes all known violations of rules in a .dependency-cruiser.js to a file.
13
- Alias for depcruise -c -T baseline -f .dependency-cruiser-known-violations.json [files-or-directories]
14
- Details: https://github.com/sverweij/dependency-cruiser
13
+ Alias for dependency-cruiser --baseline
15
14
 
16
15
  Options:
17
16
  -c, --config [file] read rules and options from [file] (default: true)
18
17
  -f, --output-to [file] file to write output to; - for stdout (default: ".dependency-cruiser-known-violations.json")
19
18
  -V, --version display version number
20
19
  -h, --help display help for command
20
+
21
+ ‼ Deprecated. Use dependency-cruiser --baseline instead.
22
+ ‼ See https://github.com/sverweij/dependency-cruiser/blob/main/doc/cli.md#--baseline-create-or-update-a-known-violations-baseline
23
+
21
24
  `);
22
25
  }
23
26
 
@@ -36,6 +39,10 @@ try {
36
39
  short: "f",
37
40
  default: ".dependency-cruiser-known-violations.json",
38
41
  },
42
+ progress: {
43
+ type: "string",
44
+ default: "none",
45
+ },
39
46
  version: {
40
47
  type: "boolean",
41
48
  short: "V",
@@ -56,11 +63,11 @@ try {
56
63
  if (options.config === "") {
57
64
  options.config = true;
58
65
  }
66
+
59
67
  process.exitCode = await cli(positionals, {
60
68
  config: options.config,
61
- outputTo: options["output-to"],
62
- cache: false,
63
- outputType: "baseline",
69
+ baseline: options["output-to"],
70
+ progress: options["progress"],
64
71
  });
65
72
  }
66
73
  } catch (pError) {
@@ -72,10 +72,11 @@ try {
72
72
  "include modules matching the regex, but don't follow their dependencies",
73
73
  )
74
74
  .option(
75
- "--ignore-known [file]",
76
- "ignore known violations as saved in [file] (default: .dependency-cruiser-known-violations.json)",
75
+ "-S, --collapse <regex>",
76
+ "collapse a to a folder depth by passing a single digit (e.g. 2). When passed a " +
77
+ 'regex collapses to that pattern. E.g. "^packages/[^/]+/" would collapse to ' +
78
+ `modules/ folders directly under your packages folder. ${EOL}`,
77
79
  )
78
- .addOption(new Option("--no-ignore-known").hideHelp(true))
79
80
  .addOption(
80
81
  new Option(
81
82
  "--ts-config [file]",
@@ -96,20 +97,20 @@ try {
96
97
  ).hideHelp(true),
97
98
  )
98
99
  .option(
99
- "-S, --collapse <regex>",
100
- "collapse a to a folder depth by passing a single digit (e.g. 2). When passed a " +
101
- 'regex collapses to that pattern. E.g. "^packages/[^/]+/" would collapse to ' +
102
- "modules/ folders directly under your packages folder. ",
100
+ "--baseline [file]",
101
+ 'create or update a known violations baseline. Implies --no-ignore-known --no-cache (default: ".dependency-cruiser-known-violations.json")',
103
102
  )
104
103
  .addOption(
105
104
  new Option(
106
- "-p, --progress [type]",
107
- "show progress while dependency-cruiser is busy",
108
- ).choices(["cli-feedback", "performance-log", "ndjson", "none"]),
105
+ "--baseline-mode <mode>",
106
+ '"shrink-only" - remove fixed violations but don\'t add new ones; "full" (default) - replace everything',
107
+ ).choices(["full", "shrink-only"]),
109
108
  )
110
- .addOption(
111
- new Option("--no-progress", "Alias of --progress none").hideHelp(true),
109
+ .option(
110
+ "--ignore-known [file]",
111
+ `ignore known violations as saved in [file] (default: ".dependency-cruiser-known-violations.json")${EOL}`,
112
112
  )
113
+ .addOption(new Option("--no-ignore-known").hideHelp(true))
113
114
  .addOption(
114
115
  new Option(
115
116
  "-d, --max-depth <n>",
@@ -125,11 +126,11 @@ try {
125
126
  )
126
127
  .option(
127
128
  "-P, --prefix <prefix>",
128
- `prefix to use for links in the dot and err-html reporters${EOL}${EOL}`,
129
+ `prefix to use for links in the dot and err-html reporters`,
129
130
  )
130
131
  .option(
131
132
  "--suffix <suffix>",
132
- `suffix to use for links in the dot and err-html reporters${EOL}${EOL}`,
133
+ `suffix to use for links in the dot and err-html reporters${EOL}`,
133
134
  )
134
135
  .option(
135
136
  "-C, --cache [cache-directory]",
@@ -155,6 +156,15 @@ try {
155
156
  "leave symlinks unchanged (off by default)",
156
157
  ).hideHelp(true),
157
158
  )
159
+ .addOption(
160
+ new Option(
161
+ "-p, --progress [type]",
162
+ "show progress while dependency-cruiser is busy",
163
+ ).choices(["cli-feedback", "performance-log", "ndjson", "none"]),
164
+ )
165
+ .addOption(
166
+ new Option("--no-progress", "Alias of --progress none").hideHelp(true),
167
+ )
158
168
  .addOption(
159
169
  new Option("-v, --validate [file]", `alias for --config`).hideHelp(true),
160
170
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dependency-cruiser",
3
- "version": "18.2.0",
3
+ "version": "18.3.1",
4
4
  "description": "Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.",
5
5
  "keywords": [
6
6
  "static analysis",
@@ -75,9 +75,9 @@
75
75
  },
76
76
  "homepage": "https://github.com/sverweij/dependency-cruiser",
77
77
  "bin": {
78
- "dependency-cruiser": "bin/dependency-cruise.mjs",
79
- "dependency-cruise": "bin/dependency-cruise.mjs",
80
- "depcruise": "bin/dependency-cruise.mjs",
78
+ "dependency-cruiser": "bin/dependency-cruiser.mjs",
79
+ "dependency-cruise": "bin/dependency-cruiser.mjs",
80
+ "depcruise": "bin/dependency-cruiser.mjs",
81
81
  "depcruise-baseline": "bin/depcruise-baseline.mjs",
82
82
  "depcruise-fmt": "bin/depcruise-fmt.mjs",
83
83
  "depcruise-wrap-stream-in-html": "bin/wrap-stream-in-html.mjs"
@@ -149,12 +149,12 @@
149
149
  "acorn-loose": "8.5.2",
150
150
  "acorn-walk": "8.3.5",
151
151
  "commander": "15.0.0",
152
- "enhanced-resolve": "5.24.5",
153
- "ignore": "7.0.6",
152
+ "enhanced-resolve": "5.25.1",
153
+ "ignore": "7.0.9",
154
154
  "interpret": "3.1.1",
155
155
  "is-installed-globally": "1.0.0",
156
156
  "json5": "2.2.3",
157
- "picomatch": "4.0.5",
157
+ "picomatch": "4.0.7",
158
158
  "prompts": "2.4.2",
159
159
  "rechoir": "0.8.0",
160
160
  "safe-regex": "2.1.1",
@@ -41,7 +41,11 @@ export default function analyzeModules(pModules, pOptions) {
41
41
  bus.info("analyze: validations");
42
42
  lModules = addValidations(lModules, pOptions.ruleSet, pOptions.validate);
43
43
 
44
- lModules = softenKnownViolations(lModules, pOptions.knownViolations);
44
+ lModules = softenKnownViolations(
45
+ lModules,
46
+ pOptions.knownViolations,
47
+ pOptions.ignoreKnown,
48
+ );
45
49
 
46
50
  return lModules;
47
51
  }
@@ -12,7 +12,7 @@ import { bus } from "#utl/bus.mjs";
12
12
  * caching) can use that.
13
13
  *
14
14
  * @param {import("../../types/dependency-cruiser.js").IModule[]} pModules
15
- * @param {import("../../types/dependency-cruiser.js").ICruiseOptions} pOptions
15
+ * @param {import("../../types/options.mjs").ICruiseOptions} pOptions
16
16
  * @param {string[]} pFileAndDirectoryArray
17
17
  * @returns {import("../../types/dependency-cruiser.js").ICruiseResult}
18
18
  */
@@ -111,16 +111,18 @@ function softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity) {
111
111
  *
112
112
  * @param {import("../../types/cruise-result.mjs").IModule[]} pModules
113
113
  * @param {import("../../types/baseline-violations.mjs").IBaselineViolations} pKnownViolations
114
+ * @param {boolean} pIgnoreKnown
114
115
  * @param {import("../../types/shared-types.mjs").SeverityType} pSoftenedSeverity
115
116
  * @returns {import("../../types/cruise-result.mjs").IModule[]}
116
117
  */
117
118
  export default function softenKnownViolations(
118
119
  pModules,
119
120
  pKnownViolations,
121
+ pIgnoreKnown,
120
122
  pSoftenedSeverity = "ignore",
121
123
  ) {
122
- if (pKnownViolations) {
123
- bus.info("analyze: compare to known errors");
124
+ if (pKnownViolations && !(pIgnoreKnown === false)) {
125
+ bus.info("analyze: compare to known violations");
124
126
  return pModules.map((pModule) =>
125
127
  softenKnownViolation(pModule, pKnownViolations, pSoftenedSeverity),
126
128
  );
@@ -1,6 +1,7 @@
1
1
  const SHAREABLE_OPTIONS = [
2
2
  "babelConfig",
3
3
  "baseDir",
4
+ "baseline",
4
5
  "cache",
5
6
  "collapse",
6
7
  "combinedDependencies",
@@ -15,6 +16,7 @@ const SHAREABLE_OPTIONS = [
15
16
  "externalModuleResolutionStrategy",
16
17
  "focus",
17
18
  "focusDepth",
19
+ "ignoreKnown",
18
20
  "includeOnly",
19
21
  "knownViolations",
20
22
  "maxDepth",
@@ -56,6 +56,7 @@ export function optionsAreCompatible(pOldOptions, pNewOptions) {
56
56
  pOldOptions.detectProcessBuiltinModuleCalls ===
57
57
  pNewOptions.detectProcessBuiltinModuleCalls &&
58
58
  pOldOptions.skipAnalysisNotInRules === pNewOptions.skipAnalysisNotInRules &&
59
+ pOldOptions.ignoreKnown === pNewOptions.ignoreKnown &&
59
60
  metricsIsCompatible(pOldOptions.metrics, pNewOptions.metrics) &&
60
61
  // includeOnly suffers from a backwards compatibility disease
61
62
  includeOnlyIsCompatible(pOldOptions.includeOnly, pNewOptions.includeOnly) &&
package/src/cli/index.mjs CHANGED
@@ -12,6 +12,7 @@ import setUpNDJSONListener from "./listeners/ndjson.mjs";
12
12
  import { set } from "#utl/object-util.mjs";
13
13
  import cruise from "#main/cruise.mjs";
14
14
  import { INFO, bus } from "#utl/bus.mjs";
15
+ import { EOL } from "node:os";
15
16
 
16
17
  // eslint-disable-next-line complexity
17
18
  async function extractResolveOptions(pCruiseOptions) {
@@ -96,9 +97,9 @@ function setUpListener(pCruiseOptions) {
96
97
  }
97
98
  }
98
99
 
99
- async function runCruise(pFileDirectoryArray, pCruiseOptions) {
100
+ async function runCruise(pFileDirectoryArray, pCruiseOptions, pErrorStream) {
100
101
  const lCruiseOptions = await addKnownViolations(
101
- await normalizeCliOptions(pCruiseOptions),
102
+ await normalizeCliOptions(pCruiseOptions, pErrorStream),
102
103
  );
103
104
 
104
105
  pFileDirectoryArray
@@ -128,6 +129,9 @@ async function runCruise(pFileDirectoryArray, pCruiseOptions) {
128
129
  bus.progress("cli: writing results", { complete: 1 });
129
130
  bus.emit("write-start");
130
131
  write(lCruiseOptions.outputTo, lReportingResult.output);
132
+ if (lReportingResult.meta) {
133
+ pErrorStream.write(`${lReportingResult.meta}${EOL}`);
134
+ }
131
135
 
132
136
  return lReportingResult.exitCode;
133
137
  }
@@ -179,7 +183,11 @@ export default async function executeCli(
179
183
  const { default: initConfig } = await import("./init-config/index.mjs");
180
184
  initConfig(lCruiseOptions.init, null, lStreams);
181
185
  } else {
182
- lExitCode = await runCruise(pFileDirectoryArray, lCruiseOptions);
186
+ lExitCode = await runCruise(
187
+ pFileDirectoryArray,
188
+ lCruiseOptions,
189
+ lStreams.stderr,
190
+ );
183
191
  }
184
192
  } catch (pError) {
185
193
  lStreams.stderr.write(
@@ -304,7 +304,7 @@ module.exports = {
304
304
  conditionNames: ['import', 'require', 'node', 'default', 'types'],
305
305
 
306
306
  // The extensions, by default are the same as the ones dependency-cruiser
307
- // can access (run \`npx depcruise --info\` to see which ones that are in
307
+ // can access (run \`npx dependency-cruiser --info\` to see which ones that are in
308
308
  // _your_ environment). If that list is larger than you need you can pass
309
309
  // the extensions you actually use (e.g. ['.js', '.jsx']). This can speed
310
310
  // up module resolution, which is the most expensive step.
@@ -71,13 +71,13 @@ export function compileRunScripts(pInitOptions) {
71
71
  const lTestLocations = (pInitOptions.testLocation || []).join(" ");
72
72
 
73
73
  lReturnValue = {
74
- depcruise: `depcruise ${lSourceLocations} ${lTestLocations}`,
75
- "depcruise:graph": `depcruise ${lSourceLocations} --include-only '${lSourceLocationRE}' --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > dependency-graph.html`,
76
- "depcruise:graph:dev": `depcruise ${lSourceLocations} --include-only '${lSourceLocationRE}' --prefix vscode://file/$(pwd)/ --output-type dot | dot -T svg | depcruise-wrap-stream-in-html | browser`,
77
- "depcruise:graph:archi": `depcruise ${lSourceLocations} --include-only '${lSourceLocationRE}' --output-type archi | dot -T svg | depcruise-wrap-stream-in-html > high-level-dependency-graph.html`,
78
- "depcruise:html": `depcruise ${lSourceLocations} ${lTestLocations} --progress --output-type err-html --output-to dependency-violation-report.html`,
79
- "depcruise:text": `depcruise ${lSourceLocations} ${lTestLocations} --progress --output-type text`,
80
- "depcruise:focus": `depcruise ${lSourceLocations} ${lTestLocations} --progress --output-type text --focus`,
74
+ depcruise: `dependency-cruiser ${lSourceLocations} ${lTestLocations}`,
75
+ "depcruise:graph": `dependency-cruiser ${lSourceLocations} --include-only '${lSourceLocationRE}' --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > dependency-graph.html`,
76
+ "depcruise:graph:dev": `dependency-cruiser ${lSourceLocations} --include-only '${lSourceLocationRE}' --prefix vscode://file/$(pwd)/ --output-type dot | dot -T svg | depcruise-wrap-stream-in-html | browser`,
77
+ "depcruise:graph:archi": `dependency-cruiser ${lSourceLocations} --include-only '${lSourceLocationRE}' --output-type archi | dot -T svg | depcruise-wrap-stream-in-html > high-level-dependency-graph.html`,
78
+ "depcruise:html": `dependency-cruiser ${lSourceLocations} ${lTestLocations} --progress --output-type err-html --output-to dependency-violation-report.html`,
79
+ "depcruise:text": `dependency-cruiser ${lSourceLocations} ${lTestLocations} --progress --output-type text`,
80
+ "depcruise:focus": `dependency-cruiser ${lSourceLocations} ${lTestLocations} --progress --output-type text --focus`,
81
81
  };
82
82
  }
83
83
 
@@ -1,5 +1,6 @@
1
1
  import { accessSync, constants } from "node:fs";
2
2
  import { isAbsolute } from "node:path";
3
+ import { styleText } from "node:util";
3
4
  import {
4
5
  RULES_FILE_NAME_SEARCH_ARRAY,
5
6
  DEFAULT_BASELINE_FILE_NAME,
@@ -12,11 +13,31 @@ import {
12
13
  } from "./defaults.mjs";
13
14
  import { set } from "#utl/object-util.mjs";
14
15
  import loadConfig from "#config-utl/extract-depcruise-config/index.mjs";
16
+ import extractKnownViolations from "#config-utl/extract-known-violations.mjs";
15
17
 
16
18
  function getOptionValue(pDefault) {
17
19
  return (pValue) => (typeof pValue === "string" ? pValue : pDefault);
18
20
  }
19
21
 
22
+ async function getCurrentBaseline(pBaselineFileName, pErrorStream) {
23
+ let lCurrentBaseline = [];
24
+ try {
25
+ lCurrentBaseline = await extractKnownViolations(pBaselineFileName);
26
+ } catch (pKnownViolationsExtractionError) {
27
+ if (pKnownViolationsExtractionError.code === "ENOENT") {
28
+ pErrorStream.write(
29
+ styleText(
30
+ "yellow",
31
+ `‼ Known violations file '${pBaselineFileName}' does not exist yet. Will assume an empty current violations set and create a new one instead.\n`,
32
+ ),
33
+ );
34
+ } else {
35
+ throw pKnownViolationsExtractionError;
36
+ }
37
+ }
38
+ return lCurrentBaseline;
39
+ }
40
+
20
41
  // eslint-disable-next-line complexity
21
42
  function normalizeConfigFileName(pCliOptions, pConfigWrapperName, pDefault) {
22
43
  const lOptions = structuredClone(pCliOptions);
@@ -108,6 +129,12 @@ function validateAndGetKnownViolationsFileName(pKnownViolations) {
108
129
  }
109
130
  }
110
131
 
132
+ function normalizeIgnoreKnown(pCliOptions) {
133
+ return Object.hasOwn(pCliOptions, "ignoreKnown")
134
+ ? { ignoreKnown: !!pCliOptions.ignoreKnown }
135
+ : {};
136
+ }
137
+
111
138
  function normalizeKnownViolationsOption(pCliOptions) {
112
139
  if (
113
140
  !Object.hasOwn(pCliOptions, "ignoreKnown") ||
@@ -196,11 +223,15 @@ function normalizeCache(pCliOptions) {
196
223
  * returns the pOptionsAsPassedFromCommander, so that the returned value contains a
197
224
  * valid value for each possible option
198
225
  *
199
- * @param {object} pOptionsAsPassedFromCommander [description]
226
+ * @param {object} pOptionsAsPassedFromCommander [description]
200
227
  * @param {any} pKnownCliOptions [description]
228
+ * @param {typeof process.stderr} pErrorStream
201
229
  * @returns {import("../../types/options.mjs").ICruiseOptions} [description]
202
230
  */
203
- export default async function normalizeOptions(pOptionsAsPassedFromCommander) {
231
+ export default async function normalizeOptions(
232
+ pOptionsAsPassedFromCommander,
233
+ pErrorStream,
234
+ ) {
204
235
  let lOptions = {
205
236
  outputTo: OUTPUT_TO,
206
237
  outputType: OUTPUT_TYPE,
@@ -217,11 +248,41 @@ export default async function normalizeOptions(pOptionsAsPassedFromCommander) {
217
248
  lOptions.validate = lOptions.config;
218
249
  }
219
250
 
251
+ if (Object.hasOwn(lOptions, "baseline")) {
252
+ const lBaselineFileName =
253
+ typeof pOptionsAsPassedFromCommander.baseline === "string"
254
+ ? pOptionsAsPassedFromCommander.baseline
255
+ : ".dependency-cruiser-known-violations.json";
256
+
257
+ // keeping the baseline option (a boolean or a string) as passed from commander
258
+ // in would overwrite the option as passed in the configuration file (an object,
259
+ // if any)
260
+ Reflect.deleteProperty(lOptions, "baseline");
261
+
262
+ lOptions.ignoreKnown = false;
263
+ lOptions.outputTo = lBaselineFileName;
264
+ lOptions.outputType = "baseline";
265
+ lOptions.knownViolations = await getCurrentBaseline(
266
+ lBaselineFileName,
267
+ pErrorStream,
268
+ );
269
+ lOptions.cache = false;
270
+ Reflect.deleteProperty(lOptions, "cacheStrategy");
271
+ }
272
+
273
+ if (Object.hasOwn(lOptions, "baselineMode")) {
274
+ lOptions.baseline = {
275
+ ...lOptions.baseline,
276
+ mode: lOptions.baselineMode,
277
+ };
278
+ }
279
+
220
280
  lOptions = { ...lOptions, ...(await normalizeValidationOption(lOptions)) };
221
281
  lOptions = { ...lOptions, ...normalizeProgress(lOptions) };
222
282
  lOptions = { ...lOptions, ...normalizeCache(lOptions) };
223
283
  lOptions = { ...lOptions, ...normalizeCacheStrategy(lOptions) };
224
284
  lOptions = { ...lOptions, ...normalizeKnownViolationsOption(lOptions) };
285
+ lOptions = { ...lOptions, ...normalizeIgnoreKnown(lOptions) };
225
286
  lOptions = normalizeConfigFileName(lOptions, "webpackConfig", WEBPACK_CONFIG);
226
287
  lOptions = normalizeConfigFileName(lOptions, "tsConfig", TYPESCRIPT_CONFIG);
227
288
  lOptions = normalizeConfigFileName(lOptions, "babelConfig", BABEL_CONFIG);
@@ -119,6 +119,7 @@ const TS_COMPATIBLE_EXTENSIONS = new Set([
119
119
  ".mts",
120
120
  ".cts",
121
121
  ".js",
122
+ ".jsx",
122
123
  ".mjs",
123
124
  ".cjs",
124
125
  ".vue",
@@ -4,10 +4,14 @@ import tryImport from "#utl/try-import.mjs";
4
4
  import meta from "#meta.cjs";
5
5
 
6
6
  /** @type {import('@swc/core/Visitor')} */
7
- const { Visitor } = await tryImport(
7
+ const lSwcVisitorModule = await tryImport(
8
8
  "@swc/core/Visitor.js",
9
9
  meta.supportedTranspilers.swc,
10
10
  );
11
+ // under bun lSwcVisitorModule resolves to something different
12
+ // as under node.js - see https://github.com/sverweij/dependency-cruiser/issues/1092
13
+ // for details. In order to enable running dependency-cruiser on both:
14
+ const Visitor = lSwcVisitorModule.Visitor ?? lSwcVisitorModule;
11
15
 
12
16
  function pryStringsFromArguments(pArguments) {
13
17
  let lReturnValue = null;
@@ -114,6 +118,16 @@ function isInterestingCallExpression(pExoticRequireStrings, pNode) {
114
118
  );
115
119
  }
116
120
 
121
+ function nodeIsTypeOnly(pNode) {
122
+ if (pNode.typeOnly) {
123
+ return true;
124
+ }
125
+ return (
126
+ pNode?.specifiers.length > 0 &&
127
+ pNode.specifiers.every((pSpecifier) => pSpecifier.isTypeOnly)
128
+ );
129
+ }
130
+
117
131
  export default Visitor
118
132
  ? class SwcDependencyVisitor extends Visitor {
119
133
  #exoticRequireStrings;
@@ -134,14 +148,15 @@ export default Visitor
134
148
  });
135
149
  }
136
150
  }
137
-
138
151
  #pushImportSource(pNode) {
139
152
  if (pNode.source) {
140
153
  this.#result.push({
141
154
  module: pNode.source.value,
142
155
  moduleSystem: "es6",
143
156
  exoticallyRequired: false,
144
- dependencyTypes: ["import"],
157
+ dependencyTypes: nodeIsTypeOnly(pNode)
158
+ ? ["import", "type-only"]
159
+ : ["import"],
145
160
  });
146
161
  }
147
162
  }
@@ -17,13 +17,23 @@ const SWC_PARSE_OPTIONS = {
17
17
  target: "es2022",
18
18
  // allow for decorators
19
19
  decorators: true,
20
- // TODO: {tj}sx ?
20
+ // no tsx by default - overridden when the extension calls for it (see getOptionsFor)
21
+ // tsx: false
21
22
  };
22
23
  /** @type {Map<string, ModuleItem[]>} */
23
24
  const CACHE = new Map();
24
25
 
25
- export function getASTFromSource(pSource) {
26
- return swc.parseSync(pSource, SWC_PARSE_OPTIONS);
26
+ /**
27
+ * Returns the parse options necessary to have swc parse the file name
28
+ * pFileName correctly (mainly important for jsx/ tsx)
29
+ *
30
+ * @param {string} pFileName
31
+ * @returns {ParseOptions}
32
+ */
33
+ export function getOptionsFor(pFileName) {
34
+ return /[.](?:tsx|jsx)$/.test(pFileName)
35
+ ? { ...SWC_PARSE_OPTIONS, tsx: true }
36
+ : SWC_PARSE_OPTIONS;
27
37
  }
28
38
 
29
39
  /**
@@ -39,7 +49,7 @@ export function getASTCached(pFileName) {
39
49
  return CACHE.get(pFileName);
40
50
  }
41
51
  /** @type {swcCore} */
42
- const lAST = swc.parseFileSync(pFileName, SWC_PARSE_OPTIONS);
52
+ const lAST = swc.parseFileSync(pFileName, getOptionsFor(pFileName));
43
53
  CACHE.set(pFileName, lAST);
44
54
  return lAST;
45
55
  }
@@ -14,7 +14,7 @@ function extractRootModuleName(pModuleName) {
14
14
 
15
15
  function getVersion(pModuleName) {
16
16
  // of course we'd love to use something like an import with an import assertion
17
- // As that's _experimental_ we have to use _require_ in stead.
17
+ // As that's _experimental_ we have to use _require_ instead.
18
18
  // eslint-disable-next-line import/no-dynamic-require, security/detect-non-literal-require
19
19
  const lManifest = require(
20
20
  path.join(extractRootModuleName(pModuleName), "package.json"),