From 0071e0252ae84fddfad9ca18a7bf08b8e203e9c3 Mon Sep 17 00:00:00 2001 From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:11:55 +0545 Subject: [PATCH] fix: allow ts casts in bindings (#10181) fixes #10179 --------- Co-authored-by: Simon Holthausen --- .changeset/selfish-dragons-knock.md | 5 ++++ .../compiler/phases/2-analyze/validation.js | 8 ++++-- .../phases/3-transform/client/utils.js | 19 +++++++------ .../3-transform/client/visitors/template.js | 28 +++++-------------- packages/svelte/src/compiler/utils/ast.js | 17 +++++++++-- .../typescript-as-expression/_config.js | 3 +- .../typescript-as-expression/main.svelte | 8 ++++++ .../typescript-non-null-expression/_config.js | 2 +- .../main.svelte | 3 ++ 9 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 .changeset/selfish-dragons-knock.md diff --git a/.changeset/selfish-dragons-knock.md b/.changeset/selfish-dragons-knock.md new file mode 100644 index 0000000000..b3c2f5b42b --- /dev/null +++ b/.changeset/selfish-dragons-knock.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: allow ts casts in bindings diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 49aea8c195..c07afd7484 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -338,9 +338,11 @@ export const validation = { BindDirective(node, context) { validate_no_const_assignment(node, node.expression, context.state.scope, true); - let left = node.expression; + const assignee = unwrap_ts_expression(node.expression); + let left = assignee; + while (left.type === 'MemberExpression') { - left = /** @type {import('estree').MemberExpression} */ (left.object); + left = unwrap_ts_expression(/** @type {import('estree').MemberExpression} */ (left.object)); } if (left.type !== 'Identifier') { @@ -348,7 +350,7 @@ export const validation = { } if ( - node.expression.type === 'Identifier' && + assignee.type === 'Identifier' && node.name !== 'this' // bind:this also works for regular variables ) { const binding = context.state.scope.get(left.name); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 7490636a21..c5ff3866d1 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,5 +1,5 @@ import * as b from '../../../utils/builders.js'; -import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; +import { extract_paths, is_simple_expression, unwrap_ts_expression } from '../../../utils/ast.js'; import { error } from '../../../errors.js'; import { PROPS_IS_LAZY_INITIAL, @@ -223,10 +223,11 @@ function is_expression_async(expression) { export function serialize_set_binding(node, context, fallback, options) { const { state, visit } = context; + const assignee = unwrap_ts_expression(node.left); if ( - node.left.type === 'ArrayPattern' || - node.left.type === 'ObjectPattern' || - node.left.type === 'RestElement' + assignee.type === 'ArrayPattern' || + assignee.type === 'ObjectPattern' || + assignee.type === 'RestElement' ) { // Turn assignment into an IIFE, so that `$.set` calls etc don't produce invalid code const tmp_id = context.state.scope.generate('tmp'); @@ -237,7 +238,7 @@ export function serialize_set_binding(node, context, fallback, options) { /** @type {import('estree').Expression[]} */ const assignments = []; - const paths = extract_paths(node.left); + const paths = extract_paths(assignee); for (const path of paths) { const value = path.expression?.(b.id(tmp_id)); @@ -275,11 +276,11 @@ export function serialize_set_binding(node, context, fallback, options) { } } - if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') { - error(node, 'INTERNAL', `Unexpected assignment type ${node.left.type}`); + if (assignee.type !== 'Identifier' && assignee.type !== 'MemberExpression') { + error(node, 'INTERNAL', `Unexpected assignment type ${assignee.type}`); } - let left = node.left; + let left = assignee; // Handle class private/public state assignment cases while (left.type === 'MemberExpression') { @@ -342,7 +343,7 @@ export function serialize_set_binding(node, context, fallback, options) { } } // @ts-expect-error - left = left.object; + left = unwrap_ts_expression(left.object); } if (left.type !== 'Identifier') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index ae7cbbec2b..fa6c5608df 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -3,7 +3,8 @@ import { extract_paths, is_event_attribute, is_text_attribute, - object + object, + unwrap_ts_expression } from '../../../../utils/ast.js'; import { binding_properties } from '../../../bindings.js'; import { @@ -2579,24 +2580,9 @@ export const template_visitors = { }, BindDirective(node, context) { const { state, path, visit } = context; - - /** @type {import('estree').Expression[]} */ - const properties = []; - - let expression = node.expression; - while (expression.type === 'MemberExpression') { - properties.unshift( - expression.computed - ? /** @type {import('estree').Expression} */ (expression.property) - : b.literal(/** @type {import('estree').Identifier} */ (expression.property).name) - ); - expression = /** @type {import('estree').Identifier | import('estree').MemberExpression} */ ( - expression.object - ); - } - - const getter = b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression))); - const assignment = b.assignment('=', node.expression, b.id('$$value')); + const expression = unwrap_ts_expression(node.expression); + const getter = b.thunk(/** @type {import('estree').Expression} */ (visit(expression))); + const assignment = b.assignment('=', expression, b.id('$$value')); const setter = b.arrow( [b.id('$$value')], serialize_set_binding( @@ -2716,7 +2702,7 @@ export const template_visitors = { setter, /** @type {import('estree').Expression} */ ( // if expression is not an identifier, we know it can't be a signal - node.expression.type === 'Identifier' ? node.expression : undefined + expression.type === 'Identifier' ? expression : undefined ) ); break; @@ -2765,7 +2751,7 @@ export const template_visitors = { group_getter = b.thunk( b.block([ b.stmt(serialize_attribute_value(value, context)[1]), - b.return(/** @type {import('estree').Expression} */ (visit(node.expression))) + b.return(/** @type {import('estree').Expression} */ (visit(expression))) ]) ); } diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index dd9bbfc17a..910d5f6eef 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -1,4 +1,3 @@ -import { error } from '../errors.js'; import * as b from '../utils/builders.js'; /** @@ -7,10 +6,13 @@ import * as b from '../utils/builders.js'; * @returns {import('estree').Identifier | null} */ export function object(expression) { + expression = unwrap_ts_expression(expression); + while (expression.type === 'MemberExpression') { expression = /** @type {import('estree').MemberExpression | import('estree').Identifier} */ ( expression.object ); + expression = unwrap_ts_expression(expression); } if (expression.type !== 'Identifier') { @@ -270,6 +272,9 @@ function _extract_paths(assignments = [], param, expression, update_expression) * The Acorn TS plugin defines `foo!` as a `TSNonNullExpression` node, and * `foo as Bar` as a `TSAsExpression` node. This function unwraps those. * + * We can't just remove the typescript AST nodes in the parser stage because subsequent + * parsing would fail, since AST start/end nodes would point at the wrong positions. + * * @template {import('#compiler').SvelteNode | undefined | null} T * @param {T} node * @returns {T} @@ -279,8 +284,14 @@ export function unwrap_ts_expression(node) { return node; } - // @ts-expect-error these types don't exist on the base estree types - if (node.type === 'TSNonNullExpression' || node.type === 'TSAsExpression') { + if ( + // @ts-expect-error these types don't exist on the base estree types + node.type === 'TSNonNullExpression' || + // @ts-expect-error these types don't exist on the base estree types + node.type === 'TSAsExpression' || + // @ts-expect-error these types don't exist on the base estree types + node.type === 'TSSatisfiesExpression' + ) { // @ts-expect-error return node.expression; } diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js index 4fd52f2d54..4113497518 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/_config.js @@ -1,5 +1,6 @@ import { test } from '../../test'; export default test({ - html: '1 2' + html: '1 2
', + ssrHtml: '1 2
' }); diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte index 015c2d979d..8b515712ca 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/typescript-as-expression/main.svelte @@ -1,6 +1,14 @@ {count as number} {double as number} + +
+ + diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js index 4fd52f2d54..484af54ab0 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/_config.js @@ -1,5 +1,5 @@ import { test } from '../../test'; export default test({ - html: '1 2' + html: '1 2 ' }); diff --git a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte index e851a57828..57fce96e26 100644 --- a/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/typescript-non-null-expression/main.svelte @@ -1,6 +1,9 @@ {count!} {double!} + +