Skip to content

Commit

Permalink
fix: support for if and each block
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed Dec 24, 2024
1 parent 04e9465 commit fa01cf5
Show file tree
Hide file tree
Showing 49 changed files with 308 additions and 34 deletions.
280 changes: 254 additions & 26 deletions packages/svelte-template-compiler/src/ir/svelte.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { parse } from 'svelte/compiler'
import { expect, test } from 'vitest'
import { describe, expect, test } from 'vitest'
import { enableStructures } from './svelte.ts'

test('enableStructures', () => {
const code = `
import type { SvelteElseBlock } from './svelte.ts'

describe('enableStructures', () => {
test('basic', () => {
const code = `
<script>
let count = 0
const increment = () => {
Expand All @@ -24,27 +27,252 @@ test('enableStructures', () => {
}
</style>
`
const { html } = parse(code)

enableStructures(html)
// div.container
const divContainer = (html.children || [])[2]
expect(divContainer.parent).toEqual(html)
expect(divContainer.prev).toBeUndefined()
expect(divContainer.next).toBeUndefined()
// div.header
const divHeader = divContainer.children![1]
expect(divHeader.parent).toEqual(divContainer)
expect(divHeader.prev).toBeUndefined()
expect(divHeader.next).toBeUndefined()
// p
const p = divHeader.children![1]
// img
const img = divHeader.children![3]
expect(p.parent).toEqual(divHeader)
expect(img.parent).toEqual(divHeader)
expect(p.prev).toBeUndefined()
expect(p.next).toEqual(img)
expect(img.prev).toEqual(p)
expect(img.next).toBeUndefined()
const { html } = parse(code)
enableStructures(html)

// div.container
const divContainer = (html.children || [])[2]
expect(divContainer.parent).toEqual(html)
expect(divContainer.prev).toBeUndefined()
expect(divContainer.next).toBeUndefined()
// div.header
const divHeader = divContainer.children![1]
expect(divHeader.parent).toEqual(divContainer)
expect(divHeader.prev).toBeUndefined()
expect(divHeader.next).toBeUndefined()
// p
const p = divHeader.children![1]
// img
const img = divHeader.children![3]
expect(p.parent).toEqual(divHeader)
expect(img.parent).toEqual(divHeader)
expect(p.prev).toBeUndefined()
expect(p.next).toEqual(img)
expect(img.prev).toEqual(p)
expect(img.next).toBeUndefined()
})

test('{#if expression}...{/if}', () => {
const code = `
<script>
let count = 1
</script>
<div>
<p>count:</p>
{#if count === 1}
<p>This is count 1</p>
{/if}
</div>
`
const { html } = parse(code)
enableStructures(html)

// div
const div = (html.children || [])[2]
expect(div.parent).toEqual(html)
expect(div.prev).toBeUndefined()
expect(div.next).toBeUndefined()
// p
const p = div.children![1]
// {#if}
const ifBlock = div.children![3]
expect(p.parent).toEqual(div)
expect(p.prev).toBeUndefined()
expect(p.next).toEqual(ifBlock)
expect(ifBlock.parent).toEqual(div)
expect(ifBlock.prev).toEqual(p)
expect(ifBlock.next).toBeUndefined()
// p inside {#if}
const pInsideIf = ifBlock.children![0]
expect(pInsideIf.parent).toEqual(ifBlock)
expect(pInsideIf.prev).toBeUndefined()
expect(pInsideIf.next).toBeUndefined()
})

test('{#if expression}...{:else if expression}...{/if}', () => {
const code = `
<script>
let count = 1
</script>
<div>
<p>count:</p>
{#if count === 1}
<p>This is count 1</p>
{:else if count === 2}
<p>This is count 2</p>
<p>Another count 2</p>
{/if}
</div>
`
const { html } = parse(code)
enableStructures(html)

// div
const div = (html.children || [])[2]
expect(div.parent).toEqual(html)
expect(div.prev).toBeUndefined()
expect(div.next).toBeUndefined()
// p
const p = div.children![1]
// {#if}
const ifBlock = div.children![3]
expect(p.parent).toEqual(div)
expect(p.prev).toBeUndefined()
expect(p.next).toEqual(ifBlock)
expect(ifBlock.parent).toEqual(div)
expect(ifBlock.prev).toEqual(p)
expect(ifBlock.next).toBeUndefined()
// p inside {#if}
const pInsideIf = ifBlock.children![0]
expect(pInsideIf.parent).toEqual(ifBlock)
expect(pInsideIf.prev).toBeUndefined()
expect(pInsideIf.next).toBeUndefined()
// {:else ..}
const elseIfBlock = ifBlock.else as SvelteElseBlock
expect(elseIfBlock.parent).toEqual(ifBlock)
expect(elseIfBlock.prev).toBeUndefined()
expect(elseIfBlock.next).toBeUndefined()
// {... if}
const ifBlock2 = elseIfBlock.children[0]
expect(ifBlock2.parent).toEqual(elseIfBlock)
expect(ifBlock2.prev).toBeUndefined()
expect(ifBlock2.next).toBeUndefined()
// p[0] inside {:else if}
const pInsideElseIf1 = ifBlock2.children![0]
// p[1] inside {:else if}
const pInsideElseIf2 = ifBlock2.children![2]
expect(pInsideElseIf1.parent).toEqual(ifBlock2)
expect(pInsideElseIf1.prev).toBeUndefined()
expect(pInsideElseIf1.next).toEqual(pInsideElseIf2)
expect(pInsideElseIf2.parent).toEqual(ifBlock2)
expect(pInsideElseIf2.prev).toEqual(pInsideElseIf1)
expect(pInsideElseIf2.next).toBeUndefined()
})

test('{#if expression}...{:else}...{/if}', () => {
const code = `
<script>
let count = 1
</script>
<div>
<p>count:</p>
{#if count === 1}
<p>This is count 1</p>
{:else}
<p>This is not count</p>
{/if}
</div>
`
const { html } = parse(code)
enableStructures(html)

// div
const div = (html.children || [])[2]
expect(div.parent).toEqual(html)
expect(div.prev).toBeUndefined()
expect(div.next).toBeUndefined()
// p
const p = div.children![1]
// {#if}
const ifBlock = div.children![3]
expect(p.parent).toEqual(div)
expect(p.prev).toBeUndefined()
expect(p.next).toEqual(ifBlock)
expect(ifBlock.parent).toEqual(div)
expect(ifBlock.prev).toEqual(p)
expect(ifBlock.next).toBeUndefined()
// p inside {#if}
const pInsideIf = ifBlock.children![0]
expect(pInsideIf.parent).toEqual(ifBlock)
expect(pInsideIf.prev).toBeUndefined()
expect(pInsideIf.next).toBeUndefined()
// {:else}
const elseBlock = ifBlock.else as SvelteElseBlock
expect(elseBlock.parent).toEqual(ifBlock)
expect(elseBlock.prev).toBeUndefined()
expect(elseBlock.next).toBeUndefined()
// p inside {:else}
const pInsideElse = elseBlock.children[0]
expect(pInsideElse.parent).toEqual(elseBlock)
expect(pInsideElse.prev).toBeUndefined()
expect(pInsideElse.next).toBeUndefined()
})

test('{#each expression as name}...{/each}', () => {
const code = `
<script>
let items = [1]
</script>
<div>
<h1>Shopping list</h1>
{#each items as item}
<p>{item.name} x {item.qty}</p>
{/each}
</div>
`
const { html } = parse(code)
enableStructures(html)

// div
const div = (html.children || [])[2]
expect(div.parent).toEqual(html)
expect(div.prev).toBeUndefined()
expect(div.next).toBeUndefined()
// h
const h = div.children![1]
// {#each}
const eachBlock = div.children![3]
expect(h.parent).toEqual(div)
expect(h.prev).toBeUndefined()
expect(h.next).toEqual(eachBlock)
expect(eachBlock.parent).toEqual(div)
expect(eachBlock.prev).toEqual(h)
expect(eachBlock.next).toBeUndefined()
// p inside {#each}
const pInsideIf = eachBlock.children![0]
expect(pInsideIf.parent).toEqual(eachBlock)
expect(pInsideIf.prev).toBeUndefined()
})

test('{#each expression as name}...{:else}...{/each}', () => {
const code = `
<script>
let items = [1]
</script>
<div>
<h1>Shopping list</h1>
{#each items as item}
<p>{item.name} x {item.qty}</p>
{:else}
<p>No items</p>
{/each}
</div>
`
const { html } = parse(code)
enableStructures(html)

// div
const div = (html.children || [])[2]
// {#each}
const eachBlock = div.children![3]
// {:else}
const elseBlock = eachBlock.else as SvelteElseBlock
expect(elseBlock.parent).toEqual(eachBlock)
expect(elseBlock.prev).toBeUndefined()
expect(elseBlock.next).toBeUndefined()
// p inside {:else}
const pInsideIf = elseBlock.children[0]
expect(pInsideIf.parent).toEqual(elseBlock)
expect(pInsideIf.prev).toBeUndefined()
})

test('{#await expression}...{:then name}...{:catch name}...{/await}', () => {})
test('{#await expression}...{:then name}...{/await}', () => {})
test('{#await expression then name}...{/await}', () => {})
test('{#await expression catch name}...{/await}', () => {})
})
22 changes: 22 additions & 0 deletions packages/svelte-template-compiler/src/ir/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
BaseNode as SvelteBaseNode,
ComponentTag as SvelteComponentTag,
Directive as SvelteDirective,
EachBlock as SvelteEachBlock,
Element as SvelteElement,
ElseBlock as SvelteElseBlock,
IfBlock as SvelteIfBlock,
Expand Down Expand Up @@ -102,10 +103,18 @@ export function isSvelteEventHandler(node: unknown): node is SvelteBaseExpressio
return isObject(node) && 'type' in node && node.type === 'EventHandler'
}

export function isSvelteIfBlock(node: unknown): node is SvelteIfBlock {
return isObject(node) && 'type' in node && node.type === 'IfBlock'
}

export function isSvelteElseBlock(node: unknown): node is SvelteElseBlock {
return isObject(node) && 'type' in node && node.type === 'ElseBlock'
}

export function isSvelteEachBlock(node: unknown): node is SvelteEachBlock {
return isObject(node) && 'type' in node && node.type === 'EachBlock'
}

export function isIfBlockOnTop(node: SvelteIfBlock): boolean {
return node.type === 'IfBlock' && !node.elseif
}
Expand Down Expand Up @@ -200,7 +209,15 @@ export type CompatLocationable = {
export function enableStructures(node: SvelteTemplateNode): void {
let last: SvelteTemplateNode | undefined
const children = node.children || []
if (__DEV__) {
console.log('enableStructures type:', node.type, node.parent?.type)
}

children.forEach(child => {
if (__DEV__) {
console.log('enableStructures child type:', child.type)
}

// ignores
if (isSvelteText(child) || isSvelteComponentTag(child)) {
return
Expand All @@ -218,6 +235,11 @@ export function enableStructures(node: SvelteTemplateNode): void {
if (child.children) {
enableStructures(child)
}

if ((isSvelteIfBlock(child) || isSvelteEachBlock(child)) && isSvelteElseBlock(child.else)) {
child.else.parent = child
enableStructures(child.else)
}
})
}

Expand Down
Loading

0 comments on commit fa01cf5

Please sign in to comment.