@@ -137,8 +137,9 @@ namespace ts.Completions.PathCompletions {
137137 if ( directories ) {
138138 for ( const directory of directories ) {
139139 const directoryName = getBaseFileName ( normalizePath ( directory ) ) ;
140-
141- result . push ( nameAndKind ( directoryName , ScriptElementKind . directory ) ) ;
140+ if ( directoryName !== "@types" ) {
141+ result . push ( nameAndKind ( directoryName , ScriptElementKind . directory ) ) ;
142+ }
142143 }
143144 }
144145 }
@@ -177,19 +178,33 @@ namespace ts.Completions.PathCompletions {
177178 }
178179 }
179180
180- if ( compilerOptions . moduleResolution === ModuleResolutionKind . NodeJs ) {
181- forEachAncestorDirectory ( scriptPath , ancestor => {
182- const nodeModules = combinePaths ( ancestor , "node_modules" ) ;
183- if ( host . directoryExists ( nodeModules ) ) {
184- getCompletionEntriesForDirectoryFragment ( fragment , nodeModules , fileExtensions , /*includeExtensions*/ false , host , /*exclude*/ undefined , result ) ;
185- }
186- } ) ;
181+ const fragmentDirectory = containsSlash ( fragment ) ? getDirectoryPath ( fragment ) : undefined ;
182+ for ( const ambientName of getAmbientModuleCompletions ( fragment , fragmentDirectory , typeChecker ) ) {
183+ result . push ( nameAndKind ( ambientName , ScriptElementKind . externalModuleName ) ) ;
187184 }
188185
189186 getCompletionEntriesFromTypings ( host , compilerOptions , scriptPath , result ) ;
190187
191- for ( const moduleName of enumeratePotentialNonRelativeModules ( fragment , scriptPath , compilerOptions , typeChecker , host ) ) {
192- result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName ) ) ;
188+ if ( getEmitModuleResolutionKind ( compilerOptions ) === ModuleResolutionKind . NodeJs ) {
189+ // If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies.
190+ // (But do if we didn't find anything, e.g. 'package.json' missing.)
191+ let foundGlobal = false ;
192+ if ( fragmentDirectory === undefined ) {
193+ for ( const moduleName of enumerateNodeModulesVisibleToScript ( host , scriptPath ) ) {
194+ if ( ! result . some ( entry => entry . name === moduleName ) ) {
195+ foundGlobal = true ;
196+ result . push ( nameAndKind ( moduleName , ScriptElementKind . externalModuleName ) ) ;
197+ }
198+ }
199+ }
200+ if ( ! foundGlobal ) {
201+ forEachAncestorDirectory ( scriptPath , ancestor => {
202+ const nodeModules = combinePaths ( ancestor , "node_modules" ) ;
203+ if ( tryDirectoryExists ( host , nodeModules ) ) {
204+ getCompletionEntriesForDirectoryFragment ( fragment , nodeModules , fileExtensions , /*includeExtensions*/ false , host , /*exclude*/ undefined , result ) ;
205+ }
206+ } ) ;
207+ }
193208 }
194209
195210 return result ;
@@ -228,7 +243,7 @@ namespace ts.Completions.PathCompletions {
228243 const normalizedPrefixDirectory = getDirectoryPath ( normalizedPrefix ) ;
229244 const normalizedPrefixBase = getBaseFileName ( normalizedPrefix ) ;
230245
231- const fragmentHasPath = stringContains ( fragment , directorySeparator ) ;
246+ const fragmentHasPath = containsSlash ( fragment ) ;
232247
233248 // Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
234249 const expandedPrefixDirectory = fragmentHasPath ? combinePaths ( normalizedPrefixDirectory , normalizedPrefixBase + getDirectoryPath ( fragment ) ) : normalizedPrefixDirectory ;
@@ -262,45 +277,19 @@ namespace ts.Completions.PathCompletions {
262277 return path [ 0 ] === directorySeparator ? path . slice ( 1 ) : path ;
263278 }
264279
265- function enumeratePotentialNonRelativeModules ( fragment : string , scriptPath : string , options : CompilerOptions , typeChecker : TypeChecker , host : LanguageServiceHost ) : string [ ] {
266- // Check If this is a nested module
267- const isNestedModule = stringContains ( fragment , directorySeparator ) ;
268- const moduleNameFragment = isNestedModule ? fragment . substr ( 0 , fragment . lastIndexOf ( directorySeparator ) ) : undefined ;
269-
280+ function getAmbientModuleCompletions ( fragment : string , fragmentDirectory : string | undefined , checker : TypeChecker ) : ReadonlyArray < string > {
270281 // Get modules that the type checker picked up
271- const ambientModules = map ( typeChecker . getAmbientModules ( ) , sym => stripQuotes ( sym . name ) ) ;
272- let nonRelativeModuleNames = filter ( ambientModules , moduleName => startsWith ( moduleName , fragment ) ) ;
282+ const ambientModules = checker . getAmbientModules ( ) . map ( sym => stripQuotes ( sym . name ) ) ;
283+ const nonRelativeModuleNames = ambientModules . filter ( moduleName => startsWith ( moduleName , fragment ) ) ;
273284
274285 // Nested modules of the form "module-name/sub" need to be adjusted to only return the string
275286 // after the last '/' that appears in the fragment because that's where the replacement span
276287 // starts
277- if ( isNestedModule ) {
278- const moduleNameWithSeperator = ensureTrailingDirectorySeparator ( moduleNameFragment ) ;
279- nonRelativeModuleNames = map ( nonRelativeModuleNames , nonRelativeModuleName => {
280- return removePrefix ( nonRelativeModuleName , moduleNameWithSeperator ) ;
281- } ) ;
282- }
283-
284-
285- if ( ! options . moduleResolution || options . moduleResolution === ModuleResolutionKind . NodeJs ) {
286- for ( const visibleModule of enumerateNodeModulesVisibleToScript ( host , scriptPath ) ) {
287- if ( ! isNestedModule ) {
288- nonRelativeModuleNames . push ( visibleModule . moduleName ) ;
289- }
290- else if ( startsWith ( visibleModule . moduleName , moduleNameFragment ) ) {
291- const nestedFiles = tryReadDirectory ( host , visibleModule . moduleDir , supportedTypeScriptExtensions , /*exclude*/ undefined , /*include*/ [ "./*" ] ) ;
292- if ( nestedFiles ) {
293- for ( let f of nestedFiles ) {
294- f = normalizePath ( f ) ;
295- const nestedModule = removeFileExtension ( getBaseFileName ( f ) ) ;
296- nonRelativeModuleNames . push ( nestedModule ) ;
297- }
298- }
299- }
300- }
288+ if ( fragmentDirectory !== undefined ) {
289+ const moduleNameWithSeperator = ensureTrailingDirectorySeparator ( fragmentDirectory ) ;
290+ return nonRelativeModuleNames . map ( nonRelativeModuleName => removePrefix ( nonRelativeModuleName , moduleNameWithSeperator ) ) ;
301291 }
302-
303- return deduplicate ( nonRelativeModuleNames , equateStringsCaseSensitive , compareStringsCaseSensitive ) ;
292+ return nonRelativeModuleNames ;
304293 }
305294
306295 export function getTripleSlashReferenceCompletion ( sourceFile : SourceFile , position : number , compilerOptions : CompilerOptions , host : LanguageServiceHost ) : ReadonlyArray < PathCompletion > | undefined {
@@ -390,55 +379,24 @@ namespace ts.Completions.PathCompletions {
390379 return paths ;
391380 }
392381
393- function enumerateNodeModulesVisibleToScript ( host : LanguageServiceHost , scriptPath : string ) {
394- const result : VisibleModuleInfo [ ] = [ ] ;
395-
396- if ( host . readFile && host . fileExists ) {
397- for ( const packageJson of findPackageJsons ( scriptPath , host ) ) {
398- const contents = tryReadingPackageJson ( packageJson ) ;
399- if ( ! contents ) {
400- return ;
401- }
382+ function enumerateNodeModulesVisibleToScript ( host : LanguageServiceHost , scriptPath : string ) : ReadonlyArray < string > {
383+ if ( ! host . readFile || ! host . fileExists ) return emptyArray ;
402384
403- const nodeModulesDir = combinePaths ( getDirectoryPath ( packageJson ) , "node_modules" ) ;
404- const foundModuleNames : string [ ] = [ ] ;
405-
406- // Provide completions for all non @types dependencies
407- for ( const key of nodeModulesDependencyKeys ) {
408- addPotentialPackageNames ( contents [ key ] , foundModuleNames ) ;
409- }
410-
411- for ( const moduleName of foundModuleNames ) {
412- const moduleDir = combinePaths ( nodeModulesDir , moduleName ) ;
413- result . push ( {
414- moduleName,
415- moduleDir
416- } ) ;
417- }
418- }
419- }
420-
421- return result ;
422-
423- function tryReadingPackageJson ( filePath : string ) {
424- try {
425- const fileText = tryReadFile ( host , filePath ) ;
426- return fileText ? JSON . parse ( fileText ) : undefined ;
427- }
428- catch ( e ) {
429- return undefined ;
430- }
431- }
432-
433- function addPotentialPackageNames ( dependencies : any , result : string [ ] ) {
434- if ( dependencies ) {
385+ const result : string [ ] = [ ] ;
386+ for ( const packageJson of findPackageJsons ( scriptPath , host ) ) {
387+ const contents = readJson ( packageJson , host as { readFile : ( filename : string ) => string | undefined } ) ; // Cast to assert that readFile is defined
388+ // Provide completions for all non @types dependencies
389+ for ( const key of nodeModulesDependencyKeys ) {
390+ const dependencies : object | undefined = ( contents as any ) [ key ] ;
391+ if ( ! dependencies ) continue ;
435392 for ( const dep in dependencies ) {
436393 if ( dependencies . hasOwnProperty ( dep ) && ! startsWith ( dep , "@types/" ) ) {
437394 result . push ( dep ) ;
438395 }
439396 }
440397 }
441398 }
399+ return result ;
442400 }
443401
444402 // Replace everything after the last directory seperator that appears
@@ -484,11 +442,6 @@ namespace ts.Completions.PathCompletions {
484442 */
485443 const tripleSlashDirectiveFragmentRegex = / ^ ( \/ \/ \/ \s * < r e f e r e n c e \s + ( p a t h | t y p e s ) \s * = \s * (?: ' | " ) ) ( [ ^ \3" ] * ) $ / ;
486444
487- interface VisibleModuleInfo {
488- moduleName : string ;
489- moduleDir : string ;
490- }
491-
492445 const nodeModulesDependencyKeys = [ "dependencies" , "devDependencies" , "peerDependencies" , "optionalDependencies" ] ;
493446
494447 function tryGetDirectories ( host : LanguageServiceHost , directoryName : string ) : string [ ] {
@@ -499,10 +452,6 @@ namespace ts.Completions.PathCompletions {
499452 return tryIOAndConsumeErrors ( host , host . readDirectory , path , extensions , exclude , include ) || emptyArray ;
500453 }
501454
502- function tryReadFile ( host : LanguageServiceHost , path : string ) : string | undefined {
503- return tryIOAndConsumeErrors ( host , host . readFile , path ) ;
504- }
505-
506455 function tryFileExists ( host : LanguageServiceHost , path : string ) : boolean {
507456 return tryIOAndConsumeErrors ( host , host . fileExists , path ) ;
508457 }
@@ -522,4 +471,8 @@ namespace ts.Completions.PathCompletions {
522471 catch { /*ignore*/ }
523472 return undefined ;
524473 }
474+
475+ function containsSlash ( fragment : string ) {
476+ return stringContains ( fragment , directorySeparator ) ;
477+ }
525478}
0 commit comments