@@ -101,7 +101,7 @@ namespace ts.codefix {
101
101
const exportInfos = getAllReExportingModules ( exportedSymbol , moduleSymbol , symbolName , sourceFile , checker , allSourceFiles ) ;
102
102
Debug . assert ( exportInfos . some ( info => info . moduleSymbol === moduleSymbol ) ) ;
103
103
// We sort the best codefixes first, so taking `first` is best for completions.
104
- const moduleSpecifier = first ( getNewImportInfos ( program , sourceFile , exportInfos , compilerOptions , getCanonicalFileName , host , preferences ) ) . moduleSpecifier ;
104
+ const moduleSpecifier = first ( getNewImportInfos ( program , sourceFile , exportInfos , host , preferences ) ) . moduleSpecifier ;
105
105
const ctx : ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken, preferences } ;
106
106
return { moduleSpecifier, codeAction : first ( getCodeActionsForImport ( exportInfos , ctx ) ) } ;
107
107
}
@@ -225,10 +225,6 @@ namespace ts.codefix {
225
225
}
226
226
}
227
227
228
- function usesJsExtensionOnImports ( sourceFile : SourceFile ) : boolean {
229
- return firstDefined ( sourceFile . imports , ( { text } ) => pathIsRelative ( text ) ? fileExtensionIs ( text , Extension . Js ) : undefined ) || false ;
230
- }
231
-
232
228
function createImportClauseOfKind ( kind : ImportKind . Default | ImportKind . Named | ImportKind . Namespace , symbolName : string ) {
233
229
const id = createIdentifier ( symbolName ) ;
234
230
switch ( kind ) {
@@ -247,320 +243,17 @@ namespace ts.codefix {
247
243
program : Program ,
248
244
sourceFile : SourceFile ,
249
245
moduleSymbols : ReadonlyArray < SymbolExportInfo > ,
250
- compilerOptions : CompilerOptions ,
251
- getCanonicalFileName : ( file : string ) => string ,
252
246
host : LanguageServiceHost ,
253
247
preferences : UserPreferences ,
254
248
) : ReadonlyArray < NewImportInfo > {
255
- const { baseUrl, paths, rootDirs } = compilerOptions ;
256
- const moduleResolutionKind = getEmitModuleResolutionKind ( compilerOptions ) ;
257
- const addJsExtension = usesJsExtensionOnImports ( sourceFile ) ;
258
249
const choicesForEachExportingModule = flatMap < SymbolExportInfo , NewImportInfo [ ] > ( moduleSymbols , ( { moduleSymbol, importKind } ) => {
259
- const modulePathsGroups = getAllModulePaths ( program , moduleSymbol . valueDeclaration . getSourceFile ( ) ) . map ( moduleFileName => {
260
- const sourceDirectory = getDirectoryPath ( sourceFile . fileName ) ;
261
- const global = tryGetModuleNameFromAmbientModule ( moduleSymbol )
262
- || tryGetModuleNameFromTypeRoots ( compilerOptions , host , getCanonicalFileName , moduleFileName , addJsExtension )
263
- || tryGetModuleNameAsNodeModule ( compilerOptions , moduleFileName , host , getCanonicalFileName , sourceDirectory )
264
- || rootDirs && tryGetModuleNameFromRootDirs ( rootDirs , moduleFileName , sourceDirectory , getCanonicalFileName ) ;
265
- if ( global ) {
266
- return [ global ] ;
267
- }
268
-
269
- const relativePath = removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , moduleResolutionKind , addJsExtension ) ;
270
- if ( ! baseUrl || preferences . importModuleSpecifierPreference === "relative" ) {
271
- return [ relativePath ] ;
272
- }
273
-
274
- const relativeToBaseUrl = getRelativePathIfInDirectory ( moduleFileName , baseUrl , getCanonicalFileName ) ;
275
- if ( ! relativeToBaseUrl ) {
276
- return [ relativePath ] ;
277
- }
278
-
279
- const importRelativeToBaseUrl = removeExtensionAndIndexPostFix ( relativeToBaseUrl , moduleResolutionKind , addJsExtension ) ;
280
- if ( paths ) {
281
- const fromPaths = tryGetModuleNameFromPaths ( removeFileExtension ( relativeToBaseUrl ) , importRelativeToBaseUrl , paths ) ;
282
- if ( fromPaths ) {
283
- return [ fromPaths ] ;
284
- }
285
- }
286
-
287
- if ( preferences . importModuleSpecifierPreference === "non-relative" ) {
288
- return [ importRelativeToBaseUrl ] ;
289
- }
290
-
291
- if ( preferences . importModuleSpecifierPreference !== undefined ) Debug . assertNever ( preferences . importModuleSpecifierPreference ) ;
292
-
293
- if ( isPathRelativeToParent ( relativeToBaseUrl ) ) {
294
- return [ relativePath ] ;
295
- }
296
-
297
- /*
298
- Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl.
299
-
300
- Suppose we have:
301
- baseUrl = /base
302
- sourceDirectory = /base/a/b
303
- moduleFileName = /base/foo/bar
304
- Then:
305
- relativePath = ../../foo/bar
306
- getRelativePathNParents(relativePath) = 2
307
- pathFromSourceToBaseUrl = ../../
308
- getRelativePathNParents(pathFromSourceToBaseUrl) = 2
309
- 2 < 2 = false
310
- In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar".
311
-
312
- Suppose we have:
313
- baseUrl = /base
314
- sourceDirectory = /base/foo/a
315
- moduleFileName = /base/foo/bar
316
- Then:
317
- relativePath = ../a
318
- getRelativePathNParents(relativePath) = 1
319
- pathFromSourceToBaseUrl = ../../
320
- getRelativePathNParents(pathFromSourceToBaseUrl) = 2
321
- 1 < 2 = true
322
- In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
323
- */
324
- const pathFromSourceToBaseUrl = ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , baseUrl , getCanonicalFileName ) ) ;
325
- const relativeFirst = getRelativePathNParents ( relativePath ) < getRelativePathNParents ( pathFromSourceToBaseUrl ) ;
326
- return relativeFirst ? [ relativePath , importRelativeToBaseUrl ] : [ importRelativeToBaseUrl , relativePath ] ;
327
- } ) ;
250
+ const modulePathsGroups = moduleSpecifiers . getModuleSpecifiers ( moduleSymbol , program , sourceFile , host , preferences ) ;
328
251
return modulePathsGroups . map ( group => group . map ( moduleSpecifier => ( { moduleSpecifier, importKind } ) ) ) ;
329
252
} ) ;
330
253
// Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together
331
254
return flatten < NewImportInfo > ( choicesForEachExportingModule . sort ( ( a , b ) => first ( a ) . moduleSpecifier . length - first ( b ) . moduleSpecifier . length ) ) ;
332
255
}
333
256
334
- /**
335
- * Looks for a existing imports that use symlinks to this module.
336
- * Only if no symlink is available, the real path will be used.
337
- */
338
- function getAllModulePaths ( program : Program , { fileName } : SourceFile ) : ReadonlyArray < string > {
339
- const symlinks = mapDefined ( program . getSourceFiles ( ) , sf =>
340
- sf . resolvedModules && firstDefinedIterator ( sf . resolvedModules . values ( ) , res =>
341
- res && res . resolvedFileName === fileName ? res . originalPath : undefined ) ) ;
342
- return symlinks . length === 0 ? [ fileName ] : symlinks ;
343
- }
344
-
345
- function getRelativePathNParents ( relativePath : string ) : number {
346
- const components = getPathComponents ( relativePath ) ;
347
- if ( components [ 0 ] || components . length === 1 ) return 0 ;
348
- for ( let i = 1 ; i < components . length ; i ++ ) {
349
- if ( components [ i ] !== ".." ) return i - 1 ;
350
- }
351
- return components . length - 1 ;
352
- }
353
-
354
- function tryGetModuleNameFromAmbientModule ( moduleSymbol : Symbol ) : string | undefined {
355
- const decl = moduleSymbol . valueDeclaration ;
356
- if ( isModuleDeclaration ( decl ) && isStringLiteral ( decl . name ) ) {
357
- return decl . name . text ;
358
- }
359
- }
360
-
361
- function tryGetModuleNameFromPaths ( relativeToBaseUrlWithIndex : string , relativeToBaseUrl : string , paths : MapLike < ReadonlyArray < string > > ) : string | undefined {
362
- for ( const key in paths ) {
363
- for ( const patternText of paths [ key ] ) {
364
- const pattern = removeFileExtension ( normalizePath ( patternText ) ) ;
365
- const indexOfStar = pattern . indexOf ( "*" ) ;
366
- if ( indexOfStar === 0 && pattern . length === 1 ) {
367
- continue ;
368
- }
369
- else if ( indexOfStar !== - 1 ) {
370
- const prefix = pattern . substr ( 0 , indexOfStar ) ;
371
- const suffix = pattern . substr ( indexOfStar + 1 ) ;
372
- if ( relativeToBaseUrl . length >= prefix . length + suffix . length &&
373
- startsWith ( relativeToBaseUrl , prefix ) &&
374
- endsWith ( relativeToBaseUrl , suffix ) ) {
375
- const matchedStar = relativeToBaseUrl . substr ( prefix . length , relativeToBaseUrl . length - suffix . length ) ;
376
- return key . replace ( "*" , matchedStar ) ;
377
- }
378
- }
379
- else if ( pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex ) {
380
- return key ;
381
- }
382
- }
383
- }
384
- }
385
-
386
- function tryGetModuleNameFromRootDirs ( rootDirs : ReadonlyArray < string > , moduleFileName : string , sourceDirectory : string , getCanonicalFileName : ( file : string ) => string ) : string | undefined {
387
- const normalizedTargetPath = getPathRelativeToRootDirs ( moduleFileName , rootDirs , getCanonicalFileName ) ;
388
- if ( normalizedTargetPath === undefined ) {
389
- return undefined ;
390
- }
391
-
392
- const normalizedSourcePath = getPathRelativeToRootDirs ( sourceDirectory , rootDirs , getCanonicalFileName ) ;
393
- const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName ( getRelativePathFromDirectory ( normalizedSourcePath , normalizedTargetPath , getCanonicalFileName ) ) : normalizedTargetPath ;
394
- return removeFileExtension ( relativePath ) ;
395
- }
396
-
397
- function tryGetModuleNameFromTypeRoots (
398
- options : CompilerOptions ,
399
- host : GetEffectiveTypeRootsHost ,
400
- getCanonicalFileName : ( file : string ) => string ,
401
- moduleFileName : string ,
402
- addJsExtension : boolean ,
403
- ) : string | undefined {
404
- const roots = getEffectiveTypeRoots ( options , host ) ;
405
- return firstDefined ( roots , unNormalizedTypeRoot => {
406
- const typeRoot = toPath ( unNormalizedTypeRoot , /*basePath*/ undefined , getCanonicalFileName ) ;
407
- if ( startsWith ( moduleFileName , typeRoot ) ) {
408
- // For a type definition, we can strip `/index` even with classic resolution.
409
- return removeExtensionAndIndexPostFix ( moduleFileName . substring ( typeRoot . length + 1 ) , ModuleResolutionKind . NodeJs , addJsExtension ) ;
410
- }
411
- } ) ;
412
- }
413
-
414
- function tryGetModuleNameAsNodeModule (
415
- options : CompilerOptions ,
416
- moduleFileName : string ,
417
- host : LanguageServiceHost ,
418
- getCanonicalFileName : ( file : string ) => string ,
419
- sourceDirectory : string ,
420
- ) : string | undefined {
421
- if ( getEmitModuleResolutionKind ( options ) !== ModuleResolutionKind . NodeJs ) {
422
- // nothing to do here
423
- return undefined ;
424
- }
425
-
426
- const parts = getNodeModulePathParts ( moduleFileName ) ;
427
-
428
- if ( ! parts ) {
429
- return undefined ;
430
- }
431
-
432
- // Simplify the full file path to something that can be resolved by Node.
433
-
434
- // If the module could be imported by a directory name, use that directory's name
435
- let moduleSpecifier = getDirectoryOrExtensionlessFileName ( moduleFileName ) ;
436
- // Get a path that's relative to node_modules or the importing file's path
437
- moduleSpecifier = getNodeResolvablePath ( moduleSpecifier ) ;
438
- // If the module was found in @types , get the actual Node package name
439
- return getPackageNameFromAtTypesDirectory ( moduleSpecifier ) ;
440
-
441
- function getDirectoryOrExtensionlessFileName ( path : string ) : string {
442
- // If the file is the main module, it can be imported by the package name
443
- const packageRootPath = path . substring ( 0 , parts . packageRootIndex ) ;
444
- const packageJsonPath = combinePaths ( packageRootPath , "package.json" ) ;
445
- if ( host . fileExists ( packageJsonPath ) ) {
446
- const packageJsonContent = JSON . parse ( host . readFile ( packageJsonPath ) ) ;
447
- if ( packageJsonContent ) {
448
- const mainFileRelative = packageJsonContent . typings || packageJsonContent . types || packageJsonContent . main ;
449
- if ( mainFileRelative ) {
450
- const mainExportFile = toPath ( mainFileRelative , packageRootPath , getCanonicalFileName ) ;
451
- if ( mainExportFile === getCanonicalFileName ( path ) ) {
452
- return packageRootPath ;
453
- }
454
- }
455
- }
456
- }
457
-
458
- // We still have a file name - remove the extension
459
- const fullModulePathWithoutExtension = removeFileExtension ( path ) ;
460
-
461
- // If the file is /index, it can be imported by its directory name
462
- if ( getCanonicalFileName ( fullModulePathWithoutExtension . substring ( parts . fileNameIndex ) ) === "/index" ) {
463
- return fullModulePathWithoutExtension . substring ( 0 , parts . fileNameIndex ) ;
464
- }
465
-
466
- return fullModulePathWithoutExtension ;
467
- }
468
-
469
- function getNodeResolvablePath ( path : string ) : string {
470
- const basePath = path . substring ( 0 , parts . topLevelNodeModulesIndex ) ;
471
- if ( sourceDirectory . indexOf ( basePath ) === 0 ) {
472
- // if node_modules folder is in this folder or any of its parent folders, no need to keep it.
473
- return path . substring ( parts . topLevelPackageNameIndex + 1 ) ;
474
- }
475
- else {
476
- return ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , path , getCanonicalFileName ) ) ;
477
- }
478
- }
479
- }
480
-
481
- function getNodeModulePathParts ( fullPath : string ) {
482
- // If fullPath can't be valid module file within node_modules, returns undefined.
483
- // Example of expected pattern: /base/path/node_modules/[@scope /otherpackage/@otherscope /node_modules/]package/[subdirectory/]file.js
484
- // Returns indices: ^ ^ ^ ^
485
-
486
- let topLevelNodeModulesIndex = 0 ;
487
- let topLevelPackageNameIndex = 0 ;
488
- let packageRootIndex = 0 ;
489
- let fileNameIndex = 0 ;
490
-
491
- const enum States {
492
- BeforeNodeModules ,
493
- NodeModules ,
494
- Scope ,
495
- PackageContent
496
- }
497
-
498
- let partStart = 0 ;
499
- let partEnd = 0 ;
500
- let state = States . BeforeNodeModules ;
501
-
502
- while ( partEnd >= 0 ) {
503
- partStart = partEnd ;
504
- partEnd = fullPath . indexOf ( "/" , partStart + 1 ) ;
505
- switch ( state ) {
506
- case States . BeforeNodeModules :
507
- if ( fullPath . indexOf ( "/node_modules/" , partStart ) === partStart ) {
508
- topLevelNodeModulesIndex = partStart ;
509
- topLevelPackageNameIndex = partEnd ;
510
- state = States . NodeModules ;
511
- }
512
- break ;
513
- case States . NodeModules :
514
- case States . Scope :
515
- if ( state === States . NodeModules && fullPath . charAt ( partStart + 1 ) === "@" ) {
516
- state = States . Scope ;
517
- }
518
- else {
519
- packageRootIndex = partEnd ;
520
- state = States . PackageContent ;
521
- }
522
- break ;
523
- case States . PackageContent :
524
- if ( fullPath . indexOf ( "/node_modules/" , partStart ) === partStart ) {
525
- state = States . NodeModules ;
526
- }
527
- else {
528
- state = States . PackageContent ;
529
- }
530
- break ;
531
- }
532
- }
533
-
534
- fileNameIndex = partStart ;
535
-
536
- return state > States . NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined ;
537
- }
538
-
539
- function getPathRelativeToRootDirs ( path : string , rootDirs : ReadonlyArray < string > , getCanonicalFileName : GetCanonicalFileName ) : string | undefined {
540
- return firstDefined ( rootDirs , rootDir => {
541
- const relativePath = getRelativePathIfInDirectory ( path , rootDir , getCanonicalFileName ) ;
542
- return isPathRelativeToParent ( relativePath ) ? undefined : relativePath ;
543
- } ) ;
544
- }
545
-
546
- function removeExtensionAndIndexPostFix ( fileName : string , moduleResolutionKind : ModuleResolutionKind , addJsExtension : boolean ) : string {
547
- const noExtension = removeFileExtension ( fileName ) ;
548
- return addJsExtension
549
- ? noExtension + ".js"
550
- : moduleResolutionKind === ModuleResolutionKind . NodeJs
551
- ? removeSuffix ( noExtension , "/index" )
552
- : noExtension ;
553
- }
554
-
555
- function getRelativePathIfInDirectory ( path : string , directoryPath : string , getCanonicalFileName : GetCanonicalFileName ) : string | undefined {
556
- const relativePath = getRelativePathToDirectoryOrUrl ( directoryPath , path , directoryPath , getCanonicalFileName , /*isAbsolutePathAnUrl*/ false ) ;
557
- return isRootedDiskPath ( relativePath ) ? undefined : relativePath ;
558
- }
559
-
560
- function isPathRelativeToParent ( path : string ) : boolean {
561
- return startsWith ( path , ".." ) ;
562
- }
563
-
564
257
function getCodeActionsForAddImport (
565
258
exportInfos : ReadonlyArray < SymbolExportInfo > ,
566
259
ctx : ImportCodeFixContext ,
@@ -585,7 +278,7 @@ namespace ts.codefix {
585
278
const existingDeclaration = firstDefined ( existingImports , newImportInfoFromExistingSpecifier ) ;
586
279
const newImportInfos = existingDeclaration
587
280
? [ existingDeclaration ]
588
- : getNewImportInfos ( ctx . program , ctx . sourceFile , exportInfos , ctx . compilerOptions , ctx . getCanonicalFileName , ctx . host , ctx . preferences ) ;
281
+ : getNewImportInfos ( ctx . program , ctx . sourceFile , exportInfos , ctx . host , ctx . preferences ) ;
589
282
for ( const info of newImportInfos ) {
590
283
addNew . push ( getCodeActionForNewImport ( ctx , info ) ) ;
591
284
}
0 commit comments