move most parse errors into central location

pull/5328/head
pngwn 5 years ago
parent ae3a00259a
commit 3faeb9c953

@ -16,7 +16,24 @@ interface ErroMessage {
*/
type error_generator = (...args: string[]) => ErroMessage;
export const end_of_file_error: error_generator = () => ({
code: `unexpected-eof`,
message: `Unexpected end of input`
});
export const template_errors: Record<string, error_generator> = {
attribute_duplicate: () => ({
code: `duplicate-attribute`,
message: 'Attributes need to be unique'
}),
attribute_quote_outside_value: () => ({
code: `unexpected-token`,
message: `Expected =`
}),
directive_value_invalid: () => ({
code: `invalid-directive-value`,
message: `Directive value must be a JavaScript expression enclosed in curly braces`
}),
duplicate_style: () => ({
code: 'duplicate-style',
message: 'You can only have one top-level <style> tag per component'
@ -50,19 +67,93 @@ export const template_errors: Record<string, error_generator> = {
message: `Expected to close ${block} before seeing {:else if ...} block`
}),
else_without_if_each: () => ({
code: `invalid-elseif-placement`,
code: `invalid-else-placement`,
message: `Cannot have an {:else} block outside an {#if ...} or {#each ...} block`
}),
else_before_block_close: (block) => ({
code: `invalid-elseif-placement`,
code: `invalid-else-placement`,
message: `Expected to close ${block} before seeing {:else} block`
}),
then_before_close_block: (block) => ({
code: `invalid-then-placement`,
message: `Expected to close ${block} before seeing {:then} block`
}),
then_without_await: () => ({
code: `invalid-then-placement`,
message: `Cannot have an {:then} block outside an {#await ...} block`
}),
catch_before_close_block: (block) => ({
code: `invalid-catch-placement`,
message: `Expected to close ${block} before seeing {:catch} block`
}),
catch_without_await: () => ({
code: `invalid-catch-placement`,
message: `Cannot have an {:catch} block outside an {#await ...} block`
}),
component_definition_invalid: () => ({
code: `invalid-component-definition`,
message: `invalid component definition`
}),
component_definition_missing: () => ({
code: `missing-component-definition`,
message: `<svelte:component> must have a 'this' attribute`
}),
expected_block_type: () => ({
code: `expected-block-type`,
message: `Expected if, each or await`
}),
expected_name: () => ({
code: `expected-name`,
message: `Expected name`
}),
unexpected_block_close: () => ({
code: `unexpected-block-close`,
message: `Unexpected block closing tag`
}),
debug_args: () => ({
code: 'invalid-debug-args',
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
}),
element_unopened: (name) => ({
code: `invalid-closing-tag`,
message: `</${name}> attempted to close an element that was not open`
}),
element_autoclosed: (name, reason) => ({
code: `invalid-closing-tag`,
message: `</${name}> attempted to close <${name}> that was already automatically closed by <${reason}>`
}),
element_tag_name_invalid: () => ({
code: `invalid-tag-name`,
message: `Expected valid tag name`
}),
meta_no_children: (slug, name) => ({
code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`
}),
meta_duplicate: (slug, name) => ({
code: `duplicate-${slug}`,
message: `A component can only have one <${name}> tag`
}),
meta_top_level: (slug, name) => ({
code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks`
}),
self_placement_invalid: () => ({
code: `invalid-self-placement`,
message: `<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components`
}),
meta_tag_name_invalid: (tags, match) => ({
code: 'invalid-tag-name',
message: `Valid <svelte:...> tag names are ${tags}${match ? '(did you mean ' + match + '?)' : ''}`
}),
ref_directive_invalid: (name) => ({
code: `invalid-ref-directive`,
message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead`
}),
void_no_children: (name) => ({
code: `invalid-void-content`,
message: `<${name}> is a void element and cannot have children, or a closing tag`
}),
unclosed_script: () => ({
code: `unclosed-script`,
message: `<script> must have a closing tag`

@ -5,7 +5,7 @@ import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at';
import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces';
import error from '../utils/error';
import errors from './errors';
import { template_errors } from './errors';
type ParserState = (parser: Parser) => (ParserState | void);
@ -218,18 +218,18 @@ export default function parse(
// TODO we may want to allow multiple <style> tags —
// one scoped, one global. for now, only allow one
if (parser.css.length > 1) {
parser.error(errors.duplicate_style(), parser.css[1].start);
parser.error(template_errors.duplicate_style(), parser.css[1].start);
}
const instance_scripts = parser.js.filter(script => script.context === 'default');
const module_scripts = parser.js.filter(script => script.context === 'module');
if (instance_scripts.length > 1) {
parser.error(errors.duplicate_instance_script(), instance_scripts[1].start);
parser.error(template_errors.duplicate_instance_script(), instance_scripts[1].start);
}
if (module_scripts.length > 1) {
parser.error(errors.duplicate_module_script(), module_scripts[1].start);
parser.error(template_errors.duplicate_module_script(), module_scripts[1].start);
}
return {

@ -2,20 +2,20 @@ import * as acorn from '../acorn';
import { Parser } from '../index';
import { Script } from '../../interfaces';
import { Node, Program } from 'estree';
import errors from '../errors';
import { template_errors } from '../errors';
function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find(attribute => attribute.name === 'context');
if (!context) return 'default';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
parser.error(errors.dynamic_context_attribute(), start);
parser.error(template_errors.dynamic_context_attribute(), start);
}
const value = context.value[0].data;
if (value !== 'module') {
parser.error(errors.fixed_context_attribute(), context.start);
parser.error(template_errors.fixed_context_attribute(), context.start);
}
return value;
@ -24,9 +24,9 @@ function get_context(parser: Parser, attributes: any[], start: number): string {
export default function read_script(parser: Parser, start: number, attributes: Node[]): Script {
const script_start = parser.index;
const [data, matched] = parser.read_until(/<\/script\s*>/, errors.unclosed_script());
const [data, matched] = parser.read_until(/<\/script\s*>/, template_errors.unclosed_script());
if (!matched) parser.error(errors.unclosed_script());
if (!matched) parser.error(template_errors.unclosed_script());
const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + data;

@ -143,7 +143,7 @@ export default function mustache(parser: Parser) {
parser.error(
parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? template_errors.else_before_block_close(to_string(block))
: template_errors.else_without_if()
: template_errors.else_without_if_each()
);
}
@ -165,21 +165,18 @@ export default function mustache(parser: Parser) {
if (is_then) {
if (block.type !== 'PendingBlock') {
parser.error({
code: `invalid-then-placement`,
message: parser.stack.some(block => block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:then} block`
: `Cannot have an {:then} block outside an {#await ...} block`
});
parser.error(
parser.stack.some(block => block.type === 'PendingBlock')
? template_errors.then_before_close_block(to_string(block))
: template_errors.then_without_await()
);
}
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({
code: `invalid-catch-placement`,
message: parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? `Expected to close ${to_string(block)} before seeing {:catch} block`
: `Cannot have an {:catch} block outside an {#await ...} block`
});
parser.error(parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? template_errors.catch_before_close_block(to_string(block))
: template_errors.catch_without_await()
);
}
}
@ -215,10 +212,7 @@ export default function mustache(parser: Parser) {
} else if (parser.eat('await')) {
type = 'AwaitBlock';
} else {
parser.error({
code: `expected-block-type`,
message: `Expected if, each or await`
});
parser.error(template_errors.expected_block_type());
}
parser.require_whitespace();
@ -277,10 +271,7 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) {
parser.allow_whitespace();
block.index = parser.read_identifier();
if (!block.index) parser.error({
code: `expected-name`,
message: `Expected name`
});
if (!block.index) parser.error(template_errors.expected_name());
parser.allow_whitespace();
}
@ -360,10 +351,7 @@ export default function mustache(parser: Parser) {
identifiers.forEach(node => {
if (node.type !== 'Identifier') {
parser.error({
code: 'invalid-debug-args',
message: '{@debug ...} arguments must be identifiers, not arbitrary expressions'
}, node.start);
parser.error(template_errors.debug_args(), node.start);
}
});

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

Loading…
Cancel
Save