From f1ae8f991c510ea80e8e4db45c43a94f65cd6f0e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 17 Aug 2020 16:13:18 +0200 Subject: [PATCH 01/18] (feat) ComponentEvents interface This adds the possibility to use a reserved interface name `ComponentEvents` and define all possible events within it. Also adds autocompletion for these events. Also disables autocompletions from HTMLPlugin on component tags. #424 #304 --- .../src/plugins/html/HTMLPlugin.ts | 12 ++- .../plugins/typescript/DocumentSnapshot.ts | 25 +++++- .../typescript/features/CompletionProvider.ts | 57 +++++++++--- packages/svelte2tsx/index.d.ts | 6 ++ packages/svelte2tsx/src/interfaces.ts | 4 +- .../svelte2tsx/src/nodes/ComponentEvents.ts | 86 +++++++++++++++++++ packages/svelte2tsx/src/svelte2tsx.ts | 29 +++++-- .../component-events-interface/expected.tsx | 17 ++++ .../component-events-interface/input.svelte | 10 +++ 9 files changed, 225 insertions(+), 21 deletions(-) create mode 100644 packages/svelte2tsx/src/nodes/ComponentEvents.ts create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte diff --git a/packages/language-server/src/plugins/html/HTMLPlugin.ts b/packages/language-server/src/plugins/html/HTMLPlugin.ts index a34284ff1..2543277cf 100644 --- a/packages/language-server/src/plugins/html/HTMLPlugin.ts +++ b/packages/language-server/src/plugins/html/HTMLPlugin.ts @@ -63,7 +63,11 @@ export class HTMLPlugin implements HoverProvider, CompletionsProvider { this.lang.setCompletionParticipants([ getEmmetCompletionParticipants(document, position, 'html', {}, emmetResults), ]); - const results = this.lang.doComplete(document, position, html); + const results = this.isInComponentTag(html, document, position) + ? // Only allow emmet inside component element tags. + // Other attributes/events would be false positives. + CompletionList.create([]) + : this.lang.doComplete(document, position, html); return CompletionList.create( [...results.items, ...this.getLangCompletions(results.items), ...emmetResults.items], // Emmet completions change on every keystroke, so they are never complete @@ -71,6 +75,12 @@ export class HTMLPlugin implements HoverProvider, CompletionsProvider { ); } + private isInComponentTag(html: HTMLDocument, document: Document, position: Position) { + const offset = document.offsetAt(position); + const node = html.findNodeAt(offset); + return !!node.tag && node.tag[0] === node.tag[0].toUpperCase(); + } + private getLangCompletions(completions: CompletionItem[]): CompletionItem[] { const styleScriptTemplateCompletions = completions.filter((completion) => ['template', 'style', 'script'].includes(completion.label), diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 2af1ccd48..762bd823c 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -1,5 +1,5 @@ import { RawSourceMap, SourceMapConsumer } from 'source-map'; -import svelte2tsx, { IExportedNames } from 'svelte2tsx'; +import svelte2tsx, { IExportedNames, ComponentEvents } from 'svelte2tsx'; import ts from 'typescript'; import { Position, Range } from 'vscode-languageserver'; import { @@ -86,6 +86,7 @@ export namespace DocumentSnapshot { tsxMap, text, exportedNames, + componentEvents, parserError, nrPrependedLines, scriptKind, @@ -98,6 +99,7 @@ export namespace DocumentSnapshot { text, nrPrependedLines, exportedNames, + componentEvents, tsxMap, ); } @@ -127,6 +129,7 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions let nrPrependedLines = 0; let text = document.getText(); let exportedNames: IExportedNames = { has: () => false }; + let componentEvents: ComponentEvents | undefined = undefined; const scriptKind = [ getScriptKindFromAttributes(document.scriptInfo?.attributes ?? {}), @@ -144,6 +147,7 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions text = tsx.code; tsxMap = tsx.map; exportedNames = tsx.exportedNames; + componentEvents = tsx.events; if (tsxMap) { tsxMap.sources = [document.uri]; @@ -171,7 +175,15 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions text = document.scriptInfo ? document.scriptInfo.content : ''; } - return { tsxMap, text, exportedNames, parserError, nrPrependedLines, scriptKind }; + return { + tsxMap, + text, + exportedNames, + componentEvents, + parserError, + nrPrependedLines, + scriptKind, + }; } /** @@ -189,6 +201,7 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { private readonly text: string, private readonly nrPrependedLines: number, private readonly exportedNames: IExportedNames, + private readonly componentEvents?: ComponentEvents, private readonly tsxMap?: RawSourceMap, ) {} @@ -216,6 +229,14 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { return this.exportedNames.has(name); } + getEvents() { + return this.componentEvents?.getAll() || []; + } + + getEvent(name: string) { + return this.componentEvents?.get(name); + } + async getFragment() { if (!this.fragment) { const uri = pathToUrl(this.filePath); diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index d5e4ea7a3..ffa05bd86 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -18,13 +18,14 @@ import { } from '../../../lib/documents'; import { isNotNullOrUndefined, pathToUrl } from '../../../utils'; import { AppCompletionItem, AppCompletionList, CompletionsProvider } from '../../interfaces'; -import { SvelteSnapshotFragment } from '../DocumentSnapshot'; +import { SvelteSnapshotFragment, SvelteDocumentSnapshot } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; import { convertRange, getCommitCharactersForScriptElement, scriptElementKindToCompletionItemKind, } from '../utils'; +import { getLanguageService } from 'vscode-html-languageservice'; export interface CompletionEntryWithIdentifer extends ts.CompletionEntry, TextDocumentIdentifier { position: Position; @@ -71,10 +72,11 @@ export class CompletionsProviderImpl implements CompletionsProvider this.toCompletionItem(fragment, comp, pathToUrl(tsDoc.filePath), position), ) .filter(isNotNullOrUndefined) - .map((comp) => mapCompletionItemToOriginal(fragment, comp)); + .map((comp) => mapCompletionItemToOriginal(fragment, comp)) + .concat(eventCompletions); return CompletionList.create(completionItems, !!tsDoc.parserError); } + private getEventCompletions( + lang: ts.LanguageService, + doc: Document, + tsDoc: SvelteDocumentSnapshot, + originalPosition: Position, + ): AppCompletionItem[] { + if (tsDoc.parserError) { + return []; + } + + const node = getLanguageService() + // TODO performance: this is done already in Document and HTMLPlugin. Consolidate somehow. + .parseHTMLDocument(doc) + .findNodeAt(doc.offsetAt(originalPosition)); + const def = lang.getDefinitionAtPosition(tsDoc.filePath, node.start + 1)?.[0]; + if (!def) { + return []; + } + + const snapshot = this.lsAndTsDocResovler.getSnapshot(def.fileName); + if (!(snapshot instanceof SvelteDocumentSnapshot)) { + return []; + } + + return snapshot.getEvents().map((event) => ({ + label: 'on:' + event.name, + sortText: '-1', + detail: event.name + ': ' + event.type, + documentation: event.doc && { kind: MarkupKind.Markdown, value: event.doc }, + })); + } + private toCompletionItem( fragment: SvelteSnapshotFragment, comp: ts.CompletionEntry, diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts index 1cdcb43bb..dcd96d206 100644 --- a/packages/svelte2tsx/index.d.ts +++ b/packages/svelte2tsx/index.d.ts @@ -2,12 +2,18 @@ export interface SvelteCompiledToTsx { code: string; map: import("magic-string").SourceMap; exportedNames: IExportedNames; + events: ComponentEvents; } export interface IExportedNames { has(name: string): boolean; } +export interface ComponentEvents { + getAll(): { name: string; type: string; doc?: string }[]; + get(name: string): { type: string; doc?: string } | undefined; +} + export default function svelte2tsx( svelte: string, options?: { diff --git a/packages/svelte2tsx/src/interfaces.ts b/packages/svelte2tsx/src/interfaces.ts index 2613bb5c6..c9cecc1ac 100644 --- a/packages/svelte2tsx/src/interfaces.ts +++ b/packages/svelte2tsx/src/interfaces.ts @@ -1,9 +1,11 @@ import MagicString from 'magic-string'; import { Node } from 'estree-walker'; import { ExportedNames } from './nodes/ExportedNames'; +import { ComponentEvents } from './nodes/ComponentEvents'; export interface InstanceScriptProcessResult { exportedNames: ExportedNames; + events: ComponentEvents; uses$$props: boolean; uses$$restProps: boolean; getters: Set; @@ -14,6 +16,6 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult { scriptTag: Node; scriptDestination: number; slots: Map>; - events: Map; + events: ComponentEvents; isTsFile: boolean; } diff --git a/packages/svelte2tsx/src/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/nodes/ComponentEvents.ts new file mode 100644 index 000000000..9863d13a6 --- /dev/null +++ b/packages/svelte2tsx/src/nodes/ComponentEvents.ts @@ -0,0 +1,86 @@ +import ts from 'typescript'; +import { eventMapToString } from './event-handler'; + +export abstract class ComponentEvents { + protected events = new Map(); + + getAll(): { name: string; type?: string; doc?: string }[] { + const entries: { name: string; type: string; doc?: string }[] = []; + + const iterableEntries = this.events.entries(); + for (const entry of iterableEntries) { + entries.push({ name: entry[0], ...entry[1] }); + } + + return entries; + } + + get(name: string): { type: string; doc?: string } | undefined { + return this.events.get(name); + } + + abstract toDefString(): string; +} + +export class ComponentEventsFromInterface extends ComponentEvents { + constructor(node: ts.InterfaceDeclaration) { + super(); + this.events = this.extractEvents(node); + } + + toDefString() { + return '{} as unknown as ComponentEvents'; + } + + private extractEvents(node: ts.InterfaceDeclaration) { + const map = new Map(); + + node.members.filter(ts.isPropertySignature).forEach((member) => { + map.set(member.name.getText(), { + type: member.type?.getText() || 'Event', + doc: this.getDoc(node, member), + }); + }); + + return map; + } + + private getDoc(node: ts.InterfaceDeclaration, member: ts.PropertySignature) { + let doc = undefined; + const comment = ts.getLeadingCommentRanges( + node.getText(), + member.getFullStart() - node.getStart(), + ); + + if (comment) { + doc = node + .getText() + .substring(comment[0].pos, comment[0].end) + // Remove /** */ + .replace(/\s*\/\*\*/, '') + .replace(/\s*\*\//, '') + .replace(/\s*\*/g, ''); + } + + return doc; + } +} + +export class ComponentEventsFromEventsMap extends ComponentEvents { + constructor(private eventsMap: Map) { + super(); + this.events = this.extractEvents(eventsMap); + } + + toDefString() { + return eventMapToString(this.eventsMap); + } + + private extractEvents(eventsMap: Map) { + const map = new Map(); + for (const name of eventsMap.keys()) { + map.set(name, { type: 'Event' }); + } + return map; + } +} diff --git a/packages/svelte2tsx/src/svelte2tsx.ts b/packages/svelte2tsx/src/svelte2tsx.ts index 34499ad9b..7ab029f9c 100644 --- a/packages/svelte2tsx/src/svelte2tsx.ts +++ b/packages/svelte2tsx/src/svelte2tsx.ts @@ -6,12 +6,17 @@ import { parseHtmlx } from './htmlxparser'; import { convertHtmlxToJsx } from './htmlxtojsx'; import { Node } from 'estree-walker'; import * as ts from 'typescript'; -import { createEventHandlerTransformer, eventMapToString } from './nodes/event-handler'; +import { createEventHandlerTransformer } from './nodes/event-handler'; import { findExortKeyword, getBinaryAssignmentExpr } from './utils/tsAst'; import { InstanceScriptProcessResult, CreateRenderFunctionPara } from './interfaces'; import { createRenderFunctionGetterStr, createClassGetters } from './nodes/exportgetters'; import { ExportedNames } from './nodes/ExportedNames'; import { ImplicitTopLevelNames } from './nodes/ImplicitTopLevelNames'; +import { + ComponentEvents, + ComponentEventsFromInterface, + ComponentEventsFromEventsMap, +} from './nodes/ComponentEvents'; function AttributeValueAsJsExpression(htmlx: string, attr: Node): string { if (attr.value.length == 0) return "''"; //wut? @@ -47,7 +52,7 @@ type TemplateProcessResult = { moduleScriptTag: Node; /** To be added later as a comment on the default class export */ componentDocumentation: string | null; - events: Map; + events: ComponentEvents; }; class Scope { @@ -371,14 +376,18 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult { moduleScriptTag, scriptTag, slots, - events: getEvents(), + events: new ComponentEventsFromEventsMap(getEvents()), uses$$props, uses$$restProps, componentDocumentation, }; } -function processInstanceScriptContent(str: MagicString, script: Node): InstanceScriptProcessResult { +function processInstanceScriptContent( + str: MagicString, + script: Node, + events: ComponentEvents, +): InstanceScriptProcessResult { const htmlx = str.original; const scriptContent = htmlx.substring(script.content.start, script.content.end); const tsAst = ts.createSourceFile( @@ -663,6 +672,10 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS type onLeaveCallback = () => void; const onLeaveCallbacks: onLeaveCallback[] = []; + if (ts.isInterfaceDeclaration(node) && node.name.text === 'ComponentEvents') { + events = new ComponentEventsFromInterface(node); + } + if (ts.isVariableStatement(node)) { const exportModifier = findExortKeyword(node); if (exportModifier) { @@ -801,6 +814,7 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS return { exportedNames, + events, uses$$props, uses$$restProps, getters, @@ -933,7 +947,7 @@ function createRenderFunction({ `\nreturn { props: ${exportedNames.createPropsStr( isTsFile, )}, slots: ${slotsAsDef}, getters: ${createRenderFunctionGetterStr(getters)}` + - `, events: ${eventMapToString(events)} }}`; + `, events: ${events.toDefString()} }}`; // wrap template with callback if (scriptTag) { @@ -984,11 +998,11 @@ export function svelte2tsx( if (scriptTag.start != instanceScriptTarget) { str.move(scriptTag.start, scriptTag.end, instanceScriptTarget); } - const res = processInstanceScriptContent(str, scriptTag); + const res = processInstanceScriptContent(str, scriptTag, events); uses$$props = uses$$props || res.uses$$props; uses$$restProps = uses$$restProps || res.uses$$restProps; - ({ exportedNames, getters } = res); + ({ exportedNames, events, getters } = res); } //wrap the script tag and template content in a function returning the slot and exports @@ -1028,5 +1042,6 @@ export function svelte2tsx( code: str.toString(), map: str.generateMap({ hires: true, source: options?.filename }), exportedNames, + events, }; } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx new file mode 100644 index 000000000..cab504709 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx @@ -0,0 +1,17 @@ +/// +<>;function render() { + + interface ComponentEvents { + /** + * Some doc + */ + a: boolean; + b: string; + c; + } +; +() => (<>); +return { props: {}, slots: {}, getters: {}, events: {} as unknown as ComponentEvents }} + +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/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte new file mode 100644 index 000000000..9fd230ea0 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte @@ -0,0 +1,10 @@ + \ No newline at end of file From d1337be969185b04f59ef487aa0a87807a4cb871 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 17 Aug 2020 18:33:42 +0200 Subject: [PATCH 02/18] tests --- .../typescript/features/CompletionProvider.ts | 15 ++++- .../features/CompletionProvider.test.ts | 61 ++++++++++++++++--- .../{ => completions}/completions.svelte | 0 .../{ => completions}/completionsstyle.svelte | 0 .../component-events-completion.svelte | 5 ++ .../component-events-interface.svelte | 10 +++ .../completions/importcompletions.svelte | 1 + .../importcompletions1.svelte | 0 .../completions/importcompletions2.svelte | 4 ++ .../completions/importcompletions3.svelte | 1 + .../importcompletions4.svelte | 0 .../importcompletions5.svelte | 0 .../completions/importcompletions6.svelte | 4 ++ .../testfiles/importcompletions.svelte | 1 - .../testfiles/importcompletions2.svelte | 4 -- .../testfiles/importcompletions3.svelte | 1 - .../testfiles/importcompletions6.svelte | 4 -- 17 files changed, 91 insertions(+), 20 deletions(-) rename packages/language-server/test/plugins/typescript/testfiles/{ => completions}/completions.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => completions}/completionsstyle.svelte (100%) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/component-events-completion.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions.svelte rename packages/language-server/test/plugins/typescript/testfiles/{ => completions}/importcompletions1.svelte (100%) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions2.svelte create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions3.svelte rename packages/language-server/test/plugins/typescript/testfiles/{ => completions}/importcompletions4.svelte (100%) rename packages/language-server/test/plugins/typescript/testfiles/{ => completions}/importcompletions5.svelte (100%) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions6.svelte delete mode 100644 packages/language-server/test/plugins/typescript/testfiles/importcompletions.svelte delete mode 100644 packages/language-server/test/plugins/typescript/testfiles/importcompletions2.svelte delete mode 100644 packages/language-server/test/plugins/typescript/testfiles/importcompletions3.svelte delete mode 100644 packages/language-server/test/plugins/typescript/testfiles/importcompletions6.svelte diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index ffa05bd86..0ba231cd4 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -91,7 +91,13 @@ export class CompletionsProviderImpl implements CompletionsProvider[] { if (tsDoc.parserError) { @@ -122,7 +129,11 @@ export class CompletionsProviderImpl implements CompletionsProvider { @@ -74,6 +74,51 @@ describe('CompletionProviderImpl', () => { }); }); + it('provides event completions', async () => { + const { completionProvider, document } = setup('component-events-completion.svelte'); + + const completions = await completionProvider.getCompletions( + document, + Position.create(4, 5), + { + triggerKind: CompletionTriggerKind.Invoked, + }, + ); + + assert.ok( + Array.isArray(completions && completions.items), + 'Expected completion items to be an array', + ); + assert.ok(completions!.items.length > 0, 'Expected completions to have length'); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const eventCompletions = completions!.items.filter((item) => item.label.startsWith('on:')); + + assert.deepStrictEqual(eventCompletions, [ + { + detail: 'a: CustomEvent', + documentation: undefined, + label: 'on:a', + sortText: '-1', + }, + { + detail: 'b: MouseEvent', + documentation: { + kind: 'markdown', + value: ' TEST', + }, + label: 'on:b', + sortText: '-1', + }, + { + detail: 'c: Event', + documentation: undefined, + label: 'on:c', + sortText: '-1', + }, + ]); + }); + it('does not provide completions inside style tag', async () => { const { completionProvider, document } = setup('completionsstyle.svelte'); @@ -123,7 +168,7 @@ describe('CompletionProviderImpl', () => { }); it('resolve completion and provide documentation', async () => { - const { completionProvider, document } = setup('documentation.svelte'); + const { completionProvider, document } = setup('../documentation.svelte'); const { documentation, detail } = await completionProvider.resolveCompletion(document, { label: 'foo', @@ -232,12 +277,12 @@ describe('CompletionProviderImpl', () => { item!, ); - assert.strictEqual(detail, 'Auto import from ./definitions\nfunction blubb(): boolean'); + assert.strictEqual(detail, 'Auto import from ../definitions\nfunction blubb(): boolean'); assert.strictEqual( harmonizeNewLines(additionalTextEdits![0]?.newText), // " instead of ' because VSCode uses " by default when there are no other imports indicating otherwise - `${newLine}import { blubb } from "./definitions";${newLine}${newLine}`, + `${newLine}import { blubb } from "../definitions";${newLine}${newLine}`, ); assert.deepEqual( @@ -265,11 +310,11 @@ describe('CompletionProviderImpl', () => { item!, ); - assert.strictEqual(detail, 'Auto import from ./definitions\nfunction blubb(): boolean'); + assert.strictEqual(detail, 'Auto import from ../definitions\nfunction blubb(): boolean'); assert.strictEqual( harmonizeNewLines(additionalTextEdits![0]?.newText), - `import { blubb } from './definitions';${newLine}`, + `import { blubb } from '../definitions';${newLine}`, ); assert.deepEqual( @@ -297,11 +342,11 @@ describe('CompletionProviderImpl', () => { item!, ); - assert.strictEqual(detail, 'Auto import from ./definitions\nfunction blubb(): boolean'); + assert.strictEqual(detail, 'Auto import from ../definitions\nfunction blubb(): boolean'); assert.strictEqual( harmonizeNewLines(additionalTextEdits![0]?.newText), - `${newLine}import { blubb } from './definitions';${newLine}`, + `${newLine}import { blubb } from '../definitions';${newLine}`, ); assert.deepEqual( diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/completions.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/completions.svelte rename to packages/language-server/test/plugins/typescript/testfiles/completions/completions.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/completionsstyle.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/completionsstyle.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/completionsstyle.svelte rename to packages/language-server/test/plugins/typescript/testfiles/completions/completionsstyle.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-completion.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-completion.svelte new file mode 100644 index 000000000..165911948 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-completion.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file 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 new file mode 100644 index 000000000..a87dc62b6 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/component-events-interface.svelte @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions.svelte new file mode 100644 index 000000000..f785e0c06 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions1.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions1.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/importcompletions1.svelte rename to packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions1.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions2.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions2.svelte new file mode 100644 index 000000000..d548946a1 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions2.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions3.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions3.svelte new file mode 100644 index 000000000..1a56b9e7e --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions3.svelte @@ -0,0 +1 @@ + diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions4.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions4.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/importcompletions4.svelte rename to packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions4.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions5.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions5.svelte similarity index 100% rename from packages/language-server/test/plugins/typescript/testfiles/importcompletions5.svelte rename to packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions5.svelte diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions6.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions6.svelte new file mode 100644 index 000000000..5c1ef9cd9 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/importcompletions6.svelte @@ -0,0 +1,4 @@ + +import {} from '../testfiles/' blu \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions2.svelte b/packages/language-server/test/plugins/typescript/testfiles/importcompletions2.svelte deleted file mode 100644 index b04f6cd6f..000000000 --- a/packages/language-server/test/plugins/typescript/testfiles/importcompletions2.svelte +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions3.svelte b/packages/language-server/test/plugins/typescript/testfiles/importcompletions3.svelte deleted file mode 100644 index 3c6d6c947..000000000 --- a/packages/language-server/test/plugins/typescript/testfiles/importcompletions3.svelte +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/language-server/test/plugins/typescript/testfiles/importcompletions6.svelte b/packages/language-server/test/plugins/typescript/testfiles/importcompletions6.svelte deleted file mode 100644 index 096c277e7..000000000 --- a/packages/language-server/test/plugins/typescript/testfiles/importcompletions6.svelte +++ /dev/null @@ -1,4 +0,0 @@ - - Date: Mon, 17 Aug 2020 18:45:34 +0200 Subject: [PATCH 03/18] docs --- docs/preprocessors/typescript.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/preprocessors/typescript.md b/docs/preprocessors/typescript.md index 910051c2c..ab2fc57ca 100644 --- a/docs/preprocessors/typescript.md +++ b/docs/preprocessors/typescript.md @@ -42,6 +42,25 @@ You will need to tell svelte-vscode to restart the svelte language server in ord Hit `ctrl-shift-p` or `cmd-shift-p` on mac, type `svelte restart`, and select `Svelte: Restart Language Server`. Any errors you were seeing should now go away and you're now all set up! +## Typing component events + +When you are using TypeScript, you can type which events your component has by defining a reserved `interface` (_NOT_ `type`) called `ComponentEvents`: + +```html + +``` + +Doing this will give you autocompletion for these events as well as type safety when using the component in other components. + +> In case you ask why this cannot be infered: Due to Svelte's dynamic nature, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of a mixin. This is almost impossible to infer, so we need you to tell us which events are possible. + ## Troubleshooting / FAQ ### I cannot use TS inside my script even when `lang="ts"` is present @@ -81,6 +100,7 @@ At the moment, you cannot. Only `script`/`style` tags are preprocessed/transpile ### Why is VSCode not finding absolute paths for type imports? You may need to set `baseUrl` in `tsconfig.json` at the project root to include (restart the language server to see this take effect): + ``` "compilerOptions": { "baseUrl": "." From aa5902123a48429695a25bbcfee254354bfe1fff Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 17 Aug 2020 18:47:51 +0200 Subject: [PATCH 04/18] don't search for events if it's not a component --- .../src/plugins/typescript/features/CompletionProvider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index 0ba231cd4..2534a9559 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -129,6 +129,11 @@ export class CompletionsProviderImpl implements CompletionsProvider not a component + return []; + } + const generatedPosition = fragment.getGeneratedPosition(doc.positionAt(node.start + 1)); const def = lang.getDefinitionAtPosition( tsDoc.filePath, From 36bbfbe8db69e42be8627b9932b0f3faec3b6982 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 17 Aug 2020 19:04:29 +0200 Subject: [PATCH 05/18] strict events when interface given --- packages/svelte2tsx/README.md | 4 +++- packages/svelte2tsx/src/svelte2tsx.ts | 9 ++++++--- packages/svelte2tsx/svelte-shims.d.ts | 5 ++++- packages/svelte2tsx/test/sourcemaps/event-binding.html | 2 +- packages/svelte2tsx/test/sourcemaps/let.html | 2 +- packages/svelte2tsx/test/sourcemaps/repl.html | 2 +- .../test/svelte2tsx/samples/$store-index/expected.tsx | 2 +- .../svelte2tsx/samples/array-binding-export/expected.tsx | 2 +- .../test/svelte2tsx/samples/ast-offset-none/expected.tsx | 2 +- .../test/svelte2tsx/samples/ast-offset-some/expected.tsx | 2 +- .../svelte2tsx/samples/await-with-$store/expected.tsx | 2 +- .../svelte2tsx/samples/binding-group-store/expected.tsx | 2 +- .../samples/circle-drawer-example/expected.tsx | 2 +- .../samples/component-default-slot/expected.tsx | 2 +- .../samples/component-multiple-slots/expected.tsx | 2 +- .../samples/component-slot-crazy-attributes/expected.tsx | 2 +- .../samples/component-with-documentation/expected.tsx | 2 +- .../expected.tsx | 2 +- .../component-with-multiline-documentation/expected.tsx | 2 +- .../samples/event-bubble-component-multi/expected.tsx | 2 +- .../samples/event-bubble-component/expected.tsx | 2 +- .../svelte2tsx/samples/event-bubble-element/expected.tsx | 2 +- .../samples/event-bubble-svelte-element/expected.tsx | 2 +- .../test/svelte2tsx/samples/export-class/expected.tsx | 2 +- .../svelte2tsx/samples/export-js-strictMode/expected.tsx | 2 +- .../test/svelte2tsx/samples/export-list/expected.tsx | 2 +- .../samples/export-references-local/expected.tsx | 2 +- .../samples/export-with-default-multi/expected.tsx | 2 +- .../svelte2tsx/samples/import-single-quote/expected.tsx | 2 +- .../test/svelte2tsx/samples/imports/expected.tsx | 2 +- .../module-script-and-script-in-line/expected.tsx | 2 +- .../module-script-and-script-in-line2/expected.tsx | 2 +- .../samples/module-script-and-script/expected.tsx | 2 +- .../samples/module-script-and-script2/expected.tsx | 2 +- .../samples/nested-$-variables-script/expected.tsx | 2 +- .../samples/nested-$-variables-template/expected.tsx | 2 +- .../samples/object-binding-export/expected.tsx | 2 +- .../samples/reactive-$store-destructuring/expected.tsx | 2 +- .../test/svelte2tsx/samples/reactive-block/expected.tsx | 2 +- .../samples/reactive-declare-object/expected.tsx | 2 +- .../svelte2tsx/samples/reactive-declare/expected.tsx | 2 +- .../svelte2tsx/samples/reactive-store-set/expected.tsx | 2 +- .../test/svelte2tsx/samples/renamed-exports/expected.tsx | 2 +- .../samples/script-and-module-script/expected.tsx | 2 +- .../expected.tsx | 2 +- .../svelte2tsx/samples/script-on-bottom/expected.tsx | 2 +- .../samples/script-style-like-component/expected.tsx | 2 +- .../samples/self-closing-component/expected.tsx | 2 +- .../test/svelte2tsx/samples/single-element/expected.tsx | 2 +- .../test/svelte2tsx/samples/single-export/expected.tsx | 2 +- .../test/svelte2tsx/samples/stores-mustache/expected.tsx | 2 +- .../samples/style-after-selfclosing-iframe/expected.tsx | 2 +- .../svelte2tsx/samples/style-attribute-bare/expected.tsx | 2 +- .../test/svelte2tsx/samples/style-attribute/expected.tsx | 2 +- .../test/svelte2tsx/samples/style/expected.tsx | 2 +- .../samples/ts-export-arrow-function/expected.tsx | 2 +- .../test/svelte2tsx/samples/ts-export-const/expected.tsx | 2 +- .../samples/ts-export-has-initializer/expected.tsx | 2 +- .../svelte2tsx/samples/ts-export-has-type/expected.tsx | 2 +- .../svelte2tsx/samples/ts-export-interface/expected.tsx | 2 +- .../svelte2tsx/samples/ts-export-strictMode/expected.tsx | 2 +- .../svelte2tsx/samples/ts-multiple-export/expected.tsx | 2 +- .../samples/ts-typed-export-with-default/expected.tsx | 2 +- .../samples/ts-uses-$$props-strictMode/expected.tsx | 2 +- .../svelte2tsx/samples/uses-$$props-script/expected.tsx | 2 +- .../test/svelte2tsx/samples/uses-$$props/expected.tsx | 2 +- .../samples/uses-$$restProps-script/expected.tsx | 2 +- .../svelte2tsx/samples/uses-$$restProps/expected.tsx | 2 +- .../samples/uses-$store-in-event-binding/expected.tsx | 2 +- .../uses-$store-with-assignment-operators/expected.tsx | 2 +- .../uses-$store-with-exclamation-mark/expected.tsx | 2 +- .../samples/uses-$store-with-increments/expected.tsx | 2 +- .../test/svelte2tsx/samples/uses-$store/expected.tsx | 2 +- .../samples/uses-svelte-components/expected.tsx | 2 +- 74 files changed, 84 insertions(+), 76 deletions(-) diff --git a/packages/svelte2tsx/README.md b/packages/svelte2tsx/README.md index 59dcbd286..a491547fb 100644 --- a/packages/svelte2tsx/README.md +++ b/packages/svelte2tsx/README.md @@ -36,7 +36,9 @@ function render() { return { props: { world }, slots: {}, events: {} }; } -export default class _World_ extends createSvelte2TsxComponent(__sveltets_partial(render)) {} +export default class _World_ extends createSvelte2TsxComponent( + __sveltets_partial(__sveltets_with_any_event(render)), +) {} ``` with a v3 SourceMap back to the original source. diff --git a/packages/svelte2tsx/src/svelte2tsx.ts b/packages/svelte2tsx/src/svelte2tsx.ts index 7ab029f9c..f8ac87a4a 100644 --- a/packages/svelte2tsx/src/svelte2tsx.ts +++ b/packages/svelte2tsx/src/svelte2tsx.ts @@ -839,21 +839,23 @@ function addComponentExport( str: MagicString, uses$$propsOr$$restProps: boolean, strictMode: boolean, + strictEvents: boolean, isTsFile: boolean, getters: Set, /** A named export allows for TSDoc-compatible docstrings */ className?: string, componentDocumentation?: string | null, ) { + const eventsDef = strictEvents ? 'render' : '__sveltets_with_any_event(render)'; const propDef = // Omit partial-wrapper only if both strict mode and ts file, because // in a js file the user has no way of telling the language that // the prop is optional strictMode && isTsFile ? uses$$propsOr$$restProps - ? '__sveltets_with_any(render)' - : 'render' - : `__sveltets_partial${uses$$propsOr$$restProps ? '_with_any' : ''}(render)`; + ? `__sveltets_with_any(${eventsDef})` + : eventsDef + : `__sveltets_partial${uses$$propsOr$$restProps ? '_with_any' : ''}(${eventsDef})`; const doc = formatComponentDocumentation(componentDocumentation); @@ -1030,6 +1032,7 @@ export function svelte2tsx( str, uses$$props || uses$$restProps, !!options?.strictMode, + events instanceof ComponentEventsFromInterface, options?.isTsFile, getters, className, diff --git a/packages/svelte2tsx/svelte-shims.d.ts b/packages/svelte2tsx/svelte-shims.d.ts index 36a3618e7..b3a4e54d9 100644 --- a/packages/svelte2tsx/svelte-shims.d.ts +++ b/packages/svelte2tsx/svelte-shims.d.ts @@ -112,6 +112,9 @@ declare function __sveltets_partial_with_any( render: () => {props?: Props, events?: Events, slots?: Slots } ): () => {props?: Props & SvelteAllProps, events?: Events, slots?: Slots } +declare function __sveltets_with_any_event( + render: () => {props?: Props, events?: Events, slots?: Slots } +): () => {props?: Props, events?: Events & {[evt: string]: CustomEvent;}, slots?: Slots } declare function __sveltets_store_get(store: SvelteStore): T declare function __sveltets_any(dummy: any): any; declare function __sveltets_empty(dummy: any): {}; @@ -162,4 +165,4 @@ declare function __sveltets_each( declare function createSvelte2TsxComponent( render: () => {props?: Props, events?: Events, slots?: Slots } -): AConstructorTypeOf;}, Slots>>; +): AConstructorTypeOf>; diff --git a/packages/svelte2tsx/test/sourcemaps/event-binding.html b/packages/svelte2tsx/test/sourcemaps/event-binding.html index 5504bd10d..3055c47c1 100644 --- a/packages/svelte2tsx/test/sourcemaps/event-binding.html +++ b/packages/svelte2tsx/test/sourcemaps/event-binding.html @@ -6,7 +6,7 @@ 3==== 4================== return { props: {}, slots: {}, getters: {}, events: {} }} -export default class extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } !Expected diff --git a/packages/svelte2tsx/test/sourcemaps/let.html b/packages/svelte2tsx/test/sourcemaps/let.html index 087b191d0..a6e113a42 100644 --- a/packages/svelte2tsx/test/sourcemaps/let.html +++ b/packages/svelte2tsx/test/sourcemaps/let.html @@ -7,7 +7,7 @@ ); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } !Expected \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js new file mode 100644 index 000000000..d1a59ed3f --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js @@ -0,0 +1,12 @@ +let assert = require('assert') + +module.exports = function ({events}) { + assert.deepEqual( + events.getAll(), + [ + {name: 'a-b', type: 'boolean', doc: ' Some doc'}, + {name: 'b', type: 'string', doc: undefined}, + {name: 'c', type: 'Event', doc: undefined} + ] + ); +} 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 new file mode 100644 index 000000000..6845dc046 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.tsx @@ -0,0 +1,17 @@ +/// +<>;function render() { + + interface ComponentEvents { + /** + * Some doc + */ + 'a-b': boolean; + 'b': string; + 'c'; + } +; +() => (<>); +return { props: {}, slots: {}, getters: {}, events: {} as unknown as ComponentEvents }} + +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 new file mode 100644 index 000000000..402e21f19 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/input.svelte @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js new file mode 100644 index 000000000..ff917f277 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js @@ -0,0 +1,12 @@ +let assert = require('assert') + +module.exports = function ({events}) { + assert.deepEqual( + events.getAll(), + [ + {name: 'a', type: 'boolean', doc: ' Some doc'}, + {name: 'b', type: 'string', doc: undefined}, + {name: 'c', type: 'Event', doc: undefined} + ] + ); +} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx index cab504709..fc05e51ad 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx @@ -5,9 +5,9 @@ /** * Some doc */ - a: boolean; - b: string; - c; + a: boolean; + b: string; + c; } ; () => (<>); diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte index 9fd230ea0..16e2e4ff2 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte @@ -3,8 +3,8 @@ /** * Some doc */ - a: boolean; - b: string; - c; + a: boolean; + b: string; + c; } \ No newline at end of file From 582fef4908e56abea71445cc2b439db1003bc52c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 20 Aug 2020 10:07:31 +0200 Subject: [PATCH 16/18] fine-tune docs /** * */ removal --- packages/svelte2tsx/src/nodes/ComponentEvents.ts | 14 ++++++++++---- .../expected.js | 2 +- .../expected.js | 2 +- .../samples/component-events-interface/expected.js | 2 +- .../component-events-interface/expected.tsx | 2 +- .../component-events-interface/input.svelte | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/svelte2tsx/src/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/nodes/ComponentEvents.ts index 1895fa093..c5b8ba134 100644 --- a/packages/svelte2tsx/src/nodes/ComponentEvents.ts +++ b/packages/svelte2tsx/src/nodes/ComponentEvents.ts @@ -99,10 +99,16 @@ export class ComponentEventsFromInterface extends ComponentEvents { doc = node .getText() .substring(comment[0].pos, comment[0].end) - // Remove /** */ - .replace(/\s*\/\*\*/, '') - .replace(/\s*\*\//, '') - .replace(/\s*\*/g, ''); + .split('\n') + .map((line) => + // Remove /** */ + line + .replace(/\s*\/\*\*/, '') + .replace(/\s*\*\//, '') + .replace(/\s*\*/, '') + .trim(), + ) + .join('\n'); } return doc; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-constant/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-constant/expected.js index ff917f277..a14e5f924 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-constant/expected.js +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-constant/expected.js @@ -4,7 +4,7 @@ module.exports = function ({events}) { assert.deepEqual( events.getAll(), [ - {name: 'a', type: 'boolean', doc: ' Some doc'}, + {name: 'a', type: 'boolean', doc: '\nSome doc\n'}, {name: 'b', type: 'string', doc: undefined}, {name: 'c', type: 'Event', doc: undefined} ] diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js index d1a59ed3f..b4385c1a1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface-string-literals/expected.js @@ -4,7 +4,7 @@ module.exports = function ({events}) { assert.deepEqual( events.getAll(), [ - {name: 'a-b', type: 'boolean', doc: ' Some doc'}, + {name: 'a-b', type: 'boolean', doc: '\nSome doc\n'}, {name: 'b', type: 'string', doc: undefined}, {name: 'c', type: 'Event', doc: undefined} ] diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js index ff917f277..4432bacdc 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.js @@ -4,7 +4,7 @@ module.exports = function ({events}) { assert.deepEqual( events.getAll(), [ - {name: 'a', type: 'boolean', doc: ' Some doc'}, + {name: 'a', type: 'boolean', doc: '\nSome *doc*\n'}, {name: 'b', type: 'string', doc: undefined}, {name: 'c', type: 'Event', doc: undefined} ] diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx index fc05e51ad..80f502100 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/expected.tsx @@ -3,7 +3,7 @@ interface ComponentEvents { /** - * Some doc + * Some *doc* */ a: boolean; b: string; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte index 16e2e4ff2..a87e07b87 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte @@ -1,7 +1,7 @@ ``` -Doing this will give you autocompletion for these events as well as type safety when using the component in other components. +Doing this will give you autocompletion for these events as well as type safety when listening to the events in other components. -> In case you ask why this cannot be infered: Due to Svelte's dynamic nature, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of a mixin. This is almost impossible to infer, so we need you to tell us which events are possible. +If you want to be sure that the interface definition names correspond to your dispatched events, you can use computed property names: + +```html + +``` + +> In case you ask why the events cannot be infered: Due to Svelte's dynamic nature, component events could be fired not only from a dispatcher created directly in the component, but from a dispatcher which is created as part of a mixin. This is almost impossible to infer, so we need you to tell us which events are possible. ## Troubleshooting / FAQ