@@ -18,7 +18,7 @@ namespace ts.Completions {
1818 return undefined ;
1919 }
2020
21- const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, requestJsDocTagName , requestJsDocTag , hasFilteredClassMemberKeywords } = completionData ;
21+ const { symbols, isGlobalCompletion, isMemberCompletion, isNewIdentifierLocation, location, request , hasFilteredClassMemberKeywords } = completionData ;
2222
2323 if ( sourceFile . languageVariant === LanguageVariant . JSX &&
2424 location && location . parent && location . parent . kind === SyntaxKind . JsxClosingElement ) {
@@ -36,14 +36,15 @@ namespace ts.Completions {
3636 } ] } ;
3737 }
3838
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 } ;
4748 }
4849
4950 const entries : CompletionEntry [ ] = [ ] ;
@@ -66,7 +67,7 @@ namespace ts.Completions {
6667 addRange ( entries , classMemberKeywordCompletions ) ;
6768 }
6869 // Add keywords if this is not a member completion list
69- else if ( ! isMemberCompletion && ! requestJsDocTag && ! requestJsDocTagName ) {
70+ else if ( ! isMemberCompletion ) {
7071 addRange ( entries , keywordCompletions ) ;
7172 }
7273
@@ -347,16 +348,27 @@ namespace ts.Completions {
347348 return undefined ;
348349 }
349350
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 {
351364 const isJavaScriptFile = isSourceFileJavaScript ( sourceFile ) ;
352365
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 ;
357367
358368 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+
360372 log ( "getCompletionData: Get current token: " + ( timestamp ( ) - start ) ) ;
361373
362374 start = timestamp ( ) ;
@@ -366,10 +378,10 @@ namespace ts.Completions {
366378
367379 if ( insideComment ) {
368380 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
371381 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" } ;
373385 }
374386 else {
375387 // When completion is requested without "@", we will have check to make sure that
@@ -389,34 +401,39 @@ namespace ts.Completions {
389401 // * |c|
390402 // */
391403 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+ }
393407 }
394408 }
395409
396410 // Completion should work inside certain JsDoc tags. For example:
397411 // /** @type {number | string } */
398412 // Completion should work in the brackets
399413 let insideJsDocTagExpression = false ;
400- const tag = getJsDocTagAtPosition ( sourceFile , position ) ;
414+ const tag = getJsDocTagAtPosition ( currentToken , position ) ;
401415 if ( tag ) {
402416 if ( tag . tagName . pos <= position && position <= tag . tagName . end ) {
403- requestJsDocTagName = true ;
417+ request = { kind : "JsDocTagName" } ;
404418 }
405419
406420 switch ( tag . kind ) {
407421 case SyntaxKind . JSDocTypeTag :
408422 case SyntaxKind . JSDocParameterTag :
409423 case SyntaxKind . JSDocReturnTag :
410424 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 } ;
413430 }
414431 break ;
415432 }
416433 }
417434
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 } ;
420437 }
421438
422439 if ( ! insideJsDocTagExpression ) {
@@ -553,7 +570,7 @@ namespace ts.Completions {
553570
554571 log ( "getCompletionData: Semantic work: " + ( timestamp ( ) - semanticStart ) ) ;
555572
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 } ;
557574
558575 function getTypeScriptMemberSymbols ( ) : void {
559576 // Right of dot member completion list
@@ -1518,4 +1535,34 @@ namespace ts.Completions {
15181535 kind === SyntaxKind . EqualsEqualsEqualsToken ||
15191536 kind === SyntaxKind . ExclamationEqualsEqualsToken ;
15201537 }
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+ }
15211568}
0 commit comments