@@ -11,6 +11,22 @@ namespace ts.Completions {
1111 }
1212 export type Log = ( message : string ) => void ;
1313
14+ /**
15+ * Special values for `CompletionInfo['source']` used to disambiguate
16+ * completion items with the same `name`. (Each completion item must
17+ * have a unique name/source combination, because those two fields
18+ * comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`.
19+ *
20+ * When the completion item is an auto-import suggestion, the source
21+ * is the module specifier of the suggestion. To avoid collisions,
22+ * the values here should not be a module specifier we would ever
23+ * generate for an auto-import.
24+ */
25+ export enum CompletionSource {
26+ /** Completions that require `this.` insertion text */
27+ ThisProperty = "ThisProperty/"
28+ }
29+
1430 const enum SymbolOriginInfoKind {
1531 ThisType = 1 << 0 ,
1632 SymbolMember = 1 << 1 ,
@@ -52,6 +68,11 @@ namespace ts.Completions {
5268 return ! ! ( origin . kind & SymbolOriginInfoKind . Nullable ) ;
5369 }
5470
71+ interface UniqueNameSet {
72+ add ( name : string ) : void ;
73+ has ( name : string ) : boolean ;
74+ }
75+
5576 /**
5677 * Map from symbol id -> SymbolOriginInfo.
5778 * Only populated for symbols that come from other modules.
@@ -298,7 +319,7 @@ namespace ts.Completions {
298319 function getJSCompletionEntries (
299320 sourceFile : SourceFile ,
300321 position : number ,
301- uniqueNames : Map < true > ,
322+ uniqueNames : UniqueNameSet ,
302323 target : ScriptTarget ,
303324 entries : Push < CompletionEntry > ) : void {
304325 getNameTable ( sourceFile ) . forEach ( ( pos , name ) => {
@@ -307,7 +328,8 @@ namespace ts.Completions {
307328 return ;
308329 }
309330 const realName = unescapeLeadingUnderscores ( name ) ;
310- if ( addToSeen ( uniqueNames , realName ) && isIdentifierText ( realName , target ) ) {
331+ if ( ! uniqueNames . has ( realName ) && isIdentifierText ( realName , target ) ) {
332+ uniqueNames . add ( realName ) ;
311333 entries . push ( {
312334 name : realName ,
313335 kind : ScriptElementKind . warning ,
@@ -429,7 +451,12 @@ namespace ts.Completions {
429451 }
430452
431453 function getSourceFromOrigin ( origin : SymbolOriginInfo | undefined ) : string | undefined {
432- return origin && originIsExport ( origin ) ? stripQuotes ( origin . moduleSymbol . name ) : undefined ;
454+ if ( originIsExport ( origin ) ) {
455+ return stripQuotes ( origin . moduleSymbol . name ) ;
456+ }
457+ if ( origin ?. kind === SymbolOriginInfoKind . ThisType ) {
458+ return CompletionSource . ThisProperty ;
459+ }
433460 }
434461
435462 export function getCompletionEntriesFromSymbols (
@@ -448,21 +475,21 @@ namespace ts.Completions {
448475 recommendedCompletion ?: Symbol ,
449476 symbolToOriginInfoMap ?: SymbolOriginInfoMap ,
450477 symbolToSortTextMap ?: SymbolSortTextMap ,
451- ) : Map < true > {
478+ ) : UniqueNameSet {
452479 const start = timestamp ( ) ;
453480 // Tracks unique names.
454- // We don't set this for global variables or completions from external module exports, because we can have multiple of those.
455- // Based on the order we add things we will always see locals first, then globals, then module exports.
481+ // Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
482+ // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
456483 // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name.
457- const uniques = createMap < true > ( ) ;
484+ const uniques = createMap < boolean > ( ) ;
458485 for ( const symbol of symbols ) {
459486 const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap [ getSymbolId ( symbol ) ] : undefined ;
460487 const info = getCompletionEntryDisplayNameForSymbol ( symbol , target , origin , kind , ! ! jsxIdentifierExpected ) ;
461488 if ( ! info ) {
462489 continue ;
463490 }
464491 const { name, needsConvertPropertyAccess } = info ;
465- if ( uniques . has ( name ) ) {
492+ if ( uniques . get ( name ) ) {
466493 continue ;
467494 }
468495
@@ -484,16 +511,22 @@ namespace ts.Completions {
484511 continue ;
485512 }
486513
487- // Latter case tests whether this is a global variable.
488- if ( ! origin && ! ( symbol . parent === undefined && ! some ( symbol . declarations , d => d . getSourceFile ( ) === location ! . getSourceFile ( ) ) ) ) { // TODO: GH#18217
489- uniques . set ( name , true ) ;
490- }
514+ /** True for locals; false for globals, module exports from other files, `this.` completions. */
515+ const shouldShadowLaterSymbols = ! origin && ! ( symbol . parent === undefined && ! some ( symbol . declarations , d => d . getSourceFile ( ) === location ! . getSourceFile ( ) ) ) ;
516+ uniques . set ( name , shouldShadowLaterSymbols ) ;
491517
492518 entries . push ( entry ) ;
493519 }
494520
495521 log ( "getCompletionsAtPosition: getCompletionEntriesFromSymbols: " + ( timestamp ( ) - start ) ) ;
496- return uniques ;
522+
523+ // Prevent consumers of this map from having to worry about
524+ // the boolean value. Externally, it should be seen as the
525+ // set of all names.
526+ return {
527+ has : name => uniques . has ( name ) ,
528+ add : name => uniques . set ( name , true ) ,
529+ } ;
497530 }
498531
499532 function getLabelCompletionAtPosition ( node : BreakOrContinueStatement ) : CompletionInfo | undefined {
@@ -1359,7 +1392,7 @@ namespace ts.Completions {
13591392 // Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
13601393 if ( preferences . includeCompletionsWithInsertText && scopeNode . kind !== SyntaxKind . SourceFile ) {
13611394 const thisType = typeChecker . tryGetThisTypeAt ( scopeNode , /*includeGlobalThis*/ false ) ;
1362- if ( thisType ) {
1395+ if ( thisType && ! isProbablyGlobalType ( thisType , sourceFile , typeChecker ) ) {
13631396 for ( const symbol of getPropertiesForCompletion ( thisType , typeChecker ) ) {
13641397 symbolToOriginInfoMap [ getSymbolId ( symbol ) ] = { kind : SymbolOriginInfoKind . ThisType } ;
13651398 symbols . push ( symbol ) ;
@@ -2723,13 +2756,22 @@ namespace ts.Completions {
27232756 }
27242757 }
27252758
2726- function isNonGlobalDeclaration ( declaration : Declaration ) {
2727- const sourceFile = declaration . getSourceFile ( ) ;
2728- // If the file is not a module, the declaration is global
2729- if ( ! sourceFile . externalModuleIndicator && ! sourceFile . commonJsModuleIndicator ) {
2730- return false ;
2759+ /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */
2760+ function isProbablyGlobalType ( type : Type , sourceFile : SourceFile , checker : TypeChecker ) {
2761+ // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in
2762+ // lib.webworker.d.ts, so checking against `self` is also a check against `window` when it exists.
2763+ const selfSymbol = checker . resolveName ( "self" , /*location*/ undefined , SymbolFlags . Value , /*excludeGlobals*/ false ) ;
2764+ if ( selfSymbol && checker . getTypeOfSymbolAtLocation ( selfSymbol , sourceFile ) === type ) {
2765+ return true ;
2766+ }
2767+ const globalSymbol = checker . resolveName ( "global" , /*location*/ undefined , SymbolFlags . Value , /*excludeGlobals*/ false ) ;
2768+ if ( globalSymbol && checker . getTypeOfSymbolAtLocation ( globalSymbol , sourceFile ) === type ) {
2769+ return true ;
2770+ }
2771+ const globalThisSymbol = checker . resolveName ( "globalThis" , /*location*/ undefined , SymbolFlags . Value , /*excludeGlobals*/ false ) ;
2772+ if ( globalThisSymbol && checker . getTypeOfSymbolAtLocation ( globalThisSymbol , sourceFile ) === type ) {
2773+ return true ;
27312774 }
2732- // If the file is a module written in TypeScript, it still might be in a `declare global` augmentation
2733- return isInJSFile ( declaration ) || ! findAncestor ( declaration , isGlobalScopeAugmentation ) ;
2775+ return false ;
27342776 }
27352777}
0 commit comments