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
pull/16500/head
ComputerGuy 2 months ago committed by GitHub
parent 0bba84cc20
commit b0f9ea3ae6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: throw on duplicate class field declarations

@ -364,6 +364,12 @@ The $ name is reserved, and cannot be used for variables and imports
The $ prefix is reserved, and cannot be used for variables and imports The $ prefix is reserved, and cannot be used for variables and imports
``` ```
### duplicate_class_field
```
`%name%` has already been declared
```
### each_item_invalid_assignment ### each_item_invalid_assignment
``` ```

@ -30,6 +30,10 @@
> The $ prefix is reserved, and cannot be used for variables and imports > The $ prefix is reserved, and cannot be used for variables and imports
## duplicate_class_field
> `%name%` has already been declared
## each_item_invalid_assignment ## each_item_invalid_assignment
> 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}`) > 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}`)

@ -152,6 +152,16 @@ export function dollar_prefix_invalid(node) {
e(node, 'dollar_prefix_invalid', `The $ prefix is reserved, and cannot be used for variables and imports\nhttps://svelte.dev/e/dollar_prefix_invalid`); e(node, 'dollar_prefix_invalid', `The $ prefix is reserved, and cannot be used for variables and imports\nhttps://svelte.dev/e/dollar_prefix_invalid`);
} }
/**
* `%name%` has already been declared
* @param {null | number | NodeLike} node
* @param {string} name
* @returns {never}
*/
export function duplicate_class_field(node, name) {
e(node, 'duplicate_class_field', `\`${name}\` has already been declared\nhttps://svelte.dev/e/duplicate_class_field`);
}
/** /**
* 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}`) * 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}`)
* @param {null | number | NodeLike} node * @param {null | number | NodeLike} node

@ -33,6 +33,9 @@ export function ClassBody(node, context) {
/** @type {Map<string, StateField>} */ /** @type {Map<string, StateField>} */
const state_fields = new Map(); const state_fields = new Map();
/** @type {Map<string, Array<MethodDefinition['kind'] | 'prop' | 'assigned_prop'>>} */
const fields = new Map();
context.state.analysis.classes.set(node, state_fields); context.state.analysis.classes.set(node, state_fields);
/** @type {MethodDefinition | null} */ /** @type {MethodDefinition | null} */
@ -54,6 +57,14 @@ export function ClassBody(node, context) {
e.state_field_duplicate(node, name); e.state_field_duplicate(node, name);
} }
const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name;
const field = fields.get(_key);
// if there's already a method or assigned field, error
if (field && !(field.length === 1 && field[0] === 'prop')) {
e.duplicate_class_field(node, _key);
}
state_fields.set(name, { state_fields.set(name, {
node, node,
type: rune, type: rune,
@ -67,10 +78,48 @@ export function ClassBody(node, context) {
for (const child of node.body) { for (const child of node.body) {
if (child.type === 'PropertyDefinition' && !child.computed && !child.static) { if (child.type === 'PropertyDefinition' && !child.computed && !child.static) {
handle(child, child.key, child.value); handle(child, child.key, child.value);
const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
const field = fields.get(key);
if (!field) {
fields.set(key, [child.value ? 'assigned_prop' : 'prop']);
continue;
}
e.duplicate_class_field(child, key);
} }
if (child.type === 'MethodDefinition' && child.kind === 'constructor') { if (child.type === 'MethodDefinition') {
if (child.kind === 'constructor') {
constructor = child; constructor = child;
} else if (!child.computed) {
const key = (child.key.type === 'PrivateIdentifier' ? '#' : '') + get_name(child.key);
const field = fields.get(key);
if (!field) {
fields.set(key, [child.kind]);
continue;
}
if (
field.includes(child.kind) ||
field.includes('prop') ||
field.includes('assigned_prop')
) {
e.duplicate_class_field(child, key);
}
if (child.kind === 'get') {
if (field.length === 1 && field[0] === 'set') {
field.push('get');
continue;
}
} else if (child.kind === 'set') {
if (field.length === 1 && field[0] === 'get') {
field.push('set');
continue;
}
} else {
field.push(child.kind);
continue;
}
e.duplicate_class_field(child, key);
}
} }
} }

@ -1,14 +1,14 @@
[ [
{ {
"code": "state_field_invalid_assignment", "code": "duplicate_class_field",
"message": "Cannot assign to a state field before its declaration", "message": "`count` has already been declared",
"start": { "start": {
"line": 2, "line": 5,
"column": 1 "column": 2
}, },
"end": { "end": {
"line": 2, "line": 5,
"column": 12 "column": 24
} }
} }
] ]

Loading…
Cancel
Save