Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
}
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script lang="ts">
import { writable } from 'svelte/store';
import { SvelteComponentTyped } from 'svelte';

const storeNr = writable(1);
const storeBool = writable(true);
const storeObjNr = writable({foo: 1});
const storeObjBool = writable({foo: true});

class Component extends SvelteComponentTyped<{prop: number}> {}
</script>

<!-- valid -->
<div bind:offsetHeight={$storeNr} />
<Component bind:prop={$storeNr} />
<div bind:offsetHeight={$storeObjNr.foo} />
<Component bind:prop={$storeObjNr.foo} />

<!-- error -->
<div bind:offsetHeight={$storeBool} />
<Component bind:prop={$storeBool} />
<div bind:offsetHeight={$storeObjBool.foo} />
<Component bind:prop={$storeObjBool.foo} />
9 changes: 7 additions & 2 deletions packages/svelte2tsx/src/htmlxtojsx/nodes/binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ const oneWayBindingAttributes: Map<string, string> = 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
Expand Down Expand Up @@ -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;
}
Expand Down
17 changes: 16 additions & 1 deletion packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 = ['+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
Expand All @@ -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;
Expand All @@ -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})`);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte2tsx/svelte-shims.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ declare function __sveltets_1_with_any_event<Props = {}, Events = {}, Slots = {}

declare function __sveltets_1_store_get<T = any>(store: SvelteStore<T>): 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<Svelte2TsxComponent<any, any, any>>
declare function __sveltets_1_invalidate<T>(getValue: () => T): T

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<><Component type="radio" {...__sveltets_1_empty((element = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/))} value="Plain"/></>
<><Component type="radio" {...__sveltets_1_empty(element = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/)} value="Plain"/></>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<><sveltebody {...__sveltets_1_empty((element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(HTMLBodyElement)/*Ωignore_endΩ*/))} /></>
<><sveltebody {...__sveltets_1_empty(element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(HTMLBodyElement)/*Ωignore_endΩ*/)} /></>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<><sveltecomponent this={A} {...__sveltets_1_empty((element = /*Ωignore_startΩ*/new (A)({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/))} /></>
<><sveltecomponent this={A} {...__sveltets_1_empty(element = /*Ωignore_startΩ*/new (A)({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/)} /></>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<>{(false) ? <>
<svelteself {...__sveltets_1_empty((element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_componentType())/*Ωignore_endΩ*/))} />
<svelteself {...__sveltets_1_empty(element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_componentType())/*Ωignore_endΩ*/)} />
</> : <></>}</>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<><input type="radio" {...__sveltets_1_empty((element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('input')))/*Ωignore_endΩ*/))} value="Plain"/></>
<><input type="radio" {...__sveltets_1_empty(element = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('input')))/*Ωignore_endΩ*/)} value="Plain"/></>
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

<Component {/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{...__sveltets_1_empty((bar = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/))} {/**
╚{...__sveltets_1_empty((bar•=•/*Ωignore_startΩ*/new•Component({target:•__sveltets_1_any(''),•props:•__sveltets_1_any('')})/*Ωignore_endΩ*/))}↲ [generated] line 18
╚b bar}
{...__sveltets_1_empty(bar = /*Ωignore_startΩ*/new Component({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/)} {/**
╚{...__sveltets_1_empty(bar•=•/*Ωignore_startΩ*/new•Component({target:•__sveltets_1_any(''),•props:•__sveltets_1_any('')})/*Ωignore_endΩ*/)}↲ [generated] line 18
╚b bar}
╚b bar}↲
╚bind:this={bar}↲ [original] line 16
╚bind:this={bar}↲ [original] line 16
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
/></> {/**
/></>↲ [generated] line 19
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,11 @@ s
</div>
{/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
<div class="chapter-markup" {...__sveltets_1_empty((scrollable = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/))}>{/**
╚╚╚<div•class="chapter-markup"•{...__sveltets_1_empty((scrollable•=•/*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/))}>↲ [generated] line 139
╚╚╚<div•c ="chapter-markup"•b scrollable} >↲
╚╚╚<div•c ="chapter-markup"•b scrollable}>↲
╚╚╚<div•class="chapter-markup"•bind:this={scrollable}>↲ [original] line 278
<div class="chapter-markup" {...__sveltets_1_empty(scrollable = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/)}>{/**
╚╚╚<div•class="chapter-markup"•{...__sveltets_1_empty(scrollable•=•/*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/)}>↲ [generated] line 139
╚╚╚<div•c ="chapter-markup"•b scrollable} >↲
╚╚╚<div•c ="chapter-markup"•b scrollable}>↲
╚╚╚<div•class="chapter-markup"•bind:this={scrollable}>↲ [original] line 278
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{ chapter.html} {/**
╚╚╚╚{•chapter.html}↲ [generated] line 140
Expand Down Expand Up @@ -400,11 +400,11 @@ s
<div class="tutorial-repl">
<Repl {/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
{...__sveltets_1_empty((repl = /*Ωignore_startΩ*/new Repl({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/))}{/**
╚╚╚╚{...__sveltets_1_empty((repl•=•/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•__sveltets_1_any('')})/*Ωignore_endΩ*/))}↲ [generated] line 163
╚╚╚╚b repl}
╚╚╚╚b repl}↲
╚╚╚╚bind:this={repl}↲ [original] line 303
{...__sveltets_1_empty(repl = /*Ωignore_startΩ*/new Repl({target: __sveltets_1_any(''), props: __sveltets_1_any('')})/*Ωignore_endΩ*/)}{/**
╚╚╚╚{...__sveltets_1_empty(repl•=•/*Ωignore_startΩ*/new•Repl({target:•__sveltets_1_any(''),•props:•__sveltets_1_any('')})/*Ωignore_endΩ*/)}↲ [generated] line 163
╚╚╚╚b repl} ↲
╚╚╚╚b repl}↲
╚╚╚╚bind:this={repl}↲ [original] line 303
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
workersUrl="workers" {/**
------------------------------------------------------------------------------------------------------------------------------------------------------ */}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
///<reference types="svelte" />
<></>;function render() {
<><div {...__sveltets_1_empty(__sveltets_1_store_get(compile_options), $compile_options=__sveltets_1_instanceOf(HTMLDivElement).offsetHeight)} />
<div {...__sveltets_1_empty((__sveltets_1_store_get(compile_options), $compile_options).foo=__sveltets_1_instanceOf(HTMLDivElement).offsetHeight)} />
<div {...__sveltets_1_empty(__sveltets_1_store_get(compile_options), $compile_options = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/)} />
<div {...__sveltets_1_empty((__sveltets_1_store_get(compile_options), $compile_options).foo = /*Ωignore_startΩ*/__sveltets_1_instanceOf(__sveltets_1_ctorOf(__sveltets_1_mapElementTag('div')))/*Ωignore_endΩ*/)} />
<div noAssignment={(__sveltets_1_store_get(compile_options), $compile_options)} />
<div noAssignment={(__sveltets_1_store_get(compile_options), $compile_options).foo} /></>
return { props: {}, slots: {}, getters: {}, events: {} }}

export default class Input__SvelteComponent_ extends __sveltets_1_createSvelte2TsxComponent(__sveltets_1_partial(__sveltets_1_with_any_event(render()))) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div bind:offsetHeight={$compile_options} />
<div bind:offsetHeight={$compile_options.foo} />
<div bind:this={$compile_options} />
<div bind:this={$compile_options.foo} />
<div bind:noAssignment={$compile_options} />
<div bind:noAssignment={$compile_options.foo} />
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
///<reference types="svelte" />
<></>;function render() {
/*Ωignore_startΩ*/;const __sveltets_ensureSlot = __sveltets_1_createEnsureSlot();/*Ωignore_endΩ*/
<><slot name="s" {...__sveltets_1_empty((s = /*Ωignore_startΩ*/__sveltets_1_instanceOf(HTMLSlotElement)/*Ωignore_endΩ*/))} /></>
<><slot name="s" {...__sveltets_1_empty(s = /*Ωignore_startΩ*/__sveltets_1_instanceOf(HTMLSlotElement)/*Ω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()))) {
Expand Down