slot validation

pull/9723/head
Simon Holthausen 3 years ago
parent 8df058ea3d
commit 03309a718e

@ -252,13 +252,19 @@ const attributes = {
const slots = { const slots = {
'invalid-slot-element-attribute': () => `<slot> can only receive attributes, not directives`, 'invalid-slot-element-attribute': () => `<slot> can only receive attributes, not directives`,
'invalid-slot-attribute': () => `slot attribute must be a static value`, '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': () => 'invalid-slot-placement': () =>
`Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`, `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) => /** @param {string} name @param {string} component */
`Duplicate slot name '${name}' in <${component}>`, 'duplicate-slot-name': (name, component) => `Duplicate slot name '${name}' in <${component}>`,
'invalid-default-slot-content': () => '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} <slot> element`
}; };
/** @satisfies {Errors} */ /** @satisfies {Errors} */
@ -358,10 +364,6 @@ const errors = {
// code: 'dynamic-slot-name', // code: 'dynamic-slot-name',
// message: '<slot> name cannot be dynamic' // message: '<slot> 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: { // invalid_slot_attribute_value_missing: {
// code: 'invalid-slot-attribute', // code: 'invalid-slot-attribute',
// message: 'slot attribute value is missing' // message: 'slot attribute value is missing'

@ -364,6 +364,7 @@ export function analyze_component(root, options) {
ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module', ast_type: ast === instance.ast ? 'instance' : ast === template.ast ? 'template' : 'module',
parent_element: null, parent_element: null,
has_props_rune: false, has_props_rune: false,
slots: new Set(),
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
private_derived_state: [], private_derived_state: [],
@ -401,6 +402,7 @@ export function analyze_component(root, options) {
instance_scope: instance.scope, instance_scope: instance.scope,
reactive_statement: null, reactive_statement: null,
reactive_statements: analysis.reactive_statements, reactive_statements: analysis.reactive_statements,
slots: new Set(),
component_slots: new Set(), component_slots: new Set(),
expression: null, expression: null,
private_derived_state: [], private_derived_state: [],

@ -16,6 +16,7 @@ export interface AnalysisState {
ast_type: 'instance' | 'template' | 'module'; ast_type: 'instance' | 'template' | 'module';
parent_element: string | null; parent_element: string | null;
has_props_rune: boolean; has_props_rune: boolean;
slots: Set<string>;
/** Which slots the current parent component has */ /** Which slots the current parent component has */
component_slots: Set<string>; component_slots: Set<string>;
/** The current {expression}, if any */ /** The current {expression}, if any */

@ -485,18 +485,35 @@ export const validation = {
} }
} }
}, },
SlotElement(node) { SlotElement(node, context) {
let is_named_slot = false;
for (const attribute of node.attributes) { for (const attribute of node.attributes) {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
if (attribute.name === 'name') { if (attribute.name === 'name') {
if (!is_text_attribute(attribute)) { 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') { } else if (attribute.type !== 'SpreadAttribute') {
error(attribute, 'invalid-slot-element-attribute'); 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, Component: validate_component,
SvelteComponent: validate_component, SvelteComponent: validate_component,

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,9 +1,14 @@
[ [
{ {
"code": "duplicate-slot-declaration",
"message": "duplicate default <slot> element", "message": "duplicate default <slot> element",
"start": { "start": {
"line": 2, "line": 2,
"column": 0 "column": 0
},
"end": {
"line": 2,
"column": 13
} }
} }
] ]

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,7 +1,7 @@
[ [
{ {
"code": "invalid-slot-attribute", "code": "invalid-slot-attribute",
"message": "slot attribute cannot have a dynamic value", "message": "slot attribute must be a static value",
"start": { "start": {
"line": 6, "line": 6,
"column": 9 "column": 9

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,7 +1,7 @@
[ [
{ {
"code": "dynamic-slot-name", "code": "invalid-slot-name",
"message": "<slot> name cannot be dynamic", "message": "slot attribute must be a static value",
"start": { "start": {
"line": 1, "line": 1,
"column": 6 "column": 6

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,9 +1,14 @@
[ [
{ {
"code": "duplicate-slot-declaration",
"message": "duplicate 'foo' <slot> element", "message": "duplicate 'foo' <slot> element",
"start": { "start": {
"line": 2, "line": 2,
"column": 6 "column": 6
},
"end": {
"line": 2,
"column": 16
} }
} }
] ]

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -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", "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 }, "start": { "line": 10, "column": 9 },
"end": { "line": 10, "column": 19 } "end": { "line": 10, "column": 19 }

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -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", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element",
"start": { "start": {
"line": 7, "line": 7,

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -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", "message": "Element with a slot='...' attribute must be a child of a component or a descendant of a custom element",
"start": { "start": {
"line": 7, "line": 7,

Loading…
Cancel
Save