@@ -5,6 +5,47 @@ namespace ts.moduleSpecifiers {
5
5
readonly importModuleSpecifierPreference ?: "relative" | "non-relative" ;
6
6
}
7
7
8
+ const enum RelativePreference { Relative , NonRelative , Auto }
9
+ // Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js"
10
+ const enum Ending { Minimal , Index , JsExtension }
11
+
12
+ // Processed preferences
13
+ interface Preferences {
14
+ readonly relativePreference : RelativePreference ;
15
+ readonly ending : Ending ;
16
+ }
17
+
18
+ function getPreferences ( { importModuleSpecifierPreference } : ModuleSpecifierPreferences , compilerOptions : CompilerOptions , importingSourceFile : SourceFile ) : Preferences {
19
+ return {
20
+ relativePreference : importModuleSpecifierPreference === "relative" ? RelativePreference . Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference . NonRelative : RelativePreference . Auto ,
21
+ ending : usesJsExtensionOnImports ( importingSourceFile ) ? Ending . JsExtension
22
+ : getEmitModuleResolutionKind ( compilerOptions ) !== ModuleResolutionKind . NodeJs ? Ending . Index : Ending . Minimal ,
23
+ } ;
24
+ }
25
+
26
+ function getPreferencesForUpdate ( _ : ModuleSpecifierPreferences , compilerOptions : CompilerOptions , oldImportSpecifier : string ) : Preferences {
27
+ return {
28
+ relativePreference : isExternalModuleNameRelative ( oldImportSpecifier ) ? RelativePreference . Relative : RelativePreference . NonRelative ,
29
+ ending : fileExtensionIs ( oldImportSpecifier , Extension . Js ) ? Ending . JsExtension
30
+ : getEmitModuleResolutionKind ( compilerOptions ) !== ModuleResolutionKind . NodeJs || endsWith ( oldImportSpecifier , "index" ) || endsWith ( oldImportSpecifier , "index.js" ) ? Ending . Index : Ending . Minimal ,
31
+ } ;
32
+ }
33
+
34
+ export function updateModuleSpecifier (
35
+ compilerOptions : CompilerOptions ,
36
+ importingSourceFileName : Path ,
37
+ toFileName : string ,
38
+ host : ModuleSpecifierResolutionHost ,
39
+ files : ReadonlyArray < SourceFile > ,
40
+ preferences : ModuleSpecifierPreferences = { } ,
41
+ redirectTargetsMap : RedirectTargetsMap ,
42
+ oldImportSpecifier : string ,
43
+ ) : string | undefined {
44
+ const res = getModuleSpecifierWorker ( compilerOptions , importingSourceFileName , toFileName , host , files , redirectTargetsMap , getPreferencesForUpdate ( preferences , compilerOptions , oldImportSpecifier ) ) ;
45
+ if ( res === oldImportSpecifier ) return undefined ;
46
+ return res ;
47
+ }
48
+
8
49
// Note: importingSourceFile is just for usesJsExtensionOnImports
9
50
export function getModuleSpecifier (
10
51
compilerOptions : CompilerOptions ,
@@ -16,9 +57,21 @@ namespace ts.moduleSpecifiers {
16
57
preferences : ModuleSpecifierPreferences = { } ,
17
58
redirectTargetsMap : RedirectTargetsMap ,
18
59
) : string {
19
- const info = getInfo ( compilerOptions , importingSourceFile , importingSourceFileName , host ) ;
60
+ return getModuleSpecifierWorker ( compilerOptions , importingSourceFileName , toFileName , host , files , redirectTargetsMap , getPreferences ( preferences , compilerOptions , importingSourceFile ) ) ;
61
+ }
62
+
63
+ function getModuleSpecifierWorker (
64
+ compilerOptions : CompilerOptions ,
65
+ importingSourceFileName : Path ,
66
+ toFileName : string ,
67
+ host : ModuleSpecifierResolutionHost ,
68
+ files : ReadonlyArray < SourceFile > ,
69
+ redirectTargetsMap : RedirectTargetsMap ,
70
+ preferences : Preferences
71
+ ) : string {
72
+ const info = getInfo ( importingSourceFileName , host ) ;
20
73
const modulePaths = getAllModulePaths ( files , importingSourceFileName , toFileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
21
- return firstDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ||
74
+ return firstDefined ( modulePaths , moduleFileName => tryGetModuleNameAsNodeModule ( moduleFileName , info , host , compilerOptions ) ) ||
22
75
first ( getLocalModuleSpecifiers ( toFileName , info , compilerOptions , preferences ) ) ;
23
76
}
24
77
@@ -41,60 +94,42 @@ namespace ts.moduleSpecifiers {
41
94
importingSourceFile : SourceFile ,
42
95
host : ModuleSpecifierResolutionHost ,
43
96
files : ReadonlyArray < SourceFile > ,
44
- preferences : ModuleSpecifierPreferences ,
97
+ userPreferences : ModuleSpecifierPreferences ,
45
98
redirectTargetsMap : RedirectTargetsMap ,
46
99
) : ReadonlyArray < ReadonlyArray < string > > {
47
100
const ambient = tryGetModuleNameFromAmbientModule ( moduleSymbol ) ;
48
101
if ( ambient ) return [ [ ambient ] ] ;
49
102
50
- const info = getInfo ( compilerOptions , importingSourceFile , importingSourceFile . path , host ) ;
103
+ const info = getInfo ( importingSourceFile . path , host ) ;
51
104
if ( ! files ) {
52
105
return Debug . fail ( "Files list must be present to resolve symlinks in specifier resolution" ) ;
53
106
}
54
107
const moduleSourceFile = getSourceFileOfNode ( moduleSymbol . valueDeclaration ) ;
55
108
const modulePaths = getAllModulePaths ( files , importingSourceFile . path , moduleSourceFile . fileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
56
109
57
- const global = mapDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ;
110
+ const preferences = getPreferences ( userPreferences , compilerOptions , importingSourceFile ) ;
111
+ const global = mapDefined ( modulePaths , moduleFileName => tryGetModuleNameAsNodeModule ( moduleFileName , info , host , compilerOptions ) ) ;
58
112
return global . length ? global . map ( g => [ g ] ) : modulePaths . map ( moduleFileName =>
59
113
getLocalModuleSpecifiers ( moduleFileName , info , compilerOptions , preferences ) ) ;
60
114
}
61
115
62
116
interface Info {
63
- readonly moduleResolutionKind : ModuleResolutionKind ;
64
- readonly addJsExtension : boolean ;
65
117
readonly getCanonicalFileName : GetCanonicalFileName ;
66
118
readonly sourceDirectory : Path ;
67
119
}
68
120
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
69
- function getInfo ( compilerOptions : CompilerOptions , importingSourceFile : SourceFile , importingSourceFileName : Path , host : ModuleSpecifierResolutionHost ) : Info {
70
- const moduleResolutionKind = getEmitModuleResolutionKind ( compilerOptions ) ;
71
- const addJsExtension = usesJsExtensionOnImports ( importingSourceFile ) ;
121
+ function getInfo ( importingSourceFileName : Path , host : ModuleSpecifierResolutionHost ) : Info {
72
122
const getCanonicalFileName = createGetCanonicalFileName ( host . useCaseSensitiveFileNames ? host . useCaseSensitiveFileNames ( ) : true ) ;
73
123
const sourceDirectory = getDirectoryPath ( importingSourceFileName ) ;
74
- return { moduleResolutionKind , addJsExtension , getCanonicalFileName, sourceDirectory } ;
124
+ return { getCanonicalFileName, sourceDirectory } ;
75
125
}
76
126
77
- function getGlobalModuleSpecifier (
78
- moduleFileName : string ,
79
- { addJsExtension, getCanonicalFileName, sourceDirectory } : Info ,
80
- host : ModuleSpecifierResolutionHost ,
81
- compilerOptions : CompilerOptions ,
82
- ) {
83
- return tryGetModuleNameFromTypeRoots ( compilerOptions , host , getCanonicalFileName , moduleFileName , addJsExtension )
84
- || tryGetModuleNameAsNodeModule ( compilerOptions , moduleFileName , host , getCanonicalFileName , sourceDirectory ) ;
85
- }
86
-
87
- function getLocalModuleSpecifiers (
88
- moduleFileName : string ,
89
- { moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory } : Info ,
90
- compilerOptions : CompilerOptions ,
91
- preferences : ModuleSpecifierPreferences ,
92
- ) : ReadonlyArray < string > {
127
+ function getLocalModuleSpecifiers ( moduleFileName : string , { getCanonicalFileName, sourceDirectory } : Info , compilerOptions : CompilerOptions , { ending, relativePreference } : Preferences ) : ReadonlyArray < string > {
93
128
const { baseUrl, paths, rootDirs } = compilerOptions ;
94
129
95
130
const relativePath = rootDirs && tryGetModuleNameFromRootDirs ( rootDirs , moduleFileName , sourceDirectory , getCanonicalFileName ) ||
96
- removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , moduleResolutionKind , addJsExtension ) ;
97
- if ( ! baseUrl || preferences . importModuleSpecifierPreference === "relative" ) {
131
+ removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , ending ) ;
132
+ if ( ! baseUrl || relativePreference === RelativePreference . Relative ) {
98
133
return [ relativePath ] ;
99
134
}
100
135
@@ -103,19 +138,19 @@ namespace ts.moduleSpecifiers {
103
138
return [ relativePath ] ;
104
139
}
105
140
106
- const importRelativeToBaseUrl = removeExtensionAndIndexPostFix ( relativeToBaseUrl , moduleResolutionKind , addJsExtension ) ;
141
+ const importRelativeToBaseUrl = removeExtensionAndIndexPostFix ( relativeToBaseUrl , ending ) ;
107
142
if ( paths ) {
108
143
const fromPaths = tryGetModuleNameFromPaths ( removeFileExtension ( relativeToBaseUrl ) , importRelativeToBaseUrl , paths ) ;
109
144
if ( fromPaths ) {
110
145
return [ fromPaths ] ;
111
146
}
112
147
}
113
148
114
- if ( preferences . importModuleSpecifierPreference === "non-relative" ) {
149
+ if ( relativePreference === RelativePreference . NonRelative ) {
115
150
return [ importRelativeToBaseUrl ] ;
116
151
}
117
152
118
- if ( preferences . importModuleSpecifierPreference !== undefined ) Debug . assertNever ( preferences . importModuleSpecifierPreference ) ;
153
+ if ( relativePreference !== RelativePreference . Auto ) Debug . assertNever ( relativePreference ) ;
119
154
120
155
if ( isPathRelativeToParent ( relativeToBaseUrl ) ) {
121
156
return [ relativePath ] ;
@@ -271,37 +306,8 @@ namespace ts.moduleSpecifiers {
271
306
return removeFileExtension ( relativePath ) ;
272
307
}
273
308
274
- function tryGetModuleNameFromTypeRoots (
275
- options : CompilerOptions ,
276
- host : GetEffectiveTypeRootsHost ,
277
- getCanonicalFileName : ( file : string ) => string ,
278
- moduleFileName : string ,
279
- addJsExtension : boolean ,
280
- ) : string | undefined {
281
- const roots = getEffectiveTypeRoots ( options , host ) ;
282
- return firstDefined ( roots , unNormalizedTypeRoot => {
283
- const typeRoot = toPath ( unNormalizedTypeRoot , /*basePath*/ undefined , getCanonicalFileName ) ;
284
- if ( startsWith ( moduleFileName , typeRoot ) ) {
285
- // For a type definition, we can strip `/index` even with classic resolution.
286
- return removeExtensionAndIndexPostFix ( moduleFileName . substring ( typeRoot . length + 1 ) , ModuleResolutionKind . NodeJs , addJsExtension ) ;
287
- }
288
- } ) ;
289
- }
290
-
291
- function tryGetModuleNameAsNodeModule (
292
- options : CompilerOptions ,
293
- moduleFileName : string ,
294
- host : ModuleSpecifierResolutionHost ,
295
- getCanonicalFileName : ( file : string ) => string ,
296
- sourceDirectory : Path ,
297
- ) : string | undefined {
298
- if ( getEmitModuleResolutionKind ( options ) !== ModuleResolutionKind . NodeJs ) {
299
- // nothing to do here
300
- return undefined ;
301
- }
302
-
309
+ function tryGetModuleNameAsNodeModule ( moduleFileName : string , { getCanonicalFileName, sourceDirectory } : Info , host : ModuleSpecifierResolutionHost , options : CompilerOptions ) : string | undefined {
303
310
const parts : NodeModulePathParts = getNodeModulePathParts ( moduleFileName ) ! ;
304
-
305
311
if ( ! parts ) {
306
312
return undefined ;
307
313
}
@@ -313,8 +319,12 @@ namespace ts.moduleSpecifiers {
313
319
// Get a path that's relative to node_modules or the importing file's path
314
320
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
315
321
if ( ! startsWith ( sourceDirectory , getCanonicalFileName ( moduleSpecifier . substring ( 0 , parts . topLevelNodeModulesIndex ) ) ) ) return undefined ;
322
+
316
323
// If the module was found in @types , get the actual Node package name
317
- return getPackageNameFromAtTypesDirectory ( moduleSpecifier . substring ( parts . topLevelPackageNameIndex + 1 ) ) ;
324
+ const nodeModulesDirectoryName = moduleSpecifier . substring ( parts . topLevelPackageNameIndex + 1 ) ;
325
+ const packageName = getPackageNameFromAtTypesDirectory ( nodeModulesDirectoryName ) ;
326
+ // For classic resolution, only allow importing from node_modules/@types, not other node_modules
327
+ return getEmitModuleResolutionKind ( options ) !== ModuleResolutionKind . NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName ;
318
328
319
329
function getDirectoryOrExtensionlessFileName ( path : string ) : string {
320
330
// If the file is the main module, it can be imported by the package name
@@ -428,13 +438,18 @@ namespace ts.moduleSpecifiers {
428
438
} ) ;
429
439
}
430
440
431
- function removeExtensionAndIndexPostFix ( fileName : string , moduleResolutionKind : ModuleResolutionKind , addJsExtension : boolean ) : string {
441
+ function removeExtensionAndIndexPostFix ( fileName : string , ending : Ending ) : string {
432
442
const noExtension = removeFileExtension ( fileName ) ;
433
- return addJsExtension
434
- ? noExtension + ".js"
435
- : moduleResolutionKind === ModuleResolutionKind . NodeJs
436
- ? removeSuffix ( noExtension , "/index" )
437
- : noExtension ;
443
+ switch ( ending ) {
444
+ case Ending . Minimal :
445
+ return removeSuffix ( noExtension , "/index" ) ;
446
+ case Ending . Index :
447
+ return noExtension ;
448
+ case Ending . JsExtension :
449
+ return noExtension + ".js" ;
450
+ default :
451
+ return Debug . assertNever ( ending ) ;
452
+ }
438
453
}
439
454
440
455
function getRelativePathIfInDirectory ( path : string , directoryPath : string , getCanonicalFileName : GetCanonicalFileName ) : string | undefined {
0 commit comments