@vibe-agent-toolkit/utils 0.1.42 → 0.2.0-rc.10
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 +81 -19
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/entrypoint.d.ts +54 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +71 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/env-flag.d.ts +51 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +62 -0
- package/dist/env-flag.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.d.ts +60 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +214 -24
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +628 -38
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +770 -71
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +9 -1
- package/dist/fs.js.map +1 -1
- package/dist/git-root-cache.d.ts +44 -0
- package/dist/git-root-cache.d.ts.map +1 -0
- package/dist/git-root-cache.js +68 -0
- package/dist/git-root-cache.js.map +1 -0
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +199 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +250 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +166 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +250 -22
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +95 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +227 -81
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +13 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +15 -6
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/index.d.ts +33 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +113 -33
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +112 -0
- package/dist/listing-refusal.d.ts.map +1 -0
- package/dist/listing-refusal.js +105 -0
- package/dist/listing-refusal.js.map +1 -0
- package/dist/numeric-args.d.ts +24 -0
- package/dist/numeric-args.d.ts.map +1 -0
- package/dist/numeric-args.js +37 -0
- package/dist/numeric-args.js.map +1 -0
- package/dist/path-containment.d.ts +64 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +111 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +143 -18
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +171 -23
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/process.d.ts +7 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts +7 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +24 -10
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +40 -11
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +111 -25
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +30 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +71 -5
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +55 -10
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/spawn-hardened.js +0 -1
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +130 -79
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +229 -124
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +94 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +119 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +197 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +354 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +45 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +51 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +229 -0
- package/dist/timing-dump.js.map +1 -0
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +224 -50
- package/eslint/index.cjs +111 -97
- package/eslint/index.d.cts +16 -6
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +62 -12
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +21 -3
- package/eslint/rules/exempt-path-matcher.cjs +2 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +217 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
- package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +146 -96
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +305 -0
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +168 -0
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +33 -1
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +16 -16
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -417
package/eslint/index.cjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* than in CI on a different OS.
|
|
10
10
|
*
|
|
11
11
|
* They ship as a SUBPATH rather than a separate package because an ESLint plugin
|
|
12
|
-
* is data, not code that runs: every module
|
|
12
|
+
* is data, not code that runs: every rule module exports a plain rule object and
|
|
13
13
|
* none of them `require('eslint')`. So this entry resolves — and the other twelve
|
|
14
14
|
* subpaths keep resolving — whether or not ESLint is installed, which is why
|
|
15
15
|
* `eslint` is declared as an OPTIONAL peer dependency. One install, one version,
|
|
@@ -20,6 +20,28 @@
|
|
|
20
20
|
* entry point can be both `require()`d from an `eslint.config.cjs` and `import`ed
|
|
21
21
|
* from an `eslint.config.js`/`.mjs`.
|
|
22
22
|
*
|
|
23
|
+
* ## The manifest is the directory
|
|
24
|
+
*
|
|
25
|
+
* There is no hand-maintained list of rules here. Every `rules/*.cjs` whose
|
|
26
|
+
* export carries a `meta` object IS a rule, keyed by its basename; the modules
|
|
27
|
+
* that export a factory or a helper object (`eslint-rule-factory`,
|
|
28
|
+
* `no-command-direct-factory`, `exempt-path-matcher`, `safe-import`,
|
|
29
|
+
* `dead-import`) have no `meta` and are skipped. Each rule then declares its own
|
|
30
|
+
* place in `configs.recommended` through `meta.docs.recommended` and
|
|
31
|
+
* `meta.docs.recommendedSeverity`, beside the rule it describes — so adding a
|
|
32
|
+
* rule is one file, and the README/docs table is generated from the same
|
|
33
|
+
* metadata (`bun run generate:eslint-rules-doc` in `packages/utils`).
|
|
34
|
+
*
|
|
35
|
+
* The last hand list here held 27 entries and was mirrored by three literal
|
|
36
|
+
* counts in two test files and two prose counts in two docs, every one of which
|
|
37
|
+
* had drifted at least once. A directory listing cannot drift.
|
|
38
|
+
*
|
|
39
|
+
* `node:fs` and `node:path` are the ONLY external modules this subpath reaches,
|
|
40
|
+
* and only from this file: the rule modules themselves still require nothing —
|
|
41
|
+
* `test/eslint/subpath-purity.test.ts` pins both halves. Two builtins that ship
|
|
42
|
+
* with every Node install do not change the optional-peer property, which was
|
|
43
|
+
* only ever about `eslint` and third-party packages.
|
|
44
|
+
*
|
|
23
45
|
* Rules whose exemptions name a file (the ONE implementation file allowed to call
|
|
24
46
|
* the primitive) take an `exemptFiles` option — see README.md. The shipped
|
|
25
47
|
* defaults are EMPTY: an exemption is a claim about a specific repo's layout, and
|
|
@@ -29,109 +51,75 @@
|
|
|
29
51
|
|
|
30
52
|
'use strict';
|
|
31
53
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'no-unix-shell-commands': require('./rules/no-unix-shell-commands.cjs'),
|
|
37
|
-
'no-os-tmpdir': require('./rules/no-os-tmpdir.cjs'),
|
|
38
|
-
'no-fs-mkdirSync': require('./rules/no-fs-mkdirSync.cjs'),
|
|
39
|
-
'no-fs-realpathSync': require('./rules/no-fs-realpathSync.cjs'),
|
|
40
|
-
'no-manual-path-normalize': require('./rules/no-manual-path-normalize.cjs'),
|
|
41
|
-
'no-path-sep-in-strings': require('./rules/no-path-sep-in-strings.cjs'),
|
|
42
|
-
'no-path-operations-in-comparisons': require('./rules/no-path-operations-in-comparisons.cjs'),
|
|
43
|
-
'no-path-join': require('./rules/no-path-join.cjs'),
|
|
44
|
-
'no-path-resolve': require('./rules/no-path-resolve.cjs'),
|
|
45
|
-
'no-path-relative': require('./rules/no-path-relative.cjs'),
|
|
46
|
-
'no-test-scoped-functions': require('./rules/no-test-scoped-functions.cjs'),
|
|
47
|
-
'no-fs-promises-cp': require('./rules/no-fs-promises-cp.cjs'),
|
|
48
|
-
'no-url-pathname-for-fs': require('./rules/no-url-pathname-for-fs.cjs'),
|
|
49
|
-
'no-bare-dynamic-import-path': require('./rules/no-bare-dynamic-import-path.cjs'),
|
|
50
|
-
'no-file-url-string-concat': require('./rules/no-file-url-string-concat.cjs'),
|
|
51
|
-
'prefer-startswith-over-regex': require('./rules/prefer-startswith-over-regex.cjs'),
|
|
52
|
-
'no-unsafe-root-join': require('./rules/no-unsafe-root-join.cjs'),
|
|
53
|
-
'require-justified-skip': require('./rules/require-justified-skip.cjs'),
|
|
54
|
-
};
|
|
54
|
+
const { readdirSync } = require('node:fs');
|
|
55
|
+
const path = require('node:path');
|
|
56
|
+
|
|
57
|
+
const RULES_DIR = path.join(__dirname, 'rules');
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* that
|
|
61
|
-
* write tests. The two below are neither — they encode a position on TEST STYLE:
|
|
62
|
-
*
|
|
63
|
-
* - `require-justified-skip` — a specific annotation grammar (`SKIP(#123): reason`)
|
|
64
|
-
* for a disabled test, plus a view on what counts as a tautological assertion.
|
|
65
|
-
* - `no-test-scoped-functions` — a view on WHERE a test helper may be declared
|
|
66
|
-
* (module scope, never inside `describe`/`it`).
|
|
67
|
-
*
|
|
68
|
-
* Someone installing this package for `safePath.join()` should not silently
|
|
69
|
-
* inherit either. Both ship in `rules` and stay enabled explicitly:
|
|
70
|
-
*
|
|
71
|
-
* '@vibe-agent-toolkit/require-justified-skip': 'error',
|
|
72
|
-
*
|
|
73
|
-
* That is exactly what VAT's own `eslint.config.js` does — it does not consume
|
|
74
|
-
* `configs.recommended` at all, so this exclusion changes nothing about how this
|
|
75
|
-
* repo lints itself.
|
|
60
|
+
* The two values `recommendedSeverity` may take. A rule that is not
|
|
61
|
+
* recommended may still declare one — it is the severity the rule WOULD ride
|
|
62
|
+
* at, and the generated docs table prints it — but `off` is not a spelling: a
|
|
63
|
+
* rule that wants to be off is a rule with `recommended: false`.
|
|
76
64
|
*/
|
|
77
|
-
const
|
|
78
|
-
'require-justified-skip',
|
|
79
|
-
'no-test-scoped-functions',
|
|
80
|
-
// Excluded for a DIFFERENT reason than the two above: not a style opinion, but
|
|
81
|
-
// an unsound heuristic. It keys on whether an identifier's name ends in `root`
|
|
82
|
-
// rather than on whether any segment is caller-controlled, which makes it
|
|
83
|
-
// simultaneously noisy and blind. Measured on a 4,670-file adopter tree: 108
|
|
84
|
-
// findings, 0 autofixable, and every one of these verified by execution here:
|
|
85
|
-
//
|
|
86
|
-
// FIRES safePath.join(repoRoot, 'docs', 'product') <- all literals, cannot escape
|
|
87
|
-
// FIRES safePath.resolve(packageRoot, '..', '..') <- escaping IS the intent; the fix breaks it
|
|
88
|
-
// FIRES safePath.join(repoRoot) <- one argument, no segment at all
|
|
89
|
-
// silent safePath.join(base, userInput) <- THE dangerous shape, missed
|
|
90
|
-
//
|
|
91
|
-
// A rule that misses the case it exists to catch must not ride in a config
|
|
92
|
-
// named `recommended` at any severity — a safety core that cries wolf teaches
|
|
93
|
-
// people to ignore it, which costs the true positives too. It still ships, and
|
|
94
|
-
// it still earns `error` where scoped to directories in which a path escape is
|
|
95
|
-
// a security boundary (this repo scopes it to the skill-test staging code).
|
|
96
|
-
// Re-include it when it keys on taint rather than on naming.
|
|
97
|
-
'no-unsafe-root-join',
|
|
98
|
-
]);
|
|
65
|
+
const RECOMMENDED_SEVERITIES = new Set(['error', 'warn']);
|
|
99
66
|
|
|
100
67
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* `
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
68
|
+
* Read a rule's `meta.docs` and refuse anything a rule in this pack must not ship
|
|
69
|
+
* without. Thrown at load time, so a malformed rule fails every consumer's
|
|
70
|
+
* `eslint` run at startup rather than silently landing outside `recommended`.
|
|
71
|
+
*/
|
|
72
|
+
function validateRuleDocs(name, rule) {
|
|
73
|
+
if (typeof rule.create !== 'function') {
|
|
74
|
+
throw new TypeError(`eslint rule '${name}' exports a meta but no create function`);
|
|
75
|
+
}
|
|
76
|
+
const docs = rule.meta.docs;
|
|
77
|
+
if (typeof docs?.description !== 'string' || docs.description.length === 0) {
|
|
78
|
+
throw new TypeError(`eslint rule '${name}' has no meta.docs.description`);
|
|
79
|
+
}
|
|
80
|
+
if (typeof docs.recommended !== 'boolean') {
|
|
81
|
+
throw new TypeError(`eslint rule '${name}' must declare meta.docs.recommended as a boolean`);
|
|
82
|
+
}
|
|
83
|
+
if (docs.recommendedSeverity !== undefined && !RECOMMENDED_SEVERITIES.has(docs.recommendedSeverity)) {
|
|
84
|
+
throw new TypeError(`eslint rule '${name}' declares meta.docs.recommendedSeverity outside 'error' | 'warn'`);
|
|
85
|
+
}
|
|
86
|
+
if (docs.recommended && docs.recommendedSeverity === undefined) {
|
|
87
|
+
throw new TypeError(`eslint rule '${name}' is recommended but declares no meta.docs.recommendedSeverity`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Every rule module under `rules/`, keyed by basename, in directory order
|
|
93
|
+
* (which `readdirSync` returns sorted on every platform this package supports).
|
|
115
94
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* briefly graded on a different axis ("style, not a defect") and demoted — that was
|
|
121
|
-
* wrong twice over: avoiding a SonarQube S6557 at lint time instead of at merge time
|
|
122
|
-
* is a real saving, and the rule's matcher rejects any regex containing a
|
|
123
|
-
* metacharacter, so it only fires on true literal prefixes and has near-zero churn.
|
|
124
|
-
* It is `error`.
|
|
95
|
+
* Distinguished from the factories and helpers by a `meta` export, not by
|
|
96
|
+
* filename: a naming convention is a claim nobody checks, and a factory that
|
|
97
|
+
* happened to be named `no-…` would otherwise be registered as a rule whose
|
|
98
|
+
* `create` is a function that builds rules.
|
|
125
99
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
100
|
+
function discoverRules(rulesDir = RULES_DIR) {
|
|
101
|
+
const rules = {};
|
|
102
|
+
for (const file of readdirSync(rulesDir)) {
|
|
103
|
+
if (!file.endsWith('.cjs')) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// eslint-disable-next-line security/detect-non-literal-require -- the directory listing IS the manifest; every entry is a file this package ships
|
|
107
|
+
const candidate = require(path.join(rulesDir, file));
|
|
108
|
+
if (typeof candidate !== 'object' || candidate === null || typeof candidate.meta !== 'object') {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const name = file.slice(0, -'.cjs'.length);
|
|
112
|
+
validateRuleDocs(name, candidate);
|
|
113
|
+
rules[name] = candidate;
|
|
114
|
+
}
|
|
115
|
+
return rules;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const rules = discoverRules();
|
|
131
119
|
|
|
132
120
|
/**
|
|
133
121
|
* Plugin namespace an adopter gets from `configs.recommended`, and therefore the
|
|
134
|
-
* prefix on every rule id (`@vibe-agent-toolkit/no-path
|
|
122
|
+
* prefix on every rule id (`@vibe-agent-toolkit/no-raw-node-path`).
|
|
135
123
|
*
|
|
136
124
|
* Deliberately the SCOPE, not the full subpath specifier: rule ids are the surface
|
|
137
125
|
* adopters type into `rules`, `eslint-disable` comments and CI baselines, and they
|
|
@@ -146,15 +134,41 @@ const plugin = {
|
|
|
146
134
|
},
|
|
147
135
|
rules,
|
|
148
136
|
configs: {},
|
|
137
|
+
/** Test seam: the discovery walk over an arbitrary directory, so the load-time refusals can be exercised on a fixture. */
|
|
138
|
+
__internal: { discoverRules },
|
|
149
139
|
};
|
|
150
140
|
|
|
141
|
+
/**
|
|
142
|
+
* `configs.recommended` is the cross-platform-safety core: every rule whose
|
|
143
|
+
* `meta.docs.recommended` is true, at the severity it declares.
|
|
144
|
+
*
|
|
145
|
+
* `error` is the norm: every such rule flags a call whose replacement is a
|
|
146
|
+
* one-line swap, and a wrong answer is a real bug on some platform. `warn` is
|
|
147
|
+
* reserved for the case where a fresh adopter's first run would otherwise be a
|
|
148
|
+
* wall of blocking errors they cannot triage in one sitting — the criterion is
|
|
149
|
+
* MIGRATION VOLUME, not how real the finding is. A rule whose findings were
|
|
150
|
+
* doubted would be out of `recommended` entirely, not demoted.
|
|
151
|
+
*
|
|
152
|
+
* Each rule that opts OUT states why beside its own `recommended: false`. The
|
|
153
|
+
* reasons fall into a few families — a position on TEST STYLE
|
|
154
|
+
* (`require-justified-skip`, `no-test-scoped-functions`), a heuristic that keys
|
|
155
|
+
* on a NAMING CONVENTION rather than the property it cares about
|
|
156
|
+
* (`no-unsafe-root-join`, `no-process-exit-in-phase`), a SEAM that only exists
|
|
157
|
+
* once the consumer writes it (`no-raw-text-decode`), a REQUIRED OPTION this
|
|
158
|
+
* config cannot supply (`no-self-package-import`), and a claim about the
|
|
159
|
+
* CONSUMER's environment that is right for some and wrong for others
|
|
160
|
+
* (`no-fragile-entrypoint-guard`, `no-bare-symlink-in-tests`). Someone installing
|
|
161
|
+
* this package for `safePath.join()` should not silently inherit any of them.
|
|
162
|
+
* All still ship in `rules` and are enabled by naming them — which is what
|
|
163
|
+
* VAT's own `eslint.config.js` does; it does not consume `configs.recommended`.
|
|
164
|
+
*/
|
|
151
165
|
plugin.configs.recommended = {
|
|
152
166
|
name: '@vibe-agent-toolkit/utils/eslint/recommended',
|
|
153
167
|
plugins: { [NAMESPACE]: plugin },
|
|
154
168
|
rules: Object.fromEntries(
|
|
155
|
-
Object.
|
|
156
|
-
.filter((
|
|
157
|
-
.map((name) => [`${NAMESPACE}/${name}`,
|
|
169
|
+
Object.entries(rules)
|
|
170
|
+
.filter(([, rule]) => rule.meta.docs.recommended)
|
|
171
|
+
.map(([name, rule]) => [`${NAMESPACE}/${name}`, rule.meta.docs.recommendedSeverity]),
|
|
158
172
|
),
|
|
159
173
|
};
|
|
160
174
|
|
package/eslint/index.d.cts
CHANGED
|
@@ -37,20 +37,30 @@ declare namespace plugin {
|
|
|
37
37
|
interface FlatConfig {
|
|
38
38
|
name: string;
|
|
39
39
|
plugins: Record<string, Plugin>;
|
|
40
|
-
/** Rule id → severity. Keys are namespaced: `@vibe-agent-toolkit/no-path
|
|
40
|
+
/** Rule id → severity. Keys are namespaced: `@vibe-agent-toolkit/no-raw-node-path`. */
|
|
41
41
|
rules: Record<string, 'error' | 'warn' | 'off'>;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface Plugin {
|
|
45
45
|
meta: { name: string };
|
|
46
|
-
/** Rule name WITHOUT the namespace prefix, e.g. `no-path
|
|
46
|
+
/** Rule name WITHOUT the namespace prefix, e.g. `no-raw-node-path`. */
|
|
47
47
|
rules: Record<string, RuleModule>;
|
|
48
48
|
configs: {
|
|
49
49
|
/**
|
|
50
|
-
* The cross-platform safety core:
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* The cross-platform safety core: every rule whose `meta.docs.recommended`
|
|
51
|
+
* is true, at the severity its `meta.docs.recommendedSeverity` declares.
|
|
52
|
+
*
|
|
53
|
+
* No count lives here on purpose. This comment once read "18 of the 22
|
|
54
|
+
* rules, four are excluded" while the registry held 24 and the exclude
|
|
55
|
+
* set six, and later "19 of the 27" — a number in prose is a claim the
|
|
56
|
+
* manifest cannot check. The generated table in `README.md` carries the
|
|
57
|
+
* live counts, and `test/eslint/rule-manifest.test.ts` asserts the
|
|
58
|
+
* config against the directory rather than against a literal.
|
|
59
|
+
*
|
|
60
|
+
* Every rule that opts out states why beside its own `recommended: false`
|
|
61
|
+
* in `rules/<name>.cjs`; all of them still ship in `rules` and are
|
|
62
|
+
* enabled by naming them — which is what this repo's own
|
|
63
|
+
* `eslint.config.js` does.
|
|
54
64
|
*/
|
|
55
65
|
recommended: FlatConfig;
|
|
56
66
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared by the rules that match a call by name: the identifier a call is
|
|
3
|
+
* made through, bare (`spawn(…)`) or as the last member of a non-computed
|
|
4
|
+
* member expression (`cp.spawn(…)`), or `null` for any other callee.
|
|
5
|
+
*
|
|
6
|
+
* Not a rule — exports no `meta`, so `index.cjs` never registers it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/** @param {object} call - ESTree `CallExpression` node. */
|
|
12
|
+
function calleeName(call) {
|
|
13
|
+
const { callee } = call;
|
|
14
|
+
if (callee.type === 'Identifier') {
|
|
15
|
+
return callee.name;
|
|
16
|
+
}
|
|
17
|
+
if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier') {
|
|
18
|
+
return callee.property.name;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { calleeName };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: commands-import-boundary
|
|
3
|
+
*
|
|
4
|
+
* In a command module — a file under one of the configured command
|
|
5
|
+
* directories — flags any import of the filesystem (`node:fs`,
|
|
6
|
+
* `node:fs/promises`, `fs`, `fs/promises`) and any import whose specifier
|
|
7
|
+
* matches a configured forbidden-module pattern (a package's internals, a
|
|
8
|
+
* local parser or walker module).
|
|
9
|
+
*
|
|
10
|
+
* A command's job is to parse arguments, call a seam, and render a report. The
|
|
11
|
+
* moment it opens a directory itself it has become a fifth enumeration lane
|
|
12
|
+
* nobody documented: `docs/contributing/command-lane-table.md` names the
|
|
13
|
+
* walkers every population must come through, and one command carrying its
|
|
14
|
+
* own ~700-line walker beside them was the audit finding behind this rule.
|
|
15
|
+
* The boundary is syntactic — an import is an import — so it holds at the
|
|
16
|
+
* desk rather than in a review.
|
|
17
|
+
*
|
|
18
|
+
* Shapes covered: static `import`, `import type` is IGNORED (a type does no
|
|
19
|
+
* I/O), `export … from`, dynamic `import('…')` and `require('…')` with a
|
|
20
|
+
* literal specifier.
|
|
21
|
+
*
|
|
22
|
+
* Options:
|
|
23
|
+
* - `commandGlobs: string[]` — repo-relative directories that hold commands
|
|
24
|
+
* (default `['packages/cli/src/commands/']`). A trailing `/**` is accepted
|
|
25
|
+
* and ignored; matching is by anchored directory prefix, so nested command
|
|
26
|
+
* directories are covered.
|
|
27
|
+
* - `forbiddenModules: string[]` — regex sources tested against the import
|
|
28
|
+
* specifier (default `['^@vibe-agent-toolkit/resources/']`: the barrel is a
|
|
29
|
+
* seam, a subpath is an internal).
|
|
30
|
+
* - `allowFiles: string[]` — repo-relative paths of today's offenders, the
|
|
31
|
+
* ratchet. Name files, never directories.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // BAD — the command is now a walker
|
|
35
|
+
* import { readdirSync } from 'node:fs';
|
|
36
|
+
*
|
|
37
|
+
* // GOOD — the population comes through a declared lane
|
|
38
|
+
* import { crawlDirectory } from '@vibe-agent-toolkit/utils/crawl';
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
'use strict';
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
createExemptDirectoryMatcher,
|
|
45
|
+
createExemptPathMatcher,
|
|
46
|
+
} = require('./exempt-path-matcher.cjs');
|
|
47
|
+
|
|
48
|
+
const FS_MODULES = new Set(['node:fs', 'node:fs/promises', 'fs', 'fs/promises']);
|
|
49
|
+
const DEFAULT_COMMAND_DIRS = ['packages/cli/src/commands/'];
|
|
50
|
+
const DEFAULT_FORBIDDEN = ['^@vibe-agent-toolkit/resources/'];
|
|
51
|
+
|
|
52
|
+
/** Strip a trailing glob so `packages/cli/src/commands/**` reads as its directory. */
|
|
53
|
+
function toDirectory(entry) {
|
|
54
|
+
return entry.endsWith('/**') ? entry.slice(0, -2) : entry;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The string value of a literal module specifier, or null. */
|
|
58
|
+
function literalSpecifier(node) {
|
|
59
|
+
return node?.type === 'Literal' && typeof node.value === 'string' ? node.value : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
meta: {
|
|
64
|
+
type: 'problem',
|
|
65
|
+
docs: {
|
|
66
|
+
description:
|
|
67
|
+
'Disallow filesystem and internal-module imports in command modules — a command calls a ' +
|
|
68
|
+
'declared enumeration lane, it does not become one',
|
|
69
|
+
recommended: false,
|
|
70
|
+
recommendedSeverity: 'warn',
|
|
71
|
+
},
|
|
72
|
+
schema: [
|
|
73
|
+
{
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
commandGlobs: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
77
|
+
forbiddenModules: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
78
|
+
allowFiles: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
79
|
+
},
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
messages: {
|
|
84
|
+
fsImport:
|
|
85
|
+
"Command modules do not import '{{source}}': reading the tree here makes this command an " +
|
|
86
|
+
'undeclared enumeration lane. Go through a seam in @vibe-agent-toolkit/utils or ' +
|
|
87
|
+
'@vibe-agent-toolkit/resources (see docs/contributing/command-lane-table.md).',
|
|
88
|
+
forbiddenModule:
|
|
89
|
+
"Command modules do not import '{{source}}' (matches forbidden pattern /{{pattern}}/): " +
|
|
90
|
+
'it is an internal, not a seam. Import the barrel or the declared lane instead.',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
create(context) {
|
|
95
|
+
const options = context.options?.[0] ?? {};
|
|
96
|
+
const filename = context.filename ?? context.getFilename();
|
|
97
|
+
const commandDirs = (options.commandGlobs ?? DEFAULT_COMMAND_DIRS).map(toDirectory);
|
|
98
|
+
const isCommandFile = createExemptDirectoryMatcher(commandDirs);
|
|
99
|
+
const isAllowed = createExemptPathMatcher(options.allowFiles ?? []);
|
|
100
|
+
if (!isCommandFile(filename) || isAllowed(filename)) {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const forbidden = (options.forbiddenModules ?? DEFAULT_FORBIDDEN).map((pattern) => ({
|
|
105
|
+
pattern,
|
|
106
|
+
// eslint-disable-next-line security/detect-non-literal-regexp -- the option is a regex source by contract: a config-time string written by the repo, not user data
|
|
107
|
+
regex: new RegExp(pattern, 'u'),
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
function check(node, source) {
|
|
111
|
+
if (source === null) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (FS_MODULES.has(source)) {
|
|
115
|
+
context.report({ node, messageId: 'fsImport', data: { source } });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const hit = forbidden.find(({ regex }) => regex.test(source));
|
|
119
|
+
if (hit) {
|
|
120
|
+
context.report({ node, messageId: 'forbiddenModule', data: { source, pattern: hit.pattern } });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
ImportDeclaration(node) {
|
|
126
|
+
if (node.importKind !== 'type') {
|
|
127
|
+
check(node, literalSpecifier(node.source));
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
ExportNamedDeclaration(node) {
|
|
131
|
+
if (node.source && node.exportKind !== 'type') {
|
|
132
|
+
check(node, literalSpecifier(node.source));
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
ExportAllDeclaration(node) {
|
|
136
|
+
check(node, literalSpecifier(node.source));
|
|
137
|
+
},
|
|
138
|
+
ImportExpression(node) {
|
|
139
|
+
check(node, literalSpecifier(node.source));
|
|
140
|
+
},
|
|
141
|
+
CallExpression(node) {
|
|
142
|
+
if (node.callee.type === 'Identifier' && node.callee.name === 'require') {
|
|
143
|
+
check(node, literalSpecifier(node.arguments[0]));
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -63,9 +63,16 @@ const REMOVABLE_MODULES = new Set([
|
|
|
63
63
|
]);
|
|
64
64
|
|
|
65
65
|
const DEAD_UNSAFE_IMPORT = 'deadUnsafeImport';
|
|
66
|
+
// States what the rule can actually observe, and no more. The earlier wording —
|
|
67
|
+
// "this rule's autofix rewrote the last call that referenced it" — asserted a
|
|
68
|
+
// CAUSE, and the leg cannot see causes: by the time a binding reads as dead the
|
|
69
|
+
// rewrite that killed it is a pass in the past and left no trace naming which
|
|
70
|
+
// import it drew on. On a file with two imports of the same module, one of them
|
|
71
|
+
// dead since before the pack ever ran, that sentence was simply false about one
|
|
72
|
+
// of the two. See `reportDeadUnsafeImports` for what the gate does prove.
|
|
66
73
|
const DEAD_UNSAFE_IMPORT_MESSAGE =
|
|
67
|
-
"'{{local}}'
|
|
68
|
-
"
|
|
74
|
+
"'{{local}}' has no remaining references, and this file already calls the safe " +
|
|
75
|
+
"replacement this rule's autofix writes. Remove the '{{module}}' import.";
|
|
69
76
|
|
|
70
77
|
/**
|
|
71
78
|
* Does this declaration bring in anything the type checker alone can see?
|
|
@@ -128,12 +135,46 @@ function isDeadRemovableImport(sourceCode, node) {
|
|
|
128
135
|
* reported. The removal therefore lands on a later pass, after the rewrite that
|
|
129
136
|
* consumed the last reference — never speculatively alongside it.
|
|
130
137
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
138
|
+
* ## The two-part gate, and the evidence it cannot get
|
|
139
|
+
*
|
|
140
|
+
* Both gates keep this a REPAIR leg rather than a general unused-import rule,
|
|
141
|
+
* and both must be read from the SOURCE, never from a flag a `fix()` can flip:
|
|
142
|
+
* ESLint runs `fix()` for a suppressed problem before the `eslint-disable`
|
|
143
|
+
* filter discards it, so any mutable "did I add the import?" flag is already
|
|
144
|
+
* spent and lying by the time this runs.
|
|
145
|
+
*
|
|
146
|
+
* - `safeBoundInSource` — the safe symbol is in scope at all.
|
|
147
|
+
* - `replacementCalled` — THIS rule's own replacement is actually CALLED in this
|
|
148
|
+
* file (`safePath.join(…)`, `normalizedTmpdir()`, `toForwardSlash(…)`), which
|
|
149
|
+
* is the text its fixer writes and nothing else does.
|
|
150
|
+
*
|
|
151
|
+
* The first alone was the whole gate once, and it let an unrelated coincidence
|
|
152
|
+
* arm the leg: `safePath` reaches scope for reasons that have nothing to do with
|
|
153
|
+
* a call this pack consumed, and any `node:path` import that happened to be dead
|
|
154
|
+
* already — dead before the pack ever ran, for somebody else's reason — was then
|
|
155
|
+
* deleted under a message claiming this fixer had orphaned it. That is the
|
|
156
|
+
* general unused-import rule the module docstring above declines to be, reached
|
|
157
|
+
* by accident. The second gate closes it: no call to this rule's replacement
|
|
158
|
+
* means this rule rewrote nothing here, whatever else is in scope.
|
|
159
|
+
*
|
|
160
|
+
* It does NOT get down to the individual declaration, and it cannot. Per-import
|
|
161
|
+
* evidence has to name WHICH import a consumed reference belonged to, and the
|
|
162
|
+
* only pass that knows is the one doing the rewrite — where every reference is
|
|
163
|
+
* still live and nothing is dead to report. Removing speculatively in that pass
|
|
164
|
+
* instead is the trap the rest of this pack is built around: a suppressed call
|
|
165
|
+
* report still runs its `fix()`, so the removal lands while the call it was
|
|
166
|
+
* paired with survives, and the file is left with a dangling reference (146 of
|
|
167
|
+
* them, measured, in the defect this file exists to prevent). Latching state
|
|
168
|
+
* across passes to carry the answer forward is the rejected `WeakMap` approach
|
|
169
|
+
* for the same reason, one layer worse. So a file holding two imports of one
|
|
170
|
+
* module, one of them dead all along, still gets both removed and one true
|
|
171
|
+
* message plus one that merely describes the file rather than the history. The
|
|
172
|
+
* message is worded to stay true of both; the residual is a redundant deletion,
|
|
173
|
+
* never a broken one.
|
|
174
|
+
*
|
|
175
|
+
* Being over-strict here is cheap by construction: a gate that declines leaves
|
|
176
|
+
* an unused import, which is a lint finding a human reads. Only firing wrongly
|
|
177
|
+
* costs anything, and only the CALL rewrite can break a file.
|
|
137
178
|
*
|
|
138
179
|
* Its own report, with its own `fix`, deliberately — so the deletion appears in
|
|
139
180
|
* lint output and can be suppressed at the import line, rather than a rewrite
|
|
@@ -161,10 +202,19 @@ function isDeadRemovableImport(sourceCode, node) {
|
|
|
161
202
|
* @param {object} context - ESLint rule context.
|
|
162
203
|
* @param {object} sourceCode - ESLint `SourceCode` for the file being linted.
|
|
163
204
|
* @param {object[]} importNodes - Unsafe-module `ImportDeclaration`s seen this pass.
|
|
164
|
-
* @param {boolean}
|
|
205
|
+
* @param {boolean} safeBoundInSource - Was the safe symbol already bound in the SOURCE?
|
|
206
|
+
* @param {boolean} replacementCalled - Does the SOURCE call this rule's own safe
|
|
207
|
+
* replacement? Both are required, and both are the caller's to compute from
|
|
208
|
+
* source — see the gate discussion above for why neither may be defaulted.
|
|
165
209
|
*/
|
|
166
|
-
function reportDeadUnsafeImports(
|
|
167
|
-
|
|
210
|
+
function reportDeadUnsafeImports(
|
|
211
|
+
context,
|
|
212
|
+
sourceCode,
|
|
213
|
+
importNodes,
|
|
214
|
+
safeBoundInSource,
|
|
215
|
+
replacementCalled,
|
|
216
|
+
) {
|
|
217
|
+
if (!safeBoundInSource || !replacementCalled) {
|
|
168
218
|
return;
|
|
169
219
|
}
|
|
170
220
|
for (const node of importNodes) {
|
|
@@ -183,7 +233,7 @@ function reportDeadUnsafeImports(context, sourceCode, importNodes, migrated) {
|
|
|
183
233
|
},
|
|
184
234
|
// `fixer.remove(node)` takes the declaration and leaves its newline, so a
|
|
185
235
|
// blank line remains where the import was. That is exactly what the
|
|
186
|
-
// specifier-removal leg in `
|
|
236
|
+
// specifier-removal leg in `no-raw-node-path.cjs` has always
|
|
187
237
|
// done — its fixtures pin the leading `\n` — and matching it keeps one
|
|
188
238
|
// behaviour rather than two. Extending the range through a trailing
|
|
189
239
|
// whitespace-only remainder would tidy both, and should be done to both at
|