vocs 2.0.6 → 2.0.8
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/dist/internal/mdx.d.ts +17 -0
- package/dist/internal/mdx.d.ts.map +1 -1
- package/dist/internal/mdx.js +56 -1
- package/dist/internal/mdx.js.map +1 -1
- package/dist/internal/shiki-transformers.d.ts +17 -0
- package/dist/internal/shiki-transformers.d.ts.map +1 -1
- package/dist/internal/shiki-transformers.js +30 -4
- package/dist/internal/shiki-transformers.js.map +1 -1
- package/dist/internal/twoslash/file-patcher.d.ts +24 -0
- package/dist/internal/twoslash/file-patcher.d.ts.map +1 -0
- package/dist/internal/twoslash/file-patcher.js +58 -0
- package/dist/internal/twoslash/file-patcher.js.map +1 -0
- package/dist/internal/twoslash/index.d.ts +1 -0
- package/dist/internal/twoslash/index.d.ts.map +1 -1
- package/dist/internal/twoslash/index.js +1 -0
- package/dist/internal/twoslash/index.js.map +1 -1
- package/dist/internal/twoslash/inline-cache.d.ts +71 -0
- package/dist/internal/twoslash/inline-cache.d.ts.map +1 -0
- package/dist/internal/twoslash/inline-cache.js +233 -0
- package/dist/internal/twoslash/inline-cache.js.map +1 -0
- package/dist/internal/vite-plugins.d.ts.map +1 -1
- package/dist/internal/vite-plugins.js +4 -0
- package/dist/internal/vite-plugins.js.map +1 -1
- package/package.json +3 -1
- package/src/internal/mdx.ts +60 -1
- package/src/internal/shiki-transformers.ts +40 -4
- package/src/internal/twoslash/file-patcher.ts +60 -0
- package/src/internal/twoslash/index.ts +1 -0
- package/src/internal/twoslash/inline-cache.test.ts +212 -0
- package/src/internal/twoslash/inline-cache.ts +313 -0
- package/src/internal/vite-plugins.ts +4 -0
package/src/internal/mdx.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { remarkVocsScope } from './remark-vocs-scope.js'
|
|
|
41
41
|
import { remarkSandbox } from './sandbox.js'
|
|
42
42
|
import * as ShikiTransformers from './shiki-transformers.js'
|
|
43
43
|
import * as Snippets from './snippets.js'
|
|
44
|
+
import * as InlineCache from './twoslash/inline-cache.js'
|
|
44
45
|
import type { ExactPartial, UnionOmit } from './types.js'
|
|
45
46
|
|
|
46
47
|
export { default as remarkFrontmatter } from 'remark-frontmatter'
|
|
@@ -121,6 +122,51 @@ export declare namespace remarkMermaid {
|
|
|
121
122
|
type ReturnType = (tree: MdAst.Root) => void
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Remark plugin that injects an ephemeral source-map comment into the body of
|
|
127
|
+
* each `twoslash` code block. The comment carries the block's source position
|
|
128
|
+
* (`{ path, from, to }`) so the inline types cache knows where to write the
|
|
129
|
+
* `// @twoslash-cache: ...` comment back to.
|
|
130
|
+
*
|
|
131
|
+
* The injected comment is stripped again by the `extractInlineCacheSourceMap`
|
|
132
|
+
* shiki transformer before twoslash/shiki run, so it never reaches the output
|
|
133
|
+
* or the on-disk file.
|
|
134
|
+
*
|
|
135
|
+
* Only top-level (non-indented) fences are supported, which covers the
|
|
136
|
+
* overwhelming majority of twoslash blocks.
|
|
137
|
+
*/
|
|
138
|
+
export function remarkInlineCache(): remarkInlineCache.ReturnType {
|
|
139
|
+
return (tree: MdAst.Root, file: VFile) => {
|
|
140
|
+
const raw = typeof file.value === 'string' ? file.value : file.value?.toString()
|
|
141
|
+
const filePath = file.path
|
|
142
|
+
if (!raw || !filePath) return
|
|
143
|
+
|
|
144
|
+
UnistUtil.visit(tree, 'code', (node) => {
|
|
145
|
+
if (!node.meta?.includes('twoslash')) return
|
|
146
|
+
|
|
147
|
+
const start = node.position?.start
|
|
148
|
+
const end = node.position?.end
|
|
149
|
+
if (start?.offset == null || end?.offset == null) return
|
|
150
|
+
// Only top-level fences (not nested in lists/blockquotes) are supported.
|
|
151
|
+
if (start.column !== 1) return
|
|
152
|
+
|
|
153
|
+
const newlineIndex = raw.indexOf('\n', start.offset)
|
|
154
|
+
if (newlineIndex === -1) return
|
|
155
|
+
const bodyStart = newlineIndex + 1
|
|
156
|
+
|
|
157
|
+
node.value = InlineCache.injectSourceMapComment(node.value, {
|
|
158
|
+
path: filePath,
|
|
159
|
+
from: bodyStart,
|
|
160
|
+
to: end.offset,
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export declare namespace remarkInlineCache {
|
|
167
|
+
type ReturnType = (tree: MdAst.Root, file: VFile) => void
|
|
168
|
+
}
|
|
169
|
+
|
|
124
170
|
const extensions = ['.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.md', '.mdx']
|
|
125
171
|
const logger = createLogger(undefined, { allowClearScreen: false, prefix: '[vocs]' })
|
|
126
172
|
|
|
@@ -247,6 +293,11 @@ export function getCompileOptions(
|
|
|
247
293
|
remarkSubheading,
|
|
248
294
|
remarkVocsScope,
|
|
249
295
|
...(markdown?.remarkPlugins ?? []),
|
|
296
|
+
// Runs after snippet/include processing so the injected source-map
|
|
297
|
+
// comment doesn't interfere with snippet detection.
|
|
298
|
+
...(twoslash && typeof twoslash === 'object' && InlineCache.enabled(twoslash.inlineCache)
|
|
299
|
+
? [remarkInlineCache]
|
|
300
|
+
: []),
|
|
250
301
|
remarkRestoreUnknownTextDirectives,
|
|
251
302
|
],
|
|
252
303
|
recmaPlugins: [recmaMdxLayout(config), ...(markdown?.recmaPlugins ?? [])],
|
|
@@ -487,6 +538,11 @@ export function rehypeShiki(
|
|
|
487
538
|
langs: mergedLangs,
|
|
488
539
|
// TODO: infer `langs` for faster cold start.
|
|
489
540
|
transformers: [
|
|
541
|
+
// Must run before the twoslash transformer so `this.meta.sourceMap` is
|
|
542
|
+
// available to the inline types cache.
|
|
543
|
+
twoslash && typeof twoslash === 'object' && InlineCache.enabled(twoslash.inlineCache)
|
|
544
|
+
? ShikiTransformers.extractInlineCacheSourceMap()
|
|
545
|
+
: undefined,
|
|
490
546
|
rootDir && srcDir ? ShikiTransformers.notationInclude({ srcDir, rootDir }) : undefined,
|
|
491
547
|
twoslash
|
|
492
548
|
? ShikiTransformers.twoslash({
|
|
@@ -1062,7 +1118,10 @@ function getVirtualFiles(codeNodes: MdAst.Code[]): Map<string, string> {
|
|
|
1062
1118
|
for (const node of codeNodes) {
|
|
1063
1119
|
const fileName = getCodeFileName(node)
|
|
1064
1120
|
if (!fileName) continue
|
|
1065
|
-
|
|
1121
|
+
// Strip any inline twoslash cache comment so it isn't inlined ahead of a
|
|
1122
|
+
// host block's code when this virtual file is imported/included. The
|
|
1123
|
+
// original node keeps its comment for its own rendering.
|
|
1124
|
+
virtualFiles.set(fileName, InlineCache.stripInlineCacheComments(node.value))
|
|
1066
1125
|
}
|
|
1067
1126
|
return virtualFiles
|
|
1068
1127
|
}
|
|
@@ -19,6 +19,7 @@ type TransformerTwoslashIndexOptions = TransformerTwoslashOptions
|
|
|
19
19
|
|
|
20
20
|
import * as Snippets from './snippets.js'
|
|
21
21
|
import * as TwoslashChecker from './twoslash/checker.js'
|
|
22
|
+
import * as InlineCache from './twoslash/inline-cache.js'
|
|
22
23
|
import * as Renderer from './twoslash/renderer.js'
|
|
23
24
|
import * as TypesCache from './twoslash/types-cache.js'
|
|
24
25
|
|
|
@@ -240,7 +241,7 @@ let typescript: TS | undefined
|
|
|
240
241
|
const twoslasherResetInterval = 200
|
|
241
242
|
|
|
242
243
|
export function twoslash(options: twoslash.Options): ShikiTransformer {
|
|
243
|
-
const { cacheDir } = options
|
|
244
|
+
const { cacheDir, inlineCache } = options
|
|
244
245
|
const {
|
|
245
246
|
checkOnly = false,
|
|
246
247
|
explicitTrigger = true,
|
|
@@ -252,7 +253,9 @@ export function twoslash(options: twoslash.Options): ShikiTransformer {
|
|
|
252
253
|
renderer = Renderer.rich(),
|
|
253
254
|
throws = true,
|
|
254
255
|
twoslashOptions,
|
|
255
|
-
typesCache =
|
|
256
|
+
typesCache = InlineCache.enabled(inlineCache)
|
|
257
|
+
? InlineCache.getOrCreate().typesCache
|
|
258
|
+
: TypesCache.fs({ dir: cacheDir ? path.join(cacheDir, 'twoslash') : undefined }),
|
|
256
259
|
} = options
|
|
257
260
|
|
|
258
261
|
const lazyTwoslasher = (
|
|
@@ -304,8 +307,13 @@ export function twoslash(options: twoslash.Options): ShikiTransformer {
|
|
|
304
307
|
executeOptions?: Parameters<TwoslashInstance>[2],
|
|
305
308
|
meta?: Parameters<NonNullable<typeof typesCache.preprocess>>[3],
|
|
306
309
|
) {
|
|
307
|
-
|
|
308
|
-
|
|
310
|
+
// Normalize before the inner preprocess so the cache key hashed on
|
|
311
|
+
// read matches the code hashed on write (which runs against the
|
|
312
|
+
// normalized output). Without this, snippets containing consecutive
|
|
313
|
+
// custom tag lines (`@log`/`@error`/`@warn`/`@annotate`) would never
|
|
314
|
+
// hit the cache.
|
|
315
|
+
const normalized = Renderer.normalizeCustomTagBlocks(code)
|
|
316
|
+
return typesCache.preprocess?.(normalized, lang, executeOptions, meta) ?? normalized
|
|
309
317
|
},
|
|
310
318
|
}
|
|
311
319
|
: undefined
|
|
@@ -344,6 +352,34 @@ export declare namespace twoslash {
|
|
|
344
352
|
*/
|
|
345
353
|
checkOnly?: boolean | undefined
|
|
346
354
|
cacheDir?: string | undefined
|
|
355
|
+
/**
|
|
356
|
+
* Persist twoslash results inline in the markdown source as
|
|
357
|
+
* `// @twoslash-cache: ...` comments so the cache travels with the repo
|
|
358
|
+
* (e.g. for fresh clones and CI). Overridden by the `TWOSLASH_INLINE_CACHE`
|
|
359
|
+
* environment variable.
|
|
360
|
+
*
|
|
361
|
+
* @default false
|
|
362
|
+
*/
|
|
363
|
+
inlineCache?: boolean | undefined
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Shiki transformer that extracts the ephemeral source-map comment injected by
|
|
369
|
+
* the inline-cache remark plugin, exposing it on `this.meta.sourceMap` for the
|
|
370
|
+
* inline types cache and stripping it from the code before twoslash runs.
|
|
371
|
+
*
|
|
372
|
+
* Must run before the twoslash transformer.
|
|
373
|
+
*/
|
|
374
|
+
export function extractInlineCacheSourceMap(): ShikiTransformer {
|
|
375
|
+
return {
|
|
376
|
+
name: 'vocs:twoslash-inline-cache-source-map',
|
|
377
|
+
enforce: 'pre',
|
|
378
|
+
preprocess(code) {
|
|
379
|
+
const { code: stripped, sourceMap } = InlineCache.extractSourceMapComment(code)
|
|
380
|
+
this.meta.sourceMap = sourceMap
|
|
381
|
+
return stripped
|
|
382
|
+
},
|
|
347
383
|
}
|
|
348
384
|
}
|
|
349
385
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as node_fs from 'node:fs'
|
|
2
|
+
import MagicString from 'magic-string'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Accumulates character-range patches per file and flushes them all at once.
|
|
6
|
+
*
|
|
7
|
+
* Used by the twoslash inline cache to write `// @twoslash-cache: ...` comments
|
|
8
|
+
* back into the original markdown source after a code block has been processed.
|
|
9
|
+
*
|
|
10
|
+
* Ported from `@shikijs/vitepress-twoslash`'s `FilePatcher`.
|
|
11
|
+
*/
|
|
12
|
+
export class FilePatcher {
|
|
13
|
+
private files = new Map<string, { content: string; patches: Map<string, string> } | null>()
|
|
14
|
+
|
|
15
|
+
/** Build a patch key from a character range. Omitting `to` denotes an insertion. */
|
|
16
|
+
static key(from: number, to?: number): string {
|
|
17
|
+
return `${from}${to ? `:${to}` : ''}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load a file's contents (cached for the lifetime of the patcher, until
|
|
22
|
+
* flushed via {@link patch}). Returns `null` if the file does not exist.
|
|
23
|
+
*/
|
|
24
|
+
load(path: string): { content: string; patches: Map<string, string> } | null {
|
|
25
|
+
let file = this.files.get(path)
|
|
26
|
+
if (file === undefined) {
|
|
27
|
+
if (node_fs.existsSync(path)) {
|
|
28
|
+
const content = node_fs.readFileSync(path, { encoding: 'utf-8' })
|
|
29
|
+
file = { content, patches: new Map() }
|
|
30
|
+
} else {
|
|
31
|
+
file = null
|
|
32
|
+
}
|
|
33
|
+
this.files.set(path, file)
|
|
34
|
+
}
|
|
35
|
+
return file
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Apply all queued patches for `path` and write the result back to disk. */
|
|
39
|
+
patch(path: string): void {
|
|
40
|
+
const file = this.files.get(path)
|
|
41
|
+
if (!file) return
|
|
42
|
+
|
|
43
|
+
if (file.patches.size) {
|
|
44
|
+
const s = new MagicString(file.content)
|
|
45
|
+
|
|
46
|
+
for (const [key, value] of file.patches) {
|
|
47
|
+
const [from, to] = key.split(':').map((x) => (x !== '' ? Number(x) : undefined))
|
|
48
|
+
if (from === undefined) continue
|
|
49
|
+
|
|
50
|
+
if (to !== undefined) s.update(from, to, value)
|
|
51
|
+
else s.appendRight(from, value)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = s.toString()
|
|
55
|
+
if (content !== file.content) node_fs.writeFileSync(path, content, { encoding: 'utf-8' })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.files.delete(path)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as os from 'node:os'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import {
|
|
6
|
+
createInlineTypesCache,
|
|
7
|
+
extractSourceMapComment,
|
|
8
|
+
injectSourceMapComment,
|
|
9
|
+
stripInlineCacheComments,
|
|
10
|
+
} from './inline-cache.js'
|
|
11
|
+
|
|
12
|
+
describe('source map codec', () => {
|
|
13
|
+
it('round-trips a source map through inject/extract', () => {
|
|
14
|
+
const sourceMap = { path: '/docs/a.md', from: 15, to: 42 }
|
|
15
|
+
const injected = injectSourceMapComment('const a = 1', sourceMap)
|
|
16
|
+
expect(injected.startsWith('// @vocs-twoslash-source:')).toBe(true)
|
|
17
|
+
|
|
18
|
+
const result = extractSourceMapComment(injected)
|
|
19
|
+
expect(result.code).toBe('const a = 1')
|
|
20
|
+
expect(result.sourceMap).toEqual(sourceMap)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns null source map when none present', () => {
|
|
24
|
+
const result = extractSourceMapComment('const a = 1')
|
|
25
|
+
expect(result.code).toBe('const a = 1')
|
|
26
|
+
expect(result.sourceMap).toBeNull()
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('stripInlineCacheComments', () => {
|
|
31
|
+
it('removes all cache comments', () => {
|
|
32
|
+
const code = [
|
|
33
|
+
'// @twoslash-cache: {"v":1,"hash":"abc","data":"x"}',
|
|
34
|
+
'export const client = 1',
|
|
35
|
+
].join('\n')
|
|
36
|
+
expect(stripInlineCacheComments(code)).toBe('export const client = 1')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('leaves code without cache comments untouched', () => {
|
|
40
|
+
const code = 'export const client = 1\nconst a = 2'
|
|
41
|
+
expect(stripInlineCacheComments(code)).toBe(code)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('inline types cache', () => {
|
|
46
|
+
const tmpFiles: string[] = []
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
for (const file of tmpFiles.splice(0)) fs.rmSync(file, { force: true })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
function createMarkdown(body: string) {
|
|
53
|
+
const file = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'vocs-inline-cache-')), 'page.md')
|
|
54
|
+
const md = `\`\`\`ts twoslash\n${body}\n\`\`\`\n`
|
|
55
|
+
fs.writeFileSync(file, md, 'utf-8')
|
|
56
|
+
tmpFiles.push(file)
|
|
57
|
+
// body starts right after the first newline (the opening fence line).
|
|
58
|
+
const from = md.indexOf('\n') + 1
|
|
59
|
+
return { file, md, from, to: md.length }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Pull the on-disk fence body the way shiki would see it (everything between
|
|
63
|
+
// the opening fence line and the closing fence).
|
|
64
|
+
function readBody(file: string, from: number) {
|
|
65
|
+
const content = fs.readFileSync(file, 'utf-8')
|
|
66
|
+
return content.slice(from, content.lastIndexOf('\n```'))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
it('writes a cache comment back to the source and reads it on the next pass', () => {
|
|
70
|
+
const code = 'const a: string = "x"'
|
|
71
|
+
const { file, from, to } = createMarkdown(code)
|
|
72
|
+
|
|
73
|
+
const data = { nodes: [], code: 'compiled-output' }
|
|
74
|
+
|
|
75
|
+
// --- Pass 1: cache miss -> twoslash runs -> write back ---
|
|
76
|
+
{
|
|
77
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
78
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
79
|
+
|
|
80
|
+
const preprocessed = typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
81
|
+
expect(preprocessed).toBe(code)
|
|
82
|
+
expect(typesCache.read(code, 'ts', {}, meta)).toBeNull()
|
|
83
|
+
|
|
84
|
+
typesCache.write(code, data as never, 'ts', {}, meta)
|
|
85
|
+
patcher.patch(file)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const written = fs.readFileSync(file, 'utf-8')
|
|
89
|
+
expect(written).toContain('// @twoslash-cache:')
|
|
90
|
+
|
|
91
|
+
// --- Pass 2: cache hit -> twoslash skipped ---
|
|
92
|
+
{
|
|
93
|
+
const { typesCache } = createInlineTypesCache()
|
|
94
|
+
const body = readBody(file, from)
|
|
95
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
96
|
+
|
|
97
|
+
const preprocessed = typesCache.preprocess?.(body, 'ts', {}, meta)
|
|
98
|
+
// the cache comment is stripped before twoslash would run
|
|
99
|
+
expect(preprocessed).toBe(code)
|
|
100
|
+
|
|
101
|
+
const cached = typesCache.read(body, 'ts', {}, meta)
|
|
102
|
+
expect(cached).toEqual(data)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('replaces an existing cache comment in place instead of appending a duplicate', () => {
|
|
107
|
+
const code = 'const a: string = "x"'
|
|
108
|
+
const { file, from, to } = createMarkdown(code)
|
|
109
|
+
const data = { nodes: [], code: 'compiled-output' }
|
|
110
|
+
|
|
111
|
+
// --- Pass 1: cold write ---
|
|
112
|
+
{
|
|
113
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
114
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
115
|
+
typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
116
|
+
typesCache.write(code, data as never, 'ts', {}, meta)
|
|
117
|
+
patcher.patch(file)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const cacheLines = () =>
|
|
121
|
+
fs
|
|
122
|
+
.readFileSync(file, 'utf-8')
|
|
123
|
+
.split('\n')
|
|
124
|
+
.filter((l) => l.includes('// @twoslash-cache:'))
|
|
125
|
+
expect(cacheLines()).toHaveLength(1)
|
|
126
|
+
|
|
127
|
+
// --- Pass 2: a writer with stale in-memory code (no cache comment, so the
|
|
128
|
+
// existing comment can't be located via `search`) must still replace the
|
|
129
|
+
// on-disk comment rather than append a second one. ---
|
|
130
|
+
{
|
|
131
|
+
const content = fs.readFileSync(file, 'utf-8')
|
|
132
|
+
const currentTo = content.lastIndexOf('\n```')
|
|
133
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
134
|
+
const meta = { sourceMap: { path: file, from, to: currentTo } } as never
|
|
135
|
+
// Pass the original (comment-free) code to simulate a stale read.
|
|
136
|
+
typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
137
|
+
typesCache.write(code, data as never, 'ts', {}, meta)
|
|
138
|
+
patcher.patch(file)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
expect(cacheLines()).toHaveLength(1)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('invalidates the cache when the hash no longer matches', () => {
|
|
145
|
+
const code = 'const a: string = "x"'
|
|
146
|
+
const { file, from, to } = createMarkdown(code)
|
|
147
|
+
|
|
148
|
+
// Seed a cache entry.
|
|
149
|
+
{
|
|
150
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
151
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
152
|
+
typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
153
|
+
typesCache.write(code, { nodes: [], code: 'x' } as never, 'ts', {}, meta)
|
|
154
|
+
patcher.patch(file)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Read with different code -> hash mismatch -> miss.
|
|
158
|
+
{
|
|
159
|
+
const { typesCache } = createInlineTypesCache()
|
|
160
|
+
const body = readBody(file, from)
|
|
161
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
162
|
+
// The body still has the old cache line, but we validate against new code.
|
|
163
|
+
typesCache.preprocess?.(`${body}\nconst b = 2`, 'ts', {}, meta)
|
|
164
|
+
expect(typesCache.read(body, 'ts', {}, meta)).toBeNull()
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('ignoreCache skips reading existing cache', () => {
|
|
169
|
+
const code = 'const a: string = "x"'
|
|
170
|
+
const { file, from, to } = createMarkdown(code)
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
174
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
175
|
+
typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
176
|
+
typesCache.write(code, { nodes: [], code: 'x' } as never, 'ts', {}, meta)
|
|
177
|
+
patcher.patch(file)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
const { typesCache } = createInlineTypesCache({ ignoreCache: true })
|
|
182
|
+
const body = readBody(file, from)
|
|
183
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
184
|
+
typesCache.preprocess?.(body, 'ts', {}, meta)
|
|
185
|
+
expect(typesCache.read(body, 'ts', {}, meta)).toBeNull()
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('remove mode strips the cache comment from the source', () => {
|
|
190
|
+
const code = 'const a: string = "x"'
|
|
191
|
+
const { file, from, to } = createMarkdown(code)
|
|
192
|
+
|
|
193
|
+
{
|
|
194
|
+
const { typesCache, patcher } = createInlineTypesCache()
|
|
195
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
196
|
+
typesCache.preprocess?.(code, 'ts', {}, meta)
|
|
197
|
+
typesCache.write(code, { nodes: [], code: 'x' } as never, 'ts', {}, meta)
|
|
198
|
+
patcher.patch(file)
|
|
199
|
+
}
|
|
200
|
+
expect(fs.readFileSync(file, 'utf-8')).toContain('// @twoslash-cache:')
|
|
201
|
+
|
|
202
|
+
{
|
|
203
|
+
const { typesCache, patcher } = createInlineTypesCache({ remove: true })
|
|
204
|
+
const body = readBody(file, from)
|
|
205
|
+
const meta = { sourceMap: { path: file, from, to } } as never
|
|
206
|
+
typesCache.preprocess?.(body, 'ts', {}, meta)
|
|
207
|
+
typesCache.write(body, { nodes: [], code: 'x' } as never, 'ts', {}, meta)
|
|
208
|
+
patcher.patch(file)
|
|
209
|
+
}
|
|
210
|
+
expect(fs.readFileSync(file, 'utf-8')).not.toContain('// @twoslash-cache:')
|
|
211
|
+
})
|
|
212
|
+
})
|