Skip to content

Commit 66b4ba4

Browse files
Add inlay hints support (#42089)
* Add signature arguments label support * Support rest parameters and destruction * make lint * Fix tuple rest parameters * Adjust name styles * Rename to inline hints * Partition inline hints * Adjust range pred * Add function expression like hints * Support configure inline hints * Display hints in single line * Add test suits and tests * Add range tests * Support more hints * Add more options * Fix logical * Add more cases * Support call chains * Rename options * Match lastest protocol * Update protocol changes * Support context value and hover message * Revert "Support context value and hover message" This reverts commit 37a7089. * Revert "Update protocol changes" This reverts commit e5ca31b. * Add hover message * Accept baseline * Update src/services/inlineHints.ts Co-authored-by: Daniel Rosenwasser <[email protected]> * Update src/services/inlineHints.ts Co-authored-by: Daniel Rosenwasser <[email protected]> * Cache across the program * Fix possible undefined * Update protocol changes * Fix missing property * Make lint happy * Avoid call chain hints * I'm bad * Add whitespace before type * Add more tests * Should care about jsdoc * Support complex rest parameter * Avoid module symbol hints * Care about leading comments * Fix CR issues * Avoid changes * Simplify comments contains * Fix CR issues * Accept baseline * Check parameter name before create regex * Rename option * Avoid makers * Skip parens for argument * Fix CR issues * Fix enums * Accept baseline Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 2767ab3 commit 66b4ba4

File tree

71 files changed

+1829
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1829
-2
lines changed

Diff for: src/compiler/checker.ts

+34
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ namespace ts {
428428
return node ? getTypeFromTypeNode(node) : errorType;
429429
},
430430
getParameterType: getTypeAtPosition,
431+
getParameterIdentifierNameAtPosition,
431432
getPromisedTypeOfPromise,
432433
getAwaitedType: type => getAwaitedType(type),
433434
getReturnTypeOfSignature,
@@ -30352,6 +30353,39 @@ namespace ts {
3035230353
return restParameter.escapedName;
3035330354
}
3035430355

30356+
function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined {
30357+
const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
30358+
if (pos < paramCount) {
30359+
const param = signature.parameters[pos];
30360+
return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined;
30361+
}
30362+
30363+
const restParameter = signature.parameters[paramCount] || unknownSymbol;
30364+
if (!isParameterDeclarationWithIdentifierName(restParameter)) {
30365+
return undefined;
30366+
}
30367+
30368+
const restType = getTypeOfSymbol(restParameter);
30369+
if (isTupleType(restType)) {
30370+
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
30371+
const index = pos - paramCount;
30372+
const associatedName = associatedNames?.[index];
30373+
const isRestTupleElement = !!associatedName?.dotDotDotToken;
30374+
return associatedName ? [
30375+
getTupleElementLabel(associatedName),
30376+
isRestTupleElement
30377+
] : undefined;
30378+
}
30379+
30380+
if (pos === paramCount) {
30381+
return [restParameter.escapedName, true];
30382+
}
30383+
return undefined;
30384+
}
30385+
30386+
function isParameterDeclarationWithIdentifierName(symbol: Symbol) {
30387+
return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name);
30388+
}
3035530389
function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) {
3035630390
return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name));
3035730391
}

