@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12
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 +112 -10
- package/dist/archive/archive.d.ts +129 -0
- package/dist/archive/archive.d.ts.map +1 -0
- package/dist/archive/archive.js +223 -0
- package/dist/archive/archive.js.map +1 -0
- package/dist/archive/entries.d.ts +132 -0
- package/dist/archive/entries.d.ts.map +1 -0
- package/dist/archive/entries.js +223 -0
- package/dist/archive/entries.js.map +1 -0
- package/dist/archive/limits.d.ts +39 -0
- package/dist/archive/limits.d.ts.map +1 -0
- package/dist/archive/limits.js +59 -0
- package/dist/archive/limits.js.map +1 -0
- package/dist/archive/records.d.ts +14 -0
- package/dist/archive/records.d.ts.map +1 -0
- package/dist/archive/records.js +3 -0
- package/dist/archive/records.js.map +1 -0
- package/dist/archive/tar-reader.d.ts +18 -0
- package/dist/archive/tar-reader.d.ts.map +1 -0
- package/dist/archive/tar-reader.js +173 -0
- package/dist/archive/tar-reader.js.map +1 -0
- package/dist/archive/zip-reader.d.ts +76 -0
- package/dist/archive/zip-reader.d.ts.map +1 -0
- package/dist/archive/zip-reader.js +376 -0
- package/dist/archive/zip-reader.js.map +1 -0
- package/dist/archive.d.ts +14 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +12 -0
- package/dist/archive.js.map +1 -0
- package/dist/asset-reference.d.ts +17 -1
- package/dist/asset-reference.d.ts.map +1 -1
- package/dist/asset-reference.js +31 -7
- package/dist/asset-reference.js.map +1 -1
- package/dist/dirent-kind.d.ts +13 -40
- package/dist/dirent-kind.d.ts.map +1 -1
- package/dist/dirent-kind.js +21 -69
- package/dist/dirent-kind.js.map +1 -1
- package/dist/entrypoint.d.ts +2 -2
- package/dist/entrypoint.d.ts.map +1 -1
- package/dist/entrypoint.js +4 -3
- package/dist/entrypoint.js.map +1 -1
- package/dist/env-flag.d.ts +13 -5
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +13 -5
- package/dist/env-flag.js.map +1 -1
- package/dist/errors/confirmed-absent.d.ts +41 -0
- package/dist/errors/confirmed-absent.d.ts.map +1 -0
- package/dist/errors/confirmed-absent.js +43 -0
- package/dist/errors/confirmed-absent.js.map +1 -0
- package/dist/errors/errno-table.d.ts +177 -0
- package/dist/errors/errno-table.d.ts.map +1 -0
- package/dist/errors/errno-table.js +343 -0
- package/dist/errors/errno-table.js.map +1 -0
- package/dist/errors/fs-boundary.d.ts +36 -0
- package/dist/errors/fs-boundary.d.ts.map +1 -0
- package/dist/errors/fs-boundary.js +121 -0
- package/dist/errors/fs-boundary.js.map +1 -0
- package/dist/errors/fs-fault.d.ts +176 -0
- package/dist/errors/fs-fault.d.ts.map +1 -0
- package/dist/errors/fs-fault.js +215 -0
- package/dist/errors/fs-fault.js.map +1 -0
- package/dist/errors/git-errno.d.ts +13 -0
- package/dist/errors/git-errno.d.ts.map +1 -0
- package/dist/errors/git-errno.js +45 -0
- package/dist/errors/git-errno.js.map +1 -0
- package/dist/errors/path-present.d.ts +72 -0
- package/dist/errors/path-present.d.ts.map +1 -0
- package/dist/errors/path-present.js +110 -0
- package/dist/errors/path-present.js.map +1 -0
- package/dist/errors/rag-index-empty.d.ts +16 -0
- package/dist/errors/rag-index-empty.d.ts.map +1 -0
- package/dist/errors/rag-index-empty.js +16 -0
- package/dist/errors/rag-index-empty.js.map +1 -0
- package/dist/errors/suppressed-faults.d.ts +34 -0
- package/dist/errors/suppressed-faults.d.ts.map +1 -0
- package/dist/errors/suppressed-faults.js +66 -0
- package/dist/errors/suppressed-faults.js.map +1 -0
- package/dist/file-crawler.d.ts +13 -18
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +140 -24
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +75 -50
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +148 -158
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -4
- package/dist/fs.js.map +1 -1
- package/dist/git-snapshot.d.ts +25 -17
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +65 -11
- package/dist/git-snapshot.js.map +1 -1
- package/dist/git-tracker.d.ts +11 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +29 -6
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +7 -35
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +5 -4
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/in-order.d.ts +93 -0
- package/dist/in-order.d.ts.map +1 -0
- package/dist/in-order.js +165 -0
- package/dist/in-order.js.map +1 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/kill-process-tree.d.ts +43 -0
- package/dist/kill-process-tree.d.ts.map +1 -0
- package/dist/kill-process-tree.js +48 -0
- package/dist/kill-process-tree.js.map +1 -0
- package/dist/link-following.d.ts +115 -0
- package/dist/link-following.d.ts.map +1 -0
- package/dist/link-following.js +283 -0
- package/dist/link-following.js.map +1 -0
- package/dist/listing-refusal.d.ts +44 -0
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +79 -2
- package/dist/listing-refusal.js.map +1 -1
- package/dist/name-fold.d.ts +25 -0
- package/dist/name-fold.d.ts.map +1 -0
- package/dist/name-fold.js +40 -0
- package/dist/name-fold.js.map +1 -0
- package/dist/path-containment.d.ts +17 -0
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +28 -5
- package/dist/path-containment.js.map +1 -1
- package/dist/path-utils.d.ts +14 -17
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +37 -55
- package/dist/path-utils.js.map +1 -1
- package/dist/process.d.ts +2 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +2 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +8 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +6 -2
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +1 -1
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts.map +1 -1
- package/dist/skill-targets.js +3 -1
- package/dist/skill-targets.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +0 -15
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +11 -50
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/special-file.d.ts +18 -0
- package/dist/special-file.d.ts.map +1 -0
- package/dist/special-file.js +21 -0
- package/dist/special-file.js.map +1 -0
- package/dist/terminating-signals.d.ts +129 -0
- package/dist/terminating-signals.d.ts.map +1 -0
- package/dist/terminating-signals.js +210 -0
- package/dist/terminating-signals.js.map +1 -0
- package/dist/test-helpers.d.ts +9 -5
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +18 -21
- package/dist/test-helpers.js.map +1 -1
- package/dist/test-scratch-guard.d.ts +34 -0
- package/dist/test-scratch-guard.d.ts.map +1 -0
- package/dist/test-scratch-guard.js +45 -0
- package/dist/test-scratch-guard.js.map +1 -0
- package/dist/testing/archive-fixtures.d.ts +147 -0
- package/dist/testing/archive-fixtures.d.ts.map +1 -0
- package/dist/testing/archive-fixtures.js +449 -0
- package/dist/testing/archive-fixtures.js.map +1 -0
- package/dist/testing/executables.d.ts +6 -0
- package/dist/testing/executables.d.ts.map +1 -1
- package/dist/testing/executables.js +20 -9
- package/dist/testing/executables.js.map +1 -1
- package/dist/testing/fault-fs-preload.d.ts +16 -0
- package/dist/testing/fault-fs-preload.d.ts.map +1 -0
- package/dist/testing/fault-fs-preload.js +22 -0
- package/dist/testing/fault-fs-preload.js.map +1 -0
- package/dist/testing/fault-fs.d.ts +115 -0
- package/dist/testing/fault-fs.d.ts.map +1 -0
- package/dist/testing/fault-fs.js +411 -0
- package/dist/testing/fault-fs.js.map +1 -0
- package/dist/testing/fault-spec.d.ts +83 -0
- package/dist/testing/fault-spec.d.ts.map +1 -0
- package/dist/testing/fault-spec.js +132 -0
- package/dist/testing/fault-spec.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -1
- package/dist/testing/hostile-tree.js +3 -4
- package/dist/testing/hostile-tree.js.map +1 -1
- package/dist/testing/platform-gates.d.ts +8 -0
- package/dist/testing/platform-gates.d.ts.map +1 -1
- package/dist/testing/platform-gates.js +24 -0
- package/dist/testing/platform-gates.js.map +1 -1
- package/dist/testing/temp-dir.d.ts +31 -0
- package/dist/testing/temp-dir.d.ts.map +1 -1
- package/dist/testing/temp-dir.js +63 -0
- package/dist/testing/temp-dir.js.map +1 -1
- package/dist/testing/tier-timeout.d.ts +34 -0
- package/dist/testing/tier-timeout.d.ts.map +1 -0
- package/dist/testing/tier-timeout.js +45 -0
- package/dist/testing/tier-timeout.js.map +1 -0
- package/dist/testing/tree-snapshot.d.ts +71 -0
- package/dist/testing/tree-snapshot.d.ts.map +1 -0
- package/dist/testing/tree-snapshot.js +163 -0
- package/dist/testing/tree-snapshot.js.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +8 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +29 -0
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +64 -0
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts +58 -6
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +111 -11
- package/dist/text-file.js.map +1 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +1 -1
- package/dist/text.js.map +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +2 -10
- package/dist/timing-dump.js.map +1 -1
- package/dist/tree-change/apply-decisions.d.ts +101 -0
- package/dist/tree-change/apply-decisions.d.ts.map +1 -0
- package/dist/tree-change/apply-decisions.js +139 -0
- package/dist/tree-change/apply-decisions.js.map +1 -0
- package/dist/tree-change/apply.d.ts +98 -0
- package/dist/tree-change/apply.d.ts.map +1 -0
- package/dist/tree-change/apply.js +364 -0
- package/dist/tree-change/apply.js.map +1 -0
- package/dist/tree-change/copy-decisions.d.ts +58 -0
- package/dist/tree-change/copy-decisions.d.ts.map +1 -0
- package/dist/tree-change/copy-decisions.js +66 -0
- package/dist/tree-change/copy-decisions.js.map +1 -0
- package/dist/tree-change/copy-tree.d.ts +125 -0
- package/dist/tree-change/copy-tree.d.ts.map +1 -0
- package/dist/tree-change/copy-tree.js +279 -0
- package/dist/tree-change/copy-tree.js.map +1 -0
- package/dist/tree-change/files.d.ts +167 -0
- package/dist/tree-change/files.d.ts.map +1 -0
- package/dist/tree-change/files.js +423 -0
- package/dist/tree-change/files.js.map +1 -0
- package/dist/tree-change/followed-walk.d.ts +54 -0
- package/dist/tree-change/followed-walk.d.ts.map +1 -0
- package/dist/tree-change/followed-walk.js +81 -0
- package/dist/tree-change/followed-walk.js.map +1 -0
- package/dist/tree-change/host-chain.d.ts +48 -0
- package/dist/tree-change/host-chain.d.ts.map +1 -0
- package/dist/tree-change/host-chain.js +85 -0
- package/dist/tree-change/host-chain.js.map +1 -0
- package/dist/tree-change/identity-compare.d.ts +61 -0
- package/dist/tree-change/identity-compare.d.ts.map +1 -0
- package/dist/tree-change/identity-compare.js +115 -0
- package/dist/tree-change/identity-compare.js.map +1 -0
- package/dist/tree-change/identity.d.ts +62 -0
- package/dist/tree-change/identity.d.ts.map +1 -0
- package/dist/tree-change/identity.js +152 -0
- package/dist/tree-change/identity.js.map +1 -0
- package/dist/tree-change/index.d.ts +24 -0
- package/dist/tree-change/index.d.ts.map +1 -0
- package/dist/tree-change/index.js +14 -0
- package/dist/tree-change/index.js.map +1 -0
- package/dist/tree-change/link-chain.d.ts +180 -0
- package/dist/tree-change/link-chain.d.ts.map +1 -0
- package/dist/tree-change/link-chain.js +377 -0
- package/dist/tree-change/link-chain.js.map +1 -0
- package/dist/tree-change/link-containment.d.ts +105 -0
- package/dist/tree-change/link-containment.d.ts.map +1 -0
- package/dist/tree-change/link-containment.js +196 -0
- package/dist/tree-change/link-containment.js.map +1 -0
- package/dist/tree-change/link-walk.d.ts +49 -0
- package/dist/tree-change/link-walk.d.ts.map +1 -0
- package/dist/tree-change/link-walk.js +69 -0
- package/dist/tree-change/link-walk.js.map +1 -0
- package/dist/tree-change/plan.d.ts +206 -0
- package/dist/tree-change/plan.d.ts.map +1 -0
- package/dist/tree-change/plan.js +320 -0
- package/dist/tree-change/plan.js.map +1 -0
- package/dist/tree-change/readable-tree.d.ts +44 -0
- package/dist/tree-change/readable-tree.d.ts.map +1 -0
- package/dist/tree-change/readable-tree.js +52 -0
- package/dist/tree-change/readable-tree.js.map +1 -0
- package/dist/tree-change/rollback-error.d.ts +29 -0
- package/dist/tree-change/rollback-error.d.ts.map +1 -0
- package/dist/tree-change/rollback-error.js +31 -0
- package/dist/tree-change/rollback-error.js.map +1 -0
- package/dist/tree-change/staging-names.d.ts +27 -0
- package/dist/tree-change/staging-names.d.ts.map +1 -0
- package/dist/tree-change/staging-names.js +40 -0
- package/dist/tree-change/staging-names.js.map +1 -0
- package/dist/tree-change/tree-walk.d.ts +81 -0
- package/dist/tree-change/tree-walk.d.ts.map +1 -0
- package/dist/tree-change/tree-walk.js +212 -0
- package/dist/tree-change/tree-walk.js.map +1 -0
- package/dist/win32-namespace.d.ts +24 -0
- package/dist/win32-namespace.d.ts.map +1 -0
- package/dist/win32-namespace.js +31 -0
- package/dist/win32-namespace.js.map +1 -0
- package/dist/yaml/surgical-yaml.d.ts +12 -3
- package/dist/yaml/surgical-yaml.d.ts.map +1 -1
- package/dist/yaml/surgical-yaml.js +16 -6
- package/dist/yaml/surgical-yaml.js.map +1 -1
- package/eslint/README.md +12 -4
- package/eslint/rules/fs-binding-scan.cjs +278 -0
- package/eslint/rules/no-adhoc-errno.cjs +364 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
- package/eslint/rules/no-blind-catch.cjs +1 -1
- package/eslint/rules/no-destructive-fs.cjs +359 -0
- package/eslint/rules/no-existssync.cjs +70 -0
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
- package/eslint/rules/no-link-following-fs.cjs +68 -0
- package/eslint/rules/no-literal-process-exit.cjs +96 -8
- package/eslint/rules/no-literal-test-timeout.cjs +214 -0
- package/eslint/rules/no-raw-signal-listener.cjs +224 -0
- package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
- package/package.json +10 -2
- package/dist/errors/errno.d.ts +0 -51
- package/dist/errors/errno.d.ts.map +0 -1
- package/dist/errors/errno.js +0 -112
- package/dist/errors/errno.js.map +0 -1
- package/eslint/rules/no-fs-promises-cp.cjs +0 -43
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint rule: no-adhoc-errno
|
|
3
|
+
*
|
|
4
|
+
* Bans classifying a filesystem failure by hand. An errno name written as a string
|
|
5
|
+
* in a classifying position is a private errno table, and every private table
|
|
6
|
+
* decides a refusal code a little differently from its neighbours. One classifier
|
|
7
|
+
* (`fsFaultOf` / `classifyFsFault`, plus the single-errno predicates such as
|
|
8
|
+
* `isPathAbsentError`) owns the vocabulary; everything else asks it.
|
|
9
|
+
*
|
|
10
|
+
* Two reports:
|
|
11
|
+
*
|
|
12
|
+
* - `literal` — an errno name as a string that is (a) an operand of `===` `!==`
|
|
13
|
+
* `==` `!=`, (b) a `switch` `case` test, (c) an element of an array literal that
|
|
14
|
+
* is passed to `new Set(...)` or bound to a `const`, or (d) the argument of
|
|
15
|
+
* `.has()` / `.includes()`. (d) also catches the message match
|
|
16
|
+
* `errorMessage.includes('ENOENT')`, which hides an errno in prose.
|
|
17
|
+
* - `adhocRefusal` — inside a `catch (e)`, a `new CommandRefusalError(...)` or
|
|
18
|
+
* `new VatError(...)` whose options carry `cause: e`, where the same catch body
|
|
19
|
+
* also reads `e.code` or calls `fsFaultOf(e)` / `isPathAbsentError(e)`. The catch
|
|
20
|
+
* is deciding the refusal itself; `classifyFsFault` decides it once.
|
|
21
|
+
*
|
|
22
|
+
* An inline array is a lookup table too: `['ENOENT', 'EACCES'].includes(e.code)` (and
|
|
23
|
+
* `.some` / `.indexOf` / `.find` / `.findIndex` / `.every`). An identifier bound once
|
|
24
|
+
* by `const` to an errno string (`const C = 'ENOENT'; e.code === C`) is flagged at its
|
|
25
|
+
* use, and a template literal with no expressions counts as the string it spells.
|
|
26
|
+
*
|
|
27
|
+
* What counts as an errno is a SET, not a pattern: the union of
|
|
28
|
+
* `os.constants.errno` across hosts, plus `EFTYPE` and `UNKNOWN` (libuv names Node
|
|
29
|
+
* does not export) and `EHOSTDOWN` (which Node on macOS omits). A name that merely
|
|
30
|
+
* looks like one (`EXAMPLE`) is not flagged, and `EOF` is not an errno, so it needs
|
|
31
|
+
* no special case.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately out of scope (each is a position this rule does not read):
|
|
34
|
+
* - helper-call arguments, `isErr(e, 'ENOENT')`: the callee is the private table,
|
|
35
|
+
* and judging arbitrary calls would flag every legitimate use of a name;
|
|
36
|
+
* - object literals, `{ code: 'ENOENT' }` / `{ ENOENT: 1 }`: those build or describe
|
|
37
|
+
* an error (fixtures, tables keyed by name), they do not classify one;
|
|
38
|
+
* - dynamic strings, built with a template expression or `+`: not decidable statically.
|
|
39
|
+
*
|
|
40
|
+
* Exempt: test files, and the files named by `exemptFiles` (the ones that must name
|
|
41
|
+
* errnos: the errno table, the git-stderr parser, the fault-injecting fs). No
|
|
42
|
+
* default exemption ships, so a consumer declares its own.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
EXEMPT_FILES_SCHEMA,
|
|
47
|
+
UNANCHORED_EXEMPT_FILE,
|
|
48
|
+
UNANCHORED_EXEMPT_MESSAGE,
|
|
49
|
+
createConfigurableExemptPathMatcher,
|
|
50
|
+
isTestFile,
|
|
51
|
+
reportUnanchoredExemptEntries,
|
|
52
|
+
} = require('./exempt-path-matcher.cjs');
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The errno vocabulary, spelled out so the set is identical on every host.
|
|
56
|
+
*
|
|
57
|
+
* `os.constants.errno` differs by platform (Linux exports names macOS omits, and
|
|
58
|
+
* macOS omits `EHOSTDOWN`), so reading it at lint time would make the rule pass on
|
|
59
|
+
* one CI leg and fail on another. A rule module also may not require any external
|
|
60
|
+
* module (`subpath-purity.test.ts`). So the union is written out here, and
|
|
61
|
+
* `no-adhoc-errno.test.ts` fails if this host's `os.constants.errno` has a key the
|
|
62
|
+
* list lacks. `EFTYPE` and `UNKNOWN` are libuv names Node does not export.
|
|
63
|
+
*/
|
|
64
|
+
const ERRNO_NAMES = new Set([
|
|
65
|
+
'E2BIG', 'EACCES', 'EADDRINUSE', 'EADDRNOTAVAIL', 'EAFNOSUPPORT', 'EAGAIN', 'EALREADY',
|
|
66
|
+
'EBADF', 'EBADMSG', 'EBUSY', 'ECANCELED', 'ECHILD', 'ECONNABORTED', 'ECONNREFUSED',
|
|
67
|
+
'ECONNRESET', 'EDEADLK', 'EDESTADDRREQ', 'EDOM', 'EDQUOT', 'EEXIST', 'EFAULT', 'EFBIG',
|
|
68
|
+
'EHOSTUNREACH', 'EIDRM', 'EILSEQ', 'EINPROGRESS', 'EINTR', 'EINVAL', 'EIO', 'EISCONN',
|
|
69
|
+
'EISDIR', 'ELOOP', 'EMFILE', 'EMLINK', 'EMSGSIZE', 'EMULTIHOP', 'ENAMETOOLONG', 'ENETDOWN',
|
|
70
|
+
'ENETRESET', 'ENETUNREACH', 'ENFILE', 'ENOBUFS', 'ENODATA', 'ENODEV', 'ENOENT', 'ENOEXEC',
|
|
71
|
+
'ENOLCK', 'ENOLINK', 'ENOMEM', 'ENOMSG', 'ENOPROTOOPT', 'ENOSPC', 'ENOSR', 'ENOSTR',
|
|
72
|
+
'ENOSYS', 'ENOTCONN', 'ENOTDIR', 'ENOTEMPTY', 'ENOTSOCK', 'ENOTSUP', 'ENOTTY', 'ENXIO',
|
|
73
|
+
'EOPNOTSUPP', 'EOVERFLOW', 'EPERM', 'EPIPE', 'EPROTO', 'EPROTONOSUPPORT', 'EPROTOTYPE',
|
|
74
|
+
'ERANGE', 'EROFS', 'ESPIPE', 'ESRCH', 'ESTALE', 'ETIME', 'ETIMEDOUT', 'ETXTBSY',
|
|
75
|
+
'EWOULDBLOCK', 'EXDEV',
|
|
76
|
+
// Linux-only names:
|
|
77
|
+
'EADV', 'EBADE', 'EBADFD', 'EBADR', 'EBADRQC', 'EBADSLT', 'EBFONT', 'ECHRNG', 'ECOMM',
|
|
78
|
+
'EDEADLOCK', 'EDOTDOT', 'EISNAM', 'EKEYEXPIRED', 'EKEYREJECTED', 'EKEYREVOKED', 'EL2HLT',
|
|
79
|
+
'EL2NSYNC', 'EL3HLT', 'EL3RST', 'ELIBACC', 'ELIBBAD', 'ELIBEXEC', 'ELIBMAX', 'ELIBSCN',
|
|
80
|
+
'ELNRNG', 'EMEDIUMTYPE', 'ENAVAIL', 'ENOANO', 'ENOKEY', 'ENOMEDIUM', 'ENONET', 'ENOPKG',
|
|
81
|
+
'ENOTBLK', 'ENOTNAM', 'ENOTRECOVERABLE', 'ENOTUNIQ', 'EOWNERDEAD', 'EPFNOSUPPORT', 'EREMCHG',
|
|
82
|
+
'EREMOTE', 'EREMOTEIO', 'ERESTART', 'ERFKILL', 'ESHUTDOWN', 'ESOCKTNOSUPPORT', 'ESRMNT',
|
|
83
|
+
'ESTRPIPE', 'ETOOMANYREFS', 'EUCLEAN', 'EUNATCH', 'EUSERS', 'EXFULL',
|
|
84
|
+
// Not in os.constants.errno on every host:
|
|
85
|
+
'EFTYPE', 'UNKNOWN', 'EHOSTDOWN',
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const COMPARISON_OPERATORS = new Set(['===', '!==', '==', '!=']);
|
|
89
|
+
const MEMBERSHIP_METHODS = new Set(['has', 'includes']);
|
|
90
|
+
/** Methods that make an INLINE array literal a lookup table: `['ENOENT'].includes(e.code)`. */
|
|
91
|
+
const ARRAY_LOOKUP_METHODS = new Set(['includes', 'some', 'indexOf', 'find', 'findIndex', 'every']);
|
|
92
|
+
const FUNCTION_TYPES = new Set(['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']);
|
|
93
|
+
const REFUSAL_CLASSES = new Set(['CommandRefusalError', 'VatError']);
|
|
94
|
+
const ERRNO_PREDICATES = new Set(['fsFaultOf', 'isPathAbsentError']);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The string a literal spells: a string `Literal`, or a template literal with no
|
|
98
|
+
* expressions (a backticked ENOENT is the same literal).
|
|
99
|
+
*
|
|
100
|
+
* @returns {string | undefined}
|
|
101
|
+
*/
|
|
102
|
+
function literalText(node) {
|
|
103
|
+
if (node?.type === 'Literal') {
|
|
104
|
+
return typeof node.value === 'string' ? node.value : undefined;
|
|
105
|
+
}
|
|
106
|
+
if (node?.type === 'TemplateLiteral' && node.expressions.length === 0 && node.quasis.length === 1) {
|
|
107
|
+
return node.quasis[0].value.cooked ?? undefined;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** @returns {boolean} True when `node` is a string literal naming an errno. */
|
|
113
|
+
function isErrnoLiteral(node) {
|
|
114
|
+
const text = literalText(node);
|
|
115
|
+
return text !== undefined && ERRNO_NAMES.has(text);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The declaring variable of an identifier, found by walking out through its scopes. */
|
|
119
|
+
function resolveVariable(sourceCode, node) {
|
|
120
|
+
for (let scope = sourceCode.getScope(node); scope; scope = scope.upper) {
|
|
121
|
+
const variable = scope.set.get(node.name);
|
|
122
|
+
if (variable) {
|
|
123
|
+
return variable;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** True when `node` is an identifier bound once, by `const`, to an errno literal. */
|
|
130
|
+
function isConstErrnoAlias(sourceCode, node) {
|
|
131
|
+
if (node.type !== 'Identifier') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
const variable = resolveVariable(sourceCode, node);
|
|
135
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : undefined;
|
|
136
|
+
return (
|
|
137
|
+
definition?.type === 'Variable' &&
|
|
138
|
+
definition.parent?.kind === 'const' &&
|
|
139
|
+
definition.node.id.type === 'Identifier' &&
|
|
140
|
+
isErrnoLiteral(unwrapExpression(definition.node.init))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Step over `as const`, `satisfies` and `!` wrappers to the expression inside. */
|
|
145
|
+
function unwrapExpression(node) {
|
|
146
|
+
let current = node;
|
|
147
|
+
while (
|
|
148
|
+
current &&
|
|
149
|
+
(current.type === 'TSAsExpression' ||
|
|
150
|
+
current.type === 'TSSatisfiesExpression' ||
|
|
151
|
+
current.type === 'TSNonNullExpression')
|
|
152
|
+
) {
|
|
153
|
+
current = current.expression;
|
|
154
|
+
}
|
|
155
|
+
return current;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The array literal's enclosing context: the thing that makes it a lookup table. */
|
|
159
|
+
function isLookupArray(arrayNode) {
|
|
160
|
+
// Climb through `as const` wrappers so `['ENOENT'] as const` is seen as bound.
|
|
161
|
+
let child = arrayNode;
|
|
162
|
+
let parent = arrayNode.parent;
|
|
163
|
+
while (parent && unwrapExpression(parent) !== parent && parent.expression === child) {
|
|
164
|
+
child = parent;
|
|
165
|
+
parent = parent.parent;
|
|
166
|
+
}
|
|
167
|
+
if (parent?.type === 'MemberExpression') {
|
|
168
|
+
return (
|
|
169
|
+
parent.object === child &&
|
|
170
|
+
!parent.computed &&
|
|
171
|
+
parent.property.type === 'Identifier' &&
|
|
172
|
+
ARRAY_LOOKUP_METHODS.has(parent.property.name)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (parent?.type === 'NewExpression') {
|
|
176
|
+
return parent.callee.type === 'Identifier' && parent.callee.name === 'Set' && parent.arguments[0] === child;
|
|
177
|
+
}
|
|
178
|
+
return (
|
|
179
|
+
parent?.type === 'VariableDeclarator' &&
|
|
180
|
+
parent.init === child &&
|
|
181
|
+
parent.parent?.type === 'VariableDeclaration' &&
|
|
182
|
+
parent.parent.kind === 'const'
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** @returns {boolean} True when the literal sits in one of the four classifying positions. */
|
|
187
|
+
function isClassifyingPosition(node) {
|
|
188
|
+
const parent = node.parent;
|
|
189
|
+
switch (parent?.type) {
|
|
190
|
+
case 'BinaryExpression':
|
|
191
|
+
return COMPARISON_OPERATORS.has(parent.operator);
|
|
192
|
+
case 'SwitchCase':
|
|
193
|
+
return parent.test === node;
|
|
194
|
+
case 'ArrayExpression':
|
|
195
|
+
return isLookupArray(parent);
|
|
196
|
+
case 'CallExpression':
|
|
197
|
+
return (
|
|
198
|
+
parent.arguments.includes(node) &&
|
|
199
|
+
parent.callee.type === 'MemberExpression' &&
|
|
200
|
+
!parent.callee.computed &&
|
|
201
|
+
parent.callee.property.type === 'Identifier' &&
|
|
202
|
+
MEMBERSHIP_METHODS.has(parent.callee.property.name)
|
|
203
|
+
);
|
|
204
|
+
default:
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Every AST child of `node`, skipping the back-pointer. */
|
|
210
|
+
function childNodes(node) {
|
|
211
|
+
const children = [];
|
|
212
|
+
for (const [key, value] of Object.entries(node)) {
|
|
213
|
+
if (key === 'parent') {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
217
|
+
if (item && typeof item.type === 'string') {
|
|
218
|
+
children.push(item);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return children;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Collect every node under `root` (inclusive) that `predicate` accepts, without
|
|
227
|
+
* descending into a nested catch or function: those judge their own bodies, so a
|
|
228
|
+
* re-wrap is reported once, by the catch that owns it.
|
|
229
|
+
*/
|
|
230
|
+
function collectNodes(root, predicate) {
|
|
231
|
+
const found = [];
|
|
232
|
+
const pending = [root];
|
|
233
|
+
while (pending.length > 0) {
|
|
234
|
+
const node = pending.pop();
|
|
235
|
+
if (predicate(node)) {
|
|
236
|
+
found.push(node);
|
|
237
|
+
}
|
|
238
|
+
if (node.type !== 'CatchClause' && !FUNCTION_TYPES.has(node.type)) {
|
|
239
|
+
pending.push(...childNodes(node));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return found;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const isIdentifier = (node, name) => node?.type === 'Identifier' && node.name === name;
|
|
246
|
+
|
|
247
|
+
/** `e.code`, `fsFaultOf(e)` or `isPathAbsentError(e)` for the catch parameter `name`. */
|
|
248
|
+
function inspectsErrno(node, name) {
|
|
249
|
+
if (node.type === 'MemberExpression') {
|
|
250
|
+
return (
|
|
251
|
+
isIdentifier(node.object, name) &&
|
|
252
|
+
((!node.computed && isIdentifier(node.property, 'code')) ||
|
|
253
|
+
(node.computed && node.property.type === 'Literal' && node.property.value === 'code'))
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return (
|
|
257
|
+
node.type === 'CallExpression' &&
|
|
258
|
+
node.callee.type === 'Identifier' &&
|
|
259
|
+
ERRNO_PREDICATES.has(node.callee.name) &&
|
|
260
|
+
isIdentifier(node.arguments[0], name)
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** `new CommandRefusalError(...)` / `new VatError(...)` carrying `cause: <name>`. */
|
|
265
|
+
function wrapsCause(node, name) {
|
|
266
|
+
return (
|
|
267
|
+
node.type === 'NewExpression' &&
|
|
268
|
+
node.callee.type === 'Identifier' &&
|
|
269
|
+
REFUSAL_CLASSES.has(node.callee.name) &&
|
|
270
|
+
node.arguments.some(
|
|
271
|
+
(argument) =>
|
|
272
|
+
argument.type === 'ObjectExpression' &&
|
|
273
|
+
argument.properties.some(
|
|
274
|
+
(property) =>
|
|
275
|
+
property.type === 'Property' &&
|
|
276
|
+
!property.computed &&
|
|
277
|
+
(isIdentifier(property.key, 'cause') || property.key.value === 'cause') &&
|
|
278
|
+
isIdentifier(property.value, name),
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
module.exports = {
|
|
285
|
+
meta: {
|
|
286
|
+
type: 'problem',
|
|
287
|
+
docs: {
|
|
288
|
+
description:
|
|
289
|
+
'Ban classifying a filesystem failure by hand: an errno name as a string in a comparison, case, Set/const array or .has()/.includes(), and a catch that inspects the errno then wraps it as a refusal cause',
|
|
290
|
+
category: 'Filesystem and process',
|
|
291
|
+
bans: 'an errno name (`ENOENT`, `EACCES`, …) as a string in a classifying position, and a catch that reads `e.code` then builds a refusal with `cause: e`',
|
|
292
|
+
useInstead: '`fsFaultOf(e)` / `classifyFsFault(e, …)`, or a single-errno predicate such as `isPathAbsentError(e)`',
|
|
293
|
+
// Not in `recommended`: the remedy names this repo's own classifier
|
|
294
|
+
// (`fsFaultOf`, `classifyFsFault`) and the refusal classes `CommandRefusalError` /
|
|
295
|
+
// `VatError`, none of which an adopter has. VAT enables it explicitly, naming
|
|
296
|
+
// the files that must spell errnos (the table, the git-stderr parser, the
|
|
297
|
+
// fault-injecting fs).
|
|
298
|
+
recommended: false,
|
|
299
|
+
recommendedSeverity: 'error',
|
|
300
|
+
},
|
|
301
|
+
fixable: null,
|
|
302
|
+
schema: [EXEMPT_FILES_SCHEMA],
|
|
303
|
+
messages: {
|
|
304
|
+
literal:
|
|
305
|
+
"Errno name '{{name}}' written as a string classifies a filesystem failure by hand. " +
|
|
306
|
+
'Ask the one classifier instead: fsFaultOf(e)?.faultClass, classifyFsFault(e, …), or a ' +
|
|
307
|
+
'single-errno predicate such as isPathAbsentError(e). A private errno table drifts from its neighbours.',
|
|
308
|
+
adhocRefusal:
|
|
309
|
+
'This catch inspects the errno ({{param}}.code / fsFaultOf / isPathAbsentError) and then builds a refusal ' +
|
|
310
|
+
'with cause: {{param}} by hand. Let classifyFsFault({{param}}, …) pick the refusal once.',
|
|
311
|
+
[UNANCHORED_EXEMPT_FILE]: UNANCHORED_EXEMPT_MESSAGE,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
create(context) {
|
|
316
|
+
const filename = context.getFilename();
|
|
317
|
+
|
|
318
|
+
if (createConfigurableExemptPathMatcher([])(context)(filename)) {
|
|
319
|
+
// Still surface a malformed exemption list: this file may be exempt only
|
|
320
|
+
// BECAUSE an entry is unanchored.
|
|
321
|
+
return {
|
|
322
|
+
Program(node) {
|
|
323
|
+
reportUnanchoredExemptEntries(context, node);
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (isTestFile(filename)) {
|
|
329
|
+
return {};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
Program(node) {
|
|
334
|
+
reportUnanchoredExemptEntries(context, node);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
'Literal, TemplateLiteral'(node) {
|
|
338
|
+
if (isErrnoLiteral(node) && isClassifyingPosition(node)) {
|
|
339
|
+
context.report({ node, messageId: 'literal', data: { name: literalText(node) } });
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
// `const C = 'ENOENT'; e.code === C`: flagged at the use, where the check is.
|
|
344
|
+
Identifier(node) {
|
|
345
|
+
if (isClassifyingPosition(node) && isConstErrnoAlias(context.sourceCode, node)) {
|
|
346
|
+
context.report({ node, messageId: 'literal', data: { name: node.name } });
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
CatchClause(node) {
|
|
351
|
+
if (node.param?.type !== 'Identifier') {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const param = node.param.name;
|
|
355
|
+
if (collectNodes(node.body, (child) => inspectsErrno(child, param)).length === 0) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
for (const wrapped of collectNodes(node.body, (child) => wrapsCause(child, param))) {
|
|
359
|
+
context.report({ node: wrapped, messageId: 'adhocRefusal', data: { param } });
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
};
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* production code at it would be worse advice than the bare call. Prefer a
|
|
12
12
|
* junction for a directory link on win32, or catch the failure and name the
|
|
13
13
|
* missing privilege. An `eslint-disable` justification is the sanctioned way
|
|
14
|
-
* to say "this
|
|
15
|
-
*
|
|
16
|
-
* on an unprivileged Windows and fails saying exactly that.
|
|
14
|
+
* to say "this is the guarded form" — see `dev-tools/src/link-workspace-packages.ts`,
|
|
15
|
+
* which makes a junction on win32 and names the package a failure was for.
|
|
17
16
|
*
|
|
18
17
|
* ⚠️ **The name is now narrower than the rule.** It covers shipped code too;
|
|
19
18
|
* renaming it is a public-API change to `@vibe-agent-toolkit/utils/eslint` and
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* The rule is a FLOOR, deliberately syntactic: the clause is fine if either
|
|
24
24
|
*
|
|
25
25
|
* 1. the error binding is REFERENCED anywhere in the body — narrowing on it
|
|
26
|
-
* (`
|
|
26
|
+
* (`fsFaultOf(e)`, `e instanceof X`, `e.code === 'ENOENT'`),
|
|
27
27
|
* carrying it into a report (`errors.push(String(e))`, `{ ok: false, error }`),
|
|
28
28
|
* or logging it — or
|
|
29
29
|
* 2. the body THROWS (a rethrow, or a translation into a louder error), at the
|