add codes to errors

pull/1340/head
Rich Harris 8 years ago
parent b86a1edb52
commit 35f4a1f063

@ -71,11 +71,19 @@ export class Parser {
const current = this.current(); const current = this.current();
const type = current.type === 'Element' ? `<${current.name}>` : 'Block'; const type = current.type === 'Element' ? `<${current.name}>` : 'Block';
this.error(`${type} was left open`, current.start); const slug = current.type === 'Element' ? 'element' : 'block';
this.error({
code: `unclosed-${slug}`,
message: `${type} was left open`
}, current.start);
} }
if (state !== fragment) { if (state !== fragment) {
this.error('Unexpected end of input'); this.error({
code: `unexpected-eof`,
message: 'Unexpected end of input'
});
} }
if (this.html.children.length) { if (this.html.children.length) {
@ -97,12 +105,16 @@ export class Parser {
} }
acornError(err: any) { acornError(err: any) {
this.error(err.message.replace(/ \(\d+:\d+\)$/, ''), err.pos); this.error({
code: `parse-error`,
message: err.message.replace(/ \(\d+:\d+\)$/, '')
}, err.pos);
} }
error(message: string, index = this.index) { error({ code, message }: { code: string, message: string }, index = this.index) {
error(message, { error(message, {
name: 'ParseError', name: 'ParseError',
code,
source: this.template, source: this.template,
start: index, start: index,
filename: this.filename filename: this.filename
@ -116,7 +128,10 @@ export class Parser {
} }
if (required) { if (required) {
this.error(message || `Expected ${str}`); this.error({
code: `unexpected-${this.index === this.template.length ? 'eof' : 'token'}`,
message: message || `Expected ${str}`
});
} }
return false; return false;
@ -164,7 +179,10 @@ export class Parser {
const identifier = this.template.slice(this.index, this.index = i); const identifier = this.template.slice(this.index, this.index = i);
if (reservedNames.has(identifier)) { if (reservedNames.has(identifier)) {
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start); this.error({
code: `unexpected-reserved-word`,
message: `'${identifier}' is a reserved word in JavaScript and cannot be used here`
}, start);
} }
return identifier; return identifier;
@ -172,7 +190,10 @@ export class Parser {
readUntil(pattern: RegExp) { readUntil(pattern: RegExp) {
if (this.index >= this.template.length) if (this.index >= this.template.length)
this.error('Unexpected end of input'); this.error({
code: `unexpected-eof`,
message: 'Unexpected end of input'
});
const start = this.index; const start = this.index;
const match = pattern.exec(this.template.slice(start)); const match = pattern.exec(this.template.slice(start));
@ -192,7 +213,10 @@ export class Parser {
requireWhitespace() { requireWhitespace() {
if (!whitespace.test(this.template[this.index])) { if (!whitespace.test(this.template[this.index])) {
this.error(`Expected whitespace`); this.error({
code: `missing-whitespace`,
message: `Expected whitespace`
});
} }
this.allowWhitespace(); this.allowWhitespace();

@ -2,7 +2,19 @@ import { parseExpressionAt } from 'acorn';
import repeat from '../../utils/repeat'; import repeat from '../../utils/repeat';
import { Parser } from '../index'; import { Parser } from '../index';
const DIRECTIVES = { const DIRECTIVES: Record<string, {
names: string[];
attribute: (
start: number,
end: number,
type: string,
name: string,
expression?: any,
directiveName?: string
) => { start: number, end: number, type: string, name: string, value?: any, expression?: any };
allowedExpressionTypes: string[];
error: string;
}> = {
Ref: { Ref: {
names: ['ref'], names: ['ref'],
attribute(start, end, type, name) { attribute(start, end, type, name) {
@ -143,7 +155,10 @@ export function readDirective(
try { try {
expression = readExpression(parser, expressionStart, quoteMark); expression = readExpression(parser, expressionStart, quoteMark);
if (directive.allowedExpressionTypes.indexOf(expression.type) === -1) { if (directive.allowedExpressionTypes.indexOf(expression.type) === -1) {
parser.error(directive.error, expressionStart); parser.error({
code: `invalid-directive-value`,
message: directive.error
}, expressionStart);
} }
} catch (err) { } catch (err) {
if (parser.template[expressionStart] === '{') { if (parser.template[expressionStart] === '{') {
@ -155,7 +170,10 @@ export function readDirective(
const value = parser.template.slice(expressionStart + (parser.v2 ? 1 : 2), expressionEnd); const value = parser.template.slice(expressionStart + (parser.v2 ? 1 : 2), expressionEnd);
message += ` — use '${value}', not '${parser.v2 ? `{${value}}` : `{{${value}}}`}'`; message += ` — use '${value}', not '${parser.v2 ? `{${value}}` : `{{${value}}}`}'`;
} }
parser.error(message, expressionStart); parser.error({
code: `invalid-directive-value`,
message
}, expressionStart);
} }
throw err; throw err;

@ -12,7 +12,10 @@ export default function readScript(parser: Parser, start: number, attributes: No
const scriptStart = parser.index; const scriptStart = parser.index;
const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart); const scriptEnd = parser.template.indexOf(scriptClosingTag, scriptStart);
if (scriptEnd === -1) parser.error(`<script> must have a closing tag`); if (scriptEnd === -1) parser.error({
code: `unclosed-script`,
message: `<script> must have a closing tag`
});
const source = const source =
repeat(' ', scriptStart) + parser.template.slice(scriptStart, scriptEnd); repeat(' ', scriptStart) + parser.template.slice(scriptStart, scriptEnd);

@ -17,7 +17,10 @@ export default function readStyle(parser: Parser, start: number, attributes: Nod
}); });
} catch (err) { } catch (err) {
if (err.name === 'CssSyntaxError') { if (err.name === 'CssSyntaxError') {
parser.error(err.message, err.offset); parser.error({
code: `css-syntax-error`,
message: err.message
}, err.offset);
} else { } else {
throw err; throw err;
} }

@ -56,7 +56,10 @@ export default function mustache(parser: Parser) {
} else if (block.type === 'AwaitBlock') { } else if (block.type === 'AwaitBlock') {
expected = 'await'; expected = 'await';
} else { } else {
parser.error(`Unexpected block closing tag`); parser.error({
code: `unexpected-block-close`,
message: `Unexpected block closing tag`
});
} }
parser.eat(expected, true); parser.eat(expected, true);
@ -86,9 +89,10 @@ export default function mustache(parser: Parser) {
} else if (parser.eat(parser.v2 ? ':elseif' : 'elseif')) { } else if (parser.eat(parser.v2 ? ':elseif' : 'elseif')) {
const block = parser.current(); const block = parser.current();
if (block.type !== 'IfBlock') if (block.type !== 'IfBlock')
parser.error( parser.error({
'Cannot have an {{elseif ...}} block outside an {{#if ...}} block' code: `invalid-elseif-placement`,
); message: 'Cannot have an {{elseif ...}} block outside an {{#if ...}} block'
});
parser.requireWhitespace(); parser.requireWhitespace();
@ -117,9 +121,10 @@ export default function mustache(parser: Parser) {
} else if (parser.eat(parser.v2 ? ':else' : 'else')) { } else if (parser.eat(parser.v2 ? ':else' : 'else')) {
const block = parser.current(); const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error( parser.error({
'Cannot have an {{else}} block outside an {{#if ...}} or {{#each ...}} block' code: `invalid-else-placement`,
); message: 'Cannot have an {{else}} block outside an {{#if ...}} or {{#each ...}} block'
});
} }
parser.allowWhitespace(); parser.allowWhitespace();
@ -191,7 +196,10 @@ export default function mustache(parser: Parser) {
} else if (parser.eat('await')) { } else if (parser.eat('await')) {
type = 'AwaitBlock'; type = 'AwaitBlock';
} else { } else {
parser.error(`Expected if, each or await`); parser.error({
code: `expected-block-type`,
message: `Expected if, each or await`
});
} }
parser.requireWhitespace(); parser.requireWhitespace();
@ -249,20 +257,30 @@ export default function mustache(parser: Parser) {
parser.allowWhitespace(); parser.allowWhitespace();
const destructuredContext = parser.readIdentifier(); const destructuredContext = parser.readIdentifier();
if (!destructuredContext) parser.error(`Expected name`); if (!destructuredContext) parser.error({
code: `expected-name`,
message: `Expected name`
});
block.destructuredContexts.push(destructuredContext); block.destructuredContexts.push(destructuredContext);
parser.allowWhitespace(); parser.allowWhitespace();
} while (parser.eat(',')); } while (parser.eat(','));
if (!block.destructuredContexts.length) parser.error(`Expected name`); if (!block.destructuredContexts.length) parser.error({
code: `expected-name`,
message: `Expected name`
});
block.context = block.destructuredContexts.join('_'); block.context = block.destructuredContexts.join('_');
parser.allowWhitespace(); parser.allowWhitespace();
parser.eat(']', true); parser.eat(']', true);
} else { } else {
block.context = parser.readIdentifier(); block.context = parser.readIdentifier();
if (!block.context) parser.error(`Expected name`); if (!block.context) parser.error({
code: `expected-name`,
message: `Expected name`
});
} }
parser.allowWhitespace(); parser.allowWhitespace();
@ -270,7 +288,11 @@ export default function mustache(parser: Parser) {
if (parser.eat(',')) { if (parser.eat(',')) {
parser.allowWhitespace(); parser.allowWhitespace();
block.index = parser.readIdentifier(); block.index = parser.readIdentifier();
if (!block.index) parser.error(`Expected name`); if (!block.index) parser.error({
code: `expected-name`,
message: `Expected name`
});
parser.allowWhitespace(); parser.allowWhitespace();
} }
@ -287,7 +309,10 @@ export default function mustache(parser: Parser) {
expression.property.computed || expression.property.computed ||
expression.property.type !== 'Identifier' expression.property.type !== 'Identifier'
) { ) {
parser.error('invalid key', expression.start); parser.error({
code: `invalid-key`,
message: 'invalid key'
}, expression.start);
} }
block.key = expression.property.name; block.key = expression.property.name;
@ -296,7 +321,10 @@ export default function mustache(parser: Parser) {
parser.allowWhitespace(); parser.allowWhitespace();
} else if (parser.eat('@')) { } else if (parser.eat('@')) {
block.key = parser.readIdentifier(); block.key = parser.readIdentifier();
if (!block.key) parser.error(`Expected name`); if (!block.key) parser.error({
code: `expected-name`,
message: `Expected name`
});
parser.allowWhitespace(); parser.allowWhitespace();
} }
} }

@ -83,21 +83,27 @@ export default function tag(parser: Parser) {
const name = readTagName(parser); const name = readTagName(parser);
if (metaTags.has(name)) { if (metaTags.has(name)) {
const slug = metaTags.get(name).toLowerCase();
if (isClosingTag) { if (isClosingTag) {
if ((name === ':Window' || name === 'svelte:window') && parser.current().children.length) { if ((name === ':Window' || name === 'svelte:window') && parser.current().children.length) {
parser.error( parser.error({
`<${name}> cannot have children`, code: `invalid-window-content`,
parser.current().children[0].start message: `<${name}> cannot have children`
); }, parser.current().children[0].start);
} }
} else { } else {
if (name in parser.metaTags) { if (name in parser.metaTags) {
parser.error(`A component can only have one <${name}> tag`, start); parser.error({
code: `duplicate-${slug}`,
message: `A component can only have one <${name}> tag`
}, start);
} }
if (parser.stack.length > 1) { if (parser.stack.length > 1) {
console.log(parser.stack); parser.error({
parser.error(`<${name}> tags cannot be inside elements or blocks`, start); code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks`
}, start);
} }
parser.metaTags[name] = true; parser.metaTags[name] = true;
@ -121,21 +127,21 @@ export default function tag(parser: Parser) {
if (isClosingTag) { if (isClosingTag) {
if (isVoidElementName(name)) { if (isVoidElementName(name)) {
parser.error( parser.error({
`<${name}> is a void element and cannot have children, or a closing tag`, code: `invalid-void-content`,
start message: `<${name}> is a void element and cannot have children, or a closing tag`
); }, start);
} }
if (!parser.eat('>')) parser.error(`Expected '>'`); parser.eat('>', true);
// 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')
parser.error( parser.error({
`</${name}> attempted to close an element that was not open`, code: `invalid-closing-tag`,
start message: `</${name}> attempted to close an element that was not open`
); }, start);
parent.end = start; parent.end = start;
parser.stack.pop(); parser.stack.pop();
@ -161,10 +167,10 @@ export default function tag(parser: Parser) {
while (i--) { while (i--) {
const item = parser.stack[i]; const item = parser.stack[i];
if (item.type === 'EachBlock') { if (item.type === 'EachBlock') {
parser.error( parser.error({
`<slot> cannot be a child of an each-block`, code: `invalid-slot-placement`,
start message: `<slot> cannot be a child of an each-block`
); }, start);
} }
} }
} }
@ -182,7 +188,10 @@ export default function tag(parser: Parser) {
let attribute; let attribute;
while ((attribute = readAttribute(parser, uniqueNames))) { while ((attribute = readAttribute(parser, uniqueNames))) {
if (attribute.type === 'Binding' && !parser.allowBindings) { if (attribute.type === 'Binding' && !parser.allowBindings) {
parser.error(`Two-way binding is disabled`, attribute.start); parser.error({
code: `binding-disabled`,
message: `Two-way binding is disabled`
}, attribute.start);
} }
element.attributes.push(attribute); element.attributes.push(attribute);
@ -193,12 +202,18 @@ export default function tag(parser: Parser) {
// TODO post v2, treat this just as any other attribute // TODO post v2, treat this just as any other attribute
const index = element.attributes.findIndex(attr => attr.name === 'this'); const index = element.attributes.findIndex(attr => attr.name === 'this');
if (!~index) { if (!~index) {
parser.error(`<svelte:component> must have a 'this' attribute`, start); parser.error({
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(`invalid component definition`, definition.start); parser.error({
code: `invalid-component-definition`,
message: `invalid component definition`
}, definition.start);
} }
element.expression = definition.value[0].expression; element.expression = definition.value[0].expression;
@ -210,9 +225,10 @@ export default function tag(parser: Parser) {
if (parser[special.property]) { if (parser[special.property]) {
parser.index = start; parser.index = start;
parser.error( parser.error({
`You can only have one top-level <${name}> tag per component` code: `duplicate-${name}`,
); message: `You can only have one top-level <${name}> tag per component`
});
} }
parser.eat('>', true); parser.eat('>', true);
@ -282,10 +298,10 @@ function readTagName(parser: Parser) {
} }
if (!legal) { if (!legal) {
parser.error( parser.error({
`<${SELF}> components can only exist inside if-blocks or each-blocks`, code: `invalid-self-placement`,
start message: `<${SELF}> components can only exist inside if-blocks or each-blocks`
); }, start);
} }
return SELF; return SELF;
@ -298,7 +314,10 @@ function readTagName(parser: Parser) {
if (metaTags.has(name)) return name; if (metaTags.has(name)) return name;
if (!validTagName.test(name)) { if (!validTagName.test(name)) {
parser.error(`Expected valid tag name`, start); parser.error({
code: `invalid-tag-name`,
message: `Expected valid tag name`
}, start);
} }
return name; return name;
@ -324,7 +343,10 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
}; };
} else { } else {
if (!parser.v2) { if (!parser.v2) {
parser.error('Expected spread operator (...)'); parser.error({
code: `expected-spread`,
message: 'Expected spread operator (...)'
});
} }
const valueStart = parser.index; const valueStart = parser.index;
@ -356,7 +378,10 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
let name = parser.readUntil(/(\s|=|\/|>)/); let name = parser.readUntil(/(\s|=|\/|>)/);
if (!name) return null; if (!name) return null;
if (uniqueNames.has(name)) { if (uniqueNames.has(name)) {
parser.error('Attributes need to be unique', start); parser.error({
code: `duplicate-attribute`,
message: 'Attributes need to be unique'
}, start);
} }
uniqueNames.add(name); uniqueNames.add(name);
@ -444,5 +469,8 @@ function readSequence(parser: Parser, done: () => boolean) {
} }
} }
parser.error(`Unexpected end of input`); parser.error({
code: `unexpected-eof`,
message: `Unexpected end of input`
});
} }

@ -7,7 +7,7 @@ import { Node } from '../../../interfaces';
export default function components(validator: Validator, prop: Node) { export default function components(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-components-property`,
message: `The 'components' property must be an object literal` message: `The 'components' property must be an object literal`
}); });
} }

@ -16,7 +16,7 @@ const isFunctionExpression = new Set([
export default function computed(validator: Validator, prop: Node) { export default function computed(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-computed-property`,
message: `The 'computed' property must be an object literal` message: `The 'computed' property must be an object literal`
}); });
} }

@ -8,7 +8,7 @@ export default function data(validator: Validator, prop: Node) {
if (disallowed.has(prop.value.type)) { if (disallowed.has(prop.value.type)) {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-data-property`,
message: `'data' must be a function` message: `'data' must be a function`
}); });
} }

@ -6,7 +6,7 @@ import { Node } from '../../../interfaces';
export default function events(validator: Validator, prop: Node) { export default function events(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-events-property`,
message: `The 'events' property must be an object literal` message: `The 'events' property must be an object literal`
}); });
} }

@ -9,7 +9,7 @@ import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
export default function helpers(validator: Validator, prop: Node) { export default function helpers(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-helpers-property`,
message: `The 'helpers' property must be an object literal` message: `The 'helpers' property must be an object literal`
}); });
} }

@ -4,7 +4,7 @@ import { Node } from '../../../interfaces';
export default function immutable(validator: Validator, prop: Node) { export default function immutable(validator: Validator, prop: Node) {
if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'boolean') { if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'boolean') {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-immutable-property`,
message: `'immutable' must be a boolean literal` message: `'immutable' must be a boolean literal`
}); });
} }

@ -11,7 +11,7 @@ const builtin = new Set(['set', 'get', 'on', 'fire', 'observe', 'destroy']);
export default function methods(validator: Validator, prop: Node) { export default function methods(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-methods-property`,
message: `The 'methods' property must be an object literal` message: `The 'methods' property must be an object literal`
}); });
} }

@ -11,7 +11,7 @@ export default function namespace(validator: Validator, prop: Node) {
if (typeof ns !== 'string') { if (typeof ns !== 'string') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-namespace-property`,
message: `The 'namespace' property must be a string literal representing a valid namespace` message: `The 'namespace' property must be a string literal representing a valid namespace`
}); });
} }
@ -20,12 +20,12 @@ export default function namespace(validator: Validator, prop: Node) {
const match = fuzzymatch(ns, namespaces.validNamespaces); const match = fuzzymatch(ns, namespaces.validNamespaces);
if (match) { if (match) {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-namespace-property`,
message: `Invalid namespace '${ns}' (did you mean '${match}'?)` message: `Invalid namespace '${ns}' (did you mean '${match}'?)`
}); });
} else { } else {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-namespace-property`,
message: `Invalid namespace '${ns}'` message: `Invalid namespace '${ns}'`
}); });
} }

@ -6,7 +6,7 @@ export default function oncreate(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-oncreate-property`,
message: `'oncreate' should be a function expression, not an arrow function expression` message: `'oncreate' should be a function expression, not an arrow function expression`
}); });
} }

