Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/itchy-beans-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: fix compiler errors test suite
5 changes: 5 additions & 0 deletions .changeset/sweet-mangos-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: handle css nth-selector syntax
19 changes: 8 additions & 11 deletions packages/svelte/src/compiler/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ const internal = {
const parse = {
/** @param {string} name */
'unclosed-element': (name) => `<${name}> was left open`,
'unclosed-block': () => `block was left open`,
'unclosed-block': () => `Block was left open`,
'unexpected-block-close': () => `Unexpected block closing tag`,
'unexpected-eof': () => `Unexpected end of input`,
/** @param {string} [expected] */
'unexpected-eof': (expected) =>
`Unexpected end of input` + (expected ? ` (expected ${expected})` : ''),
/** @param {string} message */
'js-parse-error': (message) => message,
/** @param {string} token */
Expand All @@ -39,17 +41,15 @@ const parse = {
'invalid-script-context': () =>
`If the context attribute is supplied, its value must be "module"`,
'invalid-elseif': () => `'elseif' should be 'else if'`,
/**
* @param {string} child
* @param {string} parent
*/
'invalid-block-parent': (child, parent) =>
`Expected to close ${parent} before seeing ${child} block`,
'invalid-continuing-block-placement': () =>
`{:...} block is invalid at this position (did you forget to close the preceeding element or block?)`,
/**
* @param {string} child
* @param {string} parent
*/
'invalid-block-missing-parent': (child, parent) => `${child} block must be a child of ${parent}`,
/** @param {string} name */
'duplicate-block-part': (name) => `${name} cannot appear more than once within a block`,
'expected-block-type': () => `Expected 'if', 'each', 'await', 'key' or 'snippet'`,
'expected-identifier': () => `Expected an identifier`,
'invalid-debug': () => `{@debug ...} arguments must be identifiers, not arbitrary expressions`,
Expand Down Expand Up @@ -98,12 +98,9 @@ const css = {
'invalid-css-empty-declaration': () => `Declaration cannot be empty`,
'invalid-css-global-placement': () =>
`:global(...) can be at the start or end of a selector sequence, but not in the middle`,

'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`,

'invalid-css-global-selector-list': () =>
`:global(...) cannot be used to modify a selector, or be modified by another selector`,

'invalid-css-selector': () => `Invalid selector`,
'invalid-css-identifier': () => 'Expected a valid CSS identifier'
};
Expand Down
4 changes: 3 additions & 1 deletion packages/svelte/src/compiler/phases/1-parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ export class Parser {
const current = this.current();

if (current.type === 'RegularElement') {
current.end = current.start + 1;
error(current, 'unclosed-element', current.name);
} else {
current.end = current.start + 1;
error(current, 'unclosed-block');
}
}
Expand Down Expand Up @@ -145,7 +147,7 @@ export class Parser {

if (required) {
if (this.index === this.template.length) {
error(this.index, 'unexpected-eof');
error(this.index, 'unexpected-eof', str);
} else {
error(this.index, 'expected-token', str);
}
Expand Down
10 changes: 10 additions & 0 deletions packages/svelte/src/compiler/phases/1-parse/read/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today,
const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/;
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
const REGEX_NTH_OF = /^(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s+of\s+)?/;
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
Expand Down Expand Up @@ -234,6 +235,8 @@ function read_selector(parser, inside_pseudo_class = false) {
if (parser.eat('(')) {
args = read_selector_list(parser, true);
parser.eat(')', true);
} else if (name === 'global') {
error(parser.index, 'invalid-css-global-selector');
}

children.push({
Expand Down Expand Up @@ -291,6 +294,13 @@ function read_selector(parser, inside_pseudo_class = false) {
start,
end: parser.index
});
} else if (parser.match_regex(REGEX_NTH_OF)) {
children.push({
type: 'Nth',
value: /** @type {string} */ (parser.read(REGEX_NTH_OF)),
start,
end: parser.index
});
} else {
let name = read_identifier(parser);
if (parser.match('|')) {
Expand Down
21 changes: 12 additions & 9 deletions packages/svelte/src/compiler/phases/1-parse/state/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,13 @@ export default function tag(parser) {

let attribute;
while ((attribute = read(parser))) {
if (
(attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
unique_names.includes(attribute.name)
) {
error(attribute.start, 'duplicate-attribute');
if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
if (unique_names.includes(attribute.name)) {
error(attribute.start, 'duplicate-attribute');
// <svelte:element bind:this this=..> is allowed
} else if (attribute.name !== 'this') {
unique_names.push(attribute.name);
}
}

element.attributes.push(attribute);
Expand Down Expand Up @@ -635,13 +637,14 @@ function read_attribute_value(parser) {
'in attribute value'
);
} catch (/** @type {any} e */ e) {
if (e.code === 'parse-error') {
if (e.code === 'js-parse-error') {
// if the attribute value didn't close + self-closing tag
// eg: `<Component test={{a:1} />`
// acorn may throw a `Unterminated regular expression` because of `/>`
if (parser.template.slice(e.pos - 1, e.pos + 1) === '/>') {
parser.index = e.pos;
error(e.pos, 'unclosed-attribute-value', quote_mark || '}');
const pos = e.position?.[0];
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
parser.index = pos;
error(pos, 'unclosed-attribute-value', quote_mark || '}');
}
}
throw e;
Expand Down
10 changes: 6 additions & 4 deletions packages/svelte/src/compiler/phases/1-parse/state/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ function next(parser) {
const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad

if (block.type === 'IfBlock') {
if (!parser.eat('else')) error(start, 'expected-token', 'else');
if (!parser.eat('else')) error(start, 'expected-token', '{:else} or {:else if}');
if (parser.eat('if')) error(start, 'invalid-elseif');

parser.allow_whitespace();
Expand Down Expand Up @@ -359,7 +359,7 @@ function next(parser) {
}

if (block.type === 'EachBlock') {
if (!parser.eat('else')) error(start, 'expected-token', 'else');
if (!parser.eat('else')) error(start, 'expected-token', '{:else}');

parser.allow_whitespace();
parser.eat('}', true);
Expand All @@ -375,7 +375,7 @@ function next(parser) {
if (block.type === 'AwaitBlock') {
if (parser.eat('then')) {
if (block.then) {
error(start, 'TODO', 'duplicate then');
error(start, 'duplicate-block-part', '{:then}');
}

if (!parser.eat('}')) {
Expand All @@ -394,7 +394,7 @@ function next(parser) {

if (parser.eat('catch')) {
if (block.catch) {
error(start, 'TODO', 'duplicate catch');
error(start, 'duplicate-block-part', '{:catch}');
}

if (!parser.eat('}')) {
Expand All @@ -413,6 +413,8 @@ function next(parser) {

error(start, 'expected-token', '{:then ...} or {:catch ...}');
}

error(start, 'invalid-continuing-block-placement');
}

/** @param {import('../index.js').Parser} parser */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ function block_might_apply_to_node(block, node) {
while (i--) {
const selector = block.selectors[i];

if (selector.type === 'Percentage') continue;
if (selector.type === 'Percentage' || selector.type === 'Nth') continue;

const name = selector.name.replace(regex_backslash_and_following_character, '$1');

Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/src/compiler/types/css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,20 @@ export interface Percentage extends BaseNode {
value: string;
}

export interface Nth extends BaseNode {
type: 'Nth';
value: string;
}

export type SimpleSelector =
| TypeSelector
| IdSelector
| ClassSelector
| AttributeSelector
| PseudoElementSelector
| PseudoClassSelector
| Percentage;
| Percentage
| Nth;

export interface Combinator extends BaseNode {
type: 'Combinator';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'missing-attribute-value',
message: 'Expected value for the attribute',
message: 'Expected attribute value',
position: [12, 12]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-catch-placement',
message: 'Expected to close {#each} block before seeing {:catch} block',
position: [41, 41]
code: 'expected-token',
message: 'Expected token {:else}',
position: [35, 35]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-catch-placement',
message: 'Cannot have an {:catch} block outside an {#await ...} block',
position: [7, 7]
code: 'invalid-continuing-block-placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [1, 1]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export default test({
error: {
code: 'invalid-state-location',
message: '$state() can only be used as a variable declaration initializer or a class field',
position: process.platform === 'win32' ? [35, 43] : [33, 41]
position: [33, 41]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'unclosed-comment',
message: 'comment was left open, expected -->',
code: 'unexpected-eof',
message: 'Unexpected end of input (expected -->)',
position: [24, 24]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'css-syntax-error',
message: ':global() must contain a selector',
position: [9, 9]
code: 'invalid-css-global-selector',
message: ':global(...) must contain exactly one selector',
position: [16, 16]
}
});
6 changes: 3 additions & 3 deletions packages/svelte/tests/compiler-errors/samples/css/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'css-syntax-error',
message: '"{" is expected',
position: [24, 24]
code: 'invalid-css-identifier',
message: 'Expected a valid CSS identifier',
position: [25, 25]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-else-placement',
message: 'Expected to close {#await} block before seeing {:else} block',
position: [29, 29]
code: 'expected-token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [24, 24]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-else-placement',
message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block',
position: [11, 11]
code: 'invalid-continuing-block-placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [6, 6]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-else-placement',
message: 'Expected to close <li> tag before seeing {:else} block',
position: [23, 23]
code: 'invalid-continuing-block-placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [18, 18]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-elseif-placement',
message: 'Expected to close <p> tag before seeing {:else if ...} block',
position: [25, 25]
code: 'invalid-continuing-block-placement',
message:
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
position: [17, 17]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-elseif-placement',
message: 'Expected to close {#await} block before seeing {:else if ...} block',
position: [34, 34]
code: 'expected-token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [26, 26]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { test } from '../../test';

export default test({
error: {
code: 'invalid-elseif-placement',
message: 'Cannot have an {:else if ...} block outside an {#if ...} block',
position: [35, 35]
code: 'expected-token',
message: 'Expected token {:then ...} or {:catch ...}',
position: [27, 27]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'empty-directive-name',
message: 'Class name cannot be empty',
message: 'ClassDirective name cannot be empty',
position: [10, 10]
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { test } from '../../test';
export default test({
error: {
code: 'empty-directive-name',
message: 'Action name cannot be empty',
message: 'UseDirective name cannot be empty',
position: [8, 8]
}
});
Loading