Skip to content

Commit b0f9ea3

Browse files
authored
fix: throw on duplicate class field declarations (#16502)
* fix: throw on duplicate class field declarations * doh * handle assignment in constructor * fix * apply suggestion from review * fix * fix failing test
1 parent 0bba84c commit b0f9ea3

File tree

6 files changed

+82
-8
lines changed

6 files changed

+82
-8
lines changed

.changeset/odd-phones-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: throw on duplicate class field declarations

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ The $ name is reserved, and cannot be used for variables and imports
364364
The $ prefix is reserved, and cannot be used for variables and imports
365365
```
366366

367+
### duplicate_class_field
368+
369+
```
370+
`%name%` has already been declared
371+
```
372+
367373
### each_item_invalid_assignment
368374

369375
```

packages/svelte/messages/compile-errors/script.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030

3131
> The $ prefix is reserved, and cannot be used for variables and imports
3232
33+
## duplicate_class_field
34+
35+
> `%name%` has already been declared
36+
3337
## each_item_invalid_assignment
3438

3539
> Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)

packages/svelte/src/compiler/errors.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@ export function dollar_prefix_invalid(node) {
152152
e(node, 'dollar_prefix_invalid', `The $ prefix is reserved, and cannot be used for variables and imports\nhttps://svelte.dev/e/dollar_prefix_invalid`);
153153
}
154154

155+
/**
156+
* `%name%` has already been declared
157+
* @param {null | number | NodeLike} node
158+
* @param {string} name
159+
* @returns {never}
160+
*/
161+
export function duplicate_class_field(node, name) {
162+
e(node, 'duplicate_class_field', `\`${name}\` has already been declared\nhttps://svelte.dev/e/duplicate_class_field`);
163+
}
164+
155165
/**
156166
* Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
157167
* @param {null | number | NodeLike} node

packages/svelte/src/compiler/phases/2-analyze/visitors/ClassBody.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export function ClassBody(node, context) {
3333
/** @type {Map<string, StateField>} */
3434
const state_fields = new Map();
3535

36+
/** @type {Map<string, Array<MethodDefinition['kind'] | 'prop' | 'assigned_prop'>>} */
37+
const fields = new Map();
38+
3639
context.state.analysis.classes.set(node, state_fields);
3740

3841
/** @type {MethodDefinition | null} */
@@ -54,6 +57,14 @@ export function ClassBody(node, context) {
5457
e.state_field_duplicate(node, name);
5558
}
5659

60+
const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name;
61+
const field = fields.get(_key);
62+
63+
// if there's already a method or assigned field, error
64+
if (field && !(field.length === 1 && field[0] === 'prop')) {
65+
e.duplicate_class_field(node, _key);
66+
}
67+
5768
state_fields.set(name, {
5869
node,
5970
type: rune,
@@ -67,10 +78,48 @@ export function ClassBody(node, context) {
6778
for (const child of node.body) {
6879
if (child.type === 'PropertyDefinition' && !child.computed && !child.static) {
6980
handle(child, child.key, child.value);
81+
const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
82+
const field = fields.get(key);
83+
if (!field) {
84+
fields.set(key, [child.value ? 'assigned_prop' : 'prop']);
85+
continue;
86+
}
87+
e.duplicate_class_field(child, key);
7088
}
7189

72-
if (child.type === 'MethodDefinition' && child.kind === 'constructor') {
73-
constructor = child;
90+
if (child.type === 'MethodDefinition') {
91+
if (child.kind === 'constructor') {
92+
constructor = child;
93+
} else if (!child.computed) {
94+
const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
95+
const field = fields.get(key);
96+
if (!field) {
97+
fields.set(key, [child.kind]);
98+
continue;
99+
}
100+
if (
101+
field.includes(child.kind) ||
102+
field.includes('prop') ||
103+
field.includes('assigned_prop')
104+
) {
105+
e.duplicate_class_field(child, key);
106+
}
107+
if (child.kind === 'get') {
108+
if (field.length === 1 && field[0] === 'set') {
109+
field.push('get');
110+
continue;
111+
}
112+
} else if (child.kind === 'set') {
113+
if (field.length === 1 && field[0] === 'get') {
114+
field.push('set');
115+
continue;
116+
}
117+
} else {
118+
field.push(child.kind);
119+
continue;
120+
}
121+
e.duplicate_class_field(child, key);
122+
}
74123
}
75124
}
76125

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[
22
{
3-
"code": "state_field_invalid_assignment",
4-
"message": "Cannot assign to a state field before its declaration",
3+
"code": "duplicate_class_field",
4+
"message": "`count` has already been declared",
55
"start": {
6-
"line": 2,
7-
"column": 1
6+
"line": 5,
7+
"column": 2
88
},
99
"end": {
10-
"line": 2,
11-
"column": 12
10+
"line": 5,
11+
"column": 24
1212
}
1313
}
1414
]

0 commit comments

Comments
 (0)