Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/odd-phones-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: throw on duplicate class field declarations
6 changes: 6 additions & 0 deletions documentation/docs/98-reference/.generated/compile-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

### duplicate_class_field

```
`%name%` has already been declared
```

### each_item_invalid_assignment

```
Expand Down
4 changes: 4 additions & 0 deletions packages/svelte/messages/compile-errors/script.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@

> The $ prefix is reserved, and cannot be used for variables and imports

## duplicate_class_field

> `%name%` has already been declared

## 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}`)
Expand Down
10 changes: 10 additions & 0 deletions packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
}

/**
* `%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}`)
* @param {null | number | NodeLike} node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export function ClassBody(node, context) {
/** @type {Map<string, StateField>} */
const state_fields = new Map();

/** @type {Map<string, Array<MethodDefinition['kind'] | 'prop'>>} */
const fields = new Map();

context.state.analysis.classes.set(node, state_fields);

/** @type {MethodDefinition | null} */
Expand All @@ -54,6 +57,13 @@ export function ClassBody(node, context) {
e.state_field_duplicate(node, name);
}

const _key = (key.type === 'PrivateIdentifier' ? '#' : '') + name;
const field = fields.get(_key);

if (field && (field.length > 1 || (field.length === 1 && field[0] !== 'prop'))) {
e.duplicate_class_field(node, _key);
}

state_fields.set(name, {
node,
type: rune,
Expand All @@ -67,10 +77,44 @@ export function ClassBody(node, context) {
for (const child of node.body) {
if (child.type === 'PropertyDefinition' && !child.computed && !child.static) {
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, ['prop']);
continue;
}
e.duplicate_class_field(child, key);
}

if (child.type === 'MethodDefinition' && child.kind === 'constructor') {
constructor = child;
if (child.type === 'MethodDefinition') {
if (child.kind === 'constructor') {
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')) {
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);
}
}
}

Expand Down
Loading