Signature help turns off current-parameter display for non-trailing rest parameters (#42592)

* Signature help: support non-trailing rest parameters

In signature help, the first rest parameter will always be the *last*
'current' parameter (disregarding types). Previously, the signature help
current-parameter highlight was only correct for trailing rest
parameters. However, with tuple types, you can now create non-trailing
rest parameters. This PR now correctly highlights non-trailing rest
parameters as the last 'current' parameter.

For example, `names` should be the current parameter in all the calls
below:

```ts
declare function loading(...args: [...names: string[], allCaps: boolean, extra: boolean]): void;

leading(/**/
leading('one', /**/
leading('one', 'two', /**/
```

And, because signature help doesn't do real overload resolution, `names`
is also the current parameter for other calls:

```ts
leading(1, 2, 3, 'ill-typed', /**/
leading('fine', true, /**/
```

* Change 'variadic' to 'rest'

* fix missed rename

* use single, original tuple instead

* Revert "use single, original tuple instead"

This reverts commit f0896f32ea.

* Improve sig help of trailing rest too

1. Trailing rest keeps highlight at end instead of going off the end.
2. Non-trailing rest disable highlight entirely (by putting the index
one past the end).

* update API baselines
This commit is contained in:
Nathan Shively-Sanders
2021-02-05 09:37:28 -08:00
committed by GitHub
parent 215ee40dc8
commit f1583f08a0
6 changed files with 90 additions and 6 deletions

View File

@@ -517,7 +517,6 @@ namespace ts.SignatureHelp {
if (argumentIndex !== 0) {
Debug.assertLessThan(argumentIndex, argumentCount);
}
let selectedItemIndex = 0;
let itemsSeen = 0;
for (let i = 0; i < items.length; i++) {
@@ -541,8 +540,19 @@ namespace ts.SignatureHelp {
}
Debug.assert(selectedItemIndex !== -1); // If candidates is non-empty it should always include bestSignature. We check for an empty candidates before calling this function.
return { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
const help = { items: flatMapToMutable(items, identity), applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
const selected = help.items[selectedItemIndex];
if (selected.isVariadic) {
const firstRest = findIndex(selected.parameters, p => !!p.isRest);
if (-1 < firstRest && firstRest < selected.parameters.length - 1) {
// We don't have any code to get this correct; instead, don't highlight a current parameter AT ALL
help.argumentIndex = selected.parameters.length;
}
else {
help.argumentIndex = Math.min(help.argumentIndex, selected.parameters.length - 1);
}
}
return help;
}
function createTypeHelpItems(
@@ -638,8 +648,9 @@ namespace ts.SignatureHelp {
const param = checker.symbolToParameterDeclaration(parameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!;
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
const isOptional = checker.isOptionalParameter(<ParameterDeclaration>parameter.valueDeclaration);
return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional };
const isOptional = checker.isOptionalParameter(parameter.valueDeclaration as ParameterDeclaration);
const isRest = !!((parameter as TransientSymbol).checkFlags & CheckFlags.RestParameter);
return { name: parameter.name, documentation: parameter.getDocumentationComment(checker), displayParts, isOptional, isRest };
}
function createSignatureHelpParameterForTypeParameter(typeParameter: TypeParameter, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile, printer: Printer): SignatureHelpParameter {
@@ -647,6 +658,6 @@ namespace ts.SignatureHelp {
const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration, signatureHelpNodeBuilderFlags)!;
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false };
return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false, isRest: false };
}
}

View File

@@ -1078,6 +1078,7 @@ namespace ts {
documentation: SymbolDisplayPart[];
displayParts: SymbolDisplayPart[];
isOptional: boolean;
isRest?: boolean;
}
export interface SelectionRange {

View File

@@ -6025,6 +6025,7 @@ declare namespace ts {
documentation: SymbolDisplayPart[];
displayParts: SymbolDisplayPart[];
isOptional: boolean;
isRest?: boolean;
}
interface SelectionRange {
textSpan: TextSpan;

View File

@@ -6025,6 +6025,7 @@ declare namespace ts {
documentation: SymbolDisplayPart[];
displayParts: SymbolDisplayPart[];
isOptional: boolean;
isRest?: boolean;
}
interface SelectionRange {
textSpan: TextSpan;

View File

@@ -0,0 +1,32 @@
/// <reference path='fourslash.ts' />
////export function leading(...args: [...names: string[], allCaps: boolean]): void {
////}
////
////leading(/*1*/);
////leading("ok", /*2*/);
////leading("ok", "ok", /*3*/);
verify.signatureHelp(
{
marker: "1",
text: "leading(...names: string[], allCaps: boolean): void",
overloadsCount: 1,
parameterCount: 2,
isVariadic: true,
},
{
marker: "2",
text: "leading(...names: string[], allCaps: boolean): void",
overloadsCount: 1,
parameterCount: 2,
isVariadic: true,
},
{
marker: "3",
text: "leading(...names: string[], allCaps: boolean): void",
overloadsCount: 1,
parameterCount: 2,
isVariadic: true,
},
);

View File

@@ -0,0 +1,38 @@
/// <reference path='fourslash.ts' />
////export function leading(allCaps: boolean, ...names: string[]): void {
////}
////
////leading(/*1*/);
////leading(false, /*2*/);
////leading(false, "ok", /*3*/);
verify.signatureHelp(
{
marker: "1",
text: "leading(allCaps: boolean, ...names: string[]): void",
overloadsCount: 1,
parameterCount: 2,
parameterName: "allCaps",
parameterSpan: "allCaps: boolean",
isVariadic: true,
},
{
marker: "2",
text: "leading(allCaps: boolean, ...names: string[]): void",
overloadsCount: 1,
parameterCount: 2,
parameterName: "names",
parameterSpan: "...names: string[]",
isVariadic: true,
},
{
marker: "3",
text: "leading(allCaps: boolean, ...names: string[]): void",
overloadsCount: 1,
parameterCount: 2,
parameterName: "names",
parameterSpan: "...names: string[]",
isVariadic: true,
},
);