Diff for: src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4104,6 +4104,7 @@ namespace ts {
41044104
* Returns `any` if the index is not valid.
41054105
*/
41064106
/* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type;
4107+
/* @internal */ getParameterIdentifierNameAtPosition(signature: Signature, parameterIndex: number): [parameterName: __String, isRestParameter: boolean] | undefined;
41074108
getNullableType(type: Type, flags: TypeFlags): Type;
41084109
getNonNullableType(type: Type): Type;
41094110
/* @internal */ getNonOptionalType(type: Type): Type;

Diff for: src/compiler/utilitiesPublic.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,11 @@ namespace ts {
12321232
return node && isFunctionLikeDeclarationKind(node.kind);
12331233
}
12341234

1235+
/* @internal */
1236+
export function isBooleanLiteral(node: Node): node is BooleanLiteral {
1237+
return node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword;
1238+
}
1239+
12351240
function isFunctionLikeDeclarationKind(kind: SyntaxKind): boolean {
12361241
switch (kind) {
12371242
case SyntaxKind.FunctionDeclaration:

Diff for: src/harness/client.ts

+14
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,20 @@ namespace ts.server {
644644

645645
applyCodeActionCommand = notImplemented;
646646

647+
provideInlayHints(file: string, span: TextSpan): InlayHint[] {
648+
const { start, length } = span;
649+
const args: protocol.InlayHintsRequestArgs = { file, start, length };
650+
651+
const request = this.processRequest<protocol.InlayHintsRequest>(CommandNames.ProvideInlayHints, args);
652+
const response = this.processResponse<protocol.InlayHintsResponse>(request);
653+
654+
return response.body!.map(item => ({ // TODO: GH#18217
655+
...item,
656+
kind: item.kind as InlayHintKind | undefined,
657+
position: this.lineOffsetToPosition(file, item.position),
658+
}));
659+
}
660+
647661
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
648662
return typeof positionOrRange === "number"
649663
? this.createFileLocationRequestArgs(fileName, positionOrRange)

Diff for: src/harness/fourslashImpl.ts

+17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
namespace FourSlash {
23
import ArrayOrSingle = FourSlashInterface.ArrayOrSingle;
34

@@ -836,6 +837,22 @@ namespace FourSlash {
836837
});
837838
}
838839

840+
public verifyInlayHints(expected: readonly FourSlashInterface.VerifyInlayHintsOptions[], span: ts.TextSpan = { start: 0, length: this.activeFile.content.length }, preference?: ts.InlayHintsOptions) {
841+
const hints = this.languageService.provideInlayHints(this.activeFile.fileName, span, preference);
842+
assert.equal(hints.length, expected.length, "Number of hints");
843+
844+
const sortHints = (a: ts.InlayHint, b: ts.InlayHint) => {
845+
return a.position - b.position;
846+
};
847+
ts.zipWith(hints.sort(sortHints), [...expected].sort(sortHints), (actual, expected) => {
848+
assert.equal(actual.text, expected.text, "Text");
849+
assert.equal(actual.position, expected.position, "Position");
850+
assert.equal(actual.kind, expected.kind, "Kind");
851+
assert.equal(actual.whitespaceBefore, expected.whitespaceBefore, "whitespaceBefore");
852+
assert.equal(actual.whitespaceAfter, expected.whitespaceAfter, "whitespaceAfter");
853+
});
854+
}
855+
839856
public verifyCompletions(options: FourSlashInterface.VerifyCompletionsOptions) {
840857
if (options.marker === undefined) {
841858
this.verifyCompletionsWorker(options);

Diff for: src/harness/fourslashInterfaceImpl.ts

+12
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,10 @@ namespace FourSlashInterface {
251251
}
252252
}
253253

254+
public getInlayHints(expected: readonly VerifyInlayHintsOptions[], span: ts.TextSpan, preference?: ts.InlayHintsOptions) {
255+
this.state.verifyInlayHints(expected, span, preference);
256+
}
257+
254258
public quickInfoIs(expectedText: string, expectedDocumentation?: string) {
255259
this.state.verifyQuickInfoString(expectedText, expectedDocumentation);
256260
}
@@ -1667,6 +1671,14 @@ namespace FourSlashInterface {
16671671
readonly containerKind?: ts.ScriptElementKind;
16681672
}
16691673

1674+
export interface VerifyInlayHintsOptions {
1675+
text: string;
1676+
position: number;
1677+
kind?: ts.InlayHintKind;
1678+
whitespaceBefore?: boolean;
1679+
whitespaceAfter?: boolean;
1680+
}
1681+
16701682
export type ArrayOrSingle<T> = T | readonly T[];
16711683

16721684
export interface VerifyCompletionListContainsOptions extends ts.UserPreferences {

Diff for: src/harness/harnessLanguageService.ts

+3
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,9 @@ namespace Harness.LanguageService {
599599
provideCallHierarchyOutgoingCalls(fileName: string, position: number) {
600600
return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position));
601601
}
602+
provideInlayHints(fileName: string, span: ts.TextSpan, preference: ts.InlayHintsOptions) {
603+
return unwrapJSONCallResult(this.shim.provideInlayHints(fileName, span, preference));
604+
}
602605
getEmitOutput(fileName: string): ts.EmitOutput {
603606
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
604607
}

Diff for: src/server/protocol.ts

+35
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ namespace ts.server.protocol {
154154
PrepareCallHierarchy = "prepareCallHierarchy",
155155
ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls",
156156
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
157+
ProvideInlayHints = "provideInlayHints"
157158

158159
// NOTE: If updating this, be sure to also update `allCommandNames` in `testRunner/unittests/tsserver/session.ts`.
159160
}
@@ -2549,6 +2550,40 @@ namespace ts.server.protocol {
25492550
body?: SignatureHelpItems;
25502551
}
25512552

2553+
export const enum InlayHintKind {
2554+
Type = "Type",
2555+
Parameter = "Parameter",
2556+
Enum = "Enum",
2557+
}
2558+
2559+
export interface InlayHintsRequestArgs extends FileRequestArgs {
2560+
/**
2561+
* Start position of the span.
2562+
*/
2563+
start: number;
2564+
/**
2565+
* Length of the span.
2566+
*/
2567+
length: number;
2568+
}
2569+
2570+
export interface InlayHintsRequest extends Request {
2571+
command: CommandTypes.ProvideInlayHints;
2572+
arguments: InlayHintsRequestArgs;
2573+
}
2574+
2575+
export interface InlayHintItem {
2576+
text: string;
2577+
position: Location;
2578+
kind?: InlayHintKind;
2579+
whitespaceBefore?: boolean;
2580+
whitespaceAfter?: boolean;
2581+
}
2582+
2583+
export interface InlayHintsResponse extends Response {
2584+
body?: InlayHintItem[];
2585+
}
2586+
25522587
/**
25532588
* Synchronous request for semantic diagnostics of one file.
25542589
*/

Diff for: src/server/session.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,17 @@ namespace ts.server {
14501450
});
14511451
}
14521452

1453+
private provideInlayHints(args: protocol.InlayHintsRequestArgs) {
1454+
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1455+
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1456+
const hints = languageService.provideInlayHints(file, args, this.getPreferences(file));
1457+
1458+
return hints.map(hint => ({
1459+
...hint,
1460+
position: scriptInfo.positionToLineOffset(hint.position),
1461+
}));
1462+
}
1463+
14531464
private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
14541465
this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath);
14551466
}
@@ -2963,6 +2974,9 @@ namespace ts.server {
29632974
[CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => {
29642975
return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false));
29652976
},
2977+
[CommandNames.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => {
2978+
return this.requiredResponse(this.provideInlayHints(request.arguments));
2979+
}
29662980
}));
29672981

29682982
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {

0 commit comments

Comments
 (0)