diff --git a/docs/preprocessors/typescript.md b/docs/preprocessors/typescript.md index 910051c2c..14b59de12 100644 --- a/docs/preprocessors/typescript.md +++ b/docs/preprocessors/typescript.md @@ -42,6 +42,38 @@ 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 listening to the events in other components. + +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 ### I cannot use TS inside my script even when `lang="ts"` is present @@ -81,6 +113,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": "." diff --git a/packages/language-server/src/lib/documents/Document.ts b/packages/language-server/src/lib/documents/Document.ts index ffa67de2c..b3d0ae6ca 100644 --- a/packages/language-server/src/lib/documents/Document.ts +++ b/packages/language-server/src/lib/documents/Document.ts @@ -1,7 +1,8 @@ import { urlToPath } from '../../utils'; import { WritableDocument } from './DocumentBase'; -import { extractScriptTags, extractStyleTag, TagInformation } from './utils'; +import { extractScriptTags, extractStyleTag, TagInformation, parseHtml } from './utils'; import { SvelteConfig, loadConfig } from './configLoader'; +import { HTMLDocument } from 'vscode-html-languageservice'; /** * Represents a text document contains a svelte component. @@ -12,6 +13,7 @@ export class Document extends WritableDocument { moduleScriptInfo: TagInformation | null = null; styleInfo: TagInformation | null = null; config!: SvelteConfig; + html!: HTMLDocument; constructor(public url: string, public content: string) { super(); @@ -22,10 +24,11 @@ export class Document extends WritableDocument { if (!this.config || this.config.loadConfigError) { this.config = loadConfig(this.getFilePath() || ''); } - const scriptTags = extractScriptTags(this.content); + this.html = parseHtml(this.content); + const scriptTags = extractScriptTags(this.content, this.html); this.scriptInfo = this.addDefaultLanguage(scriptTags?.script || null, 'script'); this.moduleScriptInfo = this.addDefaultLanguage(scriptTags?.moduleScript || null, 'script'); - this.styleInfo = this.addDefaultLanguage(extractStyleTag(this.content), 'style'); + this.styleInfo = this.addDefaultLanguage(extractStyleTag(this.content, this.html), 'style'); } /** diff --git a/packages/language-server/src/lib/documents/utils.ts b/packages/language-server/src/lib/documents/utils.ts index 16eed58f7..3821dab44 100644 --- a/packages/language-server/src/lib/documents/utils.ts +++ b/packages/language-server/src/lib/documents/utils.ts @@ -1,6 +1,6 @@ import { clamp, isInRange, regexLastIndexOf } from '../../utils'; import { Position, Range } from 'vscode-languageserver'; -import { Node, getLanguageService } from 'vscode-html-languageservice'; +import { Node, getLanguageService, HTMLDocument } from 'vscode-html-languageservice'; import * as path from 'path'; export interface TagInformation { @@ -39,7 +39,11 @@ function parseAttributes( } const parser = getLanguageService(); -function parseHtml(text: string) { + +/** + * Parses text as HTML + */ +export function parseHtml(text: string): HTMLDocument { // We can safely only set getText because only this is used for parsing return parser.parseHTMLDocument({ getText: () => text }); } @@ -77,9 +81,9 @@ function blankIfBlocks(text: string): string { * @param source text content to extract tag from * @param tag the tag to extract */ -function extractTags(text: string, tag: 'script' | 'style'): TagInformation[] { +function extractTags(text: string, tag: 'script' | 'style', html?: HTMLDocument): TagInformation[] { text = blankIfBlocks(text); - const rootNodes = parseHtml(text).roots; + const rootNodes = html?.roots || parseHtml(text).roots; const matchedNodes = rootNodes .filter((node) => node.tag === tag) .filter((tag) => { @@ -155,8 +159,9 @@ function extractTags(text: string, tag: 'script' | 'style'): TagInformation[] { export function extractScriptTags( source: string, + html?: HTMLDocument, ): { script?: TagInformation; moduleScript?: TagInformation } | null { - const scripts = extractTags(source, 'script'); + const scripts = extractTags(source, 'script', html); if (!scripts.length) { return null; } @@ -166,8 +171,8 @@ export function extractScriptTags( return { script, moduleScript }; } -export function extractStyleTag(source: string): TagInformation | null { - const styles = extractTags(source, 'style'); +export function extractStyleTag(source: string, html?: HTMLDocument): TagInformation | null { + const styles = extractTags(source, 'style', html); if (!styles.length) { return null; } @@ -291,3 +296,20 @@ export function updateRelativeImport(oldPath: string, newPath: string, relativeI } return newImportPath; } + +/** + * Returns the node if offset is inside a component's starttag + */ +export function getNodeIfIsInComponentStartTag( + html: HTMLDocument, + offset: number, +): Node | undefined { + const node = html.findNodeAt(offset); + if ( + !!node.tag && + node.tag[0] === node.tag[0].toUpperCase() && + (!node.startTagEnd || offset < node.startTagEnd) + ) { + return node; + } +} diff --git a/packages/language-server/src/plugins/html/HTMLPlugin.ts b/packages/language-server/src/plugins/html/HTMLPlugin.ts index a34284ff1..7465875db 100644 --- a/packages/language-server/src/plugins/html/HTMLPlugin.ts +++ b/packages/language-server/src/plugins/html/HTMLPlugin.ts @@ -7,7 +7,12 @@ import { SymbolInformation, CompletionItem, } from 'vscode-languageserver'; -import { DocumentManager, Document, isInTag } from '../../lib/documents'; +import { + DocumentManager, + Document, + isInTag, + getNodeIfIsInComponentStartTag, +} from '../../lib/documents'; import { LSConfigManager, LSHTMLConfig } from '../../ls-config'; import { svelteHtmlDataProvider } from './dataProvider'; import { HoverProvider, CompletionsProvider } from '../interfaces'; @@ -20,8 +25,7 @@ export class HTMLPlugin implements HoverProvider, CompletionsProvider { constructor(docManager: DocumentManager, configManager: LSConfigManager) { this.configManager = configManager; docManager.on('documentChange', (document) => { - const html = this.lang.parseHTMLDocument(document); - this.documents.set(document, html); + this.documents.set(document, document.html); }); } @@ -63,7 +67,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 +79,10 @@ export class HTMLPlugin implements HoverProvider, CompletionsProvider { ); } + private isInComponentTag(html: HTMLDocument, document: Document, position: Position) { + return !!getNodeIfIsInComponentStartTag(html, document.offsetAt(position)); + } + 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..398730c21 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,10 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot { return this.exportedNames.has(name); } + getEvents() { + return this.componentEvents?.getAll() || []; + } + 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..ef098faed 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -15,10 +15,11 @@ import { isInTag, mapCompletionItemToOriginal, mapRangeToOriginal, + getNodeIfIsInComponentStartTag, } 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, @@ -84,25 +85,98 @@ 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, + fragment: SvelteSnapshotFragment, + originalPosition: Position, + ): AppCompletionItem[] { + const snapshot = this.getComponentAtPosition(lang, doc, tsDoc, fragment, originalPosition); + if (!snapshot) { + 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 }, + })); + } + + /** + * If the completion happens inside the template and within the + * tag of a Svelte component, then retrieve its snapshot. + */ + private getComponentAtPosition( + lang: ts.LanguageService, + doc: Document, + tsDoc: SvelteDocumentSnapshot, + fragment: SvelteSnapshotFragment, + originalPosition: Position, + ): SvelteDocumentSnapshot | null { + if (tsDoc.parserError) { + return null; + } + + if ( + isInTag(originalPosition, doc.scriptInfo) || + isInTag(originalPosition, doc.moduleScriptInfo) + ) { + // Inside script tags -> not a component + return null; + } + + const node = getNodeIfIsInComponentStartTag(doc.html, doc.offsetAt(originalPosition)); + if (!node) { + return null; + } + + const generatedPosition = fragment.getGeneratedPosition(doc.positionAt(node.start + 1)); + const def = lang.getDefinitionAtPosition( + tsDoc.filePath, + fragment.offsetAt(generatedPosition), + )?.[0]; + if (!def) { + return null; + } + + const snapshot = this.lsAndTsDocResovler.getSnapshot(def.fileName); + if (!(snapshot instanceof SvelteDocumentSnapshot)) { + return null; + } + return snapshot; + } + private toCompletionItem( fragment: SvelteSnapshotFragment, comp: ts.CompletionEntry, @@ -260,7 +334,7 @@ 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: '\nTEST\n', + }, + 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 @@ - -; @@ -14,6 +16,22 @@ export interface CreateRenderFunctionPara extends InstanceScriptProcessResult { scriptTag: Node; scriptDestination: number; slots: Map>; - events: Map; + events: ComponentEvents; isTsFile: boolean; } + +export interface AddComponentExportPara { + str: MagicString; + uses$$propsOr$$restProps: boolean; + strictMode: boolean; + /** + * If true, not fallback to `CustomEvent` + * -> all unknown events will throw a type error + * */ + strictEvents: boolean; + isTsFile: boolean; + getters: Set; + /** A named export allows for TSDoc-compatible docstrings */ + className?: string; + componentDocumentation?: string | null; +} diff --git a/packages/svelte2tsx/src/nodes/ComponentEvents.ts b/packages/svelte2tsx/src/nodes/ComponentEvents.ts new file mode 100644 index 000000000..c5b8ba134 --- /dev/null +++ b/packages/svelte2tsx/src/nodes/ComponentEvents.ts @@ -0,0 +1,135 @@ +import ts from 'typescript'; +import { EventHandler } from './event-handler'; +import { getVariableAtTopLevel } from '../utils/tsAst'; + +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; + } + + 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(this.getName(member.name), { + type: member.type?.getText() || 'Event', + doc: this.getDoc(node, member), + }); + }); + + return map; + } + + private getName(prop: ts.PropertyName) { + if (ts.isIdentifier(prop) || ts.isStringLiteral(prop)) { + return prop.text; + } + + if (ts.isComputedPropertyName(prop)) { + if (ts.isIdentifier(prop.expression)) { + const identifierName = prop.expression.text; + const identifierValue = this.getIdentifierValue(prop, identifierName); + if (!identifierValue) { + this.throwError(prop); + } + return identifierValue; + } + } + + this.throwError(prop); + } + + private getIdentifierValue(prop: ts.ComputedPropertyName, identifierName: string) { + const variable = getVariableAtTopLevel(prop.getSourceFile(), identifierName); + if (variable && ts.isStringLiteral(variable.initializer)) { + return variable.initializer.text; + } + } + + private throwError(prop: ts.PropertyName) { + const error: any = new Error( + 'The ComponentEvents interface can only have properties of type ' + + 'Identifier, StringLiteral or ComputedPropertyName. ' + + 'In case of ComputedPropertyName, ' + + 'it must be a const declared within the component and initialized with a string.', + ); + error.start = toLineColumn(prop.getStart()); + error.end = toLineColumn(prop.getEnd()); + throw error; + + function toLineColumn(pos: number) { + const lineChar = prop.getSourceFile().getLineAndCharacterOfPosition(pos); + return { + line: lineChar.line + 1, + column: lineChar.character, + }; + } + } + + 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) + .split('\n') + .map((line) => + // Remove /** */ + line + .replace(/\s*\/\*\*/, '') + .replace(/\s*\*\//, '') + .replace(/\s*\*/, '') + .trim(), + ) + .join('\n'); + } + + return doc; + } +} + +export class ComponentEventsFromEventsMap extends ComponentEvents { + constructor(private eventHandler: EventHandler) { + super(); + this.events = this.extractEvents(eventHandler); + } + + toDefString() { + return this.eventHandler.eventMapToString(); + } + + private extractEvents(eventHandler: EventHandler) { + const map = new Map(); + for (const name of eventHandler.getEvents().keys()) { + map.set(name, { type: 'Event' }); + } + return map; + } +} diff --git a/packages/svelte2tsx/src/nodes/event-handler.ts b/packages/svelte2tsx/src/nodes/event-handler.ts index cdb334beb..2ca21104a 100644 --- a/packages/svelte2tsx/src/nodes/event-handler.ts +++ b/packages/svelte2tsx/src/nodes/event-handler.ts @@ -1,9 +1,9 @@ import { Node } from 'estree-walker'; -export function createEventHandlerTransformer() { - const events = new Map(); +export class EventHandler { + events = new Map(); - const handleEventHandler = (node: Node, parent: Node) => { + handleEventHandler = (node: Node, parent: Node) => { const eventName = node.name; const handleEventHandlerBubble = () => { @@ -11,8 +11,8 @@ export function createEventHandlerTransformer() { // eslint-disable-next-line max-len const exp = `__sveltets_bubbleEventDef(${componentEventDef}.$$events_def, '${eventName}')`; - const exist = events.get(eventName); - events.set(eventName, exist ? [].concat(exist, exp) : exp); + const exist = this.events.get(eventName); + this.events.set(eventName, exist ? [].concat(exist, exp) : exp); }; // pass-through/ bubble @@ -20,15 +20,18 @@ export function createEventHandlerTransformer() { if (parent.type === 'InlineComponent') { handleEventHandlerBubble(); } else { - events.set(eventName, getEventDefExpressionForNonCompoent(eventName, parent)); + this.events.set(eventName, getEventDefExpressionForNonCompoent(eventName, parent)); } } }; - return { - handleEventHandler, - getEvents: () => events, - }; + getEvents() { + return this.events; + } + + eventMapToString() { + return '{' + Array.from(this.events.entries()).map(eventMapEntryToString).join(', ') + '}'; + } } function getEventDefExpressionForNonCompoent(eventName: string, ele: Node) { @@ -44,10 +47,6 @@ function getEventDefExpressionForNonCompoent(eventName: string, ele: Node) { } } -export function eventMapToString(events: Map) { - return '{' + Array.from(events.entries()).map(eventMapEntryToString).join(', ') + '}'; -} - function eventMapEntryToString([eventName, expression]: [string, string | string[]]) { return `'${eventName}':${ Array.isArray(expression) ? `__sveltets_unionType(${expression.join(',')})` : expression diff --git a/packages/svelte2tsx/src/svelte2tsx.ts b/packages/svelte2tsx/src/svelte2tsx.ts index 34499ad9b..26ba91c8e 100644 --- a/packages/svelte2tsx/src/svelte2tsx.ts +++ b/packages/svelte2tsx/src/svelte2tsx.ts @@ -6,12 +6,21 @@ 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 { EventHandler } from './nodes/event-handler'; import { findExortKeyword, getBinaryAssignmentExpr } from './utils/tsAst'; -import { InstanceScriptProcessResult, CreateRenderFunctionPara } from './interfaces'; +import { + InstanceScriptProcessResult, + CreateRenderFunctionPara, + AddComponentExportPara, +} 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 +56,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 { @@ -286,7 +295,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult { str.remove(node.start, node.end); }; - const { handleEventHandler, getEvents } = createEventHandlerTransformer(); + const eventHandler = new EventHandler(); const onHtmlxWalk = (node: Node, parent: Node, prop: string) => { if ( @@ -325,7 +334,7 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult { enterArrowFunctionExpression(); break; case 'EventHandler': - handleEventHandler(node, parent); + eventHandler.handleEventHandler(node, parent); break; case 'VariableDeclarator': isDeclaration = true; @@ -371,14 +380,18 @@ function processSvelteTemplate(str: MagicString): TemplateProcessResult { moduleScriptTag, scriptTag, slots, - events: getEvents(), + events: new ComponentEventsFromEventsMap(eventHandler), 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 +676,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 +818,7 @@ function processInstanceScriptContent(str: MagicString, script: Node): InstanceS return { exportedNames, + events, uses$$props, uses$$restProps, getters, @@ -821,25 +839,26 @@ function formatComponentDocumentation(contents?: string | null) { return `/**\n${lines}\n */\n`; } -function addComponentExport( - str: MagicString, - uses$$propsOr$$restProps: boolean, - strictMode: boolean, - isTsFile: boolean, - getters: Set, - /** A named export allows for TSDoc-compatible docstrings */ - className?: string, - componentDocumentation?: string | null, -) { +function addComponentExport({ + str, + uses$$propsOr$$restProps, + strictMode, + strictEvents, + isTsFile, + getters, + className, + componentDocumentation, +}: AddComponentExportPara) { + 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); @@ -933,7 +952,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 +1003,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 @@ -1012,15 +1031,16 @@ export function svelte2tsx( const className = options?.filename && classNameFromFilename(options?.filename); - addComponentExport( + addComponentExport({ str, - uses$$props || uses$$restProps, - !!options?.strictMode, - options?.isTsFile, + uses$$propsOr$$restProps: uses$$props || uses$$restProps, + strictMode: !!options?.strictMode, + strictEvents: events instanceof ComponentEventsFromInterface, + isTsFile: options?.isTsFile, getters, className, componentDocumentation, - ); + }); str.prepend('///\n'); @@ -1028,5 +1048,6 @@ export function svelte2tsx( code: str.toString(), map: str.generateMap({ hires: true, source: options?.filename }), exportedNames, + events, }; } diff --git a/packages/svelte2tsx/src/utils/tsAst.ts b/packages/svelte2tsx/src/utils/tsAst.ts index c6783c271..9f8dc2cd9 100644 --- a/packages/svelte2tsx/src/utils/tsAst.ts +++ b/packages/svelte2tsx/src/utils/tsAst.ts @@ -99,3 +99,24 @@ export function isMember( ): node is ts.ElementAccessExpression | ts.PropertyAccessExpression { return ts.isElementAccessExpression(node) || ts.isPropertyAccessExpression(node); } + +/** + * Returns variable at given level with given name, + * if it is a variable declaration in the form of `const/let a = ..` + */ +export function getVariableAtTopLevel( + node: ts.SourceFile, + identifierName: string, +): ts.VariableDeclaration | undefined { + for (const child of node.statements) { + if (ts.isVariableStatement(child)) { + const variable = child.declarationList.declarations.find( + (declaration) => + ts.isIdentifier(declaration.name) && declaration.name.text === identifierName, + ); + if (variable) { + return variable; + } + } + } +} 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..b4385c1a1 --- /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: '\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.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..4432bacdc --- /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: '\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 new file mode 100644 index 000000000..80f502100 --- /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..a87e07b87 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-events-interface/input.svelte @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx index 97c57ece0..d7ca04842 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-multiple-slots/expected.tsx @@ -12,5 +12,5 @@ ); return { props: {}, slots: {default: {a:b}, test: {c:d, e:e}}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx index 73f8c5f18..aab35007d 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-slot-crazy-attributes/expected.tsx @@ -9,5 +9,5 @@ ); return { props: {}, slots: {default: {a:b, b:b, c:"b", d:"__svelte_ts_string", e:b}}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-documentation/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-documentation/expected.tsx index e2e31ce60..a0c0607e7 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-documentation/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-documentation/expected.tsx @@ -6,5 +6,5 @@ return { props: {}, slots: {}, getters: {}, events: {} }} /** This component does nothing at all */ -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-indented-multiline-documentation/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-indented-multiline-documentation/expected.tsx index a5084cb61..f5306957f 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-indented-multiline-documentation/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-indented-multiline-documentation/expected.tsx @@ -18,5 +18,5 @@ return { props: {}, slots: {}, getters: {}, events: {} }} * * The output should be indented properly! */ -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-multiline-documentation/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-multiline-documentation/expected.tsx index 27a5d3918..8e8bfe3b1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/component-with-multiline-documentation/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/component-with-multiline-documentation/expected.tsx @@ -12,5 +12,5 @@ return { props: {}, slots: {}, getters: {}, events: {} }} * type Type = 'type' * ``` */ -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component-multi/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component-multi/expected.tsx index 621f7c0d8..661f10c66 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component-multi/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component-multi/expected.tsx @@ -4,5 +4,5 @@ return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_unionType(__sveltets_bubbleEventDef(__sveltets_instanceOf(Button).$$events_def, 'click'),__sveltets_bubbleEventDef(__sveltets_instanceOf(Radio).$$events_def, 'click'))} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component/expected.tsx index bdc27b032..8281ef150 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-component/expected.tsx @@ -3,5 +3,5 @@ <> return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_bubbleEventDef(__sveltets_instanceOf(Button).$$events_def, 'click')} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-element/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-element/expected.tsx index 6710d4a4b..9fd63eb8a 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-element/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-element/expected.tsx @@ -3,5 +3,5 @@ <> return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_mapElementEvent('click')} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-svelte-element/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-svelte-element/expected.tsx index c598e41c5..436cb1c8d 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-svelte-element/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/event-bubble-svelte-element/expected.tsx @@ -4,5 +4,5 @@ return { props: {}, slots: {}, getters: {}, events: {'click':__sveltets_mapBodyEvent('click'), 'resize':__sveltets_mapWindowEvent('resize')} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-class/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-class/expected.tsx index f21b9629a..4e1402f4e 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/export-class/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-class/expected.tsx @@ -6,6 +6,6 @@ () => (<>); return { props: {}, slots: {}, getters: {Foo: Foo}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { get Foo() { return render().getters.Foo } } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-strictMode/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-strictMode/expected.tsx index 3e5c93b06..d731cbac5 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/export-js-strictMode/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-js-strictMode/expected.tsx @@ -8,5 +8,5 @@ () => (<>); return { props: {a: a , b: b , c: c}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx index 3ca0614ee..5bb2ebadb 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-list/expected.tsx @@ -8,5 +8,5 @@ () => (<>); return { props: {name: name , name2: name2}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-references-local/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-references-local/expected.tsx index 234939209..0b0ef8dd9 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/export-references-local/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-references-local/expected.tsx @@ -7,5 +7,5 @@ () => (<>); return { props: {name: name}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-with-default-multi/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-with-default-multi/expected.tsx index fbf88ce39..9df45baff 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/export-with-default-multi/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-with-default-multi/expected.tsx @@ -8,5 +8,5 @@ () => (<>); return { props: {name: name , world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/import-single-quote/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/import-single-quote/expected.tsx index 1117880a1..7196d3624 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/import-single-quote/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/import-single-quote/expected.tsx @@ -9,5 +9,5 @@ function render() { ); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/imports/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/imports/expected.tsx index 5fead24df..49c27ce6d 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/imports/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/imports/expected.tsx @@ -13,5 +13,5 @@ function render() { ); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line/expected.tsx index 18a9f0cdc..26a68e810 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line/expected.tsx @@ -4,5 +4,5 @@ () => (<>

