diff --git a/packages/language-server/src/plugins/html/dataProvider.ts b/packages/language-server/src/plugins/html/dataProvider.ts index 3031552c4..30495b6bd 100644 --- a/packages/language-server/src/plugins/html/dataProvider.ts +++ b/packages/language-server/src/plugins/html/dataProvider.ts @@ -296,6 +296,17 @@ const svelteTags: ITagData[] = [ 'Named slots allow consumers to target specific areas. They can also have fallback content.' } ] + }, + { + name: 'svelte:boundary', + description: + 'Represents a boundary in the application. Can catch errors and show fallback UI', + attributes: [ + { + name: 'onerror', + description: 'Called when an error occured within the boundary' + } + ] } ]; @@ -419,6 +430,18 @@ export const svelteHtmlDataProvider = newHTMLDataProvider('svelte-builtin', { })) ?? [] }); +const originalProvideAttributes = + svelteHtmlDataProvider.provideAttributes.bind(svelteHtmlDataProvider); + +svelteHtmlDataProvider.provideAttributes = (tag: string) => { + if (tag === 'svelte:boundary' || tag === 'svelte:options') { + // We don't want the global attributes for these tags + return svelteTags.find((t) => t.name === tag)?.attributes ?? []; + } + + return originalProvideAttributes(tag); +}; + function isEvent(attr: IAttributeData) { return attr.name.startsWith('on'); } diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts index 0fced1e1c..95f8cec4f 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/index.ts @@ -88,8 +88,10 @@ export function convertHtmlxToJsx( handleSnippet( str, node, - element instanceof InlineComponent && - estreeTypedParent.type === 'InlineComponent' + (element instanceof InlineComponent && + estreeTypedParent.type === 'InlineComponent') || + (element instanceof Element && + element.tagName === 'svelte:boundary') ? element : undefined ); @@ -133,6 +135,7 @@ export function convertHtmlxToJsx( case 'Title': case 'Document': case 'Body': + case 'SvelteBoundary': case 'Slot': case 'SlotTemplate': if (node.name !== '!DOCTYPE') { @@ -236,6 +239,7 @@ export function convertHtmlxToJsx( case 'Head': case 'Title': case 'Body': + case 'SvelteBoundary': case 'Document': case 'Slot': case 'SlotTemplate': diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts index 4a8957700..701e1b83a 100644 --- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts +++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts @@ -3,6 +3,7 @@ import { BaseNode } from '../../interfaces'; import { transform, TransformationArray } from '../utils/node-utils'; import { InlineComponent } from './InlineComponent'; import { IGNORE_POSITION_COMMENT, surroundWithIgnoreComments } from '../../utils/ignore'; +import { Element } from './Element'; /** * Transform #snippet into a function @@ -28,7 +29,7 @@ import { IGNORE_POSITION_COMMENT, surroundWithIgnoreComments } from '../../utils export function handleSnippet( str: MagicString, snippetBlock: BaseNode, - component?: InlineComponent + component?: InlineComponent | Element ): void { const isImplicitProp = component !== undefined; const endSnippet = str.original.lastIndexOf('{', snippetBlock.end - 1); @@ -64,6 +65,7 @@ export function handleSnippet( if (isImplicitProp) { str.overwrite(snippetBlock.start, snippetBlock.expression.start, '', { contentOnly: true }); const transforms: TransformationArray = ['(']; + if (parameters) { transforms.push(parameters); const [start, end] = parameters; @@ -74,12 +76,21 @@ export function handleSnippet( } else { str.overwrite(snippetBlock.expression.end, startEnd, '', { contentOnly: true }); } + transforms.push(')' + afterParameters); transforms.push([startEnd, snippetBlock.end]); - component.addImplicitSnippetProp( - [snippetBlock.expression.start, snippetBlock.expression.end], - transforms - ); + + if (component instanceof InlineComponent) { + component.addImplicitSnippetProp( + [snippetBlock.expression.start, snippetBlock.expression.end], + transforms + ); + } else { + component.addAttribute( + [[snippetBlock.expression.start, snippetBlock.expression.end]], + transforms + ); + } } else { const transforms: TransformationArray = [ 'const ', @@ -149,7 +160,7 @@ export function handleImplicitChildren(componentNode: BaseNode, component: Inlin } export function hoistSnippetBlock(str: MagicString, blockOrEl: BaseNode) { - if (blockOrEl.type === 'InlineComponent') { + if (blockOrEl.type === 'InlineComponent' || blockOrEl.type === 'SvelteBoundary') { // implicit props, handled in InlineComponent return; } diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/expectedv2.js new file mode 100644 index 000000000..1f398adda --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/expectedv2.js @@ -0,0 +1,5 @@ + { svelteHTML.createElement("svelte:boundary", { "onerror":e => e,failed:(e) => { async ()/*Ωignore_positionΩ*/ => { + { svelteHTML.createElement("p", {}); e; } + };return __sveltets_2_any(0)},}); { const $$_sliaFtahTtnenopmoC1C = __sveltets_2_ensureComponent(ComponentThatFails); new $$_sliaFtahTtnenopmoC1C({ target: __sveltets_2_any(), props: {}});} + + } \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/input.svelte new file mode 100644 index 000000000..7bcb64b7c --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/.svelte-boundary.v5/input.svelte @@ -0,0 +1,6 @@ + e}> + + {#snippet failed(e)} +

error: {e}

+ {/snippet} +