@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.11
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 +2 -2
- package/dist/env-flag.d.ts +11 -5
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +11 -5
- package/dist/env-flag.js.map +1 -1
- package/dist/fs.d.ts +2 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +2 -1
- package/dist/fs.js.map +1 -1
- package/dist/path-containment.d.ts +15 -0
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +17 -0
- package/dist/path-containment.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 +45 -6
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +78 -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/eslint/rules/no-literal-process-exit.cjs +109 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,10 +25,10 @@ The last two columns are the ones that matter when choosing. **"Resolves with ze
|
|
|
25
25
|
| Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
|
|
26
26
|
|---|---|---|---|---|
|
|
27
27
|
| `./path` | `safePath`, `toForwardSlash`, `toForwardSlashAnyPlatform`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `relativeEscapesRoot`, `isSingleFsSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
28
|
-
| `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted | **none** | — | **yes** |
|
|
28
|
+
| `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted. `TextTooLargeError` (`TEXT_TOO_LARGE`) when the engine refuses a string that long | **none** | — | **yes** |
|
|
29
29
|
| `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
|
|
30
30
|
| `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
|
|
31
|
-
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillPathSpellings`, `pathSpellingFrom`, `DirectorySpellingIndex`, `spellingWalkRoot`, `fillRealpaths`, `realpathFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync` | `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
|
|
31
|
+
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillPathSpellings`, `pathSpellingFrom`, `DirectorySpellingIndex`, `spellingWalkRoot`, `fillRealpaths`, `realpathFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync`, `readDecodableBytes` (refuses a file past `MAX_DECODABLE_BYTES` by `fstat`, before reading it), `exceedsDecodableLength`, `MAX_DECODABLE_BYTES`, `TextTooLargeError` | `buffer`, `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
|
|
32
32
|
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `createTempDir`, `removeTempDir`, `tempDirTracker`, `CANNOT_DENY_READS`, `buildHostileTree`, `HOSTILE_NAMES`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
33
33
|
| `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
|
|
34
34
|
| `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
|
package/dist/env-flag.d.ts
CHANGED
|
@@ -20,21 +20,27 @@
|
|
|
20
20
|
* needs it rather than speculatively. That second package arrived:
|
|
21
21
|
* `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
|
|
22
22
|
* `resources` parse cache does, and two independent readings of one variable is
|
|
23
|
-
* the defect, not the fix. The
|
|
23
|
+
* the defect, not the fix. The consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
|
-
* | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
27
|
+
* | `resources/link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
28
28
|
* | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
|
|
29
29
|
* | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
|
|
30
30
|
* | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
|
|
31
|
+
* | `resources/parse-dispatcher.ts` | `VAT_PARSE_POOL` | pool stays on | gates a measured speed-up — only an explicit off spelling disables it; a typo must not silently cost it |
|
|
31
32
|
*
|
|
32
33
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
33
34
|
* the contract; do not move a default in here.
|
|
34
35
|
*
|
|
35
|
-
* 🔑 The
|
|
36
|
-
* the other defaults ON, and both are still read through this
|
|
37
|
-
* because what it answers is not "is this on" but "did the
|
|
36
|
+
* 🔑 The two `projection-store.ts` rows are the interesting pair: one variable
|
|
37
|
+
* defaults OFF and the other defaults ON, and both are still read through this
|
|
38
|
+
* one function, because what it answers is not "is this on" but "did the
|
|
39
|
+
* operator say no".
|
|
40
|
+
*
|
|
41
|
+
* The table is asserted both ways, per (file, variable), against every
|
|
42
|
+
* `parseEnvBoolean` call (aliased imports included) in every package's `src` by
|
|
43
|
+
* `test/env-flag.test.ts`.
|
|
38
44
|
*/
|
|
39
45
|
/**
|
|
40
46
|
* Parse an env value as a boolean.
|
package/dist/env-flag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
|
package/dist/env-flag.js
CHANGED
|
@@ -20,21 +20,27 @@
|
|
|
20
20
|
* needs it rather than speculatively. That second package arrived:
|
|
21
21
|
* `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
|
|
22
22
|
* `resources` parse cache does, and two independent readings of one variable is
|
|
23
|
-
* the defect, not the fix. The
|
|
23
|
+
* the defect, not the fix. The consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
|
-
* | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
27
|
+
* | `resources/link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
28
28
|
* | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
|
|
29
29
|
* | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
|
|
30
30
|
* | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
|
|
31
|
+
* | `resources/parse-dispatcher.ts` | `VAT_PARSE_POOL` | pool stays on | gates a measured speed-up — only an explicit off spelling disables it; a typo must not silently cost it |
|
|
31
32
|
*
|
|
32
33
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
33
34
|
* the contract; do not move a default in here.
|
|
34
35
|
*
|
|
35
|
-
* 🔑 The
|
|
36
|
-
* the other defaults ON, and both are still read through this
|
|
37
|
-
* because what it answers is not "is this on" but "did the
|
|
36
|
+
* 🔑 The two `projection-store.ts` rows are the interesting pair: one variable
|
|
37
|
+
* defaults OFF and the other defaults ON, and both are still read through this
|
|
38
|
+
* one function, because what it answers is not "is this on" but "did the
|
|
39
|
+
* operator say no".
|
|
40
|
+
*
|
|
41
|
+
* The table is asserted both ways, per (file, variable), against every
|
|
42
|
+
* `parseEnvBoolean` call (aliased imports included) in every package's `src` by
|
|
43
|
+
* `test/env-flag.test.ts`.
|
|
38
44
|
*/
|
|
39
45
|
const TRUE_SPELLINGS = new Set(['1', 'true', 'yes', 'y', 'on']);
|
|
40
46
|
const FALSE_SPELLINGS = new Set(['0', 'false', 'no', 'n', 'off']);
|
package/dist/env-flag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AACrF,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/fs.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* `@vibe-agent-toolkit/utils/path` instead — it is dramatically cheaper.
|
|
9
9
|
*/
|
|
10
10
|
export { normalizePath, normalizedTmpdir, mkdirSyncReal, resolveFromImportMeta, dynamicImportPath, } from './path-utils.js';
|
|
11
|
-
export { readTextContent, readTextContentSync } from './text-file.js';
|
|
11
|
+
export { exceedsDecodableLength, MAX_DECODABLE_BYTES, readDecodableBytes, readTextContent, readTextContentSync, } from './text-file.js';
|
|
12
|
+
export { TextTooLargeError } from './text-content.js';
|
|
12
13
|
export type { DecodedText, EncodingSource, TextEncoding, TextProvenance } from './text-content.js';
|
|
13
14
|
export { isFilesystemAccessError, isPathAbsentError } from './errors/errno.js';
|
|
14
15
|
export { copyDirectory, DirectorySpellingIndex, fillPathSpellings, fillRealpaths, FsLookupCache, pathSpellingFrom, realpathFrom, spellingWalkRoot, } from './fs-utils.js';
|
package/dist/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKnG,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,GACd,MAAM,eAAe,CAAC"}
|
package/dist/fs.js
CHANGED
|
@@ -11,7 +11,8 @@ export { normalizePath, normalizedTmpdir, mkdirSyncReal, resolveFromImportMeta,
|
|
|
11
11
|
// Read a file and decode it through the one content-decoding seam. The DECISION
|
|
12
12
|
// about what the bytes say is pure and lives on `./text`; these two are that
|
|
13
13
|
// plus a `readFile`, which is why they are here and not there.
|
|
14
|
-
export { readTextContent, readTextContentSync } from './text-file.js';
|
|
14
|
+
export { exceedsDecodableLength, MAX_DECODABLE_BYTES, readDecodableBytes, readTextContent, readTextContentSync, } from './text-file.js';
|
|
15
|
+
export { TextTooLargeError } from './text-content.js';
|
|
15
16
|
// Two materialized columns, each a fill+judge pair and nothing else. In both the
|
|
16
17
|
// row IS the answer, so the row lookup — `pathSpellingFrom`, `realpathFrom` — is
|
|
17
18
|
// itself the judge and is exported. See `index.ts` for the full reasoning.
|
package/dist/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,6EAA6E;AAC7E,+DAA+D;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,6EAA6E;AAC7E,+DAA+D;AAC/D,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,iFAAiF;AACjF,iFAAiF;AACjF,2EAA2E;AAC3E,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,eAAe,CAAC"}
|
|
@@ -34,6 +34,21 @@
|
|
|
34
34
|
* inside the root. A create sink proceeds; a delete sink has nothing to do.
|
|
35
35
|
*/
|
|
36
36
|
export type Containment = 'inside' | 'outside' | 'absent';
|
|
37
|
+
/**
|
|
38
|
+
* The canonical (realpath) spelling of `target`, whether or not it exists.
|
|
39
|
+
*
|
|
40
|
+
* The same canonicalization {@link isUnderRoot} applies to both of its sides,
|
|
41
|
+
* exposed for a caller that must COMPARE two spellings rather than ask a
|
|
42
|
+
* containment question — a root discovered from the physical `process.cwd()`
|
|
43
|
+
* against a path the operator typed through a symlink (macOS `/tmp` →
|
|
44
|
+
* `/private/tmp`). A missing target is canonicalized from its deepest existing
|
|
45
|
+
* ancestor with the missing tail re-appended.
|
|
46
|
+
*
|
|
47
|
+
* @param target - Absolute, or relative to cwd; either separator
|
|
48
|
+
* @returns The canonical spelling, forward-slashed
|
|
49
|
+
* @throws When the OS refuses to examine the path for any reason but absence
|
|
50
|
+
*/
|
|
51
|
+
export declare function canonicalPath(target: string): string;
|
|
37
52
|
/**
|
|
38
53
|
* Is `candidate` strictly under `root`, as the filesystem judges it?
|
|
39
54
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAuC1D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAQxE"}
|
package/dist/path-containment.js
CHANGED
|
@@ -61,6 +61,23 @@ function canonicalFromAncestor(target) {
|
|
|
61
61
|
candidate = parent;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* The canonical (realpath) spelling of `target`, whether or not it exists.
|
|
66
|
+
*
|
|
67
|
+
* The same canonicalization {@link isUnderRoot} applies to both of its sides,
|
|
68
|
+
* exposed for a caller that must COMPARE two spellings rather than ask a
|
|
69
|
+
* containment question — a root discovered from the physical `process.cwd()`
|
|
70
|
+
* against a path the operator typed through a symlink (macOS `/tmp` →
|
|
71
|
+
* `/private/tmp`). A missing target is canonicalized from its deepest existing
|
|
72
|
+
* ancestor with the missing tail re-appended.
|
|
73
|
+
*
|
|
74
|
+
* @param target - Absolute, or relative to cwd; either separator
|
|
75
|
+
* @returns The canonical spelling, forward-slashed
|
|
76
|
+
* @throws When the OS refuses to examine the path for any reason but absence
|
|
77
|
+
*/
|
|
78
|
+
export function canonicalPath(target) {
|
|
79
|
+
return canonicalFromAncestor(target).canonical;
|
|
80
|
+
}
|
|
64
81
|
/** Whether an entry (a file, a directory, or a link — dangling or not) is at `p`. */
|
|
65
82
|
function entryExists(p) {
|
|
66
83
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgB1E;;;;;;;;;;;;;;GAcG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC;gBAC9E,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;aAC7B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,SAAS,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7C,CAAC"}
|
|
1
|
+
{"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgB1E;;;;;;;;;;;;;;GAcG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC;gBAC9E,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;aAC7B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,SAAS,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7C,CAAC"}
|
package/dist/text-content.d.ts
CHANGED
|
@@ -130,6 +130,34 @@
|
|
|
130
130
|
* NOT shift is the content key, which is computed over the raw bytes; see
|
|
131
131
|
* `resources`' `content-key.ts`.
|
|
132
132
|
*/
|
|
133
|
+
import { VatError } from './errors/vat-error.js';
|
|
134
|
+
/**
|
|
135
|
+
* Thrown when bytes are too many to become one JS string — the engine's refusal,
|
|
136
|
+
* made a coded error so a caller can treat it as a fact about a file.
|
|
137
|
+
*
|
|
138
|
+
* The limit is the engine's, not VAT's: V8 caps a string at
|
|
139
|
+
* `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
|
|
140
|
+
* decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
|
|
141
|
+
* whole projection population on that throw.
|
|
142
|
+
*
|
|
143
|
+
* Two places raise it, and they agree by construction rather than by a shared
|
|
144
|
+
* literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
|
|
145
|
+
* by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
|
|
146
|
+
* never pulled into memory only to be refused. {@link decodeTextContent} — pure,
|
|
147
|
+
* so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
|
|
148
|
+
* translates the engine's own refusal for bytes that arrive by any other route.
|
|
149
|
+
* Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
|
|
150
|
+
*/
|
|
151
|
+
export declare class TextTooLargeError extends VatError {
|
|
152
|
+
readonly byteLength: number;
|
|
153
|
+
/** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
|
|
154
|
+
static readonly code = "TEXT_TOO_LARGE";
|
|
155
|
+
/**
|
|
156
|
+
* @param byteLength - How many bytes the refused content holds
|
|
157
|
+
* @param options - `cause`, when an engine refusal is being translated
|
|
158
|
+
*/
|
|
159
|
+
constructor(byteLength: number, options?: ErrorOptions);
|
|
160
|
+
}
|
|
133
161
|
/** An encoding this module can decode. */
|
|
134
162
|
export type TextEncoding = 'utf-8' | 'utf-16le' | 'utf-16be' | 'utf-32le' | 'utf-32be';
|
|
135
163
|
/**
|
|
@@ -184,6 +212,7 @@ export interface DecodedText extends TextProvenance {
|
|
|
184
212
|
* @param bytes - The exact bytes read from disk
|
|
185
213
|
* @returns The decoded text, the encoding used, whether that was a fact, and how
|
|
186
214
|
* many characters the decode had to replace
|
|
215
|
+
* @throws {@link TextTooLargeError} when the engine refuses a string that long
|
|
187
216
|
*
|
|
188
217
|
* @example
|
|
189
218
|
* ```typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD
|
|
1
|
+
{"version":3,"file":"text-content.d.ts","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAkB,SAAQ,QAAQ;IAS3C,QAAQ,CAAC,UAAU,EAAE,MAAM;IAR7B,wFAAwF;IACxF,MAAM,CAAC,QAAQ,CAAC,IAAI,oBAAoB;IAExC;;;OAGG;gBAEQ,UAAU,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE,YAAY;CAQzB;AAkBD,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,yBAAyB;IACzB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAkLD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAOhE"}
|
package/dist/text-content.js
CHANGED
|
@@ -130,6 +130,51 @@
|
|
|
130
130
|
* NOT shift is the content key, which is computed over the raw bytes; see
|
|
131
131
|
* `resources`' `content-key.ts`.
|
|
132
132
|
*/
|
|
133
|
+
import { VatError } from './errors/vat-error.js';
|
|
134
|
+
/**
|
|
135
|
+
* Thrown when bytes are too many to become one JS string — the engine's refusal,
|
|
136
|
+
* made a coded error so a caller can treat it as a fact about a file.
|
|
137
|
+
*
|
|
138
|
+
* The limit is the engine's, not VAT's: V8 caps a string at
|
|
139
|
+
* `buffer.constants.MAX_STRING_LENGTH` (0x1fffffe8) UTF-16 code units, and
|
|
140
|
+
* decoding past it throws. One near-gigabyte CSV in a corpus used to abort a
|
|
141
|
+
* whole projection population on that throw.
|
|
142
|
+
*
|
|
143
|
+
* Two places raise it, and they agree by construction rather than by a shared
|
|
144
|
+
* literal. The `./fs` read path (`readDecodableBytes` in `text-file.ts`) refuses
|
|
145
|
+
* by `stat` BEFORE reading, against `MAX_DECODABLE_BYTES`, so half a gigabyte is
|
|
146
|
+
* never pulled into memory only to be refused. {@link decodeTextContent} — pure,
|
|
147
|
+
* so it cannot ask `node:buffer` for the limit (`subpath-purity.test.ts`) —
|
|
148
|
+
* translates the engine's own refusal for bytes that arrive by any other route.
|
|
149
|
+
* Dispatch with `isVatError(error, TextTooLargeError.code)`, never on the message.
|
|
150
|
+
*/
|
|
151
|
+
export class TextTooLargeError extends VatError {
|
|
152
|
+
byteLength;
|
|
153
|
+
/** The code every instance carries, for `isVatError(error, TextTooLargeError.code)`. */
|
|
154
|
+
static code = 'TEXT_TOO_LARGE';
|
|
155
|
+
/**
|
|
156
|
+
* @param byteLength - How many bytes the refused content holds
|
|
157
|
+
* @param options - `cause`, when an engine refusal is being translated
|
|
158
|
+
*/
|
|
159
|
+
constructor(byteLength, options) {
|
|
160
|
+
super(TextTooLargeError.code, `${byteLength} bytes is too large to decode into a JavaScript string`, options);
|
|
161
|
+
this.byteLength = byteLength;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The code Node's `TextDecoder` attaches when the decoded string would pass the
|
|
166
|
+
* engine's string-length limit. Matched as a `code`, never as its message.
|
|
167
|
+
*/
|
|
168
|
+
const ENGINE_STRING_TOO_LONG = 'ERR_STRING_TOO_LONG';
|
|
169
|
+
/**
|
|
170
|
+
* Whether `error` is the engine refusing to build a string that long.
|
|
171
|
+
*
|
|
172
|
+
* @param error - What a decode threw
|
|
173
|
+
* @returns True for Node's `ERR_STRING_TOO_LONG`
|
|
174
|
+
*/
|
|
175
|
+
function isEngineStringTooLong(error) {
|
|
176
|
+
return error instanceof Error && error.code === ENGINE_STRING_TOO_LONG;
|
|
177
|
+
}
|
|
133
178
|
/**
|
|
134
179
|
* Byte-order marks, **longest first**.
|
|
135
180
|
*
|
|
@@ -308,6 +353,7 @@ function countReplacementCharacters(text) {
|
|
|
308
353
|
* @param bytes - The exact bytes read from disk
|
|
309
354
|
* @returns The decoded text, the encoding used, whether that was a fact, and how
|
|
310
355
|
* many characters the decode had to replace
|
|
356
|
+
* @throws {@link TextTooLargeError} when the engine refuses a string that long
|
|
311
357
|
*
|
|
312
358
|
* @example
|
|
313
359
|
* ```typescript
|
|
@@ -318,6 +364,24 @@ function countReplacementCharacters(text) {
|
|
|
318
364
|
* ```
|
|
319
365
|
*/
|
|
320
366
|
export function decodeTextContent(bytes) {
|
|
367
|
+
try {
|
|
368
|
+
return decodeWithinEngineLimits(bytes);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (isEngineStringTooLong(error))
|
|
372
|
+
throw new TextTooLargeError(bytes.byteLength, { cause: error });
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* {@link decodeTextContent} without the translation of the engine's
|
|
378
|
+
* string-length refusal — split out so the translation wraps BOTH decodes, the
|
|
379
|
+
* fatal first pass and the lenient retry, either of which can reach the limit.
|
|
380
|
+
*
|
|
381
|
+
* @param bytes - The exact bytes read from disk
|
|
382
|
+
* @returns The decoded text and its provenance
|
|
383
|
+
*/
|
|
384
|
+
function decodeWithinEngineLimits(bytes) {
|
|
321
385
|
const bom = bomAt(bytes);
|
|
322
386
|
const encoding = bom?.encoding ?? 'utf-8';
|
|
323
387
|
const body = bom === null ? bytes : bytes.subarray(bom.length);
|
package/dist/text-content.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmIG;
|
|
1
|
+
{"version":3,"file":"text-content.js","sourceRoot":"","sources":["../src/text-content.ts"],"names":[],"mappings":"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"}
|
package/dist/text-file.d.ts
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* it worse rather than more consistent.
|
|
15
15
|
*
|
|
16
16
|
* A caller that needs the RAW bytes as well as the text — to hash them, key
|
|
17
|
-
* them, or report `stat().size` — must not use these: read the bytes once
|
|
18
|
-
* and call `decodeTextContent` on them, so the digest
|
|
19
|
-
* from the same read. `readContentWithKey` in
|
|
20
|
-
* that caller and is shaped exactly that way.
|
|
17
|
+
* them, or report `stat().size` — must not use these: read the bytes once with
|
|
18
|
+
* {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
|
|
19
|
+
* and the characters come from the same read. `readContentWithKey` in
|
|
20
|
+
* `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
|
|
21
21
|
*/
|
|
22
22
|
import { type DecodedText } from './text-content.js';
|
|
23
23
|
/**
|
|
@@ -25,7 +25,8 @@ import { type DecodedText } from './text-content.js';
|
|
|
25
25
|
*
|
|
26
26
|
* @param filePath - Path to read
|
|
27
27
|
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
28
|
-
* @throws Whatever `readFile` throws — callers decide whether that is fatal
|
|
28
|
+
* @throws Whatever `readFile` throws — callers decide whether that is fatal — and
|
|
29
|
+
* {@link TextTooLargeError} for a file too large to decode, before reading it
|
|
29
30
|
*
|
|
30
31
|
* @example
|
|
31
32
|
* ```typescript
|
|
@@ -39,7 +40,45 @@ export declare function readTextContent(filePath: string): Promise<DecodedText>;
|
|
|
39
40
|
*
|
|
40
41
|
* @param filePath - Path to read
|
|
41
42
|
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
42
|
-
* @throws Whatever `readFileSync` throws
|
|
43
|
+
* @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
|
|
43
44
|
*/
|
|
44
45
|
export declare function readTextContentSync(filePath: string): DecodedText;
|
|
46
|
+
/**
|
|
47
|
+
* The most bytes that can be decoded into one JS string: the engine's own
|
|
48
|
+
* string-length limit, read from `node:buffer` rather than written down.
|
|
49
|
+
*
|
|
50
|
+
* A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
|
|
51
|
+
* (a UTF-8 byte never yields more than one code unit) and conservative for
|
|
52
|
+
* UTF-16/UTF-32, whose bytes yield half or a quarter as many.
|
|
53
|
+
*/
|
|
54
|
+
export declare const MAX_DECODABLE_BYTES: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether content of this many bytes is past what can be decoded to a string.
|
|
57
|
+
*
|
|
58
|
+
* @param byteLength - The content's size in bytes, as `stat` reports it
|
|
59
|
+
* @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
|
|
60
|
+
*/
|
|
61
|
+
export declare function exceedsDecodableLength(byteLength: number): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Read a file's raw bytes for decoding — refusing, by `stat` and before reading
|
|
64
|
+
* a byte, one too large to decode at all.
|
|
65
|
+
*
|
|
66
|
+
* The one read path for "bytes that are about to go through
|
|
67
|
+
* {@link decodeTextContent}": {@link readTextContent} uses it, and so does
|
|
68
|
+
* `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
|
|
69
|
+
* bytes as well to key them. Without the stat, a 966 MB file is read whole and
|
|
70
|
+
* only then refused by the decoder, and past 2 GiB `readFile` fails first with
|
|
71
|
+
* its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
|
|
72
|
+
*
|
|
73
|
+
* The size comes from `fstat` on the handle that is then read, not from a
|
|
74
|
+
* separate `stat` of the path, so a rename between the two cannot substitute
|
|
75
|
+
* another file. A file that GROWS past the limit after the `fstat` still reaches
|
|
76
|
+
* the decoder, which raises the same {@link TextTooLargeError}.
|
|
77
|
+
*
|
|
78
|
+
* @param filePath - Path to read
|
|
79
|
+
* @returns The file's bytes
|
|
80
|
+
* @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
|
|
81
|
+
* and whatever `open`/`readFile` throw otherwise
|
|
82
|
+
*/
|
|
83
|
+
export declare function readDecodableBytes(filePath: string): Promise<Buffer>;
|
|
45
84
|
//# sourceMappingURL=text-file.d.ts.map
|
package/dist/text-file.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAQjE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAoC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAElE;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1E"}
|
package/dist/text-file.js
CHANGED
|
@@ -14,20 +14,22 @@
|
|
|
14
14
|
* it worse rather than more consistent.
|
|
15
15
|
*
|
|
16
16
|
* A caller that needs the RAW bytes as well as the text — to hash them, key
|
|
17
|
-
* them, or report `stat().size` — must not use these: read the bytes once
|
|
18
|
-
* and call `decodeTextContent` on them, so the digest
|
|
19
|
-
* from the same read. `readContentWithKey` in
|
|
20
|
-
* that caller and is shaped exactly that way.
|
|
17
|
+
* them, or report `stat().size` — must not use these: read the bytes once with
|
|
18
|
+
* {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
|
|
19
|
+
* and the characters come from the same read. `readContentWithKey` in
|
|
20
|
+
* `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
|
|
21
21
|
*/
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
22
|
+
import { constants } from 'node:buffer';
|
|
23
|
+
import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { open } from 'node:fs/promises';
|
|
25
|
+
import { decodeTextContent, TextTooLargeError } from './text-content.js';
|
|
25
26
|
/**
|
|
26
27
|
* Read a file and decode it through {@link decodeTextContent}.
|
|
27
28
|
*
|
|
28
29
|
* @param filePath - Path to read
|
|
29
30
|
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
30
|
-
* @throws Whatever `readFile` throws — callers decide whether that is fatal
|
|
31
|
+
* @throws Whatever `readFile` throws — callers decide whether that is fatal — and
|
|
32
|
+
* {@link TextTooLargeError} for a file too large to decode, before reading it
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
35
|
* ```typescript
|
|
@@ -36,16 +38,81 @@ import { decodeTextContent } from './text-content.js';
|
|
|
36
38
|
* ```
|
|
37
39
|
*/
|
|
38
40
|
export async function readTextContent(filePath) {
|
|
39
|
-
return decodeTextContent(await
|
|
41
|
+
return decodeTextContent(await readDecodableBytes(filePath));
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* {@link readTextContent}, synchronously.
|
|
43
45
|
*
|
|
44
46
|
* @param filePath - Path to read
|
|
45
47
|
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
46
|
-
* @throws Whatever `readFileSync` throws
|
|
48
|
+
* @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
|
|
47
49
|
*/
|
|
48
50
|
export function readTextContentSync(filePath) {
|
|
49
|
-
|
|
51
|
+
const handle = openSync(filePath, 'r');
|
|
52
|
+
try {
|
|
53
|
+
refuseUndecodableSize(fstatSync(handle).size);
|
|
54
|
+
return decodeTextContent(readFileSync(handle));
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
closeSync(handle);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The most bytes that can be decoded into one JS string: the engine's own
|
|
62
|
+
* string-length limit, read from `node:buffer` rather than written down.
|
|
63
|
+
*
|
|
64
|
+
* A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
|
|
65
|
+
* (a UTF-8 byte never yields more than one code unit) and conservative for
|
|
66
|
+
* UTF-16/UTF-32, whose bytes yield half or a quarter as many.
|
|
67
|
+
*/
|
|
68
|
+
export const MAX_DECODABLE_BYTES = constants.MAX_STRING_LENGTH;
|
|
69
|
+
/**
|
|
70
|
+
* Whether content of this many bytes is past what can be decoded to a string.
|
|
71
|
+
*
|
|
72
|
+
* @param byteLength - The content's size in bytes, as `stat` reports it
|
|
73
|
+
* @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
|
|
74
|
+
*/
|
|
75
|
+
export function exceedsDecodableLength(byteLength) {
|
|
76
|
+
return byteLength > MAX_DECODABLE_BYTES;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Throw {@link TextTooLargeError} for a size no decode can hold.
|
|
80
|
+
*
|
|
81
|
+
* @param byteLength - The content's size in bytes
|
|
82
|
+
*/
|
|
83
|
+
function refuseUndecodableSize(byteLength) {
|
|
84
|
+
if (exceedsDecodableLength(byteLength))
|
|
85
|
+
throw new TextTooLargeError(byteLength);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read a file's raw bytes for decoding — refusing, by `stat` and before reading
|
|
89
|
+
* a byte, one too large to decode at all.
|
|
90
|
+
*
|
|
91
|
+
* The one read path for "bytes that are about to go through
|
|
92
|
+
* {@link decodeTextContent}": {@link readTextContent} uses it, and so does
|
|
93
|
+
* `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
|
|
94
|
+
* bytes as well to key them. Without the stat, a 966 MB file is read whole and
|
|
95
|
+
* only then refused by the decoder, and past 2 GiB `readFile` fails first with
|
|
96
|
+
* its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
|
|
97
|
+
*
|
|
98
|
+
* The size comes from `fstat` on the handle that is then read, not from a
|
|
99
|
+
* separate `stat` of the path, so a rename between the two cannot substitute
|
|
100
|
+
* another file. A file that GROWS past the limit after the `fstat` still reaches
|
|
101
|
+
* the decoder, which raises the same {@link TextTooLargeError}.
|
|
102
|
+
*
|
|
103
|
+
* @param filePath - Path to read
|
|
104
|
+
* @returns The file's bytes
|
|
105
|
+
* @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
|
|
106
|
+
* and whatever `open`/`readFile` throw otherwise
|
|
107
|
+
*/
|
|
108
|
+
export async function readDecodableBytes(filePath) {
|
|
109
|
+
const handle = await open(filePath, 'r');
|
|
110
|
+
try {
|
|
111
|
+
refuseUndecodableSize((await handle.stat()).size);
|
|
112
|
+
return await handle.readFile();
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await handle.close();
|
|
116
|
+
}
|
|
50
117
|
}
|
|
51
118
|
//# sourceMappingURL=text-file.js.map
|
package/dist/text-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-file.js","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"text-file.js","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,OAAO,iBAAiB,CAAC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAW,SAAS,CAAC,iBAAiB,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,UAAU,GAAG,mBAAmB,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,IAAI,sBAAsB,CAAC,UAAU,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,qBAAqB,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
|
package/dist/text.d.ts
CHANGED
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
* `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
|
|
13
13
|
* `readFile`.
|
|
14
14
|
*/
|
|
15
|
-
export { decodeTextContent, type DecodedText, type EncodingSource, type TextEncoding, type TextProvenance, } from './text-content.js';
|
|
15
|
+
export { decodeTextContent, type DecodedText, type EncodingSource, type TextEncoding, type TextProvenance, TextTooLargeError, } from './text-content.js';
|
|
16
16
|
//# sourceMappingURL=text.d.ts.map
|
package/dist/text.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
package/dist/text.js
CHANGED
|
@@ -12,5 +12,5 @@
|
|
|
12
12
|
* `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
|
|
13
13
|
* `readFile`.
|
|
14
14
|
*/
|
|
15
|
-
export { decodeTextContent, } from './text-content.js';
|
|
15
|
+
export { decodeTextContent, TextTooLargeError, } from './text-content.js';
|
|
16
16
|
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EAKjB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
|
@@ -20,17 +20,69 @@
|
|
|
20
20
|
* segment boundary via `exempt-path-matcher.cjs`; give the full repo-relative
|
|
21
21
|
* path, not a bare basename.
|
|
22
22
|
*
|
|
23
|
+
* ## Option `derived` — the code is DERIVED from the document, not chosen
|
|
24
|
+
*
|
|
25
|
+
* Naming a member is not enough. `process.exit(refused ? ExitCode.FINDINGS :
|
|
26
|
+
* ExitCode.OK)` names members and still re-decides, beside the document, what
|
|
27
|
+
* the document means — and one outcome shipped with different codes in
|
|
28
|
+
* different verbs that way (a budget kill 1-or-2 by timing; an unreadable root
|
|
29
|
+
* 2 in one verb and 1 in another). Under `derived.paths`:
|
|
30
|
+
*
|
|
31
|
+
* - `ExitCode.FINDINGS` may not be NAMED at all. "What it examined failed its
|
|
32
|
+
* gate" is a claim about a published document, so it comes from
|
|
33
|
+
* `exitCodeForReport(document)` (`@vibe-agent-toolkit/schema`) and nowhere else.
|
|
34
|
+
* - `process.exit(x)` / `process.exitCode = x` takes `ExitCode.OK`,
|
|
35
|
+
* `ExitCode.ERROR`, or a call to one of `derived.calls` (the derivations:
|
|
36
|
+
* `exitCodeForReport`, `exitCodeOfChild`, …). A ternary, a variable or a
|
|
37
|
+
* forwarded field is a code decided somewhere this rule cannot see.
|
|
38
|
+
* - `derived.legacy` names the files not yet migrated. It is a RATCHET asserted
|
|
39
|
+
* both ways: a listed file that no longer decides a code by hand is itself an
|
|
40
|
+
* error (`staleLegacy`), so the list can only shrink and never holds a dead entry.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ What this cannot see: a document verb ending on a bare `ExitCode.OK` over a
|
|
43
|
+
* document that says `error`. That is a runtime fact, pinned by the exit-code
|
|
44
|
+
* matrix system test, not a syntactic one.
|
|
45
|
+
*
|
|
23
46
|
* @example
|
|
24
47
|
* // BAD — 1 means what, exactly?
|
|
25
48
|
* process.exit(1);
|
|
26
49
|
*
|
|
27
|
-
* //
|
|
28
|
-
* process.exit(ExitCode.FINDINGS);
|
|
50
|
+
* // BAD under `derived` — the call site decides what the document means
|
|
51
|
+
* process.exit(errors > 0 ? ExitCode.FINDINGS : ExitCode.OK);
|
|
52
|
+
*
|
|
53
|
+
* // GOOD — derived from what was published
|
|
54
|
+
* process.exit(exitCodeForReport(report));
|
|
29
55
|
*/
|
|
30
56
|
|
|
31
57
|
'use strict';
|
|
32
58
|
|
|
33
|
-
const { createExemptPathMatcher } = require('./exempt-path-matcher.cjs');
|
|
59
|
+
const { createExemptDirectoryMatcher, createExemptPathMatcher, isTestFile } = require('./exempt-path-matcher.cjs');
|
|
60
|
+
|
|
61
|
+
/** The members a call site may name outright: neither is a claim about a document. */
|
|
62
|
+
const UNDERIVED_MEMBERS = new Set(['OK', 'ERROR']);
|
|
63
|
+
|
|
64
|
+
/** Whether `node` is `ExitCode.<name>` (non-computed). */
|
|
65
|
+
function isExitCodeMember(node, name) {
|
|
66
|
+
return (
|
|
67
|
+
node.type === 'MemberExpression' &&
|
|
68
|
+
!node.computed &&
|
|
69
|
+
node.object.type === 'Identifier' &&
|
|
70
|
+
node.object.name === 'ExitCode' &&
|
|
71
|
+
node.property.type === 'Identifier' &&
|
|
72
|
+
(name === undefined ? UNDERIVED_MEMBERS.has(node.property.name) : node.property.name === name)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The called function's own name — `f(…)` or `x.f(…)` — or null. */
|
|
77
|
+
function calleeName(node) {
|
|
78
|
+
if (node.type !== 'CallExpression') return null;
|
|
79
|
+
const { callee } = node;
|
|
80
|
+
if (callee.type === 'Identifier') return callee.name;
|
|
81
|
+
if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier') {
|
|
82
|
+
return callee.property.name;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
34
86
|
|
|
35
87
|
/** Whether `node` is the member expression `process.<name>` (non-computed). */
|
|
36
88
|
function isProcessMember(node, name) {
|
|
@@ -84,6 +136,16 @@ module.exports = {
|
|
|
84
136
|
type: 'object',
|
|
85
137
|
properties: {
|
|
86
138
|
allow: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
139
|
+
derived: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
paths: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
143
|
+
calls: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
144
|
+
legacy: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
145
|
+
},
|
|
146
|
+
required: ['paths', 'calls', 'legacy'],
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
},
|
|
87
149
|
},
|
|
88
150
|
additionalProperties: false,
|
|
89
151
|
},
|
|
@@ -96,34 +158,73 @@ module.exports = {
|
|
|
96
158
|
literalExitCode:
|
|
97
159
|
'process.exitCode = {{literal}} re-decides the exit contract at this site. Assign an ' +
|
|
98
160
|
'ExitCode member from @vibe-agent-toolkit/schema instead.',
|
|
161
|
+
findingsNotDerived:
|
|
162
|
+
'ExitCode.FINDINGS is a claim about a published document ("what it examined failed its ' +
|
|
163
|
+
'gate"). Derive it: process.exit(exitCodeForReport(document)) — never name it at a call site.',
|
|
164
|
+
exitNotDerived:
|
|
165
|
+
'This exit code is decided here, beside the document, where it can disagree with it. Exit ' +
|
|
166
|
+
'with ExitCode.OK, ExitCode.ERROR, or a derivation: {{calls}}.',
|
|
167
|
+
staleLegacy:
|
|
168
|
+
'This file is listed in no-literal-process-exit derived.legacy but decides no exit code by ' +
|
|
169
|
+
'hand any more. Remove it from the list — the ratchet only shrinks.',
|
|
99
170
|
},
|
|
100
171
|
},
|
|
101
172
|
|
|
102
173
|
create(context) {
|
|
103
174
|
const allow = context.options?.[0]?.allow ?? [];
|
|
175
|
+
const derived = context.options?.[0]?.derived;
|
|
104
176
|
const filename = context.filename ?? context.getFilename();
|
|
105
177
|
if (createExemptPathMatcher(allow)(filename)) {
|
|
106
178
|
return {};
|
|
107
179
|
}
|
|
108
180
|
|
|
181
|
+
const inDerived = derived !== undefined
|
|
182
|
+
&& !isTestFile(filename)
|
|
183
|
+
&& createExemptDirectoryMatcher(derived.paths)(filename);
|
|
184
|
+
const isLegacy = inDerived && createExemptPathMatcher(derived.legacy)(filename);
|
|
185
|
+
const derivations = new Set(derived?.calls ?? []);
|
|
186
|
+
let handDecided = 0;
|
|
187
|
+
|
|
188
|
+
/** Report a derivation violation — or, in a legacy file, only count it. */
|
|
189
|
+
function reportDerived(node, messageId) {
|
|
190
|
+
handDecided += 1;
|
|
191
|
+
if (isLegacy) return;
|
|
192
|
+
context.report({ node, messageId, data: { calls: [...derivations].join(', ') } });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @returns {boolean} Whether a literal was reported (so nothing else is). */
|
|
109
196
|
function reportLiteral(node, expr, messageId) {
|
|
110
197
|
const leaf = numericLiteralLeaf(expr);
|
|
111
|
-
if (leaf
|
|
112
|
-
|
|
113
|
-
|
|
198
|
+
if (leaf === null) return false;
|
|
199
|
+
context.report({ node, messageId, data: { literal: String(leaf.raw ?? leaf.value) } });
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function checkExitValue(node, expr, literalMessageId) {
|
|
204
|
+
if (reportLiteral(node, expr, literalMessageId) || !inDerived) return;
|
|
205
|
+
if (isExitCodeMember(expr) || derivations.has(calleeName(expr))) return;
|
|
206
|
+
// `ExitCode.FINDINGS` itself is reported by the member visitor below.
|
|
207
|
+
if (isExitCodeMember(expr, 'FINDINGS')) return;
|
|
208
|
+
reportDerived(node, 'exitNotDerived');
|
|
114
209
|
}
|
|
115
210
|
|
|
116
211
|
return {
|
|
117
212
|
CallExpression(node) {
|
|
118
213
|
if (isProcessMember(node.callee, 'exit') && node.arguments.length > 0) {
|
|
119
|
-
|
|
214
|
+
checkExitValue(node, node.arguments[0], 'literalExit');
|
|
120
215
|
}
|
|
121
216
|
},
|
|
122
217
|
AssignmentExpression(node) {
|
|
123
218
|
if (isProcessMember(node.left, 'exitCode')) {
|
|
124
|
-
|
|
219
|
+
checkExitValue(node, node.right, 'literalExitCode');
|
|
125
220
|
}
|
|
126
221
|
},
|
|
222
|
+
MemberExpression(node) {
|
|
223
|
+
if (inDerived && isExitCodeMember(node, 'FINDINGS')) reportDerived(node, 'findingsNotDerived');
|
|
224
|
+
},
|
|
225
|
+
'Program:exit'(node) {
|
|
226
|
+
if (isLegacy && handDecided === 0) context.report({ node, messageId: 'staleLegacy' });
|
|
227
|
+
},
|
|
127
228
|
};
|
|
128
229
|
},
|
|
129
230
|
};
|