1
- // Used by importFixes to synthesize import module specifiers.
1
+ // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
2
2
/* @internal */
3
3
namespace ts . moduleSpecifiers {
4
4
export interface ModuleSpecifierPreferences {
@@ -14,13 +14,26 @@ namespace ts.moduleSpecifiers {
14
14
host : ModuleSpecifierResolutionHost ,
15
15
files : ReadonlyArray < SourceFile > ,
16
16
preferences : ModuleSpecifierPreferences = { } ,
17
+ redirectTargetsMap : RedirectTargetsMap ,
17
18
) : string {
18
19
const info = getInfo ( compilerOptions , importingSourceFile , importingSourceFileName , host ) ;
19
- const modulePaths = getAllModulePaths ( files , toFileName , info . getCanonicalFileName , host ) ;
20
+ const modulePaths = getAllModulePaths ( files , importingSourceFileName , toFileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
20
21
return firstDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ||
21
22
first ( getLocalModuleSpecifiers ( toFileName , info , compilerOptions , preferences ) ) ;
22
23
}
23
24
25
+ export function getModuleSpecifierForDeclarationFile (
26
+ moduleSymbol : Symbol ,
27
+ compilerOptions : CompilerOptions ,
28
+ importingSourceFile : SourceFile ,
29
+ host : ModuleSpecifierResolutionHost ,
30
+ files : ReadonlyArray < SourceFile > ,
31
+ preferences : ModuleSpecifierPreferences ,
32
+ redirectTargetsMap : RedirectTargetsMap ,
33
+ ) : string {
34
+ return first ( first ( getModuleSpecifiers ( moduleSymbol , compilerOptions , importingSourceFile , host , files , preferences , redirectTargetsMap ) ) ) ;
35
+ }
36
+
24
37
// For each symlink/original for a module, returns a list of ways to import that file.
25
38
export function getModuleSpecifiers (
26
39
moduleSymbol : Symbol ,
@@ -29,6 +42,7 @@ namespace ts.moduleSpecifiers {
29
42
host : ModuleSpecifierResolutionHost ,
30
43
files : ReadonlyArray < SourceFile > ,
31
44
preferences : ModuleSpecifierPreferences ,
45
+ redirectTargetsMap : RedirectTargetsMap ,
32
46
) : ReadonlyArray < ReadonlyArray < string > > {
33
47
const ambient = tryGetModuleNameFromAmbientModule ( moduleSymbol ) ;
34
48
if ( ambient ) return [ [ ambient ] ] ;
@@ -37,7 +51,8 @@ namespace ts.moduleSpecifiers {
37
51
if ( ! files ) {
38
52
return Debug . fail ( "Files list must be present to resolve symlinks in specifier resolution" ) ;
39
53
}
40
- const modulePaths = getAllModulePaths ( files , getSourceFileOfNode ( moduleSymbol . valueDeclaration ) . fileName , info . getCanonicalFileName , host ) ;
54
+ const moduleSourceFile = getSourceFileOfNode ( moduleSymbol . valueDeclaration ) ;
55
+ const modulePaths = getAllModulePaths ( files , importingSourceFile . path , moduleSourceFile . fileName , info . getCanonicalFileName , host , redirectTargetsMap ) ;
41
56
42
57
const global = mapDefined ( modulePaths , moduleFileName => getGlobalModuleSpecifier ( moduleFileName , info , host , compilerOptions ) ) ;
43
58
return global . length ? global . map ( g => [ g ] ) : modulePaths . map ( moduleFileName =>
@@ -142,64 +157,69 @@ namespace ts.moduleSpecifiers {
142
157
return firstDefined ( imports , ( { text } ) => pathIsRelative ( text ) ? fileExtensionIs ( text , Extension . Js ) : undefined ) || false ;
143
158
}
144
159
145
- function discoverProbableSymlinks ( files : ReadonlyArray < SourceFile > , getCanonicalFileName : ( file : string ) => string , host : ModuleSpecifierResolutionHost ) {
160
+ function stringsEqual ( a : string , b : string , getCanonicalFileName : GetCanonicalFileName ) : boolean {
161
+ return getCanonicalFileName ( a ) === getCanonicalFileName ( b ) ;
162
+ }
163
+
164
+ // KLUDGE: Don't assume one 'node_modules' links to another. More likely a single directory inside the node_modules is the symlink.
165
+ // ALso, don't assume that an `@foo` directory is linked. More likely the contents of that are linked.
166
+ function isNodeModulesOrScopedPackageDirectory ( s : string , getCanonicalFileName : GetCanonicalFileName ) : boolean {
167
+ return getCanonicalFileName ( s ) === "node_modules" || startsWith ( s , "@" ) ;
168
+ }
169
+
170
+ function guessDirectorySymlink ( a : string , b : string , cwd : string , getCanonicalFileName : GetCanonicalFileName ) : [ string , string ] {
171
+ const aParts = getPathComponents ( toPath ( a , cwd , getCanonicalFileName ) ) ;
172
+ const bParts = getPathComponents ( toPath ( b , cwd , getCanonicalFileName ) ) ;
173
+ while ( ! isNodeModulesOrScopedPackageDirectory ( aParts [ aParts . length - 2 ] , getCanonicalFileName ) &&
174
+ ! isNodeModulesOrScopedPackageDirectory ( bParts [ bParts . length - 2 ] , getCanonicalFileName ) &&
175
+ stringsEqual ( aParts [ aParts . length - 1 ] , bParts [ bParts . length - 1 ] , getCanonicalFileName ) ) {
176
+ aParts . pop ( ) ;
177
+ bParts . pop ( ) ;
178
+ }
179
+ return [ getPathFromPathComponents ( aParts ) , getPathFromPathComponents ( bParts ) ] ;
180
+ }
181
+
182
+ function discoverProbableSymlinks ( files : ReadonlyArray < SourceFile > , getCanonicalFileName : GetCanonicalFileName , cwd : string ) : ReadonlyMap < string > {
183
+ const result = createMap < string > ( ) ;
146
184
const symlinks = mapDefined ( files , sf =>
147
185
sf . resolvedModules && firstDefinedIterator ( sf . resolvedModules . values ( ) , res =>
148
186
res && res . originalPath && res . resolvedFileName !== res . originalPath ? [ res . resolvedFileName , res . originalPath ] : undefined ) ) ;
149
- const result = createMap < string > ( ) ;
150
- if ( symlinks ) {
151
- const currentDirectory = host . getCurrentDirectory ? host . getCurrentDirectory ( ) : "" ;
152
- const compareStrings = ( ! host . useCaseSensitiveFileNames || host . useCaseSensitiveFileNames ( ) ) ? compareStringsCaseSensitive : compareStringsCaseInsensitive ;
153
- for ( const [ resolvedPath , originalPath ] of symlinks ) {
154
- const resolvedParts = getPathComponents ( toPath ( resolvedPath , currentDirectory , getCanonicalFileName ) ) ;
155
- const originalParts = getPathComponents ( toPath ( originalPath , currentDirectory , getCanonicalFileName ) ) ;
156
- while ( compareStrings ( resolvedParts [ resolvedParts . length - 1 ] , originalParts [ originalParts . length - 1 ] ) === Comparison . EqualTo ) {
157
- resolvedParts . pop ( ) ;
158
- originalParts . pop ( ) ;
159
- }
160
- result . set ( getPathFromPathComponents ( originalParts ) , getPathFromPathComponents ( resolvedParts ) ) ;
161
- }
187
+ for ( const [ resolvedPath , originalPath ] of symlinks ) {
188
+ const [ commonResolved , commonOriginal ] = guessDirectorySymlink ( resolvedPath , originalPath , cwd , getCanonicalFileName ) ;
189
+ result . set ( commonOriginal , commonResolved ) ;
162
190
}
163
191
return result ;
164
192
}
165
193
166
- function getAllModulePathsUsingIndirectSymlinks ( files : ReadonlyArray < SourceFile > , target : string , getCanonicalFileName : ( file : string ) => string , host : ModuleSpecifierResolutionHost ) {
167
- const links = discoverProbableSymlinks ( files , getCanonicalFileName , host ) ;
168
- const paths = arrayFrom ( links . keys ( ) ) ;
169
- let options : string [ ] | undefined ;
170
- const compareStrings = ( ! host . useCaseSensitiveFileNames || host . useCaseSensitiveFileNames ( ) ) ? compareStringsCaseSensitive : compareStringsCaseInsensitive ;
171
- for ( const path of paths ) {
172
- const resolved = links . get ( path ) ! ;
173
- if ( compareStrings ( target . slice ( 0 , resolved . length + 1 ) , resolved + "/" ) === Comparison . EqualTo ) {
174
- const relative = getRelativePathFromDirectory ( resolved , target , getCanonicalFileName ) ;
175
- const option = resolvePath ( path , relative ) ;
176
- if ( ! host . fileExists || host . fileExists ( option ) ) {
177
- if ( ! options ) options = [ ] ;
178
- options . push ( option ) ;
179
- }
180
- }
181
- }
182
- if ( options ) {
183
- options . push ( target ) ; // Since these are speculative, we also include the original resolved name as a possibility
184
- return options ;
185
- }
186
- return [ target ] ;
187
- }
188
-
189
194
/**
190
195
* Looks for existing imports that use symlinks to this module.
191
- * Only if no symlink is available, the real path will be used .
196
+ * Symlinks will be returned first so they are preferred over the real path .
192
197
*/
193
- function getAllModulePaths ( files : ReadonlyArray < SourceFile > , importedFileName : string , getCanonicalFileName : ( file : string ) => string , host : ModuleSpecifierResolutionHost ) : ReadonlyArray < string > {
194
- const symlinks = mapDefined ( files , sf =>
195
- sf . resolvedModules && firstDefinedIterator ( sf . resolvedModules . values ( ) , res =>
196
- res && res . resolvedFileName === importedFileName ? res . originalPath : undefined ) ) ;
198
+ function getAllModulePaths ( files : ReadonlyArray < SourceFile > , importingFileName : string , importedFileName : string , getCanonicalFileName : GetCanonicalFileName , host : ModuleSpecifierResolutionHost , redirectTargetsMap : RedirectTargetsMap ) : ReadonlyArray < string > {
199
+ const redirects = redirectTargetsMap . get ( importedFileName ) ;
200
+ const importedFileNames = redirects ? [ ...redirects , importedFileName ] : [ importedFileName ] ;
197
201
const cwd = host . getCurrentDirectory ? host . getCurrentDirectory ( ) : "" ;
198
- const baseOptions = getAllModulePathsUsingIndirectSymlinks ( files , getNormalizedAbsolutePath ( importedFileName , cwd ) , getCanonicalFileName , host ) ;
199
- if ( symlinks . length === 0 ) {
200
- return baseOptions ;
201
- }
202
- return deduplicate ( concatenate ( baseOptions , flatMap ( symlinks , importedFileName => getAllModulePathsUsingIndirectSymlinks ( files , getNormalizedAbsolutePath ( importedFileName , cwd ) , getCanonicalFileName , host ) ) ) ) ;
202
+ const targets = importedFileNames . map ( f => getNormalizedAbsolutePath ( f , cwd ) ) ;
203
+ const links = discoverProbableSymlinks ( files , getCanonicalFileName , cwd ) ;
204
+
205
+ const result : string [ ] = [ ] ;
206
+ const compareStrings = ( ! host . useCaseSensitiveFileNames || host . useCaseSensitiveFileNames ( ) ) ? compareStringsCaseSensitive : compareStringsCaseInsensitive ;
207
+ links . forEach ( ( resolved , path ) => {
208
+ if ( startsWithDirectory ( importingFileName , resolved , getCanonicalFileName ) ) {
209
+ return ; // Don't want to a package to globally import from itself
210
+ }
211
+
212
+ const target = targets . find ( t => compareStrings ( t . slice ( 0 , resolved . length + 1 ) , resolved + "/" ) === Comparison . EqualTo ) ;
213
+ if ( target === undefined ) return ;
214
+
215
+ const relative = getRelativePathFromDirectory ( resolved , target , getCanonicalFileName ) ;
216
+ const option = resolvePath ( path , relative ) ;
217
+ if ( ! host . fileExists || host . fileExists ( option ) ) {
218
+ result . push ( option ) ;
219
+ }
220
+ } ) ;
221
+ result . push ( ...targets ) ;
222
+ return result ;
203
223
}
204
224
205
225
function getRelativePathNParents ( relativePath : string ) : number {
0 commit comments