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()))) {