diff --git a/.changeset/fix-type-identifier-in-tag.md b/.changeset/fix-type-identifier-in-tag.md new file mode 100644 index 0000000000..8e88843536 --- /dev/null +++ b/.changeset/fix-type-identifier-in-tag.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: don't mistake expressions starting with `type` (e.g. `{type === 'all' ? a : b}`) for TypeScript `type` declarations in tags diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 3c8eba4b26..de17183920 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -12,9 +12,9 @@ import { find_matching_bracket, match_bracket } from '../utils/bracket.js'; const regex_whitespace_with_closing_curly_brace = /\s*}/y; const regex_supported_declaration = /(?:let|const)\b/y; -// All except `type` are reserved keywords and cannot be used as variable names. -// For type we check if it's not something like `type .x` / `type ()` / `type % 2` / ... -const regex_unsupported_declaration = /(?:(?:var|interface|enum)\b)|(?:type\s+[^?.(`<[&|%^}])/y; +const regex_unsupported_declaration = /(?:var|interface|enum)\b/y; +// `type` is a contextual keyword; this is just a shape hint, confirmed by parsing. +const regex_maybe_type_declaration = /type\s+[\p{ID_Start}_$]/uy; const pointy_bois = { '<': '>' }; @@ -77,6 +77,26 @@ function read_declaration(parser) { e.declaration_tag_invalid_type({ start, end: start + unsupported.length }); } + const type_alias_match = parser.match_regex(regex_maybe_type_declaration); + if (type_alias_match) { + const initial_comment_count = parser.root.comments.length; + /** @type {{ type: string; start: number; end: number } | undefined} */ + let statement; + try { + statement = /** @type {{ type: string; start: number; end: number }} */ ( + /** @type {unknown} */ (parse_statement_at(parser, parser.template, start)) + ); + } catch { + e.declaration_tag_invalid_type({ start, end: start + type_alias_match.length }); + } + if (statement.type !== 'ExpressionStatement') { + e.declaration_tag_invalid_type(statement); + } + // discard probe comments so `read_expression` doesn't re-add them + parser.root.comments.length = initial_comment_count; + return null; + } + if (!parser.match_regex(regex_supported_declaration)) { return null; } diff --git a/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/input.svelte b/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/input.svelte new file mode 100644 index 0000000000..fa4195996e --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/input.svelte @@ -0,0 +1 @@ +{type instanceof /* probe */ Object} diff --git a/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/output.json b/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/output.json new file mode 100644 index 0000000000..4fe6468742 --- /dev/null +++ b/packages/svelte/tests/parser-modern/samples/tag-type-identifier-probe-comment/output.json @@ -0,0 +1,92 @@ +{ + "css": null, + "js": [], + "start": 0, + "end": 36, + "type": "Root", + "fragment": { + "type": "Fragment", + "nodes": [ + { + "type": "ExpressionTag", + "start": 0, + "end": 36, + "expression": { + "type": "BinaryExpression", + "start": 1, + "end": 35, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 35 + } + }, + "left": { + "type": "Identifier", + "start": 1, + "end": 5, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 5 + } + }, + "name": "type" + }, + "operator": "instanceof", + "right": { + "type": "Identifier", + "start": 29, + "end": 35, + "loc": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 35 + } + }, + "name": "Object", + "leadingComments": [ + { + "type": "Block", + "value": " probe ", + "start": 17, + "end": 28 + } + ] + } + } + } + ] + }, + "options": null, + "comments": [ + { + "type": "Block", + "value": " probe ", + "start": 17, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 28 + } + } + } + ] +} diff --git a/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/_config.js b/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/_config.js new file mode 100644 index 0000000000..52841fd48d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `

cast

generic-call

generic-call-space

` +}); diff --git a/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/main.svelte b/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/main.svelte new file mode 100644 index 0000000000..693a360f55 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/declaration-tag-type-identifier/main.svelte @@ -0,0 +1,16 @@ + + + + +

{'cast' as string}

+

{type('generic-call')}

+

{type ('generic-call-space')}

diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/errors.json b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/errors.json index 052636cddb..c1603f4139 100644 --- a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/errors.json +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/errors.json @@ -3,11 +3,11 @@ "code": "declaration_tag_invalid_type", "message": "Declaration tags must be `let` or `const` declarations", "start": { - "line": 14, + "line": 25, "column": 2 }, "end": { - "line": 14, + "line": 25, "column": 8 } } diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/input.svelte b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/input.svelte index 129a8ac7ba..772c0931c0 100644 --- a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/input.svelte +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-2/input.svelte @@ -10,6 +10,17 @@ {type ()} {type [1]} {type `tag`} + {type === 'all' ? 'All types' : type} + {type !== 'all'} + {type == 'all'} + {type != 'all'} + {type + 1} + {type - 1} + {type * 2} + {type / 2} + {type > 1} + {type instanceof Foo} + {type in foo} {type foo = boolean} {/if} diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/errors.json b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/errors.json new file mode 100644 index 0000000000..a60f0055ae --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "declaration_tag_invalid_type", + "message": "Declaration tags must be `let` or `const` declarations", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 33 + } + } +] diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/input.svelte b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/input.svelte new file mode 100644 index 0000000000..2e5465b786 --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-generic/input.svelte @@ -0,0 +1,2 @@ + +{type Foo = T[]} diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/errors.json b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/errors.json new file mode 100644 index 0000000000..fb4ef28f3a --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "declaration_tag_invalid_type", + "message": "Declaration tags must be `let` or `const` declarations", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 16 + } + } +] diff --git a/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/input.svelte b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/input.svelte new file mode 100644 index 0000000000..baae707898 --- /dev/null +++ b/packages/svelte/tests/validator/samples/declaration-tag-invalid-type-unicode/input.svelte @@ -0,0 +1,2 @@ + +{type Ω = string}