fix: prevent nullish snippet for rendering empty content (#13083)

* fix: prevent nullish snippet for rendering empty content

* fix: prevent nullish snippet for rendering empty content

* lint

* fix message

* alternative approach

* tweak

* feedback
pull/13145/head
Dominic Gannaway 1 year ago committed by GitHub
parent e1448f22a3
commit 3b88b88886
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: prevent nullish snippet for rendering empty content

@ -48,6 +48,10 @@
> Failed to hydrate the application
## invalid_snippet
> Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
## lifecycle_legacy_only
> `%name%(...)` cannot be used in runes mode

@ -30,6 +30,11 @@ export function RenderTag(node, context) {
let snippet_function = /** @type {Expression} */ (context.visit(callee));
if (node.metadata.dynamic) {
// If we have a chain expression then ensure a nullish snippet function gets turned into an empty one
if (node.expression.type === 'ChainExpression') {
snippet_function = b.logical('??', snippet_function, b.id('$.noop'));
}
context.state.init.push(
b.stmt(b.call('$.snippet', context.state.node, b.thunk(snippet_function), ...args))
);

@ -11,8 +11,10 @@ import { hydrate_next, hydrate_node, hydrating } from '../hydration.js';
import { create_fragment_from_html } from '../reconciler.js';
import { assign_nodes } from '../template.js';
import * as w from '../../warnings.js';
import * as e from '../../errors.js';
import { DEV } from 'esm-env';
import { get_first_child, get_next_sibling } from '../operations.js';
import { noop } from '../../../shared/utils.js';
/**
* @template {(node: TemplateNode, ...args: any[]) => void} SnippetFn
@ -25,7 +27,8 @@ export function snippet(node, get_snippet, ...args) {
var anchor = node;
/** @type {SnippetFn | null | undefined} */
var snippet;
// @ts-ignore
var snippet = noop;
/** @type {Effect | null} */
var snippet_effect;
@ -38,9 +41,11 @@ export function snippet(node, get_snippet, ...args) {
snippet_effect = null;
}
if (snippet) {
snippet_effect = branch(() => /** @type {SnippetFn} */ (snippet)(anchor, ...args));
if (DEV && snippet == null) {
e.invalid_snippet();
}
snippet_effect = branch(() => /** @type {SnippetFn} */ (snippet)(anchor, ...args));
}, EFFECT_TRANSPARENT);
if (hydrating) {

@ -210,6 +210,22 @@ export function hydration_failed() {
}
}
/**
* Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}`
* @returns {never}
*/
export function invalid_snippet() {
if (DEV) {
const error = new Error(`invalid_snippet\nCould not \`{@render}\` snippet due to the expression being \`null\` or \`undefined\`. Consider using optional chaining \`{@render snippet?.()}\``);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("invalid_snippet");
}
}
/**
* `%name%(...)` cannot be used in runes mode
* @param {string} name

@ -0,0 +1,13 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
const btn = target.querySelector('button');
assert.throws(() => {
btn?.click();
flushSync();
}, /invalid_snippet/);
}
});

@ -0,0 +1,13 @@
<script>
let state = $state({
value: counter
});
</script>
{#snippet counter()}
Test
{/snippet}
{@render state.value()}
<button onclick={() => state.value = undefined}>change</button>
Loading…
Cancel
Save