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