Skip to content

Commit 70e4128

Browse files
committed
implement rfc 33
1 parent 17c5402 commit 70e4128

File tree

63 files changed

+935
-70
lines changed

Some content is hidden

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

63 files changed

+935
-70
lines changed

src/compiler/compile/nodes/Binding.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export default class Binding extends Node {
6262
message: 'Cannot bind to a variable declared with {#await ... then} or {:catch} blocks'
6363
});
6464
}
65+
if (scope.is_const(name)) {
66+
component.error(this, {
67+
code: 'invalid-binding',
68+
message: 'Cannot bind to a variable declared with {@const ...}'
69+
});
70+
}
6571

6672
scope.dependencies_for_name.get(name).forEach(name => {
6773
const variable = component.var_lookup.get(name);

src/compiler/compile/nodes/CatchBlock.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import map_children from './shared/map_children';
21
import TemplateScope from './shared/TemplateScope';
32
import AbstractBlock from './shared/AbstractBlock';
43
import AwaitBlock from './AwaitBlock';
54
import Component from '../Component';
65
import { TemplateNode } from '../../interfaces';
6+
import get_const_tags from './shared/get_const_tags';
7+
import ConstTag from './ConstTag';
78

89
export default class CatchBlock extends AbstractBlock {
910
type: 'CatchBlock';
1011
scope: TemplateScope;
12+
const_tags: ConstTag[];
1113

1214
constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) {
1315
super(component, parent, scope, info);
@@ -18,7 +20,8 @@ export default class CatchBlock extends AbstractBlock {
1820
this.scope.add(context.key.name, parent.expression.dependencies, this);
1921
});
2022
}
21-
this.children = map_children(component, parent, this.scope, info.children);
23+
24+
([this.const_tags, this.children] = get_const_tags(info.children, component, this));
2225

2326
if (!info.skip) {
2427
this.warn_if_empty_block();
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import Node from './shared/Node';
2+
import Expression from './shared/Expression';
3+
import Component from '../Component';
4+
import TemplateScope from './shared/TemplateScope';
5+
import { Context, unpack_destructuring } from './shared/Context';
6+
import { ConstTag as ConstTagType } from '../../interfaces';
7+
import { INodeAllowConstTag } from './interfaces';
8+
import { walk } from 'estree-walker';
9+
import { extract_identifiers } from 'periscopic';
10+
import is_reference from 'is-reference';
11+
import get_object from '../utils/get_object';
12+
13+
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate']);
14+
15+
export default class ConstTag extends Node {
16+
type: 'ConstTag';
17+
expression: Expression;
18+
contexts: Context[] = [];
19+
node: ConstTagType;
20+
scope: TemplateScope;
21+
22+
assignees: Set<string> = new Set();
23+
dependencies: Set<string> = new Set();
24+
25+
constructor(component: Component, parent: INodeAllowConstTag, scope: TemplateScope, info: ConstTagType) {
26+
super(component, parent, scope, info);
27+
28+
if (!allowed_parents.has(parent.type)) {
29+
component.error(info, { code: 'invalid-const-placement', message: '{@const} must be the immediate child of {#each}, {:then}, {:catch}, <svelte:fragment> and <Component>' });
30+
}
31+
this.node = info;
32+
this.scope = scope;
33+
34+
const { assignees, dependencies } = this;
35+
36+
extract_identifiers(info.expression.left).forEach(({ name }) => {
37+
assignees.add(name);
38+
const owner = this.scope.get_owner(name);
39+
if (owner === parent) {
40+
component.error(info, { code: 'invalid-const-declaration', message: `'${name}' has already been declared` });
41+
}
42+
});
43+
44+
walk(info.expression.right, {
45+
enter(node, parent) {
46+
if (is_reference(node, parent)) {
47+
const identifier = get_object(node);
48+
const { name } = identifier;
49+
dependencies.add(name);
50+
}
51+
}
52+
});
53+
}
54+
55+
parse_expression() {
56+
unpack_destructuring(this.contexts, this.node.expression.left);
57+
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
58+
this.contexts.forEach(context => {
59+
const owner = this.scope.get_owner(context.key.name);
60+
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
61+
this.component.error(this.node, { code: 'invalid-const-declaration', message: `'${context.key.name}' has already been declared` });
62+
}
63+
this.scope.add(context.key.name, this.expression.dependencies, this);
64+
});
65+
}
66+
}

src/compiler/compile/nodes/DefaultSlotTemplate.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/compiler/compile/nodes/EachBlock.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import ElseBlock from './ElseBlock';
22
import Expression from './shared/Expression';
3-
import map_children from './shared/map_children';
43
import TemplateScope from './shared/TemplateScope';
54
import AbstractBlock from './shared/AbstractBlock';
65
import Element from './Element';
6+
import ConstTag from './ConstTag';
77
import { Context, unpack_destructuring } from './shared/Context';
88
import { Node } from 'estree';
99
import Component from '../Component';
1010
import { TemplateNode } from '../../interfaces';
11+
import get_const_tags from './shared/get_const_tags';
1112

1213
export default class EachBlock extends AbstractBlock {
1314
type: 'EachBlock';
@@ -21,6 +22,7 @@ export default class EachBlock extends AbstractBlock {
2122
key: Expression;
2223
scope: TemplateScope;
2324
contexts: Context[];
25+
const_tags: ConstTag[];
2426
has_animation: boolean;
2527
has_binding = false;
2628
has_index_binding = false;
@@ -56,7 +58,7 @@ export default class EachBlock extends AbstractBlock {
5658

5759
this.has_animation = false;
5860

59-
this.children = map_children(component, this, this.scope, info.children);
61+
([this.const_tags, this.children] = get_const_tags(info.children, component, this));
6062

6163
if (this.has_animation) {
6264
if (this.children.length !== 1) {

src/compiler/compile/nodes/InlineComponent.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,15 @@ export default class InlineComponent extends Node {
137137
slot_template.attributes.push(attribute);
138138
}
139139
}
140-
140+
// transfer const
141+
for (let i = child.children.length - 1; i >= 0; i--) {
142+
const child_child = child.children[i];
143+
if (child_child.type === 'ConstTag') {
144+
slot_template.children.push(child_child);
145+
child.children.splice(i, 1);
146+
}
147+
}
148+
141149
children.push(slot_template);
142150
info.children.splice(i, 1);
143151
}

src/compiler/compile/nodes/SlotTemplate.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import map_children from './shared/map_children';
21
import Component from '../Component';
32
import TemplateScope from './shared/TemplateScope';
43
import Node from './shared/Node';
54
import Let from './Let';
65
import Attribute from './Attribute';
76
import { INode } from './interfaces';
7+
import get_const_tags from './shared/get_const_tags';
8+
import ConstTag from './ConstTag';
89

910
export default class SlotTemplate extends Node {
1011
type: 'SlotTemplate';
1112
scope: TemplateScope;
1213
children: INode[];
1314
lets: Let[] = [];
15+
const_tags: ConstTag[];
1416
slot_attribute: Attribute;
1517
slot_template_name: string = 'default';
1618

@@ -68,7 +70,7 @@ export default class SlotTemplate extends Node {
6870
});
6971

7072
this.scope = scope;
71-
this.children = map_children(component, this, this.scope, info.children);
73+
([this.const_tags, this.children] = get_const_tags(info.children, component, this));
7274
}
7375

7476
validate_slot_template_placement() {

src/compiler/compile/nodes/ThenBlock.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import map_children from './shared/map_children';
21
import TemplateScope from './shared/TemplateScope';
32
import AbstractBlock from './shared/AbstractBlock';
43
import AwaitBlock from './AwaitBlock';
54
import Component from '../Component';
65
import { TemplateNode } from '../../interfaces';
6+
import get_const_tags from './shared/get_const_tags';
7+
import ConstTag from './ConstTag';
78

89
export default class ThenBlock extends AbstractBlock {
910
type: 'ThenBlock';
1011
scope: TemplateScope;
12+
const_tags: ConstTag[];
1113

1214
constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) {
1315
super(component, parent, scope, info);
@@ -18,7 +20,8 @@ export default class ThenBlock extends AbstractBlock {
1820
this.scope.add(context.key.name, parent.expression.dependencies, this);
1921
});
2022
}
21-
this.children = map_children(component, parent, this.scope, info.children);
23+
24+
([this.const_tags, this.children] = get_const_tags(info.children, component, this));
2225

2326
if (!info.skip) {
2427
this.warn_if_empty_block();

src/compiler/compile/nodes/interfaces.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Body from './Body';
99
import CatchBlock from './CatchBlock';
1010
import Class from './Class';
1111
import Comment from './Comment';
12+
import ConstTag from './ConstTag';
1213
import DebugTag from './DebugTag';
1314
import EachBlock from './EachBlock';
1415
import Element from './Element';
@@ -26,7 +27,6 @@ import PendingBlock from './PendingBlock';
2627
import RawMustacheTag from './RawMustacheTag';
2728
import Slot from './Slot';
2829
import SlotTemplate from './SlotTemplate';
29-
import DefaultSlotTemplate from './DefaultSlotTemplate';
3030
import Text from './Text';
3131
import ThenBlock from './ThenBlock';
3232
import Title from './Title';
@@ -44,6 +44,7 @@ export type INode = Action
4444
| CatchBlock
4545
| Class
4646
| Comment
47+
| ConstTag
4748
| DebugTag
4849
| EachBlock
4950
| Element
@@ -61,10 +62,16 @@ export type INode = Action
6162
| RawMustacheTag
6263
| Slot
6364
| SlotTemplate
64-
| DefaultSlotTemplate
6565
| Tag
6666
| Text
6767
| ThenBlock
6868
| Title
6969
| Transition
7070
| Window;
71+
72+
export type INodeAllowConstTag =
73+
| EachBlock
74+
| CatchBlock
75+
| ThenBlock
76+
| InlineComponent
77+
| SlotTemplate;

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ export default class Expression {
135135
if (names) {
136136
names.forEach(name => {
137137
if (template_scope.names.has(name)) {
138+
if (template_scope.is_const(name)) {
139+
component.error(node, {
140+
code: 'invalid-const-update',
141+
message: `'${name}' is declared using {@const ...} and it is read-only`
142+
});
143+
}
144+
138145
template_scope.dependencies_for_name.get(name).forEach(name => {
139146
const variable = component.var_lookup.get(name);
140147
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
@@ -174,7 +181,7 @@ export default class Expression {
174181
}
175182

176183
// TODO move this into a render-dom wrapper?
177-
manipulate(block?: Block) {
184+
manipulate(block?: Block, ctx?: string | void) {
178185
// TODO ideally we wouldn't end up calling this method
179186
// multiple times
180187
if (this.manipulated) return this.manipulated;
@@ -221,7 +228,7 @@ export default class Expression {
221228
component.add_reference(name); // TODO is this redundant/misplaced?
222229
}
223230
} else if (is_contextual(component, template_scope, name)) {
224-
const reference = block.renderer.reference(node);
231+
const reference = block.renderer.reference(node, ctx);
225232
this.replace(reference);
226233
}
227234

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import CatchBlock from '../CatchBlock';
44
import InlineComponent from '../InlineComponent';
55
import Element from '../Element';
66
import SlotTemplate from '../SlotTemplate';
7+
import ConstTag from '../ConstTag';
78

8-
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate;
9+
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate | ConstTag;
910

1011
export default class TemplateScope {
1112
names: Set<string>;
@@ -48,4 +49,9 @@ export default class TemplateScope {
4849
const owner = this.get_owner(name);
4950
return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock');
5051
}
52+
53+
is_const(name: string) {
54+
const owner = this.get_owner(name);
55+
return owner && owner.type === 'ConstTag';
56+
}
5157
}

0 commit comments

Comments
 (0)