Fixed a regression related to determining argument index when spread elements are involved (#57637)

Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
This commit is contained in:
Mateusz Burzyński
2024-03-29 00:18:28 +01:00
committed by GitHub
parent 6ff28d14d1
commit 97dc5f0dd9
8 changed files with 983 additions and 68 deletions

View File

@@ -3161,8 +3161,7 @@ function getContextualType(previousToken: Node, position: number, sourceFile: So
default:
const argInfo = SignatureHelp.getArgumentInfoForCompletions(previousToken, position, sourceFile, checker);
return argInfo ?
// At `,`, treat this as the next argument after the comma.
checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) :
checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex) :
isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ?
// completion at `x ===/**/` should be for the right side
checker.getTypeAtLocation(parent.left) :

View File

@@ -6,7 +6,6 @@ import {
canHaveSymbol,
CheckFlags,
contains,
countWhere,
createPrinterWithRemoveComments,
createTextSpan,
createTextSpanFromBounds,
@@ -60,7 +59,6 @@ import {
JsxTagNameExpression,
last,
lastOrUndefined,
length,
ListFormat,
map,
mapToDisplayParts,
@@ -288,7 +286,7 @@ function getArgumentOrParameterListInfo(node: Node, position: number, sourceFile
if (!info) return undefined;
const { list, argumentIndex } = info;
const argumentCount = getArgumentCount(list, /*ignoreTrailingComma*/ isInString(sourceFile, position, node), checker);
const argumentCount = getArgumentCount(checker, list);
if (argumentIndex !== 0) {
Debug.assertLessThan(argumentIndex, argumentCount);
}
@@ -309,7 +307,7 @@ function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile,
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
const list = findContainingList(node);
return list && { list, argumentIndex: getArgumentIndex(list, node, checker) };
return list && { list, argumentIndex: getArgumentIndex(checker, list, node) };
}
}
@@ -481,37 +479,6 @@ function chooseBetterSymbol(s: Symbol): Symbol {
: s;
}
function getArgumentIndex(argumentsList: Node, node: Node, checker: TypeChecker) {
// The list we got back can include commas. In the presence of errors it may
// also just have nodes without commas. For example "Foo(a b c)" will have 3
// args without commas. We want to find what index we're at. So we count
// forward until we hit ourselves, only incrementing the index if it isn't a
// comma.
//
// Note: the subtlety around trailing commas (in getArgumentCount) does not apply
// here. That's because we're only walking forward until we hit the node we're
// on. In that case, even if we're after the trailing comma, we'll still see
// that trailing comma in the list, and we'll have generated the appropriate
// arg index.
const args = argumentsList.getChildren();
let argumentIndex = 0;
for (let pos = 0; pos < length(args); pos++) {
const child = args[pos];
if (child === node) {
break;
}
if (isSpreadElement(child)) {
argumentIndex = argumentIndex + getSpreadElementCount(child, checker) + (pos > 0 ? pos : 0);
}
else {
if (child.kind !== SyntaxKind.CommaToken) {
argumentIndex++;
}
}
}
return argumentIndex;
}
function getSpreadElementCount(node: SpreadElement, checker: TypeChecker) {
const spreadType = checker.getTypeAtLocation(node.expression);
if (checker.isTupleType(spreadType)) {
@@ -525,32 +492,54 @@ function getSpreadElementCount(node: SpreadElement, checker: TypeChecker) {
return 0;
}
function getArgumentCount(argumentsList: Node, ignoreTrailingComma: boolean, checker: TypeChecker) {
function getArgumentIndex(checker: TypeChecker, argumentsList: Node, node: Node) {
return getArgumentIndexOrCount(checker, argumentsList, node);
}
function getArgumentCount(checker: TypeChecker, argumentsList: Node) {
return getArgumentIndexOrCount(checker, argumentsList, /*node*/ undefined);
}
function getArgumentIndexOrCount(checker: TypeChecker, argumentsList: Node, node: Node | undefined) {
// The list we got back can include commas. In the presence of errors it may
// also just have nodes without commas. For example "Foo(a b c)" will have 3
// args without commas.
const args = argumentsList.getChildren();
let argumentIndex = 0;
let skipComma = false;
for (const child of args) {
if (node && child === node) {
if (!skipComma && child.kind === SyntaxKind.CommaToken) {
argumentIndex++;
}
return argumentIndex;
}
if (isSpreadElement(child)) {
argumentIndex += getSpreadElementCount(child, checker);
skipComma = true;
continue;
}
if (child.kind !== SyntaxKind.CommaToken) {
argumentIndex++;
skipComma = true;
continue;
}
if (skipComma) {
skipComma = false;
continue;
}
argumentIndex++;
}
if (node) {
return argumentIndex;
}
// The argument count for a list is normally the number of non-comma children it has.
// For example, if you have "Foo(a,b)" then there will be three children of the arg
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
// is a small subtlety. If you have "Foo(a,)", then the child list will just have
// 'a' '<comma>'. So, in the case where the last child is a comma, we increase the
// arg count by one to compensate.
//
// Note: this subtlety only applies to the last comma. If you had "Foo(a,," then
// we'll have: 'a' '<comma>' '<missing>'
// That will give us 2 non-commas. We then add one for the last comma, giving us an
// arg count of 3.
const listChildren = argumentsList.getChildren();
let argumentCount = 0;
for (const child of listChildren) {
if (isSpreadElement(child)) {
argumentCount = argumentCount + getSpreadElementCount(child, checker);
}
}
argumentCount = argumentCount + countWhere(listChildren, arg => arg.kind !== SyntaxKind.CommaToken);
if (!ignoreTrailingComma && listChildren.length > 0 && last(listChildren).kind === SyntaxKind.CommaToken) {
argumentCount++;
}
return argumentCount;
return args.length && last(args).kind === SyntaxKind.CommaToken ? argumentIndex + 1 : argumentIndex;
}
// spanIndex is either the index for a given template span.

View File

@@ -1,5 +1,5 @@
// === SignatureHelp ===
=== /tests/cases/fourslash/signatureHelpRestArgs.ts ===
=== /tests/cases/fourslash/signatureHelpRestArgs1.ts ===
// function fn(a: number, b: number, c: number) {}
// const a = [1, 2] as const;
// const b = [1] as const;
@@ -33,7 +33,7 @@
[
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs.ts",
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs1.ts",
"position": 109,
"name": "1"
},
@@ -163,12 +163,12 @@
},
"selectedItemIndex": 0,
"argumentIndex": 2,
"argumentCount": 4
"argumentCount": 3
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs.ts",
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs1.ts",
"position": 115,
"name": "2"
},
@@ -303,7 +303,7 @@
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs.ts",
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs1.ts",
"position": 134,
"name": "3"
},
@@ -433,12 +433,12 @@
},
"selectedItemIndex": 0,
"argumentIndex": 1,
"argumentCount": 3
"argumentCount": 2
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs.ts",
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs1.ts",
"position": 140,
"name": "4"
},
@@ -573,7 +573,7 @@
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs.ts",
"fileName": "/tests/cases/fourslash/signatureHelpRestArgs1.ts",
"position": 148,
"name": "5"
},

