From ff6ebc9e86d0cf4a3b7f387623dae36bca80baa8 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 7 Sep 2020 13:52:54 +0200 Subject: [PATCH] (feat) show doc for props (hover/completion) #306 --- .../src/svelte2tsx/nodes/ExportedNames.ts | 66 +++++++++++++++++-- .../processInstanceScriptContent.ts | 30 ++------- .../samples/export-doc/expected.tsx | 24 +++++++ .../samples/export-doc/input.svelte | 11 ++++ .../export-with-default-multi/expected.tsx | 4 +- .../samples/ts-export-doc/expected.tsx | 24 +++++++ .../samples/ts-export-doc/input.svelte | 11 ++++ 7 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/export-doc/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/input.svelte diff --git a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts index d2a11528a..3fd6269ee 100644 --- a/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts +++ b/packages/svelte2tsx/src/svelte2tsx/nodes/ExportedNames.ts @@ -1,3 +1,5 @@ +import ts from 'typescript'; + export interface IExportedNames { has(name: string): boolean; } @@ -9,9 +11,55 @@ export class ExportedNames type?: string; identifierText?: string; required?: boolean; + doc?: string; } > implements IExportedNames { + /** + * Adds export to map + */ + addExport( + name: ts.BindingName, + target: ts.BindingName = null, + type: ts.TypeNode = null, + required = false, + ): void { + if (name.kind != ts.SyntaxKind.Identifier) { + throw Error('export source kind not supported ' + name); + } + if (target && target.kind != ts.SyntaxKind.Identifier) { + throw Error('export target kind not supported ' + target); + } + + if (target) { + this.set(name.text, { + type: type?.getText(), + identifierText: (target as ts.Identifier).text, + required, + doc: this.getDoc(target), + }); + } else { + this.set(name.text, {}); + } + } + + private getDoc(target: ts.BindingName) { + let doc = undefined; + // Traverse `a` up to `export let a` + const exportExpr = target?.parent?.parent?.parent; + + if (exportExpr) { + const fileText = exportExpr.getSourceFile().getFullText(); + const comment = ts.getLeadingCommentRanges(fileText, exportExpr.getFullStart()); + + if (comment) { + doc = fileText.substring(comment[0].pos, comment[0].end); + } + } + + return doc; + } + /** * Creates a string from the collected props * @@ -19,24 +67,28 @@ export class ExportedNames */ createPropsStr(isTsFile: boolean) { const names = Array.from(this.entries()); + const dontAddTypeDef = + !isTsFile || + names.length === 0 || + names.every(([_, value]) => !value.type && value.required); const returnElements = names.map(([key, value]) => { // Important to not use shorthand props for rename functionality - return `${value.identifierText || key}: ${key}`; + return `${dontAddTypeDef && value.doc ? `\n${value.doc}` : ''}${ + value.identifierText || key + }: ${key}`; }); - if ( - !isTsFile || - names.length === 0 || - names.every(([_, value]) => !value.type && value.required) - ) { + if (dontAddTypeDef) { // No exports or only `typeof` exports -> omit the `as {...}` completely. // If not TS, omit the types to not have a "cannot use types in jsx" error. return `{${returnElements.join(' , ')}}`; } const returnElementsType = names.map(([key, value]) => { - const identifier = `${value.identifierText || key}${value.required ? '' : '?'}`; + const identifier = `${value.doc ? `\n${value.doc}` : ''}${value.identifierText || key}${ + value.required ? '' : '?' + }`; if (!value.type) { return `${identifier}: typeof ${key}`; } diff --git a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts index 5e214508e..1d9058528 100644 --- a/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts +++ b/packages/svelte2tsx/src/svelte2tsx/processInstanceScriptContent.ts @@ -58,28 +58,6 @@ export function processInstanceScriptContent( const pushScope = () => (scope = new Scope(scope)); const popScope = () => (scope = scope.parent); - const addExport = ( - name: ts.BindingName, - target: ts.BindingName = null, - type: ts.TypeNode = null, - required = false, - ) => { - if (name.kind != ts.SyntaxKind.Identifier) { - throw Error('export source kind not supported ' + name); - } - if (target && target.kind != ts.SyntaxKind.Identifier) { - throw Error('export target kind not supported ' + target); - } - if (target) { - exportedNames.set(name.text, { - type: type?.getText(), - identifierText: (target as ts.Identifier).text, - required, - }); - } else { - exportedNames.set(name.text, {}); - } - }; const addGetter = (node: ts.Identifier) => { if (!node) { return; @@ -277,14 +255,14 @@ export function processInstanceScriptContent( ts.forEachChild(list, (node) => { if (ts.isVariableDeclaration(node)) { if (ts.isIdentifier(node.name)) { - addExport(node.name, node.name, node.type, !node.initializer); + exportedNames.addExport(node.name, node.name, node.type, !node.initializer); } else if ( ts.isObjectBindingPattern(node.name) || ts.isArrayBindingPattern(node.name) ) { ts.forEachChild(node.name, (element) => { if (ts.isBindingElement(element)) { - addExport(element.name); + exportedNames.addExport(element.name); } }); } @@ -376,9 +354,9 @@ export function processInstanceScriptContent( if (ts.isNamedExports(exportClause)) { for (const ne of exportClause.elements) { if (ne.propertyName) { - addExport(ne.propertyName, ne.name); + exportedNames.addExport(ne.propertyName, ne.name); } else { - addExport(ne.name); + exportedNames.addExport(ne.name); } } //we can remove entire statement diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx new file mode 100644 index 000000000..194fbaf3c --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/expected.tsx @@ -0,0 +1,24 @@ +/// +<>;function render() { + + /** + * DOCS! + */ + let a; + /** + * MORE DOCS! + */ + let b; + let c; +; +() => (<>); +return { props: { +/** + * DOCS! + */a: a , +/** + * MORE DOCS! + */b: b , c: c}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/input.svelte new file mode 100644 index 000000000..da4d456b3 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/export-doc/input.svelte @@ -0,0 +1,11 @@ + 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 9df45baff..df5b73354 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 @@ -6,7 +6,9 @@ world = ''; ; () => (<>); -return { props: {name: name , world: world}, slots: {}, getters: {}, events: {} }} +return { props: { +/**@type { string | number }*/name: name , +/**@type { string | number }*/world: world}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { } diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx new file mode 100644 index 000000000..b85854a12 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/expected.tsx @@ -0,0 +1,24 @@ +/// +<>;function render() { + + /** + * DOCS! + */ + let a: string; + /** + * MORE DOCS! + */ + let b = 1; + let c; +; +() => (<>); +return { props: {a: a , b: b , c: c} as { +/** + * DOCS! + */a: string, +/** + * MORE DOCS! + */b?: typeof b, c: typeof c}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} \ No newline at end of file diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/input.svelte new file mode 100644 index 000000000..7ef6b1241 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/ts-export-doc/input.svelte @@ -0,0 +1,11 @@ +