Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/twoslash-svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
"start": "esno src/index.ts"
},
"peerDependencies": {
"svelte": ">=5",
"typescript": "*"
},
"dependencies": {
"@jridgewell/sourcemap-codec": "catalog:",
"@volar/language-core": "catalog:",
"estree-walker": "catalog:",
"svelte2tsx": "catalog:",
"twoslash": "workspace:*",
"twoslash-protocol": "workspace:*"
Expand Down
69 changes: 61 additions & 8 deletions packages/twoslash-svelte/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { createRequire } from 'node:module'
import { decode } from '@jridgewell/sourcemap-codec'
import { SourceMap } from '@volar/language-core'
import { svelte2tsx } from 'svelte2tsx'
import { parse } from 'svelte/compiler'
import { createTwoslasher as createTwoSlasherBase, defaultCompilerOptions, defaultHandbookOptions, findFlagNotations, findQueryMarkers } from 'twoslash'
import { createPositionConverter, removeCodeRanges, resolveNodePositions } from 'twoslash-protocol'
import { findCutNotations } from 'twoslash/core'
import ts from 'typescript'

export interface CreateTwoslashSvelteOptions extends CreateTwoslashOptions {
Expand Down Expand Up @@ -78,6 +80,7 @@ export function createTwoslasher(createOptions: CreateTwoslashSvelteOptions = {}
}

const compiled = svelte2tsx(strippedCode)

const map = generateSourceMap(strippedCode, compiled.code, compiled.map.mappings)

function getLastGeneratedOffset(pos: number) {
Expand Down Expand Up @@ -146,16 +149,20 @@ export function createTwoslasher(createOptions: CreateTwoslashSvelteOptions = {}
})
.filter(value => value != null)

const ast = parse(strippedCode, { modern: true })

findCutNotations(code, sourceMeta, {
reCutBefore: /^<!--\s*---cut(-before)?---\s*-->$/,
reCutAfter: /^<!--\s*---cut-after---\s*-->$/,
reCutStart: /^<!--\s*---cut-start---\s*-->$/,
reCutEnd: /^<!--\s*---cut-end---\s*-->$/,
})

const mappedRemovals = [
...sourceMeta.removals,
...result.meta.removals.map((r) => {
const start = get(map.toSourceLocation(r[0]), 0)?.[0] ?? code.match(/(?<=<script[\s\S]*>\s)/)?.index
const end = get(map.toSourceLocation(r[1]), 0)?.[0]
if (start == null || end == null || start < 0 || end < 0 || start >= end) {
return undefined
}
return [start, end] as Range
}).filter(value => value != null),
...result.meta.removals
.map(r => mapRemovalToSource(r, map, ast))
.filter(value => value != null),
]

if (!options.handbookOptions?.keepNotations) {
Expand Down Expand Up @@ -275,3 +282,49 @@ function generateSourceMap(
}
return new SourceMap(mappings)
}

function mapRemovalToSource(
r: Range,
map: SourceMap,
ast: ReturnType<typeof parse>,
): Range | undefined {
const instanceContent = hasRange(ast.instance?.content) ? ast.instance.content : undefined
const moduleContent = hasRange(ast.module?.content) ? ast.module.content : undefined

let start = get(map.toSourceLocation(r[0]), 0)?.[0]
let end = get(map.toSourceLocation(r[1]), 0)?.[0]

// Determine which script block this removal belongs to
const scriptContent = start != null
? (instanceContent && start >= instanceContent.start && start <= instanceContent.end ? instanceContent : undefined)
?? (moduleContent && start >= moduleContent.start && start <= moduleContent.end ? moduleContent : undefined)
: instanceContent ?? moduleContent

// Fall back to script boundaries for unmappable positions
start ??= scriptContent?.start
if (end == null && scriptContent != null && r[1] > scriptContent.end)
end = scriptContent.end

if (start == null || end == null || start < 0 || end < 0 || start >= end)
return undefined

// Clamp to script content boundaries to protect <script> tags
if (scriptContent) {
start = Math.max(start, scriptContent.start)
end = Math.min(end, scriptContent.end)
}

if (start >= end)
return undefined

return [start, end]
}

function hasRange(range: unknown): range is { start: number, end: number } {
return typeof range === 'object'
&& range != null
&& 'start' in range
&& 'end' in range
&& typeof range.start === 'number'
&& typeof range.end === 'number'
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<script>
let count = $state(0);
// ^|

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count is: {count}
</button>
10 changes: 10 additions & 0 deletions packages/twoslash-svelte/test/fixtures/script/cut-after.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
// ---cut-after---
function increment() {
count++;
}
</script>
<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<script>
let count = $state(0);

// ---cut-before---
function increment() {
count++;
}
// ---cut-after---
</script>

<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<script>
let count = $state(0);

// ---cut-start---
function increment() {
count++;
}
// ---cut-end---
</script>

<button onclick={increment}>
Count is: {count}
</button>
10 changes: 10 additions & 0 deletions packages/twoslash-svelte/test/fixtures/script/cut.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
// ---cut---
function increment() {
count++;
}
</script>
<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
<script>
let count = $state(0);

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<script>
let count = $state(0);
// ^?

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
// ---cut-start---
function increment() {
count++;
}
</script>
<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
// ---cut-end---
</script>
<button onclick={increment}>
Count is: {count}
</button>
10 changes: 10 additions & 0 deletions packages/twoslash-svelte/test/fixtures/template/cut-after.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<!-- ---cut-after--- -->
<button onclick={increment}>
Count is: {count}
</button>
10 changes: 10 additions & 0 deletions packages/twoslash-svelte/test/fixtures/template/cut-before.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<!-- ---cut-before--- -->
<button onclick={increment}>
Count is: {count}
</button>
11 changes: 11 additions & 0 deletions packages/twoslash-svelte/test/fixtures/template/cut-region.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<!-- ---cut-start--- -->
<button onclick={increment}>
Count is: {count}
</button>
<!-- ---cut-end--- -->
10 changes: 10 additions & 0 deletions packages/twoslash-svelte/test/fixtures/template/cut.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<!-- ---cut--- -->
<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<!-- ---cut-start--- -->
<button onclick={increment}>
Count is: {count}
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<button onclick={increment}>
Count is: {count}
</button>
<!-- ---cut-end--- -->

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions packages/twoslash-svelte/test/results/script/cut-after.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading