@@ -12,6 +12,7 @@ import {
12
12
createTextSpanFromBounds ,
13
13
createTextSpanFromNode ,
14
14
Debug ,
15
+ ElementFlags ,
15
16
EmitHint ,
16
17
emptyArray ,
17
18
Expression ,
@@ -49,6 +50,7 @@ import {
49
50
isPropertyAccessExpression ,
50
51
isSourceFile ,
51
52
isSourceFileJS ,
53
+ isSpreadElement ,
52
54
isTaggedTemplateExpression ,
53
55
isTemplateHead ,
54
56
isTemplateLiteralToken ,
@@ -58,6 +60,7 @@ import {
58
60
JsxTagNameExpression ,
59
61
last ,
60
62
lastOrUndefined ,
63
+ length ,
61
64
ListFormat ,
62
65
map ,
63
66
mapToDisplayParts ,
@@ -77,6 +80,7 @@ import {
77
80
skipTrivia ,
78
81
SourceFile ,
79
82
spacePart ,
83
+ SpreadElement ,
80
84
Symbol ,
81
85
SymbolDisplayPart ,
82
86
symbolToDisplayParts ,
@@ -85,6 +89,7 @@ import {
85
89
TemplateExpression ,
86
90
TextSpan ,
87
91
tryCast ,
92
+ TupleTypeReference ,
88
93
Type ,
89
94
TypeChecker ,
90
95
TypeParameter ,
@@ -272,25 +277,25 @@ export interface ArgumentInfoForCompletions {
272
277
readonly argumentCount : number ;
273
278
}
274
279
/** @internal */
275
- export function getArgumentInfoForCompletions ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentInfoForCompletions | undefined {
276
- const info = getImmediatelyContainingArgumentInfo ( node , position , sourceFile ) ;
280
+ export function getArgumentInfoForCompletions ( node : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : ArgumentInfoForCompletions | undefined {
281
+ const info = getImmediatelyContainingArgumentInfo ( node , position , sourceFile , checker ) ;
277
282
return ! info || info . isTypeParameterList || info . invocation . kind !== InvocationKind . Call ? undefined
278
283
: { invocation : info . invocation . node , argumentCount : info . argumentCount , argumentIndex : info . argumentIndex } ;
279
284
}
280
285
281
- function getArgumentOrParameterListInfo ( node : Node , position : number , sourceFile : SourceFile ) : { readonly list : Node ; readonly argumentIndex : number ; readonly argumentCount : number ; readonly argumentsSpan : TextSpan ; } | undefined {
282
- const info = getArgumentOrParameterListAndIndex ( node , sourceFile ) ;
286
+ function getArgumentOrParameterListInfo ( node : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : { readonly list : Node ; readonly argumentIndex : number ; readonly argumentCount : number ; readonly argumentsSpan : TextSpan ; } | undefined {
287
+ const info = getArgumentOrParameterListAndIndex ( node , sourceFile , checker ) ;
283
288
if ( ! info ) return undefined ;
284
289
const { list, argumentIndex } = info ;
285
290
286
- const argumentCount = getArgumentCount ( list , /*ignoreTrailingComma*/ isInString ( sourceFile , position , node ) ) ;
291
+ const argumentCount = getArgumentCount ( list , /*ignoreTrailingComma*/ isInString ( sourceFile , position , node ) , checker ) ;
287
292
if ( argumentIndex !== 0 ) {
288
293
Debug . assertLessThan ( argumentIndex , argumentCount ) ;
289
294
}
290
295
const argumentsSpan = getApplicableSpanForArguments ( list , sourceFile ) ;
291
296
return { list, argumentIndex, argumentCount, argumentsSpan } ;
292
297
}
293
- function getArgumentOrParameterListAndIndex ( node : Node , sourceFile : SourceFile ) : { readonly list : Node ; readonly argumentIndex : number ; } | undefined {
298
+ function getArgumentOrParameterListAndIndex ( node : Node , sourceFile : SourceFile , checker : TypeChecker ) : { readonly list : Node ; readonly argumentIndex : number ; } | undefined {
294
299
if ( node . kind === SyntaxKind . LessThanToken || node . kind === SyntaxKind . OpenParenToken ) {
295
300
// Find the list that starts right *after* the < or ( token.
296
301
// If the user has just opened a list, consider this item 0.
@@ -304,15 +309,15 @@ function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile):
304
309
// - On the target of the call (parent.func)
305
310
// - On the 'new' keyword in a 'new' expression
306
311
const list = findContainingList ( node ) ;
307
- return list && { list, argumentIndex : getArgumentIndex ( list , node ) } ;
312
+ return list && { list, argumentIndex : getArgumentIndex ( list , node , checker ) } ;
308
313
}
309
314
}
310
315
311
316
/**
312
317
* Returns relevant information for the argument list and the current argument if we are
313
318
* in the argument of an invocation; returns undefined otherwise.
314
319
*/
315
- function getImmediatelyContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentListInfo | undefined {
320
+ function getImmediatelyContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : ArgumentListInfo | undefined {
316
321
const { parent } = node ;
317
322
if ( isCallOrNewExpression ( parent ) ) {
318
323
const invocation = parent ;
@@ -331,7 +336,7 @@ function getImmediatelyContainingArgumentInfo(node: Node, position: number, sour
331
336
// Case 3:
332
337
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give signature help
333
338
// Find out if 'node' is an argument, a type argument, or neither
334
- const info = getArgumentOrParameterListInfo ( node , position , sourceFile ) ;
339
+ const info = getArgumentOrParameterListInfo ( node , position , sourceFile , checker ) ;
335
340
if ( ! info ) return undefined ;
336
341
const { list, argumentIndex, argumentCount, argumentsSpan } = info ;
337
342
const isTypeParameterList = ! ! parent . typeArguments && parent . typeArguments . pos === list . pos ;
@@ -397,7 +402,7 @@ function getImmediatelyContainingArgumentInfo(node: Node, position: number, sour
397
402
}
398
403
399
404
function getImmediatelyContainingArgumentOrContextualParameterInfo ( node : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : ArgumentListInfo | undefined {
400
- return tryGetParameterInfo ( node , position , sourceFile , checker ) || getImmediatelyContainingArgumentInfo ( node , position , sourceFile ) ;
405
+ return tryGetParameterInfo ( node , position , sourceFile , checker ) || getImmediatelyContainingArgumentInfo ( node , position , sourceFile , checker ) ;
401
406
}
402
407
403
408
function getHighestBinary ( b : BinaryExpression ) : BinaryExpression {
@@ -452,7 +457,7 @@ function getContextualSignatureLocationInfo(node: Node, sourceFile: SourceFile,
452
457
case SyntaxKind . MethodDeclaration :
453
458
case SyntaxKind . FunctionExpression :
454
459
case SyntaxKind . ArrowFunction :
455
- const info = getArgumentOrParameterListInfo ( node , position , sourceFile ) ;
460
+ const info = getArgumentOrParameterListInfo ( node , position , sourceFile , checker ) ;
456
461
if ( ! info ) return undefined ;
457
462
const { argumentIndex, argumentCount, argumentsSpan } = info ;
458
463
const contextualType = isMethodDeclaration ( parent ) ? checker . getContextualTypeForObjectLiteralElement ( parent ) : checker . getContextualType ( parent as ParenthesizedExpression | FunctionExpression | ArrowFunction ) ;
@@ -476,7 +481,7 @@ function chooseBetterSymbol(s: Symbol): Symbol {
476
481
: s ;
477
482
}
478
483
479
- function getArgumentIndex ( argumentsList : Node , node : Node ) {
484
+ function getArgumentIndex ( argumentsList : Node , node : Node , checker : TypeChecker ) {
480
485
// The list we got back can include commas. In the presence of errors it may
481
486
// also just have nodes without commas. For example "Foo(a b c)" will have 3
482
487
// args without commas. We want to find what index we're at. So we count
@@ -488,20 +493,39 @@ function getArgumentIndex(argumentsList: Node, node: Node) {
488
493
// on. In that case, even if we're after the trailing comma, we'll still see
489
494
// that trailing comma in the list, and we'll have generated the appropriate
490
495
// arg index.
496
+ const args = argumentsList . getChildren ( ) ;
491
497
let argumentIndex = 0 ;
492
- for ( const child of argumentsList . getChildren ( ) ) {
498
+ for ( let pos = 0 ; pos < length ( args ) ; pos ++ ) {
499
+ const child = args [ pos ] ;
493
500
if ( child === node ) {
494
501
break ;
495
502
}
496
- if ( child . kind !== SyntaxKind . CommaToken ) {
497
- argumentIndex ++ ;
503
+ if ( isSpreadElement ( child ) ) {
504
+ argumentIndex = argumentIndex + getSpreadElementCount ( child , checker ) + ( pos > 0 ? pos : 0 ) ;
505
+ }
506
+ else {
507
+ if ( child . kind !== SyntaxKind . CommaToken ) {
508
+ argumentIndex ++ ;
509
+ }
498
510
}
499
511
}
500
-
501
512
return argumentIndex ;
502
513
}
503
514
504
- function getArgumentCount ( argumentsList : Node , ignoreTrailingComma : boolean ) {
515
+ function getSpreadElementCount ( node : SpreadElement , checker : TypeChecker ) {
516
+ const spreadType = checker . getTypeAtLocation ( node . expression ) ;
517
+ if ( checker . isTupleType ( spreadType ) ) {
518
+ const { elementFlags, fixedLength } = ( spreadType as TupleTypeReference ) . target ;
519
+ if ( fixedLength === 0 ) {
520
+ return 0 ;
521
+ }
522
+ const firstOptionalIndex = findIndex ( elementFlags , f => ! ( f & ElementFlags . Required ) ) ;
523
+ return firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex ;
524
+ }
525
+ return 0 ;
526
+ }
527
+
528
+ function getArgumentCount ( argumentsList : Node , ignoreTrailingComma : boolean , checker : TypeChecker ) {
505
529
// The argument count for a list is normally the number of non-comma children it has.
506
530
// For example, if you have "Foo(a,b)" then there will be three children of the arg
507
531
// list 'a' '<comma>' 'b'. So, in this case the arg count will be 2. However, there
@@ -515,7 +539,14 @@ function getArgumentCount(argumentsList: Node, ignoreTrailingComma: boolean) {
515
539
// arg count of 3.
516
540
const listChildren = argumentsList . getChildren ( ) ;
517
541
518
- let argumentCount = countWhere ( listChildren , arg => arg . kind !== SyntaxKind . CommaToken ) ;
542
+ let argumentCount = 0 ;
543
+ for ( const child of listChildren ) {
544
+ if ( isSpreadElement ( child ) ) {
545
+ argumentCount = argumentCount + getSpreadElementCount ( child , checker ) ;
546
+ }
547
+ }
548
+
549
+ argumentCount = argumentCount + countWhere ( listChildren , arg => arg . kind !== SyntaxKind . CommaToken ) ;
519
550
if ( ! ignoreTrailingComma && listChildren . length > 0 && last ( listChildren ) . kind === SyntaxKind . CommaToken ) {
520
551
argumentCount ++ ;
521
552
}
0 commit comments