Skip to content

Commit ede5dab

Browse files
chore: fix compiler-errors test suite (#9754)
- fix compiler-errors test suite - handle css nth-selector syntax (fixes #9765) --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Simon Holthausen <[email protected]>
1 parent fd4a52c commit ede5dab

File tree

104 files changed

+704
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+704
-107
lines changed

.changeset/itchy-beans-melt.md

Lines changed: 5 additions & 0 deletions

.changeset/sweet-mangos-beg.md

Lines changed: 5 additions & 0 deletions

packages/svelte/src/compiler/errors.js

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ const internal = {
2424
const parse = {
2525
/** @param {string} name */
2626
'unclosed-element': (name) => `<${name}> was left open`,
27-
'unclosed-block': () => `block was left open`,
27+
'unclosed-block': () => `Block was left open`,
2828
'unexpected-block-close': () => `Unexpected block closing tag`,
29-
'unexpected-eof': () => `Unexpected end of input`,
29+
/** @param {string} [expected] */
30+
'unexpected-eof': (expected) =>
31+
`Unexpected end of input` + (expected ? ` (expected ${expected})` : ''),
3032
/** @param {string} message */
3133
'js-parse-error': (message) => message,
3234
/** @param {string} token */
@@ -39,17 +41,15 @@ const parse = {
3941
'invalid-script-context': () =>
4042
`If the context attribute is supplied, its value must be "module"`,
4143
'invalid-elseif': () => `'elseif' should be 'else if'`,
42-
/**
43-
* @param {string} child
44-
* @param {string} parent
45-
*/
46-
'invalid-block-parent': (child, parent) =>
47-
`Expected to close ${parent} before seeing ${child} block`,
44+
'invalid-continuing-block-placement': () =>
45+
`{:...} block is invalid at this position (did you forget to close the preceeding element or block?)`,
4846
/**
4947
* @param {string} child
5048
* @param {string} parent
5149
*/
5250
'invalid-block-missing-parent': (child, parent) => `${child} block must be a child of ${parent}`,
51+
/** @param {string} name */
52+
'duplicate-block-part': (name) => `${name} cannot appear more than once within a block`,
5353
'expected-block-type': () => `Expected 'if', 'each', 'await', 'key' or 'snippet'`,
5454
'expected-identifier': () => `Expected an identifier`,
5555
'invalid-debug': () => `{@debug ...} arguments must be identifiers, not arbitrary expressions`,
@@ -98,12 +98,9 @@ const css = {
9898
'invalid-css-empty-declaration': () => `Declaration cannot be empty`,
9999
'invalid-css-global-placement': () =>
100100
`:global(...) can be at the start or end of a selector sequence, but not in the middle`,
101-
102101
'invalid-css-global-selector': () => `:global(...) must contain exactly one selector`,
103-
104102
'invalid-css-global-selector-list': () =>
105103
`:global(...) cannot be used to modify a selector, or be modified by another selector`,
106-
107104
'invalid-css-selector': () => `Invalid selector`,
108105
'invalid-css-identifier': () => 'Expected a valid CSS identifier'
109106
};

packages/svelte/src/compiler/phases/1-parse/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ export class Parser {
7777
const current = this.current();
7878

7979
if (current.type === 'RegularElement') {
80+
current.end = current.start + 1;
8081
error(current, 'unclosed-element', current.name);
8182
} else {
83+
current.end = current.start + 1;
8284
error(current, 'unclosed-block');
8385
}
8486
}
@@ -145,7 +147,7 @@ export class Parser {
145147

146148
if (required) {
147149
if (this.index === this.template.length) {
148-
error(this.index, 'unexpected-eof');
150+
error(this.index, 'unexpected-eof', str);
149151
} else {
150152
error(this.index, 'expected-token', str);
151153
}

packages/svelte/src/compiler/phases/1-parse/read/style.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const REGEX_ATTRIBUTE_FLAGS = /^[a-zA-Z]+/; // only `i` and `s` are valid today,
66
const REGEX_COMBINATOR_WHITESPACE = /^\s*(\+|~|>|\|\|)\s*/;
77
const REGEX_COMBINATOR = /^(\+|~|>|\|\|)/;
88
const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
9+
const REGEX_NTH_OF = /^(even|odd|(-?[0-9]?n?(\s*\+\s*[0-9]+)?))(\s+of\s+)?/;
910
const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
1011
const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
1112
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
@@ -234,6 +235,8 @@ function read_selector(parser, inside_pseudo_class = false) {
234235
if (parser.eat('(')) {
235236
args = read_selector_list(parser, true);
236237
parser.eat(')', true);
238+
} else if (name === 'global') {
239+
error(parser.index, 'invalid-css-global-selector');
237240
}
238241

239242
children.push({
@@ -291,6 +294,13 @@ function read_selector(parser, inside_pseudo_class = false) {
291294
start,
292295
end: parser.index
293296
});
297+
} else if (parser.match_regex(REGEX_NTH_OF)) {
298+
children.push({
299+
type: 'Nth',
300+
value: /** @type {string} */ (parser.read(REGEX_NTH_OF)),
301+
start,
302+
end: parser.index
303+
});
294304
} else {
295305
let name = read_identifier(parser);
296306
if (parser.match('|')) {

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,13 @@ export default function tag(parser) {
202202

203203
let attribute;
204204
while ((attribute = read(parser))) {
205-
if (
206-
(attribute.type === 'Attribute' || attribute.type === 'BindDirective') &&
207-
unique_names.includes(attribute.name)
208-
) {
209-
error(attribute.start, 'duplicate-attribute');
205+
if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
206+
if (unique_names.includes(attribute.name)) {
207+
error(attribute.start, 'duplicate-attribute');
208+
// <svelte:element bind:this this=..> is allowed
209+
} else if (attribute.name !== 'this') {
210+
unique_names.push(attribute.name);
211+
}
210212
}
211213

212214
element.attributes.push(attribute);
@@ -635,13 +637,14 @@ function read_attribute_value(parser) {
635637
'in attribute value'
636638
);
637639
} catch (/** @type {any} e */ e) {
638-
if (e.code === 'parse-error') {
640+
if (e.code === 'js-parse-error') {
639641
// if the attribute value didn't close + self-closing tag
640642
// eg: `<Component test={{a:1} />`
641643
// acorn may throw a `Unterminated regular expression` because of `/>`
642-
if (parser.template.slice(e.pos - 1, e.pos + 1) === '/>') {
643-
parser.index = e.pos;
644-
error(e.pos, 'unclosed-attribute-value', quote_mark || '}');
644+
const pos = e.position?.[0];
645+
if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
646+
parser.index = pos;
647+
error(pos, 'unclosed-attribute-value', quote_mark || '}');
645648
}
646649
}
647650
throw e;

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ function next(parser) {
315315
const block = parser.current(); // TODO type should not be TemplateNode, that's much too broad
316316

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

321321
parser.allow_whitespace();
@@ -359,7 +359,7 @@ function next(parser) {
359359
}
360360

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

364364
parser.allow_whitespace();
365365
parser.eat('}', true);
@@ -375,7 +375,7 @@ function next(parser) {
375375
if (block.type === 'AwaitBlock') {
376376
if (parser.eat('then')) {
377377
if (block.then) {
378-
error(start, 'TODO', 'duplicate then');
378+
error(start, 'duplicate-block-part', '{:then}');
379379
}
380380

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

395395
if (parser.eat('catch')) {
396396
if (block.catch) {
397-
error(start, 'TODO', 'duplicate catch');
397+
error(start, 'duplicate-block-part', '{:catch}');
398398
}
399399

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

414414
error(start, 'expected-token', '{:then ...} or {:catch ...}');
415415
}
416+
417+
error(start, 'invalid-continuing-block-placement');
416418
}
417419

418420
/** @param {import('../index.js').Parser} parser */

packages/svelte/src/compiler/phases/2-analyze/css/Selector.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ function block_might_apply_to_node(block, node) {
306306
while (i--) {
307307
const selector = block.selectors[i];
308308

309-
if (selector.type === 'Percentage') continue;
309+
if (selector.type === 'Percentage' || selector.type === 'Nth') continue;
310310

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

packages/svelte/src/compiler/types/css.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,20 @@ export interface Percentage extends BaseNode {
6767
value: string;
6868
}
6969

70+
export interface Nth extends BaseNode {
71+
type: 'Nth';
72+
value: string;
73+
}
74+
7075
export type SimpleSelector =
7176
| TypeSelector
7277
| IdSelector
7378
| ClassSelector
7479
| AttributeSelector
7580
| PseudoElementSelector
7681
| PseudoClassSelector
77-
| Percentage;
82+
| Percentage
83+
| Nth;
7884

7985
export interface Combinator extends BaseNode {
8086
type: 'Combinator';

packages/svelte/tests/compiler-errors/samples/attribute-empty/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'missing-attribute-value',
6-
message: 'Expected value for the attribute',
6+
message: 'Expected attribute value',
77
position: [12, 12]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/catch-before-closing/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-catch-placement',
6-
message: 'Expected to close {#each} block before seeing {:catch} block',
7-
position: [41, 41]
5+
code: 'expected-token',
6+
message: 'Expected token {:else}',
7+
position: [35, 35]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/catch-without-await/_config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-catch-placement',
6-
message: 'Cannot have an {:catch} block outside an {#await ...} block',
7-
position: [7, 7]
5+
code: 'invalid-continuing-block-placement',
6+
message:
7+
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
8+
position: [1, 1]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/class-state-field-static/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ export default test({
44
error: {
55
code: 'invalid-state-location',
66
message: '$state() can only be used as a variable declaration initializer or a class field',
7-
position: process.platform === 'win32' ? [35, 43] : [33, 41]
7+
position: [33, 41]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/comment-unclosed/_config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'unclosed-comment',
6-
message: 'comment was left open, expected -->',
5+
code: 'unexpected-eof',
6+
message: 'Unexpected end of input (expected -->)',
77
position: [24, 24]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/css-global-without-selector/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'css-syntax-error',
6-
message: ':global() must contain a selector',
7-
position: [9, 9]
5+
code: 'invalid-css-global-selector',
6+
message: ':global(...) must contain exactly one selector',
7+
position: [16, 16]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/css/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'css-syntax-error',
6-
message: '"{" is expected',
7-
position: [24, 24]
5+
code: 'invalid-css-identifier',
6+
message: 'Expected a valid CSS identifier',
7+
position: [25, 25]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/else-before-closing-2/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-else-placement',
6-
message: 'Expected to close {#await} block before seeing {:else} block',
7-
position: [29, 29]
5+
code: 'expected-token',
6+
message: 'Expected token {:then ...} or {:catch ...}',
7+
position: [24, 24]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/else-before-closing-3/_config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-else-placement',
6-
message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block',
7-
position: [11, 11]
5+
code: 'invalid-continuing-block-placement',
6+
message:
7+
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
8+
position: [6, 6]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/else-before-closing/_config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-else-placement',
6-
message: 'Expected to close <li> tag before seeing {:else} block',
7-
position: [23, 23]
5+
code: 'invalid-continuing-block-placement',
6+
message:
7+
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
8+
position: [18, 18]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/else-if-before-closing-2/_config.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-elseif-placement',
6-
message: 'Expected to close <p> tag before seeing {:else if ...} block',
7-
position: [25, 25]
5+
code: 'invalid-continuing-block-placement',
6+
message:
7+
'{:...} block is invalid at this position (did you forget to close the preceeding element or block?)',
8+
position: [17, 17]
89
}
910
});

packages/svelte/tests/compiler-errors/samples/else-if-before-closing/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-elseif-placement',
6-
message: 'Expected to close {#await} block before seeing {:else if ...} block',
7-
position: [34, 34]
5+
code: 'expected-token',
6+
message: 'Expected token {:then ...} or {:catch ...}',
7+
position: [26, 26]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/else-if-without-if/_config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { test } from '../../test';
22

33
export default test({
44
error: {
5-
code: 'invalid-elseif-placement',
6-
message: 'Cannot have an {:else if ...} block outside an {#if ...} block',
7-
position: [35, 35]
5+
code: 'expected-token',
6+
message: 'Expected token {:then ...} or {:catch ...}',
7+
position: [27, 27]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/empty-classname-binding/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'empty-directive-name',
6-
message: 'Class name cannot be empty',
6+
message: 'ClassDirective name cannot be empty',
77
position: [10, 10]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/empty-directive-name/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'empty-directive-name',
6-
message: 'Action name cannot be empty',
6+
message: 'UseDirective name cannot be empty',
77
position: [8, 8]
88
}
99
});

0 commit comments

Comments
 (0)