Skip to content

Commit 67299ed

Browse files
authored
fix(54458): The inlay hint does not handle TypeScript's spread operator well (#54503)
1 parent cbda2fc commit 67299ed

8 files changed

+199
-3
lines changed

src/services/inlayHints.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {
44
CallExpression,
55
createPrinterWithRemoveComments,
66
Debug,
7+
ElementFlags,
78
EmitHint,
89
EnumMember,
910
equateStringsCaseInsensitive,
1011
Expression,
1112
findChildOfKind,
13+
findIndex,
1214
forEachChild,
1315
FunctionDeclaration,
1416
FunctionExpression,
@@ -44,6 +46,7 @@ import {
4446
isParameterDeclaration,
4547
isPropertyAccessExpression,
4648
isPropertyDeclaration,
49+
isSpreadElement,
4750
isTypeNode,
4851
isVarConst,
4952
isVariableDeclaration,
@@ -61,6 +64,7 @@ import {
6164
SymbolFlags,
6265
SyntaxKind,
6366
textSpanIntersectsWith,
67+
TupleTypeReference,
6468
Type,
6569
TypeFormatFlags,
6670
unescapeLeadingUnderscores,
@@ -226,14 +230,31 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
226230
return;
227231
}
228232

229-
for (let i = 0; i < args.length; ++i) {
230-
const originalArg = args[i];
233+
let signatureParamPos = 0;
234+
for (const originalArg of args) {
231235
const arg = skipParentheses(originalArg);
232236
if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) {
233237
continue;
234238
}
235239

236-
const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, i);
240+
let spreadArgs = 0;
241+
if (isSpreadElement(arg)) {
242+
const spreadType = checker.getTypeAtLocation(arg.expression);
243+
if (checker.isTupleType(spreadType)) {
244+
const { elementFlags, fixedLength } = (spreadType as TupleTypeReference).target;
245+
if (fixedLength === 0) {
246+
continue;
247+
}
248+
const firstOptionalIndex = findIndex(elementFlags, f => !(f & ElementFlags.Required));
249+
const requiredArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
250+
if (requiredArgs > 0) {
251+
spreadArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
252+
}
253+
}
254+
}
255+
256+
const identifierNameInfo = checker.getParameterIdentifierNameAtPosition(signature, signatureParamPos);
257+
signatureParamPos = signatureParamPos + (spreadArgs || 1);
237258
if (identifierNameInfo) {
238259
const [parameterName, isFirstVariadicArgument] = identifierNameInfo;
239260
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown) { }
4+
////function bar(...x: [number, number]) {
5+
//// foo(/*a*/...x, /*c*/3);
6+
////}
7+
8+
const [a, c] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'c:',
19+
position: c.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
], undefined, {
24+
includeInlayParameterNameHints: "all"
25+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown, d: unknown) { }
4+
////function bar(...x: [number, number, number]) {
5+
//// foo(/*a*/...x, /*d*/3);
6+
////}
7+
8+
const [a, d] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'd:',
19+
position: d.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
], undefined, {
24+
includeInlayParameterNameHints: "all"
25+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown) { }
4+
////function bar(...x: [number, number?]) {
5+
//// foo(/*a*/...x, /*b*/3);
6+
////}
7+
8+
const [a, b] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'b:',
19+
position: b.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
], undefined, {
24+
includeInlayParameterNameHints: "all"
25+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown) { }
4+
////function bar(...x: [number, number?]) {
5+
//// foo(/*a*/1, /*b*/...x);
6+
////}
7+
8+
const [a, b] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'b:',
19+
position: b.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
], undefined, {
24+
includeInlayParameterNameHints: "all"
25+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown) { }
4+
////function bar(...x: [number, number | undefined]) {
5+
//// foo(/*a*/...x, /*c*/3);
6+
////}
7+
8+
const [a, b] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'c:',
19+
position: b.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
], undefined, {
24+
includeInlayParameterNameHints: "all"
25+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo(a: unknown, b: unknown, c: unknown) { }
4+
////function bar(...x: []) {
5+
//// foo(...x, /*a*/1, /*b*/2, /*c*/3);
6+
////}
7+
8+
const [a, b, c] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'a:',
13+
position: a.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
{
18+
text: 'b:',
19+
position: b.position,
20+
kind: ts.InlayHintKind.Parameter,
21+
whitespaceAfter: true
22+
},
23+
{
24+
text: 'c:',
25+
position: c.position,
26+
kind: ts.InlayHintKind.Parameter,
27+
whitespaceAfter: true
28+
},
29+
], undefined, {
30+
includeInlayParameterNameHints: "all"
31+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////function foo({ a, b }: { a: unknown, b: unknown }, c: unknown, d: unknown) { }
4+
////function bar(...x: [{ a: unknown, b: unknown }, number]) {
5+
//// foo(...x, /*d*/1);
6+
////}
7+
8+
const [d] = test.markers();
9+
10+
verify.getInlayHints([
11+
{
12+
text: 'd:',
13+
position: d.position,
14+
kind: ts.InlayHintKind.Parameter,
15+
whitespaceAfter: true
16+
},
17+
], undefined, {
18+
includeInlayParameterNameHints: "all"
19+
});

0 commit comments

Comments
 (0)