@@ -1645,7 +1645,7 @@ namespace ts.Completions {
1645
1645
}
1646
1646
1647
1647
/** 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 {
1649
1649
const sym = skipAlias ( symbol . exportSymbol || symbol , typeChecker ) ;
1650
1650
return ! ! ( sym . flags & SymbolFlags . Type ) ||
1651
1651
! ! ( sym . flags & SymbolFlags . Module ) &&
@@ -1655,50 +1655,7 @@ namespace ts.Completions {
1655
1655
1656
1656
/**
1657
1657
* 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"`.
1702
1659
*/
1703
1660
function getSymbolsFromOtherSourceFileExports ( target : ScriptTarget , host : LanguageServiceHost ) : readonly AutoImportSuggestion [ ] {
1704
1661
const cached = importSuggestionsCache && importSuggestionsCache . get (
@@ -1713,16 +1670,8 @@ namespace ts.Completions {
1713
1670
1714
1671
const startTime = timestamp ( ) ;
1715
1672
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 > ( ) ;
1726
1675
1727
1676
codefix . forEachExternalModuleToImportFrom ( program , host , sourceFile , ! detailsEntryId , /*useAutoImportProvider*/ true , ( moduleSymbol , _ , program , isFromPackageJson ) => {
1728
1677
// Perf -- ignore other modules if this is a request for details
@@ -1744,73 +1693,52 @@ namespace ts.Completions {
1744
1693
}
1745
1694
1746
1695
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
- }
1752
1696
// If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion.
1753
1697
if ( some ( symbol . declarations , d => isExportSpecifier ( d ) && ! ! d . propertyName && isIdentifierANonContextualKeyword ( d . name ) ) ) {
1754
1698
continue ;
1755
1699
}
1756
1700
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 ) ;
1782
1702
}
1783
1703
} ) ;
1784
1704
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 ) ) ;
1788
1705
log ( `getSymbolsFromOtherSourceFileExports: ${ timestamp ( ) - startTime } ` ) ;
1789
- return results ;
1706
+ return flatten ( arrayFrom ( results . values ( ) ) ) ;
1790
1707
1791
1708
function pushSymbol ( symbol : Symbol , moduleSymbol : Symbol , isFromPackageJson : boolean , skipFilter : boolean ) {
1792
1709
const isDefaultExport = symbol . escapedName === InternalSymbolName . Default ;
1710
+ const nonLocalSymbol = symbol ;
1793
1711
if ( isDefaultExport ) {
1794
1712
symbol = getLocalSymbolForExportDefault ( symbol ) || symbol ;
1795
1713
}
1796
1714
if ( typeChecker . isUndefinedSymbol ( symbol ) ) {
1797
1715
return ;
1798
1716
}
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
+ }
1807
1729
}
1808
1730
}
1809
1731
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 ;
1814
1742
}
1815
1743
1816
1744
/**
@@ -2897,15 +2825,6 @@ namespace ts.Completions {
2897
2825
return nodeIsMissing ( left ) ;
2898
2826
}
2899
2827
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
-
2909
2828
/** Determines if a type is exactly the same type resolved by the global 'self', 'global', or 'globalThis'. */
2910
2829
function isProbablyGlobalType ( type : Type , sourceFile : SourceFile , checker : TypeChecker ) {
2911
2830
// The type of `self` and `window` is the same in lib.dom.d.ts, but `window` does not exist in
0 commit comments