@vibe-agent-toolkit/utils 0.2.0-rc.8 → 0.2.0-rc.9
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 +3 -2
- package/dist/env-flag.d.ts +6 -1
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +6 -1
- package/dist/env-flag.js.map +1 -1
- package/dist/git-snapshot.d.ts +18 -0
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +18 -0
- package/dist/git-snapshot.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/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/path-core.d.ts +35 -14
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +42 -16
- package/dist/path-core.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/eslint/README.md +2 -2
- package/eslint/rules/exempt-path-matcher.cjs +1 -0
- package/eslint/rules/no-decaying-referent.cjs +1 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +23 -17
- package/eslint/rules/no-io-in-unit-tier.cjs +1 -0
- package/eslint/rules/no-manual-path-normalize.cjs +131 -107
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ The last two columns are the ones that matter when choosing. **"Resolves with ze
|
|
|
24
24
|
|
|
25
25
|
| Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
|
|
26
26
|
|---|---|---|---|---|
|
|
27
|
-
| `./path` | `safePath`, `toForwardSlash`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `relativeEscapesRoot`, `isSingleFsSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
27
|
+
| `./path` | `safePath`, `toForwardSlash`, `toForwardSlashAnyPlatform`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `relativeEscapesRoot`, `isSingleFsSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
28
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** |
|
|
29
29
|
| `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
|
|
30
30
|
| `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
|
|
@@ -128,7 +128,8 @@ FUNCTION, LAZY, PROMISE, and more...
|
|
|
128
128
|
These always return forward slashes on every platform, so they are safe for comparisons, `Map` keys, globs, and display.
|
|
129
129
|
|
|
130
130
|
- `safePath.join()` / `.resolve()` / `.relative()` - forward-slash equivalents of the `node:path` functions
|
|
131
|
-
- `toForwardSlash()` -
|
|
131
|
+
- `toForwardSlash()` - converter for a NATIVE path (fs, `path.*`, git output): converts only where the host separator is a backslash (win32); on POSIX a backslash is a filename character and is kept
|
|
132
|
+
- `toForwardSlashAnyPlatform()` - converter for AUTHOR-WRITTEN text (hrefs, globs, config values, CLI arguments, archive entry names): converts every backslash on every host. Never for a path read from disk — on POSIX it would turn the one file `docs/x\y.md` into a phantom `docs/x/y.md`
|
|
132
133
|
- `toNfc()` - Unicode-NFC normalizer for filename **comparison keys** (see the warning below)
|
|
133
134
|
- `toAbsolutePath()` - resolve a path relative to a base directory
|
|
134
135
|
- `getRelativePath()` - relative path between two absolute paths
|
package/dist/env-flag.d.ts
CHANGED
|
@@ -20,16 +20,21 @@
|
|
|
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 four consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
27
|
* | `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
|
+
* | `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 |
|
|
30
31
|
*
|
|
31
32
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
32
33
|
* the contract; do not move a default in here.
|
|
34
|
+
*
|
|
35
|
+
* 🔑 The last two rows are the interesting pair: one variable defaults OFF and
|
|
36
|
+
* the other defaults ON, and both are still read through this one function,
|
|
37
|
+
* because what it answers is not "is this on" but "did the operator say no".
|
|
33
38
|
*/
|
|
34
39
|
/**
|
|
35
40
|
* 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
|
package/dist/env-flag.js
CHANGED
|
@@ -20,16 +20,21 @@
|
|
|
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 four consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
27
|
* | `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
|
+
* | `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 |
|
|
30
31
|
*
|
|
31
32
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
32
33
|
* the contract; do not move a default in here.
|
|
34
|
+
*
|
|
35
|
+
* 🔑 The last two rows are the interesting pair: one variable defaults OFF and
|
|
36
|
+
* the other defaults ON, and both are still read through this one function,
|
|
37
|
+
* because what it answers is not "is this on" but "did the operator say no".
|
|
33
38
|
*/
|
|
34
39
|
const TRUE_SPELLINGS = new Set(['1', 'true', 'yes', 'y', 'on']);
|
|
35
40
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;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/git-snapshot.d.ts
CHANGED
|
@@ -153,6 +153,24 @@ export declare function withGitSnapshotCache<T>(run: () => T): T;
|
|
|
153
153
|
export declare function gitTreeSnapshot(options: {
|
|
154
154
|
cwd: string;
|
|
155
155
|
}): GitTreeSnapshot | null;
|
|
156
|
+
/**
|
|
157
|
+
* A snapshot taken NOW, whatever bracket is open — never read from or written to
|
|
158
|
+
* its memo.
|
|
159
|
+
*
|
|
160
|
+
* ⛔ The bracket makes two consumers agree with EACH OTHER; it cannot make them
|
|
161
|
+
* agree with files read later. A population keyed inside a bracket reads the
|
|
162
|
+
* working tree afterwards, so an edit landing in between is read and then filed
|
|
163
|
+
* under the pre-edit key. This is how a caller checks, before filing, that the
|
|
164
|
+
* bracket's answer still describes the tree. It costs a full snapshot, so it
|
|
165
|
+
* belongs on the write path, never on a hit.
|
|
166
|
+
*
|
|
167
|
+
* @param options - Where to look
|
|
168
|
+
* @param options.cwd - Any directory inside the repository of interest
|
|
169
|
+
* @returns The snapshot, or `null` when git could not answer
|
|
170
|
+
*/
|
|
171
|
+
export declare function freshGitTreeSnapshot(options: {
|
|
172
|
+
cwd: string;
|
|
173
|
+
}): GitTreeSnapshot | null;
|
|
156
174
|
/**
|
|
157
175
|
* The snapshot this bracket ALREADY holds for a repository — never a new one.
|
|
158
176
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-snapshot.d.ts","sourceRoot":"","sources":["../src/git-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AASH,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,GAAG,IAAI,CA2BhF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAY5E"}
|
|
1
|
+
{"version":3,"file":"git-snapshot.d.ts","sourceRoot":"","sources":["../src/git-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AASH,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAEvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,GAAG,IAAI,CA2BhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,GAAG,IAAI,CAErF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAY5E"}
|
package/dist/git-snapshot.js
CHANGED
|
@@ -164,6 +164,24 @@ export function gitTreeSnapshot(options) {
|
|
|
164
164
|
memo?.set(repositoryRoot, snapshot);
|
|
165
165
|
return snapshot;
|
|
166
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* A snapshot taken NOW, whatever bracket is open — never read from or written to
|
|
169
|
+
* its memo.
|
|
170
|
+
*
|
|
171
|
+
* ⛔ The bracket makes two consumers agree with EACH OTHER; it cannot make them
|
|
172
|
+
* agree with files read later. A population keyed inside a bracket reads the
|
|
173
|
+
* working tree afterwards, so an edit landing in between is read and then filed
|
|
174
|
+
* under the pre-edit key. This is how a caller checks, before filing, that the
|
|
175
|
+
* bracket's answer still describes the tree. It costs a full snapshot, so it
|
|
176
|
+
* belongs on the write path, never on a hit.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Where to look
|
|
179
|
+
* @param options.cwd - Any directory inside the repository of interest
|
|
180
|
+
* @returns The snapshot, or `null` when git could not answer
|
|
181
|
+
*/
|
|
182
|
+
export function freshGitTreeSnapshot(options) {
|
|
183
|
+
return snapshotsInBracket.exit(() => gitTreeSnapshot(options));
|
|
184
|
+
}
|
|
167
185
|
/**
|
|
168
186
|
* The snapshot this bracket ALREADY holds for a repository — never a new one.
|
|
169
187
|
*
|
package/dist/git-snapshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-snapshot.js","sourceRoot":"","sources":["../src/git-snapshot.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"git-snapshot.js","sourceRoot":"","sources":["../src/git-snapshot.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
export { runGit, runGitOrThrow, type GitRunOptions, type GitRunResult, } from './git-run.js';
|
|
26
26
|
export { gitFindRoot, gitLsFiles, gitLsOthers, isGitIgnored } from './git-utils.js';
|
|
27
27
|
export type { RefuseListingContext, UnreadablePolicy } from './listing-refusal.js';
|
|
28
|
-
export { gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, type GitSnapshotEntry, type GitTreeSnapshot, } from './git-snapshot.js';
|
|
28
|
+
export { freshGitTreeSnapshot, gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, type GitSnapshotEntry, type GitTreeSnapshot, } from './git-snapshot.js';
|
|
29
29
|
export { loadGitignoreRules } from './gitignore-checker.js';
|
|
30
30
|
export { GitTracker, type GitTrackerInitOptions } from './git-tracker.js';
|
|
31
31
|
export { isGitUrl, nonInteractiveGitOverrides, parseGitUrl, type NonInteractiveGitOverrides, type ParsedGitUrl, } from './git-url.js';
|
package/dist/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIpF,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,EACX,KAAK,0BAA0B,EAC/B,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAIpF,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,EACX,KAAK,0BAA0B,EAC/B,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC"}
|
package/dist/git.js
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
export { runGit, runGitOrThrow, } from './git-run.js';
|
|
26
26
|
export { gitFindRoot, gitLsFiles, gitLsOthers, isGitIgnored } from './git-utils.js';
|
|
27
|
-
export { gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, } from './git-snapshot.js';
|
|
27
|
+
export { freshGitTreeSnapshot, gitTreeSnapshot, peekGitTreeSnapshot, withGitSnapshotCache, } from './git-snapshot.js';
|
|
28
28
|
export { loadGitignoreRules } from './gitignore-checker.js';
|
|
29
29
|
export { GitTracker } from './git-tracker.js';
|
|
30
30
|
export { isGitUrl, nonInteractiveGitOverrides, parseGitUrl, } from './git-url.js';
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,GAGd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKpF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,oBAAoB,GAGrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,GAGZ,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EACL,MAAM,EACN,aAAa,GAGd,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKpF,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,oBAAoB,GAGrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,0BAA0B,EAC1B,WAAW,GAGZ,MAAM,cAAc,CAAC"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* sets an `escaped` flag when it sees `\`; the *next* character is skipped for
|
|
12
12
|
* magic detection if `escaped` is true.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { toForwardSlashAnyPlatform } from '../path-core.js';
|
|
15
15
|
/** The glob metacharacters recognised by this module. */
|
|
16
16
|
const MAGIC_CHARS = new Set(['*', '?', '[']);
|
|
17
17
|
/** Sentinel returned by {@link staticGlobBase} when the first segment is magic. */
|
|
@@ -69,9 +69,9 @@ export function isGlob(source) {
|
|
|
69
69
|
* staticGlobBase('foo/bar.txt') // 'foo/bar.txt'
|
|
70
70
|
*/
|
|
71
71
|
export function staticGlobBase(pattern) {
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
const normalized =
|
|
72
|
+
// A glob is author-written text: a Windows-spelled separator reads as `/` on
|
|
73
|
+
// every host, whatever platform VAT runs on.
|
|
74
|
+
const normalized = toForwardSlashAnyPlatform(pattern);
|
|
75
75
|
const segments = normalized.split('/');
|
|
76
76
|
const staticSegments = [];
|
|
77
77
|
for (const segment of segments) {
|
|
@@ -112,7 +112,7 @@ export function staticGlobBase(pattern) {
|
|
|
112
112
|
* globMagicRemainder('../mycli/dist/*.mjs') // '*.mjs'
|
|
113
113
|
*/
|
|
114
114
|
export function globMagicRemainder(pattern) {
|
|
115
|
-
const forward =
|
|
115
|
+
const forward = toForwardSlashAnyPlatform(pattern);
|
|
116
116
|
const base = staticGlobBase(pattern);
|
|
117
117
|
if (base === DOT) {
|
|
118
118
|
// First segment was magic — the remainder is the full (forward-slash) pattern.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob-pattern.js","sourceRoot":"","sources":["../../src/glob/glob-pattern.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"glob-pattern.js","sourceRoot":"","sources":["../../src/glob/glob-pattern.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,yDAAyD;AACzD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7C,mFAAmF;AACnF,MAAM,GAAG,GAAG,GAAG,CAAC;AAEhB;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,MAAM,CAAC,MAAc;IACnC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,KAAK,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,6EAA6E;IAC7E,6CAA6C;IAC7C,MAAM,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACpB,MAAM;QACR,CAAC;QACD,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,cAAc,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9C,oEAAoE;QACpE,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,gDAAgD;QAChD,OAAO,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,OAAO,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,+EAA+E;QAC/E,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,yDAAyD;QACzD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACjB,yEAAyE;QACzE,yEAAyE;QACzE,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,wDAAwD;IACxD,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/path-core.d.ts
CHANGED
|
@@ -64,8 +64,9 @@ export declare function isAbsoluteAnyPlatform(p: string): boolean;
|
|
|
64
64
|
/**
|
|
65
65
|
* True if `p` contains a `..` parent-directory traversal segment.
|
|
66
66
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* Backslashes are read as separators on every host (the input is author-written
|
|
68
|
+
* config), then each `/`-delimited segment is inspected — so a `..` is caught
|
|
69
|
+
* regardless of the separator the author typed. A containment guard
|
|
69
70
|
* for config-supplied relative paths (skill `files:` dest values, glob magic
|
|
70
71
|
* remainders) that must never climb above their anchor directory.
|
|
71
72
|
*
|
|
@@ -188,23 +189,42 @@ export declare function toAbsolutePath(p: string, baseDir: string): string;
|
|
|
188
189
|
*/
|
|
189
190
|
export declare function getRelativePath(from: string, to: string): string;
|
|
190
191
|
/**
|
|
191
|
-
* Convert a path
|
|
192
|
+
* Convert a NATIVE path — one the filesystem, `path.*`, `readdir` or git
|
|
193
|
+
* handed you — to forward slashes.
|
|
192
194
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
195
|
+
* Converts only where a backslash is a separator (win32). On POSIX a backslash is a
|
|
196
|
+
* legal filename character, so `docs/x\y.md` is one file and is returned
|
|
197
|
+
* unchanged: converting it would invent a phantom `docs/x/` directory and, in
|
|
198
|
+
* {@link safePath.joinUnderRoot}, turn an `x\..\..` NAME into a climb.
|
|
196
199
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
200
|
+
* For AUTHOR-WRITTEN text — an href, a glob, a config value, a CLI argument,
|
|
201
|
+
* an archive entry name — whose backslashes must read as separators on every
|
|
202
|
+
* host, use {@link toForwardSlashAnyPlatform}.
|
|
199
203
|
*
|
|
200
|
-
* @
|
|
201
|
-
*
|
|
202
|
-
* // Returns: 'C:/Users/docs/README.md'
|
|
204
|
+
* @param p - A native path
|
|
205
|
+
* @returns The path with forward slashes (identity on POSIX)
|
|
203
206
|
*
|
|
204
|
-
*
|
|
205
|
-
* //
|
|
207
|
+
* @example
|
|
208
|
+
* toForwardSlash('C:\\Users\\docs\\README.md') // win32: 'C:/Users/docs/README.md'
|
|
209
|
+
* toForwardSlash('docs/x\\y.md') // POSIX: 'docs/x\\y.md' (unchanged)
|
|
206
210
|
*/
|
|
207
211
|
export declare function toForwardSlash(p: string): string;
|
|
212
|
+
/**
|
|
213
|
+
* Convert every backslash to a forward slash, on every host.
|
|
214
|
+
*
|
|
215
|
+
* For AUTHOR-WRITTEN text that may carry Windows spellings regardless of where
|
|
216
|
+
* VAT runs — markdown hrefs, globs, config values, CLI arguments, zip entry
|
|
217
|
+
* names — and for containment guards that must refuse `..\x` everywhere.
|
|
218
|
+
* Never use it on a path read from the filesystem or git: on POSIX that
|
|
219
|
+
* backslash is part of a filename. Use {@link toForwardSlash} for those.
|
|
220
|
+
*
|
|
221
|
+
* @param text - Author-written path text
|
|
222
|
+
* @returns The text with every backslash replaced by `/`
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* toForwardSlashAnyPlatform('..\\evil') // '../evil' on every host
|
|
226
|
+
*/
|
|
227
|
+
export declare function toForwardSlashAnyPlatform(text: string): string;
|
|
208
228
|
/**
|
|
209
229
|
* Normalize text to Unicode NFC — the form in which two *visually identical*
|
|
210
230
|
* filenames compare equal.
|
|
@@ -239,7 +259,8 @@ export declare function toNfc(value: string): string;
|
|
|
239
259
|
* Cross-platform safe path operations.
|
|
240
260
|
*
|
|
241
261
|
* Wraps Node's `path.join()`, `path.resolve()`, and `path.relative()` to always
|
|
242
|
-
* return forward-slash paths
|
|
262
|
+
* return forward-slash paths (converted through {@link toForwardSlash}, so a
|
|
263
|
+
* backslash inside a POSIX filename survives). On Windows, the native `path.*` functions return
|
|
243
264
|
* backslashes, which causes bugs when paths are used as Map keys, compared as
|
|
244
265
|
* strings, or matched with glob patterns.
|
|
245
266
|
*
|
package/dist/path-core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-core.d.ts","sourceRoot":"","sources":["../src/path-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;IAChD,2FAA2F;IAC3F,MAAM,CAAC,QAAQ,CAAC,IAAI,uBAAuB;IAE3C;;;OAGG;gBACS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAGzC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAExD;AAED
|
|
1
|
+
{"version":3,"file":"path-core.d.ts","sourceRoot":"","sources":["../src/path-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,QAAQ;IAChD,2FAA2F;IAC3F,MAAM,CAAC,QAAQ,CAAC,IAAI,uBAAuB;IAE3C;;;OAGG;gBACS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAGzC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,mBAAmB,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAGvE;AAKD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUvD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhE;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,QAAQ;IACnB,6DAA6D;8BAC9C,MAAM,EAAE,KAAG,MAAM;IAIhC,gEAAgE;iCAC9C,MAAM,EAAE,KAAG,MAAM;IAInC,iEAAiE;8BAClD,MAAM,MAAM,MAAM,KAAG,MAAM;IAI1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;mCACiB,MAAM,eAAe,MAAM,EAAE,KAAG,MAAM;CAiClD,CAAC"}
|
package/dist/path-core.js
CHANGED
|
@@ -71,8 +71,9 @@ export function isAbsoluteAnyPlatform(p) {
|
|
|
71
71
|
/**
|
|
72
72
|
* True if `p` contains a `..` parent-directory traversal segment.
|
|
73
73
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* Backslashes are read as separators on every host (the input is author-written
|
|
75
|
+
* config), then each `/`-delimited segment is inspected — so a `..` is caught
|
|
76
|
+
* regardless of the separator the author typed. A containment guard
|
|
76
77
|
* for config-supplied relative paths (skill `files:` dest values, glob magic
|
|
77
78
|
* remainders) that must never climb above their anchor directory.
|
|
78
79
|
*
|
|
@@ -84,7 +85,7 @@ export function isAbsoluteAnyPlatform(p) {
|
|
|
84
85
|
*/
|
|
85
86
|
export function hasParentTraversalSegment(p) {
|
|
86
87
|
// eslint-disable-next-line local/no-dotdot-containment -- this IS the one lexical `..`-segment test the rule points to; it classifies a config-supplied RELATIVE spelling before any root exists to ask the filesystem about. Sinks use isUnderRoot().
|
|
87
|
-
return
|
|
88
|
+
return toForwardSlashAnyPlatform(p).split('/').includes('..');
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
91
|
* True when a root-relative path — as `safePath.relative(root, p)` spells it —
|
|
@@ -222,25 +223,49 @@ export function getRelativePath(from, to) {
|
|
|
222
223
|
// Calculate relative path from source directory to target file
|
|
223
224
|
return toForwardSlash(path.relative(fromDir, to));
|
|
224
225
|
}
|
|
226
|
+
/** True on hosts (win32) where a backslash is a path separator; everywhere else it is a filename character. */
|
|
227
|
+
const BACKSLASH_IS_NATIVE_SEPARATOR = path.sep === '\\';
|
|
225
228
|
/**
|
|
226
|
-
* Convert a path
|
|
229
|
+
* Convert a NATIVE path — one the filesystem, `path.*`, `readdir` or git
|
|
230
|
+
* handed you — to forward slashes.
|
|
227
231
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
232
|
+
* Converts only where a backslash is a separator (win32). On POSIX a backslash is a
|
|
233
|
+
* legal filename character, so `docs/x\y.md` is one file and is returned
|
|
234
|
+
* unchanged: converting it would invent a phantom `docs/x/` directory and, in
|
|
235
|
+
* {@link safePath.joinUnderRoot}, turn an `x\..\..` NAME into a climb.
|
|
231
236
|
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
237
|
+
* For AUTHOR-WRITTEN text — an href, a glob, a config value, a CLI argument,
|
|
238
|
+
* an archive entry name — whose backslashes must read as separators on every
|
|
239
|
+
* host, use {@link toForwardSlashAnyPlatform}.
|
|
234
240
|
*
|
|
235
|
-
* @
|
|
236
|
-
*
|
|
237
|
-
* // Returns: 'C:/Users/docs/README.md'
|
|
241
|
+
* @param p - A native path
|
|
242
|
+
* @returns The path with forward slashes (identity on POSIX)
|
|
238
243
|
*
|
|
239
|
-
*
|
|
240
|
-
* //
|
|
244
|
+
* @example
|
|
245
|
+
* toForwardSlash('C:\\Users\\docs\\README.md') // win32: 'C:/Users/docs/README.md'
|
|
246
|
+
* toForwardSlash('docs/x\\y.md') // POSIX: 'docs/x\\y.md' (unchanged)
|
|
241
247
|
*/
|
|
242
248
|
export function toForwardSlash(p) {
|
|
243
|
-
return p
|
|
249
|
+
return BACKSLASH_IS_NATIVE_SEPARATOR ? toForwardSlashAnyPlatform(p) : p;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Convert every backslash to a forward slash, on every host.
|
|
253
|
+
*
|
|
254
|
+
* For AUTHOR-WRITTEN text that may carry Windows spellings regardless of where
|
|
255
|
+
* VAT runs — markdown hrefs, globs, config values, CLI arguments, zip entry
|
|
256
|
+
* names — and for containment guards that must refuse `..\x` everywhere.
|
|
257
|
+
* Never use it on a path read from the filesystem or git: on POSIX that
|
|
258
|
+
* backslash is part of a filename. Use {@link toForwardSlash} for those.
|
|
259
|
+
*
|
|
260
|
+
* @param text - Author-written path text
|
|
261
|
+
* @returns The text with every backslash replaced by `/`
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* toForwardSlashAnyPlatform('..\\evil') // '../evil' on every host
|
|
265
|
+
*/
|
|
266
|
+
export function toForwardSlashAnyPlatform(text) {
|
|
267
|
+
// eslint-disable-next-line local/no-manual-path-normalize -- this IS the converter the rule's autofix writes; it cannot call itself.
|
|
268
|
+
return text.replaceAll('\\', '/');
|
|
244
269
|
}
|
|
245
270
|
/**
|
|
246
271
|
* Normalize text to Unicode NFC — the form in which two *visually identical*
|
|
@@ -278,7 +303,8 @@ export function toNfc(value) {
|
|
|
278
303
|
* Cross-platform safe path operations.
|
|
279
304
|
*
|
|
280
305
|
* Wraps Node's `path.join()`, `path.resolve()`, and `path.relative()` to always
|
|
281
|
-
* return forward-slash paths
|
|
306
|
+
* return forward-slash paths (converted through {@link toForwardSlash}, so a
|
|
307
|
+
* backslash inside a POSIX filename survives). On Windows, the native `path.*` functions return
|
|
282
308
|
* backslashes, which causes bugs when paths are used as Map keys, compared as
|
|
283
309
|
* strings, or matched with glob patterns.
|
|
284
310
|
*
|
package/dist/path-core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-core.js","sourceRoot":"","sources":["../src/path-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,OAAO,oBAAqB,SAAQ,QAAQ;IAChD,2FAA2F;IAC3F,MAAM,CAAU,IAAI,GAAG,mBAAmB,CAAC;IAE3C;;;OAGG;IACH,YAAY,IAAY,EAAE,MAAc;QACtC,KAAK,CAAC,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,MAAM,kBAAkB,IAAI,IAAI,CAAC,CAAC;IAChG,CAAC;;AAGH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAS;IAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"path-core.js","sourceRoot":"","sources":["../src/path-core.ts"],"names":[],"mappings":"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"}
|
package/dist/path.d.ts
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
* If you need `normalizedTmpdir`, `mkdirSyncReal`, or `normalizePath`, those
|
|
9
9
|
* touch the filesystem — import them from `@vibe-agent-toolkit/utils/fs`.
|
|
10
10
|
*/
|
|
11
|
-
export { PathEscapesRootError, safePath, toForwardSlash, toNfc, isAbsolutePath, isAbsoluteAnyPlatform, hasParentTraversalSegment, isSingleFsSegment, relativeEscapesRoot, toAbsolutePath, getRelativePath, issueLocation, } from './path-core.js';
|
|
11
|
+
export { PathEscapesRootError, safePath, toForwardSlash, toForwardSlashAnyPlatform, toNfc, isAbsolutePath, isAbsoluteAnyPlatform, hasParentTraversalSegment, isSingleFsSegment, relativeEscapesRoot, toAbsolutePath, getRelativePath, issueLocation, } from './path-core.js';
|
|
12
12
|
//# sourceMappingURL=path.d.ts.map
|
package/dist/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,cAAc,EACd,KAAK,EACL,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,cAAc,EACd,yBAAyB,EACzB,KAAK,EACL,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
package/dist/path.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
* If you need `normalizedTmpdir`, `mkdirSyncReal`, or `normalizePath`, those
|
|
9
9
|
* touch the filesystem — import them from `@vibe-agent-toolkit/utils/fs`.
|
|
10
10
|
*/
|
|
11
|
-
export { PathEscapesRootError, safePath, toForwardSlash, toNfc, isAbsolutePath, isAbsoluteAnyPlatform, hasParentTraversalSegment, isSingleFsSegment, relativeEscapesRoot, toAbsolutePath, getRelativePath, issueLocation, } from './path-core.js';
|
|
11
|
+
export { PathEscapesRootError, safePath, toForwardSlash, toForwardSlashAnyPlatform, toNfc, isAbsolutePath, isAbsoluteAnyPlatform, hasParentTraversalSegment, isSingleFsSegment, relativeEscapesRoot, toAbsolutePath, getRelativePath, issueLocation, } from './path-core.js';
|
|
12
12
|
//# sourceMappingURL=path.js.map
|
package/dist/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,cAAc,EACd,KAAK,EACL,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,cAAc,EACd,yBAAyB,EACzB,KAAK,EACL,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,gBAAgB,CAAC"}
|
package/eslint/README.md
CHANGED
|
@@ -79,8 +79,8 @@ The table is **generated** from each rule's `meta.docs` by `bun run generate:cla
|
|
|
79
79
|
|
|
80
80
|
| Rule | Bans | Use instead | Subpath | Fix | `recommended` |
|
|
81
81
|
|---|---|---|---|---|---|
|
|
82
|
-
| `no-hardcoded-path-split` | `split('/')` / `split('\\')` on a path | `path.basename()`, or `toForwardSlash()` first | `/path` | | `error` |
|
|
83
|
-
| `no-manual-path-normalize` | hand-rolled `.replace(/\\/g, '/')` | `toForwardSlash()` | `/path` | ✓ | `error` |
|
|
82
|
+
| `no-hardcoded-path-split` | `split('/')` / `split('\\')` on a path | `path.basename()`, or `toForwardSlash()` / `toForwardSlashAnyPlatform()` first | `/path` | | `error` |
|
|
83
|
+
| `no-manual-path-normalize` | hand-rolled `.replace(/\\/g, '/')` / `split(path.sep).join('/')` | `toForwardSlash()` (native paths) / `toForwardSlashAnyPlatform()` (authored text) | `/path` | ✓ | `error` |
|
|
84
84
|
| `no-path-operations-in-comparisons` | raw `path.*()` results in string comparisons | wrap in `toForwardSlash()` | `/path` | | `error` |
|
|
85
85
|
| `no-path-sep-in-strings` | `path.sep` embedded in a string literal | `toForwardSlash()` | `/path` | | `error` |
|
|
86
86
|
| `no-path-startswith` | `path.startsWith()` on a raw path | `toForwardSlash()` first | `/path` | | `error` |
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
/** Forward-slash a path and drop any leading `./` or `/` noise used for anchoring. */
|
|
38
38
|
function normalizeForMatch(value) {
|
|
39
|
+
// eslint-disable-next-line local/no-manual-path-normalize -- the rule pack is standalone CommonJS and cannot import the ESM helper; config paths and linted filenames are matched separator-agnostically on purpose.
|
|
39
40
|
return String(value).replaceAll('\\', '/').replace(/^(?:\.\/)+/, '');
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -103,6 +103,7 @@ function isSourceFile(filename) {
|
|
|
103
103
|
if (!filename) {
|
|
104
104
|
return false;
|
|
105
105
|
}
|
|
106
|
+
// eslint-disable-next-line local/no-manual-path-normalize -- the rule pack is standalone CommonJS and cannot import the ESM helper; config paths and linted filenames are matched separator-agnostically on purpose.
|
|
106
107
|
const normalized = String(filename).replaceAll('\\', '/');
|
|
107
108
|
return normalized.includes('/src/') && !isTestFile(normalized);
|
|
108
109
|
}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* ESLint rule to disallow splitting strings by hardcoded path separators
|
|
3
3
|
*
|
|
4
4
|
* Using .split('/') or .split('\\') on file paths breaks on Windows/Unix.
|
|
5
|
-
* Use path.basename(), path.dirname(), or normalize
|
|
5
|
+
* Use path.basename(), path.dirname(), or normalize first: toForwardSlash() for a
|
|
6
|
+
* NATIVE path (fs, path.*, the VCS — converts only where the host separator is a
|
|
7
|
+
* backslash), toForwardSlashAnyPlatform() for AUTHOR-WRITTEN text (hrefs, globs,
|
|
8
|
+
* config values, archive entries — converts every backslash on every host).
|
|
6
9
|
*
|
|
7
10
|
* This rule is smart enough to detect when paths are already normalized:
|
|
8
|
-
* - Inline: toForwardSlash(path).split('/') ✅
|
|
11
|
+
* - Inline: toForwardSlash(path).split('/') ✅ (either converter)
|
|
9
12
|
* - Variable: const normalized = toForwardSlash(path); normalized.split('/') ✅
|
|
10
13
|
*
|
|
11
14
|
* @example
|
|
@@ -24,6 +27,9 @@
|
|
|
24
27
|
* // ✅ GOOD - normalize then split (variable)
|
|
25
28
|
* const normalizedPath = toForwardSlash(filePath);
|
|
26
29
|
* const parts = normalizedPath.split('/');
|
|
30
|
+
*
|
|
31
|
+
* // ✅ GOOD - authored text (a glob, an href) converts on every host
|
|
32
|
+
* const segments = toForwardSlashAnyPlatform(pattern).split('/');
|
|
27
33
|
*/
|
|
28
34
|
|
|
29
35
|
const {
|
|
@@ -32,6 +38,14 @@ const {
|
|
|
32
38
|
resolveSafeModule,
|
|
33
39
|
} = require('./safe-import.cjs');
|
|
34
40
|
|
|
41
|
+
/** The converters whose result is forward-slashed, so splitting it on `/` is safe. */
|
|
42
|
+
const NORMALIZERS = new Set(['toForwardSlash', 'toForwardSlashAnyPlatform']);
|
|
43
|
+
|
|
44
|
+
/** A direct call to one of {@link NORMALIZERS}? */
|
|
45
|
+
function isNormalizerCall(node) {
|
|
46
|
+
return node?.type === 'CallExpression' && node.callee.type === 'Identifier' && NORMALIZERS.has(node.callee.name);
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
module.exports = {
|
|
36
50
|
meta: {
|
|
37
51
|
type: 'problem',
|
|
@@ -40,7 +54,7 @@ module.exports = {
|
|
|
40
54
|
'Disallow splitting strings by hardcoded path separators',
|
|
41
55
|
category: 'Path handling',
|
|
42
56
|
bans: "`split('/')` / `split('\\\\')` on a path",
|
|
43
|
-
useInstead: '`path.basename()`, or `toForwardSlash()` first',
|
|
57
|
+
useInstead: '`path.basename()`, or `toForwardSlash()` / `toForwardSlashAnyPlatform()` first',
|
|
44
58
|
subpath: '/path',
|
|
45
59
|
recommended: true,
|
|
46
60
|
recommendedSeverity: 'error',
|
|
@@ -48,26 +62,22 @@ module.exports = {
|
|
|
48
62
|
messages: {
|
|
49
63
|
noHardcodedSplit:
|
|
50
64
|
String.raw`Avoid .split('/') or .split('\') on file paths (breaks on Windows/Unix). ` +
|
|
51
|
-
'Use path.basename() to extract filename, or toForwardSlash()
|
|
65
|
+
'Use path.basename() to extract filename, or normalize first with toForwardSlash() (native paths) ' +
|
|
66
|
+
'or toForwardSlashAnyPlatform() (authored text such as globs and hrefs) from {{safeModule}}.',
|
|
52
67
|
},
|
|
53
68
|
schema: [SAFE_MODULE_ONLY_SCHEMA],
|
|
54
69
|
},
|
|
55
70
|
|
|
56
71
|
create(context) {
|
|
57
72
|
const reportData = { safeModule: resolveSafeModule(context, SAFE_PATH_MODULE) };
|
|
58
|
-
// Track variables that were assigned from
|
|
73
|
+
// Track variables that were assigned from a normalizer
|
|
59
74
|
const normalizedVariables = new Set();
|
|
60
75
|
|
|
61
76
|
return {
|
|
62
77
|
// Track variable declarations
|
|
63
78
|
VariableDeclarator(node) {
|
|
64
|
-
// Check if this variable is assigned from
|
|
65
|
-
if (
|
|
66
|
-
node.init?.type === 'CallExpression' &&
|
|
67
|
-
node.init.callee.type === 'Identifier' &&
|
|
68
|
-
node.init.callee.name === 'toForwardSlash' &&
|
|
69
|
-
node.id.type === 'Identifier'
|
|
70
|
-
) {
|
|
79
|
+
// Check if this variable is assigned from a normalizer
|
|
80
|
+
if (isNormalizerCall(node.init) && node.id.type === 'Identifier') {
|
|
71
81
|
normalizedVariables.add(node.id.name);
|
|
72
82
|
}
|
|
73
83
|
},
|
|
@@ -107,11 +117,7 @@ module.exports = {
|
|
|
107
117
|
const object = node.callee.object;
|
|
108
118
|
|
|
109
119
|
// Case 1: Inline normalization - toForwardSlash(...).split('/')
|
|
110
|
-
if (
|
|
111
|
-
object.type === 'CallExpression' &&
|
|
112
|
-
object.callee.type === 'Identifier' &&
|
|
113
|
-
object.callee.name === 'toForwardSlash'
|
|
114
|
-
) {
|
|
120
|
+
if (isNormalizerCall(object)) {
|
|
115
121
|
return; // Safe - normalized inline
|
|
116
122
|
}
|
|
117
123
|
|
|
@@ -54,6 +54,7 @@ function isUnitTierFile(filename) {
|
|
|
54
54
|
if (!filename) {
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
|
+
// eslint-disable-next-line local/no-manual-path-normalize -- the rule pack is standalone CommonJS and cannot import the ESM helper; config paths and linted filenames are matched separator-agnostically on purpose.
|
|
57
58
|
const normalized = String(filename).replaceAll('\\', '/');
|
|
58
59
|
return isTestFile(normalized) && PACKAGE_TEST_DIR.test(normalized) && !OTHER_TIER.test(normalized);
|
|
59
60
|
}
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ESLint rule to enforce
|
|
2
|
+
* ESLint rule to enforce the forward-slash converters instead of hand-rolled
|
|
3
|
+
* normalization.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
+
* Two converters, because a backslash means two different things:
|
|
6
|
+
*
|
|
7
|
+
* - `toForwardSlash(p)` — a NATIVE path (from `fs`, `path.*`, git). Converts
|
|
8
|
+
* only where the host's separator is a backslash; on POSIX a backslash is a
|
|
9
|
+
* filename character and is kept. `split(path.sep).join('/')` is exactly
|
|
10
|
+
* this, so it autofixes here.
|
|
11
|
+
* - `toForwardSlashAnyPlatform(text)` — AUTHOR-WRITTEN text (an href, a glob, a
|
|
12
|
+
* config value, an archive entry name). Converts every backslash on every
|
|
13
|
+
* host. A literal-backslash `split('\\').join('/')`, `replaceAll('\\', '/')`
|
|
14
|
+
* or `replace(/\\/g, '/')` is exactly this, so each autofixes here — never to
|
|
15
|
+
* `toForwardSlash`, which would silently stop converting on POSIX.
|
|
16
|
+
*
|
|
17
|
+
* Whether a given literal-backslash site is really author text is the author's
|
|
18
|
+
* call; the fix preserves behaviour, and a native-path site should then be
|
|
19
|
+
* switched to `toForwardSlash` by hand.
|
|
5
20
|
*
|
|
6
21
|
* @example
|
|
7
22
|
* // ❌ BAD - manual normalization
|
|
8
|
-
* const
|
|
9
|
-
* const
|
|
23
|
+
* const a = relativePath.split(path.sep).join('/');
|
|
24
|
+
* const b = href.replaceAll('\\', '/');
|
|
10
25
|
*
|
|
11
|
-
* // ✅ GOOD - use utility
|
|
12
|
-
* import { toForwardSlash } from '@vibe-agent-toolkit/utils/path';
|
|
13
|
-
* const
|
|
26
|
+
* // ✅ GOOD - use the utility functions
|
|
27
|
+
* import { toForwardSlash, toForwardSlashAnyPlatform } from '@vibe-agent-toolkit/utils/path';
|
|
28
|
+
* const a = toForwardSlash(relativePath);
|
|
29
|
+
* const b = toForwardSlashAnyPlatform(href);
|
|
14
30
|
*/
|
|
15
31
|
|
|
16
32
|
const {
|
|
@@ -26,8 +42,62 @@ const {
|
|
|
26
42
|
resolveSafeModule,
|
|
27
43
|
} = require('./safe-import.cjs');
|
|
28
44
|
|
|
29
|
-
const
|
|
45
|
+
const NATIVE_FN = 'toForwardSlash';
|
|
46
|
+
const ANY_PLATFORM_FN = 'toForwardSlashAnyPlatform';
|
|
47
|
+
const MESSAGE_FOR = { [NATIVE_FN]: 'useToForwardSlash', [ANY_PLATFORM_FN]: 'useToForwardSlashAnyPlatform' };
|
|
30
48
|
const PATH_MODULES = new Set(['node:path', 'path']);
|
|
49
|
+
const BACKSLASH = '\\';
|
|
50
|
+
|
|
51
|
+
/** `.name(...)` on some receiver, with exactly `arity` arguments? */
|
|
52
|
+
function isMethodCall(node, name, arity) {
|
|
53
|
+
return (
|
|
54
|
+
node?.type === 'CallExpression' &&
|
|
55
|
+
node.callee.type === 'MemberExpression' &&
|
|
56
|
+
node.callee.property.name === name &&
|
|
57
|
+
node.arguments.length === arity
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isStringLiteral(node, value) {
|
|
62
|
+
return node.type === 'Literal' && node.value === value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `/\\/g` — one literal backslash, global. */
|
|
66
|
+
function isGlobalBackslashRegex(node) {
|
|
67
|
+
return node.type === 'Literal' && node.regex?.pattern === String.raw`\\` && node.regex.flags.includes('g');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isPathSep(node) {
|
|
71
|
+
return node.type === 'MemberExpression' && node.object.name === 'path' && node.property.name === 'sep';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `<x>.split(<sep>).join('/')` → the converter it is equivalent to.
|
|
76
|
+
*
|
|
77
|
+
* Splitting on a TWO-backslash SEQUENCE (e.g. collapsing a UNC server prefix)
|
|
78
|
+
* is a different operation that neither converter matches, so it is not
|
|
79
|
+
* reported.
|
|
80
|
+
*/
|
|
81
|
+
function matchSplitJoin(node) {
|
|
82
|
+
if (!isMethodCall(node, 'join', 1) || !isStringLiteral(node.arguments[0], '/')) return undefined;
|
|
83
|
+
const split = node.callee.object;
|
|
84
|
+
if (!isMethodCall(split, 'split', 1)) return undefined;
|
|
85
|
+
const [separator] = split.arguments;
|
|
86
|
+
if (isPathSep(separator)) return { receiver: split.callee.object, fn: NATIVE_FN };
|
|
87
|
+
if (isStringLiteral(separator, BACKSLASH)) return { receiver: split.callee.object, fn: ANY_PLATFORM_FN };
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `<x>.replaceAll('\\', '/')` or `<x>.replace(/\\/g, '/')` → the any-platform converter. */
|
|
92
|
+
function matchReplace(node) {
|
|
93
|
+
const isReplace = isMethodCall(node, 'replace', 2) || isMethodCall(node, 'replaceAll', 2);
|
|
94
|
+
if (!isReplace || !isStringLiteral(node.arguments[1], '/')) return undefined;
|
|
95
|
+
const [pattern] = node.arguments;
|
|
96
|
+
const allBackslashes =
|
|
97
|
+
isGlobalBackslashRegex(pattern) ||
|
|
98
|
+
(node.callee.property.name === 'replaceAll' && isStringLiteral(pattern, BACKSLASH));
|
|
99
|
+
return allBackslashes ? { receiver: node.callee.object, fn: ANY_PLATFORM_FN } : undefined;
|
|
100
|
+
}
|
|
31
101
|
|
|
32
102
|
module.exports = {
|
|
33
103
|
meta: {
|
|
@@ -36,8 +106,8 @@ module.exports = {
|
|
|
36
106
|
description:
|
|
37
107
|
'Disallow manual path normalization patterns',
|
|
38
108
|
category: 'Path handling',
|
|
39
|
-
bans: "hand-rolled `.replace(/\\\\/g, '/')`",
|
|
40
|
-
useInstead: '`toForwardSlash()`',
|
|
109
|
+
bans: "hand-rolled `.replace(/\\\\/g, '/')` / `split(path.sep).join('/')`",
|
|
110
|
+
useInstead: '`toForwardSlash()` (native paths) / `toForwardSlashAnyPlatform()` (authored text)',
|
|
41
111
|
subpath: '/path',
|
|
42
112
|
recommended: true,
|
|
43
113
|
recommendedSeverity: 'error',
|
|
@@ -46,7 +116,11 @@ module.exports = {
|
|
|
46
116
|
messages: {
|
|
47
117
|
useToForwardSlash:
|
|
48
118
|
'Use toForwardSlash() from {{safeModule}} instead of manual path normalization. ' +
|
|
49
|
-
'
|
|
119
|
+
'It converts a native path only where the host separator is a backslash.',
|
|
120
|
+
useToForwardSlashAnyPlatform:
|
|
121
|
+
'Use toForwardSlashAnyPlatform() from {{safeModule}} instead of a hand-rolled backslash replace. ' +
|
|
122
|
+
'If this string is a native filesystem/git path rather than authored text, use toForwardSlash() — ' +
|
|
123
|
+
'on POSIX a backslash is a filename character.',
|
|
50
124
|
[DEAD_UNSAFE_IMPORT]: DEAD_UNSAFE_IMPORT_MESSAGE,
|
|
51
125
|
},
|
|
52
126
|
schema: [SAFE_MODULE_ONLY_SCHEMA],
|
|
@@ -55,24 +129,43 @@ module.exports = {
|
|
|
55
129
|
create(context) {
|
|
56
130
|
const sourceCode = context.getSourceCode();
|
|
57
131
|
const targetModule = resolveSafeModule(context, SAFE_PATH_MODULE);
|
|
58
|
-
// Seeded from SCOPE: a file that already
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
132
|
+
// Seeded from SCOPE: a file that already binds the name must have the call
|
|
133
|
+
// rewritten WITHOUT gaining a second binding of the same name — that is a
|
|
134
|
+
// SyntaxError. See `safe-import.cjs`.
|
|
135
|
+
const bound = {
|
|
136
|
+
[NATIVE_FN]: isNameAlreadyBound(sourceCode, NATIVE_FN),
|
|
137
|
+
[ANY_PLATFORM_FN]: isNameAlreadyBound(sourceCode, ANY_PLATFORM_FN),
|
|
138
|
+
};
|
|
62
139
|
// Never mutated — the dead-import leg must not be armed by a flag that a
|
|
63
140
|
// suppressed report's `fix()` can spend. See `dead-import.cjs`.
|
|
64
|
-
const safeBoundInSource =
|
|
141
|
+
const safeBoundInSource = bound[NATIVE_FN];
|
|
65
142
|
// The dead-import leg's OTHER gate: a `toForwardSlash(…)` call is the text
|
|
66
|
-
// this fixer writes
|
|
67
|
-
//
|
|
68
|
-
// `dead-import.cjs`. Read from the source, never from a `fix()`.
|
|
143
|
+
// this fixer writes for `path.sep`, and the only evidence available that it
|
|
144
|
+
// wrote it here. Read from the source, never from a `fix()`.
|
|
69
145
|
let safeReplacementCalled = false;
|
|
70
146
|
let utilsImportNode = null;
|
|
71
147
|
// `path.sep` is the last `path.*` reference in plenty of files, and
|
|
72
|
-
// `toForwardSlash(raw)` consumes it — leaving
|
|
73
|
-
// binding the `safePath` rules used to leave.
|
|
148
|
+
// `toForwardSlash(raw)` consumes it — leaving a dead `node:path` binding.
|
|
74
149
|
const pathImportNodes = [];
|
|
75
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Add `fn` to the import, when nothing binds it yet.
|
|
153
|
+
*
|
|
154
|
+
* NOT latched: two reports insert identical text at the identical anchor, so
|
|
155
|
+
* ESLint applies one and drops the other as overlapping. Latching is not
|
|
156
|
+
* free — ESLint runs `fix()` for a SUPPRESSED problem before the
|
|
157
|
+
* `eslint-disable` filter discards it, so a latch could be spent by a report
|
|
158
|
+
* that is then thrown away.
|
|
159
|
+
*/
|
|
160
|
+
function importFix(fixer, fn) {
|
|
161
|
+
if (bound[fn]) return [];
|
|
162
|
+
if (utilsImportNode) {
|
|
163
|
+
return [fixer.insertTextAfter(utilsImportNode.specifiers.at(-1), `, ${fn}`)];
|
|
164
|
+
}
|
|
165
|
+
const newImport = `import { ${fn} } from '${targetModule}';\n`;
|
|
166
|
+
return [insertAboveWithComments(fixer, sourceCode, sourceCode.ast.body[0], newImport)];
|
|
167
|
+
}
|
|
168
|
+
|
|
76
169
|
return {
|
|
77
170
|
'Program:exit'() {
|
|
78
171
|
reportDeadUnsafeImports(
|
|
@@ -88,99 +181,30 @@ module.exports = {
|
|
|
88
181
|
if (PATH_MODULES.has(node.source.value)) {
|
|
89
182
|
pathImportNodes.push(node);
|
|
90
183
|
}
|
|
91
|
-
if (node.source.value
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
184
|
+
if (node.source.value !== targetModule) return;
|
|
185
|
+
utilsImportNode = node;
|
|
186
|
+
for (const spec of node.specifiers) {
|
|
187
|
+
if (spec.type === 'ImportSpecifier' && spec.imported.name in bound) {
|
|
188
|
+
bound[spec.imported.name] = true;
|
|
97
189
|
}
|
|
98
190
|
}
|
|
99
191
|
},
|
|
100
192
|
|
|
101
193
|
CallExpression(node) {
|
|
102
|
-
if (node.callee.type === 'Identifier' && node.callee.name ===
|
|
194
|
+
if (node.callee.type === 'Identifier' && node.callee.name === NATIVE_FN) {
|
|
103
195
|
safeReplacementCalled = true;
|
|
104
196
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
node
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
splitCall.type === 'CallExpression' &&
|
|
118
|
-
splitCall.callee.type === 'MemberExpression' &&
|
|
119
|
-
splitCall.callee.property.name === 'split' &&
|
|
120
|
-
splitCall.arguments.length === 1
|
|
121
|
-
) {
|
|
122
|
-
const splitArg = splitCall.arguments[0];
|
|
123
|
-
|
|
124
|
-
// Split on path.sep, or on a single backslash character
|
|
125
|
-
// (source literal '\\'). Splitting on a two-backslash SEQUENCE
|
|
126
|
-
// (source literal '\\\\', decoded value: two backslash characters)
|
|
127
|
-
// is a different, rarer operation -- e.g. collapsing a UNC path's
|
|
128
|
-
// leading double-backslash server prefix -- and toForwardSlash()
|
|
129
|
-
// is not equivalent to it. Autofixing that case would silently
|
|
130
|
-
// change program behavior, so it is deliberately excluded here.
|
|
131
|
-
const isSplittingByPathSep =
|
|
132
|
-
(splitArg.type === 'MemberExpression' &&
|
|
133
|
-
splitArg.object.name === 'path' &&
|
|
134
|
-
splitArg.property.name === 'sep') ||
|
|
135
|
-
(splitArg.type === 'Literal' && splitArg.value === '\\');
|
|
136
|
-
|
|
137
|
-
if (isSplittingByPathSep) {
|
|
138
|
-
const variableBeingSplit = splitCall.callee.object;
|
|
139
|
-
|
|
140
|
-
context.report({
|
|
141
|
-
node,
|
|
142
|
-
messageId: 'useToForwardSlash',
|
|
143
|
-
data: { safeModule: targetModule },
|
|
144
|
-
fix(fixer) {
|
|
145
|
-
const fixes = [];
|
|
146
|
-
|
|
147
|
-
// Replace the entire .split(...).join('/') with toForwardSlash(...)
|
|
148
|
-
const originalVar = sourceCode.getText(variableBeingSplit);
|
|
149
|
-
fixes.push(fixer.replaceText(node, `${SAFE_FN}(${originalVar})`));
|
|
150
|
-
|
|
151
|
-
// Add import if needed
|
|
152
|
-
if (!hasToForwardSlashImport) {
|
|
153
|
-
if (utilsImportNode) {
|
|
154
|
-
// Add to existing utils import
|
|
155
|
-
const lastSpecifier = utilsImportNode.specifiers.at(-1);
|
|
156
|
-
fixes.push(fixer.insertTextAfter(lastSpecifier, `, ${SAFE_FN}`));
|
|
157
|
-
} else {
|
|
158
|
-
// Create new import at the top
|
|
159
|
-
const firstNode = sourceCode.ast.body[0];
|
|
160
|
-
const newImport = `import { ${SAFE_FN} } from '${targetModule}';\n`;
|
|
161
|
-
fixes.push(insertAboveWithComments(fixer, sourceCode, firstNode, newImport));
|
|
162
|
-
}
|
|
163
|
-
// NOT latched. The comment here used to claim that without a
|
|
164
|
-
// `hasToForwardSlashImport = true` a second occurrence would
|
|
165
|
-
// insert the import twice; an adversarial run could not
|
|
166
|
-
// reproduce that at any occurrence count. It cannot happen:
|
|
167
|
-
// both reports insert identical text at the identical anchor,
|
|
168
|
-
// so the ranges coincide and ESLint applies one and drops the
|
|
169
|
-
// other as overlapping.
|
|
170
|
-
//
|
|
171
|
-
// Latching it is not free, either. ESLint runs `fix()` for a
|
|
172
|
-
// SUPPRESSED problem before the `eslint-disable` filter
|
|
173
|
-
// discards it, so the first report could spend the flag and
|
|
174
|
-
// then be thrown away — leaving later occurrences rewritten
|
|
175
|
-
// to a `toForwardSlash` nothing imports.
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return fixes;
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
197
|
+
const match = matchSplitJoin(node) ?? matchReplace(node);
|
|
198
|
+
if (!match) return;
|
|
199
|
+
context.report({
|
|
200
|
+
node,
|
|
201
|
+
messageId: MESSAGE_FOR[match.fn],
|
|
202
|
+
data: { safeModule: targetModule },
|
|
203
|
+
fix(fixer) {
|
|
204
|
+
const receiver = sourceCode.getText(match.receiver);
|
|
205
|
+
return [fixer.replaceText(node, `${match.fn}(${receiver})`), ...importFix(fixer, match.fn)];
|
|
206
|
+
},
|
|
207
|
+
});
|
|
184
208
|
},
|
|
185
209
|
};
|
|
186
210
|
},
|