slot validation

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

@ -252,13 +252,19 @@ const attributes = {
const slots = {
'invalid-slot-element-attribute': () => `<slot> 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} <slot> element`
};
/** @satisfies {Errors} */
@ -358,10 +364,6 @@ const errors = {
// code: 'dynamic-slot-name',
// 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: {
// code: 'invalid-slot-attribute',
// 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',
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: [],

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

@ -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",
"start": {
"line": 2,
"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",
"message": "slot attribute cannot have a dynamic value",
"message": "slot attribute must be a static value",
"start": {
"line": 6,
"column": 9

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

@ -1,7 +1,7 @@
[
{
"code": "dynamic-slot-name",
"message": "<slot> name cannot be dynamic",
"code": "invalid-slot-name",
"message": "slot attribute must be a static value",
"start": {
"line": 1,
"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",
"start": {
"line": 2,
"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",
"start": { "line": 10, "column": 9 },
"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",
"start": {
"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",
"start": {
"line": 7,

Loading…
Cancel
Save