|
|
@ -7,6 +7,7 @@ import { Parser } from '../index';
|
|
|
|
import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
|
|
|
|
import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
|
|
|
|
import fuzzymatch from '../../utils/fuzzymatch';
|
|
|
|
import fuzzymatch from '../../utils/fuzzymatch';
|
|
|
|
import list from '../../utils/list';
|
|
|
|
import list from '../../utils/list';
|
|
|
|
|
|
|
|
import { template_errors, end_of_file_error } from '../errors';
|
|
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-useless-escape
|
|
|
|
// eslint-disable-next-line no-useless-escape
|
|
|
|
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
|
|
|
|
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
|
|
|
@ -80,24 +81,18 @@ export default function tag(parser: Parser) {
|
|
|
|
(name === 'svelte:window' || name === 'svelte:body') &&
|
|
|
|
(name === 'svelte:window' || name === 'svelte:body') &&
|
|
|
|
parser.current().children.length
|
|
|
|
parser.current().children.length
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(
|
|
|
|
code: `invalid-${slug}-content`,
|
|
|
|
template_errors.meta_no_children(slug, name),
|
|
|
|
message: `<${name}> cannot have children`
|
|
|
|
parser.current().children[0].start
|
|
|
|
}, parser.current().children[0].start);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if (name in parser.meta_tags) {
|
|
|
|
if (name in parser.meta_tags) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.meta_duplicate(slug, name), start);
|
|
|
|
code: `duplicate-${slug}`,
|
|
|
|
|
|
|
|
message: `A component can only have one <${name}> tag`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (parser.stack.length > 1) {
|
|
|
|
if (parser.stack.length > 1) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.meta_top_level(slug, name), start);
|
|
|
|
code: `invalid-${slug}-placement`,
|
|
|
|
|
|
|
|
message: `<${name}> tags cannot be inside elements or blocks`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parser.meta_tags[name] = true;
|
|
|
|
parser.meta_tags[name] = true;
|
|
|
@ -123,10 +118,7 @@ export default function tag(parser: Parser) {
|
|
|
|
|
|
|
|
|
|
|
|
if (is_closing_tag) {
|
|
|
|
if (is_closing_tag) {
|
|
|
|
if (is_void(name)) {
|
|
|
|
if (is_void(name)) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.void_no_children(name), start);
|
|
|
|
code: `invalid-void-content`,
|
|
|
|
|
|
|
|
message: `<${name}> is a void element and cannot have children, or a closing tag`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parser.eat('>', true);
|
|
|
|
parser.eat('>', true);
|
|
|
@ -134,13 +126,10 @@ export default function tag(parser: Parser) {
|
|
|
|
// close any elements that don't have their own closing tags, e.g. <div><p></div>
|
|
|
|
// close any elements that don't have their own closing tags, e.g. <div><p></div>
|
|
|
|
while (parent.name !== name) {
|
|
|
|
while (parent.name !== name) {
|
|
|
|
if (parent.type !== 'Element') {
|
|
|
|
if (parent.type !== 'Element') {
|
|
|
|
const message = parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name
|
|
|
|
const error = parser.last_auto_closed_tag && parser.last_auto_closed_tag.tag === name
|
|
|
|
? `</${name}> attempted to close <${name}> that was already automatically closed by <${parser.last_auto_closed_tag.reason}>`
|
|
|
|
? template_errors.element_autoclosed(name, parser.last_auto_closed_tag.reason)
|
|
|
|
: `</${name}> attempted to close an element that was not open`;
|
|
|
|
: template_errors.element_unopened(name);
|
|
|
|
parser.error({
|
|
|
|
parser.error(error, start);
|
|
|
|
code: `invalid-closing-tag`,
|
|
|
|
|
|
|
|
message
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parent.end = start;
|
|
|
|
parent.end = start;
|
|
|
@ -178,18 +167,12 @@ export default function tag(parser: Parser) {
|
|
|
|
if (name === 'svelte:component') {
|
|
|
|
if (name === 'svelte:component') {
|
|
|
|
const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this');
|
|
|
|
const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this');
|
|
|
|
if (!~index) {
|
|
|
|
if (!~index) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.component_definition_missing(), start);
|
|
|
|
code: `missing-component-definition`,
|
|
|
|
|
|
|
|
message: `<svelte:component> must have a 'this' attribute`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const definition = element.attributes.splice(index, 1)[0];
|
|
|
|
const definition = element.attributes.splice(index, 1)[0];
|
|
|
|
if (definition.value === true || definition.value.length !== 1 || definition.value[0].type === 'Text') {
|
|
|
|
if (definition.value === true || definition.value.length !== 1 || definition.value[0].type === 'Text') {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.component_definition_invalid(), definition.start);
|
|
|
|
code: `invalid-component-definition`,
|
|
|
|
|
|
|
|
message: `invalid component definition`
|
|
|
|
|
|
|
|
}, definition.start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
element.expression = definition.value[0].expression;
|
|
|
|
element.expression = definition.value[0].expression;
|
|
|
@ -261,10 +244,7 @@ function read_tag_name(parser: Parser) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!legal) {
|
|
|
|
if (!legal) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.self_placement_invalid(), start);
|
|
|
|
code: `invalid-self-placement`,
|
|
|
|
|
|
|
|
message: `<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return 'svelte:self';
|
|
|
|
return 'svelte:self';
|
|
|
@ -279,20 +259,14 @@ function read_tag_name(parser: Parser) {
|
|
|
|
if (name.startsWith('svelte:')) {
|
|
|
|
if (name.startsWith('svelte:')) {
|
|
|
|
const match = fuzzymatch(name.slice(7), valid_meta_tags);
|
|
|
|
const match = fuzzymatch(name.slice(7), valid_meta_tags);
|
|
|
|
|
|
|
|
|
|
|
|
let message = `Valid <svelte:...> tag names are ${list(valid_meta_tags)}`;
|
|
|
|
parser.error(
|
|
|
|
if (match) message += ` (did you mean '${match}'?)`;
|
|
|
|
template_errors.meta_tag_name_invalid(list(valid_meta_tags), match),
|
|
|
|
|
|
|
|
start
|
|
|
|
parser.error({
|
|
|
|
);
|
|
|
|
code: 'invalid-tag-name',
|
|
|
|
|
|
|
|
message
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!valid_tag_name.test(name)) {
|
|
|
|
if (!valid_tag_name.test(name)) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.element_tag_name_invalid(), start);
|
|
|
|
code: `invalid-tag-name`,
|
|
|
|
|
|
|
|
message: `Expected valid tag name`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return name;
|
|
|
|
return name;
|
|
|
@ -303,10 +277,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
|
|
|
|
|
|
|
|
|
|
|
|
function check_unique(name: string) {
|
|
|
|
function check_unique(name: string) {
|
|
|
|
if (unique_names.has(name)) {
|
|
|
|
if (unique_names.has(name)) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.attribute_duplicate(), start);
|
|
|
|
code: `duplicate-attribute`,
|
|
|
|
|
|
|
|
message: 'Attributes need to be unique'
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unique_names.add(name);
|
|
|
|
unique_names.add(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -372,10 +343,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
|
|
|
|
value = read_attribute_value(parser);
|
|
|
|
value = read_attribute_value(parser);
|
|
|
|
end = parser.index;
|
|
|
|
end = parser.index;
|
|
|
|
} else if (parser.match_regex(/["']/)) {
|
|
|
|
} else if (parser.match_regex(/["']/)) {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.attribute_quote_outside_value(), parser.index);
|
|
|
|
code: `unexpected-token`,
|
|
|
|
|
|
|
|
message: `Expected =`
|
|
|
|
|
|
|
|
}, parser.index);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (type) {
|
|
|
|
if (type) {
|
|
|
@ -388,18 +356,12 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (type === 'Ref') {
|
|
|
|
if (type === 'Ref') {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.ref_directive_invalid(directive_name), start);
|
|
|
|
code: `invalid-ref-directive`,
|
|
|
|
|
|
|
|
message: `The ref directive is no longer supported — use \`bind:this={${directive_name}}\` instead`
|
|
|
|
|
|
|
|
}, start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (value[0]) {
|
|
|
|
if (value[0]) {
|
|
|
|
if ((value as any[]).length > 1 || value[0].type === 'Text') {
|
|
|
|
if ((value as any[]).length > 1 || value[0].type === 'Text') {
|
|
|
|
parser.error({
|
|
|
|
parser.error(template_errors.directive_value_invalid(), value[0].start);
|
|
|
|
code: `invalid-directive-value`,
|
|
|
|
|
|
|
|
message: `Directive value must be a JavaScript expression enclosed in curly braces`
|
|
|
|
|
|
|
|
}, value[0].start);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -519,8 +481,5 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
parser.error({
|
|
|
|
parser.error(end_of_file_error());
|
|
|
|
code: `unexpected-eof`,
|
|
|
|
|
|
|
|
message: `Unexpected end of input`
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|