From b49b87b1044eba7f094eeb93166aa84663073af1 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 14 Jun 2021 12:34:59 +0200 Subject: [PATCH] (feat) add $$Events and strictEvents support $$Events lets the user define all events that a component can dispatch. If createEventDispatcher is not typed, the $$Events definition is added to it under the hood for type checking. strictEvents can be used when the user is sure that there are no other events besides the one from createEventDispatcher and forwarded events - this removes the custom event fallback typing. #442 #424 https://github.com/sveltejs/rfcs/pull/38 --- .../features/DiagnosticsProvider.test.ts | 113 +++++++++++ .../component-events-interface.svelte | 2 +- .../testfiles/diagnostics/$$events.svelte | 17 ++ .../diagnostics/diagnostics-$$events.svelte | 8 + .../diagnostics-strictEvents.svelte | 8 + .../testfiles/diagnostics/strictEvents.svelte | 8 + .../hover/hover-events-interface.svelte | 2 +- packages/svelte2tsx/src/htmlxtojsx/index.ts | 2 +- packages/svelte2tsx/src/svelte2tsx/index.ts | 15 +- .../src/svelte2tsx/nodes/ComponentEvents.ts | 186 +++++++++++++----- .../processInstanceScriptContent.ts | 7 +- .../svelte2tsx/src/svelte2tsx/svelteShims.ts | 3 + packages/svelte2tsx/src/utils/htmlxparser.ts | 10 +- packages/svelte2tsx/svelte-shims.d.ts | 3 + .../expected.tsx | 4 +- .../input.svelte | 2 +- .../expected.tsx | 23 +++ .../input.svelte | 14 ++ .../expected.tsx | 4 +- .../input.svelte | 2 +- .../component-events-interface/expected.tsx | 4 +- .../component-events-interface/input.svelte | 2 +- .../expected.tsx | 17 ++ .../input.svelte | 8 + .../samples/creates-dts/expected.tsx | 3 + .../samples/ts-$$generics-dts/expected.tsx | 3 + .../samples/ts-creates-dts/expected.tsx | 3 + 27 files changed, 395 insertions(+), 78 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/$$events.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$$events.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-strictEvents.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/diagnostics/strictEvents.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-dispatcher/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-dispatcher/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-strictEvents/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-strictEvents/input.svelte 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 06c52476a..4f14095d7 100644 --- a/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/DiagnosticsProvider.test.ts @@ -1293,4 +1293,117 @@ describe('DiagnosticsProvider', () => { } ]); }); + + it('checks $$Events usage', async () => { + const { plugin, document } = setup('$$events.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + code: 2345, + message: + "Argument of type 'true' is not assignable to parameter of type 'string | undefined'.", + range: { + start: { + character: 20, + line: 12 + }, + end: { + character: 24, + line: 12 + } + }, + severity: 1, + source: 'ts', + tags: [] + }, + { + code: 2345, + message: + 'Argument of type \'"click"\' is not assignable to parameter of type \'"foo"\'.', + range: { + start: { + character: 13, + line: 13 + }, + end: { + character: 20, + line: 13 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); + + it('checks $$Events component usage', async () => { + const { plugin, document } = setup('diagnostics-$$events.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + code: 2345, + message: + // Note: If you only run this test, the test message is slightly different for some reason + 'Argument of type \'"bar"\' is not assignable to parameter of type \'"foo" | "click"\'.', + range: { + start: { + character: 10, + line: 7 + }, + end: { + character: 15, + line: 7 + } + }, + 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: { + character: 37, + line: 7 + }, + end: { + character: 54, + line: 7 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); + + it('checks strictEvents', async () => { + const { plugin, document } = setup('diagnostics-strictEvents.svelte'); + const diagnostics = await plugin.getDiagnostics(document); + assert.deepStrictEqual(diagnostics, [ + { + code: 2345, + message: + // Note: If you only run this test, the test message is slightly different for some reason + 'Argument of type \'"bar"\' is not assignable to parameter of type \'"foo" | "click"\'.', + range: { + start: { + character: 16, + line: 7 + }, + end: { + character: 21, + line: 7 + } + }, + severity: 1, + source: 'ts', + tags: [] + } + ]); + }); }); diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte index a87dc62b6..c7c3eeef0 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte @@ -1,5 +1,5 @@ + + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$$events.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$$events.svelte new file mode 100644 index 000000000..400adae57 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-$$events.svelte @@ -0,0 +1,8 @@ + + + + e} on:foo={e => e.detail === 'bar'} /> + + e} on:foo={e => e.detail === true} /> \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-strictEvents.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-strictEvents.svelte new file mode 100644 index 000000000..a5d3698fc --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/diagnostics-strictEvents.svelte @@ -0,0 +1,8 @@ + + + + e} on:click={e => e} /> + + e} /> \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/diagnostics/strictEvents.svelte b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/strictEvents.svelte new file mode 100644 index 000000000..416fc3437 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/diagnostics/strictEvents.svelte @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/hover/hover-events-interface.svelte b/packages/language-server/test/plugins/typescript/testfiles/hover/hover-events-interface.svelte index e2cd5ec82..1d5dbe66c 100644 --- a/packages/language-server/test/plugins/typescript/testfiles/hover/hover-events-interface.svelte +++ b/packages/language-server/test/plugins/typescript/testfiles/hover/hover-events-interface.svelte @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.tsx index 9ae3ad44e..399a6acdb 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.tsx @@ -1,7 +1,7 @@ /// <>;function render() { - interface ComponentEvents { + interface $$Events { /** * Some doc */ @@ -11,7 +11,7 @@ } ; () => (<>); -return { props: {}, slots: {}, getters: {}, events: {} as unknown as ComponentEvents }} +return { props: {}, slots: {}, getters: {}, events: {} as unknown as $$Events }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render())) { } \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/input.svelte index 402e21f19..abf27bd14 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/input.svelte @@ -1,5 +1,5 @@ + + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx index ab8c546ba..69130333b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/creates-dts/expected.tsx @@ -98,6 +98,9 @@ type SvelteStore = { subscribe: (run: (value: T) => any, invalidate?: any) => // which helps for error messages type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type KeysMatching = {[K in keyof Obj]-?: Obj[K] extends V ? K : never}[keyof Obj] +declare type __Sveltets_CustomEvents = {[K in KeysMatching]: T[K] extends CustomEvent ? T[K]['detail']: T[K]} + declare var process: NodeJS.Process & { browser: boolean } declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx index de10fcd72..04e22e766 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-$$generics-dts/expected.tsx @@ -98,6 +98,9 @@ type SvelteStore = { subscribe: (run: (value: T) => any, invalidate?: any) => // which helps for error messages type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type KeysMatching = {[K in keyof Obj]-?: Obj[K] extends V ? K : never}[keyof Obj] +declare type __Sveltets_CustomEvents = {[K in KeysMatching]: T[K] extends CustomEvent ? T[K]['detail']: T[K]} + declare var process: NodeJS.Process & { browser: boolean } declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx index b325b8cd8..1f56c00c3 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-creates-dts/expected.tsx @@ -98,6 +98,9 @@ type SvelteStore = { subscribe: (run: (value: T) => any, invalidate?: any) => // which helps for error messages type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +type KeysMatching = {[K in keyof Obj]-?: Obj[K] extends V ? K : never}[keyof Obj] +declare type __Sveltets_CustomEvents = {[K in KeysMatching]: T[K] extends CustomEvent ? T[K]['detail']: T[K]} + declare var process: NodeJS.Process & { browser: boolean } declare var __sveltets_AnimationMove: { from: DOMRect, to: DOMRect }