Skip to content

Commit 9e4479a

Browse files
committed
add validation and test
1 parent ac8f2d6 commit 9e4479a

File tree

70 files changed

+778
-106
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+778
-106
lines changed

src/compiler/compile/nodes/Element.ts

+5
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,11 @@ export default class Element extends Node {
847847
);
848848
}
849849
}
850+
851+
get slot_template_name() {
852+
// attribute.get_static_value
853+
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
854+
}
850855
}
851856

852857
function should_have_attribute(

src/compiler/compile/nodes/SlotTemplate.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default class SlotTemplate extends Node {
1212
children: INode[];
1313
lets: Let[] = [];
1414
slot_attribute: Attribute;
15-
slot_name: string = 'default';
15+
slot_template_name: string = 'default';
1616

1717
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
1818
super(component, parent, scope, info);
@@ -52,7 +52,7 @@ export default class SlotTemplate extends Node {
5252
message: `slot attribute value is missing`
5353
});
5454
}
55-
this.slot_name = this.slot_attribute.get_static_value() as string;
55+
this.slot_template_name = this.slot_attribute.get_static_value() as string;
5656
break;
5757
}
5858
throw new Error(`Invalid attribute "${node.name}" in <svelte:slot>`);
@@ -70,7 +70,7 @@ export default class SlotTemplate extends Node {
7070
if (this.parent.type !== 'InlineComponent') {
7171
this.component.error(this, {
7272
code: `invalid-slotted-content`,
73-
message: `Element with a slot='...' attribute must be a child of a component`
73+
message: `<svelte:slot> must be a child of a component`
7474
});
7575
}
7676
}

src/compiler/compile/nodes/shared/TemplateScope.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import ThenBlock from '../ThenBlock';
33
import CatchBlock from '../CatchBlock';
44
import InlineComponent from '../InlineComponent';
55
import Element from '../Element';
6+
import SlotTemplate from '../SlotTemplate';
67

7-
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element;
8+
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate;
89

