@@ -137,8 +137,9 @@ namespace ts.Completions.PathCompletions {
137
137
if ( directories ) {
138
138
for ( const directory of directories ) {
139
139
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
+ }
142
143
}
143
144
}
144
145
}
@@ -177,19 +178,33 @@ namespace ts.Completions.PathCompletions {
177
178
}
178
179
}
179
180
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 ) ) ;
187
184
}
188
185
189
186
getCompletionEntriesFromTypings ( host , compilerOptions , scriptPath , result ) ;
190
187
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
+ }
193
208
}
194
209
195
210
return result ;
@@ -228,7 +243,7 @@ namespace ts.Completions.PathCompletions {
228
243
const normalizedPrefixDirectory = getDirectoryPath ( normalizedPrefix ) ;
229
244
const normalizedPrefixBase = getBaseFileName ( normalizedPrefix ) ;
230
245
231
- const fragmentHasPath = stringContains ( fragment , directorySeparator ) ;
246
+ const fragmentHasPath = containsSlash ( fragment ) ;
232
247
233
248
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
234
249
const expandedPrefixDirectory = fragmentHasPath ? combinePaths ( normalizedPrefixDirectory , normalizedPrefixBase + getDirectoryPath ( fragment ) ) : normalizedPrefixDirectory ;
@@ -262,45 +277,19 @@ namespace ts.Completions.PathCompletions {
262
277
return path [ 0 ] === directorySeparator ? path . slice ( 1 ) : path ;
263
278
}
264
279
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 > {
270
281
// 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 ) ) ;
273
284
274
285
// Nested modules of the form "module-name/sub" need to be adjusted to only return the string
275
286
// after the last '/' that appears in the fragment because that's where the replacement span
276
287
// 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 ) ) ;
301
291
}
302
-
303
- return deduplicate ( nonRelativeModuleNames , equateStringsCaseSensitive , compareStringsCaseSensitive ) ;
292
+ return nonRelativeModuleNames ;
304
293
}
305
294
306
295
export function getTripleSlashReferenceCompletion ( sourceFile : SourceFile , position : number , compilerOptions : CompilerOptions , host : LanguageServiceHost ) : ReadonlyArray < PathCompletion > | undefined {
@@ -390,55 +379,24 @@ namespace ts.Completions.PathCompletions {
390
379
return paths ;
391
380
}
392
381
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 ;
402
384
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 ;
435
392
for ( const dep in dependencies ) {
436
393
if ( dependencies . hasOwnProperty ( dep ) && ! startsWith ( dep , "@types/" ) ) {
437
394
result . push ( dep ) ;
438
395
}
439
396
}
440
397
}
441
398
}
399
+ return result ;
442
400
}
443
401
444
402
// Replace everything after the last directory seperator that appears
@@ -484,11 +442,6 @@ namespace ts.Completions.PathCompletions {
484
442
*/
485
443
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" ] * ) $ / ;
486
444
487
- interface VisibleModuleInfo {
488
- moduleName : string ;
489
- moduleDir : string ;
490
- }
491
-
492
445
const nodeModulesDependencyKeys = [ "dependencies" , "devDependencies" , "peerDependencies" , "optionalDependencies" ] ;
493
446
494
447
function tryGetDirectories ( host : LanguageServiceHost , directoryName : string ) : string [ ] {
@@ -499,10 +452,6 @@ namespace ts.Completions.PathCompletions {
499
452
return tryIOAndConsumeErrors ( host , host . readDirectory , path , extensions , exclude , include ) || emptyArray ;
500
453
}
501
454
502
- function tryReadFile ( host : LanguageServiceHost , path : string ) : string | undefined {
503
- return tryIOAndConsumeErrors ( host , host . readFile , path ) ;
504
- }
505
-
506
455
function tryFileExists ( host : LanguageServiceHost , path : string ) : boolean {
507
456
return tryIOAndConsumeErrors ( host , host . fileExists , path ) ;
508
457
}
@@ -522,4 +471,8 @@ namespace ts.Completions.PathCompletions {
522
471
catch { /*ignore*/ }
523
472
return undefined ;
524
473
}
474
+
475
+ function containsSlash ( fragment : string ) {
476
+ return stringContains ( fragment , directorySeparator ) ;
477
+ }
525
478
}
0 commit comments