1
1
/* @internal */
2
2
namespace ts . SignatureHelp {
3
- export const enum ArgumentListKind {
3
+ const enum ArgumentListKind {
4
4
TypeArguments ,
5
5
CallArguments ,
6
6
TaggedTemplateArguments ,
7
7
JSXAttributesArguments
8
8
}
9
9
10
- export interface ArgumentListInfo {
10
+ const enum InvocationKind { Call , TypeArgs }
11
+ type Invocation = { kind : InvocationKind . Call , node : CallLikeExpression } | { kind : InvocationKind . TypeArgs , called : Expression } ;
12
+
13
+ interface ArgumentListInfo {
11
14
kind : ArgumentListKind ;
12
- invocation : CallLikeExpression ;
15
+ invocation : Invocation ;
13
16
argumentsSpan : TextSpan ;
14
- argumentIndex ? : number ;
17
+ argumentIndex : number ;
15
18
/** argumentCount is the *apparent* number of arguments. */
16
19
argumentCount : number ;
17
20
}
@@ -32,32 +35,38 @@ namespace ts.SignatureHelp {
32
35
cancellationToken . throwIfCancellationRequested ( ) ;
33
36
34
37
// Semantic filtering of signature help
35
- const call = argumentInfo . invocation ;
36
- const candidates : Signature [ ] = [ ] ;
37
- const resolvedSignature = typeChecker . getResolvedSignature ( call , candidates , argumentInfo . argumentCount ) ;
38
+ const candidateInfo = getCandidateInfo ( argumentInfo , typeChecker ) ;
38
39
cancellationToken . throwIfCancellationRequested ( ) ;
39
40
40
- if ( ! candidates . length ) {
41
+ if ( ! candidateInfo ) {
41
42
// We didn't have any sig help items produced by the TS compiler. If this is a JS
42
43
// file, then see if we can figure out anything better.
43
44
if ( isSourceFileJavaScript ( sourceFile ) ) {
44
45
return createJavaScriptSignatureHelpItems ( argumentInfo , program , cancellationToken ) ;
45
46
}
46
-
47
47
return undefined ;
48
48
}
49
49
50
- return typeChecker . runWithCancellationToken ( cancellationToken , typeChecker => createSignatureHelpItems ( candidates , resolvedSignature , argumentInfo , typeChecker ) ) ;
50
+ return typeChecker . runWithCancellationToken ( cancellationToken , typeChecker => createSignatureHelpItems ( candidateInfo . candidates , candidateInfo . resolvedSignature , argumentInfo , sourceFile , typeChecker ) ) ;
51
51
}
52
52
53
- function createJavaScriptSignatureHelpItems ( argumentInfo : ArgumentListInfo , program : Program , cancellationToken : CancellationToken ) : SignatureHelpItems {
54
- if ( argumentInfo . invocation . kind !== SyntaxKind . CallExpression ) {
55
- return undefined ;
53
+ function getCandidateInfo ( argumentInfo : ArgumentListInfo , checker : TypeChecker ) : { readonly candidates : ReadonlyArray < Signature > , readonly resolvedSignature : Signature } | undefined {
54
+ const { invocation } = argumentInfo ;
55
+ if ( invocation . kind === InvocationKind . Call ) {
56
+ const candidates : Signature [ ] = [ ] ;
57
+ const resolvedSignature = checker . getResolvedSignature ( invocation . node , candidates , argumentInfo . argumentCount ) ;
58
+ return candidates . length === 0 ? undefined : { candidates, resolvedSignature } ;
56
59
}
60
+ else {
61
+ const type = checker . getTypeAtLocation ( invocation . called ) ;
62
+ const candidates = type . getCallSignatures ( ) . filter ( candidate => ! ! candidate . typeParameters && candidate . typeParameters . length >= argumentInfo . argumentCount ) ;
63
+ return candidates . length === 0 ? undefined : { candidates, resolvedSignature : first ( candidates ) } ;
64
+ }
65
+ }
57
66
67
+ function createJavaScriptSignatureHelpItems ( argumentInfo : ArgumentListInfo , program : Program , cancellationToken : CancellationToken ) : SignatureHelpItems {
58
68
// See if we can find some symbol with the call expression name that has call signatures.
59
- const callExpression = argumentInfo . invocation ;
60
- const expression = callExpression . expression ;
69
+ const expression = getExpressionFromInvocation ( argumentInfo . invocation ) ;
61
70
const name = isIdentifier ( expression ) ? expression : isPropertyAccessExpression ( expression ) ? expression . name : undefined ;
62
71
if ( ! name || ! name . escapedText ) {
63
72
return undefined ;
@@ -76,7 +85,7 @@ namespace ts.SignatureHelp {
76
85
if ( type ) {
77
86
const callSignatures = type . getCallSignatures ( ) ;
78
87
if ( callSignatures && callSignatures . length ) {
79
- return typeChecker . runWithCancellationToken ( cancellationToken , typeChecker => createSignatureHelpItems ( callSignatures , callSignatures [ 0 ] , argumentInfo , typeChecker ) ) ;
88
+ return typeChecker . runWithCancellationToken ( cancellationToken , typeChecker => createSignatureHelpItems ( callSignatures , callSignatures [ 0 ] , argumentInfo , sourceFile , typeChecker ) ) ;
80
89
}
81
90
}
82
91
}
@@ -85,11 +94,22 @@ namespace ts.SignatureHelp {
85
94
}
86
95
}
87
96
97
+ export interface ArgumentInfoForCompletions {
98
+ readonly invocation : CallLikeExpression ;
99
+ readonly argumentIndex : number ;
100
+ readonly argumentCount : number ;
101
+ }
102
+ export function getArgumentInfoForCompletions ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentInfoForCompletions | undefined {
103
+ const info = getImmediatelyContainingArgumentInfo ( node , position , sourceFile ) ;
104
+ return ! info || info . kind === ArgumentListKind . TypeArguments || info . invocation . kind === InvocationKind . TypeArgs ? undefined
105
+ : { invocation : info . invocation . node , argumentCount : info . argumentCount , argumentIndex : info . argumentIndex } ;
106
+ }
107
+
88
108
/**
89
109
* Returns relevant information for the argument list and the current argument if we are
90
110
* in the argument of an invocation; returns undefined otherwise.
91
111
*/
92
- export function getImmediatelyContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentListInfo | undefined {
112
+ function getImmediatelyContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentListInfo | undefined {
93
113
if ( isCallOrNewExpression ( node . parent ) ) {
94
114
const invocation = node . parent ;
95
115
let list : Node ;
@@ -134,7 +154,7 @@ namespace ts.SignatureHelp {
134
154
Debug . assertLessThan ( argumentIndex , argumentCount ) ;
135
155
}
136
156
const argumentsSpan = getApplicableSpanForArguments ( list , sourceFile ) ;
137
- return { kind, invocation, argumentsSpan, argumentIndex, argumentCount } ;
157
+ return { kind, invocation : { kind : InvocationKind . Call , node : invocation } , argumentsSpan, argumentIndex, argumentCount } ;
138
158
}
139
159
else if ( node . kind === SyntaxKind . NoSubstitutionTemplateLiteral && node . parent . kind === SyntaxKind . TaggedTemplateExpression ) {
140
160
// Check if we're actually inside the template;
@@ -178,16 +198,35 @@ namespace ts.SignatureHelp {
178
198
const attributeSpanEnd = skipTrivia ( sourceFile . text , node . parent . attributes . getEnd ( ) , /*stopAfterLineBreak*/ false ) ;
179
199
return {
180
200
kind : ArgumentListKind . JSXAttributesArguments ,
181
- invocation : node . parent ,
201
+ invocation : { kind : InvocationKind . Call , node : node . parent } ,
182
202
argumentsSpan : createTextSpan ( attributeSpanStart , attributeSpanEnd - attributeSpanStart ) ,
183
203
argumentIndex : 0 ,
184
204
argumentCount : 1
185
205
} ;
186
206
}
187
-
207
+ else if ( isBinaryExpression ( node . parent ) ) {
208
+ const info = getInfoForBinaryExpressionResemblingTypeArguments ( node . parent ) ;
209
+ if ( ! info ) return undefined ;
210
+ const { called, nTypeArguments } = info ;
211
+ const argumentsSpan = createTextSpanFromBounds ( called . getStart ( sourceFile ) , node . end ) ;
212
+ return { kind : ArgumentListKind . TypeArguments , invocation : { kind : InvocationKind . TypeArgs , called } , argumentsSpan, argumentIndex : nTypeArguments - 1 , argumentCount : nTypeArguments } ;
213
+ }
188
214
return undefined ;
189
215
}
190
216
217
+ /** Code like `f<T, U,` will be parsed as a series of `BinaryExpression`s. */
218
+ function getInfoForBinaryExpressionResemblingTypeArguments ( bin : BinaryExpression ) : { readonly called : Expression , readonly nTypeArguments : number } | undefined {
219
+ switch ( bin . operatorToken . kind ) {
220
+ case SyntaxKind . LessThanToken :
221
+ return { called : bin . left , nTypeArguments : 1 } ;
222
+ case SyntaxKind . CommaToken :
223
+ const left = isBinaryExpression ( bin . left ) ? getInfoForBinaryExpressionResemblingTypeArguments ( bin . left ) : undefined ;
224
+ return left && { called : left . called , nTypeArguments : left . nTypeArguments + 1 } ;
225
+ default :
226
+ return undefined ;
227
+ }
228
+ }
229
+
191
230
function getArgumentIndex ( argumentsList : Node , node : Node ) {
192
231
// The list we got back can include commas. In the presence of errors it may
193
232
// also just have nodes without commas. For example "Foo(a b c)" will have 3
@@ -269,7 +308,7 @@ namespace ts.SignatureHelp {
269
308
}
270
309
return {
271
310
kind : ArgumentListKind . TaggedTemplateArguments ,
272
- invocation : tagExpression ,
311
+ invocation : { kind : InvocationKind . Call , node : tagExpression } ,
273
312
argumentsSpan : getApplicableSpanForTaggedTemplate ( tagExpression , sourceFile ) ,
274
313
argumentIndex,
275
314
argumentCount
@@ -313,25 +352,15 @@ namespace ts.SignatureHelp {
313
352
return createTextSpan ( applicableSpanStart , applicableSpanEnd - applicableSpanStart ) ;
314
353
}
315
354
316
- export function getContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentListInfo {
317
- for ( let n = node ; n . kind !== SyntaxKind . SourceFile ; n = n . parent ) {
318
- if ( isFunctionBlock ( n ) ) {
319
- return undefined ;
320
- }
321
-
355
+ function getContainingArgumentInfo ( node : Node , position : number , sourceFile : SourceFile ) : ArgumentListInfo {
356
+ for ( let n = node ; ! isBlock ( n ) && ! isSourceFile ( n ) ; n = n . parent ) {
322
357
// If the node is not a subspan of its parent, this is a big problem.
323
358
// There have been crashes that might be caused by this violation.
324
- if ( n . pos < n . parent . pos || n . end > n . parent . end ) {
325
- Debug . fail ( "Node of kind " + n . kind + " is not a subspan of its parent of kind " + n . parent . kind ) ;
326
- }
327
-
359
+ Debug . assert ( rangeContainsRange ( n . parent , n ) , "Not a subspan" , ( ) => `Child: ${ Debug . showSyntaxKind ( n ) } , parent: ${ Debug . showSyntaxKind ( n . parent ) } ` ) ;
328
360
const argumentInfo = getImmediatelyContainingArgumentInfo ( n , position , sourceFile ) ;
329
361
if ( argumentInfo ) {
330
362
return argumentInfo ;
331
363
}
332
-
333
-
334
- // TODO: Handle generic call with incomplete syntax
335
364
}
336
365
return undefined ;
337
366
}
@@ -343,16 +372,20 @@ namespace ts.SignatureHelp {
343
372
return children [ indexOfOpenerToken + 1 ] ;
344
373
}
345
374
375
+ function getExpressionFromInvocation ( invocation : Invocation ) : Expression {
376
+ return invocation . kind === InvocationKind . Call ? getInvokedExpression ( invocation . node ) : invocation . called ;
377
+ }
378
+
346
379
const signatureHelpNodeBuilderFlags = NodeBuilderFlags . OmitParameterModifiers | NodeBuilderFlags . IgnoreErrors ;
347
- function createSignatureHelpItems ( candidates : Signature [ ] , resolvedSignature : Signature , argumentListInfo : ArgumentListInfo , typeChecker : TypeChecker ) : SignatureHelpItems {
380
+ function createSignatureHelpItems ( candidates : ReadonlyArray < Signature > , resolvedSignature : Signature , argumentListInfo : ArgumentListInfo , sourceFile : SourceFile , typeChecker : TypeChecker ) : SignatureHelpItems {
348
381
const { argumentCount, argumentsSpan : applicableSpan , invocation, argumentIndex } = argumentListInfo ;
349
382
const isTypeParameterList = argumentListInfo . kind === ArgumentListKind . TypeArguments ;
350
383
351
- const callTarget = getInvokedExpression ( invocation ) ;
352
- const callTargetSymbol = typeChecker . getSymbolAtLocation ( callTarget ) ;
384
+ const enclosingDeclaration = invocation . kind === InvocationKind . Call ? invocation . node : invocation . called ;
385
+ const callTargetSymbol = typeChecker . getSymbolAtLocation ( getExpressionFromInvocation ( invocation ) ) ;
353
386
const callTargetDisplayParts = callTargetSymbol && symbolToDisplayParts ( typeChecker , callTargetSymbol , /*enclosingDeclaration*/ undefined , /*meaning*/ undefined ) ;
354
387
const printer = createPrinter ( { removeComments : true } ) ;
355
- const items : SignatureHelpItem [ ] = map ( candidates , candidateSignature => {
388
+ const items = candidates . map < SignatureHelpItem > ( candidateSignature => {
356
389
let signatureHelpParameters : SignatureHelpParameter [ ] ;
357
390
const prefixDisplayParts : SymbolDisplayPart [ ] = [ ] ;
358
391
const suffixDisplayParts : SymbolDisplayPart [ ] = [ ] ;
@@ -369,18 +402,18 @@ namespace ts.SignatureHelp {
369
402
signatureHelpParameters = typeParameters && typeParameters . length > 0 ? map ( typeParameters , createSignatureHelpParameterForTypeParameter ) : emptyArray ;
370
403
suffixDisplayParts . push ( punctuationPart ( SyntaxKind . GreaterThanToken ) ) ;
371
404
const parameterParts = mapToDisplayParts ( writer => {
372
- const thisParameter = candidateSignature . thisParameter ? [ typeChecker . symbolToParameterDeclaration ( candidateSignature . thisParameter , invocation , signatureHelpNodeBuilderFlags ) ] : [ ] ;
373
- const params = createNodeArray ( [ ...thisParameter , ...map ( candidateSignature . parameters , param => typeChecker . symbolToParameterDeclaration ( param , invocation , signatureHelpNodeBuilderFlags ) ) ] ) ;
374
- printer . writeList ( ListFormat . CallExpressionArguments , params , getSourceFileOfNode ( getParseTreeNode ( invocation ) ) , writer ) ;
405
+ const thisParameter = candidateSignature . thisParameter ? [ typeChecker . symbolToParameterDeclaration ( candidateSignature . thisParameter , enclosingDeclaration , signatureHelpNodeBuilderFlags ) ] : [ ] ;
406
+ const params = createNodeArray ( [ ...thisParameter , ...map ( candidateSignature . parameters , param => typeChecker . symbolToParameterDeclaration ( param , enclosingDeclaration , signatureHelpNodeBuilderFlags ) ) ] ) ;
407
+ printer . writeList ( ListFormat . CallExpressionArguments , params , sourceFile , writer ) ;
375
408
} ) ;
376
409
addRange ( suffixDisplayParts , parameterParts ) ;
377
410
}
378
411
else {
379
412
isVariadic = candidateSignature . hasRestParameter ;
380
413
const typeParameterParts = mapToDisplayParts ( writer => {
381
414
if ( candidateSignature . typeParameters && candidateSignature . typeParameters . length ) {
382
- const args = createNodeArray ( map ( candidateSignature . typeParameters , p => typeChecker . typeParameterToDeclaration ( p , invocation ) ) ) ;
383
- printer . writeList ( ListFormat . TypeParameters , args , getSourceFileOfNode ( getParseTreeNode ( invocation ) ) , writer ) ;
415
+ const args = createNodeArray ( map ( candidateSignature . typeParameters , p => typeChecker . typeParameterToDeclaration ( p , enclosingDeclaration ) ) ) ;
416
+ printer . writeList ( ListFormat . TypeParameters , args , sourceFile , writer ) ;
384
417
}
385
418
} ) ;
386
419
addRange ( prefixDisplayParts , typeParameterParts ) ;
@@ -395,10 +428,10 @@ namespace ts.SignatureHelp {
395
428
writer . writeSpace ( " " ) ;
396
429
const predicate = typeChecker . getTypePredicateOfSignature ( candidateSignature ) ;
397
430
if ( predicate ) {
398
- typeChecker . writeTypePredicate ( predicate , invocation , /*flags*/ undefined , writer ) ;
431
+ typeChecker . writeTypePredicate ( predicate , enclosingDeclaration , /*flags*/ undefined , writer ) ;
399
432
}
400
433
else {
401
- typeChecker . writeType ( typeChecker . getReturnTypeOfSignature ( candidateSignature ) , invocation , /*flags*/ undefined , writer ) ;
434
+ typeChecker . writeType ( typeChecker . getReturnTypeOfSignature ( candidateSignature ) , enclosingDeclaration , /*flags*/ undefined , writer ) ;
402
435
}
403
436
} ) ;
404
437
addRange ( suffixDisplayParts , returnTypeParts ) ;
@@ -425,8 +458,8 @@ namespace ts.SignatureHelp {
425
458
426
459
function createSignatureHelpParameterForParameter ( parameter : Symbol ) : SignatureHelpParameter {
427
460
const displayParts = mapToDisplayParts ( writer => {
428
- const param = typeChecker . symbolToParameterDeclaration ( parameter , invocation , signatureHelpNodeBuilderFlags ) ;
429
- printer . writeNode ( EmitHint . Unspecified , param , getSourceFileOfNode ( getParseTreeNode ( invocation ) ) , writer ) ;
461
+ const param = typeChecker . symbolToParameterDeclaration ( parameter , enclosingDeclaration , signatureHelpNodeBuilderFlags ) ;
462
+ printer . writeNode ( EmitHint . Unspecified , param , sourceFile , writer ) ;
430
463
} ) ;
431
464
432
465
return {
@@ -439,8 +472,8 @@ namespace ts.SignatureHelp {
439
472
440
473
function createSignatureHelpParameterForTypeParameter ( typeParameter : TypeParameter ) : SignatureHelpParameter {
441
474
const displayParts = mapToDisplayParts ( writer => {
442
- const param = typeChecker . typeParameterToDeclaration ( typeParameter , invocation ) ;
443
- printer . writeNode ( EmitHint . Unspecified , param , getSourceFileOfNode ( getParseTreeNode ( invocation ) ) , writer ) ;
475
+ const param = typeChecker . typeParameterToDeclaration ( typeParameter , enclosingDeclaration ) ;
476
+ printer . writeNode ( EmitHint . Unspecified , param , sourceFile , writer ) ;
444
477
} ) ;
445
478
446
479
return {
0 commit comments