Skip to content

Commit 67a6753

Browse files
committed
init
1 parent aaeda65 commit 67a6753

File tree

10 files changed

+75
-5
lines changed

10 files changed

+75
-5
lines changed

.changeset/bright-jeans-compare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
fix: add snippet argument validation in dev

documentation/docs/98-reference/.generated/shared-errors.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ This error would be thrown in a setup like this:
3030

3131
Here, `List.svelte` is using `{@render children(item)` which means it expects `Parent.svelte` to use snippets. Instead, `Parent.svelte` uses the deprecated `let:` directive. This combination of APIs is incompatible, hence the error.
3232

33+
### invalid_snippet_arguments
34+
35+
```
36+
A snippet function was passed invalid arguments. A snippet function should only be called via `{@render ...}`
37+
```
38+
3339
### lifecycle_outside_component
3440

3541
```

packages/svelte/messages/shared-errors/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ This error would be thrown in a setup like this:
2626

2727
Here, `List.svelte` is using `{@render children(item)` which means it expects `Parent.svelte` to use snippets. Instead, `Parent.svelte` uses the deprecated `let:` directive. This combination of APIs is incompatible, hence the error.
2828

29+
## invalid_snippet_arguments
30+
31+
> A snippet function was passed invalid arguments. A snippet function should only be called via `{@render ...}`
32+
2933
## lifecycle_outside_component
3034

3135
> `%name%(...)` can only be used during component initialisation

packages/svelte/src/compiler/phases/3-transform/client/visitors/SnippetBlock.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { BlockStatement, Expression, Identifier, Pattern, Statement } from 'estree' */
1+
/** @import { AssignmentPattern, BlockStatement, Expression, Identifier, Statement } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
44
import { dev } from '../../../../state.js';
@@ -12,7 +12,7 @@ import { get_value } from './shared/declarations.js';
1212
*/
1313
export function SnippetBlock(node, context) {
1414
// TODO hoist where possible
15-
/** @type {Pattern[]} */
15+
/** @type {(Identifier | AssignmentPattern)[]} */
1616
const args = [b.id('$$anchor')];
1717

1818
/** @type {BlockStatement} */
@@ -66,7 +66,18 @@ export function SnippetBlock(node, context) {
6666
}
6767
}
6868
}
69-
69+
if (dev) {
70+
declarations.unshift(
71+
b.stmt(
72+
b.call(
73+
'$.validate_snippet_args',
74+
.../** @type {Identifier[]} */ (
75+
args.map((arg) => (arg?.type === 'Identifier' ? arg : arg?.left))
76+
)
77+
)
78+
)
79+
);
80+
}
7081
body = b.block([
7182
...declarations,
7283
.../** @type {BlockStatement} */ (context.visit(node.body, child_state)).body

packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @import { BlockStatement } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types.js' */
4+
import { dev } from '../../../../state.js';
45
import * as b from '../../../../utils/builders.js';
56

67
/**
@@ -13,7 +14,9 @@ export function SnippetBlock(node, context) {
1314
[b.id('$$payload'), ...node.parameters],
1415
/** @type {BlockStatement} */ (context.visit(node.body))
1516
);
16-
17+
if (dev) {
18+
fn.body.body.unshift(b.stmt(b.call('$.validate_snippet_args', b.id('$$payload'))));
19+
}
1720
// @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone
1821
fn.___snippet = true;
1922

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { invalid_snippet_arguments } from '../../shared/errors';
2+
/**
3+
* @param {Node} anchor
4+
* @param {...(()=>any)[]} args
5+
*/
6+
export function validate_snippet_args(anchor, ...args) {
7+
if (typeof anchor !== 'object' || !(anchor instanceof Node)) {
8+
invalid_snippet_arguments();
9+
}
10+
for (let arg of args) {
11+
if (typeof arg !== 'function') {
12+
invalid_snippet_arguments();
13+
}
14+
}
15+
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export {
1616
export { check_target, legacy_api } from './dev/legacy.js';
1717
export { trace } from './dev/tracing.js';
1818
export { inspect } from './dev/inspect.js';
19+
export { validate_snippet_args } from './dev/validation.js';
1920
export { await_block as await } from './dom/blocks/await.js';
2021
export { if_block as if } from './dom/blocks/if.js';
2122
export { key_block as key } from './dom/blocks/key.js';

packages/svelte/src/internal/server/dev.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
is_tag_valid_with_parent
66
} from '../../html-tree-validation.js';
77
import { current_component } from './context.js';
8+
import { invalid_snippet_arguments } from '../shared/errors';
89

910
/**
1011
* @typedef {{
@@ -98,3 +99,12 @@ export function push_element(payload, tag, line, column) {
9899
export function pop_element() {
99100
parent = /** @type {Element} */ (parent).parent;
100101
}
102+
103+
/**
104+
* @param {Payload} payload
105+
*/
106+
export function validate_snippet_args(payload) {
107+
if (typeof payload !== 'object' || !('out' in payload)) {
108+
invalid_snippet_arguments();
109+
}
110+
}

packages/svelte/src/internal/server/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ export { html } from './blocks/html.js';
541541

542542
export { push, pop } from './context.js';
543543

544-
export { push_element, pop_element } from './dev.js';
544+
export { push_element, pop_element, validate_snippet_args } from './dev.js';
545545

546546
export { snapshot } from '../shared/clone.js';
547547

packages/svelte/src/internal/shared/errors.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ export function invalid_default_snippet() {
1717
}
1818
}
1919

20+
/**
21+
* A snippet function was passed invalid arguments. A snippet function should only be called via `{@render ...}`
22+
* @returns {never}
23+
*/
24+
export function invalid_snippet_arguments() {
25+
if (DEV) {
26+
const error = new Error(`invalid_snippet_arguments\nA snippet function was passed invalid arguments. A snippet function should only be called via \`{@render ...}\`\nhttps://svelte.dev/e/invalid_snippet_arguments`);
27+
28+
error.name = 'Svelte error';
29+
throw error;
30+
} else {
31+
throw new Error(`https://svelte.dev/e/invalid_snippet_arguments`);
32+
}
33+
}
34+
2035
/**
2136
* `%name%(...)` can only be used during component initialisation
2237
* @param {string} name

0 commit comments

Comments
 (0)