From 15a41f3f02f8964c2dcf389bf99982bd756f8ef6 Mon Sep 17 00:00:00 2001
From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com>
Date: Tue, 19 Mar 2024 18:55:11 +0800
Subject: [PATCH] feat: TypeScript 5.4 support for svelte-check (#2313)
* bump ts to 5.4.2
* assign to bound variable for ts 5.4 to widen to control flow
* format
* oops this doesn't work in 5.3
* revert bumping but keeps compatibility fixes
* lock
* Update packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
---
.../features/DiagnosticsProvider.ts | 16 ++++---
.../src/plugins/typescript/module-loader.ts | 3 +-
.../fixtures/bind-union/Component.svelte | 5 +++
.../fixtures/bind-union/expectedv2.json | 1 +
.../fixtures/bind-union/input.svelte | 18 ++++++++
.../src/htmlxtojsx_v2/nodes/Binding.ts | 6 +++
.../samples/binding-bare/expectedv2.js | 4 +-
.../htmlx2jsx/samples/binding/expectedv2.js | 6 +--
.../samples/directive-quoted/expectedv2.js | 2 +-
.../samples/editing-binding/expectedv2.js | 4 +-
.../samples/component-props/mappings.jsx | 38 ++++++++---------
.../samples/element-attributes/mappings.jsx | 42 +++++++++----------
.../samples/large-sample-1/mappings.jsx | 24 +++++------
.../binding-assignment-$store/expectedv2.ts | 4 +-
14 files changed, 105 insertions(+), 68 deletions(-)
create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/Component.svelte
create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/expectedv2.json
create mode 100644 packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/input.svelte
diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
index c5f9c6aa4..64223ac35 100644
--- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
+++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts
@@ -543,11 +543,17 @@ function expectedTransitionThirdArgument(
return false;
}
- const callExpression = findNodeAtSpan(
- node,
- { start: node.getStart(), length: node.getWidth() },
- ts.isCallExpression
- );
+ // in TypeScript 5.4 the error is on the function name
+ // in earlier versions it's on the whole call expression
+ const callExpression =
+ ts.isIdentifier(node) && ts.isCallExpression(node.parent)
+ ? node.parent
+ : findNodeAtSpan(
+ node,
+ { start: node.getStart(), length: node.getWidth() },
+ ts.isCallExpression
+ );
+
const signature =
callExpression && lang.getProgram()?.getTypeChecker().getResolvedSignature(callExpression);
diff --git a/packages/language-server/src/plugins/typescript/module-loader.ts b/packages/language-server/src/plugins/typescript/module-loader.ts
index 4eefdc700..b681978bf 100644
--- a/packages/language-server/src/plugins/typescript/module-loader.ts
+++ b/packages/language-server/src/plugins/typescript/module-loader.ts
@@ -102,7 +102,8 @@ class ImpliedNodeFormatResolver {
let mode = undefined;
if (sourceFile) {
this.cacheImpliedNodeFormat(sourceFile, compilerOptions);
- mode = ts.getModeForResolutionAtIndex(sourceFile, importIdxInFile);
+ // @ts-expect-error remove when bumping to TS 5.4
+ mode = ts.getModeForResolutionAtIndex(sourceFile, importIdxInFile, compilerOptions);
}
return mode;
}
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/Component.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/Component.svelte
new file mode 100644
index 000000000..bef74c544
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/Component.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/expectedv2.json b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/expectedv2.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/expectedv2.json
@@ -0,0 +1 @@
+[]
diff --git a/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/input.svelte b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/input.svelte
new file mode 100644
index 000000000..43ae46b8b
--- /dev/null
+++ b/packages/language-server/test/plugins/typescript/features/diagnostics/fixtures/bind-union/input.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+{#if checked === true}
+ checked
+{/if}
+
+{#if value === 'bar'}
+ bar
+{/if}
+
+
\ No newline at end of file
diff --git a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
index 31a01ea39..b4c6add56 100644
--- a/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
+++ b/packages/svelte2tsx/src/htmlxtojsx_v2/nodes/Binding.ts
@@ -94,6 +94,12 @@ export function handleBinding(
return;
}
+ // add reassignment to force TS to widen the type of the declaration (in case it's never reassigned anywhere else)
+ const expressionStr = str.original.substring(attr.expression.start, getEnd(attr.expression));
+ element.appendToStartEnd([
+ surroundWithIgnoreComments(`() => ${expressionStr} = __sveltets_2_any(null);`)
+ ]);
+
// other bindings which are transformed to normal attributes/props
const isShorthand = attr.expression.start === attr.start + 'bind:'.length;
const name: TransformationArray =
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js
index bfef43414..0b1b0af3e 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-bare/expectedv2.js
@@ -1,2 +1,2 @@
- { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});}
- { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});}
\ No newline at end of file
+ { svelteHTML.createElement("input", { "type":`text`,"bind:value":value,});/*Ωignore_startΩ*/() => value = __sveltets_2_any(null);/*Ωignore_endΩ*/}
+ { svelteHTML.createElement("input", { "type":`checkbox`,"bind:checked":checked,});/*Ωignore_startΩ*/() => checked = __sveltets_2_any(null);/*Ωignore_endΩ*/}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js
index 41ce94abb..e386fcd48 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding/expectedv2.js
@@ -1,3 +1,3 @@
- { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});}
- { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});}
- { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});}
\ No newline at end of file
+ { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/}
+ { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/}
+ { svelteHTML.createElement("input", { "type":`text`,"bind:value":test,});/*Ωignore_startΩ*/() => test = __sveltets_2_any(null);/*Ωignore_endΩ*/}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js
index 57df4fdb0..869a203b9 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/directive-quoted/expectedv2.js
@@ -4,4 +4,4 @@
{ svelteHTML.createElement("img", { });__sveltets_2_ensureTransition(fade(svelteHTML.mapElementTag('img'),(params)));}
{ svelteHTML.createElement("img", { });classthing;}
{ svelteHTML.createElement("img", { });__sveltets_2_ensureAnimation(thing(svelteHTML.mapElementTag('img'),__sveltets_2_AnimationMove,(params)));}
- { svelteHTML.createElement("img", { "bind:thing":binding,});}
\ No newline at end of file
+ { svelteHTML.createElement("img", { "bind:thing":binding,});/*Ωignore_startΩ*/() => binding = __sveltets_2_any(null);/*Ωignore_endΩ*/}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js
index d568573db..84c329d67 100644
--- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js
+++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-binding/expectedv2.js
@@ -1,3 +1,3 @@
{ svelteHTML.createElement("input", { });obj.;}
- { svelteHTML.createElement("input", { "bind:value":obj.,});}
- { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});}
\ No newline at end of file
+ { svelteHTML.createElement("input", { "bind:value":obj.,});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/}
+ { const $$_tupnI0C = __sveltets_2_ensureComponent(Input); new $$_tupnI0C({ target: __sveltets_2_any(), props: { value:obj.,}});/*Ωignore_startΩ*/() => obj = __sveltets_2_any(null);/*Ωignore_endΩ*/}
\ No newline at end of file
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx
index 0c0b579df..86889cddc 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx
@@ -54,25 +54,25 @@ async•()•=>•{••{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureCom
{/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
- { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { foo:bar,}});} {/**
-•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•••foo:bar,}});}↲ [generated] line 11
-•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{ [generated] subset
-< Component
-↲ [original] line 13
+ { const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { foo:bar,}});/*Ωignore_startΩ*/() => bar = __sveltets_2_any(null);/*Ωignore_endΩ*/}{/**
+•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•••foo:bar,}});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 11
+•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{ [generated] subset
+< Component
+•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 11
+ •• foo:bar,}});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/} [generated] subset
+ b{ foo=bar}
+ #= Order-breaking mappings
+ b foo={bar}
+╚bind:foo={bar}↲ [original] line 12
+
+•{•const•$$_tnenopmoC0C•=•__sveltets_2_ensureComponent(Component);•new•$$_tnenopmoC0C({•target:•__sveltets_2_any(),•props:•{•••foo:bar,}});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 11
+ • ↲ [generated] subset
+ / ↲
+/ ↲
+/>↲ [original] line 13
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{/**
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx
index 2946a8661..7ef16bb3f 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/element-attributes/mappings.jsx
@@ -99,46 +99,46 @@ async•()•=>•{•{•svelteHTML.createElement("element",•{"foo":true,});}
{/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
- { svelteHTML.createElement("element", { "bind:foo":foo,});} {/**
-•{•svelteHTML.createElement("element",•{••"bind:foo":foo,});}↲ [generated] line 13
-•{•svelteHTML.createElement("element",•{ "bind:foo": [generated] subset
+ { svelteHTML.createElement("element", { "bind:foo":foo,});/*Ωignore_startΩ*/() => foo = __sveltets_2_any(null);/*Ωignore_endΩ*/} {/**
+•{•svelteHTML.createElement("element",•{••"bind:foo":foo,});/*Ωignore_startΩ*/()•=>•foo•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 13
+•{•svelteHTML.createElement("element",•{ "bind:foo": [generated] subset
< element ↲
•foo•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 13
+ • foo,});/*Ωignore_startΩ*/()•=>•foo•=•__sveltets_2_any(null);/*Ωignore_endΩ*/} [generated] subset
• foo•
• foo•
-••••bind:foo•↲ [original] line 20
+••••bind:foo•↲ [original] line 20
-•{•svelteHTML.createElement("element",•{••"bind:foo":foo,});}↲ [generated] line 13
- • ↲ [generated] subset
- / ↲
+•{•svelteHTML.createElement("element",•{••"bind:foo":foo,});/*Ωignore_startΩ*/()•=>•foo•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 13
+ • ↲ [generated] subset
+ / ↲
/ ↲
-/>↲ [original] line 21
+/>↲ [original] line 21
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
- { svelteHTML.createElement("element", { "bind:foo":bar,});}}; {/**
-•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});}};↲ [generated] line 15
-•{•svelteHTML.createElement("element",•{ "bind:foo": [generated] subset
+ { svelteHTML.createElement("element", { "bind:foo":bar,});/*Ωignore_startΩ*/() => bar = __sveltets_2_any(null);/*Ωignore_endΩ*/}}; {/**
+•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15
+•{•svelteHTML.createElement("element",•{ "bind:foo": [generated] subset
< element ↲
•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15
+ • bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] subset
• bar}
• bar}
-••••bind:foo={bar}↲ [original] line 24
+••••bind:foo={bar}↲ [original] line 24
-•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});}};↲ [generated] line 15
- • [generated] subset
+•{•svelteHTML.createElement("element",•{••"bind:foo":bar,});/*Ωignore_startΩ*/()•=>•bar•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}};↲ [generated] line 15
+ • [generated] subset
/
/
-/> [original] line 25
+/> [original] line 25
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
return { props: /** @type {Record} */ ({}), slots: {}, events: {} }}
diff --git a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
index 13d0d2485..dd6cc6ee5 100644
--- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
+++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx
@@ -328,12 +328,12 @@ s ↲
{/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
- { svelteHTML.createElement("svelte:window", { "bind:innerWidth":width,});} {/**
-••{•svelteHTML.createElement("svelte:window",•{•"bind:innerWidth":width,});}↲ [generated] line 133
-<> ib width} ↲
- #=============================================# Order-breaking mappings
-< bi width} >↲
-↲ [original] line 269
+ { svelteHTML.createElement("svelte:window", { "bind:innerWidth":width,});/*Ωignore_startΩ*/() => width = __sveltets_2_any(null);/*Ωignore_endΩ*/} {/**
+••{•svelteHTML.createElement("svelte:window",•{•"bind:innerWidth":width,});/*Ωignore_startΩ*/()•=>•width•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 133
+<> ib width} ↲
+ #=============================================# Order-breaking mappings
+< bi width} >↲
+↲ [original] line 269
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{/**
@@ -640,12 +640,12 @@ s ↲
{ mobile}↲
╚{#if•mobile}↲ [original] line 316
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
- { const $$_elggoTneercS1C = __sveltets_2_ensureComponent(ScreenToggle); new $$_elggoTneercS1C({ target: __sveltets_2_any(), props: { offset,"labels":['tutorial', 'input', 'output'],}});}{/**
- ╚╚••{•const•$$_elggoTneercS1C•=•__sveltets_2_ensureComponent(ScreenToggle);•new•$$_elggoTneercS1C({•target:•__sveltets_2_any(),•props:•{••offset,"labels":['tutorial',•'input',•'output'],}});}↲ [generated] line 170
- ╚╚<> ScreenToggle i{offset•l abels= ['tutorial',•'input',•'output']} ↲
- #========================================================= # Order-breaking mappings
- ╚╚↲
- ╚╚↲ [original] line 317
+ { const $$_elggoTneercS1C = __sveltets_2_ensureComponent(ScreenToggle); new $$_elggoTneercS1C({ target: __sveltets_2_any(), props: { offset,"labels":['tutorial', 'input', 'output'],}});/*Ωignore_startΩ*/() => offset = __sveltets_2_any(null);/*Ωignore_endΩ*/}{/**
+ ╚╚••{•const•$$_elggoTneercS1C•=•__sveltets_2_ensureComponent(ScreenToggle);•new•$$_elggoTneercS1C({•target:•__sveltets_2_any(),•props:•{••offset,"labels":['tutorial',•'input',•'output'],}});/*Ωignore_startΩ*/()•=>•offset•=•__sveltets_2_any(null);/*Ωignore_endΩ*/}↲ [generated] line 170
+ ╚╚<> ScreenToggle i{offset•l abels= ['tutorial',•'input',•'output']} ↲
+ #========================================================= # Order-breaking mappings
+ ╚╚↲
+ ╚╚↲ [original] line 317
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
} {/**
╚}↲ [generated] line 171
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts
index 9bce5e2b7..2b1baf659 100644
--- a/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts
+++ b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/expectedv2.ts
@@ -4,8 +4,8 @@ async () => { { const $$_div0 = svelteHTML.createElement("div", { });$compile_o
{ const $$_div0 = svelteHTML.createElement("div", { });$compile_options.foo= $$_div0.offsetHeight;}
{ const $$_div0 = svelteHTML.createElement("div", { });$compile_options = $$_div0;}
{ const $$_div0 = svelteHTML.createElement("div", { });$compile_options.foo = $$_div0;}
- { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options,});}
- { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options.foo,});}};
+ { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options,});/*Ωignore_startΩ*/() => $compile_options = __sveltets_2_any(null);/*Ωignore_endΩ*/}
+ { svelteHTML.createElement("div", { "bind:noAssignment":$compile_options.foo,});/*Ωignore_startΩ*/() => $compile_options.foo = __sveltets_2_any(null);/*Ωignore_endΩ*/}};
return { props: /** @type {Record} */ ({}), slots: {}, events: {} }}
export default class Input__SvelteComponent_ extends __sveltets_2_createSvelte2TsxComponent(__sveltets_2_partial(__sveltets_2_with_any_event(render()))) {