1import { 2 __String, 3 allKeysStartWithDot, 4 AmbientModuleDeclaration, 5 append, 6 arrayFrom, 7 CharacterCodes, 8 combinePaths, 9 compareBooleans, 10 compareNumberOfDirectorySeparators, 11 comparePaths, 12 Comparison, 13 CompilerOptions, 14 containsIgnoredPath, 15 containsPath, 16 createGetCanonicalFileName, 17 Debug, 18 directorySeparator, 19 emptyArray, 20 endsWith, 21 ensurePathIsNonModuleName, 22 ensureTrailingDirectorySeparator, 23 every, 24 ExportAssignment, 25 Extension, 26 extensionFromPath, 27 fileExtensionIsOneOf, 28 FileIncludeKind, 29 firstDefined, 30 flatMap, 31 flatten, 32 forEach, 33 forEachAncestorDirectory, 34 GetCanonicalFileName, 35 getDirectoryPath, 36 getEmitModuleResolutionKind, 37 getImpliedNodeFormatForFile, 38 getModeForResolutionAtIndex, 39 getModuleNameStringLiteralAt, 40 getNodeModulePathParts, 41 getNormalizedAbsolutePath, 42 getOwnKeys, 43 getPackageJsonByPMType, 44 getPackageJsonTypesVersionsPaths, 45 getPackageNameFromTypesPackageName, 46 getPathsBasePath, 47 getRelativePathFromDirectory, 48 getRelativePathToDirectoryOrUrl, 49 getSourceFileOfModule, 50 getSupportedExtensions, 51 getTextOfIdentifierOrLiteral, 52 hasJSFileExtension, 53 hasTSFileExtension, 54 hostGetCanonicalFileName, 55 Identifier, 56 isAmbientModule, 57 isApplicableVersionedTypesKey, 58 isExternalModuleAugmentation, 59 isExternalModuleNameRelative, 60 isModuleBlock, 61 isModuleDeclaration, 62 isNonGlobalAmbientModule, 63 isOhpm, 64 isRootedDiskPath, 65 isSourceFile, 66 isString, 67 JsxEmit, 68 map, 69 Map, 70 mapDefined, 71 MapLike, 72 matchPatternOrExact, 73 min, 74 ModuleDeclaration, 75 ModuleKind, 76 ModulePath, 77 ModuleResolutionHost, 78 ModuleResolutionKind, 79 ModuleSpecifierCache, 80 ModuleSpecifierOptions, 81 ModuleSpecifierResolutionHost, 82 NodeFlags, 83 NodeModulePathParts, 84 nodeModulesPathPart, 85 normalizePath, 86 ohModulesPathPart, 87 Path, 88 pathContainsNodeModules, 89 pathContainsOHModules, 90 pathIsBareSpecifier, 91 pathIsRelative, 92 PropertyAccessExpression, 93 removeFileExtension, 94 removeSuffix, 95 resolvePath, 96 ScriptKind, 97 some, 98 SourceFile, 99 startsWith, 100 startsWithDirectory, 101 stringContains, 102 StringLiteral, 103 Symbol, 104 SymbolFlags, 105 toPath, 106 tryGetExtensionFromPath, 107 tryParsePatterns, 108 TypeChecker, 109 UserPreferences, 110} from "./_namespaces/ts"; 111 112// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. 113 114const enum RelativePreference { Relative, NonRelative, Shortest, ExternalNonRelative } 115// See UserPreferences#importPathEnding 116const enum Ending { Minimal, Index, JsExtension } 117 118// Processed preferences 119interface Preferences { 120 readonly relativePreference: RelativePreference; 121 readonly ending: Ending; 122} 123 124function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { 125 return { 126 relativePreference: 127 importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : 128 importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : 129 importModuleSpecifierPreference === "project-relative" ? RelativePreference.ExternalNonRelative : 130 RelativePreference.Shortest, 131 ending: getEnding(), 132 }; 133 function getEnding(): Ending { 134 switch (importModuleSpecifierEnding) { 135 case "minimal": return Ending.Minimal; 136 case "index": return Ending.Index; 137 case "js": return Ending.JsExtension; 138 default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension 139 : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; 140 } 141 } 142} 143 144function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { 145 return { 146 relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, 147 ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? 148 Ending.JsExtension : 149 getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, 150 }; 151} 152 153function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { 154 if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 155 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { 156 return false; 157 } 158 return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; 159} 160 161function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { 162 return { 163 fileExists: host.fileExists, 164 readFile: Debug.checkDefined(host.readFile), 165 directoryExists: host.directoryExists, 166 getCurrentDirectory: host.getCurrentDirectory, 167 realpath: host.realpath, 168 useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), 169 }; 170} 171 172// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? 173// Because when this is called by the file renamer, `importingSourceFile` is the file being renamed, 174// while `importingSourceFileName` its *new* name. We need a source file just to get its 175// `impliedNodeFormat` and to detect certain preferences from existing import module specifiers. 176/** @internal */ 177export function updateModuleSpecifier( 178 compilerOptions: CompilerOptions, 179 importingSourceFile: SourceFile, 180 importingSourceFileName: Path, 181 toFileName: string, 182 host: ModuleSpecifierResolutionHost, 183 oldImportSpecifier: string, 184 options: ModuleSpecifierOptions = {}, 185): string | undefined { 186 const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}, options); 187 if (res === oldImportSpecifier) return undefined; 188 return res; 189} 190 191// `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? 192// Because when this is called by the declaration emitter, `importingSourceFile` is the implementation 193// file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the 194// one currently being produced; the latter to the one being imported). We need an implementation file 195// just to get its `impliedNodeFormat` and to detect certain preferences from existing import module 196// specifiers. 197/** @internal */ 198export function getModuleSpecifier( 199 compilerOptions: CompilerOptions, 200 importingSourceFile: SourceFile, 201 importingSourceFileName: Path, 202 toFileName: string, 203 host: ModuleSpecifierResolutionHost, 204 options: ModuleSpecifierOptions = {}, 205): string { 206 return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}, options); 207} 208 209/** @internal */ 210export function getNodeModulesPackageName( 211 compilerOptions: CompilerOptions, 212 importingSourceFile: SourceFile, 213 nodeModulesFileName: string, 214 host: ModuleSpecifierResolutionHost, 215 preferences: UserPreferences, 216 options: ModuleSpecifierOptions = {}, 217): string | undefined { 218 const info = getInfo(importingSourceFile.path, host); 219 const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences, options, compilerOptions.packageManagerType); 220 return firstDefined(modulePaths, 221 modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, preferences, /*packageNameOnly*/ true, options.overrideImportMode)); 222} 223 224function getModuleSpecifierWorker( 225 compilerOptions: CompilerOptions, 226 importingSourceFile: SourceFile, 227 importingSourceFileName: Path, 228 toFileName: string, 229 host: ModuleSpecifierResolutionHost, 230 preferences: Preferences, 231 userPreferences: UserPreferences, 232 options: ModuleSpecifierOptions = {} 233): string { 234 const info = getInfo(importingSourceFileName, host); 235 const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences, options, compilerOptions.packageManagerType); 236 return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode)) || 237 getLocalModuleSpecifier(toFileName, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); 238} 239 240/** @internal */ 241export function tryGetModuleSpecifiersFromCache( 242 moduleSymbol: Symbol, 243 importingSourceFile: SourceFile, 244 host: ModuleSpecifierResolutionHost, 245 userPreferences: UserPreferences, 246 options: ModuleSpecifierOptions = {}, 247): readonly string[] | undefined { 248 return tryGetModuleSpecifiersFromCacheWorker( 249 moduleSymbol, 250 importingSourceFile, 251 host, 252 userPreferences, 253 options)[0]; 254} 255 256function tryGetModuleSpecifiersFromCacheWorker( 257 moduleSymbol: Symbol, 258 importingSourceFile: SourceFile, 259 host: ModuleSpecifierResolutionHost, 260 userPreferences: UserPreferences, 261 options: ModuleSpecifierOptions = {}, 262): readonly [specifiers?: readonly string[], moduleFile?: SourceFile, modulePaths?: readonly ModulePath[], cache?: ModuleSpecifierCache] { 263 const moduleSourceFile = getSourceFileOfModule(moduleSymbol); 264 if (!moduleSourceFile) { 265 return emptyArray as []; 266 } 267 268 const cache = host.getModuleSpecifierCache?.(); 269 const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options); 270 return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache]; 271} 272 273/** 274 * Returns an import for each symlink and for the realpath. 275 * 276 * @internal 277 */ 278export function getModuleSpecifiers( 279 moduleSymbol: Symbol, 280 checker: TypeChecker, 281 compilerOptions: CompilerOptions, 282 importingSourceFile: SourceFile, 283 host: ModuleSpecifierResolutionHost, 284 userPreferences: UserPreferences, 285 options: ModuleSpecifierOptions = {}, 286): readonly string[] { 287 return getModuleSpecifiersWithCacheInfo( 288 moduleSymbol, 289 checker, 290 compilerOptions, 291 importingSourceFile, 292 host, 293 userPreferences, 294 options 295 ).moduleSpecifiers; 296} 297 298/** @internal */ 299export function getModuleSpecifiersWithCacheInfo( 300 moduleSymbol: Symbol, 301 checker: TypeChecker, 302 compilerOptions: CompilerOptions, 303 importingSourceFile: SourceFile, 304 host: ModuleSpecifierResolutionHost, 305 userPreferences: UserPreferences, 306 options: ModuleSpecifierOptions = {}, 307): { moduleSpecifiers: readonly string[], computedWithoutCache: boolean } { 308 let computedWithoutCache = false; 309 const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker); 310 if (ambient) return { moduleSpecifiers: [ambient], computedWithoutCache }; 311 312 // eslint-disable-next-line prefer-const 313 let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker( 314 moduleSymbol, 315 importingSourceFile, 316 host, 317 userPreferences, 318 options 319 ); 320 if (specifiers) return { moduleSpecifiers: specifiers, computedWithoutCache }; 321 if (!moduleSourceFile) return { moduleSpecifiers: emptyArray, computedWithoutCache }; 322 323 computedWithoutCache = true; 324 modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host, compilerOptions.packageManagerType); 325 const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences, options); 326 cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result); 327 return { moduleSpecifiers: result, computedWithoutCache }; 328} 329 330function computeModuleSpecifiers( 331 modulePaths: readonly ModulePath[], 332 compilerOptions: CompilerOptions, 333 importingSourceFile: SourceFile, 334 host: ModuleSpecifierResolutionHost, 335 userPreferences: UserPreferences, 336 options: ModuleSpecifierOptions = {}, 337): readonly string[] { 338 const info = getInfo(importingSourceFile.path, host); 339 const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); 340 const existingSpecifier = forEach(modulePaths, modulePath => forEach( 341 host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), 342 reason => { 343 if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined; 344 // If the candidate import mode doesn't match the mode we're generating for, don't consider it 345 // TODO: maybe useful to keep around as an alternative option for certain contexts where the mode is overridable 346 if (importingSourceFile.impliedNodeFormat && importingSourceFile.impliedNodeFormat !== getModeForResolutionAtIndex(importingSourceFile, reason.index)) return undefined; 347 const specifier = getModuleNameStringLiteralAt(importingSourceFile, reason.index).text; 348 // If the preference is for non relative and the module specifier is relative, ignore it 349 return preferences.relativePreference !== RelativePreference.NonRelative || !pathIsRelative(specifier) ? 350 specifier : 351 undefined; 352 } 353 )); 354 if (existingSpecifier) { 355 const moduleSpecifiers = [existingSpecifier]; 356 return moduleSpecifiers; 357 } 358 359 const importedFileIsInNodeModules = some(modulePaths, p => p.isInNodeModules); 360 361 // Module specifier priority: 362 // 1. "Bare package specifiers" (e.g. "@foo/bar") resulting from a path through node_modules or oh_modules to a package.json's or oh-package.json5's "types" entry 363 // 2. Specifiers generated using "paths" from tsconfig 364 // 3. Non-relative specfiers resulting from a path through node_modules or oh_modules(e.g. "@foo/bar/path/to/file") 365 // 4. Relative paths 366 let modulesSpecifiers: string[] | undefined; 367 let pathsSpecifiers: string[] | undefined; 368 let relativeSpecifiers: string[] | undefined; 369 for (const modulePath of modulePaths) { 370 const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, userPreferences, /*packageNameOnly*/ undefined, options.overrideImportMode); 371 modulesSpecifiers = append(modulesSpecifiers, specifier); 372 if (specifier && modulePath.isRedirect) { 373 // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", 374 // not "@foo/bar/path/to/file"). No other specifier will be this good, so stop looking. 375 return modulesSpecifiers!; 376 } 377 378 if (!specifier && !modulePath.isRedirect) { 379 const local = getLocalModuleSpecifier(modulePath.path, info, compilerOptions, host, options.overrideImportMode || importingSourceFile.impliedNodeFormat, preferences); 380 if (pathIsBareSpecifier(local)) { 381 pathsSpecifiers = append(pathsSpecifiers, local); 382 } 383 else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) { 384 // Why this extra conditional, not just an `else`? If some path to the file contained 385 // 'node_modules' or 'oh_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"), 386 // that means we had to go through a *sibling's* node_modules or oh_modules, not one we can access directly. 387 // If some path to the file was in node_modules or oh_modules but another was not, this likely indicates that 388 // we have a monorepo structure with symlinks. In this case, the non-node_modules or oh_modules path is 389 // probably the realpath, e.g. "../bar/path/to/file", but a relative path to another package 390 // in a monorepo is probably not portable. So, the module specifier we actually go with will be 391 // the relative path through node_modules or oh_modules, so that the declaration emitter can produce a 392 // portability error. (See declarationEmitReexportedSymlinkReference3) 393 relativeSpecifiers = append(relativeSpecifiers, local); 394 } 395 } 396 } 397 398 return pathsSpecifiers?.length ? pathsSpecifiers : 399 modulesSpecifiers?.length ? modulesSpecifiers : 400 Debug.checkDefined(relativeSpecifiers); 401} 402 403interface Info { 404 readonly getCanonicalFileName: GetCanonicalFileName; 405 readonly importingSourceFileName: Path 406 readonly sourceDirectory: Path; 407} 408// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path 409function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info { 410 const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true); 411 const sourceDirectory = getDirectoryPath(importingSourceFileName); 412 return { getCanonicalFileName, importingSourceFileName, sourceDirectory }; 413} 414 415function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOptions: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: SourceFile["impliedNodeFormat"], { ending, relativePreference }: Preferences): string { 416 const { baseUrl, paths, rootDirs } = compilerOptions; 417 const { sourceDirectory, getCanonicalFileName } = info; 418 const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, ending, compilerOptions) || 419 removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions); 420 if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { 421 return relativePath; 422 } 423 424 const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); 425 const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName); 426 if (!relativeToBaseUrl) { 427 return relativePath; 428 } 429 430 const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, getAllowedEndings(ending, compilerOptions, importMode), host, compilerOptions); 431 const nonRelative = fromPaths === undefined && baseUrl !== undefined ? removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) : fromPaths; 432 if (!nonRelative) { 433 return relativePath; 434 } 435 436 if (relativePreference === RelativePreference.NonRelative) { 437 return nonRelative; 438 } 439 440 if (relativePreference === RelativePreference.ExternalNonRelative) { 441 const projectDirectory = compilerOptions.configFilePath ? 442 toPath(getDirectoryPath(compilerOptions.configFilePath), host.getCurrentDirectory(), info.getCanonicalFileName) : 443 info.getCanonicalFileName(host.getCurrentDirectory()); 444 const modulePath = toPath(moduleFileName, projectDirectory, getCanonicalFileName); 445 const sourceIsInternal = startsWith(sourceDirectory, projectDirectory); 446 const targetIsInternal = startsWith(modulePath, projectDirectory); 447 if (sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal) { 448 // 1. The import path crosses the boundary of the tsconfig.json-containing directory. 449 // 450 // src/ 451 // tsconfig.json 452 // index.ts ------- 453 // lib/ | (path crosses tsconfig.json) 454 // imported.ts <--- 455 // 456 return nonRelative; 457 } 458 const packageManagerType = compilerOptions.packageManagerType; 459 const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson(host, getDirectoryPath(modulePath), packageManagerType); 460 const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory, packageManagerType); 461 if (nearestSourcePackageJson !== nearestTargetPackageJson) { 462 // 2. The importing and imported files are part of different packages. 463 // 464 // packages/a/ 465 // package.json 466 // index.ts -------- 467 // packages/b/ | (path crosses package.json) 468 // package.json | 469 // component.ts <--- 470 // 471 return nonRelative; 472 } 473 474 return relativePath; 475 } 476 477 if (relativePreference !== RelativePreference.Shortest) Debug.assertNever(relativePreference); 478 479 // Prefer a relative import over a baseUrl import if it has fewer components. 480 return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative; 481} 482 483/** @internal */ 484export function countPathComponents(path: string): number { 485 let count = 0; 486 for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) { 487 if (path.charCodeAt(i) === CharacterCodes.slash) count++; 488 } 489 return count; 490} 491 492function usesJsExtensionOnImports({ imports }: SourceFile): boolean { 493 return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false; 494} 495 496function comparePathsByRedirectAndNumberOfDirectorySeparators(a: ModulePath, b: ModulePath) { 497 return compareBooleans(b.isRedirect, a.isRedirect) || compareNumberOfDirectorySeparators(a.path, b.path); 498} 499 500function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolutionHost, fileName: string, packageManagerType?: string) { 501 if (host.getNearestAncestorDirectoryWithPackageJson) { 502 return host.getNearestAncestorDirectoryWithPackageJson(fileName); 503 } 504 return !!forEachAncestorDirectory(fileName, directory => { 505 return host.fileExists(combinePaths(directory, getPackageJsonByPMType(packageManagerType))) ? true : undefined; 506 }); 507} 508 509/** @internal */ 510export function forEachFileNameOfModule<T>( 511 importingFileName: string, 512 importedFileName: string, 513 host: ModuleSpecifierResolutionHost, 514 preferSymlinks: boolean, 515 cb: (fileName: string, isRedirect: boolean) => T | undefined 516): T | undefined { 517 const getCanonicalFileName = hostGetCanonicalFileName(host); 518 const cwd = host.getCurrentDirectory(); 519 const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined; 520 const importedPath = toPath(importedFileName, cwd, getCanonicalFileName); 521 const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray; 522 const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; 523 const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); 524 let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath); 525 526 if (!preferSymlinks) { 527 // Symlinks inside ignored paths are already filtered out of the symlink cache, 528 // so we only need to remove them from the realpath filenames. 529 const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p)); 530 if (result) return result; 531 } 532 533 const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath(); 534 const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd); 535 const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => { 536 const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName))); 537 if (!symlinkDirectories) return undefined; // Continue to ancestor directory 538 539 // Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts) 540 if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) { 541 return false; // Stop search, each ancestor directory will also hit this condition 542 } 543 544 return forEach(targets, target => { 545 if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) { 546 return; 547 } 548 549 const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName); 550 for (const symlinkDirectory of symlinkDirectories) { 551 const option = resolvePath(symlinkDirectory, relative); 552 const result = cb(option, target === referenceRedirect); 553 shouldFilterIgnoredPaths = true; // We found a non-ignored path in symlinks, so we can reject ignored-path realpaths 554 if (result) return result; 555 } 556 }); 557 }); 558 return result || (preferSymlinks 559 ? forEach(targets, p => shouldFilterIgnoredPaths && containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) 560 : undefined); 561} 562 563/** 564 * Looks for existing imports that use symlinks to this module. 565 * Symlinks will be returned first so they are preferred over the real path. 566 */ 567function getAllModulePaths( 568 importingFilePath: Path, 569 importedFileName: string, 570 host: ModuleSpecifierResolutionHost, 571 preferences: UserPreferences, 572 options: ModuleSpecifierOptions = {}, 573 packageManagerType?: string 574) { 575 const importedFilePath = toPath(importedFileName, host.getCurrentDirectory(), hostGetCanonicalFileName(host)); 576 const cache = host.getModuleSpecifierCache?.(); 577 if (cache) { 578 const cached = cache.get(importingFilePath, importedFilePath, preferences, options); 579 if (cached?.modulePaths) return cached.modulePaths; 580 } 581 const modulePaths = getAllModulePathsWorker(importingFilePath, importedFileName, host, packageManagerType); 582 if (cache) { 583 cache.setModulePaths(importingFilePath, importedFilePath, preferences, options, modulePaths); 584 } 585 return modulePaths; 586} 587 588function getAllModulePathsWorker(importingFileName: Path, importedFileName: string, host: ModuleSpecifierResolutionHost, packageManagerType?: string): readonly ModulePath[] { 589 const getCanonicalFileName = hostGetCanonicalFileName(host); 590 const allFileNames = new Map<string, { path: string, isRedirect: boolean, isInNodeModules: boolean }>(); 591 let importedFileFromNodeModules = false; 592 forEachFileNameOfModule( 593 importingFileName, 594 importedFileName, 595 host, 596 /*preferSymlinks*/ true, 597 (path, isRedirect) => { 598 const isInNodeModules = isOhpm(packageManagerType) ? pathContainsOHModules(path) : pathContainsNodeModules(path); 599 allFileNames.set(path, { path: getCanonicalFileName(path), isRedirect, isInNodeModules }); 600 importedFileFromNodeModules = importedFileFromNodeModules || isInNodeModules; 601 // don't return value, so we collect everything 602 } 603 ); 604 605 // Sort by paths closest to importing file Name directory 606 const sortedPaths: ModulePath[] = []; 607 for ( 608 let directory = getDirectoryPath(importingFileName); 609 allFileNames.size !== 0; 610 ) { 611 const directoryStart = ensureTrailingDirectorySeparator(directory); 612 let pathsInDirectory: ModulePath[] | undefined; 613 allFileNames.forEach(({ path, isRedirect, isInNodeModules }, fileName) => { 614 if (startsWith(path, directoryStart)) { 615 (pathsInDirectory ||= []).push({ path: fileName, isRedirect, isInNodeModules }); 616 allFileNames.delete(fileName); 617 } 618 }); 619 if (pathsInDirectory) { 620 if (pathsInDirectory.length > 1) { 621 pathsInDirectory.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); 622 } 623 sortedPaths.push(...pathsInDirectory); 624 } 625 const newDirectory = getDirectoryPath(directory); 626 if (newDirectory === directory) break; 627 directory = newDirectory; 628 } 629 if (allFileNames.size) { 630 const remainingPaths = arrayFrom(allFileNames.values()); 631 if (remainingPaths.length > 1) remainingPaths.sort(comparePathsByRedirectAndNumberOfDirectorySeparators); 632 sortedPaths.push(...remainingPaths); 633 } 634 635 return sortedPaths; 636} 637 638function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol, checker: TypeChecker): string | undefined { 639 const decl = moduleSymbol.declarations?.find( 640 d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name))) 641 ) as (ModuleDeclaration & { name: StringLiteral }) | undefined; 642 if (decl) { 643 return decl.name.text; 644 } 645 646 // the module could be a namespace, which is export through "export=" from an ambient module. 647 /** 648 * declare module "m" { 649 * namespace ns { 650 * class c {} 651 * } 652 * export = ns; 653 * } 654 */ 655 // `import {c} from "m";` is valid, in which case, `moduleSymbol` is "ns", but the module name should be "m" 656 const ambientModuleDeclareCandidates = mapDefined(moduleSymbol.declarations, 657 d => { 658 if (!isModuleDeclaration(d)) return; 659 const topNamespace = getTopNamespace(d); 660 if (!(topNamespace?.parent?.parent 661 && isModuleBlock(topNamespace.parent) && isAmbientModule(topNamespace.parent.parent) && isSourceFile(topNamespace.parent.parent.parent))) return; 662 const exportAssignment = ((topNamespace.parent.parent.symbol.exports?.get("export=" as __String)?.valueDeclaration as ExportAssignment)?.expression as PropertyAccessExpression | Identifier); 663 if (!exportAssignment) return; 664 const exportSymbol = checker.getSymbolAtLocation(exportAssignment); 665 if (!exportSymbol) return; 666 const originalExportSymbol = exportSymbol?.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol; 667 if (originalExportSymbol === d.symbol) return topNamespace.parent.parent; 668 669 function getTopNamespace(namespaceDeclaration: ModuleDeclaration) { 670 while (namespaceDeclaration.flags & NodeFlags.NestedNamespace) { 671 namespaceDeclaration = namespaceDeclaration.parent as ModuleDeclaration; 672 } 673 return namespaceDeclaration; 674 } 675 } 676 ); 677 const ambientModuleDeclare = ambientModuleDeclareCandidates[0] as (AmbientModuleDeclaration & { name: StringLiteral }) | undefined; 678 if (ambientModuleDeclare) { 679 return ambientModuleDeclare.name.text; 680 } 681} 682 683function getAllowedEndings(preferredEnding: Ending, compilerOptions: CompilerOptions, importMode: SourceFile["impliedNodeFormat"]) { 684 if (getEmitModuleResolutionKind(compilerOptions) >= ModuleResolutionKind.Node16 && importMode === ModuleKind.ESNext) { 685 return [Ending.JsExtension]; 686 } 687 switch (preferredEnding) { 688 case Ending.JsExtension: return [Ending.JsExtension, Ending.Minimal, Ending.Index]; 689 case Ending.Index: return [Ending.Index, Ending.Minimal, Ending.JsExtension]; 690 case Ending.Minimal: return [Ending.Minimal, Ending.Index, Ending.JsExtension]; 691 default: Debug.assertNever(preferredEnding); 692 } 693} 694 695function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<readonly string[]>, allowedEndings: Ending[], host: ModuleSpecifierResolutionHost, compilerOptions: CompilerOptions): string | undefined { 696 for (const key in paths) { 697 for (const patternText of paths[key]) { 698 const pattern = normalizePath(patternText); 699 const indexOfStar = pattern.indexOf("*"); 700 // In module resolution, if `pattern` itself has an extension, a file with that extension is looked up directly, 701 // meaning a '.ts' or '.d.ts' extension is allowed to resolve. This is distinct from the case where a '*' substitution 702 // causes a module specifier to have an extension, i.e. the extension comes from the module specifier in a JS/TS file 703 // and matches the '*'. For example: 704 // 705 // Module Specifier | Path Mapping (key: [pattern]) | Interpolation | Resolution Action 706 // ---------------------->------------------------------->--------------------->--------------------------------------------------------------- 707 // import "@app/foo" -> "@app/*": ["./src/app/*.ts"] -> "./src/app/foo.ts" -> tryFile("./src/app/foo.ts") || [continue resolution algorithm] 708 // import "@app/foo.ts" -> "@app/*": ["./src/app/*"] -> "./src/app/foo.ts" -> [continue resolution algorithm] 709 // 710 // (https://github.com/microsoft/TypeScript/blob/ad4ded80e1d58f0bf36ac16bea71bc10d9f09895/src/compiler/moduleNameResolver.ts#L2509-L2516) 711 // 712 // The interpolation produced by both scenarios is identical, but only in the former, where the extension is encoded in 713 // the path mapping rather than in the module specifier, will we prioritize a file lookup on the interpolation result. 714 // (In fact, currently, the latter scenario will necessarily fail since no resolution mode recognizes '.ts' as a valid 715 // extension for a module specifier.) 716 // 717 // Here, this means we need to be careful about whether we generate a match from the target filename (typically with a 718 // .ts extension) or the possible relative module specifiers representing that file: 719 // 720 // Filename | Relative Module Specifier Candidates | Path Mapping | Filename Result | Module Specifier Results 721 // --------------------<----------------------------------------------<------------------------------<-------------------||---------------------------- 722 // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*.d.ts"] <- @app/haha || (none) 723 // dist/haha.d.ts <- dist/haha, dist/haha.js <- "@app/*": ["./dist/*"] <- (none) || @app/haha, @app/haha.js 724 // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*.d.ts"] <- @app/foo/index || (none) 725 // dist/foo/index.d.ts <- dist/foo, dist/foo/index, dist/foo/index.js <- "@app/*": ["./dist/*"] <- (none) || @app/foo, @app/foo/index, @app/foo/index.js 726 // dist/wow.js.js <- dist/wow.js, dist/wow.js.js <- "@app/*": ["./dist/*.js"] <- @app/wow.js || @app/wow, @app/wow.js 727 // 728 // The "Filename Result" can be generated only if `pattern` has an extension. Care must be taken that the list of 729 // relative module specifiers to run the interpolation (a) is actually valid for the module resolution mode, (b) takes 730 // into account the existence of other files (e.g. 'dist/wow.js' cannot refer to 'dist/wow.js.js' if 'dist/wow.js' 731 // exists) and (c) that they are ordered by preference. The last row shows that the filename result and module 732 // specifier results are not mutually exclusive. Note that the filename result is a higher priority in module 733 // resolution, but as long criteria (b) above is met, I don't think its result needs to be the highest priority result 734 // in module specifier generation. I have included it last, as it's difficult to tell exactly where it should be 735 // sorted among the others for a particular value of `importModuleSpecifierEnding`. 736 const candidates: { ending: Ending | undefined, value: string }[] = allowedEndings.map(ending => ({ 737 ending, 738 value: removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions) 739 })); 740 if (tryGetExtensionFromPath(pattern)) { 741 candidates.push({ ending: undefined, value: relativeToBaseUrl }); 742 } 743 744 if (indexOfStar !== -1) { 745 const prefix = pattern.substring(0, indexOfStar); 746 const suffix = pattern.substring(indexOfStar + 1); 747 for (const { ending, value } of candidates) { 748 if (value.length >= prefix.length + suffix.length && 749 startsWith(value, prefix) && 750 endsWith(value, suffix) && 751 validateEnding({ ending, value }) 752 ) { 753 const matchedStar = value.substring(prefix.length, value.length - suffix.length); 754 return key.replace("*", matchedStar); 755 } 756 } 757 } 758 else if ( 759 some(candidates, c => c.ending !== Ending.Minimal && pattern === c.value) || 760 some(candidates, c => c.ending === Ending.Minimal && pattern === c.value && validateEnding(c)) 761 ) { 762 return key; 763 } 764 } 765 } 766 767 function validateEnding({ ending, value }: { ending: Ending | undefined, value: string }) { 768 // Optimization: `removeExtensionAndIndexPostFix` can query the file system (a good bit) if `ending` is `Minimal`, the basename 769 // is 'index', and a `host` is provided. To avoid that until it's unavoidable, we ran the function with no `host` above. Only 770 // here, after we've checked that the minimal ending is indeed a match (via the length and prefix/suffix checks / `some` calls), 771 // do we check that the host-validated result is consistent with the answer we got before. If it's not, it falls back to the 772 // `Ending.Index` result, which should already be in the list of candidates if `Minimal` was. (Note: the assumption here is 773 // that every module resolution mode that supports dropping extensions also supports dropping `/index`. Like literally 774 // everything else in this file, this logic needs to be updated if that's not true in some future module resolution mode.) 775 return ending !== Ending.Minimal || value === removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions, host); 776 } 777} 778 779const enum MatchingMode { 780 Exact, 781 Directory, 782 Pattern 783} 784 785function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { 786 if (typeof exports === "string") { 787 const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); 788 const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; 789 switch (mode) { 790 case MatchingMode.Exact: 791 if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { 792 return { moduleFileToTry: packageName }; 793 } 794 break; 795 case MatchingMode.Directory: 796 if (containsPath(pathOrPattern, targetFilePath)) { 797 const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); 798 return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; 799 } 800 break; 801 case MatchingMode.Pattern: 802 const starPos = pathOrPattern.indexOf("*"); 803 const leadingSlice = pathOrPattern.slice(0, starPos); 804 const trailingSlice = pathOrPattern.slice(starPos + 1); 805 if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { 806 const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); 807 return { moduleFileToTry: packageName.replace("*", starReplacement) }; 808 } 809 if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { 810 const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); 811 return { moduleFileToTry: packageName.replace("*", starReplacement) }; 812 } 813 break; 814 } 815 } 816 else if (Array.isArray(exports)) { 817 return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); 818 } 819 else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null 820 if (allKeysStartWithDot(exports as MapLike<unknown>)) { 821 // sub-mappings 822 // 3 cases: 823 // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) 824 // * pattern mappings (contains a *) 825 // * exact mappings (no *, does not end with /) 826 return forEach(getOwnKeys(exports as MapLike<unknown>), k => { 827 const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); 828 const mode = endsWith(k, "/") ? MatchingMode.Directory 829 : stringContains(k, "*") ? MatchingMode.Pattern 830 : MatchingMode.Exact; 831 return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike<unknown>)[k], conditions, mode); 832 }); 833 } 834 else { 835 // conditional mapping 836 for (const key of getOwnKeys(exports as MapLike<unknown>)) { 837 if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { 838 const subTarget = (exports as MapLike<unknown>)[key]; 839 const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); 840 if (result) { 841 return result; 842 } 843 } 844 } 845 } 846 } 847 return undefined; 848} 849 850function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { 851 const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); 852 if (normalizedTargetPaths === undefined) { 853 return undefined; 854 } 855 856 const normalizedSourcePaths = getPathsRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName); 857 const relativePaths = flatMap(normalizedSourcePaths, sourcePath => { 858 return map(normalizedTargetPaths, targetPath => ensurePathIsNonModuleName(getRelativePathFromDirectory(sourcePath, targetPath, getCanonicalFileName))); 859 }); 860 const shortest = min(relativePaths, compareNumberOfDirectorySeparators); 861 if (!shortest) { 862 return undefined; 863 } 864 865 return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs 866 ? removeExtensionAndIndexPostFix(shortest, ending, compilerOptions) 867 : removeFileExtension(shortest); 868} 869 870function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ModuleKind.ESNext | ModuleKind.CommonJS): string | undefined { 871 if (!host.fileExists || !host.readFile) { 872 return undefined; 873 } 874 const parts: NodeModulePathParts = getNodeModulePathParts(path, isOhpm(options.packageManagerType) ? ohModulesPathPart : nodeModulesPathPart)!; 875 if (!parts) { 876 return undefined; 877 } 878 879 // Simplify the full file path to something that can be resolved by Node. 880 881 const preferences = getPreferences(host, userPreferences, options, importingSourceFile); 882 let moduleSpecifier = path; 883 let isPackageRootPath = false; 884 if (!packageNameOnly) { 885 let packageRootIndex = parts.packageRootIndex; 886 let moduleFileName: string | undefined; 887 while (true) { 888 // If the module could be imported by a directory name, use that directory's name 889 const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); 890 if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { 891 if (blockedByExports) { 892 return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution 893 } 894 if (verbatimFromExports) { 895 return moduleFileToTry; 896 } 897 } 898 if (packageRootPath) { 899 moduleSpecifier = packageRootPath; 900 isPackageRootPath = true; 901 break; 902 } 903 if (!moduleFileName) moduleFileName = moduleFileToTry; 904 905 // try with next level of directory 906 packageRootIndex = path.indexOf(directorySeparator, packageRootIndex + 1); 907 if (packageRootIndex === -1) { 908 moduleSpecifier = removeExtensionAndIndexPostFix(moduleFileName, preferences.ending, options, host); 909 break; 910 } 911 } 912 } 913 914 if (isRedirect && !isPackageRootPath) { 915 return undefined; 916 } 917 918 const globalTypingsCacheLocation = host.getGlobalTypingsCacheLocation && host.getGlobalTypingsCacheLocation(); 919 // Get a path that's relative to node_modules, oh_modules or the importing file's path 920 // if node_modules or oh_modules folder is in this folder or any of its parent folders, no need to keep it. 921 const pathToTopLevelNodeModules = getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)); 922 if (!(startsWith(sourceDirectory, pathToTopLevelNodeModules) || globalTypingsCacheLocation && startsWith(getCanonicalFileName(globalTypingsCacheLocation), pathToTopLevelNodeModules))) { 923 return undefined; 924 } 925 926 // If the module was found in @types, get the actual Node package name 927 const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); 928 const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); 929 // For classic resolution, only allow importing from node_modules/@types, not other node_modules 930 return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; 931 932 function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { 933 const packageRootPath = path.substring(0, packageRootIndex); 934 const packageJsonPath = combinePaths(packageRootPath, getPackageJsonByPMType(options.packageManagerType)); 935 let moduleFileToTry = path; 936 let maybeBlockedByTypesVersions = false; 937 const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); 938 if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { 939 const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || 940 isOhpm(options.packageManagerType) ? require("json5").parse(host.readFile!(packageJsonPath)!) : JSON.parse(host.readFile!(packageJsonPath)!); 941 const importMode = overrideMode || importingSourceFile.impliedNodeFormat; 942 if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { 943 const conditions = ["node", importMode === ModuleKind.ESNext ? "import" : "require", "types"]; 944 const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" 945 ? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions) 946 : undefined; 947 if (fromExports) { 948 const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) 949 ? fromExports 950 : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; 951 return { ...withJsExtension, verbatimFromExports: true }; 952 } 953 if (packageJsonContent.exports) { 954 return { moduleFileToTry: path, blockedByExports: true }; 955 } 956 } 957 const versionPaths = packageJsonContent.typesVersions 958 ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) 959 : undefined; 960 if (versionPaths) { 961 const subModuleName = path.slice(packageRootPath.length + 1); 962 const fromPaths = tryGetModuleNameFromPaths( 963 subModuleName, 964 versionPaths.paths, 965 getAllowedEndings(preferences.ending, options, importMode), 966 host, 967 options 968 ); 969 if (fromPaths === undefined) { 970 maybeBlockedByTypesVersions = true; 971 } 972 else { 973 moduleFileToTry = combinePaths(packageRootPath, fromPaths); 974 } 975 } 976 // If the file is the main module, it can be imported by the package name 977 const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js"; 978 if (isString(mainFileRelative) && !(maybeBlockedByTypesVersions && matchPatternOrExact(tryParsePatterns(versionPaths!.paths), mainFileRelative))) { 979 // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path 980 // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable. 981 // (The only way this can happen is if some file in a package that's not resolvable from outside the 982 // package got pulled into the program anyway, e.g. transitively through a file that *is* reachable. It 983 // happens very easily in fourslash tests though, since every test file listed gets included. See 984 // importNameCodeFix_typesVersions.ts for an example.) 985 const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName); 986 if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) { 987 // ^ An arbitrary removal of file extension for this comparison is almost certainly wrong 988 return { packageRootPath, moduleFileToTry }; 989 } 990 } 991 } 992 else { 993 // No package.json exists; an index.js will still resolve as the package name 994 const fileName = getCanonicalFileName(moduleFileToTry.substring(parts.packageRootIndex + 1)); 995 if (fileName === "index.d.ts" || fileName === "index.js" || fileName === "index.ts" || fileName === "index.tsx") { 996 return { moduleFileToTry, packageRootPath }; 997 } 998 } 999 return { moduleFileToTry }; 1000 } 1001} 1002 1003function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { 1004 if (!host.fileExists) return; 1005 // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory 1006 const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); 1007 for (const e of extensions) { 1008 const fullPath = path + e; 1009 if (host.fileExists(fullPath)) { 1010 return fullPath; 1011 } 1012 } 1013} 1014 1015function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined { 1016 return mapDefined(rootDirs, rootDir => { 1017 const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); 1018 return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath; 1019 }); 1020} 1021 1022function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions, host?: ModuleSpecifierResolutionHost): string { 1023 if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; 1024 const noExtension = removeFileExtension(fileName); 1025 if (fileName === noExtension) return fileName; 1026 if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); 1027 switch (ending) { 1028 case Ending.Minimal: 1029 const withoutIndex = removeSuffix(noExtension, "/index"); 1030 if (host && withoutIndex !== noExtension && tryGetAnyFileFromPath(host, withoutIndex)) { 1031 // Can't remove index if there's a file by the same name as the directory. 1032 // Probably more callers should pass `host` so we can determine this? 1033 return noExtension; 1034 } 1035 return withoutIndex; 1036 case Ending.Index: 1037 return noExtension; 1038 case Ending.JsExtension: 1039 return noExtension + getJSExtensionForFile(fileName, options); 1040 default: 1041 return Debug.assertNever(ending); 1042 } 1043} 1044 1045function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension { 1046 return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`); 1047} 1048 1049/** @internal */ 1050export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined { 1051 const ext = tryGetExtensionFromPath(fileName); 1052 switch (ext) { 1053 case Extension.Ts: 1054 case Extension.Dts: 1055 case Extension.Ets: 1056 case Extension.Dets: 1057 return Extension.Js; 1058 case Extension.Tsx: 1059 return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js; 1060 case Extension.Js: 1061 case Extension.Jsx: 1062 case Extension.Json: 1063 return ext; 1064 case Extension.Dmts: 1065 case Extension.Mts: 1066 case Extension.Mjs: 1067 return Extension.Mjs; 1068 case Extension.Dcts: 1069 case Extension.Cts: 1070 case Extension.Cjs: 1071 return Extension.Cjs; 1072 default: 1073 return undefined; 1074 } 1075} 1076 1077function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { 1078 const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false); 1079 return isRootedDiskPath(relativePath) ? undefined : relativePath; 1080} 1081 1082function isPathRelativeToParent(path: string): boolean { 1083 return startsWith(path, ".."); 1084} 1085