@@ -101,7 +101,7 @@ namespace ts.Completions {
101101 }
102102
103103 function completionInfoFromData ( sourceFile : SourceFile , typeChecker : TypeChecker , compilerOptions : CompilerOptions , log : Log , completionData : CompletionData , preferences : UserPreferences ) : CompletionInfo | undefined {
104- const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData ;
104+ const { symbols, completionKind, isInSnippetScope, isNewIdentifierLocation, location, propertyAccessToConvert, keywordFilters, literals , symbolToOriginInfoMap, recommendedCompletion, isJsxInitializer } = completionData ;
105105
106106 if ( sourceFile . languageVariant === LanguageVariant . JSX && location && location . parent && isJsxClosingElement ( location . parent ) ) {
107107 // In the TypeScript JSX element, if such element is not defined. When users query for completion at closing tag,
@@ -143,6 +143,10 @@ namespace ts.Completions {
143143 addRange ( entries , getKeywordCompletions ( keywordFilters ) ) ;
144144 }
145145
146+ for ( const literal of literals ) {
147+ entries . push ( createCompletionEntryForLiteral ( literal ) ) ;
148+ }
149+
146150 return { isGlobalCompletion : isInSnippetScope , isMemberCompletion, isNewIdentifierLocation, entries } ;
147151 }
148152
@@ -184,6 +188,11 @@ namespace ts.Completions {
184188 } ) ;
185189 }
186190
191+ const completionNameForLiteral = JSON . stringify ;
192+ function createCompletionEntryForLiteral ( literal : string | number ) : CompletionEntry {
193+ return { name : completionNameForLiteral ( literal ) , kind : ScriptElementKind . string , kindModifiers : ScriptElementKindModifier . none , sortText : "0" } ;
194+ }
195+
187196 function createCompletionEntry (
188197 symbol : Symbol ,
189198 location : Node | undefined ,
@@ -372,7 +381,7 @@ namespace ts.Completions {
372381 case SyntaxKind . LiteralType :
373382 switch ( node . parent . parent . kind ) {
374383 case SyntaxKind . TypeReference :
375- return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( typeChecker . getTypeArgumentConstraint ( node . parent as LiteralTypeNode ) , typeChecker ) , isNewIdentifier : false } ;
384+ return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( typeChecker . getTypeArgumentConstraint ( node . parent as LiteralTypeNode ) ) , isNewIdentifier : false } ;
376385 case SyntaxKind . IndexedAccessType :
377386 // Get all apparent property names
378387 // i.e. interface Foo {
@@ -448,7 +457,7 @@ namespace ts.Completions {
448457 function fromContextualType ( ) : StringLiteralCompletion {
449458 // Get completion for string literal from string literal type
450459 // i.e. var x: "hi" | "hello" = "/*completion position*/"
451- return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( getContextualTypeFromParent ( node , typeChecker ) , typeChecker ) , isNewIdentifier : false } ;
460+ return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( getContextualTypeFromParent ( node , typeChecker ) ) , isNewIdentifier : false } ;
452461 }
453462 }
454463
@@ -462,7 +471,7 @@ namespace ts.Completions {
462471 if ( ! candidate . hasRestParameter && argumentInfo . argumentCount > candidate . parameters . length ) return ;
463472 const type = checker . getParameterType ( candidate , argumentInfo . argumentIndex ) ;
464473 isNewIdentifier = isNewIdentifier || ! ! ( type . flags & TypeFlags . String ) ;
465- return getStringLiteralTypes ( type , checker , uniques ) ;
474+ return getStringLiteralTypes ( type , uniques ) ;
466475 } ) ;
467476
468477 return { kind : StringLiteralCompletionKind . Types , types, isNewIdentifier } ;
@@ -472,11 +481,11 @@ namespace ts.Completions {
472481 return type && { kind : StringLiteralCompletionKind . Properties , symbols : type . getApparentProperties ( ) , hasIndexSignature : hasIndexSignature ( type ) } ;
473482 }
474483
475- function getStringLiteralTypes ( type : Type | undefined , typeChecker : TypeChecker , uniques = createMap < true > ( ) ) : ReadonlyArray < StringLiteralType > {
484+ function getStringLiteralTypes ( type : Type | undefined , uniques = createMap < true > ( ) ) : ReadonlyArray < StringLiteralType > {
476485 if ( ! type ) return emptyArray ;
477486 type = skipConstraint ( type ) ;
478487 return type . isUnion ( )
479- ? flatMap ( type . types , t => getStringLiteralTypes ( t , typeChecker , uniques ) )
488+ ? flatMap ( type . types , t => getStringLiteralTypes ( t , uniques ) )
480489 : type . isStringLiteral ( ) && ! ( type . flags & TypeFlags . EnumLiteral ) && addToSeen ( uniques , type . value )
481490 ? [ type ]
482491 : emptyArray ;
@@ -491,7 +500,7 @@ namespace ts.Completions {
491500 readonly isJsxInitializer : IsJsxInitializer ;
492501 }
493502 function getSymbolCompletionFromEntryId ( program : Program , log : Log , sourceFile : SourceFile , position : number , entryId : CompletionEntryIdentifier ,
494- ) : SymbolCompletion | { type : "request" , request : Request } | { type : "none" } {
503+ ) : SymbolCompletion | { type : "request" , request : Request } | { type : "literal" , literal : string | number } | { type : " none" } {
495504 const compilerOptions = program . getCompilerOptions ( ) ;
496505 const completionData = getCompletionData ( program , log , sourceFile , isUncheckedFile ( sourceFile , compilerOptions ) , position , { includeCompletionsForModuleExports : true , includeCompletionsWithInsertText : true } , entryId ) ;
497506 if ( ! completionData ) {
@@ -501,7 +510,10 @@ namespace ts.Completions {
501510 return { type : "request" , request : completionData } ;
502511 }
503512
504- const { symbols, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData ;
513+ const { symbols, literals, location, completionKind, symbolToOriginInfoMap, previousToken, isJsxInitializer } = completionData ;
514+
515+ const literal = find ( literals , l => completionNameForLiteral ( l ) === entryId . name ) ;
516+ if ( literal !== undefined ) return { type : "literal" , literal } ;
505517
506518 // Find the symbol with the matching entry name.
507519 // We don't need to perform character checks here because we're only comparing the
@@ -574,12 +586,22 @@ namespace ts.Completions {
574586 const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay ( symbolToOriginInfoMap , symbol , program , typeChecker , host , compilerOptions , sourceFile , previousToken , formatContext , getCanonicalFileName , program . getSourceFiles ( ) , preferences ) ;
575587 return createCompletionDetailsForSymbol ( symbol , typeChecker , sourceFile , location ! , cancellationToken , codeActions , sourceDisplay ) ; // TODO: GH#18217
576588 }
589+ case "literal" : {
590+ const { literal } = symbolCompletion ;
591+ return createSimpleDetails ( completionNameForLiteral ( literal ) , ScriptElementKind . string , typeof literal === "string" ? SymbolDisplayPartKind . stringLiteral : SymbolDisplayPartKind . numericLiteral ) ;
592+ }
577593 case "none" :
578594 // Didn't find a symbol with this name. See if we can find a keyword instead.
579- return allKeywordsCompletions ( ) . some ( c => c . name === name ) ? createCompletionDetails ( name , ScriptElementKindModifier . none , ScriptElementKind . keyword , [ displayPart ( name , SymbolDisplayPartKind . keyword ) ] ) : undefined ;
595+ return allKeywordsCompletions ( ) . some ( c => c . name === name ) ? createSimpleDetails ( name , ScriptElementKind . keyword , SymbolDisplayPartKind . keyword ) : undefined ;
596+ default :
597+ Debug . assertNever ( symbolCompletion ) ;
580598 }
581599 }
582600
601+ function createSimpleDetails ( name : string , kind : ScriptElementKind , kind2 : SymbolDisplayPartKind ) : CompletionEntryDetails {
602+ return createCompletionDetails ( name , ScriptElementKindModifier . none , kind , [ displayPart ( name , kind2 ) ] ) ;
603+ }
604+
583605 function createCompletionDetailsForSymbol ( symbol : Symbol , checker : TypeChecker , sourceFile : SourceFile , location : Node , cancellationToken : CancellationToken , codeActions ?: CodeAction [ ] , sourceDisplay ?: SymbolDisplayPart [ ] ) : CompletionEntryDetails {
584606 const { displayParts, documentation, symbolKind, tags } =
585607 checker . runWithCancellationToken ( cancellationToken , checker =>
@@ -669,6 +691,7 @@ namespace ts.Completions {
669691 readonly isNewIdentifierLocation : boolean ;
670692 readonly location : Node | undefined ;
671693 readonly keywordFilters : KeywordCompletionFilters ;
694+ readonly literals : ReadonlyArray < string | number > ;
672695 readonly symbolToOriginInfoMap : SymbolOriginInfoMap ;
673696 readonly recommendedCompletion : Symbol | undefined ;
674697 readonly previousToken : Node | undefined ;
@@ -685,23 +708,22 @@ namespace ts.Completions {
685708 None ,
686709 }
687710
688- function getRecommendedCompletion ( currentToken : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : Symbol | undefined {
689- const contextualType = getContextualType ( currentToken , position , sourceFile , checker ) ;
711+ function getRecommendedCompletion ( previousToken : Node , contextualType : Type , checker : TypeChecker ) : Symbol | undefined {
690712 // For a union, return the first one with a recommended completion.
691713 return firstDefined ( contextualType && ( contextualType . isUnion ( ) ? contextualType . types : [ contextualType ] ) , type => {
692714 const symbol = type && type . symbol ;
693715 // Don't include make a recommended completion for an abstract class
694716 return symbol && ( symbol . flags & ( SymbolFlags . EnumMember | SymbolFlags . Enum | SymbolFlags . Class ) && ! isAbstractConstructorSymbol ( symbol ) )
695- ? getFirstSymbolInChain ( symbol , currentToken , checker )
717+ ? getFirstSymbolInChain ( symbol , previousToken , checker )
696718 : undefined ;
697719 } ) ;
698720 }
699721
700- function getContextualType ( currentToken : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : Type | undefined {
701- const { parent } = currentToken ;
702- switch ( currentToken . kind ) {
722+ function getContextualType ( previousToken : Node , position : number , sourceFile : SourceFile , checker : TypeChecker ) : Type | undefined {
723+ const { parent } = previousToken ;
724+ switch ( previousToken . kind ) {
703725 case SyntaxKind . Identifier :
704- return getContextualTypeFromParent ( currentToken as Identifier , checker ) ;
726+ return getContextualTypeFromParent ( previousToken as Identifier , checker ) ;
705727 case SyntaxKind . EqualsToken :
706728 switch ( parent . kind ) {
707729 case SyntaxKind . VariableDeclaration :
@@ -720,14 +742,14 @@ namespace ts.Completions {
720742 case SyntaxKind . OpenBraceToken :
721743 return isJsxExpression ( parent ) && parent . parent . kind !== SyntaxKind . JsxElement ? checker . getContextualTypeForJsxAttribute ( parent . parent ) : undefined ;
722744 default :
723- const argInfo = SignatureHelp . getArgumentInfoForCompletions ( currentToken , position , sourceFile ) ;
745+ const argInfo = SignatureHelp . getArgumentInfoForCompletions ( previousToken , position , sourceFile ) ;
724746 return argInfo
725747 // At `,`, treat this as the next argument after the comma.
726- ? checker . getContextualTypeForArgumentAtIndex ( argInfo . invocation , argInfo . argumentIndex + ( currentToken . kind === SyntaxKind . CommaToken ? 1 : 0 ) )
727- : isEqualityOperatorKind ( currentToken . kind ) && isBinaryExpression ( parent ) && isEqualityOperatorKind ( parent . operatorToken . kind )
748+ ? checker . getContextualTypeForArgumentAtIndex ( argInfo . invocation , argInfo . argumentIndex + ( previousToken . kind === SyntaxKind . CommaToken ? 1 : 0 ) )
749+ : isEqualityOperatorKind ( previousToken . kind ) && isBinaryExpression ( parent ) && isEqualityOperatorKind ( parent . operatorToken . kind )
728750 // completion at `x ===/**/` should be for the right side
729751 ? checker . getTypeAtLocation ( parent . left )
730- : checker . getContextualType ( currentToken as Expression ) ;
752+ : checker . getContextualType ( previousToken as Expression ) ;
731753 }
732754 }
733755
@@ -1005,8 +1027,11 @@ namespace ts.Completions {
10051027
10061028 log ( "getCompletionData: Semantic work: " + ( timestamp ( ) - semanticStart ) ) ;
10071029
1008- const recommendedCompletion = previousToken && getRecommendedCompletion ( previousToken , position , sourceFile , typeChecker ) ;
1009- return { kind : CompletionDataKind . Data , symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer } ;
1030+ const contextualType = previousToken && getContextualType ( previousToken , position , sourceFile , typeChecker ) ;
1031+ const literals = mapDefined ( contextualType && ( contextualType . isUnion ( ) ? contextualType . types : [ contextualType ] ) , t => t . isLiteral ( ) ? t . value : undefined ) ;
1032+
1033+ const recommendedCompletion = previousToken && contextualType && getRecommendedCompletion ( previousToken , contextualType , typeChecker ) ;
1034+ return { kind : CompletionDataKind . Data , symbols, completionKind, isInSnippetScope, propertyAccessToConvert, isNewIdentifierLocation, location, keywordFilters, literals, symbolToOriginInfoMap, recommendedCompletion, previousToken, isJsxInitializer } ;
10101035
10111036 type JSDocTagWithTypeExpression = JSDocParameterTag | JSDocPropertyTag | JSDocReturnTag | JSDocTypeTag | JSDocTypedefTag ;
10121037
0 commit comments