44namespace ts . Completions {
55 export type Log = ( message : string ) => void ;
66
7- type SymbolOriginInfo = { type : "this-type" } | SymbolOriginInfoExport ;
7+ type SymbolOriginInfo = { type : "this-type" } | { type : "symbol-member" } | SymbolOriginInfoExport ;
88 interface SymbolOriginInfoExport {
99 type : "export" ;
1010 moduleSymbol : Symbol ;
@@ -83,7 +83,7 @@ namespace ts.Completions {
8383 }
8484 case StringLiteralCompletionKind . Types : {
8585 const entries = completion . types . map ( type => ( { name : type . value , kindModifiers : ScriptElementKindModifier . none , kind : ScriptElementKind . typeElement , sortText : "0" } ) ) ;
86- return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : false , entries } ;
86+ return { isGlobalCompletion : false , isMemberCompletion : false , isNewIdentifierLocation : completion . isNewIdentifier , entries } ;
8787 }
8888 default :
8989 return Debug . assertNever ( completion ) ;
@@ -206,11 +206,13 @@ namespace ts.Completions {
206206 if ( origin && origin . type === "this-type" ) {
207207 insertText = needsConvertPropertyAccess ? `this[${ quote ( name , preferences ) } ]` : `this.${ name } ` ;
208208 }
209- else if ( needsConvertPropertyAccess ) {
210- insertText = `[${ quote ( name , preferences ) } ]` ;
209+ // We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
210+ // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
211+ else if ( ( origin && origin . type === "symbol-member" || needsConvertPropertyAccess ) && propertyAccessToConvert ) {
212+ insertText = needsConvertPropertyAccess ? `[${ quote ( name , preferences ) } ]` : `[${ name } ]` ;
211213 const dot = findChildOfKind ( propertyAccessToConvert ! , SyntaxKind . DotToken , sourceFile ) ! ;
212214 // If the text after the '.' starts with this name, write over it. Else, add new text.
213- const end = startsWith ( name , propertyAccessToConvert ! . name . text ) ? propertyAccessToConvert ! . name . end : dot . end ;
215+ const end = startsWith ( name , propertyAccessToConvert . name . text ) ? propertyAccessToConvert . name . end : dot . end ;
214216 replacementSpan = createTextSpanFromBounds ( dot . getStart ( sourceFile ) , end ) ;
215217 }
216218
@@ -358,16 +360,18 @@ namespace ts.Completions {
358360 readonly symbols : ReadonlyArray < Symbol > ;
359361 readonly hasIndexSignature : boolean ;
360362 }
361- type StringLiteralCompletion =
362- | { readonly kind : StringLiteralCompletionKind . Paths , readonly paths : ReadonlyArray < PathCompletions . PathCompletion > }
363- | StringLiteralCompletionsFromProperties
364- | { readonly kind : StringLiteralCompletionKind . Types , readonly types : ReadonlyArray < StringLiteralType > } ;
363+ interface StringLiteralCompletionsFromTypes {
364+ readonly kind : StringLiteralCompletionKind . Types ;
365+ readonly types : ReadonlyArray < StringLiteralType > ;
366+ readonly isNewIdentifier : boolean ;
367+ }
368+ type StringLiteralCompletion = { readonly kind : StringLiteralCompletionKind . Paths , readonly paths : ReadonlyArray < PathCompletions . PathCompletion > } | StringLiteralCompletionsFromProperties | StringLiteralCompletionsFromTypes ;
365369 function getStringLiteralCompletionEntries ( sourceFile : SourceFile , node : StringLiteralLike , position : number , typeChecker : TypeChecker , compilerOptions : CompilerOptions , host : LanguageServiceHost ) : StringLiteralCompletion | undefined {
366370 switch ( node . parent . kind ) {
367371 case SyntaxKind . LiteralType :
368372 switch ( node . parent . parent . kind ) {
369373 case SyntaxKind . TypeReference :
370- return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( typeChecker . getTypeArgumentConstraint ( node . parent as LiteralTypeNode ) , typeChecker ) } ;
374+ return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( typeChecker . getTypeArgumentConstraint ( node . parent as LiteralTypeNode ) , typeChecker ) , isNewIdentifier : false } ;
371375 case SyntaxKind . IndexedAccessType :
372376 // Get all apparent property names
373377 // i.e. interface Foo {
@@ -376,6 +380,8 @@ namespace ts.Completions {
376380 // }
377381 // let x: Foo["/*completion position*/"]
378382 return stringLiteralCompletionsFromProperties ( typeChecker . getTypeFromTypeNode ( ( node . parent . parent as IndexedAccessTypeNode ) . objectType ) ) ;
383+ case SyntaxKind . ImportTypeNode :
384+ return { kind : StringLiteralCompletionKind . Paths , paths : PathCompletions . getStringLiteralCompletionsFromModuleNames ( sourceFile , node , compilerOptions , host , typeChecker ) } ;
379385 default :
380386 return undefined ;
381387 }
@@ -419,13 +425,7 @@ namespace ts.Completions {
419425 // Get string literal completions from specialized signatures of the target
420426 // i.e. declare function f(a: 'A');
421427 // f("/*completion position*/")
422- if ( argumentInfo ) {
423- const candidates : Signature [ ] = [ ] ;
424- typeChecker . getResolvedSignature ( argumentInfo . invocation , candidates , argumentInfo . argumentCount ) ;
425- const uniques = createMap < true > ( ) ;
426- return { kind : StringLiteralCompletionKind . Types , types : flatMap ( candidates , candidate => getStringLiteralTypes ( typeChecker . getParameterType ( candidate , argumentInfo . argumentIndex ) , typeChecker , uniques ) ) } ;
427- }
428- return fromContextualType ( ) ;
428+ return argumentInfo ? getStringLiteralCompletionsFromSignature ( argumentInfo , typeChecker ) : fromContextualType ( ) ;
429429 }
430430 // falls through (is `require("")` or `import("")`)
431431
@@ -447,10 +447,26 @@ namespace ts.Completions {
447447 function fromContextualType ( ) : StringLiteralCompletion {
448448 // Get completion for string literal from string literal type
449449 // i.e. var x: "hi" | "hello" = "/*completion position*/"
450- return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( getContextualTypeFromParent ( node , typeChecker ) , typeChecker ) } ;
450+ return { kind : StringLiteralCompletionKind . Types , types : getStringLiteralTypes ( getContextualTypeFromParent ( node , typeChecker ) , typeChecker ) , isNewIdentifier : false } ;
451451 }
452452 }
453453
454+ function getStringLiteralCompletionsFromSignature ( argumentInfo : SignatureHelp . ArgumentListInfo , checker : TypeChecker ) : StringLiteralCompletionsFromTypes {
455+ let isNewIdentifier = false ;
456+
457+ const uniques = createMap < true > ( ) ;
458+ const candidates : Signature [ ] = [ ] ;
459+ checker . getResolvedSignature ( argumentInfo . invocation , candidates , argumentInfo . argumentCount ) ;
460+ const types = flatMap ( candidates , candidate => {
461+ if ( ! candidate . hasRestParameter && argumentInfo . argumentCount > candidate . parameters . length ) return ;
462+ const type = checker . getParameterType ( candidate , argumentInfo . argumentIndex ) ;
463+ isNewIdentifier = isNewIdentifier || ! ! ( type . flags & TypeFlags . String ) ;
464+ return getStringLiteralTypes ( type , checker , uniques ) ;
465+ } ) ;
466+
467+ return { kind : StringLiteralCompletionKind . Types , types, isNewIdentifier } ;
468+ }
469+
454470 function stringLiteralCompletionsFromProperties ( type : Type | undefined ) : StringLiteralCompletionsFromProperties | undefined {
455471 return type && { kind : StringLiteralCompletionKind . Properties , symbols : type . getApparentProperties ( ) , hasIndexSignature : hasIndexSignature ( type ) } ;
456472 }
@@ -1051,13 +1067,34 @@ namespace ts.Completions {
10511067 }
10521068 else {
10531069 for ( const symbol of type . getApparentProperties ( ) ) {
1054- if ( typeChecker . isValidPropertyAccessForCompletions ( node . kind === SyntaxKind . ImportTypeNode ? < ImportTypeNode > node : < PropertyAccessExpression > ( node . parent ) , type , symbol ) ) {
1055- symbols . push ( symbol ) ;
1070+ if ( typeChecker . isValidPropertyAccessForCompletions ( node . kind === SyntaxKind . ImportTypeNode ? < ImportTypeNode > node : < PropertyAccessExpression > ( node . parent , type , symbol ) ) {
1071+ addPropertySymbol ( symbol ) ;
10561072 }
10571073 }
10581074 }
10591075 }
10601076
1077+ function addPropertySymbol ( symbol : Symbol ) {
1078+ // If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
1079+ const symbolSymbol = firstDefined ( symbol . declarations , decl => {
1080+ const name = getNameOfDeclaration ( decl ) ;
1081+ const leftName = name . kind === SyntaxKind . ComputedPropertyName ? getLeftMostName ( name . expression ) : undefined ;
1082+ return leftName && typeChecker . getSymbolAtLocation ( leftName ) ;
1083+ } ) ;
1084+ if ( symbolSymbol ) {
1085+ symbols . push ( symbolSymbol ) ;
1086+ symbolToOriginInfoMap [ getSymbolId ( symbolSymbol ) ] = { type : "symbol-member" } ;
1087+ }
1088+ else {
1089+ symbols . push ( symbol ) ;
1090+ }
1091+ }
1092+
1093+ /** Given 'a.b.c', returns 'a'. */
1094+ function getLeftMostName ( e : Expression ) : Identifier | undefined {
1095+ return isIdentifier ( e ) ? e : isPropertyAccessExpression ( e ) ? getLeftMostName ( e . expression ) : undefined ;
1096+ }
1097+
10611098 function tryGetGlobalSymbols ( ) : boolean {
10621099 const result : GlobalsSearch = tryGetObjectLikeCompletionSymbols ( )
10631100 || tryGetImportOrExportClauseCompletionSymbols ( )
@@ -2049,7 +2086,7 @@ namespace ts.Completions {
20492086 // TODO: GH#18169
20502087 return { name : JSON . stringify ( name ) , needsConvertPropertyAccess : false } ;
20512088 case CompletionKind . PropertyAccess :
2052- case CompletionKind . Global :
2089+ case CompletionKind . Global : // For a 'this.' completion it will be in a global context, but may have a non-identifier name.
20532090 // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547
20542091 return name . charCodeAt ( 0 ) === CharacterCodes . space ? undefined : { name, needsConvertPropertyAccess : true } ;
20552092 case CompletionKind . None :
0 commit comments