diff --git a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts index c7a35a63f..4f84e9dd9 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -2021,4 +2021,80 @@ describe('DiagnosticsProvider', () => { } ]); }); + + it('diagnoses bindings with $store', async () => { + const { plugin, document } = setup('bind-to-$store.svelte'); + + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + range: { + start: { + line: 19, + character: 33 + }, + end: { + line: 19, + character: 34 + } + }, + severity: 1, + source: 'ts', + message: "Type 'number' is not assignable to type 'boolean'.", + code: 2322, + tags: [] + }, + { + range: { + start: { + line: 20, + character: 16 + }, + end: { + line: 20, + character: 20 + } + }, + severity: 1, + source: 'ts', + message: "Type 'boolean' is not assignable to type 'number'.", + code: 2322, + tags: [] + }, + { + range: { + start: { + line: 21, + character: 24 + }, + end: { + line: 21, + character: 41 + } + }, + severity: 1, + source: 'ts', + message: "Type 'number' is not assignable to type 'boolean'.", + code: 2322, + tags: [] + }, + { + range: { + start: { + line: 22, + character: 16 + }, + end: { + line: 22, + character: 20 + } + }, + severity: 1, + source: 'ts', + message: "Type 'boolean' is not assignable to type 'number'.", + code: 2322, + tags: [] + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/bind-to-$store.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/bind-to-$store.svelte new file mode 100644 index 000000000..2275ee68c --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/bind-to-$store.svelte @@ -0,0 +1,23 @@ + + + +
+ +
+ + + +
+ +
+ diff --git a/packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts b/packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts index aa3b9be7b..7d503444d 100644 --- a/packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts +++ b/packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts @@ -13,6 +13,11 @@ const oneWayBindingAttributes: Map = new Map( ]) ) ); +/** + * List of all binding names that are transformed to sth like `binding = variable`. + * This applies to readonly bindings and the this binding. + */ +export const assignmentBindings = new Set([...oneWayBindingAttributes.keys(), 'this']); /** * Transform bind:xxx into something that conforms to JSX @@ -57,8 +62,8 @@ export function handleBinding( const thisType = getInstanceTypeSimple(el, str); if (thisType) { - str.overwrite(attr.start, attr.expression.start, '{...__sveltets_1_empty(('); - const instanceOfThisAssignment = ' = ' + surroundWithIgnoreComments(thisType) + '))}'; + str.overwrite(attr.start, attr.expression.start, '{...__sveltets_1_empty('); + const instanceOfThisAssignment = ' = ' + surroundWithIgnoreComments(thisType) + ')}'; str.overwrite(attr.expression.end, attr.end, instanceOfThisAssignment); return; } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts index 06faee332..e9f46c3c3 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts @@ -2,9 +2,11 @@ import MagicString from 'magic-string'; import { Node } from 'estree-walker'; import { ScopeStack, Scope } from '../utils/Scope'; import { isObjectKey, isMember } from '../../utils/svelteAst'; +import { assignmentBindings } from '../../htmlxtojsx/nodes/binding'; export function handleStore(node: Node, parent: Node, str: MagicString): void { const storename = node.name.slice(1); + //handle assign to if (parent.type == 'AssignmentExpression' && parent.left == node && parent.operator == '=') { const dollar = str.original.indexOf('$', node.start); @@ -13,6 +15,7 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { str.appendLeft(parent.end, ')'); return; } + // handle Assignment operators ($store +=, -=, *=, /=, %=, **=, etc.) // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment const operators = ['+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '>>>=', '&=', '^=', '|=']; @@ -31,6 +34,7 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { str.appendLeft(parent.end, ')'); return; } + // handle $store++, $store--, ++$store, --$store if (parent.type == 'UpdateExpression') { let simpleOperator; @@ -55,10 +59,21 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { return; } + const dollar = str.original.indexOf('$', node.start); + + // handle bindings which are transformed to assignments. These need special treatment because + // `(__sveltets_1_store_get(foo), foo$) = something` is syntactically invalid + // Therefore remove the outer commas. Note: This relies on the binding expression wrapping + // this statement with __sveltets_1_empty + if (parent.type === 'Binding' && assignmentBindings.has(parent.name)) { + str.overwrite(dollar, dollar + 1, '__sveltets_1_store_get(', { contentOnly: true }); + str.prependLeft(node.end, `), $${storename}`); + return; + } + // we change "$store" references into "(__sveltets_1_store_get(store), $store)" // - in order to get ts errors if store is not assignable to SvelteStore // - use $store variable defined above to get ts flow control - const dollar = str.original.indexOf('$', node.start); str.overwrite(dollar, dollar + 1, '(__sveltets_1_store_get(', { contentOnly: true }); str.prependLeft(node.end, `), $${storename})`); } diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 084a88b9a..f179c52b9 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -160,7 +160,7 @@ declare function __sveltets_1_with_any_event(store: SvelteStore): T declare function __sveltets_1_any(dummy: any): any; -declare function __sveltets_1_empty(dummy: any): {}; +declare function __sveltets_1_empty(...dummy: any[]): {}; declare function __sveltets_1_componentType(): AConstructorTypeOf> declare function __sveltets_1_invalidate(getValue: () => T): T diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-component/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-component/expected.jsx index 7a8e490a3..b50b5b0b9 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-component/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-component/expected.jsx @@ -1 +1 @@ -<> \ No newline at end of file +<> \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-body/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-body/expected.jsx index 2997455b2..b3b8138b4 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-body/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-body/expected.jsx @@ -1 +1 @@ -<> \ No newline at end of file +<> \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-component/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-component/expected.jsx index dbf6b4ffe..b5bea26f8 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-component/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-component/expected.jsx @@ -1 +1 @@ -<> \ No newline at end of file +<> \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-self/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-self/expected.jsx index e57f57e85..9e56b2094 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-self/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this-svelte-self/expected.jsx @@ -1,3 +1,3 @@ <>{(false) ? <> - + : <>} \ No newline at end of file diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this/expected.jsx index 0aa088562..cdd5bb8f6 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/binding-this/expected.jsx @@ -1 +1 @@ -<> \ No newline at end of file +<> \ 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 953be8533..f1a7caec5 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/component-props/mappings.jsx @@ -27,11 +27,11 @@ {/** />↲ [generated] line 19 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 63a5658ca..3ca70f65a 100644 --- a/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx +++ b/packages/svelte2tsx/test/sourcemaps/samples/large-sample-1/mappings.jsx @@ -323,11 +323,11 @@ s
{/** ------------------------------------------------------------------------------------------------------------------------------------------------------ */} -
{/** - ╚╚╚↲ [generated] line 139 - ╚╚╚↲ - ╚╚╚↲ - ╚╚╚↲ [original] line 278 +
{/** + ╚╚╚↲ [generated] line 139 + ╚╚╚↲ + ╚╚╚↲ + ╚╚╚↲ [original] line 278 ------------------------------------------------------------------------------------------------------------------------------------------------------ */} { chapter.html} {/** ╚╚╚╚{•chapter.html}↲ [generated] line 140 @@ -400,11 +400,11 @@ s
+<>;function render() { +<>
+
+
+
+
+
+return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/input.svelte new file mode 100644 index 000000000..b508059e9 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/binding-assignment-$store/input.svelte @@ -0,0 +1,6 @@ +
+
+
+
+
+
diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx index 449338254..ff47d9a87 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/slot-bind-this/expected.tsx @@ -1,7 +1,7 @@ /// <>;function render() { /*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_1_createEnsureSlot();/*Ωignore_endΩ*/ -<> +<> return { props: {}, slots: {'s': {}}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {