@@ -368,9 +368,20 @@ namespace ts.Completions.StringCompletions {
368
368
}
369
369
}
370
370
371
+ function isEmitResolutionKindUsingNodeModules ( compilerOptions : CompilerOptions ) : boolean {
372
+ return getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeJs ||
373
+ getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . Node12 ||
374
+ getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeNext ;
375
+ }
376
+
377
+ function isEmitModuleResolutionRespectingExportMaps ( compilerOptions : CompilerOptions ) {
378
+ return getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . Node12 ||
379
+ getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeNext ;
380
+ }
381
+
371
382
function getSupportedExtensionsForModuleResolution ( compilerOptions : CompilerOptions ) : readonly Extension [ ] [ ] {
372
383
const extensions = getSupportedExtensions ( compilerOptions ) ;
373
- return getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeJs ?
384
+ return isEmitResolutionKindUsingNodeModules ( compilerOptions ) ?
374
385
getSupportedExtensionsWithJsonIfResolveJsonModule ( compilerOptions , extensions ) :
375
386
extensions ;
376
387
}
@@ -549,7 +560,7 @@ namespace ts.Completions.StringCompletions {
549
560
550
561
getCompletionEntriesFromTypings ( host , compilerOptions , scriptPath , fragmentDirectory , extensionOptions , result ) ;
551
562
552
- if ( getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeJs ) {
563
+ if ( isEmitResolutionKindUsingNodeModules ( compilerOptions ) ) {
553
564
// If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies.
554
565
// (But do if we didn't find anything, e.g. 'package.json' missing.)
555
566
let foundGlobal = false ;
@@ -562,12 +573,65 @@ namespace ts.Completions.StringCompletions {
562
573
}
563
574
}
564
575
if ( ! foundGlobal ) {
565
- forEachAncestorDirectory ( scriptPath , ancestor => {
576
+ let ancestorLookup : ( directory : string ) => void | undefined = ancestor => {
566
577
const nodeModules = combinePaths ( ancestor , "node_modules" ) ;
567
578
if ( tryDirectoryExists ( host , nodeModules ) ) {
568
579
getCompletionEntriesForDirectoryFragment ( fragment , nodeModules , extensionOptions , host , /*exclude*/ undefined , result ) ;
569
580
}
570
- } ) ;
581
+ } ;
582
+ if ( fragmentDirectory && isEmitModuleResolutionRespectingExportMaps ( compilerOptions ) ) {
583
+ const nodeModulesDirectoryLookup = ancestorLookup ;
584
+ ancestorLookup = ancestor => {
585
+ const components = getPathComponents ( fragment ) ;
586
+ components . shift ( ) ; // shift off empty root
587
+ let packagePath = components . shift ( ) ;
588
+ if ( ! packagePath ) {
589
+ return nodeModulesDirectoryLookup ( ancestor ) ;
590
+ }
591
+ if ( startsWith ( packagePath , "@" ) ) {
592
+ const subName = components . shift ( ) ;
593
+ if ( ! subName ) {
594
+ return nodeModulesDirectoryLookup ( ancestor ) ;
595
+ }
596
+ packagePath = combinePaths ( packagePath , subName ) ;
597
+ }
598
+ const packageFile = combinePaths ( ancestor , "node_modules" , packagePath , "package.json" ) ;
599
+ if ( tryFileExists ( host , packageFile ) ) {
600
+ const packageJson = readJson ( packageFile , host as { readFile : ( filename : string ) => string | undefined } ) ;
601
+ const exports = ( packageJson as any ) . exports ;
602
+ if ( exports ) {
603
+ if ( typeof exports !== "object" || exports === null ) { // eslint-disable-line no-null/no-null
604
+ return ; // null exports or entrypoint only, no sub-modules available
605
+ }
606
+ const keys = getOwnKeys ( exports ) ;
607
+ const fragmentSubpath = components . join ( "/" ) ;
608
+ const processedKeys = mapDefined ( keys , k => {
609
+ if ( k === "." ) return undefined ;
610
+ if ( ! startsWith ( k , "./" ) ) return undefined ;
611
+ const subpath = k . substring ( 2 ) ;
612
+ if ( ! startsWith ( subpath , fragmentSubpath ) ) return undefined ;
613
+ // subpath is a valid export (barring conditions, which we don't currently check here)
614
+ if ( ! stringContains ( subpath , "*" ) ) {
615
+ return subpath ;
616
+ }
617
+ // pattern export - only return everything up to the `*`, so the user can autocomplete, then
618
+ // keep filling in the pattern (we could speculatively return a list of options by hitting disk,
619
+ // but conditions will make that somewhat awkward, as each condition may have a different set of possible
620
+ // options for the `*`.
621
+ return subpath . slice ( 0 , subpath . indexOf ( "*" ) ) ;
622
+ } ) ;
623
+ forEach ( processedKeys , k => {
624
+ if ( k ) {
625
+ result . push ( nameAndKind ( k , ScriptElementKind . externalModuleName , /*extension*/ undefined ) ) ;
626
+ }
627
+ } ) ;
628
+ return ;
629
+ }
630
+ }
631
+ return nodeModulesDirectoryLookup ( ancestor ) ;
632
+ } ;
633
+ }
634
+ forEachAncestorDirectory ( scriptPath , ancestorLookup ) ;
571
635
}
572
636
}
573
637
0 commit comments