@@ -69,7 +69,7 @@ namespace ts.moduleSpecifiers {
69
69
const info = getInfo ( importingSourceFileName , host ) ;
70
70
const modulePaths = getAllModulePaths ( importingSourceFileName , nodeModulesFileName , host ) ;
71
71
return firstDefined ( modulePaths ,
72
- moduleFileName => tryGetModuleNameAsNodeModule ( moduleFileName , info , host , compilerOptions , /*packageNameOnly*/ true ) ) ;
72
+ modulePath => tryGetModuleNameAsNodeModule ( modulePath , info , host , compilerOptions , /*packageNameOnly*/ true ) ) ;
73
73
}
74
74
75
75
function getModuleSpecifierWorker (
@@ -81,7 +81,7 @@ namespace ts.moduleSpecifiers {
81
81
) : string {
82
82
const info = getInfo ( importingSourceFileName , host ) ;
83
83
const modulePaths = getAllModulePaths ( importingSourceFileName , toFileName , host ) ;
84
- return firstDefined ( modulePaths , moduleFileName => tryGetModuleNameAsNodeModule ( moduleFileName , info , host , compilerOptions ) ) ||
84
+ return firstDefined ( modulePaths , modulePath => tryGetModuleNameAsNodeModule ( modulePath , info , host , compilerOptions ) ) ||
85
85
getLocalModuleSpecifier ( toFileName , info , compilerOptions , preferences ) ;
86
86
}
87
87
@@ -101,8 +101,48 @@ namespace ts.moduleSpecifiers {
101
101
const modulePaths = getAllModulePaths ( importingSourceFile . path , moduleSourceFile . originalFileName , host ) ;
102
102
103
103
const preferences = getPreferences ( userPreferences , compilerOptions , importingSourceFile ) ;
104
- const global = mapDefined ( modulePaths , moduleFileName => tryGetModuleNameAsNodeModule ( moduleFileName , info , host , compilerOptions ) ) ;
105
- return global . length ? global : modulePaths . map ( moduleFileName => getLocalModuleSpecifier ( moduleFileName , info , compilerOptions , preferences ) ) ;
104
+ const importedFileIsInNodeModules = some ( modulePaths , p => p . isInNodeModules ) ;
105
+
106
+ // Module specifier priority:
107
+ // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules to a package.json's "types" entry
108
+ // 2. Specifiers generated using "paths" from tsconfig
109
+ // 3. Non-relative specfiers resulting from a path through node_modules (e.g. "@foo/bar/path/to/file")
110
+ // 4. Relative paths
111
+ let nodeModulesSpecifiers : string [ ] | undefined ;
112
+ let pathsSpecifiers : string [ ] | undefined ;
113
+ let relativeSpecifiers : string [ ] | undefined ;
114
+ for ( const modulePath of modulePaths ) {
115
+ const specifier = tryGetModuleNameAsNodeModule ( modulePath , info , host , compilerOptions ) ;
116
+ nodeModulesSpecifiers = append ( nodeModulesSpecifiers , specifier ) ;
117
+ if ( specifier && modulePath . isRedirect ) {
118
+ // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar",
119
+ // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking.
120
+ return nodeModulesSpecifiers ! ;
121
+ }
122
+
123
+ if ( ! specifier && ! modulePath . isRedirect ) {
124
+ const local = getLocalModuleSpecifier ( modulePath . path , info , compilerOptions , preferences ) ;
125
+ if ( pathIsBareSpecifier ( local ) ) {
126
+ pathsSpecifiers = append ( pathsSpecifiers , local ) ;
127
+ }
128
+ else if ( ! importedFileIsInNodeModules || modulePath . isInNodeModules ) {
129
+ // Why this extra conditional, not just an `else`? If some path to the file contained
130
+ // 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"),
131
+ // that means we had to go through a *sibling's* node_modules, not one we can access directly.
132
+ // If some path to the file was in node_modules but another was not, this likely indicates that
133
+ // we have a monorepo structure with symlinks. In this case, the non-node_modules path is
134
+ // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package
135
+ // in a monorepo is probably not portable. So, the module specifier we actually go with will be
136
+ // the relative path through node_modules, so that the declaration emitter can produce a
137
+ // portability error. (See declarationEmitReexportedSymlinkReference3)
138
+ relativeSpecifiers = append ( relativeSpecifiers , local ) ;
139
+ }
140
+ }
141
+ }
142
+
143
+ return pathsSpecifiers ?. length ? pathsSpecifiers :
144
+ nodeModulesSpecifiers ?. length ? nodeModulesSpecifiers :
145
+ Debug . checkDefined ( relativeSpecifiers ) ;
106
146
}
107
147
108
148
interface Info {
@@ -161,10 +201,10 @@ namespace ts.moduleSpecifiers {
161
201
return match ? match . length : 0 ;
162
202
}
163
203
164
- function comparePathsByNumberOfDirectorySeparators ( a : string , b : string ) {
165
- return compareValues (
166
- numberOfDirectorySeparators ( a ) ,
167
- numberOfDirectorySeparators ( b )
204
+ function comparePathsByRedirectAndNumberOfDirectorySeparators ( a : ModulePath , b : ModulePath ) {
205
+ return compareBooleans ( b . isRedirect , a . isRedirect ) || compareValues (
206
+ numberOfDirectorySeparators ( a . path ) ,
207
+ numberOfDirectorySeparators ( b . path )
168
208
) ;
169
209
}
170
210
@@ -173,7 +213,7 @@ namespace ts.moduleSpecifiers {
173
213
importedFileName : string ,
174
214
host : ModuleSpecifierResolutionHost ,
175
215
preferSymlinks : boolean ,
176
- cb : ( fileName : string ) => T | undefined
216
+ cb : ( fileName : string , isRedirect : boolean ) => T | undefined
177
217
) : T | undefined {
178
218
const getCanonicalFileName = hostGetCanonicalFileName ( host ) ;
179
219
const cwd = host . getCurrentDirectory ( ) ;
@@ -182,7 +222,7 @@ namespace ts.moduleSpecifiers {
182
222
const importedFileNames = [ ...( referenceRedirect ? [ referenceRedirect ] : emptyArray ) , importedFileName , ...redirects ] ;
183
223
const targets = importedFileNames . map ( f => getNormalizedAbsolutePath ( f , cwd ) ) ;
184
224
if ( ! preferSymlinks ) {
185
- const result = forEach ( targets , cb ) ;
225
+ const result = forEach ( targets , p => cb ( p , referenceRedirect === p ) ) ;
186
226
if ( result ) return result ;
187
227
}
188
228
const links = host . getSymlinkCache
@@ -197,61 +237,68 @@ namespace ts.moduleSpecifiers {
197
237
return undefined ; // Don't want to a package to globally import from itself
198
238
}
199
239
200
- const target = find ( targets , t => compareStrings ( t . slice ( 0 , resolved . real . length ) , resolved . real ) === Comparison . EqualTo ) ;
201
- if ( target === undefined ) return undefined ;
240
+ return forEach ( targets , target => {
241
+ if ( compareStrings ( target . slice ( 0 , resolved . real . length ) , resolved . real ) !== Comparison . EqualTo ) {
242
+ return ;
243
+ }
202
244
203
- const relative = getRelativePathFromDirectory ( resolved . real , target , getCanonicalFileName ) ;
204
- const option = resolvePath ( path , relative ) ;
205
- if ( ! host . fileExists || host . fileExists ( option ) ) {
206
- const result = cb ( option ) ;
207
- if ( result ) return result ;
208
- }
245
+ const relative = getRelativePathFromDirectory ( resolved . real , target , getCanonicalFileName ) ;
246
+ const option = resolvePath ( path , relative ) ;
247
+ if ( ! host . fileExists || host . fileExists ( option ) ) {
248
+ const result = cb ( option , target === referenceRedirect ) ;
249
+ if ( result ) return result ;
250
+ }
251
+ } ) ;
209
252
} ) ;
210
253
return result ||
211
- ( preferSymlinks ? forEach ( targets , cb ) : undefined ) ;
254
+ ( preferSymlinks ? forEach ( targets , p => cb ( p , p === referenceRedirect ) ) : undefined ) ;
255
+ }
256
+
257
+ interface ModulePath {
258
+ path : string ;
259
+ isInNodeModules : boolean ;
260
+ isRedirect : boolean ;
212
261
}
213
262
214
263
/**
215
264
* Looks for existing imports that use symlinks to this module.
216
265
* Symlinks will be returned first so they are preferred over the real path.
217
266
*/
218
- function getAllModulePaths ( importingFileName : string , importedFileName : string , host : ModuleSpecifierResolutionHost ) : readonly string [ ] {
267
+ function getAllModulePaths ( importingFileName : string , importedFileName : string , host : ModuleSpecifierResolutionHost ) : readonly ModulePath [ ] {
219
268
const cwd = host . getCurrentDirectory ( ) ;
220
269
const getCanonicalFileName = hostGetCanonicalFileName ( host ) ;
221
- const allFileNames = new Map < string , string > ( ) ;
270
+ const allFileNames = new Map < string , { path : string , isRedirect : boolean , isInNodeModules : boolean } > ( ) ;
222
271
let importedFileFromNodeModules = false ;
223
272
forEachFileNameOfModule (
224
273
importingFileName ,
225
274
importedFileName ,
226
275
host ,
227
276
/*preferSymlinks*/ true ,
228
- path => {
277
+ ( path , isRedirect ) => {
278
+ const isInNodeModules = pathContainsNodeModules ( path ) ;
279
+ allFileNames . set ( path , { path : getCanonicalFileName ( path ) , isRedirect, isInNodeModules } ) ;
280
+ importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules ;
229
281
// dont return value, so we collect everything
230
- allFileNames . set ( path , getCanonicalFileName ( path ) ) ;
231
- importedFileFromNodeModules = importedFileFromNodeModules || pathContainsNodeModules ( path ) ;
232
282
}
233
283
) ;
234
284
235
285
// Sort by paths closest to importing file Name directory
236
- const sortedPaths : string [ ] = [ ] ;
286
+ const sortedPaths : ModulePath [ ] = [ ] ;
237
287
for (
238
288
let directory = getDirectoryPath ( toPath ( importingFileName , cwd , getCanonicalFileName ) ) ;
239
289
allFileNames . size !== 0 ;
240
290
) {
241
291
const directoryStart = ensureTrailingDirectorySeparator ( directory ) ;
242
- let pathsInDirectory : string [ ] | undefined ;
243
- allFileNames . forEach ( ( canonicalFileName , fileName ) => {
244
- if ( startsWith ( canonicalFileName , directoryStart ) ) {
245
- // If the importedFile is from node modules, use only paths in node_modules folder as option
246
- if ( ! importedFileFromNodeModules || pathContainsNodeModules ( fileName ) ) {
247
- ( pathsInDirectory || ( pathsInDirectory = [ ] ) ) . push ( fileName ) ;
248
- }
292
+ let pathsInDirectory : ModulePath [ ] | undefined ;
293
+ allFileNames . forEach ( ( { path, isRedirect, isInNodeModules } , fileName ) => {
294
+ if ( startsWith ( path , directoryStart ) ) {
295
+ ( pathsInDirectory ||= [ ] ) . push ( { path : fileName , isRedirect, isInNodeModules } ) ;
249
296
allFileNames . delete ( fileName ) ;
250
297
}
251
298
} ) ;
252
299
if ( pathsInDirectory ) {
253
300
if ( pathsInDirectory . length > 1 ) {
254
- pathsInDirectory . sort ( comparePathsByNumberOfDirectorySeparators ) ;
301
+ pathsInDirectory . sort ( comparePathsByRedirectAndNumberOfDirectorySeparators ) ;
255
302
}
256
303
sortedPaths . push ( ...pathsInDirectory ) ;
257
304
}
@@ -261,7 +308,7 @@ namespace ts.moduleSpecifiers {
261
308
}
262
309
if ( allFileNames . size ) {
263
310
const remainingPaths = arrayFrom ( allFileNames . values ( ) ) ;
264
- if ( remainingPaths . length > 1 ) remainingPaths . sort ( comparePathsByNumberOfDirectorySeparators ) ;
311
+ if ( remainingPaths . length > 1 ) remainingPaths . sort ( comparePathsByRedirectAndNumberOfDirectorySeparators ) ;
265
312
sortedPaths . push ( ...remainingPaths ) ;
266
313
}
267
314
return sortedPaths ;
@@ -312,18 +359,19 @@ namespace ts.moduleSpecifiers {
312
359
: removeFileExtension ( relativePath ) ;
313
360
}
314
361
315
- function tryGetModuleNameAsNodeModule ( moduleFileName : string , { getCanonicalFileName, sourceDirectory } : Info , host : ModuleSpecifierResolutionHost , options : CompilerOptions , packageNameOnly ?: boolean ) : string | undefined {
362
+ function tryGetModuleNameAsNodeModule ( { path , isRedirect } : ModulePath , { getCanonicalFileName, sourceDirectory } : Info , host : ModuleSpecifierResolutionHost , options : CompilerOptions , packageNameOnly ?: boolean ) : string | undefined {
316
363
if ( ! host . fileExists || ! host . readFile ) {
317
364
return undefined ;
318
365
}
319
- const parts : NodeModulePathParts = getNodeModulePathParts ( moduleFileName ) ! ;
366
+ const parts : NodeModulePathParts = getNodeModulePathParts ( path ) ! ;
320
367
if ( ! parts ) {
321
368
return undefined ;
322
369
}
323
370
324
371
// Simplify the full file path to something that can be resolved by Node.
325
372
326
- let moduleSpecifier = moduleFileName ;
373
+ let moduleSpecifier = path ;
374
+ let isPackageRootPath = false ;
327
375
if ( ! packageNameOnly ) {
328
376
let packageRootIndex = parts . packageRootIndex ;
329
377
let moduleFileNameForExtensionless : string | undefined ;
@@ -332,19 +380,24 @@ namespace ts.moduleSpecifiers {
332
380
const { moduleFileToTry, packageRootPath } = tryDirectoryWithPackageJson ( packageRootIndex ) ;
333
381
if ( packageRootPath ) {
334
382
moduleSpecifier = packageRootPath ;
383
+ isPackageRootPath = true ;
335
384
break ;
336
385
}
337
386
if ( ! moduleFileNameForExtensionless ) moduleFileNameForExtensionless = moduleFileToTry ;
338
387
339
388
// try with next level of directory
340
- packageRootIndex = moduleFileName . indexOf ( directorySeparator , packageRootIndex + 1 ) ;
389
+ packageRootIndex = path . indexOf ( directorySeparator , packageRootIndex + 1 ) ;
341
390
if ( packageRootIndex === - 1 ) {
342
391
moduleSpecifier = getExtensionlessFileName ( moduleFileNameForExtensionless ) ;
343
392
break ;
344
393
}
345
394
}
346
395
}
347
396
397
+ if ( isRedirect && ! isPackageRootPath ) {
398
+ return undefined ;
399
+ }
400
+
348
401
const globalTypingsCacheLocation = host . getGlobalTypingsCacheLocation && host . getGlobalTypingsCacheLocation ( ) ;
349
402
// Get a path that's relative to node_modules or the importing file's path
350
403
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
@@ -360,16 +413,16 @@ namespace ts.moduleSpecifiers {
360
413
return getEmitModuleResolutionKind ( options ) !== ModuleResolutionKind . NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName ;
361
414
362
415
function tryDirectoryWithPackageJson ( packageRootIndex : number ) {
363
- const packageRootPath = moduleFileName . substring ( 0 , packageRootIndex ) ;
416
+ const packageRootPath = path . substring ( 0 , packageRootIndex ) ;
364
417
const packageJsonPath = combinePaths ( packageRootPath , "package.json" ) ;
365
- let moduleFileToTry = moduleFileName ;
418
+ let moduleFileToTry = path ;
366
419
if ( host . fileExists ( packageJsonPath ) ) {
367
420
const packageJsonContent = JSON . parse ( host . readFile ! ( packageJsonPath ) ! ) ;
368
421
const versionPaths = packageJsonContent . typesVersions
369
422
? getPackageJsonTypesVersionsPaths ( packageJsonContent . typesVersions )
370
423
: undefined ;
371
424
if ( versionPaths ) {
372
- const subModuleName = moduleFileName . slice ( packageRootPath . length + 1 ) ;
425
+ const subModuleName = path . slice ( packageRootPath . length + 1 ) ;
373
426
const fromPaths = tryGetModuleNameFromPaths (
374
427
removeFileExtension ( subModuleName ) ,
375
428
removeExtensionAndIndexPostFix ( subModuleName , Ending . Minimal , options ) ,
0 commit comments