[feat] centralise parse errors (#6519)

Co-authored-by: pngwn <hello@pngwn.io>
pull/6527/head
Tan Li Hau 3 years ago committed by GitHub
parent fdd3d4b448
commit 23f8f4ba6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,192 @@
// All parser errors should be listed and accessed from here
import list from '../utils/list';
/**
* @internal
*/
export default {
css_syntax_error: (message) => ({
code: 'css-syntax-error',
message
}),
duplicate_attribute: {
code: 'duplicate-attribute',
message: 'Attributes need to be unique'
},
duplicate_element: (slug: string, name: string) => ({
code: `duplicate-${slug}`,
message: `A component can only have one <${name}> tag`
}),
duplicate_style: {
code: 'duplicate-style',
message: 'You can only have one top-level <style> tag per component'
},
empty_attribute_shorthand: {
code: 'empty-attribute-shorthand',
message: 'Attribute shorthand cannot be empty'
},
empty_directive_name: (type: string) => ({
code: 'empty-directive-name',
message: `${type} name cannot be empty`
}),
empty_global_selector: {
code: 'css-syntax-error',
message: ':global() must contain a selector'
},
expected_block_type: {
code: 'expected-block-type',
message: 'Expected if, each or await'
},
expected_name: {
code: 'expected-name',
message: 'Expected name'
},
invalid_catch_placement_unclosed_block: (block) => ({
code: 'invalid-catch-placement',
message: `Expected to close ${block} before seeing {:catch} block`
}),
invalid_catch_placement_without_await: {
code: 'invalid-catch-placement',
message: 'Cannot have an {:catch} block outside an {#await ...} block'
},
invalid_component_definition: {
code: 'invalid-component-definition',
message: 'invalid component definition'
},
invalid_closing_tag_unopened: (name: string) => ({
code: 'invalid-closing-tag',
message: `</${name}> attempted to close an element that was not open`
}),
invalid_closing_tag_autoclosed: (name: string, reason: string) => ({
code: 'invalid-closing-tag',
message: `</${name}> attempted to close <${name}> that was already automatically closed by <${reason}>`
}),
invalid_debug_args: {
code: 'invalid-debug-args',
message:
'{@debug ...} arguments must be identifiers, not arbitrary expressions'
},
invalid_declaration: {
code: 'invalid-declaration',
message: 'Declaration cannot be empty'
},
invalid_directive_value: {
code: 'invalid-directive-value',
message: 'Directive value must be a JavaScript expression enclosed in curly braces'
},
invalid_elseif: {
code: 'invalid-elseif',
message: '\'elseif\' should be \'else if\''
},
invalid_elseif_placement_outside_if: {
code: 'invalid-elseif-placement',
message: 'Cannot have an {:else if ...} block outside an {#if ...} block'
},
invalid_elseif_placement_unclosed_block: (block) => ({
code: 'invalid-elseif-placement',
message: `Expected to close ${block} before seeing {:else if ...} block`
}),
invalid_else_placement_outside_if: {
code: 'invalid-else-placement',
message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block'
},
invalid_else_placement_unclosed_block: (block) => ({
code: 'invalid-else-placement',
message: `Expected to close ${block} before seeing {:else} block`
}),
invalid_element_content: (slug: string, name: string) => ({
code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`
}),
invalid_element_placement: (slug: string, name: string) => ({
code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks`
}),
invalid_ref_directive: (name: string) => ({
code: 'invalid-ref-directive',
message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead`
}),
invalid_ref_selector: {
code: 'invalid-ref-selector',
message: 'ref selectors are no longer supported'
},
invalid_self_placement: {
code: 'invalid-self-placement',
message: '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components'
},
invalid_script_instance: {
code: 'invalid-script',
message: 'A component can only have one instance-level <script> element'
},
invalid_script_module: {
code: 'invalid-script',
message: 'A component can only have one <script context="module"> element'
},
invalid_script_context_attribute: {
code: 'invalid-script',
message: 'context attribute must be static'
},
invalid_script_context_value: {
code: 'invalid-script',
message: 'If the context attribute is supplied, its value must be "module"'
},
invalid_tag_name: {
code: 'invalid-tag-name',
message: 'Expected valid tag name'
},
invalid_tag_name_svelte_element: (tags: string[], match: string) => ({
code: 'invalid-tag-name',
message: `Valid <svelte:...> tag names are ${list(tags)}${
match ? ' (did you mean ' + match + '?)' : ''
}`
}),
invalid_then_placement_unclosed_block: (block) => ({
code: 'invalid-then-placement',
message: `Expected to close ${block} before seeing {:then} block`
}),
invalid_then_placement_without_await: {
code: 'invalid-then-placement',
message: 'Cannot have an {:then} block outside an {#await ...} block'
},
invalid_void_content: (name: string) => ({
code: 'invalid-void-content',
message: `<${name}> is a void element and cannot have children, or a closing tag`
}),
missing_component_definition: {
code: 'missing-component-definition',
message: '<svelte:component> must have a \'this\' attribute'
},
unclosed_script: {
code: 'unclosed-script',
message: '<script> must have a closing tag'
},
unclosed_style: {
code: 'unclosed-style',
message: '<style> must have a closing tag'
},
unclosed_comment: {
code: 'unclosed-comment',
message: 'comment was left open, expected -->'
},
unexpected_block_close: {
code: 'unexpected-block-close',
message: 'Unexpected block closing tag'
},
unexpected_eof: {
code: 'unexpected-eof',
message: 'Unexpected end of input'
},
unexpected_eof_token: (token: string) => ({
code: 'unexpected-eof',
message: `Unexpected ${token}`
}),
unexpected_token: (token: string) => ({
code: 'unexpected-token',
message: `Expected ${token}`
}),
unexpected_token_destructure: {
code: 'unexpected-token',
message: 'Expected identifier or destructure pattern'
}
};

@ -5,6 +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 parser_errors from './errors';
type ParserState = (parser: Parser) => (ParserState | void);
@ -106,17 +107,18 @@ export class Parser {
});
}
eat(str: string, required?: boolean, message?: string) {
eat(str: string, required?: boolean, error?: { code: string, message: string }) {
if (this.match(str)) {
this.index += str.length;
return true;
}
if (required) {
this.error({
code: `unexpected-${this.index === this.template.length ? 'eof' : 'token'}`,
message: message || `Expected ${str}`
});
this.error(error ||
(this.index === this.template.length
? parser_errors.unexpected_eof_token(str)
: parser_errors.unexpected_token(str))
);
}
return false;
@ -218,27 +220,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({
code: 'duplicate-style',
message: 'You can only have one top-level <style> tag per component'
}, parser.css[1].start);
parser.error(parser_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({
code: 'invalid-script',
message: 'A component can only have one instance-level <script> element'
}, instance_scripts[1].start);
parser.error(parser_errors.invalid_script_instance, instance_scripts[1].start);
}
if (module_scripts.length > 1) {
parser.error({
code: 'invalid-script',
message: 'A component can only have one <script context="module"> element'
}, module_scripts[1].start);
parser.error(parser_errors.invalid_script_module, module_scripts[1].start);
}
return {

@ -9,6 +9,7 @@ import {
} from '../utils/bracket';
import { parse_expression_at } from '../acorn';
import { Pattern } from 'estree';
import parser_errors from '../errors';
export default function read_context(
parser: Parser
@ -27,10 +28,7 @@ export default function read_context(
}
if (!is_bracket_open(code)) {
parser.error({
code: 'unexpected-token',
message: 'Expected identifier or destructure pattern'
});
parser.error(parser_errors.unexpected_token_destructure);
}
const bracket_stack = [code];
@ -42,12 +40,11 @@ export default function read_context(
bracket_stack.push(code);
} else if (is_bracket_close(code)) {
if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) {
parser.error({
code: 'unexpected-token',
message: `Expected ${String.fromCharCode(
get_bracket_close(bracket_stack[bracket_stack.length - 1])
)}`
});
parser.error(
parser_errors.unexpected_token(
String.fromCharCode(get_bracket_close(bracket_stack[bracket_stack.length - 1]))
)
);
}
bracket_stack.pop();
if (bracket_stack.length === 0) {

@ -2,6 +2,7 @@ import { parse_expression_at } from '../acorn';
import { Parser } from '../index';
import { Node } from 'estree';
import { whitespace } from '../../utils/patterns';
import parser_errors from '../errors';
export default function read_expression(parser: Parser): Node {
try {
@ -20,10 +21,7 @@ export default function read_expression(parser: Parser): Node {
if (char === ')') {
num_parens -= 1;
} else if (!whitespace.test(char)) {
parser.error({
code: 'unexpected-token',
message: 'Expected )'
}, index);
parser.error(parser_errors.unexpected_token(')'), index);
}
index += 1;

@ -2,25 +2,20 @@ import * as acorn from '../acorn';
import { Parser } from '../index';
import { Script } from '../../interfaces';
import { Node, Program } from 'estree';
import parser_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({
code: 'invalid-script',
message: 'context attribute must be static'
}, start);
parser.error(parser_errors.invalid_script_context_attribute, start);
}
const value = context.value[0].data;
if (value !== 'module') {
parser.error({
code: 'invalid-script',
message: 'If the context attribute is supplied, its value must be "module"'
}, context.start);
parser.error(parser_errors.invalid_script_context_value, context.start);
}
return value;
@ -28,13 +23,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 error_message = {
code: 'unclosed-script',
message: '<script> must have a closing tag'
};
const data = parser.read_until(/<\/script\s*>/, error_message);
const data = parser.read_until(/<\/script\s*>/, parser_errors.unclosed_script);
if (parser.index >= parser.template.length) {
parser.error(error_message);
parser.error(parser_errors.unclosed_script);
}
const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + data;

@ -3,18 +3,15 @@ import { walk } from 'estree-walker';
import { Parser } from '../index';
import { Node } from 'estree';
import { Style } from '../../interfaces';
import parser_errors from '../errors';
export default function read_style(parser: Parser, start: number, attributes: Node[]): Style {
const content_start = parser.index;
const error_message = {
code: 'unclosed-style',
message: '<style> must have a closing tag'
};
const styles = parser.read_until(/<\/style\s*>/, error_message);
const styles = parser.read_until(/<\/style\s*>/, parser_errors.unclosed_style);
if (parser.index >= parser.template.length) {
parser.error(error_message);
parser.error(parser_errors.unclosed_style);
}
const content_end = parser.index;
@ -31,10 +28,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
});
} catch (err) {
if (err.name === 'SyntaxError') {
parser.error({
code: 'css-syntax-error',
message: err.message
}, err.offset);
parser.error(parser_errors.css_syntax_error(err.message), err.offset);
} else {
throw err;
}
@ -52,26 +46,17 @@ export default function read_style(parser: Parser, start: number, attributes: No
const b = node.children[i + 1];
if (is_ref_selector(a, b)) {
parser.error({
code: 'invalid-ref-selector',
message: 'ref selectors are no longer supported'
}, a.loc.start.offset);
parser.error(parser_errors.invalid_ref_selector, a.loc.start.offset);
}
}
}
if (node.type === 'Declaration' && node.value.type === 'Value' && node.value.children.length === 0) {
parser.error({
code: 'invalid-declaration',
message: 'Declaration cannot be empty'
}, node.start);
parser.error(parser_errors.invalid_declaration, node.start);
}
if (node.type === 'PseudoClassSelector' && node.name === 'global' && node.children === null) {
parser.error({
code: 'css-syntax-error',
message: ':global() must contain a selector'
}, node.loc.start.offset);
parser.error(parser_errors.empty_global_selector, node.loc.start.offset);
}
if (node.loc) {

@ -6,6 +6,7 @@ import { trim_start, trim_end } from '../../utils/trim';
import { to_string } from '../utils/node';
import { Parser } from '../index';
import { TemplateNode } from '../../interfaces';
import parser_errors from '../errors';
function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after: boolean) {
if (!block.children || block.children.length === 0) return; // AwaitBlock
@ -66,10 +67,7 @@ export default function mustache(parser: Parser) {
} else if (block.type === 'KeyBlock') {
expected = 'key';
} else {
parser.error({
code: 'unexpected-block-close',
message: 'Unexpected block closing tag'
});
parser.error(parser_errors.unexpected_block_close);
}
parser.eat(expected, true);
@ -98,10 +96,7 @@ export default function mustache(parser: Parser) {
parser.stack.pop();
} else if (parser.eat(':else')) {
if (parser.eat('if')) {
parser.error({
code: 'invalid-elseif',
message: "'elseif' should be 'else if'"
});
parser.error(parser_errors.invalid_elseif);
}
parser.allow_whitespace();
@ -110,12 +105,11 @@ export default function mustache(parser: Parser) {
if (parser.eat('if')) {
const block = parser.current();
if (block.type !== 'IfBlock') {
parser.error({
code: 'invalid-elseif-placement',
message: parser.stack.some(block => block.type === 'IfBlock')
? `Expected to close ${to_string(block)} before seeing {:else if ...} block`
: 'Cannot have an {:else if ...} block outside an {#if ...} block'
});
parser.error(
parser.stack.some(block => block.type === 'IfBlock')
? parser_errors.invalid_elseif_placement_unclosed_block(to_string(block))
: parser_errors.invalid_elseif_placement_outside_if
);
}
parser.require_whitespace();
@ -146,12 +140,11 @@ export default function mustache(parser: Parser) {
// :else
const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error({
code: 'invalid-else-placement',
message: parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? `Expected to close ${to_string(block)} before seeing {:else} block`
: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block'
});
parser.error(
parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? parser_errors.invalid_else_placement_unclosed_block(to_string(block))
: parser_errors.invalid_else_placement_outside_if
);
}
parser.allow_whitespace();
@ -172,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')
? parser_errors.invalid_then_placement_unclosed_block(to_string(block))
: parser_errors.invalid_then_placement_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')
? parser_errors.invalid_catch_placement_unclosed_block(to_string(block))
: parser_errors.invalid_catch_placement_without_await
);
}
}
@ -224,10 +214,7 @@ export default function mustache(parser: Parser) {
} else if (parser.eat('key')) {
type = 'KeyBlock';
} else {
parser.error({
code: 'expected-block-type',
message: 'Expected if, each, await or key'
});
parser.error(parser_errors.expected_block_type);
}
parser.require_whitespace();
@ -286,12 +273,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(parser_errors.expected_name);
parser.allow_whitespace();
}
@ -371,10 +353,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(parser_errors.invalid_debug_args, node.start);
}
});

@ -6,7 +6,7 @@ import { is_void } from '../../utils/names';
import { Parser } from '../index';
import { Directive, DirectiveType, TemplateNode, Text } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import parser_errors from '../errors';
// eslint-disable-next-line no-useless-escape
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
@ -58,7 +58,7 @@ export default function tag(parser: Parser) {
if (parser.eat('!--')) {
const data = parser.read_until(/-->/);
parser.eat('-->', true, 'comment was left open, expected -->');
parser.eat('-->', true, parser_errors.unclosed_comment);
parser.current().children.push({
start,
@ -81,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(
parser_errors.invalid_element_content(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(parser_errors.duplicate_element(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(parser_errors.invalid_element_placement(slug, name), start);
}
parser.meta_tags[name] = true;
@ -125,10 +119,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(parser_errors.invalid_void_content(name), start);
}
parser.eat('>', true);
@ -136,13 +127,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
? parser_errors.invalid_closing_tag_autoclosed(name, parser.last_auto_closed_tag.reason)
: parser_errors.invalid_closing_tag_unopened(name);
parser.error(error, start);
}
parent.end = start;
@ -180,18 +168,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(parser_errors.missing_component_definition, 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(parser_errors.invalid_component_definition, definition.start);
}
element.expression = definition.value[0].expression;
@ -256,10 +238,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(parser_errors.invalid_self_placement, start);
}
return 'svelte:self';
@ -276,20 +255,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(
parser_errors.invalid_tag_name_svelte_element(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(parser_errors.invalid_tag_name, start);
}
return name;
@ -300,10 +273,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(parser_errors.duplicate_attribute, start);
}
unique_names.add(name);
}
@ -331,10 +301,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
parser.eat('}', true);
if (name === null) {
parser.error({
code: 'empty-attribute-shorthand',
message: 'Attribute shorthand cannot be empty'
}, start);
parser.error(parser_errors.empty_attribute_shorthand, start);
}
check_unique(name);
@ -376,20 +343,14 @@ 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(parser_errors.unexpected_token('='), parser.index);
}
if (type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (directive_name === '') {
parser.error({
code: 'empty-directive-name',
message: `${type} name cannot be empty`
}, start + colon_index + 1);
parser.error(parser_errors.empty_directive_name(type), start + colon_index + 1);
}
if (type === 'Binding' && directive_name !== 'this') {
@ -399,18 +360,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(parser_errors.invalid_ref_directive(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(parser_errors.invalid_directive_value, value[0].start);
}
}
@ -530,8 +485,5 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
}
}
parser.error({
code: 'unexpected-eof',
message: 'Unexpected end of input'
});
parser.error(parser_errors.unexpected_eof);
}

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

Loading…
Cancel
Save