@@ -5,6 +5,50 @@ namespace ts.moduleSpecifiers {
5
5
readonly importModuleSpecifierPreference ?: "relative" | "non-relative" ;
6
6
}
7
7
8
+ const enum RelativePreference { Relative , NonRelative , Auto }
9
+ const enum Ending {
10
+ Minimal , // Import "foo.ts" as "foo", "foo/index.ts" as "foo"
11
+ Index , // Import "foo.ts" as "foo", "foo/index.ts" as "foo/index"
12
+ JsExtension , // Import "foo.ts" as "foo.js", "foo/index.ts" as "foo/index.js"
13
+ }
14
+
15
+ // Processed preferences
16
+ interface Preferences {
17
+ readonly relativePreference : RelativePreference ;
18
+ readonly ending : Ending ;
19
+ }
20
+
21
+ function getPreferences ( { importModuleSpecifierPreference } : ModuleSpecifierPreferences , compilerOptions : CompilerOptions , importingSourceFile : SourceFile ) : Preferences {
22
+ return {
23
+ relativePreference : importModuleSpecifierPreference === "relative" ? RelativePreference . Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference . NonRelative : RelativePreference . Auto ,
24
+ ending : usesJsExtensionOnImports ( importingSourceFile ) ? Ending . JsExtension
25
+ : getEmitModuleResolutionKind ( compilerOptions ) !== ModuleResolutionKind . NodeJs ? Ending . Index : Ending . Minimal ,
26
+ } ;
27
+ }
28
+
29
+ function getPreferencesForUpdate ( _ : ModuleSpecifierPreferences , compilerOptions : CompilerOptions , oldImportSpecifier : string ) : Preferences {
30
+ return {
31
+ relativePreference : isExternalModuleNameRelative ( oldImportSpecifier ) ? RelativePreference . Relative : RelativePreference . NonRelative ,
32
+ ending : fileExtensionIs ( oldImportSpecifier , Extension . Js ) ? Ending . JsExtension
33
+ : getEmitModuleResolutionKind ( compilerOptions ) !== ModuleResolutionKind . NodeJs || endsWith ( oldImportSpecifier , "index" ) || endsWith ( oldImportSpecifier , "index.js" ) ? Ending . Index : Ending . Minimal ,
34
+ } ;
35
+ }
36
+
37
+ export function updateModuleSpecifier (
38
+ compilerOptions : CompilerOptions ,
39
+ importingSourceFileName : Path ,
40
+ toFileName : string ,
41
+ host : ModuleSpecifierResolutionHost ,
42
+ files : ReadonlyArray < SourceFile > ,
43
+ preferences : ModuleSpecifierPreferences = { } ,
44
+ redirectTargetsMap : RedirectTargetsMap ,
45
+ oldImportSpecifier : string ,
46
+ ) : string | undefined {
47
+ const res = getModuleSpecifierWorker ( compilerOptions , importingSourceFileName , toFileName , host , files , redirectTargetsMap , getPreferencesForUpdate ( preferences , compilerOptions , oldImportSpecifier ) ) ;
48
+ if ( res === oldImportSpecifier ) return undefined ;
49
+ return res ;
50
+ }
51
+
8
52
// Note: importingSourceFile is just for usesJsExtensionOnImports
9
53
export function getModuleSpecifier (
10
54
compilerOptions : CompilerOptions ,
@@ -16,9 +60,21 @@ namespace ts.moduleSpecifiers {
16
60
preferences : ModuleSpecifierPreferences = { } ,
17
61
redirectTargetsMap : RedirectTargetsMap ,
18
62
) : string {
19
- const info = getInfo ( compilerOptions , importingSourceFile , importingSourceFileName , host ) ;
63
+ return getModuleSpecifierWorker ( compilerOptions , importingSourceFileName , toFileName , host , files , redirectTargetsMap , getPreferences ( preferences , compilerOptions , importingSourceFile ) ) ;
64
+ }
65
+
66
+ function getModuleSpecifierWorker (
67
+ compilerOptions : CompilerOptions ,
68
+ importingSourceFileName : Path ,
69
+ toFileName : string ,
70
+ host : ModuleSpecifierResolutionHost ,
71
+ files : ReadonlyArray < SourceFile > ,
72
+ redirectTargetsMap : RedirectTargetsMap ,
73
+ preferences : Preferences
74
+ ) : string {
75
+ const info = getInfo ( importingSourceFileName , host ) ;
20
76
const modulePaths = getAllModulePaths ( files , importingSourceFileName , toFileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
21
- return firstDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ||
77
+ return firstDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions , preferences . ending ) ) ||
22
78
first ( getLocalModuleSpecifiers ( toFileName , info , compilerOptions , preferences ) ) ;
23
79
}
24
80
@@ -41,60 +97,47 @@ namespace ts.moduleSpecifiers {
41
97
importingSourceFile : SourceFile ,
42
98
host : ModuleSpecifierResolutionHost ,
43
99
files : ReadonlyArray < SourceFile > ,
44
- preferences : ModuleSpecifierPreferences ,
100
+ userPreferences : ModuleSpecifierPreferences ,
45
101
redirectTargetsMap : RedirectTargetsMap ,
46
102
) : ReadonlyArray < ReadonlyArray < string > > {
47
103
const ambient = tryGetModuleNameFromAmbientModule ( moduleSymbol ) ;
48
104
if ( ambient ) return [ [ ambient ] ] ;
49
105
50
- const info = getInfo ( compilerOptions , importingSourceFile , importingSourceFile . path , host ) ;
106
+ const info = getInfo ( importingSourceFile . path , host ) ;
51
107
if ( ! files ) {
52
108
return Debug . fail ( "Files list must be present to resolve symlinks in specifier resolution" ) ;
53
109
}
54
110
const moduleSourceFile = getSourceFileOfNode ( moduleSymbol . valueDeclaration ) ;
55
111
const modulePaths = getAllModulePaths ( files , importingSourceFile . path , moduleSourceFile . fileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
56
112
57
- const global = mapDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ;
113
+ const preferences = getPreferences ( userPreferences , compilerOptions , importingSourceFile ) ;
114
+ const global = mapDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions , preferences . ending ) ) ;
58
115
return global . length ? global . map ( g => [ g ] ) : modulePaths . map ( moduleFileName =>
59
116
getLocalModuleSpecifiers ( moduleFileName , info , compilerOptions , preferences ) ) ;
60
117
}
61
118
62
119
interface Info {
63
- readonly moduleResolutionKind : ModuleResolutionKind ;
64
- readonly addJsExtension : boolean ;
65
120
readonly getCanonicalFileName : GetCanonicalFileName ;
66
121
readonly sourceDirectory : Path ;
67
122
}
68
123
// 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 ) ;
124
+ function getInfo ( importingSourceFileName : Path , host : ModuleSpecifierResolutionHost ) : Info {
72
125
const getCanonicalFileName = createGetCanonicalFileName ( host . useCaseSensitiveFileNames ? host . useCaseSensitiveFileNames ( ) : true ) ;
73
126
const sourceDirectory = getDirectoryPath ( importingSourceFileName ) ;
74
- return { moduleResolutionKind , addJsExtension , getCanonicalFileName, sourceDirectory } ;
127
+ return { getCanonicalFileName, sourceDirectory } ;
75
128
}
76
129
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 )
130
+ function getGlobalModuleSpecifier ( moduleFileName : string , { getCanonicalFileName, sourceDirectory } : Info , host : ModuleSpecifierResolutionHost , compilerOptions : CompilerOptions , ending : Ending ) {
131
+ return tryGetModuleNameFromTypeRoots ( compilerOptions , host , getCanonicalFileName , moduleFileName , ending )
84
132
|| tryGetModuleNameAsNodeModule ( compilerOptions , moduleFileName , host , getCanonicalFileName , sourceDirectory ) ;
85
133
}
86
134
87
- function getLocalModuleSpecifiers (
88
- moduleFileName : string ,
89
- { moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory } : Info ,
90
- compilerOptions : CompilerOptions ,
91
- preferences : ModuleSpecifierPreferences ,
92
- ) : ReadonlyArray < string > {
135
+ function getLocalModuleSpecifiers ( moduleFileName : string , { getCanonicalFileName, sourceDirectory } : Info , compilerOptions : CompilerOptions , { ending, relativePreference } : Preferences ) : ReadonlyArray < string > {
93
136
const { baseUrl, paths, rootDirs } = compilerOptions ;
94
137
95
138
const relativePath = rootDirs && tryGetModuleNameFromRootDirs ( rootDirs , moduleFileName , sourceDirectory , getCanonicalFileName ) ||
96
- removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , moduleResolutionKind , addJsExtension ) ;
97
- if ( ! baseUrl || preferences . importModuleSpecifierPreference === "relative" ) {
139
+ removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , ending ) ;
140
+ if ( ! baseUrl || relativePreference === RelativePreference . Relative ) {
98
141
return [ relativePath ] ;
99
142
}
100
143
@@ -103,19 +146,19 @@ namespace ts.moduleSpecifiers {
103
146
return [ relativePath ] ;
104
147
}
105
148
106
- const importRelativeToBaseUrl = removeExtensionAndIndexPostFix ( relativeToBaseUrl , moduleResolutionKind , addJsExtension ) ;
149
+ const importRelativeToBaseUrl = removeExtensionAndIndexPostFix ( relativeToBaseUrl , ending ) ;
107
150
if ( paths ) {
108
151
const fromPaths = tryGetModuleNameFromPaths ( removeFileExtension ( relativeToBaseUrl ) , importRelativeToBaseUrl , paths ) ;
109
152
if ( fromPaths ) {
110
153
return [ fromPaths ] ;
111
154
}
112
155
}
113
156
114
- if ( preferences . importModuleSpecifierPreference === "non-relative" ) {
157
+ if ( relativePreference === RelativePreference . NonRelative ) {
115
158
return [ importRelativeToBaseUrl ] ;
116
159
}
117
160
118
- if ( preferences . importModuleSpecifierPreference !== undefined ) Debug . assertNever ( preferences . importModuleSpecifierPreference ) ;
161
+ if ( relativePreference !== RelativePreference . Auto ) Debug . assertNever ( relativePreference ) ;
119
162
120
163
if ( isPathRelativeToParent ( relativeToBaseUrl ) ) {
121
164
return [ relativePath ] ;
@@ -276,14 +319,14 @@ namespace ts.moduleSpecifiers {
276
319
host : GetEffectiveTypeRootsHost ,
277
320
getCanonicalFileName : ( file : string ) => string ,
278
321
moduleFileName : string ,
279
- addJsExtension : boolean ,
322
+ ending : Ending ,
280
323
) : string | undefined {
281
324
const roots = getEffectiveTypeRoots ( options , host ) ;
282
325
return firstDefined ( roots , unNormalizedTypeRoot => {
283
326
const typeRoot = toPath ( unNormalizedTypeRoot , /*basePath*/ undefined , getCanonicalFileName ) ;
284
327
if ( startsWith ( moduleFileName , typeRoot ) ) {
285
328
// For a type definition, we can strip `/index` even with classic resolution.
286
- return removeExtensionAndIndexPostFix ( moduleFileName . substring ( typeRoot . length + 1 ) , ModuleResolutionKind . NodeJs , addJsExtension ) ;
329
+ return removeExtensionAndIndexPostFix ( moduleFileName . substring ( typeRoot . length + 1 ) , ending ) ;
287
330
}
288
331
} ) ;
289
332
}
@@ -428,13 +471,18 @@ namespace ts.moduleSpecifiers {
428
471
} ) ;
429
472
}
430
473
431
- function removeExtensionAndIndexPostFix ( fileName : string , moduleResolutionKind : ModuleResolutionKind , addJsExtension : boolean ) : string {
474
+ function removeExtensionAndIndexPostFix ( fileName : string , ending : Ending ) : string {
432
475
const noExtension = removeFileExtension ( fileName ) ;
433
- return addJsExtension
434
- ? noExtension + ".js"
435
- : moduleResolutionKind === ModuleResolutionKind . NodeJs
436
- ? removeSuffix ( noExtension , "/index" )
437
- : noExtension ;
476
+ switch ( ending ) {
477
+ case Ending . Minimal :
478
+ return removeSuffix ( noExtension , "/index" ) ;
479
+ case Ending . Index :
480
+ return noExtension ;
481
+ case Ending . JsExtension :
482
+ return noExtension + ".js" ;
483
+ default :
484
+ return Debug . assertNever ( ending ) ;
485
+ }
438
486
}
439
487
440
488
function getRelativePathIfInDirectory ( path : string , directoryPath : string , getCanonicalFileName : GetCanonicalFileName ) : string | undefined {
0 commit comments