hello {world}

); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line2/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line2/expected.tsx index 18a9f0cdc..26a68e810 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line2/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script-in-line2/expected.tsx @@ -4,5 +4,5 @@ () => (<>

hello {world}

); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script/expected.tsx index c08ce8ace..203ff83db 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script/expected.tsx @@ -11,5 +11,5 @@

hello {world}

); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script2/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script2/expected.tsx index a2c73cdc3..a8356b8c2 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script2/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/module-script-and-script2/expected.tsx @@ -11,5 +11,5 @@ ); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +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 cca3234ba..95949af67 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-script/expected.tsx @@ -25,5 +25,5 @@ const test4 = ({a, b: { $top1: $top2 }}) => $top2 && __sveltets_store_get(top1) () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } 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 ab12e85ae..c5d3a91d1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/nested-$-variables-template/expected.tsx @@ -25,5 +25,5 @@ }}>Hi return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/object-binding-export/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/object-binding-export/expected.tsx index ee5af6283..257c8a78f 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/object-binding-export/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/object-binding-export/expected.tsx @@ -6,5 +6,5 @@ () => (<>); return { props: {rename: rename}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } 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 356f3a603..368fbd692 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-$store-destructuring/expected.tsx @@ -20,5 +20,5 @@ $: ([ bla4, bla5 ] = __sveltets_invalidate(() => __sveltets_store_get(data))) () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +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/reactive-block/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-block/expected.tsx index f1a6c18ec..a54ee57b9 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-block/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-block/expected.tsx @@ -9,5 +9,5 @@ let a: 1 | 2 = 1; () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare-object/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare-object/expected.tsx index d7a8f68c1..ba0e4db58 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare-object/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare-object/expected.tsx @@ -7,5 +7,5 @@ let b = __sveltets_invalidate(() => ({ a: 1 })); () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare/expected.tsx index e37c89697..378f874eb 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-declare/expected.tsx @@ -10,5 +10,5 @@ $: a = __sveltets_invalidate(() => 5); () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } 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 6965d50eb..f058a4e05 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/reactive-store-set/expected.tsx @@ -6,5 +6,5 @@ () => (<>); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports/expected.tsx index c8820910e..04295e89b 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/renamed-exports/expected.tsx @@ -8,5 +8,5 @@ () => (<>); return { props: {name3: name , name4: name2}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/script-and-module-script/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/script-and-module-script/expected.tsx index 2d1f094e4..64b5a9afe 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/script-and-module-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/script-and-module-script/expected.tsx @@ -11,5 +11,5 @@ ); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/script-inside-head-after-toplevel-script/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/script-inside-head-after-toplevel-script/expected.tsx index 734f66743..ebcd31ca6 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/script-inside-head-after-toplevel-script/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/script-inside-head-after-toplevel-script/expected.tsx @@ -15,5 +15,5 @@ ); return { props: {}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/script-on-bottom/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/script-on-bottom/expected.tsx index 70c0cca38..6acfa504a 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/script-on-bottom/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/script-on-bottom/expected.tsx @@ -7,5 +7,5 @@ ); return { props: {world: world}, slots: {}, getters: {}, events: {} }} -export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(render)) { +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/script-style-like-component/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/script-style-like-component/expected.tsx index 498453d9c..c5b38a4c1 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/script-style-like-component/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/script-style-like-component/expected.tsx @@ -11,5 +11,5 @@