From 582b76a07fbe2f2353be44b74c762344a0668ac5 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei-Da" <36730922+jasonlyu123@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:19:05 +0800 Subject: [PATCH] feat: ts5.2 clickable inlay parameter hints (#2162) --- packages/language-server/src/ls-config.ts | 10 ++- .../typescript/features/InlayHintProvider.ts | 75 +++++++++++++++---- .../component-handler/expectedv2.json | 30 +++++--- .../fixtures/component-handler/input.svelte | 4 +- .../expectedv2.json | 26 +++++++ .../disable-argument-name-match/input.svelte | 7 ++ .../disable-type-name-match/expectedv2.json | 8 ++ .../disable-type-name-match/input.svelte | 3 + .../fixtures/element-handler/expectedv2.json | 24 +++--- .../fixtures/element-handler/input.svelte | 3 +- .../function-call-in-props/expectedv2.json | 24 +++--- .../property-declaration/expectedv2.json | 1 + .../property-declaration/input.svelte | 5 ++ .../fixtures/reactive-block/expectedv2.json | 49 ++++++++---- .../fixtures/reactive-block/input.svelte | 9 ++- .../features/inlayHints/index.test.ts | 34 +++++++-- 16 files changed, 245 insertions(+), 67 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/input.svelte create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/expectedv2.json create mode 100644 packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/input.svelte diff --git a/packages/language-server/src/ls-config.ts b/packages/language-server/src/ls-config.ts index 623a139b8..070fe0cd8 100644 --- a/packages/language-server/src/ls-config.ts +++ b/packages/language-server/src/ls-config.ts @@ -247,7 +247,7 @@ export interface TsInlayHintsConfig { | undefined; parameterTypes: { enabled: boolean } | undefined; propertyDeclarationTypes: { enabled: boolean } | undefined; - variableTypes: { enabled: boolean } | undefined; + variableTypes: { enabled: boolean; suppressWhenTypeMatchesName: boolean } | undefined; } export type TsUserConfigLang = 'typescript' | 'javascript'; @@ -440,9 +440,13 @@ export class LSConfigManager { includeInlayFunctionLikeReturnTypeHints: inlayHints?.functionLikeReturnTypes?.enabled, includeInlayParameterNameHints: inlayHints?.parameterNames?.enabled, includeInlayParameterNameHintsWhenArgumentMatchesName: - inlayHints?.parameterNames?.suppressWhenArgumentMatchesName, + inlayHints?.parameterNames?.suppressWhenArgumentMatchesName === false, includeInlayFunctionParameterTypeHints: inlayHints?.parameterTypes?.enabled, - includeInlayVariableTypeHints: inlayHints?.variableTypes?.enabled + includeInlayVariableTypeHints: inlayHints?.variableTypes?.enabled, + includeInlayPropertyDeclarationTypeHints: inlayHints?.propertyDeclarationTypes?.enabled, + includeInlayVariableTypeHintsWhenTypeMatchesName: + inlayHints?.variableTypes?.suppressWhenTypeMatchesName === false, + interactiveInlayHints: true }; } diff --git a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts index e90bbd469..1c3ba13af 100644 --- a/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/InlayHintProvider.ts @@ -1,7 +1,13 @@ import ts from 'typescript'; import { CancellationToken } from 'vscode-languageserver'; -import { Position, Range, InlayHint, InlayHintKind } from 'vscode-languageserver-types'; -import { Document, isInTag } from '../../../lib/documents'; +import { + Position, + Range, + InlayHint, + InlayHintKind, + InlayHintLabelPart +} from 'vscode-languageserver-types'; +import { Document, isInTag, mapLocationToOriginal } from '../../../lib/documents'; import { getAttributeContextAtPosition } from '../../../lib/documents/parseHtml'; import { InlayHintProvider } from '../../interfaces'; import { DocumentSnapshot, SvelteDocumentSnapshot } from '../DocumentSnapshot'; @@ -11,8 +17,10 @@ import { isInGeneratedCode, findChildOfKind, findRenderFunction, - findClosestContainingNode + findClosestContainingNode, + SnapshotMap } from './utils'; +import { convertRange } from '../utils'; export class InlayHintProviderImpl implements InlayHintProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} @@ -52,7 +60,10 @@ export class InlayHintProviderImpl implements InlayHintProvider { const renderFunctionReturnTypeLocation = renderFunction && this.getTypeAnnotationPosition(renderFunction); - const result = inlayHints + const snapshotMap = new SnapshotMap(this.lsAndTsDocResolver); + snapshotMap.set(tsDoc.filePath, tsDoc); + + const convertPromises = inlayHints .filter( (inlayHint) => !isInGeneratedCode(tsDoc.getFullText(), inlayHint.position) && @@ -61,21 +72,20 @@ export class InlayHintProviderImpl implements InlayHintProvider { !this.isGeneratedVariableTypeHint(sourceFile, inlayHint) && !this.isGeneratedFunctionReturnType(sourceFile, inlayHint) ) - .map((inlayHint) => ({ - label: inlayHint.text, + .map(async (inlayHint) => ({ + label: await this.convertInlayHintLabelParts(inlayHint, snapshotMap), position: this.getOriginalPosition(document, tsDoc, inlayHint), kind: this.convertInlayHintKind(inlayHint.kind), paddingLeft: inlayHint.whitespaceBefore, paddingRight: inlayHint.whitespaceAfter - })) - .filter( - (inlayHint) => - inlayHint.position.line >= 0 && - inlayHint.position.character >= 0 && - !this.checkGeneratedFunctionHintWithSource(inlayHint, document) - ); + })); - return result; + return (await Promise.all(convertPromises)).filter( + (inlayHint) => + inlayHint.position.line >= 0 && + inlayHint.position.character >= 0 && + !this.checkGeneratedFunctionHintWithSource(inlayHint, document) + ); } private areInlayHintsEnabled(preferences: ts.UserPreferences) { @@ -106,6 +116,43 @@ export class InlayHintProviderImpl implements InlayHintProvider { }; } + private async convertInlayHintLabelParts(inlayHint: ts.InlayHint, snapshotMap: SnapshotMap) { + if (!inlayHint.displayParts) { + return inlayHint.text; + } + + const convertPromises = inlayHint.displayParts.map( + async (part): Promise => { + if (!part.file || !part.span) { + return { + value: part.text + }; + } + + const snapshot = await snapshotMap.retrieve(part.file); + if (!snapshot) { + return { + value: part.text + }; + } + + const originalLocation = mapLocationToOriginal( + snapshot, + convertRange(snapshot, part.span) + ); + + return { + value: part.text, + location: originalLocation.range.start.line < 0 ? undefined : originalLocation + }; + } + ); + + const parts = await Promise.all(convertPromises); + + return parts; + } + private getOriginalPosition( document: Document, tsDoc: SvelteDocumentSnapshot, diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json index d995d6142..dc08238cf 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/expectedv2.json @@ -1,19 +1,31 @@ [ + { + "label": ": void", + "position": { "line": 5, "character": 30 }, + "kind": 1, + "paddingLeft": true + }, { "label": ": MouseEvent", - "position": { - "line": 7, - "character": 22 - }, + "position": { "line": 9, "character": 22 }, "kind": 1, "paddingLeft": true }, { - "label": "message:", - "position": { - "line": 7, - "character": 38 - }, + "label": [ + { + "value": "message", + "location": { + "range": { + "start": { "line": 5, "character": 17 }, + "end": { "line": 5, "character": 24 } + }, + "uri": "/component-handler/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 9, "character": 30 }, "kind": 2, "paddingRight": true } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/input.svelte index f634486c5..663197d44 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/component-handler/input.svelte @@ -2,7 +2,9 @@ import { SvelteComponentTyped } from 'svelte'; let Component: typeof SvelteComponentTyped<{}, { click: MouseEvent }, {}>; + + function log(message: any) {} - console.log(e)}> \ No newline at end of file + log(e)}> \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/expectedv2.json new file mode 100644 index 000000000..4bba9e794 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/expectedv2.json @@ -0,0 +1,26 @@ +[ + { + "label": ": void", + "position": { "line": 1, "character": 33 }, + "kind": 1, + "paddingLeft": true + }, + { + "label": [ + { + "value": "message", + "location": { + "range": { + "start": { "line": 1, "character": 17 }, + "end": { "line": 1, "character": 24 } + }, + "uri": "/disable-argument-name-match/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 5, "character": 8 }, + "kind": 2, + "paddingRight": true + } +] diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/input.svelte new file mode 100644 index 000000000..e27ba1a32 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-argument-name-match/input.svelte @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/expectedv2.json new file mode 100644 index 000000000..abfa8410b --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/expectedv2.json @@ -0,0 +1,8 @@ +[ + { + "label": ": number", + "position": { "line": 1, "character": 14 }, + "kind": 1, + "paddingLeft": true + } +] diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/input.svelte new file mode 100644 index 000000000..baa03009f --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/disable-type-name-match/input.svelte @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json index 74a8d1cd7..aac643248 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/expectedv2.json @@ -1,19 +1,25 @@ [ { "label": ": MouseEvent & { currentTarge...", - "position": { - "line": 4, - "character": 19 - }, + "position": { "line": 5, "character": 19 }, "kind": 1, "paddingLeft": true }, { - "label": "message:", - "position": { - "line": 4, - "character": 35 - }, + "label": [ + { + "value": "message", + "location": { + "range": { + "start": { "line": 1, "character": 17 }, + "end": { "line": 1, "character": 24 } + }, + "uri": "/element-handler/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 5, "character": 27 }, "kind": 2, "paddingRight": true } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/input.svelte index c83b27296..2484de219 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/element-handler/input.svelte @@ -1,5 +1,6 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/function-call-in-props/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/function-call-in-props/expectedv2.json index d8dcb08fc..42563e736 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/function-call-in-props/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/function-call-in-props/expectedv2.json @@ -1,19 +1,25 @@ [ { "label": ": void", - "position": { - "line": 4, - "character": 29 - }, + "position": { "line": 4, "character": 29 }, "kind": 1, "paddingLeft": true }, { - "label": "name:", - "position": { - "line": 7, - "character": 20 - }, + "label": [ + { + "value": "name", + "location": { + "range": { + "start": { "line": 4, "character": 16 }, + "end": { "line": 4, "character": 20 } + }, + "uri": "/function-call-in-props/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 7, "character": 20 }, "kind": 2, "paddingRight": true } diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/expectedv2.json new file mode 100644 index 000000000..a597d047d --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/expectedv2.json @@ -0,0 +1 @@ +[{ "label": ": number", "position": { "line": 2, "character": 9 }, "kind": 1, "paddingLeft": true }] diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/input.svelte new file mode 100644 index 000000000..933fe8672 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/property-declaration/input.svelte @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/expectedv2.json b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/expectedv2.json index 913eda6fe..4c8ddbad2 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/expectedv2.json +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/expectedv2.json @@ -1,29 +1,50 @@ [ { "label": ": number", - "position": { - "line": 1, - "character": 12 - }, + "position": { "line": 1, "character": 12 }, "kind": 1, "paddingLeft": true }, { - "label": "message:", - "position": { - "line": 2, - "character": 19 - }, + "label": [ + { + "value": "message", + "location": { + "range": { + "start": { "line": 10, "character": 17 }, + "end": { "line": 10, "character": 24 } + }, + "uri": "/reactive-block/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 2, "character": 11 }, "kind": 2, "paddingRight": true }, { - "label": "message:", - "position": { - "line": 4, - "character": 20 - }, + "label": [ + { + "value": "message", + "location": { + "range": { + "start": { "line": 10, "character": 17 }, + "end": { "line": 10, "character": 24 } + }, + "uri": "/reactive-block/input.svelte" + } + }, + { "value": ":" } + ], + "position": { "line": 4, "character": 12 }, "kind": 2, "paddingRight": true + }, + { + "label": ": any", + "position": { "line": 10, "character": 24 }, + "kind": 1, + "paddingLeft": true } ] diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/input.svelte b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/input.svelte index b5588ed8f..7b6c8b005 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/input.svelte +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/fixtures/reactive-block/input.svelte @@ -1,7 +1,12 @@ \ No newline at end of file diff --git a/packages/language-server/test/plugins/typescript/features/inlayHints/index.test.ts b/packages/language-server/test/plugins/typescript/features/inlayHints/index.test.ts index f9a8cc35e..578f8f0e9 100644 --- a/packages/language-server/test/plugins/typescript/features/inlayHints/index.test.ts +++ b/packages/language-server/test/plugins/typescript/features/inlayHints/index.test.ts @@ -12,6 +12,7 @@ import { createSnapshotTester, updateSnapshotIfFailedOrEmpty } from '../../test-utils'; +import { InlayHint } from 'vscode-languageserver-types'; function setup(workspaceDir: string, filePath: string) { const docManager = new DocumentManager( @@ -24,7 +25,7 @@ function setup(workspaceDir: string, filePath: string) { parameterNames: { enabled: 'all', suppressWhenArgumentMatchesName: false }, parameterTypes: { enabled: true }, propertyDeclarationTypes: { enabled: true }, - variableTypes: { enabled: true } + variableTypes: { enabled: true, suppressWhenTypeMatchesName: false } }; configManager.updateTsJsUserPreferences({ typescript: { @@ -59,10 +60,13 @@ async function executeTest( ) { const expected = 'expectedv2.json'; const { plugin, document } = setup(workspaceDir, inputFile); - const inlayHints = await plugin.getInlayHints(document, { - start: { line: 0, character: 0 }, - end: document.positionAt(document.getTextLength()) - }); + const workspaceUri = pathToUrl(workspaceDir); + const inlayHints = sanitizeUri( + await plugin.getInlayHints(document, { + start: { line: 0, character: 0 }, + end: document.positionAt(document.getTextLength()) + }) + ); const expectedFile = join(dir, expected); if (process.argv.includes('--debug')) { @@ -85,6 +89,26 @@ async function executeTest( rootDir: __dirname }); + function sanitizeUri(inlayHints: InlayHint[] | null) { + if (!inlayHints) { + return; + } + + for (const inlayHint of inlayHints) { + if (!Array.isArray(inlayHint.label)) { + continue; + } + + for (const label of inlayHint.label) { + if (label.location) { + label.location.uri = label.location.uri.replace(workspaceUri, ''); + } + } + } + + return inlayHints; + } + function appendInlayHintAsComment() { if (!inlayHints) { return document.getText();