depvitals 0.1.2 → 0.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.
Files changed (86) hide show
  1. package/README.md +39 -61
  2. package/dist/cli/entry.d.ts +59 -0
  3. package/dist/cli/entry.d.ts.map +1 -0
  4. package/dist/cli/entry.js +95 -0
  5. package/dist/cli/entry.js.map +1 -0
  6. package/dist/cli/index.js +14 -18
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/notifier/eligibility.d.ts +65 -0
  9. package/dist/cli/notifier/eligibility.d.ts.map +1 -0
  10. package/dist/cli/notifier/eligibility.js +123 -0
  11. package/dist/cli/notifier/eligibility.js.map +1 -0
  12. package/dist/cli/notifier/registryLookup.d.ts +111 -0
  13. package/dist/cli/notifier/registryLookup.d.ts.map +1 -0
  14. package/dist/cli/notifier/registryLookup.js +211 -0
  15. package/dist/cli/notifier/registryLookup.js.map +1 -0
  16. package/dist/cli/notifier/updateCache.d.ts +101 -0
  17. package/dist/cli/notifier/updateCache.d.ts.map +1 -0
  18. package/dist/cli/notifier/updateCache.js +352 -0
  19. package/dist/cli/notifier/updateCache.js.map +1 -0
  20. package/dist/cli/notifier/updateCheck.d.ts +88 -0
  21. package/dist/cli/notifier/updateCheck.d.ts.map +1 -0
  22. package/dist/cli/notifier/updateCheck.js +166 -0
  23. package/dist/cli/notifier/updateCheck.js.map +1 -0
  24. package/dist/cli/notifier/versionCompare.d.ts +48 -0
  25. package/dist/cli/notifier/versionCompare.d.ts.map +1 -0
  26. package/dist/cli/notifier/versionCompare.js +89 -0
  27. package/dist/cli/notifier/versionCompare.js.map +1 -0
  28. package/dist/cli/runCli.d.ts +2 -0
  29. package/dist/cli/runCli.d.ts.map +1 -1
  30. package/dist/cli/runCli.js +36 -4
  31. package/dist/cli/runCli.js.map +1 -1
  32. package/dist/config/ignoreDependencies.d.ts +7 -0
  33. package/dist/config/ignoreDependencies.d.ts.map +1 -0
  34. package/dist/config/ignoreDependencies.js +18 -0
  35. package/dist/config/ignoreDependencies.js.map +1 -0
  36. package/dist/config/ignorePaths.d.ts +13 -0
  37. package/dist/config/ignorePaths.d.ts.map +1 -0
  38. package/dist/config/ignorePaths.js +47 -0
  39. package/dist/config/ignorePaths.js.map +1 -0
  40. package/dist/config/projectConfigError.d.ts +7 -0
  41. package/dist/config/projectConfigError.d.ts.map +1 -0
  42. package/dist/config/projectConfigError.js +10 -0
  43. package/dist/config/projectConfigError.js.map +1 -0
  44. package/dist/config/readProjectConfig.d.ts +23 -0
  45. package/dist/config/readProjectConfig.d.ts.map +1 -0
  46. package/dist/config/readProjectConfig.js +106 -0
  47. package/dist/config/readProjectConfig.js.map +1 -0
  48. package/dist/core/analyzeProject.d.ts +48 -6
  49. package/dist/core/analyzeProject.d.ts.map +1 -1
  50. package/dist/core/analyzeProject.js +123 -16
  51. package/dist/core/analyzeProject.js.map +1 -1
  52. package/dist/detectors/eslintManifestConfig.d.ts +111 -0
  53. package/dist/detectors/eslintManifestConfig.d.ts.map +1 -0
  54. package/dist/detectors/eslintManifestConfig.js +351 -0
  55. package/dist/detectors/eslintManifestConfig.js.map +1 -0
  56. package/dist/detectors/eslintRcJsonConfig.d.ts +85 -0
  57. package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -0
  58. package/dist/detectors/eslintRcJsonConfig.js +189 -0
  59. package/dist/detectors/eslintRcJsonConfig.js.map +1 -0
  60. package/dist/detectors/jestManifestConfig.d.ts +76 -0
  61. package/dist/detectors/jestManifestConfig.d.ts.map +1 -0
  62. package/dist/detectors/jestManifestConfig.js +294 -0
  63. package/dist/detectors/jestManifestConfig.js.map +1 -0
  64. package/dist/reporters/terminalReporter.js +27 -1
  65. package/dist/reporters/terminalReporter.js.map +1 -1
  66. package/dist/scanner/moduleSpecifiers.d.ts +9 -1
  67. package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
  68. package/dist/scanner/moduleSpecifiers.js +3317 -205
  69. package/dist/scanner/moduleSpecifiers.js.map +1 -1
  70. package/dist/scanner/packageSpecifier.d.ts +26 -2
  71. package/dist/scanner/packageSpecifier.d.ts.map +1 -1
  72. package/dist/scanner/packageSpecifier.js +70 -2
  73. package/dist/scanner/packageSpecifier.js.map +1 -1
  74. package/dist/scanner/projectManifest.d.ts +31 -1
  75. package/dist/scanner/projectManifest.d.ts.map +1 -1
  76. package/dist/scanner/projectManifest.js +83 -4
  77. package/dist/scanner/projectManifest.js.map +1 -1
  78. package/dist/scanner/scriptUsage.d.ts +2 -1
  79. package/dist/scanner/scriptUsage.d.ts.map +1 -1
  80. package/dist/scanner/scriptUsage.js +216 -39
  81. package/dist/scanner/scriptUsage.js.map +1 -1
  82. package/dist/scanner/sourceFiles.d.ts +16 -2
  83. package/dist/scanner/sourceFiles.d.ts.map +1 -1
  84. package/dist/scanner/sourceFiles.js +12 -6
  85. package/dist/scanner/sourceFiles.js.map +1 -1
  86. package/package.json +7 -7
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Legacy `package.json#eslintConfig` usage detection.
3
+ *
4
+ * This module answers exactly one narrow question: which declared dependencies are referenced by a
5
+ * small, explicitly enumerated subset of the legacy ESLint configuration block stored inside
6
+ * `package.json`? The result is a set of configuration-only package names that is handed to the
7
+ * Step 56 analyzer seam, so a recognized name can only suppress an `unused` finding and can never
8
+ * create a `missing` one.
9
+ *
10
+ * It deliberately does not:
11
+ * - evaluate, `import()`, `require()` or otherwise run ESLint, a config loader or any target code;
12
+ * - implement the ESLint configuration schema. Only `extends` and `plugins` are read, and only
13
+ * through the fixed allowlists below;
14
+ * - resolve `overrides`, scoped plugin shorthands, `plugin:<name>/<config>` forms other than the one
15
+ * allowlisted string, shareable-config subpaths, or external `.eslintrc.*` files, which are read
16
+ * by the sibling `findEslintRcJsonUsage` reader instead of duplicating these tables;
17
+ * - guess a package from arbitrary configuration text, substring-match a value, or invent a name;
18
+ * - report anything about a package the manifest does not declare;
19
+ * - mutate the analyzed project.
20
+ *
21
+ * The manifest model intentionally does not carry `eslintConfig`, so `package.json` is read a second
22
+ * time here as plain text: the loader is reused as the validity gate, and one extra small read beats
23
+ * widening the manifest model for one detector. Every unusable shape is ignored rather than
24
+ * diagnosed: manifest validity stays with the manifest loader and the analyzer, which report a
25
+ * genuinely invalid `package.json` as they always have.
26
+ *
27
+ * A conservative precedence guard applies before anything is read: when a hard barrier (a modern
28
+ * flat-config entrypoint, or a legacy `.eslintrc.js`/`.cjs`/`.yaml`/`.yml`) exists at the project
29
+ * root, the legacy block is not provably the active configuration, so no legacy hints are produced
30
+ * from either source. Presence is checked by directory entry name only; no entry is read,
31
+ * interpreted or executed. The precedence is computed once by {@link readLegacyEslintPrecedence},
32
+ * shared by both legacy readers, so the two sources obey exactly one set of rules.
33
+ */
34
+ import { readdir } from 'node:fs/promises';
35
+ import { resolve } from 'node:path';
36
+ import { loadProjectManifest, ProjectManifestError, readManifestText, } from '../scanner/projectManifest.js';
37
+ import { assertReadableRoot, SourceFilesError } from '../scanner/sourceFiles.js';
38
+ /**
39
+ * The legacy `extends` values recognized by this release and the package each one refers to.
40
+ *
41
+ * The table is a fixed allowlist, not a resolution algorithm. It contains the three documented
42
+ * shorthand aliases (`prettier`, `airbnb` and the single `plugin:react/recommended` form) plus the
43
+ * already complete package name of each shareable config they point at, because a complete
44
+ * `eslint-config-*` name is a valid `extends` value in its own right.
45
+ *
46
+ * There is deliberately no plugin package name in this table. Legacy ESLint resolves `extends` as a
47
+ * shareable config name, so `extends: "eslint-plugin-react"` refers to a package named
48
+ * `eslint-config-eslint-plugin-react`, not to `eslint-plugin-react`; the only valid `extends` form
49
+ * for a plugin is `plugin:<name>/<config>`. A plugin is therefore credited through
50
+ * {@link PLUGIN_PACKAGE_NAMES} (or through an allowlisted `plugin:` form), never through a bare
51
+ * plugin name in `extends`, which would suppress a real `unused` finding based on a value ESLint
52
+ * cannot load.
53
+ *
54
+ * Anything else is unsupported on purpose: `eslint:recommended`, `eslint:all`,
55
+ * `plugin:other/recommended`, `prettier/react`, a scoped config, an unknown alias or any other
56
+ * string contributes no evidence, because a guessed alias would create false confidence.
57
+ */
58
+ const EXTENDS_PACKAGE_NAMES = new Map([
59
+ ['prettier', 'eslint-config-prettier'],
60
+ ['airbnb', 'eslint-config-airbnb'],
61
+ ['plugin:react/recommended', 'eslint-plugin-react'],
62
+ ['eslint-config-prettier', 'eslint-config-prettier'],
63
+ ['eslint-config-airbnb', 'eslint-config-airbnb'],
64
+ ]);
65
+ /**
66
+ * The legacy `plugins` entries recognized by this release and the package each one refers to.
67
+ *
68
+ * Same rule as {@link EXTENDS_PACKAGE_NAMES}: only the documented `react` shorthand and the already
69
+ * complete package name are supported. Invented plugin names, scoped shorthands such as
70
+ * `@acme/eslint-plugin` and any other plugin string produce no evidence.
71
+ *
72
+ * `plugins` is where the complete `eslint-plugin-react` name is valid, unlike in `extends` (see
73
+ * {@link EXTENDS_PACKAGE_NAMES}).
74
+ */
75
+ const PLUGIN_PACKAGE_NAMES = new Map([
76
+ ['react', 'eslint-plugin-react'],
77
+ ['eslint-plugin-react', 'eslint-plugin-react'],
78
+ ]);
79
+ /**
80
+ * Modern flat-config entrypoint names that form a hard barrier: their presence disables every legacy
81
+ * ESLint hint, from both `package.json#eslintConfig` and `.eslintrc.json`.
82
+ *
83
+ * Compared case-insensitively (see {@link readLegacyEslintPrecedence}) because case-insensitive
84
+ * filesystems treat `ESLint.Config.js` as the same file, so DepVitals must not disagree with itself
85
+ * about one project on different platforms. Added in Step 68.1: `mts`/`cts` join the original four.
86
+ */
87
+ const FLAT_CONFIG_ENTRY_NAMES = new Set([
88
+ 'eslint.config.js',
89
+ 'eslint.config.mjs',
90
+ 'eslint.config.cjs',
91
+ 'eslint.config.ts',
92
+ 'eslint.config.mts',
93
+ 'eslint.config.cts',
94
+ ]);
95
+ /**
96
+ * Legacy `.eslintrc.*` names that form a hard barrier for every legacy hint, same rule as the flat
97
+ * configs: `.eslintrc.js`, `.eslintrc.cjs`, `.eslintrc.yaml` and `.eslintrc.yml`.
98
+ *
99
+ * Legacy ESLint ignores `package.json#eslintConfig` once any `.eslintrc.*` file exists by name, so
100
+ * these names disable the manifest block too. Compared case-insensitively, like the flat configs.
101
+ */
102
+ const LEGACY_BARRIER_ENTRY_NAMES = new Set([
103
+ '.eslintrc.js',
104
+ '.eslintrc.cjs',
105
+ '.eslintrc.yaml',
106
+ '.eslintrc.yml',
107
+ ]);
108
+ /**
109
+ * Legacy `.eslintrc` names whose presence displaces the manifest block but does not by itself disable
110
+ * the `.eslintrc.json` reader: the extensionless `.eslintrc` and `.eslintrc.json` itself.
111
+ *
112
+ * Legacy ESLint ignores the manifest block once any `.eslintrc.*` file exists by name, so the
113
+ * extensionless `.eslintrc` displaces only the manifest block. `.eslintrc.json` is the file the JSON
114
+ * reader actually reads, so it is listed here as a pure name check for the manifest block. Compared
115
+ * case-insensitively, like the other legacy tables.
116
+ */
117
+ const LEGACY_RC_BY_NAME_ENTRY_NAMES = new Set(['.eslintrc.json', '.eslintrc']);
118
+ /**
119
+ * Compute the legacy ESLint precedence state for the project rooted at `projectRoot`.
120
+ *
121
+ * The two legacy readers ({@link findEslintManifestUsage} and {@link findEslintRcJsonUsage}) share
122
+ * this so a hard barrier disables both, `.eslintrc.json` wins over the manifest block, and an
123
+ * extensionless `.eslintrc` displaces only the manifest block. Neither reader unions the displaced
124
+ * source, so two contradictory legacy configurations are never combined.
125
+ *
126
+ * The legacy `.eslintrc.json` name is resolved case-insensitively, so the matched directory entry
127
+ * name is returned as `legacyRcJsonEntryName` for the JSON reader to reuse.
128
+ *
129
+ * @param projectRoot - Project directory to inspect; resolved to an absolute path.
130
+ */
131
+ export async function readLegacyEslintPrecedence(projectRoot) {
132
+ const root = resolve(projectRoot);
133
+ let entries;
134
+ try {
135
+ await assertReadableRoot(root);
136
+ entries = await readdir(root);
137
+ }
138
+ catch {
139
+ return {
140
+ hardBarrier: false,
141
+ legacyRcByName: false,
142
+ legacyRcJsonEntryName: undefined,
143
+ };
144
+ }
145
+ const has = (names) => entries.some((entry) => names.has(entry.toLowerCase()));
146
+ // The legacy `.eslintrc.json` name is matched case-insensitively (Windows/macOS resolve the
147
+ // same file under a different casing), so remember the actual entry that was found. The JSON
148
+ // reader reuses this exact entry name instead of assuming the canonical lowercase spelling.
149
+ // Prefer the canonical lowercase entry when both it and an uppercase copy exist in a
150
+ // case-sensitive root, so directory order cannot make the reader open the wrong file.
151
+ const legacyRcJsonEntryName = entries.includes('.eslintrc.json')
152
+ ? '.eslintrc.json'
153
+ : entries.find((entry) => entry.toLowerCase() === '.eslintrc.json');
154
+ return {
155
+ hardBarrier: has(FLAT_CONFIG_ENTRY_NAMES) || has(LEGACY_BARRIER_ENTRY_NAMES),
156
+ legacyRcByName: has(LEGACY_RC_BY_NAME_ENTRY_NAMES),
157
+ legacyRcJsonEntryName,
158
+ };
159
+ }
160
+ /**
161
+ * Match the allowlisted subset of one legacy `eslintConfig` value against the declared packages.
162
+ *
163
+ * Pure and side-effect free: it takes the raw `eslintConfig` JSON value (any shape, including
164
+ * something that is not an object) and the declared dependency names, and returns the declared
165
+ * packages that have configuration-only evidence, in alphabetical order. Neither argument is
166
+ * mutated, and a candidate that is not declared is dropped, so this function can never describe an
167
+ * undeclared package as used.
168
+ *
169
+ * @param eslintConfig - The raw `package.json#eslintConfig` value, validated here and never trusted.
170
+ * @param declaredPackageNames - Declared dependency names, the only names this may return.
171
+ */
172
+ export function findEslintConfigUsage(eslintConfig, declaredPackageNames) {
173
+ if (!isPlainRecord(eslintConfig)) {
174
+ return new Set();
175
+ }
176
+ const candidates = [
177
+ ...matchTable(extendsValues(eslintConfig['extends']), EXTENDS_PACKAGE_NAMES),
178
+ ...matchTable(pluginsValues(eslintConfig['plugins']), PLUGIN_PACKAGE_NAMES),
179
+ ];
180
+ const used = new Set();
181
+ for (const candidate of candidates) {
182
+ if (declaredPackageNames.has(candidate)) {
183
+ used.add(candidate);
184
+ }
185
+ }
186
+ // Rebuilt from sorted names so iteration is deterministic for every input.
187
+ return new Set([...used].sort(compareNames));
188
+ }
189
+ /**
190
+ * Collect the legacy `package.json#eslintConfig` hints for the project rooted at `projectRoot`.
191
+ *
192
+ * The returned set is shaped for the analyzer's `configUsedNames` option (the Step 56 handoff),
193
+ * which is the only way configuration evidence reaches the analyzer. Nothing is decided here: the
194
+ * analyzer stays the sole owner of the unused/missing categories.
195
+ *
196
+ * No configuration problem is reported as an error. A missing manifest, an unreadable or invalid
197
+ * `package.json`, an unusable `eslintConfig` shape or an unreadable root all yield the empty set;
198
+ * the analyzer loads the manifest itself and remains the component that reports manifest failures,
199
+ * so this detector never replaces or duplicates that diagnosis.
200
+ *
201
+ * Legacy precedence (Step 68.1) is delegated to {@link readLegacyEslintPrecedence}, which both
202
+ * legacy readers share: a hard barrier disables this block outright, and any legacy `.eslintrc.*`
203
+ * by name displaces it in favour of the lower-priority `.eslintrc.json` reader. This reader never
204
+ * unions the displaced source, so the two legacy configurations are never combined.
205
+ *
206
+ * @param projectRoot - Project directory to inspect; resolved to an absolute path.
207
+ */
208
+ export async function findEslintManifestUsage(projectRoot) {
209
+ const root = resolve(projectRoot);
210
+ const precedence = await readLegacyEslintPrecedence(root);
211
+ if (precedence.hardBarrier || precedence.legacyRcByName) {
212
+ return new Set();
213
+ }
214
+ let packageJsonPath;
215
+ let declaredPackageNames;
216
+ try {
217
+ const manifest = await loadProjectManifest(root);
218
+ packageJsonPath = manifest.packageJsonPath;
219
+ declaredPackageNames = manifest.declaredDependencyNames;
220
+ }
221
+ catch (error) {
222
+ if (error instanceof ProjectManifestError || error instanceof SourceFilesError) {
223
+ return new Set();
224
+ }
225
+ throw error;
226
+ }
227
+ return findEslintConfigUsage(await readEslintConfigValue(packageJsonPath), declaredPackageNames);
228
+ }
229
+ /**
230
+ * Whether the on-disk directory entries contain a modern flat-config entrypoint (see
231
+ * {@link FLAT_CONFIG_ENTRY_NAMES}).
232
+ *
233
+ * A legacy `.eslintrc.*` file is not a flat-config entrypoint, so this predicate checks only the
234
+ * flat-config names.
235
+ */
236
+ function hasFlatConfigEntryEntries(entries) {
237
+ return entries.some((entry) => FLAT_CONFIG_ENTRY_NAMES.has(entry.toLowerCase()));
238
+ }
239
+ /**
240
+ * Whether the project rooted at `projectRoot` contains a modern flat-config entrypoint (see
241
+ * {@link FLAT_CONFIG_ENTRY_NAMES}).
242
+ *
243
+ * A flat config is a hard barrier: its presence disables every legacy hint, including the
244
+ * `package.json#eslintConfig` block, so it is checked before any legacy reader contributes a hint.
245
+ * This is a thin convenience for callers that only need the flat-config question. Only flat-config
246
+ * entrypoints count: a legacy `.eslintrc.*` file reports `false`, even though it still participates
247
+ * in the hard-barrier check inside the legacy readers.
248
+ *
249
+ * @param projectRoot - Project directory to inspect; resolved to an absolute path.
250
+ */
251
+ export async function hasFlatConfigEntry(projectRoot) {
252
+ let entries;
253
+ try {
254
+ await assertReadableRoot(resolve(projectRoot));
255
+ entries = await readdir(resolve(projectRoot));
256
+ }
257
+ catch {
258
+ return false;
259
+ }
260
+ return hasFlatConfigEntryEntries(entries);
261
+ }
262
+ /**
263
+ * Read the raw `package.json#eslintConfig` value, ignoring everything that cannot be used.
264
+ *
265
+ * The manifest is read as text and parsed with `JSON.parse`, so no target-project code runs. An
266
+ * unreadable file, invalid JSON, a non-object document or an absent `eslintConfig` all yield
267
+ * `undefined`; the loader-facing diagnosis of a broken manifest stays in {@link loadProjectManifest}.
268
+ */
269
+ async function readEslintConfigValue(packageJsonPath) {
270
+ let text;
271
+ try {
272
+ text = await readManifestText(packageJsonPath);
273
+ }
274
+ catch {
275
+ return undefined;
276
+ }
277
+ let parsed;
278
+ try {
279
+ parsed = JSON.parse(text);
280
+ }
281
+ catch {
282
+ return undefined;
283
+ }
284
+ return isPlainRecord(parsed) ? parsed['eslintConfig'] : undefined;
285
+ }
286
+ /**
287
+ * The string values of `extends`, which ESLint accepts as one string or as an array.
288
+ *
289
+ * Non-string array entries contribute nothing on their own, so a malformed array still yields no
290
+ * evidence for the malformed entries. Any other shape (number, object, `null`, ...) contributes
291
+ * nothing at all.
292
+ */
293
+ function extendsValues(value) {
294
+ if (typeof value === 'string') {
295
+ return [value];
296
+ }
297
+ return stringItems(value);
298
+ }
299
+ /**
300
+ * The string values of `plugins`, which ESLint accepts only as an array.
301
+ *
302
+ * A bare string such as `"react"` is not a valid `plugins` value and is deliberately not accepted,
303
+ * so a typo-shaped value never becomes evidence.
304
+ */
305
+ function pluginsValues(value) {
306
+ return stringItems(value);
307
+ }
308
+ /**
309
+ * The string entries of an array value; a non-array value contributes nothing.
310
+ */
311
+ function stringItems(value) {
312
+ if (!Array.isArray(value)) {
313
+ return [];
314
+ }
315
+ return value.filter((entry) => typeof entry === 'string');
316
+ }
317
+ /**
318
+ * Look every value up in one fixed allowlist table.
319
+ *
320
+ * Lookup is exact-string only: no prefix, suffix, substring or case-insensitive matching, so
321
+ * `prettier/react`, `eslint-config-unknown` and `React` never match an entry.
322
+ */
323
+ function matchTable(values, table) {
324
+ const matched = [];
325
+ for (const value of values) {
326
+ const mapped = table.get(value);
327
+ if (mapped !== undefined) {
328
+ matched.push(mapped);
329
+ }
330
+ }
331
+ return matched;
332
+ }
333
+ /**
334
+ * A JSON object (not an array, not `null`) whose keys are strings.
335
+ */
336
+ function isPlainRecord(value) {
337
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
338
+ }
339
+ /**
340
+ * Locale-independent lexicographic ordering, so the same configuration always yields the same order.
341
+ */
342
+ function compareNames(a, b) {
343
+ if (a < b) {
344
+ return -1;
345
+ }
346
+ if (a > b) {
347
+ return 1;
348
+ }
349
+ return 0;
350
+ }
351
+ //# sourceMappingURL=eslintManifestConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslintManifestConfig.js","sourceRoot":"","sources":["../../src/detectors/eslintManifestConfig.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Legacy external `.eslintrc.json` usage detection.
3
+ *
4
+ * This module adds exactly one static data source to the ESLint rules proven in Step 57: the single
5
+ * file `<projectRoot>/.eslintrc.json`. Its parsed JSON document is handed to the same pure matcher
6
+ * ({@link findEslintConfigUsage}) that reads `package.json#eslintConfig`, so the fixed allowlists,
7
+ * the exact-string lookup and the "declared packages only" rule are reused instead of duplicated.
8
+ * The result is a set of configuration-only package names for the Step 56 analyzer seam, so a
9
+ * recognized name can only suppress an `unused` finding and can never create a `missing` one.
10
+ *
11
+ * It deliberately does not:
12
+ * - evaluate, `import()`, `require()` or otherwise run ESLint, a config loader or any target code.
13
+ * The file is read as UTF-8 text and parsed with `JSON.parse`;
14
+ * - support any other legacy format: `.eslintrc.js` / `.cjs` / `.mjs` (JavaScript config modules),
15
+ * `.eslintrc.yml` / `.yaml` and extensionless `.eslintrc` are explicitly dismissed in this
16
+ * release, as is JSON5 or comment-tolerant JSON. A `.eslintrc.json` that uses JavaScript-style
17
+ * comments is therefore reported as the operational error described below rather than parsed by a
18
+ * second JSON dialect: DepVitals reads one strict JSON document, and a hand-written comment
19
+ * stripper that mis-handled a `//` inside a string value could alter the document and invent
20
+ * evidence. A leading UTF-8 BOM is different - it is an encoding artifact of the very same
21
+ * document, so it is removed before parsing;
22
+ * - traverse parent directories, nested packages or workspaces looking for further config files, or
23
+ * resolve `extends` references to other configuration files;
24
+ * - read anything besides the `extends`/`plugins` subset that {@link findEslintConfigUsage} accepts;
25
+ * every other ESLint field (`env`, `globals`, `rules`, `parserOptions`, `overrides`, ...) is
26
+ * ignored, and no unknown value produces evidence;
27
+ * - report anything about a package the manifest does not declare, or mutate the analyzed project.
28
+ *
29
+ * Two conservative file rules keep the reader honest:
30
+ *
31
+ * - only a regular file at exactly `<projectRoot>/.eslintrc.json` is read. The path is built from a
32
+ * fixed file name joined to the resolved root, so it cannot point outside the project, and
33
+ * `lstat` (not `stat`) is used so a symlinked `.eslintrc.json` is skipped rather than followed.
34
+ * Following a link could read content that lives outside the analyzed tree, which the scan policy
35
+ * forbids; skipping is the safe answer and is a documented limitation;
36
+ * - a file that exists and is a regular file but cannot be read or parsed is reported as an
37
+ * operational {@link EslintRcJsonError}. Silently ignoring it would drop the only evidence for a
38
+ * package that the broken configuration may well reference, and would let the scan present a
39
+ * misleadingly clean or wrongly `unused` result. Valid JSON with an unusable top-level shape
40
+ * (array, string, number, `null`) is different: it is not an ESLint configuration at all, so it
41
+ * contributes no evidence and is never an error, exactly like the unknown-field case.
42
+ *
43
+ * Precedence follows the legacy ESLint rule that one active configuration wins: a hard barrier (a
44
+ * modern flat config or `.eslintrc.js`/`.cjs`/`.yaml`/`.yml`) disables every legacy hint, and
45
+ * otherwise this file wins over `package.json#eslintConfig`. The precedence is computed once by a
46
+ * helper both legacy readers share; the barrier is presence-based and never reads the displaced
47
+ * source for evidence, so two contradictory legacy sources are never unioned.
48
+ */
49
+ /**
50
+ * Why a present `.eslintrc.json` could not be used as a configuration data source.
51
+ *
52
+ * - `unreadable`: the entry is a regular file but could not be read (permissions, I/O failure, ...).
53
+ * - `invalid-json`: the file content is not valid JSON, so the configuration it intends to declare
54
+ * is unknowable rather than empty.
55
+ *
56
+ * A missing file, a non-regular entry (directory, FIFO, socket, symlink) and valid JSON with an
57
+ * unusable top-level shape are not errors: they simply produce no evidence.
58
+ */
59
+ export type EslintRcJsonErrorCode = 'unreadable' | 'invalid-json';
60
+ /**
61
+ * Operational error for an unusable `.eslintrc.json`.
62
+ *
63
+ * The `code` lets callers render a clean message without matching on error strings, following the
64
+ * existing `ProjectManifestError` / `SourceFilesError` convention. Throwing is deliberate: an
65
+ * unreadable or unparsable configuration that DepVitals silently skipped could otherwise turn into
66
+ * an unfounded `unused` finding.
67
+ */
68
+ export declare class EslintRcJsonError extends Error {
69
+ readonly code: EslintRcJsonErrorCode;
70
+ constructor(message: string, code: EslintRcJsonErrorCode, cause?: unknown);
71
+ }
72
+ /**
73
+ * Collect the external `.eslintrc.json` hints for the project rooted at `projectRoot`.
74
+ *
75
+ * The returned set is shaped for the analyzer's `configUsedNames` option (the Step 56 handoff),
76
+ * which is the only way configuration evidence reaches the analyzer. Nothing is decided here: the
77
+ * analyzer stays the sole owner of the unused/missing categories, and an undeclared package can
78
+ * never appear in the result.
79
+ *
80
+ * @param projectRoot - Project directory to inspect; resolved to an absolute path.
81
+ * @throws {EslintRcJsonError} when a regular `.eslintrc.json` file exists but cannot be read or
82
+ * parsed as JSON. Absent, skipped and unusable-but-parsable files return the empty set.
83
+ */
84
+ export declare function findEslintRcJsonUsage(projectRoot: string): Promise<ReadonlySet<string>>;
85
+ //# sourceMappingURL=eslintRcJsonConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslintRcJsonConfig.d.ts","sourceRoot":"","sources":["../../src/detectors/eslintRcJsonConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAiBH;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,cAAc,CAAC;AAElE;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,KAAK,CAAC,EAAE,OAAO;CAK1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAsD7F"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Legacy external `.eslintrc.json` usage detection.
3
+ *
4
+ * This module adds exactly one static data source to the ESLint rules proven in Step 57: the single
5
+ * file `<projectRoot>/.eslintrc.json`. Its parsed JSON document is handed to the same pure matcher
6
+ * ({@link findEslintConfigUsage}) that reads `package.json#eslintConfig`, so the fixed allowlists,
7
+ * the exact-string lookup and the "declared packages only" rule are reused instead of duplicated.
8
+ * The result is a set of configuration-only package names for the Step 56 analyzer seam, so a
9
+ * recognized name can only suppress an `unused` finding and can never create a `missing` one.
10
+ *
11
+ * It deliberately does not:
12
+ * - evaluate, `import()`, `require()` or otherwise run ESLint, a config loader or any target code.
13
+ * The file is read as UTF-8 text and parsed with `JSON.parse`;
14
+ * - support any other legacy format: `.eslintrc.js` / `.cjs` / `.mjs` (JavaScript config modules),
15
+ * `.eslintrc.yml` / `.yaml` and extensionless `.eslintrc` are explicitly dismissed in this
16
+ * release, as is JSON5 or comment-tolerant JSON. A `.eslintrc.json` that uses JavaScript-style
17
+ * comments is therefore reported as the operational error described below rather than parsed by a
18
+ * second JSON dialect: DepVitals reads one strict JSON document, and a hand-written comment
19
+ * stripper that mis-handled a `//` inside a string value could alter the document and invent
20
+ * evidence. A leading UTF-8 BOM is different - it is an encoding artifact of the very same
21
+ * document, so it is removed before parsing;
22
+ * - traverse parent directories, nested packages or workspaces looking for further config files, or
23
+ * resolve `extends` references to other configuration files;
24
+ * - read anything besides the `extends`/`plugins` subset that {@link findEslintConfigUsage} accepts;
25
+ * every other ESLint field (`env`, `globals`, `rules`, `parserOptions`, `overrides`, ...) is
26
+ * ignored, and no unknown value produces evidence;
27
+ * - report anything about a package the manifest does not declare, or mutate the analyzed project.
28
+ *
29
+ * Two conservative file rules keep the reader honest:
30
+ *
31
+ * - only a regular file at exactly `<projectRoot>/.eslintrc.json` is read. The path is built from a
32
+ * fixed file name joined to the resolved root, so it cannot point outside the project, and
33
+ * `lstat` (not `stat`) is used so a symlinked `.eslintrc.json` is skipped rather than followed.
34
+ * Following a link could read content that lives outside the analyzed tree, which the scan policy
35
+ * forbids; skipping is the safe answer and is a documented limitation;
36
+ * - a file that exists and is a regular file but cannot be read or parsed is reported as an
37
+ * operational {@link EslintRcJsonError}. Silently ignoring it would drop the only evidence for a
38
+ * package that the broken configuration may well reference, and would let the scan present a
39
+ * misleadingly clean or wrongly `unused` result. Valid JSON with an unusable top-level shape
40
+ * (array, string, number, `null`) is different: it is not an ESLint configuration at all, so it
41
+ * contributes no evidence and is never an error, exactly like the unknown-field case.
42
+ *
43
+ * Precedence follows the legacy ESLint rule that one active configuration wins: a hard barrier (a
44
+ * modern flat config or `.eslintrc.js`/`.cjs`/`.yaml`/`.yml`) disables every legacy hint, and
45
+ * otherwise this file wins over `package.json#eslintConfig`. The precedence is computed once by a
46
+ * helper both legacy readers share; the barrier is presence-based and never reads the displaced
47
+ * source for evidence, so two contradictory legacy sources are never unioned.
48
+ */
49
+ import { lstat, readFile } from 'node:fs/promises';
50
+ import { join, resolve } from 'node:path';
51
+ import { loadProjectManifest, ProjectManifestError } from '../scanner/projectManifest.js';
52
+ import { SourceFilesError } from '../scanner/sourceFiles.js';
53
+ import { findEslintConfigUsage, readLegacyEslintPrecedence } from './eslintManifestConfig.js';
54
+ /**
55
+ * The one file name this reader looks for, relative to the resolved project root.
56
+ *
57
+ * There is intentionally no list of alternative legacy names: `.eslintrc.yml`, `.eslintrc.js` and
58
+ * the extensionless `.eslintrc` are out of scope for this release, so no reader is added for them.
59
+ */
60
+ const ESLINTRC_FILENAME = '.eslintrc.json';
61
+ /**
62
+ * Operational error for an unusable `.eslintrc.json`.
63
+ *
64
+ * The `code` lets callers render a clean message without matching on error strings, following the
65
+ * existing `ProjectManifestError` / `SourceFilesError` convention. Throwing is deliberate: an
66
+ * unreadable or unparsable configuration that DepVitals silently skipped could otherwise turn into
67
+ * an unfounded `unused` finding.
68
+ */
69
+ export class EslintRcJsonError extends Error {
70
+ code;
71
+ constructor(message, code, cause) {
72
+ super(message, cause === undefined ? undefined : { cause });
73
+ this.name = 'EslintRcJsonError';
74
+ this.code = code;
75
+ }
76
+ }
77
+ /**
78
+ * Collect the external `.eslintrc.json` hints for the project rooted at `projectRoot`.
79
+ *
80
+ * The returned set is shaped for the analyzer's `configUsedNames` option (the Step 56 handoff),
81
+ * which is the only way configuration evidence reaches the analyzer. Nothing is decided here: the
82
+ * analyzer stays the sole owner of the unused/missing categories, and an undeclared package can
83
+ * never appear in the result.
84
+ *
85
+ * @param projectRoot - Project directory to inspect; resolved to an absolute path.
86
+ * @throws {EslintRcJsonError} when a regular `.eslintrc.json` file exists but cannot be read or
87
+ * parsed as JSON. Absent, skipped and unusable-but-parsable files return the empty set.
88
+ */
89
+ export async function findEslintRcJsonUsage(projectRoot) {
90
+ const root = resolve(projectRoot);
91
+ // A hard barrier disables every legacy hint, external file included. Precedence is computed once
92
+ // by a helper both legacy readers share, so this file always wins over `package.json#eslintConfig`
93
+ // and the two legacy sources are never combined into one hint set.
94
+ const precedence = await readLegacyEslintPrecedence(root);
95
+ if (precedence.hardBarrier) {
96
+ return new Set();
97
+ }
98
+ let declaredPackageNames;
99
+ try {
100
+ const manifest = await loadProjectManifest(root);
101
+ declaredPackageNames = manifest.declaredDependencyNames;
102
+ }
103
+ catch (error) {
104
+ if (error instanceof ProjectManifestError || error instanceof SourceFilesError) {
105
+ // Manifest failures stay with the manifest loader and the analyzer, which report them as they
106
+ // always have; this detector never duplicates that diagnosis.
107
+ return new Set();
108
+ }
109
+ throw error;
110
+ }
111
+ // Reuse the on-disk entry name when the legacy `.eslintrc.json` was found under a different
112
+ // casing, so the reader opens the file that actually exists instead of assuming the canonical
113
+ // lowercase spelling.
114
+ const eslintRcFilename = precedence.legacyRcJsonEntryName ?? ESLINTRC_FILENAME;
115
+ const eslintRcPath = join(root, eslintRcFilename);
116
+ const text = await readEslintRcText(eslintRcPath);
117
+ if (text === undefined) {
118
+ return new Set();
119
+ }
120
+ let parsed;
121
+ try {
122
+ parsed = JSON.parse(withoutByteOrderMark(text));
123
+ }
124
+ catch (error) {
125
+ throw new EslintRcJsonError(`${eslintRcFilename} at ${eslintRcPath} is not valid JSON. DepVitals reads strict JSON ` +
126
+ `only, so JavaScript-style comments inside ${eslintRcFilename} are not supported.`, 'invalid-json', error);
127
+ }
128
+ // The whole document is the legacy config object, so the proven matcher is reused verbatim. A
129
+ // top-level value that is not an object simply matches nothing, and nothing undeclared is added.
130
+ return findEslintConfigUsage(parsed, declaredPackageNames);
131
+ }
132
+ /**
133
+ * Read `.eslintrc.json` as text, returning `undefined` when there is no usable file.
134
+ *
135
+ * `undefined` means "no external configuration to consult": the file is absent, or it is not a
136
+ * regular file. Anything else that prevents reading a real configuration file is an explicit error,
137
+ * because guessing that a broken file declares nothing is how a false `unused` finding starts.
138
+ */
139
+ async function readEslintRcText(eslintRcPath) {
140
+ let stats;
141
+ try {
142
+ // `lstat`, not `stat`: a symlinked `.eslintrc.json` is not followed, so content outside the
143
+ // analyzed project tree is never read. The path is `<root>/.eslintrc.json` built from a fixed
144
+ // file name, so it cannot address anything outside the resolved root by construction.
145
+ stats = await lstat(eslintRcPath);
146
+ }
147
+ catch (error) {
148
+ if (isNotFoundError(error)) {
149
+ return undefined;
150
+ }
151
+ throw toUnreadableError(eslintRcPath, error);
152
+ }
153
+ // Only a regular file is read: a directory, FIFO, socket or symlink contributes no evidence.
154
+ if (!stats.isFile()) {
155
+ return undefined;
156
+ }
157
+ try {
158
+ return await readFile(eslintRcPath, 'utf8');
159
+ }
160
+ catch (error) {
161
+ if (isNotFoundError(error)) {
162
+ // The file disappeared between the two calls: that is an absent configuration, not a broken one.
163
+ return undefined;
164
+ }
165
+ throw toUnreadableError(eslintRcPath, error);
166
+ }
167
+ }
168
+ /**
169
+ * Remove a leading UTF-8 BOM, which `readFile(..., 'utf8')` keeps verbatim.
170
+ *
171
+ * A BOM is an encoding artifact of the same JSON document rather than a different configuration
172
+ * format, so it must not turn a readable configuration into an `invalid-json` failure.
173
+ */
174
+ function withoutByteOrderMark(text) {
175
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
176
+ }
177
+ /**
178
+ * Wrap a filesystem failure on a real `.eslintrc.json` file as an operational error.
179
+ */
180
+ function toUnreadableError(eslintRcPath, error) {
181
+ return new EslintRcJsonError(`Could not read ${ESLINTRC_FILENAME} at ${eslintRcPath}`, 'unreadable', error);
182
+ }
183
+ /**
184
+ * Whether an unknown thrown value is the filesystem `ENOENT` error.
185
+ */
186
+ function isNotFoundError(error) {
187
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
188
+ }
189
+ //# sourceMappingURL=eslintRcJsonConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslintRcJsonConfig.js","sourceRoot":"","sources":["../../src/detectors/eslintRcJsonConfig.ts"],"names":[],"mappings":"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"}