From cbcbea392fed47fa7e5c8b975a139392ebf50cf2 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 24 Feb 2021 12:02:39 +0100 Subject: [PATCH] (feat) control flow for stores (#719) #493 $store --> (__svelte_store_get(store), $store) By using the comma operand we allow TypeScript's control flow to work. Before that, it was $store --> __svelte_store_get(store) which loses control flow info every time. findReferences and getDefinitions return one additional false positive reference (the svelte2tsx-added declaration) which is a TODO for later Co-authored-by: Simon Holthausen Co-authored-by: GrzegorzKazana --- package.json | 2 +- .../plugins/typescript/DocumentSnapshot.ts | 5 + .../features/DiagnosticsProvider.ts | 32 ++++++- .../typescript/features/RenameProvider.ts | 5 +- .../features/DiagnosticsProvider.test.ts | 82 ++++++++++++++++- .../features/FindReferencesProvider.test.ts | 76 ++++++++++++++++ .../typescript/features/HoverProvider.test.ts | 2 +- .../features/UpdateImportsProvider.test.ts | 4 +- .../diagnostics-$store-control-flow.svelte | 32 +++++++ .../diagnostics-$store.svelte | 0 .../diagnostics-coffeescript.svelte | 0 .../diagnostics-directive-types.svelte | 0 .../diagnostics-falsepositives.svelte | 0 .../diagnostics-js-notypecheck.svelte | 0 .../diagnostics-js-typecheck.svelte | 0 .../diagnostics-module.svelte | 0 .../diagnostics-parsererror.svelte | 0 .../diagnostics-slots-imported.svelte | 0 .../diagnostics-slots.svelte | 0 .../{ => diagnostics}/diagnostics-tag.svelte | 0 .../{ => diagnostics}/diagnostics.svelte | 0 .../testfiles/diagnostics/tsconfig.json | 10 ++ .../testfiles/find-references-$store.svelte | 8 ++ .../typescript/testfiles/updateimports.svelte | 2 +- packages/svelte2tsx/package.json | 2 +- packages/svelte2tsx/src/svelte2tsx/index.ts | 13 ++- .../svelte2tsx/nodes/ImplicitStoreValues.ts | 91 +++++++++++++++++++ .../svelte2tsx/nodes/ImplicitTopLevelNames.ts | 21 +---- .../svelte2tsx/src/svelte2tsx/nodes/Stores.ts | 34 +++---- .../processInstanceScriptContent.ts | 29 ++++-- .../svelte2tsx/src/svelte2tsx/utils/Scope.ts | 4 + .../svelte2tsx/src/svelte2tsx/utils/tsAst.ts | 21 ++++- .../test/sourcemaps/event-binding.html | 8 +- .../samples/$store-index/expected.tsx | 2 +- .../samples/ast-offset-none/expected.tsx | 2 +- .../samples/ast-offset-some/expected.tsx | 2 +- .../samples/await-with-$store/expected.tsx | 4 +- .../samples/binding-group-store/expected.tsx | 2 +- .../samples/debug-block/expected.tsx | 4 +- .../nested-$-variables-script/expected.tsx | 12 +-- .../nested-$-variables-template/expected.tsx | 10 +- .../nested-$-variables-template/input.svelte | 2 + .../expected.tsx | 16 ++-- .../samples/reactive-store-set/expected.tsx | 2 +- .../samples/store-destructuring/expected.tsx | 17 ++++ .../samples/store-destructuring/input.svelte | 10 ++ .../expected.tsx | 15 +++ .../input.svelte | 8 ++ .../samples/store-import/expected.tsx | 20 ++++ .../samples/store-import/input.svelte | 9 ++ .../store-property-access/expected.tsx | 28 ++++++ .../store-property-access/input.svelte | 21 +++++ .../samples/stores-mustache/expected.tsx | 2 +- .../uses-$store-in-event-binding/expected.tsx | 4 +- .../expected.tsx | 50 +++++----- .../expected.tsx | 6 +- .../uses-$store-with-increments/expected.tsx | 12 +-- .../samples/uses-$store/expected.tsx | 4 +- yarn.lock | 8 +- 59 files changed, 620 insertions(+), 135 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store-control-flow.svelte rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-$store.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-coffeescript.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-directive-types.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-falsepositives.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-js-notypecheck.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-js-typecheck.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-module.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-parsererror.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-slots-imported.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-slots.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics-tag.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => diagnostics}/diagnostics.svelte (100%) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json create mode 100644 packages/language-server/test/plugins/typescript/testfiles/find-references-$store.svelte create mode 100644 packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-import/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-import/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/input.svelte diff --git a/package.json b/package.json index 89e8ab517..e83e15893 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "prettier --check . && eslint \"packages/**/*.{ts,js}\"" }, "dependencies": { - "typescript": "^4.1.3" + "typescript": "^4.2.2" }, "devDependencies": { "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.2.0", diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 7214b2613..0a01a7970 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -229,6 +229,11 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { return positionAt(offset, this.text); } + getLineContainingOffset(offset: number) { + const chunks = this.getText(0, offset).split('\n'); + return chunks[chunks.length - 1]; + } + hasProp(name: string): boolean { return this.exportedNames.has(name); } diff --git a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts index e08329104..50194860a 100644 --- a/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/DiagnosticsProvider.ts @@ -50,7 +50,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider { })) .map((diagnostic) => mapObjWithRangeToOriginal(fragment, diagnostic)) .filter(hasNoNegativeLines) - .filter(isNoFalsePositive(document.getText(), tsDoc)) + .filter(isNoFalsePositive(document.getText(), tsDoc, diagnostics)) .map(enhanceIfNecessary) .map(swapRangeStartEndIfNecessary); } @@ -80,16 +80,40 @@ function hasNoNegativeLines(diagnostic: Diagnostic): boolean { return diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0; } -function isNoFalsePositive(text: string, tsDoc: SvelteDocumentSnapshot) { - return (diagnostic: Diagnostic) => { +function isNoFalsePositive( + text: string, + tsDoc: SvelteDocumentSnapshot, + rawTsDiagnostics: ts.Diagnostic[] +) { + return (diagnostic: Diagnostic, idx: number) => { return ( isNoJsxCannotHaveMultipleAttrsError(diagnostic) && isNoUnusedLabelWarningForReactiveStatement(diagnostic) && - isNoUsedBeforeAssigned(diagnostic, text, tsDoc) + isNoUsedBeforeAssigned(diagnostic, text, tsDoc) && + isNotHiddenStoreValueDeclaration(diagnostic, tsDoc, rawTsDiagnostics[idx]) ); }; } +/** + * During compilation to tsx, for each store we create an additional variable + * called `$` which contains the store value. + * This variable declaration does not show up in the sourcemaps. + * We have to ignore the error if the variable prefixed by `$` was not a store. + */ +function isNotHiddenStoreValueDeclaration( + diagnostic: Diagnostic, + tsDoc: SvelteDocumentSnapshot, + rawTsDiagnostic: ts.Diagnostic +): boolean { + if (diagnostic.code !== 2345 || !rawTsDiagnostic.start) return true; + + const affectedLine = tsDoc.getLineContainingOffset(rawTsDiagnostic.start); + const hasStoreValueDefinition = /let \$[\w$]+ = __sveltets_store_get\(/.test(affectedLine); + + return !hasStoreValueDefinition; +} + /** * Variable used before being assigned, can happen when you do `export let x` * without assigning a value in strict mode. Should not throw an error here diff --git a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts index 2295b4f07..3216b4e3a 100644 --- a/packages/language-server/src/plugins/typescript/features/RenameProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/RenameProvider.ts @@ -327,8 +327,11 @@ export class RenameProviderImpl implements RenameProvider { // When the user renames a Svelte component, ts will also want to rename // `__sveltets_instanceOf(TheComponentToRename)` or // `__sveltets_ensureType(TheComponentToRename,..`. Prevent that. + // Additionally, we cannot rename the hidden variable containing the store value return ( - notPrecededBy('__sveltets_instanceOf(') && notPrecededBy('__sveltets_ensureType(') + notPrecededBy('__sveltets_instanceOf(') && + notPrecededBy('__sveltets_ensureType(') && + notPrecededBy('= __sveltets_store_get(') ); function notPrecededBy(str: string) { 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 4a7a2027c..f8427917d 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -7,7 +7,7 @@ import { DiagnosticsProviderImpl } from '../../../../src/plugins/typescript/feat import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver'; import { pathToUrl } from '../../../../src/utils'; -const testDir = path.join(__dirname, '..', 'testfiles'); +const testDir = path.join(__dirname, '..', 'testfiles', 'diagnostics'); describe('DiagnosticsProvider', () => { function setup(filename: string) { @@ -234,7 +234,7 @@ describe('DiagnosticsProvider', () => { assert.deepStrictEqual(diagnostics, [ { code: 6385, - message: "'a' is deprecated", + message: "'a' is deprecated.", range: { end: { character: 5, @@ -399,4 +399,82 @@ describe('DiagnosticsProvider', () => { } ]); }); + + it('$store control flow', async () => { + const { plugin, document } = setup('diagnostics-$store-control-flow.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + + assert.deepStrictEqual(diagnostics, [ + { + code: 2367, + message: + "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.", + range: { + start: { + line: 9, + character: 40 + }, + end: { + line: 9, + character: 57 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2322, + message: "Type 'string' is not assignable to type 'boolean'.", + range: { + start: { + line: 15, + character: 12 + }, + end: { + line: 15, + character: 16 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2367, + message: + "This condition will always return 'false' since the types 'string' and 'boolean' have no overlap.", + range: { + start: { + line: 23, + character: 41 + }, + end: { + line: 23, + character: 58 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2322, + message: "Type 'string' is not assignable to type 'boolean'.", + range: { + start: { + line: 28, + character: 13 + }, + end: { + line: 28, + character: 17 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts b/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts index 93109f069..44cb68133 100644 --- a/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/FindReferencesProvider.test.ts @@ -80,4 +80,80 @@ describe('FindReferencesProvider', () => { it('finds references (not searching from declaration)', async () => { await test(Position.create(2, 8), true); }); + + it('finds references for $store', async () => { + const { provider, document } = setup('find-references-$store.svelte'); + + const results = await provider.findReferences(document, Position.create(2, 10), { + includeDeclaration: true + }); + assert.deepStrictEqual(results, [ + { + range: { + end: { + character: 16, + line: 1 + }, + start: { + character: 10, + line: 1 + } + }, + uri: getUri('find-references-$store.svelte') + }, + // TODO this one should be filtered out + { + range: { + end: { + character: 30, + line: 1 + }, + start: { + character: 30, + line: 1 + } + }, + uri: getUri('find-references-$store.svelte') + }, + { + range: { + end: { + character: 15, + line: 2 + }, + start: { + character: 9, + line: 2 + } + }, + uri: getUri('find-references-$store.svelte') + }, + { + range: { + end: { + character: 15, + line: 3 + }, + start: { + character: 9, + line: 3 + } + }, + uri: getUri('find-references-$store.svelte') + }, + { + range: { + end: { + character: 8, + line: 7 + }, + start: { + character: 2, + line: 7 + } + }, + uri: getUri('find-references-$store.svelte') + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts index 7e0bfce16..0662ef331 100644 --- a/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/HoverProvider.test.ts @@ -87,7 +87,7 @@ describe('HoverProvider', () => { const { provider, document } = setup('hoverinfo.svelte'); assert.deepStrictEqual(await provider.doHover(document, Position.create(9, 10)), { - contents: '```typescript\nconst withJsDocTag: true\n```\n---\n\n\n*@author* — foo', + contents: '```typescript\nconst withJsDocTag: true\n```\n---\n\n\n*@author* — foo ', range: { start: { character: 10, diff --git a/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts index 43b590b36..b460a8b73 100644 --- a/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/UpdateImportsProvider.test.ts @@ -46,14 +46,14 @@ describe('UpdateImportsProviderImpl', () => { const workspaceEdit = await updateImportsProvider.updateImports({ // imported files both old and new have to actually exist, so we just use some other test files - oldUri: pathToUrl(join(testFilesDir, 'diagnostics.svelte')), + oldUri: pathToUrl(join(testFilesDir, 'diagnostics', 'diagnostics.svelte')), newUri: pathToUrl(join(testFilesDir, 'documentation.svelte')) }); assert.deepStrictEqual(workspaceEdit?.documentChanges, [ TextDocumentEdit.create(VersionedTextDocumentIdentifier.create(fileUri, 0), [ TextEdit.replace( - Range.create(Position.create(1, 17), Position.create(1, 37)), + Range.create(Position.create(1, 17), Position.create(1, 49)), './documentation.svelte' ) ]) diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store-control-flow.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store-control-flow.svelte new file mode 100644 index 000000000..fac37c0f9 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store-control-flow.svelte @@ -0,0 +1,32 @@ + + +{#if $store} + {#if typeof $store.a === 'string'} + {test = $store.a === 'string' || $store.a === true} + {:else} + {#if isBoolean($store.a.b)} + {test = $store.a.b} + {:else} + {test = $store.a.b} + {/if} + {/if} +{/if} diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-$store.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-$store.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$store.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-coffeescript.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-coffeescript.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-coffeescript.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-coffeescript.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-directive-types.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-directive-types.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-directive-types.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-directive-types.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-falsepositives.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-falsepositives.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-falsepositives.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-js-notypecheck.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-js-notypecheck.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-js-notypecheck.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-js-notypecheck.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-js-typecheck.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-js-typecheck.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-js-typecheck.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-js-typecheck.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-module.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-module.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-module.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-module.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-parsererror.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-parsererror.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-parsererror.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-parsererror.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-slots-imported.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-slots-imported.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-slots-imported.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-slots-imported.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-slots.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-slots.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-slots.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-slots.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics-tag.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-tag.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics-tag.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-tag.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/diagnostics.svelte rename to packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json new file mode 100644 index 000000000..ccbc2ad98 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + /** + This is actually not needed, but makes the tests faster + because TS does not look up other types. + */ + "types": ["svelte"] + } +} diff --git a/packages/language-server/test/plugins/typescript/testfiles/find-references-$store.svelte b/packages/language-server/test/plugins/typescript/testfiles/find-references-$store.svelte new file mode 100644 index 000000000..5a6f9090e --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/find-references-$store.svelte @@ -0,0 +1,8 @@ + + +{$findMe} diff --git a/packages/language-server/test/plugins/typescript/testfiles/updateimports.svelte b/packages/language-server/test/plugins/typescript/testfiles/updateimports.svelte index 45648e54a..be6edb548 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/updateimports.svelte +++ b/packages/language-server/test/plugins/typescript/testfiles/updateimports.svelte @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/packages/svelte2tsx/package.json b/packages/svelte2tsx/package.json index 4b68a5064..085eda340 100644 --- a/packages/svelte2tsx/package.json +++ b/packages/svelte2tsx/package.json @@ -35,7 +35,7 @@ "svelte": "~3.32.1", "tiny-glob": "^0.2.6", "tslib": "^1.10.0", - "typescript": "^4.1.3" + "typescript": "^4.2.2" }, "peerDependencies": { "svelte": "^3.24", diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index a4a320af7..d83f35efa 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -13,6 +13,7 @@ import { handleScopeAndResolveForSlot, handleScopeAndResolveLetVarForSlot } from './nodes/handleScopeAndResolveForSlot'; +import { ImplicitStoreValues } from './nodes/ImplicitStoreValues'; import { Scripts } from './nodes/Scripts'; import { SlotHandler } from './nodes/slot'; import { Stores } from './nodes/Stores'; @@ -58,6 +59,7 @@ type TemplateProcessResult = { /** To be added later as a comment on the default class export */ componentDocumentation: ComponentDocumentation; events: ComponentEvents; + resolvedStores: string[]; }; /** @@ -260,7 +262,7 @@ function processSvelteTemplate( scripts.blankOtherScriptTags(str); //resolve stores - stores.resolveStores(); + const resolvedStores = stores.resolveStores(); return { moduleScriptTag, @@ -270,7 +272,8 @@ function processSvelteTemplate( uses$$props, uses$$restProps, uses$$slots, - componentDocumentation + componentDocumentation, + resolvedStores }; } @@ -431,7 +434,8 @@ export function svelte2tsx( uses$$slots, uses$$restProps, events, - componentDocumentation + componentDocumentation, + resolvedStores } = processSvelteTemplate(str, options); /* Rearrange the script tags so that module is first, and instance second followed finally by the template @@ -454,12 +458,13 @@ export function svelte2tsx( //move the instance script and process the content let exportedNames = new ExportedNames(); let getters = new Set(); + const implicitStoreValues = new ImplicitStoreValues(resolvedStores); if (scriptTag) { //ensure it is between the module script and the rest of the template (the variables need to be declared before the jsx template) if (scriptTag.start != instanceScriptTarget) { str.move(scriptTag.start, scriptTag.end, instanceScriptTarget); } - const res = processInstanceScriptContent(str, scriptTag, events); + const res = processInstanceScriptContent(str, scriptTag, events, implicitStoreValues); uses$$props = uses$$props || res.uses$$props; uses$$restProps = uses$$restProps || res.uses$$restProps; uses$$slots = uses$$slots || res.uses$$slots; diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts new file mode 100644 index 000000000..9f77a6a40 --- /dev/null +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitStoreValues.ts @@ -0,0 +1,91 @@ +import MagicString from 'magic-string'; +import ts from 'typescript'; +import { extractIdentifiers, getNamesFromLabeledStatement } from '../utils/tsAst'; + +/** + * Tracks all store-usages as well as all variable declarations and imports in the component. + * + * In the modification-step at the end, all variable declartaions and imports which + * were used as stores are appended with `let $xx = __sveltets_store_get(xx)` to create the store variables. + */ +export class ImplicitStoreValues { + private accessedStores = new Set(); + private variableDeclarations: ts.VariableDeclaration[] = []; + private reactiveDeclarations: ts.LabeledStatement[] = []; + private importStatements: Array = []; + + public addStoreAcess = this.accessedStores.add.bind(this.accessedStores); + public addVariableDeclaration = this.variableDeclarations.push.bind(this.variableDeclarations); + public addReactiveDeclaration = this.reactiveDeclarations.push.bind(this.reactiveDeclarations); + public addImportStatement = this.importStatements.push.bind(this.importStatements); + + constructor(storesResolvedInTemplate: string[] = []) { + storesResolvedInTemplate.forEach(this.addStoreAcess); + } + + /** + * All variable declartaions and imports which + * were used as stores are appended with `let $xx = __sveltets_store_get(xx)` to create the store variables. + */ + public modifyCode(astOffset: number, str: MagicString) { + this.variableDeclarations.forEach((node) => + this.attachStoreValueDeclarationToDecl(node, astOffset, str) + ); + + this.reactiveDeclarations.forEach((node) => + this.attachStoreValueDeclarationToReactiveAssignment(node, astOffset, str) + ); + + this.importStatements + .filter(({ name }) => name && this.accessedStores.has(name.getText())) + .forEach((node) => this.attachStoreValueDeclarationToImport(node, astOffset, str)); + } + + private attachStoreValueDeclarationToDecl( + node: ts.VariableDeclaration, + astOffset: number, + str: MagicString + ) { + const storeNames = extractIdentifiers(node.name) + .map((id) => id.text) + .filter((name) => this.accessedStores.has(name)); + + let toAppend = ''; + for (let i = 0; i < storeNames.length; i++) { + toAppend += `;let $${storeNames[i]} = __sveltets_store_get(${storeNames[i]});`; + } + + const endPos = node.getEnd() + astOffset; + str.appendRight(endPos, toAppend); + } + + private attachStoreValueDeclarationToReactiveAssignment( + node: ts.LabeledStatement, + astOffset: number, + str: MagicString + ) { + const storeNames = getNamesFromLabeledStatement(node).filter((name) => + this.accessedStores.has(name) + ); + + let toAppend = ''; + for (let i = 0; i < storeNames.length; i++) { + toAppend += `;let $${storeNames[i]} = __sveltets_store_get(${storeNames[i]});`; + } + + const endPos = node.getEnd() + astOffset; + str.appendRight(endPos, toAppend); + } + + private attachStoreValueDeclarationToImport( + node: ts.ImportClause | ts.ImportSpecifier, + astOffset: number, + str: MagicString + ) { + const storeName = node.name.getText(); + const importStatement = ts.isImportClause(node) ? node.parent : node.parent.parent.parent; + + const endPos = importStatement.getEnd() + astOffset; + str.appendRight(endPos, `;let $${storeName} = __sveltets_store_get(${storeName});`); + } +} diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts index 5c4e3ad32..280d31ec5 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ImplicitTopLevelNames.ts @@ -1,9 +1,8 @@ import ts from 'typescript'; import MagicString from 'magic-string'; import { - getBinaryAssignmentExpr, - extractIdentifiers, - isParenthesizedObjectOrArrayLiteralExpression + isParenthesizedObjectOrArrayLiteralExpression, + getNamesFromLabeledStatement } from '../utils/tsAst'; export class ImplicitTopLevelNames { @@ -15,7 +14,7 @@ export class ImplicitTopLevelNames { modifyCode(rootVariables: Set, astOffset: number, str: MagicString) { for (const node of this.map.values()) { - const names = this.getNames(node); + const names = getNamesFromLabeledStatement(node); if (names.length === 0) { continue; } @@ -37,20 +36,6 @@ export class ImplicitTopLevelNames { } } - private getNames(node: ts.LabeledStatement) { - const leftHandSide = getBinaryAssignmentExpr(node)?.left; - if (!leftHandSide) { - return []; - } - - return ( - extractIdentifiers(leftHandSide) - .map((id) => id.text) - // svelte won't let you create a variable with $ prefix (reserved for stores) - .filter((name) => !name.startsWith('$')) - ); - } - private hasOnlyImplicitTopLevelNames(names: string[], implicitTopLevelNames: string[]) { return names.length === implicitTopLevelNames.length; } diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts index cc25e1fdf..8ae55e78f 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/Stores.ts @@ -4,6 +4,7 @@ import { ScopeStack, Scope } from '../utils/Scope'; import { isObjectKey, isMember } from '../../utils/svelteAst'; 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); @@ -25,7 +26,7 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { str.overwrite( parent.start, str.original.indexOf('=', node.end) + 1, - `${storename}.set( __sveltets_store_get(${storename}) ${operator}` + `${storename}.set( $${storename} ${operator}` ); str.appendLeft(parent.end, ')'); return; @@ -40,7 +41,7 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { str.overwrite( parent.start, parent.end, - `${storename}.set( __sveltets_store_get(${storename}) ${simpleOperator} 1)` + `${storename}.set( $${storename} ${simpleOperator} 1)` ); } else { console.warn( @@ -54,10 +55,12 @@ export function handleStore(node: Node, parent: Node, str: MagicString): void { return; } - //rewrite get + // we change "$store" references into "(__sveltets_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_store_get('); - str.prependLeft(node.end, ')'); + str.overwrite(dollar, dollar + 1, '(__sveltets_store_get('); + str.prependLeft(node.end, `), $${storename})`); } type PendingStoreResolution = { @@ -99,19 +102,16 @@ export class Stores { } } - resolveStores(): void { - this.pendingStoreResolutions.forEach((pending) => { - let { node, parent, scope } = pending; + resolveStores(): string[] { + const unresolvedStores = this.pendingStoreResolutions.filter(({ node, scope }) => { const name = node.name; - while (scope) { - if (scope.declared.has(name)) { - //we were manually declared, this isn't a store access. - return; - } - scope = scope.parent; - } - //We haven't been resolved, we must be a store read/write, handle it. - handleStore(node, parent, this.str); + // if variable starting with '$' was manually declared by the user, + // this isn't a store access. + return !scope.hasDefined(name); }); + + unresolvedStores.forEach(({ node, parent }) => handleStore(node, parent, this.str)); + + return unresolvedStores.map(({ node }) => node.name.slice(1)); } } diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index ba072ee31..f7b76a5ae 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -11,6 +11,7 @@ import { ImplicitTopLevelNames } from './nodes/ImplicitTopLevelNames'; import { ComponentEvents } from './nodes/ComponentEvents'; import { Scope } from './utils/Scope'; import { handleTypeAssertion } from './nodes/handleTypeAssertion'; +import { ImplicitStoreValues } from './nodes/ImplicitStoreValues'; export interface InstanceScriptProcessResult { exportedNames: ExportedNames; @@ -30,7 +31,8 @@ type PendingStoreResolution = { export function processInstanceScriptContent( str: MagicString, script: Node, - events: ComponentEvents + events: ComponentEvents, + implicitStoreValues: ImplicitStoreValues ): InstanceScriptProcessResult { const htmlx = str.original; const scriptContent = htmlx.substring(script.content.start, script.content.end); @@ -139,6 +141,7 @@ export function processInstanceScriptContent( }; const handleStore = (ident: ts.Node, parent: ts.Node) => { + const storename = ident.getText().slice(1); // drop the $ // handle assign to // eslint-disable-next-line max-len if ( @@ -181,12 +184,11 @@ export function processInstanceScriptContent( parent.left == ident && Object.keys(operators).find((x) => x === String(parent.operatorToken.kind)) ) { - const storename = ident.getText().slice(1); // drop the $ const operator = operators[parent.operatorToken.kind]; str.overwrite( parent.getStart() + astOffset, str.original.indexOf('=', ident.end + astOffset) + 1, - `${storename}.set( __sveltets_store_get(${storename}) ${operator}` + `${storename}.set( $${storename} ${operator}` ); str.appendLeft(parent.end + astOffset, ')'); return; @@ -206,11 +208,10 @@ export function processInstanceScriptContent( } if (simpleOperator) { - const storename = ident.getText().slice(1); // drop the $ str.overwrite( parent.getStart() + astOffset, parent.end + astOffset, - `${storename}.set( __sveltets_store_get(${storename}) ${simpleOperator} 1)` + `${storename}.set( $${storename} ${simpleOperator} 1)` ); return; } else { @@ -224,10 +225,12 @@ export function processInstanceScriptContent( } } - // we must be on the right or not part of assignment + // we change "$store" references into "(__sveltets_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('$', ident.getStart() + astOffset); - str.overwrite(dollar, dollar + 1, '__sveltets_store_get('); - str.appendLeft(ident.end + astOffset, ')'); + str.overwrite(dollar, dollar + 1, '(__sveltets_store_get('); + str.prependLeft(ident.end + astOffset, `), $${storename})`); }; const resolveStore = (pending: PendingStoreResolution) => { @@ -242,6 +245,8 @@ export function processInstanceScriptContent( } //We haven't been resolved, we must be a store read/write, handle it. handleStore(node, parent); + const storename = node.getText().slice(1); + implicitStoreValues.addStoreAcess(storename); }; const handleIdentifier = (ident: ts.Identifier, parent: ts.Node) => { @@ -404,6 +409,7 @@ export function processInstanceScriptContent( if (ts.isVariableDeclaration(node)) { events.checkIfIsStringLiteralDeclaration(node); events.checkIfDeclarationInstantiatedEventDispatcher(node); + implicitStoreValues.addVariableDeclaration(node); } if (ts.isCallExpression(node)) { @@ -423,6 +429,11 @@ export function processInstanceScriptContent( if (ts.isImportClause(node)) { isDeclaration = true; onLeaveCallbacks.push(() => (isDeclaration = false)); + implicitStoreValues.addImportStatement(node); + } + + if (ts.isImportSpecifier(node)) { + implicitStoreValues.addImportStatement(node); } //handle stores etc @@ -440,6 +451,7 @@ export function processInstanceScriptContent( const binaryExpression = getBinaryAssignmentExpr(node); if (binaryExpression) { implicitTopLevelNames.add(node); + implicitStoreValues.addReactiveDeclaration(node); wrapExpressionWithInvalidate(binaryExpression.right); } else { const start = node.getStart() + astOffset; @@ -470,6 +482,7 @@ export function processInstanceScriptContent( // declare implicit reactive variables we found in the script implicitTopLevelNames.modifyCode(rootScope.declared, astOffset, str); + implicitStoreValues.modifyCode(astOffset, str); const firstImport = tsAst.statements .filter(ts.isImportDeclaration) diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/Scope.ts b/packages/svelte2tsx/src/svelte2tsx/utils/Scope.ts index 675b3d599..75e780a47 100644 --- a/packages/svelte2tsx/src/svelte2tsx/utils/Scope.ts +++ b/packages/svelte2tsx/src/svelte2tsx/utils/Scope.ts @@ -5,6 +5,10 @@ export class Scope { constructor(parent?: Scope) { this.parent = parent; } + + hasDefined(name: string) { + return this.declared.has(name) || (!!this.parent && this.parent.hasDefined(name)); + } } export class ScopeStack { diff --git a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts index 79460135c..f52b708fc 100644 --- a/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts +++ b/packages/svelte2tsx/src/svelte2tsx/utils/tsAst.ts @@ -60,6 +60,8 @@ export function extractIdentifiers( ): ts.Identifier[] { if (ts.isIdentifier(node)) { identifiers.push(node); + } else if (ts.isBindingElement(node)) { + extractIdentifiers(node.name, identifiers); } else if (isMember(node)) { let object: ts.Node = node; while (isMember(object)) { @@ -70,7 +72,7 @@ export function extractIdentifiers( } } else if (ts.isArrayBindingPattern(node) || ts.isObjectBindingPattern(node)) { node.elements.forEach((element) => { - extractIdentifiers(element); + extractIdentifiers(element, identifiers); }); } else if (ts.isObjectLiteralExpression(node)) { node.properties.forEach((child) => { @@ -155,3 +157,20 @@ export function isNotPropertyNameOfImport(identifier: ts.Identifier): boolean { !ts.isImportSpecifier(identifier.parent) || identifier.parent.propertyName !== identifier ); } + +/** + * Extract the variable names that are assigned to out of a labeled statement. + */ +export function getNamesFromLabeledStatement(node: ts.LabeledStatement): string[] { + const leftHandSide = getBinaryAssignmentExpr(node)?.left; + if (!leftHandSide) { + return []; + } + + return ( + extractIdentifiers(leftHandSide) + .map((id) => id.text) + // svelte won't let you create a variable with $ prefix (reserved for stores) + .filter((name) => !name.startsWith('$')) + ); +} diff --git a/packages/svelte2tsx/test/sourcemaps/event-binding.html b/packages/svelte2tsx/test/sourcemaps/event-binding.html index 3055c47c1..e0ada3d18 100644 --- a/packages/svelte2tsx/test/sourcemaps/event-binding.html +++ b/packages/svelte2tsx/test/sourcemaps/event-binding.html @@ -1,9 +1,9 @@ /// <>;function render() { -<>{__sveltets_instanceOf(Component).$on('click', __sveltets_store_get(check) ? method1 : method2)} - 1==== 2================== - - 3==== 4================== +<>{__sveltets_instanceOf(Component).$on('click', (__sveltets_store_get(check), $check) ? method1 : method2)} + 1==== 2================== + + 3==== 4================== return { props: {}, slots: {}, getters: {}, events: {} }} export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/$store-index/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/$store-index/expected.tsx index e86486d4f..3fe6fdfe8 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/$store-index/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/$store-index/expected.tsx @@ -1,6 +1,6 @@ /// <>;function render() { -<>{someRecordOrArr[__sveltets_store_get(store)]} +<>{someRecordOrArr[(__sveltets_store_get(store), $store)]} {someObject['$store']} {someObject.$store} return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-none/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-none/expected.tsx index d1f45385d..dc9e78ec3 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-none/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-none/expected.tsx @@ -1,6 +1,6 @@ /// <>;function render() { -__sveltets_store_get(var); +(__sveltets_store_get(var), $var); () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-some/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-some/expected.tsx index 89365c738..7c9de5f63 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-some/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ast-offset-some/expected.tsx @@ -1,6 +1,6 @@ /// <>;function render() { - __sveltets_store_get(var); + (__sveltets_store_get(var), $var); () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/await-with-$store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/await-with-$store/expected.tsx index 94380a093..a9c333090 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/await-with-$store/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/await-with-$store/expected.tsx @@ -4,11 +4,11 @@ import { readable } from 'svelte/store'; function render() { - const store = readable(Promise.resolve('test'), () => {}); + const store = readable(Promise.resolve('test'), () => {});let $store = __sveltets_store_get(store);; ; () => (<> -{() => {let _$$p = (__sveltets_store_get(store)); <> +{() => {let _$$p = ((__sveltets_store_get(store), $store)); <>

loading

; __sveltets_awaitThen(_$$p, (data) => {<> {data} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/binding-group-store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/binding-group-store/expected.tsx index acc13685a..6d2643817 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/binding-group-store/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/binding-group-store/expected.tsx @@ -1,6 +1,6 @@ /// <>;function render() { -<> +<> return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/debug-block/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/debug-block/expected.tsx index 5fafc4ed0..12c868806 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/debug-block/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/debug-block/expected.tsx @@ -1,8 +1,8 @@ /// <>;function render() { <>{myfile} -{__sveltets_store_get(myfile)}{someOtherFile} -{myfile}{__sveltets_store_get(someOtherFile)}{someThirdFile} +{(__sveltets_store_get(myfile), $myfile)}{someOtherFile} +{myfile}{(__sveltets_store_get(someOtherFile), $someOtherFile)}{someThirdFile} return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx index 95949af67..45ef55888 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx @@ -1,13 +1,13 @@ /// <>;function render() { -let top1 = someStore() -let top2 = someStore() -let topLevelGet = __sveltets_store_get(top1) -topLevelGet = __sveltets_store_get(top2) +let top1 = someStore();let $top1 = __sveltets_store_get(top1); +let top2 = someStore();let $top2 = __sveltets_store_get(top2); +let topLevelGet = (__sveltets_store_get(top1), $top1) +topLevelGet = (__sveltets_store_get(top2), $top2) function test(top1) { - let passedGet = __sveltets_store_get(top1) + let passedGet = (__sveltets_store_get(top1), $top1) } function test2($top1) { @@ -19,7 +19,7 @@ function test3() { let letshadowed = $top2 } -const test4 = ({a, b: { $top1: $top2 }}) => $top2 && __sveltets_store_get(top1) +const test4 = ({a, b: { $top1: $top2 }}) => $top2 && (__sveltets_store_get(top1), $top1) ; () => (<>); diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx index c5d3a91d1..9e8701e07 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx @@ -2,13 +2,15 @@ <>;function render() { <>

{ + // TODO: this is invalid Svelte right now, stores have to be top level + // it's therefore okay to not append "let top1$/top2$ = __svelte_store_get(..)" let top1 = someStore() let top2 = someStore() - let topLevelGet = __sveltets_store_get(top1) - topLevelGet = __sveltets_store_get(top2) + let topLevelGet = (__sveltets_store_get(top1), $top1) + topLevelGet = (__sveltets_store_get(top2), $top2) function test(top1) { - let passedGet = __sveltets_store_get(top1) + let passedGet = (__sveltets_store_get(top1), $top1) } function test2($top1) { @@ -20,7 +22,7 @@ let letshadowed = $top2 } - const test4 = ({a, b: { $top1: $top2 }}) => $top2 && __sveltets_store_get(top1) + const test4 = ({a, b: { $top1: $top2 }}) => $top2 && (__sveltets_store_get(top1), $top1) }}>Hi

return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/input.svelte index 2494a0382..628768914 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/input.svelte @@ -1,5 +1,7 @@

{ + // TODO: this is invalid Svelte right now, stores have to be top level + // it's therefore okay to not append "let top1$/top2$ = __svelte_store_get(..)" let top1 = someStore() let top2 = someStore() let topLevelGet = $top1 diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx index 368fbd692..f301ffc62 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx @@ -1,21 +1,21 @@ /// <>;function render() { - let { count } = __sveltets_invalidate(() => __sveltets_store_get(data)); - let { count2 } = __sveltets_invalidate(() => __sveltets_store_get(data)) + let { count } = __sveltets_invalidate(() => (__sveltets_store_get(data), $data)); + let { count2 } = __sveltets_invalidate(() => (__sveltets_store_get(data), $data)) let count3; - $: ({ count3 } = __sveltets_invalidate(() => __sveltets_store_get(data))) + $: ({ count3 } = __sveltets_invalidate(() => (__sveltets_store_get(data), $data))) let bla4; let bla5; -$: ({ bla4, bla5 } = __sveltets_invalidate(() => __sveltets_store_get(data))) +$: ({ bla4, bla5 } = __sveltets_invalidate(() => (__sveltets_store_get(data), $data))) - let [ count ] = __sveltets_invalidate(() => __sveltets_store_get(data)); - let [ count2 ] = __sveltets_invalidate(() => __sveltets_store_get(data)) + let [ count ] = __sveltets_invalidate(() => (__sveltets_store_get(data), $data)); + let [ count2 ] = __sveltets_invalidate(() => (__sveltets_store_get(data), $data)) let count3; - $: ([ count3 ] = __sveltets_invalidate(() => __sveltets_store_get(data))) + $: ([ count3 ] = __sveltets_invalidate(() => (__sveltets_store_get(data), $data))) let bla4; let bla5; -$: ([ bla4, bla5 ] = __sveltets_invalidate(() => __sveltets_store_get(data))) +$: ([ bla4, bla5 ] = __sveltets_invalidate(() => (__sveltets_store_get(data), $data))) ; () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx index f058a4e05..5a5bb1e05 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx @@ -1,7 +1,7 @@ /// <>;function render() { - $: store.set( __sveltets_invalidate(() => __sveltets_store_get(store) + 1)); + $: store.set( __sveltets_invalidate(() => (__sveltets_store_get(store), $store) + 1)); ; () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/expected.tsx new file mode 100644 index 000000000..c98cc3000 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/expected.tsx @@ -0,0 +1,17 @@ +/// +<>;function render() { + + const store = fromSomewhere();let $store = __sveltets_store_get(store);; + const { store1, store2, noStore } = fromSomewhere();let $store1 = __sveltets_store_get(store1);;let $store2 = __sveltets_store_get(store2);; + const [ store3, store4, noStore ] = fromSomewhere();let $store3 = __sveltets_store_get(store3);;let $store4 = __sveltets_store_get(store4);; +; +() => (<> +

{(__sveltets_store_get(store), $store)}

+

{(__sveltets_store_get(store1), $store1)}

+

{(__sveltets_store_get(store2), $store2)}

+

{(__sveltets_store_get(store3), $store3)}

+

{(__sveltets_store_get(store4), $store4)}

); +return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/input.svelte new file mode 100644 index 000000000..14e1f2eaa --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-destructuring/input.svelte @@ -0,0 +1,10 @@ + +

{$store}

+

{$store1}

+

{$store2}

+

{$store3}

+

{$store4}

diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/expected.tsx new file mode 100644 index 000000000..d1775d526 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/expected.tsx @@ -0,0 +1,15 @@ +/// +<>;function render() { + + let store = __sveltets_invalidate(() => fromSomewhere());;let $store = __sveltets_store_get(store); + let { store1, noStore } = __sveltets_invalidate(() => fromSomewhere());;let $store1 = __sveltets_store_get(store1); + let [ store2, noStore ] = __sveltets_invalidate(() => fromSomewhere());;let $store2 = __sveltets_store_get(store2); +; +() => (<> +

{(__sveltets_store_get(store), $store)}

+

{(__sveltets_store_get(store1), $store1)}

+

{(__sveltets_store_get(store2), $store2)}

); +return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/input.svelte new file mode 100644 index 000000000..6f574515f --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-from-reactive-assignment/input.svelte @@ -0,0 +1,8 @@ + +

{$store}

+

{$store1}

+

{$store2}

diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-import/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/store-import/expected.tsx new file mode 100644 index 000000000..76cacc2c8 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-import/expected.tsx @@ -0,0 +1,20 @@ +/// +<>; +import storeA from './store'; +import { storeB } from './store'; +import { storeB as storeC } from './store'; +function render() { + + ;let $storeA = __sveltets_store_get(storeA); + ;let $storeB = __sveltets_store_get(storeB); + ;let $storeC = __sveltets_store_get(storeC); +; +() => (<> + +

{(__sveltets_store_get(storeA), $storeA)}

+

{(__sveltets_store_get(storeB), $storeB)}

+

{(__sveltets_store_get(storeC), $storeC)}

); +return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-import/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/store-import/input.svelte new file mode 100644 index 000000000..b729ad1ba --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-import/input.svelte @@ -0,0 +1,9 @@ + + +

{$storeA}

+

{$storeB}

+

{$storeC}

\ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/expected.tsx new file mode 100644 index 000000000..a53d42290 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/expected.tsx @@ -0,0 +1,28 @@ +/// +<>;function render() { + + const store = someStore();let $store = __sveltets_store_get(store);; + (__sveltets_store_get(store), $store); + (__sveltets_store_get(store), $store).prop; + (__sveltets_store_get(store), $store)['prop']; + (__sveltets_store_get(store), $store).prop.anotherProp; + (__sveltets_store_get(store), $store)['prop'].anotherProp; + (__sveltets_store_get(store), $store).prop['anotherProp']; + (__sveltets_store_get(store), $store)['prop']['anotherProp']; + (__sveltets_store_get(store), $store)?.prop.anotherProp; + (__sveltets_store_get(store), $store)?.prop?.anotherProp; +; +() => (<> +

{(__sveltets_store_get(store), $store)}

+

{(__sveltets_store_get(store), $store).prop}

+

{(__sveltets_store_get(store), $store)['prop']}

+

{(__sveltets_store_get(store), $store).prop.anotherProp}

+

{(__sveltets_store_get(store), $store)['prop'].anotherProp}

+

{(__sveltets_store_get(store), $store).prop['anotherProp']}

+

{(__sveltets_store_get(store), $store)['prop']['anotherProp']}

+

{(__sveltets_store_get(store), $store)?.prop.anotherProp}

+

{(__sveltets_store_get(store), $store)?.prop?.anotherProp}

); +return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/input.svelte new file mode 100644 index 000000000..4413d65d2 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/store-property-access/input.svelte @@ -0,0 +1,21 @@ + +

{$store}

+

{$store.prop}

+

{$store['prop']}

+

{$store.prop.anotherProp}

+

{$store['prop'].anotherProp}

+

{$store.prop['anotherProp']}

+

{$store['prop']['anotherProp']}

+

{$store?.prop.anotherProp}

+

{$store?.prop?.anotherProp}

\ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/stores-mustache/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/stores-mustache/expected.tsx index 5d9b4265a..4c1c271db 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/stores-mustache/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/stores-mustache/expected.tsx @@ -1,6 +1,6 @@ /// <>;function render() { -<> +<> return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-in-event-binding/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-in-event-binding/expected.tsx index 35b6a97e6..42dbe3256 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-in-event-binding/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-in-event-binding/expected.tsx @@ -1,7 +1,7 @@ /// <>;function render() { -<>{__sveltets_instanceOf(Component).$on('click', __sveltets_store_get(check) ? method1 : method2)} - +<>{__sveltets_instanceOf(Component).$on('click', (__sveltets_store_get(check), $check) ? method1 : method2)} + return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-assignment-operators/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-assignment-operators/expected.tsx index 1e4d1a4cc..7591035fc 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-assignment-operators/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-assignment-operators/expected.tsx @@ -4,35 +4,35 @@ import { writable } from 'svelte/store'; function render() { - const count = writable(0); + const count = writable(0);let $count = __sveltets_store_get(count);; let myvar = 42 // to show that this is different from ++ or -- - const handler1 = () => count.set( __sveltets_store_get(count) + myvar) - const handler2 = () => count.set( __sveltets_store_get(count) - myvar) - const handler3 = () => count.set( __sveltets_store_get(count) * myvar) - const handler4 = () => count.set( __sveltets_store_get(count) / myvar) - const handler5 = () => count.set( __sveltets_store_get(count) ** myvar) - const handler6 = () => count.set( __sveltets_store_get(count) % myvar) - const handler7 = () => count.set( __sveltets_store_get(count) << myvar) - const handler8 = () => count.set( __sveltets_store_get(count) >> myvar) - const handler9 = () => count.set( __sveltets_store_get(count) >>> myvar) - const handler10 = () => count.set( __sveltets_store_get(count) & myvar) - const handler11 = () => count.set( __sveltets_store_get(count) ^ myvar) - const handler12 = () => count.set( __sveltets_store_get(count) | myvar) + const handler1 = () => count.set( $count + myvar) + const handler2 = () => count.set( $count - myvar) + const handler3 = () => count.set( $count * myvar) + const handler4 = () => count.set( $count / myvar) + const handler5 = () => count.set( $count ** myvar) + const handler6 = () => count.set( $count % myvar) + const handler7 = () => count.set( $count << myvar) + const handler8 = () => count.set( $count >> myvar) + const handler9 = () => count.set( $count >>> myvar) + const handler10 = () => count.set( $count & myvar) + const handler11 = () => count.set( $count ^ myvar) + const handler12 = () => count.set( $count | myvar) ; () => (<> - - - - - - - - - - - -); + + + + + + + + + + + +); return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-exclamation-mark/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-exclamation-mark/expected.tsx index 4bdbff852..21002f655 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-exclamation-mark/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-exclamation-mark/expected.tsx @@ -4,12 +4,12 @@ import { writable } from 'svelte/store'; function render() { - const count = writable(0); - const handler1 = () => !__sveltets_store_get(count) + const count = writable(0);let $count = __sveltets_store_get(count);; + const handler1 = () => !(__sveltets_store_get(count), $count) ; () => (<> -); +); return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx index 623d32999..deabcdc8c 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx @@ -4,15 +4,15 @@ import { writable } from 'svelte/store'; function render() { - const count = writable(0); - const handler1 = () => count.set( __sveltets_store_get(count) + 1) - const handler2 = () => count.set( __sveltets_store_get(count) - 1) + const count = writable(0);let $count = __sveltets_store_get(count);; + const handler1 = () => count.set( $count + 1) + const handler2 = () => count.set( $count - 1) ; () => (<> - - -); + + +); return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store/expected.tsx index 506763fce..89f9466e1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store/expected.tsx @@ -1,8 +1,8 @@ /// <>;function render() { -b.set(__sveltets_store_get(b).concat(5)); +b.set((__sveltets_store_get(b), $b).concat(5)); () => (<> -

b.set(__sveltets_store_get(b).concat(5))}>{__sveltets_store_get(b)}

); +

b.set((__sveltets_store_get(b), $b).concat(5))}>{(__sveltets_store_get(b), $b)}

); return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/yarn.lock b/yarn.lock index 7f3210e7f..8f441c8e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2598,10 +2598,10 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typescript@*, typescript@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +typescript@*, typescript@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.2.tgz#1450f020618f872db0ea17317d16d8da8ddb8c4c" + integrity sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ== unist-util-stringify-position@^2.0.0: version "2.0.3"