@@ -18,7 +18,7 @@ namespace ts.Completions {
18
18
return undefined ;
19
19
}
20
20
21
- const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName , requestJsDocTag , hasFilteredClassMemberKeywords } = completionData ;
21
+ const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, request , hasFilteredClassMemberKeywords } = completionData ;
22
22
23
23
if ( sourceFile . languageVariant === LanguageVariant . JSX &&
24
24
location && location . parent && location . parent . kind === SyntaxKind . JsxClosingElement ) {
@@ -36,14 +36,15 @@ namespace ts.Completions {
36
36
} ] } ;
37
37
}
38
38
39
- if ( requestJsDocTagName ) {
40
- // If the current position is a jsDoc tag name, only tag names should be provided for completion
41
- return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries : JsDoc . getJSDocTagNameCompletions ( ) } ;
42
- }
43
-
44
- if ( requestJsDocTag ) {
45
- // If the current position is a jsDoc tag, only tags should be provided for completion
46
- return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries : JsDoc . getJSDocTagCompletions ( ) } ;
39
+ if ( request ) {
40
+ const entries = request . kind === "JsDocTagName"
41
+ // If the current position is a jsDoc tag name, only tag names should be provided for completion
42
+ ? JsDoc . getJSDocTagNameCompletions ( )
43
+ : request . kind === "JsDocTag"
44
+ // If the current position is a jsDoc tag, only tags should be provided for completion
45
+ ? JsDoc . getJSDocTagCompletions ( )
46
+ : JsDoc . getJSDocParameterNameCompletions ( request . tag ) ;
47
+ return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
47
48
}
48
49
49
50
const entries : CompletionEntry [ ] = [ ] ;
@@ -66,7 +67,7 @@ namespace ts.Completions {
66
67
addRange ( entries , classMemberKeywordCompletions ) ;
67
68
}
68
69
// Add keywords if this is not a member completion list
69
- else if ( ! isMemberCompletion && ! requestJsDocTag && ! requestJsDocTagName ) {
70
+ else if ( ! isMemberCompletion ) {
70
71
addRange ( entries , keywordCompletions ) ;
71
72
}
72
73
@@ -347,16 +348,27 @@ namespace ts.Completions {
347
348
return undefined ;
348
349
}
349
350
350
- function getCompletionData ( typeChecker : TypeChecker , log : ( message : string ) => void , sourceFile : SourceFile , position : number ) {
351
+ interface CompletionData {
352
+ symbols : Symbol [ ] ;
353
+ isGlobalCompletion : boolean ;
354
+ isMemberCompletion : boolean ;
355
+ isNewIdentifierLocation : boolean ;
356
+ location : Node ;
357
+ isRightOfDot : boolean ;
358
+ request ?: Request ;
359
+ hasFilteredClassMemberKeywords : boolean ;
360
+ }
361
+ type Request = { kind : "JsDocTagName" } | { kind : "JsDocTag" } | { kind : "JsDocParameterName" , tag : JSDocParameterTag } ;
362
+
363
+ function getCompletionData ( typeChecker : TypeChecker , log : ( message : string ) => void , sourceFile : SourceFile , position : number ) : CompletionData {
351
364
const isJavaScriptFile = isSourceFileJavaScript ( sourceFile ) ;
352
365
353
- // JsDoc tag-name is just the name of the JSDoc tagname (exclude "@")
354
- let requestJsDocTagName = false ;
355
- // JsDoc tag includes both "@" and tag-name
356
- let requestJsDocTag = false ;
366
+ let request : Request | undefined ;
357
367
358
368
let start = timestamp ( ) ;
359
- const currentToken = getTokenAtPosition ( sourceFile , position , /*includeJsDocComment*/ false ) ; // TODO: GH#15853
369
+ const currentToken = getTokenAtPosition ( sourceFile , position , /*includeJsDocComment*/ false ) ;
370
+ // We will check for jsdoc comments with insideComment and getJsDocTagAtPosition. (TODO: that seems rather inefficient to check the same thing so many times.)
371
+
360
372
log ( "getCompletionData: Get current token: " + ( timestamp ( ) - start ) ) ;
361
373
362
374
start = timestamp ( ) ;
@@ -366,10 +378,10 @@ namespace ts.Completions {
366
378
367
379
if ( insideComment ) {
368
380
if ( hasDocComment ( sourceFile , position ) ) {
369
- // The current position is next to the '@' sign, when no tag name being provided yet.
370
- // Provide a full list of tag names
371
381
if ( sourceFile . text . charCodeAt ( position - 1 ) === CharacterCodes . at ) {
372
- requestJsDocTagName = true ;
382
+ // The current position is next to the '@' sign, when no tag name being provided yet.
383
+ // Provide a full list of tag names
384
+ request = { kind : "JsDocTagName" } ;
373
385
}
374
386
else {
375
387
// When completion is requested without "@", we will have check to make sure that
@@ -389,34 +401,39 @@ namespace ts.Completions {
389
401
// * |c|
390
402
// */
391
403
const lineStart = getLineStartPositionForPosition ( position , sourceFile ) ;
392
- requestJsDocTag = ! ( sourceFile . text . substring ( lineStart , position ) . match ( / [ ^ \* | \s | ( / \* \* ) ] / ) ) ;
404
+ if ( ! ( sourceFile . text . substring ( lineStart , position ) . match ( / [ ^ \* | \s | ( / \* \* ) ] / ) ) ) {
405
+ request = { kind : "JsDocTag" } ;
406
+ }
393
407
}
394
408
}
395
409
396
410
// Completion should work inside certain JsDoc tags. For example:
397
411
// /** @type {number | string } */
398
412
// Completion should work in the brackets
399
413
let insideJsDocTagExpression = false ;
400
- const tag = getJsDocTagAtPosition ( sourceFile , position ) ;
414
+ const tag = getJsDocTagAtPosition ( currentToken , position ) ;
401
415
if ( tag ) {
402
416
if ( tag . tagName . pos <= position && position <= tag . tagName . end ) {
403
- requestJsDocTagName = true ;
417
+ request = { kind : "JsDocTagName" } ;
404
418
}
405
419
406
420
switch ( tag . kind ) {
407
421
case SyntaxKind . JSDocTypeTag :
408
422
case SyntaxKind . JSDocParameterTag :
409
423
case SyntaxKind . JSDocReturnTag :
410
424
const tagWithExpression = < JSDocTypeTag | JSDocParameterTag | JSDocReturnTag > tag ;
411
- if ( tagWithExpression . typeExpression ) {
412
- insideJsDocTagExpression = tagWithExpression . typeExpression . pos < position && position < tagWithExpression . typeExpression . end ;
425
+ if ( tagWithExpression . typeExpression && tagWithExpression . typeExpression . pos < position && position < tagWithExpression . typeExpression . end ) {
426
+ insideJsDocTagExpression = true ;
427
+ }
428
+ else if ( isJSDocParameterTag ( tag ) && ( nodeIsMissing ( tag . name ) || tag . name . pos <= position && position <= tag . name . end ) ) {
429
+ request = { kind : "JsDocParameterName" , tag } ;
413
430
}
414
431
break ;
415
432
}
416
433
}
417
434
418
- if ( requestJsDocTagName || requestJsDocTag ) {
419
- return { symbols : undefined , isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , location : undefined , isRightOfDot : false , requestJsDocTagName , requestJsDocTag , hasFilteredClassMemberKeywords : false } ;
435
+ if ( request ) {
436
+ return { symbols : undefined , isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , location : undefined , isRightOfDot : false , request , hasFilteredClassMemberKeywords : false } ;
420
437
}
421
438
422
439
if ( ! insideJsDocTagExpression ) {
@@ -553,7 +570,7 @@ namespace ts.Completions {
553
570
554
571
log ( "getCompletionData: Semantic work: " + ( timestamp ( ) - semanticStart ) ) ;
555
572
556
- return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot : ( isRightOfDot || isRightOfOpenTag ) , requestJsDocTagName , requestJsDocTag , hasFilteredClassMemberKeywords } ;
573
+ return { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, isRightOfDot : ( isRightOfDot || isRightOfOpenTag ) , request , hasFilteredClassMemberKeywords } ;
557
574
558
575
function getTypeScriptMemberSymbols ( ) : void {
559
576
// Right of dot member completion list
@@ -1518,4 +1535,34 @@ namespace ts.Completions {
1518
1535
kind === SyntaxKind . EqualsEqualsEqualsToken ||
1519
1536
kind === SyntaxKind . ExclamationEqualsEqualsToken ;
1520
1537
}
1538
+
1539
+ /** Get the corresponding JSDocTag node if the position is in a jsDoc comment */
1540
+ function getJsDocTagAtPosition ( node : Node , position : number ) : JSDocTag | undefined {
1541
+ const { jsDoc } = getJsDocHavingNode ( node ) ;
1542
+ if ( ! jsDoc ) return undefined ;
1543
+
1544
+ for ( const { pos, end, tags } of jsDoc ) {
1545
+ if ( ! tags || position < pos || position > end ) continue ;
1546
+ for ( let i = tags . length - 1 ; i >= 0 ; i -- ) {
1547
+ const tag = tags [ i ] ;
1548
+ if ( position >= tag . pos ) {
1549
+ return tag ;
1550
+ }
1551
+ }
1552
+ }
1553
+ }
1554
+
1555
+ function getJsDocHavingNode ( node : Node ) : Node {
1556
+ if ( ! isToken ( node ) ) return node ;
1557
+
1558
+ switch ( node . kind ) {
1559
+ case SyntaxKind . VarKeyword :
1560
+ case SyntaxKind . LetKeyword :
1561
+ case SyntaxKind . ConstKeyword :
1562
+ // if the current token is var, let or const, skip the VariableDeclarationList
1563
+ return node . parent . parent ;
1564
+ default :
1565
+ return node . parent ;
1566
+ }
1567
+ }
1521
1568
}
0 commit comments