depvitals 0.1.1 → 0.2.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.
- package/README.md +37 -54
- package/dist/cli/entry.d.ts +59 -0
- package/dist/cli/entry.d.ts.map +1 -0
- package/dist/cli/entry.js +95 -0
- package/dist/cli/entry.js.map +1 -0
- package/dist/cli/index.js +14 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/notifier/eligibility.d.ts +65 -0
- package/dist/cli/notifier/eligibility.d.ts.map +1 -0
- package/dist/cli/notifier/eligibility.js +123 -0
- package/dist/cli/notifier/eligibility.js.map +1 -0
- package/dist/cli/notifier/registryLookup.d.ts +111 -0
- package/dist/cli/notifier/registryLookup.d.ts.map +1 -0
- package/dist/cli/notifier/registryLookup.js +211 -0
- package/dist/cli/notifier/registryLookup.js.map +1 -0
- package/dist/cli/notifier/updateCache.d.ts +101 -0
- package/dist/cli/notifier/updateCache.d.ts.map +1 -0
- package/dist/cli/notifier/updateCache.js +352 -0
- package/dist/cli/notifier/updateCache.js.map +1 -0
- package/dist/cli/notifier/updateCheck.d.ts +88 -0
- package/dist/cli/notifier/updateCheck.d.ts.map +1 -0
- package/dist/cli/notifier/updateCheck.js +166 -0
- package/dist/cli/notifier/updateCheck.js.map +1 -0
- package/dist/cli/notifier/versionCompare.d.ts +48 -0
- package/dist/cli/notifier/versionCompare.d.ts.map +1 -0
- package/dist/cli/notifier/versionCompare.js +89 -0
- package/dist/cli/notifier/versionCompare.js.map +1 -0
- package/dist/cli/runCli.d.ts +2 -0
- package/dist/cli/runCli.d.ts.map +1 -1
- package/dist/cli/runCli.js +30 -3
- package/dist/cli/runCli.js.map +1 -1
- package/dist/core/analyzeProject.d.ts +44 -6
- package/dist/core/analyzeProject.d.ts.map +1 -1
- package/dist/core/analyzeProject.js +110 -11
- package/dist/core/analyzeProject.js.map +1 -1
- package/dist/detectors/eslintManifestConfig.d.ts +111 -0
- package/dist/detectors/eslintManifestConfig.d.ts.map +1 -0
- package/dist/detectors/eslintManifestConfig.js +348 -0
- package/dist/detectors/eslintManifestConfig.js.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts +85 -0
- package/dist/detectors/eslintRcJsonConfig.d.ts.map +1 -0
- package/dist/detectors/eslintRcJsonConfig.js +188 -0
- package/dist/detectors/eslintRcJsonConfig.js.map +1 -0
- package/dist/detectors/jestManifestConfig.d.ts +76 -0
- package/dist/detectors/jestManifestConfig.d.ts.map +1 -0
- package/dist/detectors/jestManifestConfig.js +292 -0
- package/dist/detectors/jestManifestConfig.js.map +1 -0
- package/dist/reporters/terminalReporter.js +27 -1
- package/dist/reporters/terminalReporter.js.map +1 -1
- package/dist/scanner/moduleSpecifiers.d.ts +9 -1
- package/dist/scanner/moduleSpecifiers.d.ts.map +1 -1
- package/dist/scanner/moduleSpecifiers.js +2352 -173
- package/dist/scanner/moduleSpecifiers.js.map +1 -1
- package/dist/scanner/projectManifest.d.ts +14 -0
- package/dist/scanner/projectManifest.d.ts.map +1 -1
- package/dist/scanner/projectManifest.js +40 -0
- package/dist/scanner/projectManifest.js.map +1 -1
- package/dist/scanner/scriptUsage.d.ts +11 -5
- package/dist/scanner/scriptUsage.d.ts.map +1 -1
- package/dist/scanner/scriptUsage.js +320 -16
- package/dist/scanner/scriptUsage.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
/**
|
|
35
|
+
* The legacy ESLint precedence state for one project root, computed once by both readers so the two
|
|
36
|
+
* legacy sources obey exactly one set of rules.
|
|
37
|
+
*
|
|
38
|
+
* - `hardBarrier`: a flat config or `.eslintrc.js`/`.cjs`/`.yaml`/`.yml` exists, which disables every
|
|
39
|
+
* legacy hint from both sources;
|
|
40
|
+
* - `legacyRcByName`: a legacy `.eslintrc.json` or extensionless `.eslintrc` exists by name, which
|
|
41
|
+
* displaces the manifest block but does not by itself stop the `.eslintrc.json` reader.
|
|
42
|
+
*
|
|
43
|
+
* Presence is checked by directory entry name only; no entry is opened, stat-followed, read or
|
|
44
|
+
* executed, and an unreadable root simply means no barrier is known to exist.
|
|
45
|
+
*/
|
|
46
|
+
export type LegacyEslintPrecedence = {
|
|
47
|
+
readonly hardBarrier: boolean;
|
|
48
|
+
readonly legacyRcByName: boolean;
|
|
49
|
+
readonly legacyRcJsonEntryName: string | undefined;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Compute the legacy ESLint precedence state for the project rooted at `projectRoot`.
|
|
53
|
+
*
|
|
54
|
+
* The two legacy readers ({@link findEslintManifestUsage} and {@link findEslintRcJsonUsage}) share
|
|
55
|
+
* this so a hard barrier disables both, `.eslintrc.json` wins over the manifest block, and an
|
|
56
|
+
* extensionless `.eslintrc` displaces only the manifest block. Neither reader unions the displaced
|
|
57
|
+
* source, so two contradictory legacy configurations are never combined.
|
|
58
|
+
*
|
|
59
|
+
* The legacy `.eslintrc.json` name is resolved case-insensitively, so the matched directory entry
|
|
60
|
+
* name is returned as `legacyRcJsonEntryName` for the JSON reader to reuse.
|
|
61
|
+
*
|
|
62
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readLegacyEslintPrecedence(projectRoot: string): Promise<LegacyEslintPrecedence>;
|
|
65
|
+
/**
|
|
66
|
+
* Match the allowlisted subset of one legacy `eslintConfig` value against the declared packages.
|
|
67
|
+
*
|
|
68
|
+
* Pure and side-effect free: it takes the raw `eslintConfig` JSON value (any shape, including
|
|
69
|
+
* something that is not an object) and the declared dependency names, and returns the declared
|
|
70
|
+
* packages that have configuration-only evidence, in alphabetical order. Neither argument is
|
|
71
|
+
* mutated, and a candidate that is not declared is dropped, so this function can never describe an
|
|
72
|
+
* undeclared package as used.
|
|
73
|
+
*
|
|
74
|
+
* @param eslintConfig - The raw `package.json#eslintConfig` value, validated here and never trusted.
|
|
75
|
+
* @param declaredPackageNames - Declared dependency names, the only names this may return.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findEslintConfigUsage(eslintConfig: unknown, declaredPackageNames: ReadonlySet<string>): ReadonlySet<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Collect the legacy `package.json#eslintConfig` hints for the project rooted at `projectRoot`.
|
|
80
|
+
*
|
|
81
|
+
* The returned set is shaped for the analyzer's `configUsedNames` option (the Step 56 handoff),
|
|
82
|
+
* which is the only way configuration evidence reaches the analyzer. Nothing is decided here: the
|
|
83
|
+
* analyzer stays the sole owner of the unused/missing categories.
|
|
84
|
+
*
|
|
85
|
+
* No configuration problem is reported as an error. A missing manifest, an unreadable or invalid
|
|
86
|
+
* `package.json`, an unusable `eslintConfig` shape or an unreadable root all yield the empty set;
|
|
87
|
+
* the analyzer loads the manifest itself and remains the component that reports manifest failures,
|
|
88
|
+
* so this detector never replaces or duplicates that diagnosis.
|
|
89
|
+
*
|
|
90
|
+
* Legacy precedence (Step 68.1) is delegated to {@link readLegacyEslintPrecedence}, which both
|
|
91
|
+
* legacy readers share: a hard barrier disables this block outright, and any legacy `.eslintrc.*`
|
|
92
|
+
* by name displaces it in favour of the lower-priority `.eslintrc.json` reader. This reader never
|
|
93
|
+
* unions the displaced source, so the two legacy configurations are never combined.
|
|
94
|
+
*
|
|
95
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
96
|
+
*/
|
|
97
|
+
export declare function findEslintManifestUsage(projectRoot: string): Promise<ReadonlySet<string>>;
|
|
98
|
+
/**
|
|
99
|
+
* Whether the project rooted at `projectRoot` contains a modern flat-config entrypoint (see
|
|
100
|
+
* {@link FLAT_CONFIG_ENTRY_NAMES}).
|
|
101
|
+
*
|
|
102
|
+
* A flat config is a hard barrier: its presence disables every legacy hint, including the
|
|
103
|
+
* `package.json#eslintConfig` block, so it is checked before any legacy reader contributes a hint.
|
|
104
|
+
* This is a thin convenience for callers that only need the flat-config question. Only flat-config
|
|
105
|
+
* entrypoints count: a legacy `.eslintrc.*` file reports `false`, even though it still participates
|
|
106
|
+
* in the hard-barrier check inside the legacy readers.
|
|
107
|
+
*
|
|
108
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
109
|
+
*/
|
|
110
|
+
export declare function hasFlatConfigEntry(projectRoot: string): Promise<boolean>;
|
|
111
|
+
//# sourceMappingURL=eslintManifestConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eslintManifestConfig.d.ts","sourceRoot":"","sources":["../../src/detectors/eslintManifestConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AA4FH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,GAAG,SAAS,CAAC;CACpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,sBAAsB,CAAC,CA8BjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,OAAO,EACrB,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,GACxC,WAAW,CAAC,MAAM,CAAC,CAoBrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAwB/F;AAaD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAS9E"}
|
|
@@ -0,0 +1,348 @@
|
|
|
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 { readFile, readdir } from 'node:fs/promises';
|
|
35
|
+
import { resolve } from 'node:path';
|
|
36
|
+
import { loadProjectManifest, ProjectManifestError } from '../scanner/projectManifest.js';
|
|
37
|
+
/**
|
|
38
|
+
* The legacy `extends` values recognized by this release and the package each one refers to.
|
|
39
|
+
*
|
|
40
|
+
* The table is a fixed allowlist, not a resolution algorithm. It contains the three documented
|
|
41
|
+
* shorthand aliases (`prettier`, `airbnb` and the single `plugin:react/recommended` form) plus the
|
|
42
|
+
* already complete package name of each shareable config they point at, because a complete
|
|
43
|
+
* `eslint-config-*` name is a valid `extends` value in its own right.
|
|
44
|
+
*
|
|
45
|
+
* There is deliberately no plugin package name in this table. Legacy ESLint resolves `extends` as a
|
|
46
|
+
* shareable config name, so `extends: "eslint-plugin-react"` refers to a package named
|
|
47
|
+
* `eslint-config-eslint-plugin-react`, not to `eslint-plugin-react`; the only valid `extends` form
|
|
48
|
+
* for a plugin is `plugin:<name>/<config>`. A plugin is therefore credited through
|
|
49
|
+
* {@link PLUGIN_PACKAGE_NAMES} (or through an allowlisted `plugin:` form), never through a bare
|
|
50
|
+
* plugin name in `extends`, which would suppress a real `unused` finding based on a value ESLint
|
|
51
|
+
* cannot load.
|
|
52
|
+
*
|
|
53
|
+
* Anything else is unsupported on purpose: `eslint:recommended`, `eslint:all`,
|
|
54
|
+
* `plugin:other/recommended`, `prettier/react`, a scoped config, an unknown alias or any other
|
|
55
|
+
* string contributes no evidence, because a guessed alias would create false confidence.
|
|
56
|
+
*/
|
|
57
|
+
const EXTENDS_PACKAGE_NAMES = new Map([
|
|
58
|
+
['prettier', 'eslint-config-prettier'],
|
|
59
|
+
['airbnb', 'eslint-config-airbnb'],
|
|
60
|
+
['plugin:react/recommended', 'eslint-plugin-react'],
|
|
61
|
+
['eslint-config-prettier', 'eslint-config-prettier'],
|
|
62
|
+
['eslint-config-airbnb', 'eslint-config-airbnb'],
|
|
63
|
+
]);
|
|
64
|
+
/**
|
|
65
|
+
* The legacy `plugins` entries recognized by this release and the package each one refers to.
|
|
66
|
+
*
|
|
67
|
+
* Same rule as {@link EXTENDS_PACKAGE_NAMES}: only the documented `react` shorthand and the already
|
|
68
|
+
* complete package name are supported. Invented plugin names, scoped shorthands such as
|
|
69
|
+
* `@acme/eslint-plugin` and any other plugin string produce no evidence.
|
|
70
|
+
*
|
|
71
|
+
* `plugins` is where the complete `eslint-plugin-react` name is valid, unlike in `extends` (see
|
|
72
|
+
* {@link EXTENDS_PACKAGE_NAMES}).
|
|
73
|
+
*/
|
|
74
|
+
const PLUGIN_PACKAGE_NAMES = new Map([
|
|
75
|
+
['react', 'eslint-plugin-react'],
|
|
76
|
+
['eslint-plugin-react', 'eslint-plugin-react'],
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* Modern flat-config entrypoint names that form a hard barrier: their presence disables every legacy
|
|
80
|
+
* ESLint hint, from both `package.json#eslintConfig` and `.eslintrc.json`.
|
|
81
|
+
*
|
|
82
|
+
* Compared case-insensitively (see {@link readLegacyEslintPrecedence}) because case-insensitive
|
|
83
|
+
* filesystems treat `ESLint.Config.js` as the same file, so DepVitals must not disagree with itself
|
|
84
|
+
* about one project on different platforms. Added in Step 68.1: `mts`/`cts` join the original four.
|
|
85
|
+
*/
|
|
86
|
+
const FLAT_CONFIG_ENTRY_NAMES = new Set([
|
|
87
|
+
'eslint.config.js',
|
|
88
|
+
'eslint.config.mjs',
|
|
89
|
+
'eslint.config.cjs',
|
|
90
|
+
'eslint.config.ts',
|
|
91
|
+
'eslint.config.mts',
|
|
92
|
+
'eslint.config.cts',
|
|
93
|
+
]);
|
|
94
|
+
/**
|
|
95
|
+
* Legacy `.eslintrc.*` names that form a hard barrier for every legacy hint, same rule as the flat
|
|
96
|
+
* configs: `.eslintrc.js`, `.eslintrc.cjs`, `.eslintrc.yaml` and `.eslintrc.yml`.
|
|
97
|
+
*
|
|
98
|
+
* Legacy ESLint ignores `package.json#eslintConfig` once any `.eslintrc.*` file exists by name, so
|
|
99
|
+
* these names disable the manifest block too. Compared case-insensitively, like the flat configs.
|
|
100
|
+
*/
|
|
101
|
+
const LEGACY_BARRIER_ENTRY_NAMES = new Set([
|
|
102
|
+
'.eslintrc.js',
|
|
103
|
+
'.eslintrc.cjs',
|
|
104
|
+
'.eslintrc.yaml',
|
|
105
|
+
'.eslintrc.yml',
|
|
106
|
+
]);
|
|
107
|
+
/**
|
|
108
|
+
* Legacy `.eslintrc` names whose presence displaces the manifest block but does not by itself disable
|
|
109
|
+
* the `.eslintrc.json` reader: the extensionless `.eslintrc` and `.eslintrc.json` itself.
|
|
110
|
+
*
|
|
111
|
+
* Legacy ESLint ignores the manifest block once any `.eslintrc.*` file exists by name, so the
|
|
112
|
+
* extensionless `.eslintrc` displaces only the manifest block. `.eslintrc.json` is the file the JSON
|
|
113
|
+
* reader actually reads, so it is listed here as a pure name check for the manifest block. Compared
|
|
114
|
+
* case-insensitively, like the other legacy tables.
|
|
115
|
+
*/
|
|
116
|
+
const LEGACY_RC_BY_NAME_ENTRY_NAMES = new Set(['.eslintrc.json', '.eslintrc']);
|
|
117
|
+
/**
|
|
118
|
+
* Compute the legacy ESLint precedence state for the project rooted at `projectRoot`.
|
|
119
|
+
*
|
|
120
|
+
* The two legacy readers ({@link findEslintManifestUsage} and {@link findEslintRcJsonUsage}) share
|
|
121
|
+
* this so a hard barrier disables both, `.eslintrc.json` wins over the manifest block, and an
|
|
122
|
+
* extensionless `.eslintrc` displaces only the manifest block. Neither reader unions the displaced
|
|
123
|
+
* source, so two contradictory legacy configurations are never combined.
|
|
124
|
+
*
|
|
125
|
+
* The legacy `.eslintrc.json` name is resolved case-insensitively, so the matched directory entry
|
|
126
|
+
* name is returned as `legacyRcJsonEntryName` for the JSON reader to reuse.
|
|
127
|
+
*
|
|
128
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
129
|
+
*/
|
|
130
|
+
export async function readLegacyEslintPrecedence(projectRoot) {
|
|
131
|
+
const root = resolve(projectRoot);
|
|
132
|
+
let entries;
|
|
133
|
+
try {
|
|
134
|
+
entries = await readdir(root);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return {
|
|
138
|
+
hardBarrier: false,
|
|
139
|
+
legacyRcByName: false,
|
|
140
|
+
legacyRcJsonEntryName: undefined,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const has = (names) => entries.some((entry) => names.has(entry.toLowerCase()));
|
|
144
|
+
// The legacy `.eslintrc.json` name is matched case-insensitively (Windows/macOS resolve the
|
|
145
|
+
// same file under a different casing), so remember the actual entry that was found. The JSON
|
|
146
|
+
// reader reuses this exact entry name instead of assuming the canonical lowercase spelling.
|
|
147
|
+
// Prefer the canonical lowercase entry when both it and an uppercase copy exist in a
|
|
148
|
+
// case-sensitive root, so directory order cannot make the reader open the wrong file.
|
|
149
|
+
const legacyRcJsonEntryName = entries.includes('.eslintrc.json')
|
|
150
|
+
? '.eslintrc.json'
|
|
151
|
+
: entries.find((entry) => entry.toLowerCase() === '.eslintrc.json');
|
|
152
|
+
return {
|
|
153
|
+
hardBarrier: has(FLAT_CONFIG_ENTRY_NAMES) || has(LEGACY_BARRIER_ENTRY_NAMES),
|
|
154
|
+
legacyRcByName: has(LEGACY_RC_BY_NAME_ENTRY_NAMES),
|
|
155
|
+
legacyRcJsonEntryName,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Match the allowlisted subset of one legacy `eslintConfig` value against the declared packages.
|
|
160
|
+
*
|
|
161
|
+
* Pure and side-effect free: it takes the raw `eslintConfig` JSON value (any shape, including
|
|
162
|
+
* something that is not an object) and the declared dependency names, and returns the declared
|
|
163
|
+
* packages that have configuration-only evidence, in alphabetical order. Neither argument is
|
|
164
|
+
* mutated, and a candidate that is not declared is dropped, so this function can never describe an
|
|
165
|
+
* undeclared package as used.
|
|
166
|
+
*
|
|
167
|
+
* @param eslintConfig - The raw `package.json#eslintConfig` value, validated here and never trusted.
|
|
168
|
+
* @param declaredPackageNames - Declared dependency names, the only names this may return.
|
|
169
|
+
*/
|
|
170
|
+
export function findEslintConfigUsage(eslintConfig, declaredPackageNames) {
|
|
171
|
+
if (!isPlainRecord(eslintConfig)) {
|
|
172
|
+
return new Set();
|
|
173
|
+
}
|
|
174
|
+
const candidates = [
|
|
175
|
+
...matchTable(extendsValues(eslintConfig['extends']), EXTENDS_PACKAGE_NAMES),
|
|
176
|
+
...matchTable(pluginsValues(eslintConfig['plugins']), PLUGIN_PACKAGE_NAMES),
|
|
177
|
+
];
|
|
178
|
+
const used = new Set();
|
|
179
|
+
for (const candidate of candidates) {
|
|
180
|
+
if (declaredPackageNames.has(candidate)) {
|
|
181
|
+
used.add(candidate);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Rebuilt from sorted names so iteration is deterministic for every input.
|
|
185
|
+
return new Set([...used].sort(compareNames));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Collect the legacy `package.json#eslintConfig` hints for the project rooted at `projectRoot`.
|
|
189
|
+
*
|
|
190
|
+
* The returned set is shaped for the analyzer's `configUsedNames` option (the Step 56 handoff),
|
|
191
|
+
* which is the only way configuration evidence reaches the analyzer. Nothing is decided here: the
|
|
192
|
+
* analyzer stays the sole owner of the unused/missing categories.
|
|
193
|
+
*
|
|
194
|
+
* No configuration problem is reported as an error. A missing manifest, an unreadable or invalid
|
|
195
|
+
* `package.json`, an unusable `eslintConfig` shape or an unreadable root all yield the empty set;
|
|
196
|
+
* the analyzer loads the manifest itself and remains the component that reports manifest failures,
|
|
197
|
+
* so this detector never replaces or duplicates that diagnosis.
|
|
198
|
+
*
|
|
199
|
+
* Legacy precedence (Step 68.1) is delegated to {@link readLegacyEslintPrecedence}, which both
|
|
200
|
+
* legacy readers share: a hard barrier disables this block outright, and any legacy `.eslintrc.*`
|
|
201
|
+
* by name displaces it in favour of the lower-priority `.eslintrc.json` reader. This reader never
|
|
202
|
+
* unions the displaced source, so the two legacy configurations are never combined.
|
|
203
|
+
*
|
|
204
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
205
|
+
*/
|
|
206
|
+
export async function findEslintManifestUsage(projectRoot) {
|
|
207
|
+
const root = resolve(projectRoot);
|
|
208
|
+
const precedence = await readLegacyEslintPrecedence(root);
|
|
209
|
+
if (precedence.hardBarrier || precedence.legacyRcByName) {
|
|
210
|
+
return new Set();
|
|
211
|
+
}
|
|
212
|
+
let packageJsonPath;
|
|
213
|
+
let declaredPackageNames;
|
|
214
|
+
try {
|
|
215
|
+
const manifest = await loadProjectManifest(root);
|
|
216
|
+
packageJsonPath = manifest.packageJsonPath;
|
|
217
|
+
declaredPackageNames = manifest.declaredDependencyNames;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (error instanceof ProjectManifestError) {
|
|
221
|
+
return new Set();
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
return findEslintConfigUsage(await readEslintConfigValue(packageJsonPath), declaredPackageNames);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Whether the on-disk directory entries contain a modern flat-config entrypoint (see
|
|
229
|
+
* {@link FLAT_CONFIG_ENTRY_NAMES}).
|
|
230
|
+
*
|
|
231
|
+
* A legacy `.eslintrc.*` file is not a flat-config entrypoint, so this predicate checks only the
|
|
232
|
+
* flat-config names.
|
|
233
|
+
*/
|
|
234
|
+
function hasFlatConfigEntryEntries(entries) {
|
|
235
|
+
return entries.some((entry) => FLAT_CONFIG_ENTRY_NAMES.has(entry.toLowerCase()));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Whether the project rooted at `projectRoot` contains a modern flat-config entrypoint (see
|
|
239
|
+
* {@link FLAT_CONFIG_ENTRY_NAMES}).
|
|
240
|
+
*
|
|
241
|
+
* A flat config is a hard barrier: its presence disables every legacy hint, including the
|
|
242
|
+
* `package.json#eslintConfig` block, so it is checked before any legacy reader contributes a hint.
|
|
243
|
+
* This is a thin convenience for callers that only need the flat-config question. Only flat-config
|
|
244
|
+
* entrypoints count: a legacy `.eslintrc.*` file reports `false`, even though it still participates
|
|
245
|
+
* in the hard-barrier check inside the legacy readers.
|
|
246
|
+
*
|
|
247
|
+
* @param projectRoot - Project directory to inspect; resolved to an absolute path.
|
|
248
|
+
*/
|
|
249
|
+
export async function hasFlatConfigEntry(projectRoot) {
|
|
250
|
+
let entries;
|
|
251
|
+
try {
|
|
252
|
+
entries = await readdir(resolve(projectRoot));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
return hasFlatConfigEntryEntries(entries);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Read the raw `package.json#eslintConfig` value, ignoring everything that cannot be used.
|
|
261
|
+
*
|
|
262
|
+
* The manifest is read as text and parsed with `JSON.parse`, so no target-project code runs. An
|
|
263
|
+
* unreadable file, invalid JSON, a non-object document or an absent `eslintConfig` all yield
|
|
264
|
+
* `undefined`; the loader-facing diagnosis of a broken manifest stays in {@link loadProjectManifest}.
|
|
265
|
+
*/
|
|
266
|
+
async function readEslintConfigValue(packageJsonPath) {
|
|
267
|
+
let text;
|
|
268
|
+
try {
|
|
269
|
+
text = await readFile(packageJsonPath, 'utf8');
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
let parsed;
|
|
275
|
+
try {
|
|
276
|
+
parsed = JSON.parse(text);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
return isPlainRecord(parsed) ? parsed['eslintConfig'] : undefined;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* The string values of `extends`, which ESLint accepts as one string or as an array.
|
|
285
|
+
*
|
|
286
|
+
* Non-string array entries contribute nothing on their own, so a malformed array still yields no
|
|
287
|
+
* evidence for the malformed entries. Any other shape (number, object, `null`, ...) contributes
|
|
288
|
+
* nothing at all.
|
|
289
|
+
*/
|
|
290
|
+
function extendsValues(value) {
|
|
291
|
+
if (typeof value === 'string') {
|
|
292
|
+
return [value];
|
|
293
|
+
}
|
|
294
|
+
return stringItems(value);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The string values of `plugins`, which ESLint accepts only as an array.
|
|
298
|
+
*
|
|
299
|
+
* A bare string such as `"react"` is not a valid `plugins` value and is deliberately not accepted,
|
|
300
|
+
* so a typo-shaped value never becomes evidence.
|
|
301
|
+
*/
|
|
302
|
+
function pluginsValues(value) {
|
|
303
|
+
return stringItems(value);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The string entries of an array value; a non-array value contributes nothing.
|
|
307
|
+
*/
|
|
308
|
+
function stringItems(value) {
|
|
309
|
+
if (!Array.isArray(value)) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
return value.filter((entry) => typeof entry === 'string');
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Look every value up in one fixed allowlist table.
|
|
316
|
+
*
|
|
317
|
+
* Lookup is exact-string only: no prefix, suffix, substring or case-insensitive matching, so
|
|
318
|
+
* `prettier/react`, `eslint-config-unknown` and `React` never match an entry.
|
|
319
|
+
*/
|
|
320
|
+
function matchTable(values, table) {
|
|
321
|
+
const matched = [];
|
|
322
|
+
for (const value of values) {
|
|
323
|
+
const mapped = table.get(value);
|
|
324
|
+
if (mapped !== undefined) {
|
|
325
|
+
matched.push(mapped);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return matched;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* A JSON object (not an array, not `null`) whose keys are strings.
|
|
332
|
+
*/
|
|
333
|
+
function isPlainRecord(value) {
|
|
334
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Locale-independent lexicographic ordering, so the same configuration always yields the same order.
|
|
338
|
+
*/
|
|
339
|
+
function compareNames(a, b) {
|
|
340
|
+
if (a < b) {
|
|
341
|
+
return -1;
|
|
342
|
+
}
|
|
343
|
+
if (a > b) {
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
//# 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;AAgBH;;;;;;;;;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"}
|