@@ -1645,7 +1645,7 @@ namespace ts.Completions {
16451645 }
16461646
16471647 /** True if symbol is a type or a module containing at least one type. */
1648- function symbolCanBeReferencedAtTypeLocation ( symbol : Symbol , seenModules = new Map < string , true > ( ) ) : boolean {
1648+ function symbolCanBeReferencedAtTypeLocation ( symbol : Symbol , seenModules = new Map < SymbolId , true > ( ) ) : boolean {
16491649 const sym = skipAlias ( symbol . exportSymbol || symbol , typeChecker ) ;
16501650 return ! ! ( sym . flags & SymbolFlags . Type ) ||
16511651 ! ! ( sym . flags & SymbolFlags . Module ) &&
@@ -1655,50 +1655,7 @@ namespace ts.Completions {
16551655
16561656 /**
16571657 * Gathers symbols that can be imported from other files, de-duplicating along the way. Symbols can be "duplicates"
1658- * if re-exported from another module, e.g. `export { foo } from "./a"`. That syntax creates a fresh symbol, but
1659- * it’s just an alias to the first, and both have the same name, so we generally want to filter those aliases out,
1660- * if and only if the the first can be imported (it may be excluded due to package.json filtering in
1661- * `codefix.forEachExternalModuleToImportFrom`).
1662- *
1663- * Example. Imagine a chain of node_modules re-exporting one original symbol:
1664- *
1665- * ```js
1666- * node_modules/x/index.js node_modules/y/index.js node_modules/z/index.js
1667- * +-----------------------+ +--------------------------+ +--------------------------+
1668- * | | | | | |
1669- * | export const foo = 0; | <--- | export { foo } from 'x'; | <--- | export { foo } from 'y'; |
1670- * | | | | | |
1671- * +-----------------------+ +--------------------------+ +--------------------------+
1672- * ```
1673- *
1674- * Also imagine three buckets, which we’ll reference soon:
1675- *
1676- * ```md
1677- * | | | | | |
1678- * | **Bucket A** | | **Bucket B** | | **Bucket C** |
1679- * | Symbols to | | Aliases to symbols | | Symbols to return |
1680- * | definitely | | in Buckets A or C | | if nothing better |
1681- * | return | | (don’t return these) | | comes along |
1682- * |__________________| |______________________| |___________________|
1683- * ```
1684- *
1685- * We _probably_ want to show `foo` from 'x', but not from 'y' or 'z'. However, if 'x' is not in a package.json, it
1686- * will not appear in a `forEachExternalModuleToImportFrom` iteration. Furthermore, the order of iterations is not
1687- * guaranteed, as it is host-dependent. Therefore, when presented with the symbol `foo` from module 'y' alone, we
1688- * may not be sure whether or not it should go in the list. So, we’ll take the following steps:
1689- *
1690- * 1. Resolve alias `foo` from 'y' to the export declaration in 'x', get the symbol there, and see if that symbol is
1691- * already in Bucket A (symbols we already know will be returned). If it is, put `foo` from 'y' in Bucket B
1692- * (symbols that are aliases to symbols in Bucket A). If it’s not, put it in Bucket C.
1693- * 2. Next, imagine we see `foo` from module 'z'. Again, we resolve the alias to the nearest export, which is in 'y'.
1694- * At this point, if that nearest export from 'y' is in _any_ of the three buckets, we know the symbol in 'z'
1695- * should never be returned in the final list, so put it in Bucket B.
1696- * 3. Next, imagine we see `foo` from module 'x', the original. Syntactically, it doesn’t look like a re-export, so
1697- * we can just check Bucket C to see if we put any aliases to the original in there. If they exist, throw them out.
1698- * Put this symbol in Bucket A.
1699- * 4. After we’ve iterated through every symbol of every module, any symbol left in Bucket C means that step 3 didn’t
1700- * occur for that symbol---that is, the original symbol is not in Bucket A, so we should include the alias. Move
1701- * everything from Bucket C to Bucket A.
1658+ * if re-exported from another module by the same name, e.g. `export { foo } from "./a"`.
17021659 */
17031660 function getSymbolsFromOtherSourceFileExports ( target : ScriptTarget , host : LanguageServiceHost ) : readonly AutoImportSuggestion [ ] {
17041661 const cached = importSuggestionsCache && importSuggestionsCache . get (
@@ -1713,16 +1670,8 @@ namespace ts.Completions {
17131670
17141671 const startTime = timestamp ( ) ;
17151672 log ( `getSymbolsFromOtherSourceFileExports: Recomputing list${ detailsEntryId ? " for details entry" : "" } ` ) ;
1716- const seenResolvedModules = new Map < string , true > ( ) ;
1717- const seenExports = new Map < string , true > ( ) ;
1718- /** Bucket B */
1719- const aliasesToAlreadyIncludedSymbols = new Map < string , true > ( ) ;
1720- /** Bucket C */
1721- const aliasesToReturnIfOriginalsAreMissing = new Map < string , { alias : Symbol , moduleSymbol : Symbol , isFromPackageJson : boolean } > ( ) ;
1722- /** Bucket A */
1723- const results : AutoImportSuggestion [ ] = [ ] ;
1724- /** Ids present in `results` for faster lookup */
1725- const resultSymbolIds = new Map < string , true > ( ) ;
1673+ const seenResolvedModules = new Map < SymbolId , true > ( ) ;
1674+ const results = createMultiMap < SymbolId , AutoImportSuggestion > ( ) ;
17261675
17271676 codefix . forEachExternalModuleToImportFrom ( program , host , sourceFile , ! detailsEntryId , /*useAutoImportProvider*/ true , ( moduleSymbol , _ , program , isFromPackageJson ) => {
17281677 // Perf -- ignore other modules if this is a request for details
@@ -1744,73 +1693,52 @@ namespace ts.Completions {
17441693 }
17451694
17461695 for ( const symbol of typeChecker . getExportsAndPropertiesOfModule ( moduleSymbol ) ) {
1747- const symbolId = getSymbolId ( symbol ) . toString ( ) ;
1748- // `getExportsAndPropertiesOfModule` can include duplicates
1749- if ( ! addToSeen ( seenExports , symbolId ) ) {
1750- continue ;
1751- }
17521696 // If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion.
17531697 if ( some ( symbol . declarations , d => isExportSpecifier ( d ) && ! ! d . propertyName && isIdentifierANonContextualKeyword ( d . name ) ) ) {
17541698 continue ;
17551699 }
17561700
1757- // If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols.
1758- const isExportStarFromReExport = typeChecker . getMergedSymbol ( symbol . parent ! ) !== resolvedModuleSymbol ;
1759- // If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
1760- if ( isExportStarFromReExport || some ( symbol . declarations , d => isExportSpecifier ( d ) && ! d . propertyName && ! ! d . parent . parent . moduleSpecifier ) ) {
1761- // Walk the export chain back one module (step 1 or 2 in diagrammed example).
1762- // Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that.
1763- const nearestExportSymbol = isExportStarFromReExport ? symbol : getNearestExportSymbol ( symbol ) ;
1764- if ( ! nearestExportSymbol ) continue ;
1765- const nearestExportSymbolId = getSymbolId ( nearestExportSymbol ) . toString ( ) ;
1766- const symbolHasBeenSeen = resultSymbolIds . has ( nearestExportSymbolId ) || aliasesToAlreadyIncludedSymbols . has ( nearestExportSymbolId ) ;
1767- if ( ! symbolHasBeenSeen ) {
1768- aliasesToReturnIfOriginalsAreMissing . set ( nearestExportSymbolId , { alias : symbol , moduleSymbol, isFromPackageJson } ) ;
1769- aliasesToAlreadyIncludedSymbols . set ( symbolId , true ) ;
1770- }
1771- else {
1772- // Perf - we know this symbol is an alias to one that’s already covered in `symbols`, so store it here
1773- // in case another symbol re-exports this one; that way we can short-circuit as soon as we see this symbol id.
1774- addToSeen ( aliasesToAlreadyIncludedSymbols , symbolId ) ;
1775- }
1776- }
1777- else {
1778- // This is not a re-export, so see if we have any aliases pending and remove them (step 3 in diagrammed example)
1779- aliasesToReturnIfOriginalsAreMissing . delete ( symbolId ) ;
1780- pushSymbol ( symbol , moduleSymbol , isFromPackageJson , /*skipFilter*/ false ) ;
1781- }
1701+ pushSymbol ( symbol , moduleSymbol , isFromPackageJson , /*skipFilter*/ false ) ;
17821702 }
17831703 } ) ;
17841704
1785- // By this point, any potential duplicates that were actually duplicates have been
1786- // removed, so the rest need to be added. (Step 4 in diagrammed example)
1787- aliasesToReturnIfOriginalsAreMissing . forEach ( ( { alias, moduleSymbol, isFromPackageJson } ) => pushSymbol ( alias , moduleSymbol , isFromPackageJson , /*skipFilter*/ false ) ) ;
17881705 log ( `getSymbolsFromOtherSourceFileExports: ${ timestamp ( ) - startTime } ` ) ;
1789- return results ;
1706+ return flatten ( arrayFrom ( results . values ( ) ) ) ;
17901707
17911708 function pushSymbol ( symbol : Symbol , moduleSymbol : Symbol , isFromPackageJson : boolean , skipFilter : boolean ) {
17921709 const isDefaultExport = symbol . escapedName === InternalSymbolName . Default ;
1710+ const nonLocalSymbol = symbol ;
17931711 if ( isDefaultExport ) {
17941712 symbol = getLocalSymbolForExportDefault ( symbol ) || symbol ;
17951713 }
17961714 if ( typeChecker . isUndefinedSymbol ( symbol ) ) {
17971715 return ;
17981716 }
1799- addToSeen ( resultSymbolIds , getSymbolId ( symbol ) ) ;
1800- const origin : SymbolOriginInfoExport = { kind : SymbolOriginInfoKind . Export , moduleSymbol, isDefaultExport, isFromPackageJson } ;
1801- results . push ( {
1802- symbol,
1803- symbolName : getNameForExportedSymbol ( symbol , target ) ,
1804- origin,
1805- skipFilter,
1806- } ) ;
1717+ const original = skipAlias ( nonLocalSymbol , typeChecker ) ;
1718+ const symbolName = getNameForExportedSymbol ( symbol , target ) ;
1719+ const existingSuggestions = results . get ( getSymbolId ( original ) ) ;
1720+ if ( ! some ( existingSuggestions , s => s . symbolName === symbolName && moduleSymbolsAreDuplicateOrigins ( moduleSymbol , s . origin . moduleSymbol ) ) ) {
1721+ const origin : SymbolOriginInfoExport = { kind : SymbolOriginInfoKind . Export , moduleSymbol, isDefaultExport, isFromPackageJson } ;
1722+ results . add ( getSymbolId ( original ) , {
1723+ symbol,
1724+ symbolName,
1725+ origin,
1726+ skipFilter,
1727+ } ) ;
1728+ }
18071729 }
18081730 }
18091731
1810- function getNearestExportSymbol ( fromSymbol : Symbol ) {
1811- return findAlias ( typeChecker , fromSymbol , alias => {
1812- return some ( alias . declarations , d => isExportSpecifier ( d ) || ! ! d . localSymbol ) ;
1813- } ) ;
1732+ /**
1733+ * Determines whether a module symbol is redundant with another for purposes of offering
1734+ * auto-import completions for exports of the same symbol. Exports of the same symbol
1735+ * will not be offered from different external modules, but they will be offered from
1736+ * different ambient modules.
1737+ */
1738+ function moduleSymbolsAreDuplicateOrigins ( a : Symbol , b : Symbol ) {
1739+ const ambientNameA = pathIsBareSpecifier ( stripQuotes ( a . name ) ) ? a . name : undefined ;
1740+ const ambientNameB = pathIsBareSpecifier ( stripQuotes ( b . name ) ) ? b . name : undefined ;
1741+ return ambientNameA === ambientNameB ;
18141742 }
18151743
18161744 /**
@@ -2897,15 +2825,6 @@ namespace ts.Completions {
28972825 return nodeIsMissing ( left ) ;
28982826 }
28992827
2900- function findAlias ( typeChecker : TypeChecker , symbol : Symbol , predicate : ( symbol : Symbol ) => boolean ) : Symbol | undefined {
2901- let currentAlias : Symbol | undefined = symbol ;
2902- while ( currentAlias . flags & SymbolFlags . Alias && ( currentAlias = typeChecker . getImmediateAliasedSymbol ( currentAlias ) ) ) {
2903- if ( predicate ( currentAlias ) ) {
2904- return currentAlias ;
2905- }
2906- }
2907- }
2908-
29092828 /** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */
29102829 function isProbablyGlobalType ( type : Type , sourceFile : SourceFile , checker : TypeChecker ) {
29112830 // The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in
0 commit comments