@ -6,7 +6,7 @@ export default function ondestroy(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-ondestroy-property`,
message: `'ondestroy' should be a function expression, not an arrow function expression` message: `'ondestroy' should be a function expression, not an arrow function expression`
}); });
} }

@ -5,7 +5,7 @@ import nodeToString from '../../../utils/nodeToString';
export default function props(validator: Validator, prop: Node) { export default function props(validator: Validator, prop: Node) {
if (prop.value.type !== 'ArrayExpression') { if (prop.value.type !== 'ArrayExpression') {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-props-property`,
message: `'props' must be an array expression, if specified` message: `'props' must be an array expression, if specified`
}); });
} }
@ -13,7 +13,7 @@ export default function props(validator: Validator, prop: Node) {
prop.value.elements.forEach((element: Node) => { prop.value.elements.forEach((element: Node) => {
if (typeof nodeToString(element) !== 'string') { if (typeof nodeToString(element) !== 'string') {
validator.error(element, { validator.error(element, {
code: `invalid-property`, code: `invalid-props-property`,
message: `'props' must be an array of string literals` message: `'props' must be an array of string literals`
}); });
} }

@ -8,7 +8,7 @@ export default function setup(validator: Validator, prop: Node) {
if (disallowed.has(prop.value.type)) { if (disallowed.has(prop.value.type)) {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-setup-property`,
message: `'setup' must be a function` message: `'setup' must be a function`
}); });
} }

@ -6,14 +6,14 @@ export default function tag(validator: Validator, prop: Node) {
const tag = nodeToString(prop.value); const tag = nodeToString(prop.value);
if (typeof tag !== 'string') { if (typeof tag !== 'string') {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-tag-property`,
message: `'tag' must be a string literal` message: `'tag' must be a string literal`
}); });
} }
if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
validator.error(prop.value, { validator.error(prop.value, {
code: `invalid-property`, code: `invalid-tag-property`,
message: `tag name must be two or more words joined by the '-' character` message: `tag name must be two or more words joined by the '-' character`
}); });
} }

