From 03309a718e75af68cbfefc89d242612afb3c3c03 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 30 Nov 2023 15:18:14 +0100 Subject: [PATCH] slot validation --- packages/svelte/src/compiler/errors.js | 18 +++++++++------- .../src/compiler/phases/2-analyze/index.js | 2 ++ .../src/compiler/phases/2-analyze/types.d.ts | 1 + .../compiler/phases/2-analyze/validation.js | 21 +++++++++++++++++-- .../_config.js | 3 --- .../errors.json | 5 +++++ .../_config.js | 3 --- .../_config.js | 3 --- .../errors.json | 2 +- .../samples/component-slot-dynamic/_config.js | 3 --- .../component-slot-dynamic/errors.json | 4 ++-- .../component-slot-named-duplicate/_config.js | 3 --- .../errors.json | 5 +++++ .../_config.js | 3 --- .../errors.json | 2 +- .../component-slotted-each-block/_config.js | 3 --- .../component-slotted-each-block/errors.json | 2 +- .../component-slotted-if-block/_config.js | 3 --- .../component-slotted-if-block/errors.json | 2 +- 19 files changed, 48 insertions(+), 40 deletions(-) delete mode 100644 packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js delete mode 100644 packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index c7f120f09d..8b8a64f8ad 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -252,13 +252,19 @@ const attributes = { const slots = { 'invalid-slot-element-attribute': () => ` can only receive attributes, not directives`, 'invalid-slot-attribute': () => `slot attribute must be a static value`, - 'invalid-slot-name': () => `slot attribute must be a static value`, + /** @param {boolean} is_default */ + 'invalid-slot-name': (is_default) => + is_default + ? `default is a reserved word — it cannot be used as a slot name` + : `slot attribute must be a static value`, 'invalid-slot-placement': () => `Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`, - 'duplicate-slot-name': /** @param {string} name @param {string} component */ (name, component) => - `Duplicate slot name '${name}' in <${component}>`, + /** @param {string} name @param {string} component */ + 'duplicate-slot-name': (name, component) => `Duplicate slot name '${name}' in <${component}>`, 'invalid-default-slot-content': () => - `Found default slot content alongside an explicit slot="default"` + `Found default slot content alongside an explicit slot="default"`, + /** @param {string} name */ + 'duplicate-slot-declaration': (name) => `duplicate ${name} element` }; /** @satisfies {Errors} */ @@ -358,10 +364,6 @@ const errors = { // code: 'dynamic-slot-name', // message: ' name cannot be dynamic' // }, - // invalid_slot_name: { - // code: 'invalid-slot-name', - // message: 'default is a reserved word — it cannot be used as a slot name' - // }, // invalid_slot_attribute_value_missing: { // code: 'invalid-slot-attribute', // message: 'slot attribute value is missing' diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 71295b7059..5fd57fbf35 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -364,6 +364,7 @@ export function analyze_component(root, options) { ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module', parent_element: null, has_props_rune: false, + slots: new Set(), component_slots: new Set(), expression: null, private_derived_state: [], @@ -401,6 +402,7 @@ export function analyze_component(root, options) { instance_scope: instance.scope, reactive_statement: null, reactive_statements: analysis.reactive_statements, + slots: new Set(), component_slots: new Set(), expression: null, private_derived_state: [], diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index d2c503e8b1..03c0e9640c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -16,6 +16,7 @@ export interface AnalysisState { ast_type: 'instance' | 'template' | 'module'; parent_element: string | null; has_props_rune: boolean; + slots: Set; /** Which slots the current parent component has */ component_slots: Set; /** The current {expression}, if any */ diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 7a254c8aa2..c48e43c013 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -485,18 +485,35 @@ export const validation = { } } }, - SlotElement(node) { + SlotElement(node, context) { + let is_named_slot = false; + for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { if (attribute.name === 'name') { if (!is_text_attribute(attribute)) { - error(attribute, 'invalid-slot-name'); + error(attribute, 'invalid-slot-name', false); + } + const slot_name = attribute.value[0].data; + if (slot_name === 'default') { + error(attribute, 'invalid-slot-name', true); + } else if (context.state.slots.has(slot_name)) { + error(attribute, 'duplicate-slot-declaration', `'${slot_name}'`); } + context.state.slots.add(slot_name); + is_named_slot = true; } } else if (attribute.type !== 'SpreadAttribute') { error(attribute, 'invalid-slot-element-attribute'); } } + + if (!is_named_slot) { + if (context.state.slots.has('default')) { + error(node, 'duplicate-slot-declaration', 'default'); + } + context.state.slots.add('default'); + } }, Component: validate_component, SvelteComponent: validate_component, diff --git a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json index 8c093fd705..8304131e7d 100644 --- a/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-default-duplicate/errors.json @@ -1,9 +1,14 @@ [ { + "code": "duplicate-slot-declaration", "message": "duplicate default element", "start": { "line": 2, "column": 0 + }, + "end": { + "line": 2, + "column": 13 } } ] diff --git a/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js b/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slot-default-reserved/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json index cb15babd60..9194e8aa9b 100644 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-dynamic-attribute/errors.json @@ -1,7 +1,7 @@ [ { "code": "invalid-slot-attribute", - "message": "slot attribute cannot have a dynamic value", + "message": "slot attribute must be a static value", "start": { "line": 6, "column": 9 diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js b/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json b/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json index 65b400437f..963cb1f40d 100644 --- a/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-dynamic/errors.json @@ -1,7 +1,7 @@ [ { - "code": "dynamic-slot-name", - "message": " name cannot be dynamic", + "code": "invalid-slot-name", + "message": "slot attribute must be a static value", "start": { "line": 1, "column": 6 diff --git a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json index e8152c3602..f518f75090 100644 --- a/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json +++ b/packages/svelte/tests/validator/samples/component-slot-named-duplicate/errors.json @@ -1,9 +1,14 @@ [ { + "code": "duplicate-slot-declaration", "message": "duplicate 'foo' element", "start": { "line": 2, "column": 6 + }, + "end": { + "line": 2, + "column": 16 } } ] diff --git a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json index a0be2a67ac..da10838a69 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-custom-element-2/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 10, "column": 9 }, "end": { "line": 10, "column": 19 } diff --git a/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js b/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-each-block/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json b/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json index 38f4e6f671..12d2407540 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-each-block/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 7, diff --git a/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js b/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js deleted file mode 100644 index 64fdc120d6..0000000000 --- a/packages/svelte/tests/validator/samples/component-slotted-if-block/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({ skip: true }); diff --git a/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json b/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json index 38f4e6f671..12d2407540 100644 --- a/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json +++ b/packages/svelte/tests/validator/samples/component-slotted-if-block/errors.json @@ -1,6 +1,6 @@ [ { - "code": "invalid-slotted-content", + "code": "invalid-slot-placement", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element", "start": { "line": 7,