@@ -4,8 +4,10 @@ import { createRequire } from 'node:module'
44import { decode } from '@jridgewell/sourcemap-codec'
55import { SourceMap } from '@volar/language-core'
66import { svelte2tsx } from 'svelte2tsx'
7+ import { parse } from 'svelte/compiler'
78import { createTwoslasher as createTwoSlasherBase , defaultCompilerOptions , defaultHandbookOptions , findFlagNotations , findQueryMarkers } from 'twoslash'
89import { createPositionConverter , removeCodeRanges , resolveNodePositions } from 'twoslash-protocol'
10+ import { findCutNotations } from 'twoslash/core'
911import ts from 'typescript'
1012
1113export interface CreateTwoslashSvelteOptions extends CreateTwoslashOptions {
@@ -78,6 +80,7 @@ export function createTwoslasher(createOptions: CreateTwoslashSvelteOptions = {}
7880 }
7981
8082 const compiled = svelte2tsx ( strippedCode )
83+
8184 const map = generateSourceMap ( strippedCode , compiled . code , compiled . map . mappings )
8285
8386 function getLastGeneratedOffset ( pos : number ) {
@@ -146,16 +149,20 @@ export function createTwoslasher(createOptions: CreateTwoslashSvelteOptions = {}
146149 } )
147150 . filter ( value => value != null )
148151
152+ const ast = parse ( strippedCode , { modern : true } )
153+
154+ findCutNotations ( code , sourceMeta , {
155+ reCutBefore : / ^ < ! - - \s * - - - c u t ( - b e f o r e ) ? - - - \s * - - > $ / ,
156+ reCutAfter : / ^ < ! - - \s * - - - c u t - a f t e r - - - \s * - - > $ / ,
157+ reCutStart : / ^ < ! - - \s * - - - c u t - s t a r t - - - \s * - - > $ / ,
158+ reCutEnd : / ^ < ! - - \s * - - - c u t - e n d - - - \s * - - > $ / ,
159+ } )
160+
149161 const mappedRemovals = [
150162 ...sourceMeta . removals ,
151- ...result . meta . removals . map ( ( r ) => {
152- const start = get ( map . toSourceLocation ( r [ 0 ] ) , 0 ) ?. [ 0 ] ?? code . match ( / (?< = < s c r i p t [ \s \S ] * > \s ) / ) ?. index
153- const end = get ( map . toSourceLocation ( r [ 1 ] ) , 0 ) ?. [ 0 ]
154- if ( start == null || end == null || start < 0 || end < 0 || start >= end ) {
155- return undefined
156- }
157- return [ start , end ] as Range
158- } ) . filter ( value => value != null ) ,
163+ ...result . meta . removals
164+ . map ( r => mapRemovalToSource ( r , map , ast ) )
165+ . filter ( value => value != null ) ,
159166 ]
160167
161168 if ( ! options . handbookOptions ?. keepNotations ) {
@@ -275,3 +282,49 @@ function generateSourceMap(
275282 }
276283 return new SourceMap ( mappings )
277284}
285+
286+ function mapRemovalToSource (
287+ r : Range ,
288+ map : SourceMap ,
289+ ast : ReturnType < typeof parse > ,
290+ ) : Range | undefined {
291+ const instanceContent = hasRange ( ast . instance ?. content ) ? ast . instance . content : undefined
292+ const moduleContent = hasRange ( ast . module ?. content ) ? ast . module . content : undefined
293+
294+ let start = get ( map . toSourceLocation ( r [ 0 ] ) , 0 ) ?. [ 0 ]
295+ let end = get ( map . toSourceLocation ( r [ 1 ] ) , 0 ) ?. [ 0 ]
296+
297+ // Determine which script block this removal belongs to
298+ const scriptContent = start != null
299+ ? ( instanceContent && start >= instanceContent . start && start <= instanceContent . end ? instanceContent : undefined )
300+ ?? ( moduleContent && start >= moduleContent . start && start <= moduleContent . end ? moduleContent : undefined )
301+ : instanceContent ?? moduleContent
302+
303+ // Fall back to script boundaries for unmappable positions
304+ start ??= scriptContent ?. start
305+ if ( end == null && scriptContent != null && r [ 1 ] > scriptContent . end )
306+ end = scriptContent . end
307+
308+ if ( start == null || end == null || start < 0 || end < 0 || start >= end )
309+ return undefined
310+
311+ // Clamp to script content boundaries to protect <script> tags
312+ if ( scriptContent ) {
313+ start = Math . max ( start , scriptContent . start )
314+ end = Math . min ( end , scriptContent . end )
315+ }
316+
317+ if ( start >= end )
318+ return undefined
319+
320+ return [ start , end ]
321+ }
322+
323+ function hasRange ( range : unknown ) : range is { start : number , end : number } {
324+ return typeof range === 'object'
325+ && range != null
326+ && 'start' in range
327+ && 'end' in range
328+ && typeof range . start === 'number'
329+ && typeof range . end === 'number'
330+ }
0 commit comments