@@ -11,6 +11,22 @@ namespace ts.Completions {
11
11
}
12
12
export type Log = ( message : string ) => void ;
13
13
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
+
14
30
const enum SymbolOriginInfoKind {
15
31
ThisType = 1 << 0 ,
16
32
SymbolMember = 1 << 1 ,
@@ -52,6 +68,11 @@ namespace ts.Completions {
52
68
return ! ! ( origin . kind & SymbolOriginInfoKind . Nullable ) ;
53
69
}
54
70
71
+ interface UniqueNameSet {
72
+ add ( name : string ) : void ;
73
+ has ( name : string ) : boolean ;
74
+ }
75
+
55
76
/**
56
77
* Map from symbol id -> SymbolOriginInfo.
57
78
* Only populated for symbols that come from other modules.
@@ -298,7 +319,7 @@ namespace ts.Completions {
298
319
function getJSCompletionEntries (
299
320
sourceFile : SourceFile ,
300
321
position : number ,
301
- uniqueNames : Map < true > ,
322
+ uniqueNames : UniqueNameSet ,
302
323
target : ScriptTarget ,
303
324
entries : Push < CompletionEntry > ) : void {
304
325
getNameTable ( sourceFile ) . forEach ( ( pos , name ) => {
@@ -307,7 +328,8 @@ namespace ts.Completions {
307
328
return ;
308
329
}
309
330
const realName = unescapeLeadingUnderscores ( name ) ;
310
- if ( addToSeen ( uniqueNames , realName ) && isIdentifierText ( realName , target ) ) {
331
+ if ( ! uniqueNames . has ( realName ) && isIdentifierText ( realName , target ) ) {
332
+ uniqueNames . add ( realName ) ;
311
333
entries . push ( {
312
334
name : realName ,
313
335
kind : ScriptElementKind . warning ,
@@ -429,7 +451,12 @@ namespace ts.Completions {
429
451
}
430
452
431
453
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
+ }
433
460
}
434
461
435
462
export function getCompletionEntriesFromSymbols (
@@ -448,21 +475,21 @@ namespace ts.Completions {
448
475
recommendedCompletion ?: Symbol ,
449
476
symbolToOriginInfoMap ?: SymbolOriginInfoMap ,
450
477
symbolToSortTextMap ?: SymbolSortTextMap ,
451
- ) : Map < true > {
478
+ ) : UniqueNameSet {
452
479
const start = timestamp ( ) ;
453
480
// 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.
456
483
// 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 > ( ) ;
458
485
for ( const symbol of symbols ) {
459
486
const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap [ getSymbolId ( symbol ) ] : undefined ;
460
487
const info = getCompletionEntryDisplayNameForSymbol ( symbol , target , origin , kind , ! ! jsxIdentifierExpected ) ;
461
488
if ( ! info ) {
462
489
continue ;
463
490
}
464
491
const { name, needsConvertPropertyAccess } = info ;
465
- if ( uniques . has ( name ) ) {
492
+ if ( uniques . get ( name ) ) {
466
493
continue ;
467
494
}
468
495
@@ -484,16 +511,22 @@ namespace ts.Completions {
484
511
continue ;
485
512
}
486
513
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 ) ;
491
517
492
518
entries . push ( entry ) ;
493
519
}
494
520
495
521
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
+ } ;
497
530
}
498
531
499
532
function getLabelCompletionAtPosition ( node : BreakOrContinueStatement ) : CompletionInfo | undefined {
@@ -1359,7 +1392,7 @@ namespace ts.Completions {
1359
1392
// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
1360
1393
if ( preferences . includeCompletionsWithInsertText && scopeNode . kind !== SyntaxKind . SourceFile ) {
1361
1394
const thisType = typeChecker . tryGetThisTypeAt ( scopeNode , /*includeGlobalThis*/ false ) ;
1362
- if ( thisType ) {
1395
+ if ( thisType && ! isProbablyGlobalType ( thisType , sourceFile , typeChecker ) ) {
1363
1396
for ( const symbol of getPropertiesForCompletion ( thisType , typeChecker ) ) {
1364
1397
symbolToOriginInfoMap [ getSymbolId ( symbol ) ] = { kind : SymbolOriginInfoKind . ThisType } ;
1365
1398
symbols . push ( symbol ) ;
@@ -2723,13 +2756,22 @@ namespace ts.Completions {
2723
2756
}
2724
2757
}
2725
2758
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 ;
2731
2774
}
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 ;
2734
2776
}
2735
2777
}
0 commit comments