910
export default class TemplateScope {
1011
names: Set<string>;
@@ -40,7 +41,7 @@ export default class TemplateScope {
4041

4142
is_let(name: string) {
4243
const owner = this.get_owner(name);
43-
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
44+
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate');
4445
}
4546

4647
is_await(name: string) {

src/compiler/compile/render_dom/wrappers/Element/index.ts

+1-60
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Renderer from '../../Renderer';
22
import Element from '../../../nodes/Element';
33
import Wrapper from '../shared/Wrapper';
44
import Block from '../../Block';
5-
import { is_void, sanitize } from '../../../../utils/names';
5+
import { is_void } from '../../../../utils/names';
66
import FragmentWrapper from '../Fragment';
77
import { escape_html, string_literal } from '../../../utils/stringify';
88
import TextWrapper from '../Text';
@@ -14,12 +14,9 @@ import StyleAttributeWrapper from './StyleAttribute';
1414
import SpreadAttributeWrapper from './SpreadAttribute';
1515
import { dimensions } from '../../../../utils/patterns';
1616
import Binding from './Binding';
17-
import InlineComponentWrapper from '../InlineComponent';
1817
import add_to_set from '../../../utils/add_to_set';
1918
import { add_event_handler } from '../shared/add_event_handlers';
2019
import { add_action } from '../shared/add_actions';
21-
import create_debugging_comment from '../shared/create_debugging_comment';
22-
import { get_slot_definition } from '../shared/get_slot_definition';
2320
import bind_this from '../shared/bind_this';
2421
import { is_head } from '../shared/is_head';
2522
import { Identifier } from 'estree';
@@ -176,49 +173,6 @@ export default class ElementWrapper extends Wrapper {
176173
}
177174

178175
this.attributes = this.node.attributes.map(attribute => {
179-
if (attribute.name === 'slot') {
180-
// TODO make separate subclass for this?
181-
let owner = this.parent;
182-
while (owner) {
183-
if (owner.node.type === 'InlineComponent') {
184-
break;
185-
}
186-
187-
if (owner.node.type === 'Element' && /-/.test(owner.node.name)) {
188-
break;
189-
}
190-
191-
owner = owner.parent;
192-
}
193-
194-
if (owner && owner.node.type === 'InlineComponent') {
195-
const name = attribute.get_static_value() as string;
196-
197-
if (!(owner as unknown as InlineComponentWrapper).slots.has(name)) {
198-
const child_block = block.child({
199-
comment: create_debugging_comment(node, this.renderer.component),
200-
name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`),
201-
type: 'slot'
202-
});
203-
204-
const { scope, lets } = this.node;
205-
const seen = new Set(lets.map(l => l.name.name));
206-
207-
(owner as unknown as InlineComponentWrapper).node.lets.forEach(l => {
208-
if (!seen.has(l.name.name)) lets.push(l);
209-
});
210-
211-
(owner as unknown as InlineComponentWrapper).slots.set(
212-
name,
213-
get_slot_definition(child_block, scope, lets)
214-
);
215-
this.renderer.blocks.push(child_block);
216-
}
217-
218-
this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;
219-
block = this.slot_block;
220-
}
221-
}
222176
if (attribute.name === 'style') {
223177
return new StyleAttributeWrapper(this, block, attribute);
224178
}
@@ -273,26 +227,13 @@ export default class ElementWrapper extends Wrapper {
273227
}
274228

275229
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
276-
277-
if (this.slot_block) {
278-
block.parent.add_dependencies(block.dependencies);
279-
280-
// appalling hack
281-
const index = block.parent.wrappers.indexOf(this);
282-
block.parent.wrappers.splice(index, 1);
283-
block.wrappers.push(this);
284-
}
285230
}
286231

287232
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
288233
const { renderer } = this;
289234

290235
if (this.node.name === 'noscript') return;
291236

292-
if (this.slot_block) {
293-
block = this.slot_block;
294-
}
295-
296237
const node = this.var;
297238
const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
298239
const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;

src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts

+37-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Renderer from '../../Renderer';
44
import Block from '../../Block';
55
import InlineComponent from '../../../nodes/InlineComponent';
66
import FragmentWrapper from '../Fragment';
7+
import SlotTemplateWrapper from '../SlotTemplate';
78
import { sanitize } from '../../../../utils/names';
89
import add_to_set from '../../../utils/add_to_set';
910
import { b, x, p } from 'code-red';
@@ -19,11 +20,15 @@ import { extract_names } from 'periscopic';
1920
import mark_each_block_bindings from '../shared/mark_each_block_bindings';
2021
import { string_to_member_expression } from '../../../utils/string_to_member_expression';
2122

23+
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
24+
2225
export default class InlineComponentWrapper extends Wrapper {
2326
var: Identifier;
24-
slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
27+
slots: Map<string, SlotDefinition> = new Map();
2528
node: InlineComponent;
2629
fragment: FragmentWrapper;
30+
children: Array<Wrapper | FragmentWrapper> = [];
31+
default_slot_block: Block;
2732

2833
constructor(
2934
renderer: Renderer,
@@ -76,16 +81,35 @@ export default class InlineComponentWrapper extends Wrapper {
7681
});
7782
});
7883

84+
const children = this.node.children.slice();
85+
for (let i=children.length - 1; i>=0;) {
86+
const child = children[i];
87+
if (child.type === 'SlotTemplate' || (child.type === 'Element' && child.attributes.find(attribute => attribute.name === 'slot'))) {
88+
const slot_template = new SlotTemplateWrapper(renderer, block, this, child, strip_whitespace, next_sibling);
89+
this.children.push(slot_template);
90+
children.splice(i, 1);
91+
continue;
92+
}
93+
94+
i--;
95+
}
96+
97+
if (this.slots.has('default') && children.filter(node => !(node.type === 'Text' && node.data.trim() === ''))) {
98+
throw new Error('Found elements without slot attribute when using slot="default"');
99+
}
100+
79101
const default_slot = block.child({
80102
comment: create_debugging_comment(node, renderer.component),
81103
name: renderer.component.get_unique_name(`create_default_slot`),
82104
type: 'slot'
83105
});
84106

85107
this.renderer.blocks.push(default_slot);
108+
this.default_slot_block = default_slot;
86109

87110
this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
88-
this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, strip_whitespace, next_sibling);
111+
const fragment = new FragmentWrapper(renderer, default_slot, children, this, strip_whitespace, next_sibling);
112+
this.children.push(fragment);
89113

90114
const dependencies: Set<string> = new Set();
91115

@@ -102,6 +126,13 @@ export default class InlineComponentWrapper extends Wrapper {
102126
block.add_outro();
103127
}
104128

129+
set_slot(name: string, slot_definition: SlotDefinition) {
130+
if (this.slots.has(name)) {
131+
throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`);
132+
}
133+
this.slots.set(name, slot_definition);
134+
}
135+
105136
warn_if_reactive() {
106137
const { name } = this.node;
107138
const variable = this.renderer.component.var_lookup.get(name);
@@ -135,14 +166,10 @@ export default class InlineComponentWrapper extends Wrapper {
135166
const statements: Array<Node | Node[]> = [];
136167
const updates: Array<Node | Node[]> = [];
137168

138-
if (this.fragment) {
169+
this.children.forEach((child) => {
139170
this.renderer.add_to_context('$$scope', true);
140-
const default_slot = this.slots.get('default');
141-
142-
this.fragment.nodes.forEach((child) => {
143-
child.render(default_slot.block, null, x`#nodes` as unknown as Identifier);
144-
});
145-
}
171+
child.render(this.default_slot_block, null, x`#nodes` as Identifier);
172+
});
146173

147174
let props;
148175
const name_changes = block.get_unique_name(`${name.name}_changes`);
@@ -194,7 +221,7 @@ export default class InlineComponentWrapper extends Wrapper {
194221
component_opts.properties.push(p`$$inline: true`);
195222
}
196223

197-
const fragment_dependencies = new Set(this.fragment ? ['$$scope'] : []);
224+
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []);
198225
this.slots.forEach(slot => {
199226
slot.block.dependencies.forEach(name => {
200227
const is_let = slot.scope.is_let(name);

src/compiler/compile/render_dom/wrappers/SlotTemplate.ts

+40-26
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,77 @@
11
import Wrapper from "./shared/Wrapper";
22
import Renderer from "../Renderer";
33
import Block from "../Block";
4-
import SlotTemplate from "../../nodes/SlotTemplate";
54
import FragmentWrapper from "./Fragment";
65
import create_debugging_comment from "./shared/create_debugging_comment";
7-
import { get_slot_definition } from './shared/get_slot_definition';
6+
import { get_slot_definition } from "./shared/get_slot_definition";
87
import { x } from "code-red";
98
import { sanitize } from "../../../utils/names";
109
import { Identifier } from "estree";
1110
import InlineComponentWrapper from "./InlineComponent";
11+
import { extract_names } from "periscopic";
12+
import { INode } from "../../nodes/interfaces";
13+
import Let from "../../nodes/Let";
14+
import TemplateScope from "../../nodes/shared/TemplateScope";
1215

13-
export default class SlotWrapper extends Wrapper {
14-
node: SlotTemplate;
16+
type NodeWithLets = INode & {
17+
scope: TemplateScope;
18+
lets: Let[];
19+
slot_template_name: string;
20+
};
21+
22+
export default class SlotTemplateWrapper extends Wrapper {
23+
node: NodeWithLets;
1524
fragment: FragmentWrapper;
1625
block: Block;
26+
parent: InlineComponentWrapper;
1727

1828
constructor(
1929
renderer: Renderer,
2030
block: Block,
2131
parent: Wrapper,
22-
node: SlotTemplate,
32+
node: NodeWithLets,
2333
strip_whitespace: boolean,
2434
next_sibling: Wrapper
2535
) {
2636
super(renderer, block, parent, node);
2737

28-
const { scope, lets, slot_name } = this.node;
38+
const { scope, lets, slot_template_name } = this.node;
39+
40+
lets.forEach(l => {
41+
extract_names(l.value || l.name).forEach(name => {
42+
renderer.add_to_context(name, true);
43+
});
44+
});
2945

3046
this.block = block.child({
31-
comment: create_debugging_comment(node, this.renderer.component),
47+
comment: create_debugging_comment(this.node, this.renderer.component),
3248
name: this.renderer.component.get_unique_name(
33-
`create_${sanitize(slot_name)}_slot`
49+
`create_${sanitize(slot_template_name)}_slot`
3450
),
3551
type: "slot"
3652
});
3753
this.renderer.blocks.push(this.block);
3854

39-
if (this.parent.node.type === "InlineComponent") {
40-
const component = (this.parent as unknown) as InlineComponentWrapper;
41-
const seen = new Set(lets.map(l => l.name.name));
42-
component.node.lets.forEach(l => {
43-
if (!seen.has(l.name.name)) lets.push(l);
44-
});
45-
46-
component.slots.set(
47-
slot_name,
48-
get_slot_definition(this.block, scope, lets)
49-
);
50-
}
55+
const seen = new Set(lets.map(l => l.name.name));
56+
this.parent.node.lets.forEach(l => {
57+
if (!seen.has(l.name.name)) lets.push(l);
58+
});
5159

52-
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling);
60+
this.parent.set_slot(
61+
slot_template_name,
62+
get_slot_definition(this.block, scope, lets)
63+
);
5364

65+
this.fragment = new FragmentWrapper(
66+
renderer,
67+
this.block,
68+
node.type === "SlotTemplate" ? node.children : [node],
69+
this,
70+
strip_whitespace,
71+
next_sibling
72+
);
5473

5574
this.block.parent.add_dependencies(this.block.dependencies);
56-
57-
// appalling hack, copied from ElementWrapper
58-
const index = this.block.parent.wrappers.indexOf(this);
59-
this.block.parent.wrappers.splice(index, 1);
60-
this.block.wrappers.push(this);
6175
}
6276

6377
render() {

src/compiler/compile/render_ssr/handlers/SlotTemplate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
1919
if (!seen.has(l.name.name)) lets.push(l);
2020
});
2121

22-
options.slot_scopes.set(node.slot_name, {
22+
options.slot_scopes.set(node.slot_template_name, {
2323
input: get_slot_scope(node.lets),
2424
output: renderer.pop()
2525
});

test/parser/samples/error-svelte-selfdestructive/error.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"code": "invalid-tag-name",
3-
"message": "Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self or svelte:component",
3+
"message": "Valid <svelte:...> tag names are svelte:head, svelte:options, svelte:window, svelte:body, svelte:self, svelte:component or svelte:slot",
44
"pos": 10,
55
"start": {
66
"character": 10,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<slot value="Hi" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
error: 'Duplicate slot name "foo" in <Nested>'
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
</script>
4+
5+
<Nested>
6+
<svelte:slot slot="foo">{value}</svelte:slot>
7+
<svelte:slot slot="foo">{value}</svelte:slot>
8+
</Nested>

0 commit comments

Comments
 (0)