View File

@@ -0,0 +1,201 @@
// === SignatureHelp ===
=== /tests/cases/fourslash/index.js ===
// const promisify = function (thisArg, fnName) {
// const fn = thisArg[fnName];
// return function () {
// return new Promise((resolve) => {
// fn.call(thisArg, ...arguments, );
// ^
// | ----------------------------------------------------------------------
// | Function.call(thisArg: any, **...argArray: any[]**): any
// | Calls a method of an object, substituting another object for the current object.
// | @param thisArg The object to be used as the current object.
// | @param argArray A list of arguments to be passed to the method.
// | ----------------------------------------------------------------------
// });
// };
// };
[
{
"marker": {
"fileName": "/tests/cases/fourslash/index.js",
"position": 189,
"name": "1"
},
"item": {
"items": [
{
"isVariadic": true,
"prefixDisplayParts": [
{
"text": "Function",
"kind": "localName"
},
{
"text": ".",
"kind": "punctuation"
},
{
"text": "call",
"kind": "methodName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "any",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "thisArg",
"documentation": [
{
"text": "The object to be used as the current object.",
"kind": "text"
}
],
"displayParts": [
{
"text": "thisArg",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "any",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "argArray",
"documentation": [
{
"text": "A list of arguments to be passed to the method.",
"kind": "text"
}
],
"displayParts": [
{
"text": "...",
"kind": "punctuation"
},
{
"text": "argArray",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "any",
"kind": "keyword"
},
{
"text": "[",
"kind": "punctuation"
},
{
"text": "]",
"kind": "punctuation"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [
{
"text": "Calls a method of an object, substituting another object for the current object.",
"kind": "text"
}
],
"tags": [
{
"name": "param",
"text": [
{
"text": "thisArg",
"kind": "parameterName"
},
{
"text": " ",
"kind": "space"
},
{
"text": "The object to be used as the current object.",
"kind": "text"
}
]
},
{
"name": "param",
"text": [
{
"text": "argArray",
"kind": "parameterName"
},
{
"text": " ",
"kind": "space"
},
{
"text": "A list of arguments to be passed to the method.",
"kind": "text"
}
]
}
]
}
],
"applicableSpan": {
"start": 166,
"length": 23
},
"selectedItemIndex": 0,
"argumentIndex": 1,
"argumentCount": 2
}
}
]

View File

@@ -0,0 +1,702 @@
// === SignatureHelp ===
=== /tests/cases/fourslash/signatureHelpSkippedArgs1.ts ===
// function fn(a: number, b: number, c: number) {}
// fn(, , , , );
// ^
// | ----------------------------------------------------------------------
// | fn(**a: number**, b: number, c: number): void
// | ----------------------------------------------------------------------
// ^
// | ----------------------------------------------------------------------
// | fn(a: number, **b: number**, c: number): void
// | ----------------------------------------------------------------------
// ^
// | ----------------------------------------------------------------------
// | fn(a: number, b: number, **c: number**): void
// | ----------------------------------------------------------------------
// ^
// | ----------------------------------------------------------------------
// | fn(a: number, b: number, c: number): void
// | ----------------------------------------------------------------------
// ^
// | ----------------------------------------------------------------------
// | fn(a: number, b: number, c: number): void
// | ----------------------------------------------------------------------
[
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpSkippedArgs1.ts",
"position": 51,
"name": "1"
},
"item": {
"items": [
{
"isVariadic": false,
"prefixDisplayParts": [
{
"text": "fn",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "a",
"documentation": [],
"displayParts": [
{
"text": "a",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "b",
"documentation": [],
"displayParts": [
{
"text": "b",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "c",
"documentation": [],
"displayParts": [
{
"text": "c",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [],
"tags": []
}
],
"applicableSpan": {
"start": 51,
"length": 8
},
"selectedItemIndex": 0,
"argumentIndex": 0,
"argumentCount": 5
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpSkippedArgs1.ts",
"position": 53,
"name": "2"
},
"item": {
"items": [
{
"isVariadic": false,
"prefixDisplayParts": [
{
"text": "fn",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "a",
"documentation": [],
"displayParts": [
{
"text": "a",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "b",
"documentation": [],
"displayParts": [
{
"text": "b",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "c",
"documentation": [],
"displayParts": [
{
"text": "c",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [],
"tags": []
}
],
"applicableSpan": {
"start": 51,
"length": 8
},
"selectedItemIndex": 0,
"argumentIndex": 1,
"argumentCount": 5
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpSkippedArgs1.ts",
"position": 55,
"name": "3"
},
"item": {
"items": [
{
"isVariadic": false,
"prefixDisplayParts": [
{
"text": "fn",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "a",
"documentation": [],
"displayParts": [
{
"text": "a",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "b",
"documentation": [],
"displayParts": [
{
"text": "b",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "c",
"documentation": [],
"displayParts": [
{
"text": "c",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [],
"tags": []
}
],
"applicableSpan": {
"start": 51,
"length": 8
},
"selectedItemIndex": 0,
"argumentIndex": 2,
"argumentCount": 5
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpSkippedArgs1.ts",
"position": 57,
"name": "4"
},
"item": {
"items": [
{
"isVariadic": false,
"prefixDisplayParts": [
{
"text": "fn",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "a",
"documentation": [],
"displayParts": [
{
"text": "a",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "b",
"documentation": [],
"displayParts": [
{
"text": "b",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "c",
"documentation": [],
"displayParts": [
{
"text": "c",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [],
"tags": []
}
],
"applicableSpan": {
"start": 51,
"length": 8
},
"selectedItemIndex": 0,
"argumentIndex": 3,
"argumentCount": 5
}
},
{
"marker": {
"fileName": "/tests/cases/fourslash/signatureHelpSkippedArgs1.ts",
"position": 59,
"name": "5"
},
"item": {
"items": [
{
"isVariadic": false,
"prefixDisplayParts": [
{
"text": "fn",
"kind": "functionName"
},
{
"text": "(",
"kind": "punctuation"
}
],
"suffixDisplayParts": [
{
"text": ")",
"kind": "punctuation"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "void",
"kind": "keyword"
}
],
"separatorDisplayParts": [
{
"text": ",",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
}
],
"parameters": [
{
"name": "a",
"documentation": [],
"displayParts": [
{
"text": "a",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "b",
"documentation": [],
"displayParts": [
{
"text": "b",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
},
{
"name": "c",
"documentation": [],
"displayParts": [
{
"text": "c",
"kind": "parameterName"
},
{
"text": ":",
"kind": "punctuation"
},
{
"text": " ",
"kind": "space"
},
{
"text": "number",
"kind": "keyword"
}
],
"isOptional": false,
"isRest": false
}
],
"documentation": [],
"tags": []
}
],
"applicableSpan": {
"start": 51,
"length": 8
},
"selectedItemIndex": 0,
"argumentIndex": 4,
"argumentCount": 5
}
}
]

View File

@@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
// @strict: true
// @allowJs: true
// @checkJs: true
// @filename: index.js
//// const promisify = function (thisArg, fnName) {
//// const fn = thisArg[fnName];
//// return function () {
//// return new Promise((resolve) => {
//// fn.call(thisArg, ...arguments, /*1*/);
//// });
//// };
//// };
verify.baselineSignatureHelp();

View File

@@ -0,0 +1,6 @@
/// <reference path="fourslash.ts" />
//// function fn(a: number, b: number, c: number) {}
//// fn(/*1*/, /*2*/, /*3*/, /*4*/, /*5*/);
verify.baselineSignatureHelp();