@tsdoctor/vfs 0.4.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TsconfigParser.js +2 -1
- package/TwoslashCache.js +30 -16
- package/TypeScriptConfig.js +3 -2
- package/index.d.ts +11 -5
- package/package.json +7 -3
- package/tsdoc-metadata.json +1 -1
package/TsconfigParser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { decodeCompilerOptions } from "./TypeResolutionOptions.js";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { ErrorText } from "@effected/engine";
|
|
4
5
|
import { TsconfigLoaderSync } from "@effected/tsconfig-json";
|
|
5
6
|
import { Result } from "effect";
|
|
6
7
|
|
|
@@ -87,7 +88,7 @@ function parseTsConfig(configPath, projectRoot) {
|
|
|
87
88
|
try {
|
|
88
89
|
options = TsconfigLoaderSync.compilerOptions(absolutePath, syncHost);
|
|
89
90
|
} catch (error) {
|
|
90
|
-
throw new TsConfigParseError(absolutePath,
|
|
91
|
+
throw new TsConfigParseError(absolutePath, ErrorText.describe(error), error);
|
|
91
92
|
}
|
|
92
93
|
const decoded = decodeCompilerOptions(options);
|
|
93
94
|
if (Result.isFailure(decoded)) throw new TsConfigParseError(absolutePath, decoded.failure.message, decoded.failure);
|
package/TwoslashCache.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Result } from "effect";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
4
|
+
import { JsoncCanonicalizeOptions, JsoncFingerprint } from "@effected/jsonc";
|
|
3
5
|
|
|
4
6
|
// src/TwoslashCache.ts
|
|
5
7
|
/**
|
|
@@ -62,10 +64,16 @@ import { gunzipSync, gzipSync } from "node:zlib";
|
|
|
62
64
|
*
|
|
63
65
|
* @public
|
|
64
66
|
*/
|
|
65
|
-
const TWOSLASH_CACHE_FORMAT =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const TWOSLASH_CACHE_FORMAT = 2;
|
|
68
|
+
const digest = (bytes) => createHash("sha256").update(bytes).digest();
|
|
69
|
+
/**
|
|
70
|
+
* Compiler options are TypeScript's own plain JSON shape, but a member may be
|
|
71
|
+
* present and `undefined`; those are omitted, as `JSON.stringify` would.
|
|
72
|
+
*/
|
|
73
|
+
const COMPILER_OPTIONS_CANONICALIZATION = JsoncCanonicalizeOptions.make({
|
|
74
|
+
undefined: "omit",
|
|
75
|
+
nonFinite: "null"
|
|
76
|
+
});
|
|
69
77
|
/**
|
|
70
78
|
* Fingerprint the type environment a generation is checked against.
|
|
71
79
|
*
|
|
@@ -88,28 +96,31 @@ function sha256(input) {
|
|
|
88
96
|
*/
|
|
89
97
|
function twoslashEnvHash(vfs, toolchain) {
|
|
90
98
|
const hash = createHash("sha256");
|
|
91
|
-
hash.update(`format:${
|
|
99
|
+
hash.update(`format:${2}\0toolchain:${toolchain}\0`);
|
|
92
100
|
for (const key of [...vfs.keys()].sort()) hash.update(`${key}\0${vfs.get(key) ?? ""}\0`);
|
|
93
101
|
return hash.digest("hex");
|
|
94
102
|
}
|
|
95
|
-
/** JSON with object keys sorted, so equivalent options hash identically. */
|
|
96
|
-
function stableStringify(value) {
|
|
97
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
98
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
99
|
-
return `{${Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
100
|
-
}
|
|
101
103
|
/**
|
|
102
104
|
* Per-entry key: the code, its language, and the compiler configuration it is
|
|
103
105
|
* checked under.
|
|
104
106
|
*
|
|
105
107
|
* The configuration matters because two APIs on one site may be documented
|
|
106
108
|
* under different `tsconfig`s — the same source checked under different options
|
|
107
|
-
* can produce different types, so it must not share a cache entry.
|
|
109
|
+
* can produce different types, so it must not share a cache entry. The options
|
|
110
|
+
* are folded in through their RFC 8785 canonical form, so key order does not
|
|
111
|
+
* matter.
|
|
112
|
+
*
|
|
113
|
+
* Returns `undefined` when the options cannot be canonicalized (a function or a
|
|
114
|
+
* non-plain object among them): such a block is not cached, rather than keyed
|
|
115
|
+
* on a spelling that could collide with different options.
|
|
108
116
|
*
|
|
109
117
|
* @public
|
|
110
118
|
*/
|
|
111
119
|
function twoslashEntryKey(code, lang, compilerOptions) {
|
|
112
|
-
|
|
120
|
+
const options = JsoncFingerprint.canonicalizeResult(compilerOptions ?? {}, COMPILER_OPTIONS_CANONICALIZATION);
|
|
121
|
+
if (Result.isFailure(options)) return void 0;
|
|
122
|
+
const key = JsoncFingerprint.hashTextResult(`${lang ?? "ts"}\0${options.success}\0${code}`, digest);
|
|
123
|
+
return Result.isSuccess(key) ? key.success : void 0;
|
|
113
124
|
}
|
|
114
125
|
/**
|
|
115
126
|
* The cache key a whole generation is stored under. One blob per environment,
|
|
@@ -118,7 +129,7 @@ function twoslashEntryKey(code, lang, compilerOptions) {
|
|
|
118
129
|
* @public
|
|
119
130
|
*/
|
|
120
131
|
function twoslashBlobKey(envHash) {
|
|
121
|
-
return `twoslash/v${
|
|
132
|
+
return `twoslash/v${2}/${envHash}`;
|
|
122
133
|
}
|
|
123
134
|
/**
|
|
124
135
|
* Build a synchronous Twoslash cache over an in-memory map, optionally seeded
|
|
@@ -133,7 +144,8 @@ function makeTwoslashCache(initial) {
|
|
|
133
144
|
let dirty = false;
|
|
134
145
|
return {
|
|
135
146
|
read: (code, lang, options) => {
|
|
136
|
-
const
|
|
147
|
+
const key = twoslashEntryKey(code, lang, options?.compilerOptions);
|
|
148
|
+
const found = key === void 0 ? void 0 : map.get(key);
|
|
137
149
|
if (found === void 0) {
|
|
138
150
|
misses += 1;
|
|
139
151
|
return null;
|
|
@@ -142,12 +154,14 @@ function makeTwoslashCache(initial) {
|
|
|
142
154
|
return found;
|
|
143
155
|
},
|
|
144
156
|
write: (code, data, lang, options) => {
|
|
157
|
+
const key = twoslashEntryKey(code, lang, options?.compilerOptions);
|
|
158
|
+
if (key === void 0) return;
|
|
145
159
|
const value = {
|
|
146
160
|
nodes: data.nodes,
|
|
147
161
|
code: data.code,
|
|
148
162
|
...data.meta?.extension != null ? { meta: { extension: data.meta.extension } } : {}
|
|
149
163
|
};
|
|
150
|
-
map.set(
|
|
164
|
+
map.set(key, value);
|
|
151
165
|
dirty = true;
|
|
152
166
|
},
|
|
153
167
|
stats: () => ({
|
package/TypeScriptConfig.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { decodeCompilerOptions } from "./TypeResolutionOptions.js";
|
|
2
2
|
import { TsConfigParseError, parseTsConfig } from "./TsconfigParser.js";
|
|
3
|
+
import { ErrorText } from "@effected/engine";
|
|
3
4
|
import { Result } from "effect";
|
|
4
5
|
|
|
5
6
|
// src/TypeScriptConfig.ts
|
|
@@ -120,7 +121,7 @@ function resolveTypeScriptConfigSingle(config, projectRoot) {
|
|
|
120
121
|
options = parseTsConfig(tsconfigPath, projectRoot);
|
|
121
122
|
} catch (error) {
|
|
122
123
|
if (error instanceof TsConfigParseError) throw error;
|
|
123
|
-
throw new TsConfigParseError(tsconfigPath,
|
|
124
|
+
throw new TsConfigParseError(tsconfigPath, ErrorText.describe(error), error);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
if (config.compilerOptions) options = mergeCompilerOptions(options, decodeInput(config.compilerOptions, "compilerOptions"));
|
|
@@ -168,7 +169,7 @@ async function resolveTypeScriptConfigSingleAsync(config, projectRoot) {
|
|
|
168
169
|
options = parseTsConfig(tsconfigPath, projectRoot);
|
|
169
170
|
} catch (error) {
|
|
170
171
|
if (error instanceof TsConfigParseError) throw error;
|
|
171
|
-
throw new TsConfigParseError(tsconfigPath,
|
|
172
|
+
throw new TsConfigParseError(tsconfigPath, ErrorText.describe(error), error);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
}
|
package/index.d.ts
CHANGED
|
@@ -94,8 +94,8 @@ export declare const toProgrammaticCompilerOptions: (options: TypeResolutionComp
|
|
|
94
94
|
*/
|
|
95
95
|
export declare class TsConfigParseError extends Error {
|
|
96
96
|
readonly configPath: string;
|
|
97
|
-
readonly cause?: unknown
|
|
98
|
-
constructor(configPath: string, message: string, cause?: unknown
|
|
97
|
+
readonly cause?: unknown;
|
|
98
|
+
constructor(configPath: string, message: string, cause?: unknown);
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Parse a `tsconfig.json` and extract the compiler options used for type
|
|
@@ -305,7 +305,7 @@ export declare class TsEnvironment {
|
|
|
305
305
|
*
|
|
306
306
|
* @public
|
|
307
307
|
*/
|
|
308
|
-
export declare const TWOSLASH_CACHE_FORMAT =
|
|
308
|
+
export declare const TWOSLASH_CACHE_FORMAT = 2;
|
|
309
309
|
/**
|
|
310
310
|
* The subset of a Twoslash run that Shiki consumes, and all this cache stores.
|
|
311
311
|
*
|
|
@@ -367,11 +367,17 @@ export declare function twoslashEnvHash(vfs: ReadonlyMap<string, string>, toolch
|
|
|
367
367
|
*
|
|
368
368
|
* The configuration matters because two APIs on one site may be documented
|
|
369
369
|
* under different `tsconfig`s — the same source checked under different options
|
|
370
|
-
* can produce different types, so it must not share a cache entry.
|
|
370
|
+
* can produce different types, so it must not share a cache entry. The options
|
|
371
|
+
* are folded in through their RFC 8785 canonical form, so key order does not
|
|
372
|
+
* matter.
|
|
373
|
+
*
|
|
374
|
+
* Returns `undefined` when the options cannot be canonicalized (a function or a
|
|
375
|
+
* non-plain object among them): such a block is not cached, rather than keyed
|
|
376
|
+
* on a spelling that could collide with different options.
|
|
371
377
|
*
|
|
372
378
|
* @public
|
|
373
379
|
*/
|
|
374
|
-
export declare function twoslashEntryKey(code: string, lang: string | undefined, compilerOptions?: unknown): string;
|
|
380
|
+
export declare function twoslashEntryKey(code: string, lang: string | undefined, compilerOptions?: unknown): string | undefined;
|
|
375
381
|
/**
|
|
376
382
|
* The cache key a whole generation is stored under. One blob per environment,
|
|
377
383
|
* so a changed environment reads as a miss rather than serving stale results.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsdoctor/vfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Virtual TypeScript projects for documentation tooling: the Vfs currency type, declaration-backed virtual packages, @typescript/vfs environments, and the compiler-option resolution that configures them.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,11 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@effected/engine": "^0.11.0",
|
|
41
|
+
"@effected/jsonc": "^0.22.0"
|
|
42
|
+
},
|
|
39
43
|
"peerDependencies": {
|
|
40
|
-
"@effected/tsconfig-json": "^0.
|
|
44
|
+
"@effected/tsconfig-json": "^0.19.0",
|
|
41
45
|
"@shikijs/twoslash": "^4.4.3",
|
|
42
46
|
"@typescript/vfs": "^1.6.5",
|
|
43
|
-
"effect": "
|
|
47
|
+
"effect": "~4.1.1",
|
|
44
48
|
"typescript": "^6.0.3"
|
|
45
49
|
},
|
|
46
50
|
"peerDependenciesMeta": {
|