From 9b10b755879f757123b73b38a4eb1ba9e6043611 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 11:27:12 +0200 Subject: [PATCH 1/7] add twoslash-svelte --- packages/site-kit/package.json | 2 + .../site-kit/src/lib/markdown/renderer.ts | 2 + pnpm-lock.yaml | 54 +++++++++++++++++++ pnpm-workspace.yaml | 1 + 4 files changed, 59 insertions(+) diff --git a/packages/site-kit/package.json b/packages/site-kit/package.json index 7cc9cb2cbc..bd440f5354 100644 --- a/packages/site-kit/package.json +++ b/packages/site-kit/package.json @@ -57,6 +57,8 @@ "prettier-plugin-svelte": "^3.5.0", "svelte": "^5.53.5", "svelte-check": "^4.5.0", + "svelte2tsx": "^0.7.56", + "twoslash-svelte": "^0.0.0", "typescript": "^6.0.3", "vite": "^8.0.0-beta.15" }, diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index bf5fee6978..b5109640d1 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -9,6 +9,7 @@ import { createHighlighterCore } from 'shiki/core'; import { createOnigurumaEngine } from 'shiki/engine/oniguruma'; import { createCssVariablesTheme } from 'shiki'; import { transformerTwoslash, rendererRich } from '@shikijs/twoslash'; +import { createTwoslasher } from 'twoslash-svelte'; import { createFileSystemTypesCache } from '@shikijs/vitepress-twoslash/cache-fs'; import { compress_and_encode_text } from 'gzip'; import { @@ -1150,6 +1151,7 @@ async function syntax_highlight({ ? [ transformerTwoslash({ renderer: rendererRich(), + twoslasher: createTwoslasher(), twoslashOptions: { compilerOptions: { allowJs: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dec8fcfa2f..c4a1db6489 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -458,6 +458,12 @@ importers: svelte-check: specifier: ^4.5.0 version: 4.5.0(picomatch@4.0.3)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + svelte2tsx: + specifier: ^0.7.56 + version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + twoslash-svelte: + specifier: ^0.0.0 + version: 0.0.0(svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3))(typescript@6.0.3) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -3258,6 +3264,12 @@ packages: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 typescript: ^4.9.4 || ^5.0.0 + svelte2tsx@0.7.56: + resolution: {integrity: sha512-NTvqqL+goYlW8gWNajk81L07+uu7jw5V2m1Az5MZbYm3GEydcHXh+uTrLHM9SuGuaqCtF90vlMXkOVBotfH94g==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 || ^6.0.0 + svelte@5.55.8: resolution: {integrity: sha512-4D6lyrMHmDaZalQOEBMCWCCidyZjSnec14/oPn0k627G6goxcck9xqMwz1tFLlQz+ZFvtTTHfFOlUayuAz0z6Q==} engines: {node: '>=18'} @@ -3333,6 +3345,15 @@ packages: twoslash-protocol@0.3.6: resolution: {integrity: sha512-FHGsJ9Q+EsNr5bEbgG3hnbkvEBdW5STgPU824AHUjB4kw0Dn4p8tABT7Ncg1Ie6V0+mDg3Qpy41VafZXcQhWMA==} + twoslash-protocol@0.3.8: + resolution: {integrity: sha512-HmvAHoiEviK8LqvAQyc9/irkdvwTUiR1fHmNwH/0gq8EHxyBt4PWVPixjEXg6wJu1u6yBrILEWXGK9Kw58/8yQ==} + + twoslash-svelte@0.0.0: + resolution: {integrity: sha512-xIVdQ0HFX9IGznj48V/O5MymKMLkxJtsUjUkZ2rdjR2/bWkQyc8VohC3pvKbLkAl0xOzPNUvBRNaBe9zKFofMw==} + peerDependencies: + svelte2tsx: '*' + typescript: '*' + twoslash-vue@0.3.6: resolution: {integrity: sha512-HXYxU+Y7jZiMXJN4980fQNMYflLD8uqKey1qVW5ri8bqYTm2t5ILmOoCOli7esdCHlMq4/No3iQUWBWDhZNs9w==} peerDependencies: @@ -3343,6 +3364,11 @@ packages: peerDependencies: typescript: ^5.5.0 + twoslash@0.3.8: + resolution: {integrity: sha512-OeDz0kDl8sqPUN3nr7gqcvOs70f5lZsdhKYTX3/SgB9OvdadzzoYJI/4SBXhXV1HG8E9fLc+e17itoRYTxmoig==} + peerDependencies: + typescript: ^5.5.0 || ^6.0.0 + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -6562,6 +6588,13 @@ snapshots: svelte: 5.55.8(@typescript-eslint/types@8.58.0) typescript: 6.0.3 + svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3): + dependencies: + dedent-js: 1.0.1 + scule: 1.3.0 + svelte: 5.55.8(@typescript-eslint/types@8.58.0) + typescript: 6.0.3 + svelte@5.55.8(@typescript-eslint/types@8.58.0): dependencies: '@jridgewell/remapping': 2.3.5 @@ -6648,6 +6681,19 @@ snapshots: twoslash-protocol@0.3.6: {} + twoslash-protocol@0.3.8: {} + + twoslash-svelte@0.0.0(svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3))(typescript@6.0.3): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/language-core': 2.4.28 + svelte2tsx: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + twoslash: 0.3.8(typescript@6.0.3) + twoslash-protocol: 0.3.8 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + twoslash-vue@0.3.6(typescript@6.0.3): dependencies: '@vue/language-core': 3.2.5 @@ -6665,6 +6711,14 @@ snapshots: transitivePeerDependencies: - supports-color + twoslash@0.3.8(typescript@6.0.3): + dependencies: + '@typescript/vfs': 1.6.4(typescript@6.0.3) + twoslash-protocol: 0.3.8 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + typescript@6.0.3: {} uc.micro@2.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bcfba92078..b94601a344 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ minimumReleaseAgeExclude: - prettier-plugin-svelte - svelte-check - esm-env + - twoslash-svelte # NOTE: Remove this exclusion before this PR is merged blockExoticSubdeps: true packages: From c73bc58ae61b8059a93f2d2de3f4df2f36755c1b Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 11:41:30 +0200 Subject: [PATCH 2/7] remove svelte2tsx for now --- packages/site-kit/src/lib/markdown/renderer.ts | 2 +- pnpm-lock.yaml | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index b5109640d1..4ef32c2b67 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -1131,7 +1131,7 @@ async function syntax_highlight({ theme }) ); - } else if (language === 'js' || language === 'ts') { + } else if (language === 'js' || language === 'ts' || language === 'svelte') { /** We need to stash code wrapped in `---` highlights, because otherwise TS will error on e.g. bad syntax, duplicate declarations */ const redactions: string[] = []; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4a1db6489..066410dcc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3258,12 +3258,6 @@ packages: typescript: optional: true - svelte2tsx@0.7.51: - resolution: {integrity: sha512-YbVMQi5LtQkVGOMdATTY8v3SMtkNjzYtrVDGaN3Bv+0LQ47tGXu/Oc8ryTkcYuEJWTZFJ8G2+2I8ORcQVGt9Ag==} - peerDependencies: - svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 - typescript: ^4.9.4 || ^5.0.0 - svelte2tsx@0.7.56: resolution: {integrity: sha512-NTvqqL+goYlW8gWNajk81L07+uu7jw5V2m1Az5MZbYm3GEydcHXh+uTrLHM9SuGuaqCtF90vlMXkOVBotfH94g==} peerDependencies: @@ -4962,7 +4956,7 @@ snapshots: sade: 1.8.1 semver: 7.7.4 svelte: 5.55.8(@typescript-eslint/types@8.58.0) - svelte2tsx: 0.7.51(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + svelte2tsx: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) transitivePeerDependencies: - typescript @@ -6581,13 +6575,6 @@ snapshots: postcss: 8.5.6 typescript: 6.0.3 - svelte2tsx@0.7.51(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3): - dependencies: - dedent-js: 1.0.1 - scule: 1.3.0 - svelte: 5.55.8(@typescript-eslint/types@8.58.0) - typescript: 6.0.3 - svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3): dependencies: dedent-js: 1.0.1 From c50e983d7f8b1c9992072f9a099fc17038048c7f Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 12:00:27 +0200 Subject: [PATCH 3/7] add svelte2tsx back --- apps/svelte.dev/package.json | 1 + pnpm-lock.yaml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/svelte.dev/package.json b/apps/svelte.dev/package.json index addb92b122..87e0457bc0 100644 --- a/apps/svelte.dev/package.json +++ b/apps/svelte.dev/package.json @@ -78,6 +78,7 @@ "svelte": "^5.55.8", "svelte-check": "^4.5.0", "svelte-preprocess": "^6.0.3", + "svelte2tsx": "^0.7.56", "tiny-glob": "^0.2.9", "typescript": "^6.0.3", "valibot": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 066410dcc6..70e456f1a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,6 +192,9 @@ importers: svelte-preprocess: specifier: ^6.0.3 version: 6.0.3(postcss@8.5.6)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + svelte2tsx: + specifier: ^0.7.56 + version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) tiny-glob: specifier: ^0.2.9 version: 0.2.9 @@ -457,7 +460,7 @@ importers: version: 5.55.8(@typescript-eslint/types@8.58.0) svelte-check: specifier: ^4.5.0 - version: 4.5.0(picomatch@4.0.3)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + version: 4.5.0(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) svelte2tsx: specifier: ^0.7.56 version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) From 65d11ba502202ff8073cd7e4edc77340564ad4f5 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 13:49:54 +0200 Subject: [PATCH 4/7] fix --- apps/svelte.dev/package.json | 1 - pnpm-lock.yaml | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/svelte.dev/package.json b/apps/svelte.dev/package.json index 87e0457bc0..addb92b122 100644 --- a/apps/svelte.dev/package.json +++ b/apps/svelte.dev/package.json @@ -78,7 +78,6 @@ "svelte": "^5.55.8", "svelte-check": "^4.5.0", "svelte-preprocess": "^6.0.3", - "svelte2tsx": "^0.7.56", "tiny-glob": "^0.2.9", "typescript": "^6.0.3", "valibot": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70e456f1a6..066410dcc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,9 +192,6 @@ importers: svelte-preprocess: specifier: ^6.0.3 version: 6.0.3(postcss@8.5.6)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) - svelte2tsx: - specifier: ^0.7.56 - version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) tiny-glob: specifier: ^0.2.9 version: 0.2.9 @@ -460,7 +457,7 @@ importers: version: 5.55.8(@typescript-eslint/types@8.58.0) svelte-check: specifier: ^4.5.0 - version: 4.5.0(picomatch@4.0.4)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) + version: 4.5.0(picomatch@4.0.3)(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) svelte2tsx: specifier: ^0.7.56 version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) From 540a32ecc4608e373cc5c27d2460ee037f101b61 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 18:32:15 +0200 Subject: [PATCH 5/7] repalce twoslash-svelte with local file for testing purposes --- packages/site-kit/package.json | 5 +- .../site-kit/src/lib/markdown/renderer.ts | 2 +- .../src/lib/markdown/twoslash-svelte.ts | 214 ++++++++++++++++++ pnpm-lock.yaml | 38 ++-- 4 files changed, 234 insertions(+), 25 deletions(-) create mode 100644 packages/site-kit/src/lib/markdown/twoslash-svelte.ts diff --git a/packages/site-kit/package.json b/packages/site-kit/package.json index bd440f5354..e6fcb8da6d 100644 --- a/packages/site-kit/package.json +++ b/packages/site-kit/package.json @@ -46,9 +46,11 @@ "shiki": "^3.23.0" }, "devDependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5", "@shikijs/vitepress-twoslash": "^4.0.1", "@sveltejs/kit": "^3.0.0-next.1", "@types/node": "^20.19.33", + "@volar/language-core": "^2.4.28", "flexsearch": "^0.8.212", "gzip": "workspace:*", "magic-string": "^0.30.21", @@ -58,7 +60,8 @@ "svelte": "^5.53.5", "svelte-check": "^4.5.0", "svelte2tsx": "^0.7.56", - "twoslash-svelte": "^0.0.0", + "twoslash": "^0.3.8", + "twoslash-protocol": "^0.3.8", "typescript": "^6.0.3", "vite": "^8.0.0-beta.15" }, diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index 4ef32c2b67..bb046f8bf6 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -9,7 +9,7 @@ import { createHighlighterCore } from 'shiki/core'; import { createOnigurumaEngine } from 'shiki/engine/oniguruma'; import { createCssVariablesTheme } from 'shiki'; import { transformerTwoslash, rendererRich } from '@shikijs/twoslash'; -import { createTwoslasher } from 'twoslash-svelte'; +import { createTwoslasher } from './twoslash-svelte.ts'; import { createFileSystemTypesCache } from '@shikijs/vitepress-twoslash/cache-fs'; import { compress_and_encode_text } from 'gzip'; import { diff --git a/packages/site-kit/src/lib/markdown/twoslash-svelte.ts b/packages/site-kit/src/lib/markdown/twoslash-svelte.ts new file mode 100644 index 0000000000..fba83d501f --- /dev/null +++ b/packages/site-kit/src/lib/markdown/twoslash-svelte.ts @@ -0,0 +1,214 @@ +// @ts-nocheck + +import { createRequire } from 'node:module'; +import { decode } from '@jridgewell/sourcemap-codec'; +import { SourceMap } from '@volar/language-core'; +import { svelte2tsx } from 'svelte2tsx'; +import { createTwoslasher as createTwoslasher$1, defaultCompilerOptions, defaultHandbookOptions, findQueryMarkers, findFlagNotations } from 'twoslash'; +import { createPositionConverter, removeCodeRanges, resolveNodePositions } from 'twoslash-protocol'; +import ts from 'typescript'; + +function createTwoslasher(createOptions = {}) { + const require = createRequire(import.meta.url); + const twoslasherBase = createTwoslasher$1(createOptions); + function twoslasher(code, extension, options = {}) { + if (extension !== "svelte") { + return twoslasherBase(code, extension, options); + } + const compilerOptions = { + ...defaultCompilerOptions, + ...options.compilerOptions + }; + const handbookOptions = { + ...defaultHandbookOptions, + noErrorsCutted: true, + ...options.handbookOptions + }; + const sourceMeta = findQueryMarkers(code, { + removals: [], + positionCompletions: [], + positionQueries: [], + positionHighlights: [] + }, createPositionConverter(code)); + const customTags = options.customTags ?? createOptions.customTags ?? []; + const optionDeclarations = ts.optionDeclarations; + const flagNotations = findFlagNotations(code, customTags, optionDeclarations); + for (const flag of flagNotations) { + switch (flag.type) { + case "unknown": { + continue; + } + case "compilerOptions": { + compilerOptions[flag.name] = flag.value; + break; + } + case "handbookOptions": { + handbookOptions[flag.name] = flag.value; + break; + } + } + sourceMeta.removals.push([flag.start, flag.end]); + } + let strippedCode = code; + for (const [start, end] of sourceMeta.removals) { + strippedCode = strippedCode.slice(0, start) + strippedCode.slice(start, end).replace(/\S/g, " ") + strippedCode.slice(end); + } + const compiled = svelte2tsx(strippedCode); + const map = generateSourceMap(strippedCode, compiled.code, compiled.map.mappings); + function getLastGeneratedOffset(pos) { + const offsets = [...map.toGeneratedLocation(pos)]; + if (!offsets.length) { + return void 0; + } + return offsets[offsets.length - 1]?.[0]; + } + const result = twoslasherBase(compiled.code, "tsx", { + ...options, + compilerOptions: { + ...compilerOptions, + types: [ + ...compilerOptions.types ?? [], + require.resolve(`svelte2tsx/svelte-jsx-v4.d.ts`), + require.resolve(`svelte2tsx/svelte-shims-v4.d.ts`) + ] + }, + handbookOptions: { + ...handbookOptions, + keepNotations: true + }, + positionCompletions: sourceMeta.positionCompletions.map((p) => getLastGeneratedOffset(p)), + positionQueries: sourceMeta.positionQueries.map((p) => get(map.toGeneratedLocation(p), 0)?.[0]).filter((value) => value != null), + positionHighlights: sourceMeta.positionHighlights.map(([start, end]) => [ + get(map.toGeneratedLocation(start), 0)?.[0], + get(map.toGeneratedLocation(end), 0)?.[0] + ]).filter((x) => x[0] != null && x[1] != null) + }); + if (createOptions.debugShowGeneratedCode) { + return result; + } + const mappedNodes = result.nodes.map((node) => { + if ("text" in node && node.text === "any") { + return void 0; + } + const startMap = get(map.toSourceLocation(node.start), 0); + if (!startMap) { + return void 0; + } + const start = startMap[0]; + let end = get(map.toSourceLocation(node.start + node.length), 0)?.[0]; + if (end == null && startMap[1].sourceOffsets[0] === startMap[0]) { + end = startMap[1].sourceOffsets[1]; + } + if (end == null || start < 0 || end < 0 || start > end) { + return void 0; + } + return { + ...node, + target: code.slice(start, end), + start: startMap[0], + length: end - start + }; + }).filter((value) => value != null); + const mappedRemovals = [ + ...sourceMeta.removals, + ...result.meta.removals.map((r) => { + const start = get(map.toSourceLocation(r[0]), 0)?.[0] ?? code.match(/(?<=\s)/)?.index; + const end = get(map.toSourceLocation(r[1]), 0)?.[0]; + if (start == null || end == null || start < 0 || end < 0 || start >= end) { + return void 0; + } + return [start, end]; + }).filter((value) => value != null) + ]; + if (!options.handbookOptions?.keepNotations) { + const removed = removeCodeRanges(code, mappedRemovals, mappedNodes); + result.code = removed.code; + result.meta.removals = removed.removals; + result.nodes = resolveNodePositions(removed.nodes, result.code); + } else { + result.code = code; + result.meta.removals = mappedRemovals; + } + result.nodes = result.nodes.filter((node, index) => { + const next = result.nodes[index + 1]; + if (!next) { + return true; + } + if (next.type === node.type && next.start === node.start) { + return false; + } + return true; + }); + result.meta.extension = "svelte"; + return result; + } + twoslasher.getCacheMap = twoslasherBase.getCacheMap; + return twoslasher; +} +const createTwoslasherSvelte = createTwoslasher; +function get(iterator, index) { + for (const item of iterator) { + if (index-- === 0) + return item; + } + return void 0; +} +function generateSourceMap(sourceCode, generatedCode, encodedMappings) { + const generatedPositionConverter = createPositionConverter(generatedCode); + const sourcePositionConverter = createPositionConverter(sourceCode); + const decodedMappings = decode(encodedMappings); + const mappings = []; + let current; + for (let genLine = 0; genLine < decodedMappings.length; genLine++) { + for (const segment of decodedMappings[genLine]) { + const genCharacter = segment[0]; + const genOffset = generatedPositionConverter.posToIndex(genLine, genCharacter); + if (current) { + let length = genOffset - current.genOffset; + const sourceText = sourceCode.substring(current.sourceOffset, current.sourceOffset + length); + const genText = generatedCode.substring(current.genOffset, current.genOffset + length); + if (sourceText !== genText) { + length = 0; + for (let i = 0; i < genOffset - current.genOffset; i++) { + if (sourceText[i] === genText[i]) { + length = i + 1; + } else { + break; + } + } + } + if (length > 0) { + const lastMapping = mappings.length ? mappings[mappings.length - 1] : void 0; + if (lastMapping && lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset) { + lastMapping.lengths[0] += length; + } else { + mappings.push({ + sourceOffsets: [current.sourceOffset], + generatedOffsets: [current.genOffset], + lengths: [length], + data: { + verification: true, + completion: true, + semantic: true, + navigation: true, + structure: true, + format: false + } + }); + } + } + current = void 0; + } + if (segment[2] !== void 0 && segment[3] !== void 0) { + const sourceOffset = sourcePositionConverter.posToIndex(segment[2], segment[3]); + current = { + genOffset, + sourceOffset + }; + } + } + } + return new SourceMap(mappings); +} + +export { createTwoslasher, createTwoslasherSvelte }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 066410dcc6..4ea5fd7028 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -425,6 +425,9 @@ importers: specifier: ^3.23.0 version: 3.23.0 devDependencies: + '@jridgewell/sourcemap-codec': + specifier: ^1.5.5 + version: 1.5.5 '@shikijs/vitepress-twoslash': specifier: ^4.0.1 version: 4.0.1(typescript@6.0.3) @@ -434,6 +437,9 @@ importers: '@types/node': specifier: ^20.19.33 version: 20.19.33 + '@volar/language-core': + specifier: ^2.4.28 + version: 2.4.28 flexsearch: specifier: ^0.8.212 version: 0.8.212 @@ -461,9 +467,12 @@ importers: svelte2tsx: specifier: ^0.7.56 version: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) - twoslash-svelte: - specifier: ^0.0.0 - version: 0.0.0(svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3))(typescript@6.0.3) + twoslash: + specifier: ^0.3.8 + version: 0.3.8(typescript@6.0.3) + twoslash-protocol: + specifier: ^0.3.8 + version: 0.3.8 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -3342,12 +3351,6 @@ packages: twoslash-protocol@0.3.8: resolution: {integrity: sha512-HmvAHoiEviK8LqvAQyc9/irkdvwTUiR1fHmNwH/0gq8EHxyBt4PWVPixjEXg6wJu1u6yBrILEWXGK9Kw58/8yQ==} - twoslash-svelte@0.0.0: - resolution: {integrity: sha512-xIVdQ0HFX9IGznj48V/O5MymKMLkxJtsUjUkZ2rdjR2/bWkQyc8VohC3pvKbLkAl0xOzPNUvBRNaBe9zKFofMw==} - peerDependencies: - svelte2tsx: '*' - typescript: '*' - twoslash-vue@0.3.6: resolution: {integrity: sha512-HXYxU+Y7jZiMXJN4980fQNMYflLD8uqKey1qVW5ri8bqYTm2t5ILmOoCOli7esdCHlMq4/No3iQUWBWDhZNs9w==} peerDependencies: @@ -4768,7 +4771,7 @@ snapshots: dependencies: '@shikijs/core': 3.23.0 '@shikijs/types': 3.23.0 - twoslash: 0.3.6(typescript@6.0.3) + twoslash: 0.3.8(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4777,7 +4780,7 @@ snapshots: dependencies: '@shikijs/core': 4.0.1 '@shikijs/types': 4.0.1 - twoslash: 0.3.6(typescript@6.0.3) + twoslash: 0.3.8(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4804,7 +4807,7 @@ snapshots: mdast-util-to-hast: 13.2.1 ohash: 2.0.11 shiki: 4.0.1 - twoslash: 0.3.6(typescript@6.0.3) + twoslash: 0.3.8(typescript@6.0.3) twoslash-vue: 0.3.6(typescript@6.0.3) vue: 3.5.29(typescript@6.0.3) transitivePeerDependencies: @@ -6670,17 +6673,6 @@ snapshots: twoslash-protocol@0.3.8: {} - twoslash-svelte@0.0.0(svelte2tsx@0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3))(typescript@6.0.3): - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@volar/language-core': 2.4.28 - svelte2tsx: 0.7.56(svelte@5.55.8(@typescript-eslint/types@8.58.0))(typescript@6.0.3) - twoslash: 0.3.8(typescript@6.0.3) - twoslash-protocol: 0.3.8 - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - twoslash-vue@0.3.6(typescript@6.0.3): dependencies: '@vue/language-core': 3.2.5 From 4424cfc159b91eef29ed66618f0e615a1e824caa Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Sat, 6 Jun 2026 18:34:29 +0200 Subject: [PATCH 6/7] use local --- .../src/lib/markdown/twoslash-svelte.ts | 343 +++++++++++------- 1 file changed, 203 insertions(+), 140 deletions(-) diff --git a/packages/site-kit/src/lib/markdown/twoslash-svelte.ts b/packages/site-kit/src/lib/markdown/twoslash-svelte.ts index fba83d501f..ee586024b5 100644 --- a/packages/site-kit/src/lib/markdown/twoslash-svelte.ts +++ b/packages/site-kit/src/lib/markdown/twoslash-svelte.ts @@ -1,187 +1,252 @@ -// @ts-nocheck - -import { createRequire } from 'node:module'; -import { decode } from '@jridgewell/sourcemap-codec'; -import { SourceMap } from '@volar/language-core'; -import { svelte2tsx } from 'svelte2tsx'; -import { createTwoslasher as createTwoslasher$1, defaultCompilerOptions, defaultHandbookOptions, findQueryMarkers, findFlagNotations } from 'twoslash'; -import { createPositionConverter, removeCodeRanges, resolveNodePositions } from 'twoslash-protocol'; -import ts from 'typescript'; - -function createTwoslasher(createOptions = {}) { - const require = createRequire(import.meta.url); - const twoslasherBase = createTwoslasher$1(createOptions); - function twoslasher(code, extension, options = {}) { - if (extension !== "svelte") { - return twoslasherBase(code, extension, options); +import type { CodeMapping } from '@volar/language-core' +import type { CompilerOptionDeclaration, CreateTwoslashOptions, HandbookOptions, Range, TwoslashExecuteOptions, TwoslashInstance, TwoslashReturnMeta } from 'twoslash' +import { createRequire } from 'node:module' +import { decode } from '@jridgewell/sourcemap-codec' +import { SourceMap } from '@volar/language-core' +import { svelte2tsx } from 'svelte2tsx' +import { createTwoslasher as createTwoSlasherBase, defaultCompilerOptions, defaultHandbookOptions, findFlagNotations, findQueryMarkers } from 'twoslash' +import { createPositionConverter, removeCodeRanges, resolveNodePositions } from 'twoslash-protocol' +import ts from 'typescript' + +export interface CreateTwoslashSvelteOptions extends CreateTwoslashOptions { + /** + * Render the generated code in the output instead of the Svelte file + * + * @default false + */ + debugShowGeneratedCode?: boolean +} + +/** + * Create a twoslasher instance that add additional support for Svelte + */ +export function createTwoslasher(createOptions: CreateTwoslashSvelteOptions = {}): TwoslashInstance { + const require = createRequire(import.meta.url) + const twoslasherBase = createTwoSlasherBase(createOptions) + + function twoslasher(code: string, extension?: string, options: TwoslashExecuteOptions = {}) { + if (extension !== 'svelte') { + return twoslasherBase(code, extension, options) } - const compilerOptions = { + + const compilerOptions: ts.CompilerOptions = { ...defaultCompilerOptions, - ...options.compilerOptions - }; - const handbookOptions = { + ...options.compilerOptions, + } + const handbookOptions: HandbookOptions = { ...defaultHandbookOptions, noErrorsCutted: true, - ...options.handbookOptions - }; + ...options.handbookOptions, + } + const sourceMeta = findQueryMarkers(code, { - removals: [], - positionCompletions: [], - positionQueries: [], - positionHighlights: [] - }, createPositionConverter(code)); - const customTags = options.customTags ?? createOptions.customTags ?? []; - const optionDeclarations = ts.optionDeclarations; - const flagNotations = findFlagNotations(code, customTags, optionDeclarations); + removals: [] as Range[], + positionCompletions: [] as number[], + positionQueries: [] as number[], + positionHighlights: [] as TwoslashReturnMeta['positionHighlights'], + }, createPositionConverter(code)) + + const customTags = options.customTags ?? createOptions.customTags ?? [] + const optionDeclarations = (ts as any).optionDeclarations as CompilerOptionDeclaration[] + const flagNotations = findFlagNotations(code, customTags, optionDeclarations) + + // #region apply flags for (const flag of flagNotations) { switch (flag.type) { - case "unknown": { - continue; + case 'unknown': { + continue } - case "compilerOptions": { - compilerOptions[flag.name] = flag.value; - break; + case 'compilerOptions': { + compilerOptions[flag.name] = flag.value + break } - case "handbookOptions": { - handbookOptions[flag.name] = flag.value; - break; + case 'handbookOptions': { + // @ts-expect-error -- this is fine + handbookOptions[flag.name] = flag.value + break } } - sourceMeta.removals.push([flag.start, flag.end]); + sourceMeta.removals.push([flag.start, flag.end]) } - let strippedCode = code; + + let strippedCode = code for (const [start, end] of sourceMeta.removals) { - strippedCode = strippedCode.slice(0, start) + strippedCode.slice(start, end).replace(/\S/g, " ") + strippedCode.slice(end); + strippedCode + = strippedCode.slice(0, start) + + strippedCode.slice(start, end).replace(/\S/g, ' ') + + strippedCode.slice(end) } - const compiled = svelte2tsx(strippedCode); - const map = generateSourceMap(strippedCode, compiled.code, compiled.map.mappings); - function getLastGeneratedOffset(pos) { - const offsets = [...map.toGeneratedLocation(pos)]; + + const compiled = svelte2tsx(strippedCode) + const map = generateSourceMap(strippedCode, compiled.code, compiled.map.mappings) + + function getLastGeneratedOffset(pos: number) { + const offsets = [...map.toGeneratedLocation(pos)] if (!offsets.length) { - return void 0; + return undefined } - return offsets[offsets.length - 1]?.[0]; + return offsets[offsets.length - 1]?.[0] } - const result = twoslasherBase(compiled.code, "tsx", { + + const result = twoslasherBase(compiled.code, 'tsx', { ...options, compilerOptions: { ...compilerOptions, types: [ - ...compilerOptions.types ?? [], + ...(compilerOptions.types ?? []), require.resolve(`svelte2tsx/svelte-jsx-v4.d.ts`), - require.resolve(`svelte2tsx/svelte-shims-v4.d.ts`) - ] + require.resolve(`svelte2tsx/svelte-shims-v4.d.ts`), + ], }, handbookOptions: { ...handbookOptions, - keepNotations: true + keepNotations: true, }, - positionCompletions: sourceMeta.positionCompletions.map((p) => getLastGeneratedOffset(p)), - positionQueries: sourceMeta.positionQueries.map((p) => get(map.toGeneratedLocation(p), 0)?.[0]).filter((value) => value != null), - positionHighlights: sourceMeta.positionHighlights.map(([start, end]) => [ - get(map.toGeneratedLocation(start), 0)?.[0], - get(map.toGeneratedLocation(end), 0)?.[0] - ]).filter((x) => x[0] != null && x[1] != null) - }); + positionCompletions: sourceMeta.positionCompletions + .map(p => getLastGeneratedOffset(p)!), + positionQueries: sourceMeta.positionQueries + .map(p => get(map.toGeneratedLocation(p), 0)?.[0]) + .filter(value => value != null), + positionHighlights: sourceMeta.positionHighlights + .map(([start, end]) => [ + get(map.toGeneratedLocation(start), 0)?.[0], + get(map.toGeneratedLocation(end), 0)?.[0], + ]) + .filter((x): x is [number, number] => x[0] != null && x[1] != null), + }) + if (createOptions.debugShowGeneratedCode) { - return result; + return result } - const mappedNodes = result.nodes.map((node) => { - if ("text" in node && node.text === "any") { - return void 0; - } - const startMap = get(map.toSourceLocation(node.start), 0); - if (!startMap) { - return void 0; - } - const start = startMap[0]; - let end = get(map.toSourceLocation(node.start + node.length), 0)?.[0]; - if (end == null && startMap[1].sourceOffsets[0] === startMap[0]) { - end = startMap[1].sourceOffsets[1]; - } - if (end == null || start < 0 || end < 0 || start > end) { - return void 0; - } - return { - ...node, - target: code.slice(start, end), - start: startMap[0], - length: end - start - }; - }).filter((value) => value != null); + + // Map the tokens + const mappedNodes = result.nodes + .map((node) => { + if ('text' in node && node.text === 'any') { + return undefined + } + const startMap = get(map.toSourceLocation(node.start), 0) + if (!startMap) { + return undefined + } + const start = startMap[0] + let end = get(map.toSourceLocation(node.start + node.length), 0)?.[0] + if (end == null && startMap[1].sourceOffsets[0] === startMap[0]) { + end = startMap[1].sourceOffsets[1] + } + if (end == null || start < 0 || end < 0 || start > end) { + return undefined + } + return { + ...node, + target: code.slice(start, end), + start: startMap[0], + length: end - start, + } + }) + .filter(value => value != null) + const mappedRemovals = [ ...sourceMeta.removals, ...result.meta.removals.map((r) => { - const start = get(map.toSourceLocation(r[0]), 0)?.[0] ?? code.match(/(?<=\s)/)?.index; - const end = get(map.toSourceLocation(r[1]), 0)?.[0]; + const start = get(map.toSourceLocation(r[0]), 0)?.[0] ?? code.match(/(?<=\s)/)?.index + const end = get(map.toSourceLocation(r[1]), 0)?.[0] if (start == null || end == null || start < 0 || end < 0 || start >= end) { - return void 0; + return undefined } - return [start, end]; - }).filter((value) => value != null) - ]; + return [start, end] as Range + }).filter(value => value != null), + ] + if (!options.handbookOptions?.keepNotations) { - const removed = removeCodeRanges(code, mappedRemovals, mappedNodes); - result.code = removed.code; - result.meta.removals = removed.removals; - result.nodes = resolveNodePositions(removed.nodes, result.code); - } else { - result.code = code; - result.meta.removals = mappedRemovals; + const removed = removeCodeRanges(code, mappedRemovals, mappedNodes) + result.code = removed.code + result.meta.removals = removed.removals + result.nodes = resolveNodePositions(removed.nodes, result.code) + } + else { + result.code = code + result.meta.removals = mappedRemovals } result.nodes = result.nodes.filter((node, index) => { - const next = result.nodes[index + 1]; + const next = result.nodes[index + 1] if (!next) { - return true; + return true } + // When multiple nodes are on the same position, we keep the last one by ignoring the previous ones if (next.type === node.type && next.start === node.start) { - return false; + return false } - return true; - }); - result.meta.extension = "svelte"; - return result; + return true + }) + result.meta.extension = 'svelte' + return result } - twoslasher.getCacheMap = twoslasherBase.getCacheMap; - return twoslasher; + + twoslasher.getCacheMap = twoslasherBase.getCacheMap + + return twoslasher } -const createTwoslasherSvelte = createTwoslasher; -function get(iterator, index) { + +/** + * @deprecated Use `createTwoslasher` instead. + */ +export const createTwoslasherSvelte = createTwoslasher + +function get(iterator: IterableIterator | Generator, index: number): T | undefined { for (const item of iterator) { if (index-- === 0) - return item; + return item } - return void 0; + return undefined } -function generateSourceMap(sourceCode, generatedCode, encodedMappings) { - const generatedPositionConverter = createPositionConverter(generatedCode); - const sourcePositionConverter = createPositionConverter(sourceCode); - const decodedMappings = decode(encodedMappings); - const mappings = []; - let current; + +function generateSourceMap( + sourceCode: string, + generatedCode: string, + encodedMappings: string, +): SourceMap { + const generatedPositionConverter = createPositionConverter(generatedCode) + const sourcePositionConverter = createPositionConverter(sourceCode) + const decodedMappings = decode(encodedMappings) + const mappings: CodeMapping[] = [] + + let current: + | { + genOffset: number + sourceOffset: number + } + | undefined + for (let genLine = 0; genLine < decodedMappings.length; genLine++) { for (const segment of decodedMappings[genLine]) { - const genCharacter = segment[0]; - const genOffset = generatedPositionConverter.posToIndex(genLine, genCharacter); + const genCharacter = segment[0] + const genOffset = generatedPositionConverter.posToIndex(genLine, genCharacter) if (current) { - let length = genOffset - current.genOffset; - const sourceText = sourceCode.substring(current.sourceOffset, current.sourceOffset + length); - const genText = generatedCode.substring(current.genOffset, current.genOffset + length); + let length = genOffset - current.genOffset + const sourceText = sourceCode.substring(current.sourceOffset, current.sourceOffset + length) + const genText = generatedCode.substring(current.genOffset, current.genOffset + length) if (sourceText !== genText) { - length = 0; + length = 0 for (let i = 0; i < genOffset - current.genOffset; i++) { if (sourceText[i] === genText[i]) { - length = i + 1; - } else { - break; + length = i + 1 + } + else { + break } } } if (length > 0) { - const lastMapping = mappings.length ? mappings[mappings.length - 1] : void 0; - if (lastMapping && lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset && lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset) { - lastMapping.lengths[0] += length; - } else { + const lastMapping = mappings.length ? mappings[mappings.length - 1] : undefined + if ( + lastMapping + && lastMapping.generatedOffsets[0] + lastMapping.lengths[0] === current.genOffset + && lastMapping.sourceOffsets[0] + lastMapping.lengths[0] === current.sourceOffset + ) { + lastMapping.lengths[0] += length + } + else { mappings.push({ sourceOffsets: [current.sourceOffset], generatedOffsets: [current.genOffset], @@ -192,23 +257,21 @@ function generateSourceMap(sourceCode, generatedCode, encodedMappings) { semantic: true, navigation: true, structure: true, - format: false - } - }); + format: false, + }, + }) } } - current = void 0; + current = undefined } - if (segment[2] !== void 0 && segment[3] !== void 0) { - const sourceOffset = sourcePositionConverter.posToIndex(segment[2], segment[3]); + if (segment[2] !== undefined && segment[3] !== undefined) { + const sourceOffset = sourcePositionConverter.posToIndex(segment[2], segment[3]) current = { genOffset, - sourceOffset - }; + sourceOffset, + } } } } - return new SourceMap(mappings); -} - -export { createTwoslasher, createTwoslasherSvelte }; + return new SourceMap(mappings) +} \ No newline at end of file From 0396a01585e282a46edc1b0352334c17abceca25 Mon Sep 17 00:00:00 2001 From: Hugos68 Date: Mon, 8 Jun 2026 09:24:22 +0200 Subject: [PATCH 7/7] inject prelude --- .../site-kit/src/lib/markdown/renderer.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/site-kit/src/lib/markdown/renderer.ts b/packages/site-kit/src/lib/markdown/renderer.ts index bb046f8bf6..ef2ad5c0aa 100644 --- a/packages/site-kit/src/lib/markdown/renderer.ts +++ b/packages/site-kit/src/lib/markdown/renderer.ts @@ -1144,7 +1144,11 @@ async function syntax_highlight({ }); try { - html = highlighter.codeToHtml(prelude + redacted, { + html = highlighter.codeToHtml(injectPrelude({ + prelude, + source: redacted, + language + }), { lang: language, theme, transformers: check @@ -1321,3 +1325,18 @@ function indent_multiline_comments(str: string) { } ); } + + +function injectPrelude({ prelude, source, language }: { prelude: string; source: string; language: string }) { + if (language === 'svelte') { + const scriptTag = /]*>/; + if (scriptTag.test(source)) { + return source.replace( + scriptTag, + match => `${match}\n${prelude}\n` + ); + } + return `\n\n${source}`; + } + return prelude + source; +} \ No newline at end of file