@ -6,7 +6,7 @@ import { Node } from '../../../interfaces';
export default function transitions(validator: Validator, prop: Node) { export default function transitions(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { validator.error(prop, {
code: `invalid-property`, code: `invalid-transitions-property`,
message: `The 'transitions' property must be an object literal` message: `The 'transitions' property must be an object literal`
}); });
} }

@ -32,6 +32,7 @@ describe('parse', () => {
if (!expectedError) throw err; if (!expectedError) throw err;
try { try {
assert.equal(err.code, expectedError.code);
assert.equal(err.message, expectedError.message); assert.equal(err.message, expectedError.message);
assert.deepEqual(err.loc, expectedError.loc); assert.deepEqual(err.loc, expectedError.loc);
assert.equal(err.pos, expectedError.pos); assert.equal(err.pos, expectedError.pos);

@ -1,4 +1,5 @@
{ {
"code": "duplicate-attribute",
"message": "Attributes need to be unique", "message": "Attributes need to be unique",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "duplicate-attribute",
"message": "Attributes need to be unique", "message": "Attributes need to be unique",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "binding-disabled",
"message": "Two-way binding is disabled", "message": "Two-way binding is disabled",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "binding-disabled",
"message": "Two-way binding is disabled", "message": "Two-way binding is disabled",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "directive values should not be wrapped — use 'foo', not '{foo}'", "message": "directive values should not be wrapped — use 'foo', not '{foo}'",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "directive values should not be wrapped — use 'foo', not '{{foo}}'", "message": "directive values should not be wrapped — use 'foo', not '{{foo}}'",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)", "message": "Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)",
"pos": 19, "pos": 19,
"loc": { "loc": {

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)", "message": "Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)",
"pos": 19, "pos": 19,
"loc": { "loc": {

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "comment was left open, expected -->", "message": "comment was left open, expected -->",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "comment was left open, expected -->", "message": "comment was left open, expected -->",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "css-syntax-error",
"message": "LeftCurlyBracket is expected", "message": "LeftCurlyBracket is expected",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "css-syntax-error",
"message": "LeftCurlyBracket is expected", "message": "LeftCurlyBracket is expected",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "Expected a method call", "message": "Expected a method call",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "Expected a method call", "message": "Expected a method call",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "parse-error",
"message": "Assigning to rvalue", "message": "Assigning to rvalue",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "parse-error",
"message": "Assigning to rvalue", "message": "Assigning to rvalue",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "duplicate-style",
"message": "You can only have one top-level <style> tag per component", "message": "You can only have one top-level <style> tag per component",
"loc": { "loc": {
"line": 9, "line": 9,

@ -1,4 +1,5 @@
{ {
"code": "duplicate-style",
"message": "You can only have one top-level <style> tag per component", "message": "You can only have one top-level <style> tag per component",
"loc": { "loc": {
"line": 9, "line": 9,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "ref directives cannot have a value", "message": "ref directives cannot have a value",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-directive-value",
"message": "ref directives cannot have a value", "message": "ref directives cannot have a value",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-script",
"message": "<script> must have a closing tag", "message": "<script> must have a closing tag",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-script",
"message": "<script> must have a closing tag", "message": "<script> must have a closing tag",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,4 +1,5 @@
{ {
"code": "invalid-self-placement",
"message": "<svelte:self> components can only exist inside if-blocks or each-blocks", "message": "<svelte:self> components can only exist inside if-blocks or each-blocks",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-self-placement",
"message": "<:Self> components can only exist inside if-blocks or each-blocks", "message": "<:Self> components can only exist inside if-blocks or each-blocks",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "Unexpected end of input", "message": "Unexpected end of input",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "Unexpected end of input", "message": "Unexpected end of input",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "Unexpected end of input", "message": "Unexpected end of input",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unexpected-eof",
"message": "Unexpected end of input", "message": "Unexpected end of input",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-block",
"message": "Block was left open", "message": "Block was left open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-block",
"message": "Block was left open", "message": "Block was left open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-element",
"message": "<div> was left open", "message": "<div> was left open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "unclosed-element",
"message": "<div> was left open", "message": "<div> was left open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-closing-tag",
"message": "</div> attempted to close an element that was not open", "message": "</div> attempted to close an element that was not open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-closing-tag",
"message": "</div> attempted to close an element that was not open", "message": "</div> attempted to close an element that was not open",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-void-content",
"message": "<input> is a void element and cannot have children, or a closing tag", "message": "<input> is a void element and cannot have children, or a closing tag",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-void-content",
"message": "<input> is a void element and cannot have children, or a closing tag", "message": "<input> is a void element and cannot have children, or a closing tag",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-content",
"message": "<svelte:window> cannot have children", "message": "<svelte:window> cannot have children",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-content",
"message": "<:Window> cannot have children", "message": "<:Window> cannot have children",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
{ {
"code": "duplicate-window",
"message": "A component can only have one <svelte:window> tag", "message": "A component can only have one <svelte:window> tag",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "duplicate-window",
"message": "A component can only have one <:Window> tag", "message": "A component can only have one <:Window> tag",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-placement",
"message": "<svelte:window> tags cannot be inside elements or blocks", "message": "<svelte:window> tags cannot be inside elements or blocks",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-placement",
"message": "<:Window> tags cannot be inside elements or blocks", "message": "<:Window> tags cannot be inside elements or blocks",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-placement",
"message": "<svelte:window> tags cannot be inside elements or blocks", "message": "<svelte:window> tags cannot be inside elements or blocks",
"loc": { "loc": {
"line": 2, "line": 2,

@ -1,4 +1,5 @@
{ {
"code": "invalid-window-placement",
"message": "<:Window> tags cannot be inside elements or blocks", "message": "<:Window> tags cannot be inside elements or blocks",
"loc": { "loc": {
"line": 2, "line": 2,

@ -2,7 +2,7 @@ import * as fs from "fs";
import assert from "assert"; import assert from "assert";
import { svelte, loadConfig, tryToLoadJson } from "../helpers.js"; import { svelte, loadConfig, tryToLoadJson } from "../helpers.js";
describe.only("validate", () => { describe("validate", () => {
fs.readdirSync("test/validator/samples").forEach(dir => { fs.readdirSync("test/validator/samples").forEach(dir => {
if (dir[0] === ".") return; if (dir[0] === ".") return;

@ -1,4 +1,5 @@
[{ [{
"code": "unexpected-reserved-word",
"message": "'case' is a reserved word in JavaScript and cannot be used here", "message": "'case' is a reserved word in JavaScript and cannot be used here",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
[{ [{
"code": "unexpected-reserved-word",
"message": "'case' is a reserved word in JavaScript and cannot be used here", "message": "'case' is a reserved word in JavaScript and cannot be used here",
"loc": { "loc": {
"line": 1, "line": 1,

@ -1,4 +1,5 @@
[{ [{
"code": "parse-error",
"message": "Duplicate export 'default'", "message": "Duplicate export 'default'",
"pos": 37, "pos": 37,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-namespace-property",
"message": "Invalid namespace 'lol'", "message": "Invalid namespace 'lol'",
"pos": 29, "pos": 29,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-namespace-property",
"message": "Invalid namespace 'http://www.w3.org/1999/svg' (did you mean 'http://www.w3.org/2000/svg'?)", "message": "Invalid namespace 'http://www.w3.org/1999/svg' (did you mean 'http://www.w3.org/2000/svg'?)",
"pos": 29, "pos": 29,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-namespace-property",
"message": "The 'namespace' property must be a string literal representing a valid namespace", "message": "The 'namespace' property must be a string literal representing a valid namespace",
"pos": 79, "pos": 79,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-components-property",
"message": "The 'components' property must be an object literal", "message": "The 'components' property must be an object literal",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-events-property",
"message": "The 'events' property must be an object literal", "message": "The 'events' property must be an object literal",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-helpers-property",
"message": "The 'helpers' property must be an object literal", "message": "The 'helpers' property must be an object literal",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-methods-property",
"message": "The 'methods' property must be an object literal", "message": "The 'methods' property must be an object literal",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-transitions-property",
"message": "The 'transitions' property must be an object literal", "message": "The 'transitions' property must be an object literal",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-oncreate-property",
"message": "'oncreate' should be a function expression, not an arrow function expression", "message": "'oncreate' should be a function expression, not an arrow function expression",
"pos": 29, "pos": 29,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-ondestroy-property",
"message": "'ondestroy' should be a function expression, not an arrow function expression", "message": "'ondestroy' should be a function expression, not an arrow function expression",
"pos": 29, "pos": 29,
"loc": { "loc": {

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-computed-property",
"message": "The 'computed' property must be an object literal", "message": "The 'computed' property must be an object literal",
"loc": { "loc": {
"line": 5, "line": 5,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-data-property",
"message": "'data' must be a function", "message": "'data' must be a function",
"loc": { "loc": {
"line": 5, "line": 5,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-props-property",
"message": "'props' must be an array expression, if specified", "message": "'props' must be an array expression, if specified",
"loc": { "loc": {
"line": 5, "line": 5,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-props-property",
"message": "'props' must be an array of string literals", "message": "'props' must be an array of string literals",
"loc": { "loc": {
"line": 5, "line": 5,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-tag-property",
"message": "tag name must be two or more words joined by the '-' character", "message": "tag name must be two or more words joined by the '-' character",
"loc": { "loc": {
"line": 3, "line": 3,

@ -1,5 +1,5 @@
[{ [{
"code": "invalid-property", "code": "invalid-tag-property",
"message": "'tag' must be a string literal", "message": "'tag' must be a string literal",
"loc": { "loc": {
"line": 3, "line": 3,

Loading…
Cancel
Save