Skip to content

Commit a72a2ad

Browse files
authored
fix: take type annotations into account when transforming one-way bindings (#2283)
#2277
1 parent 2cf037e commit a72a2ad

File tree

6 files changed

+54
-20
lines changed

6 files changed

+54
-20
lines changed

packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import MagicString from 'magic-string';
2-
import { rangeWithTrailingPropertyAccess, TransformationArray } from '../utils/node-utils';
2+
import {
3+
getEnd,
4+
isTypescriptNode,
5+
rangeWithTrailingPropertyAccess,
6+
TransformationArray
7+
} from '../utils/node-utils';
38
import { BaseDirective, BaseNode } from '../../interfaces';
49
import { Element } from './Element';
510
import { InlineComponent } from './InlineComponent';
@@ -67,26 +72,20 @@ export function handleBinding(
6772
// Note: If the component unmounts (it's inside an if block, or svelte:component this={null},
6873
// the value becomes null, but we don't add it to the clause because it would introduce
6974
// worse DX for the 99% use case, and because null !== undefined which others might use to type the declaration.
70-
element.appendToStartEnd([
71-
[attr.expression.start, attr.expression.end],
72-
` = ${element.name};`
73-
]);
75+
appendOneWayBinding(attr, ` = ${element.name}`, element);
7476
return;
7577
}
7678

7779
// one way binding
7880
if (oneWayBindingAttributes.has(attr.name) && element instanceof Element) {
79-
element.appendToStartEnd([
80-
[attr.expression.start, attr.expression.end],
81-
`= ${element.name}.${attr.name};`
82-
]);
81+
appendOneWayBinding(attr, `= ${element.name}.${attr.name}`, element);
8382
return;
8483
}
8584

8685
// one way binding whose property is not on the element
8786
if (oneWayBindingAttributesNotOnElement.has(attr.name) && element instanceof Element) {
8887
element.appendToStartEnd([
89-
[attr.expression.start, attr.expression.end],
88+
[attr.expression.start, getEnd(attr.expression)],
9089
`= ${surroundWithIgnoreComments(
9190
`null as ${oneWayBindingAttributesNotOnElement.get(attr.name)}`
9291
)};`
@@ -127,3 +126,21 @@ export function handleBinding(
127126
element.addProp(name, value);
128127
}
129128
}
129+
130+
function appendOneWayBinding(
131+
attr: BaseDirective,
132+
assignment: string,
133+
element: Element | InlineComponent
134+
) {
135+
const expression = attr.expression;
136+
const end = getEnd(expression);
137+
const hasTypeAnnotation = expression.typeAnnotation || isTypescriptNode(expression);
138+
const array: TransformationArray = [
139+
[expression.start, end],
140+
assignment + (hasTypeAnnotation ? '' : ';')
141+
];
142+
if (hasTypeAnnotation) {
143+
array.push([end, expression.end], ';');
144+
}
145+
element.appendToStartEnd(array);
146+
}

packages/svelte2tsx/src/htmlxtojsx_v2/nodes/EachBlock.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import MagicString from 'magic-string';
22
import { BaseNode } from '../../interfaces';
3-
import { transform, TransformationArray } from '../utils/node-utils';
3+
import { getEnd, transform, TransformationArray } from '../utils/node-utils';
44

55
/**
66
* Transform #each into a for-of loop
@@ -74,10 +74,3 @@ export function handleEach(str: MagicString, eachBlock: BaseNode): void {
7474
});
7575
}
7676
}
77-
78-
/**
79-
* Get the end of the node, excluding the type annotation
80-
*/
81-
function getEnd(node: any) {
82-
return node.typeAnnotation?.start ?? node.end;
83-
}

packages/svelte2tsx/src/htmlxtojsx_v2/nodes/SnippetBlock.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,13 @@ export function handleSnippet(
101101
typeAnnotation + ' = ('
102102
];
103103

104+
// context was the first iteration in a .next release, remove at some point
104105
if (snippetBlock.context) {
105106
transforms.push([snippetBlock.context.start, snippetBlock.context.end]);
107+
} else if (snippetBlock.parameters?.length) {
108+
const start = snippetBlock.parameters[0].start;
109+
const end = snippetBlock.parameters.at(-1).end;
110+
transforms.push([start, end]);
106111
}
107112

108113
transforms.push(') => {');

packages/svelte2tsx/src/htmlxtojsx_v2/utils/node-utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,18 @@ export function rangeWithTrailingPropertyAccess(
226226
): [start: number, end: number] {
227227
return [node.start, withTrailingPropertyAccess(originalText, node.end)];
228228
}
229+
230+
/**
231+
* Get the end of the node, excluding the type annotation
232+
*/
233+
export function getEnd(node: any) {
234+
return isTypescriptNode(node) ? node.expression.end : node.typeAnnotation?.start ?? node.end;
235+
}
236+
237+
export function isTypescriptNode(node: any) {
238+
return (
239+
node.type === 'TSAsExpression' ||
240+
node.type === 'TSSatisfiesExpression' ||
241+
node.type === 'TSNonNullExpression'
242+
);
243+
}

packages/svelte2tsx/test/htmlx2jsx/samples/ts-in-template.v5/expected-svelte5.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ try { const $$_value = await (foo as Promise<void>);{ const result: any = $$_val
1616

1717
item as string;
1818

19-
var foo/*Ωignore_startΩ*/: import('svelte').Snippet<string>/*Ωignore_endΩ*/ = (bar: string) => { return __sveltets_2_any(0)};
19+
var foo/*Ωignore_startΩ*/: import('svelte').Snippet<[ string]>/*Ωignore_endΩ*/ = (bar: string) => { return __sveltets_2_any(0)};
2020

2121
;__sveltets_2_ensureSnippet(foo(bar as string));
2222

2323
{ svelteHTML.createElement("button", { "onclick":(e: Event) => {e as any},}); }
2424
{ const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { "attr":attr as boolean,}});}
25-
{ svelteHTML.createElement("label", { "id":ok!,}); }
25+
{ svelteHTML.createElement("label", { "id":ok!,}); }
26+
{ const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); const $$_tnenopmoC0 = new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { }});x = $$_tnenopmoC0 as any;}
27+
{ const $$_div0 = svelteHTML.createElement("div", { });x= $$_div0.clientWidth as any;}

packages/svelte2tsx/test/htmlx2jsx/samples/ts-in-template.v5/input.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@
3131
<button onclick={(e: Event) => {e as any}}>click</button>
3232
<Component attr={attr as boolean} />
3333
<label id={ok!}></label>
34+
<Component bind:this={x as any} />
35+
<div bind:clientWidth={x as any} />

0 commit comments

Comments
 (0)