Skip to content

Commit

Permalink
chore: loose parser improvements (#14733)
Browse files Browse the repository at this point in the history
* handle invalid expression inside each key

* handle invalid expression inside each expression

* handle invalid expression inside await block

* handle "in the middle of typing" components with starting lowercase and dot at the end

* changeset
  • Loading branch information
dummdidumm authored Dec 17, 2024
1 parent 36d73ca commit 3146e93
Show file tree
Hide file tree
Showing 13 changed files with 836 additions and 99 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-kings-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: more loose parser improvements
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { find_matching_bracket } from '../utils/bracket.js';

/**
* @param {Parser} parser
* @param {string} [opening_token]
* @returns {Expression}
*/
export default function read_expression(parser) {
export default function read_expression(parser, opening_token) {
try {
const node = parse_expression_at(parser.template, parser.ts, parser.index);

Expand Down Expand Up @@ -42,7 +43,7 @@ export default function read_expression(parser) {
} catch (err) {
if (parser.loose) {
// Find the next } and treat it as the end of the expression
const end = find_matching_bracket(parser.template, parser.index, '{');
const end = find_matching_bracket(parser.template, parser.index, opening_token ?? '{');
if (end) {
const start = parser.index;
parser.index = end;
Expand Down
9 changes: 6 additions & 3 deletions packages/svelte/src/compiler/phases/1-parse/state/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ export default function element(parser) {
}

if (!regex_valid_element_name.test(name) && !regex_valid_component_name.test(name)) {
const bounds = { start: start + 1, end: start + 1 + name.length };
e.tag_invalid_name(bounds);
// <div. -> in the middle of typing -> allow in loose mode
if (!parser.loose || !name.endsWith('.')) {
const bounds = { start: start + 1, end: start + 1 + name.length };
e.tag_invalid_name(bounds);
}
}

if (root_only_meta_tags.has(name)) {
Expand All @@ -141,7 +144,7 @@ export default function element(parser) {

const type = meta_tags.has(name)
? meta_tags.get(name)
: regex_valid_component_name.test(name)
: regex_valid_component_name.test(name) || (parser.loose && name.endsWith('.'))
? 'Component'
: name === 'title' && parent_is_head(parser.stack)
? 'TitleElement'
Expand Down
55 changes: 52 additions & 3 deletions packages/svelte/src/compiler/phases/1-parse/state/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,30 @@ function open(parser) {
if (parser.eat('(')) {
parser.allow_whitespace();

key = read_expression(parser);
key = read_expression(parser, '(');
parser.allow_whitespace();
parser.eat(')', true);
parser.allow_whitespace();
}

parser.eat('}', true);
const matches = parser.eat('}', true, false);

if (!matches) {
// Parser may have read the `as` as part of the expression (e.g. in `{#each foo. as x}`)
if (parser.template.slice(parser.index - 4, parser.index) === ' as ') {
const prev_index = parser.index;
context = read_pattern(parser);
parser.eat('}', true);
expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 4
};
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}

/** @type {AST.EachBlock} */
const block = parser.append({
Expand Down Expand Up @@ -246,7 +263,39 @@ function open(parser) {
parser.fragments.push(block.pending);
}

parser.eat('}', true);
const matches = parser.eat('}', true, false);

// Parser may have read the `then/catch` as part of the expression (e.g. in `{#await foo. then x}`)
if (!matches) {
if (parser.template.slice(parser.index - 6, parser.index) === ' then ') {
const prev_index = parser.index;
block.value = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 6
};
block.then = block.pending;
block.pending = null;
} else if (parser.template.slice(parser.index - 7, parser.index) === ' catch ') {
const prev_index = parser.index;
block.error = read_pattern(parser);
parser.eat('}', true);
block.expression = {
type: 'Identifier',
name: '',
start: expression.start,
end: prev_index - 7
};
block.catch = block.pending;
block.pending = null;
} else {
parser.eat('}', true); // rerun to produce the parser error
}
}

parser.stack.push(block);

return;
Expand Down
5 changes: 5 additions & 0 deletions packages/svelte/src/compiler/phases/1-parse/utils/bracket.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const SQUARE_BRACKET_OPEN = '['.charCodeAt(0);
const SQUARE_BRACKET_CLOSE = ']'.charCodeAt(0);
const CURLY_BRACKET_OPEN = '{'.charCodeAt(0);
const CURLY_BRACKET_CLOSE = '}'.charCodeAt(0);
const PARENTHESES_OPEN = '('.charCodeAt(0);
const PARENTHESES_CLOSE = ')'.charCodeAt(0);

/** @param {number} code */
export function is_bracket_open(code) {
Expand Down Expand Up @@ -34,6 +36,9 @@ export function get_bracket_close(open) {
if (open === CURLY_BRACKET_OPEN) {
return CURLY_BRACKET_CLOSE;
}
if (open === PARENTHESES_OPEN) {
return PARENTHESES_CLOSE;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@

asd{a.}asd
{foo[bar.]}

{#if x.}{/if}

{#each array as item (item.)}{/each}

{#each obj. as item}{/each}

{#await x.}{/await}

{#await x. then y}{/await}

{#await x. catch y}{/await}
Loading

0 comments on commit 3146e93

Please sign in to comment.