1import * as ts from "./_namespaces/ts"; 2import { 3 arrayToMap, CachedDirectoryStructureHost, CacheWithRedirects, CharacterCodes, clearMap, closeFileWatcher, 4 closeFileWatcherOf, CompilerOptions, contains, createCacheWithRedirects, createModeAwareCache, 5 createModuleResolutionCache, createMultiMap, createTypeReferenceDirectiveResolutionCache, Debug, Diagnostics, 6 directorySeparator, DirectoryWatcherCallback, emptyArray, emptyIterator, endsWith, ESMap, Extension, extensionIsTS, 7 fileExtensionIs, fileExtensionIsOneOf, FileReference, FileWatcher, FileWatcherCallback, firstDefinedIterator, 8 GetCanonicalFileName, getDirectoryPath, getEffectiveTypeRoots, getModeForFileReference, getModeForResolutionAtIndex, 9 getNormalizedAbsolutePath, getRootLength, HasInvalidatedResolutions, ignoredPaths, inferredTypesContainingFile, 10 isEmittedFileOfProgram, isExternalModuleNameRelative, isExternalOrCommonJsModule, isNodeModulesDirectory, 11 isOHModulesAtTypesDirectory, isOhpm, isRootedDiskPath, isString, isTargetModulesDerectory, isTraceEnabled, length, 12 loadModuleFromGlobalCache, Map, memoize, MinimalResolutionCacheHost, ModeAwareCache, ModuleKind, 13 ModuleResolutionCache, ModuleResolutionHost, mutateMap, noopFileWatcher, normalizePath, PackageId, 14 packageIdToString, parseModuleFromPath, Path, pathContainsNodeModules, pathContainsOHModules, PerModuleNameCache, 15 Program, ReadonlyESMap, removeSuffix, removeTrailingDirectorySeparator, resolutionExtensionIsTSOrJson, 16 ResolvedModuleFull, ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, 17 ResolvedTypeReferenceDirective, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, returnTrue, Set, some, 18 SourceFile, startsWith, stringContains, trace, unorderedRemoveItem, WatchDirectoryFlags, 19} from "./_namespaces/ts"; 20 21/** 22 * This is the cache of module/typedirectives resolution that can be retained across program 23 * 24 * @internal 25 */ 26export interface ResolutionCache { 27 startRecordingFilesWithChangedResolutions(): void; 28 finishRecordingFilesWithChangedResolutions(): Path[] | undefined; 29 30 resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; 31 getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; 32 resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[]; 33 34 invalidateResolutionsOfFailedLookupLocations(): boolean; 35 invalidateResolutionOfFile(filePath: Path): void; 36 removeResolutionsOfFile(filePath: Path): void; 37 removeResolutionsFromProjectReferenceRedirects(filePath: Path): void; 38 setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap<Path, readonly string[]>): void; 39 createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions; 40 hasChangedAutomaticTypeDirectiveNames(): boolean; 41 isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean; 42 43 44 startCachingPerDirectoryResolution(): void; 45 finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined): void; 46 47 updateTypeRootsWatch(): void; 48 closeTypeRootsWatch(): void; 49 50 getModuleResolutionCache(): ModuleResolutionCache; 51 52 clear(): void; 53} 54 55/** @internal */ 56export interface ResolutionWithFailedLookupLocations { 57 readonly failedLookupLocations: string[]; 58 readonly affectingLocations: string[]; 59 isInvalidated?: boolean; 60 refCount?: number; 61 // Files that have this resolution using 62 files?: Path[]; 63} 64 65interface ResolutionWithResolvedFileName { 66 resolvedFileName: string | undefined; 67 packagetId?: PackageId; 68} 69 70/** @internal */ 71export interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations { 72} 73 74interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations { 75} 76 77/** @internal */ 78export interface ResolutionCacheHost extends MinimalResolutionCacheHost { 79 toPath(fileName: string): Path; 80 getCanonicalFileName: GetCanonicalFileName; 81 getCompilationSettings(): CompilerOptions; 82 watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; 83 watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher; 84 onInvalidatedResolution(): void; 85 watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher; 86 onChangedAutomaticTypeDirectiveNames(): void; 87 scheduleInvalidateResolutionsOfFailedLookupLocations(): void; 88 getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; 89 projectName?: string; 90 getGlobalCache?(): string | undefined; 91 globalCacheResolutionModuleName?(externalModuleName: string): string; 92 writeLog(s: string): void; 93 getCurrentProgram(): Program | undefined; 94 fileIsOpen(filePath: Path): boolean; 95 onDiscoveredSymlink?(): void; 96} 97 98interface FileWatcherOfAffectingLocation { 99 /** watcher for the lookup */ 100 watcher: FileWatcher; 101 resolutions: number; 102 files: number; 103 paths: Set<string>; 104} 105 106interface DirectoryWatchesOfFailedLookup { 107 /** watcher for the lookup */ 108 watcher: FileWatcher; 109 /** ref count keeping this watch alive */ 110 refCount: number; 111 /** is the directory watched being non recursive */ 112 nonRecursive?: boolean; 113} 114 115interface DirectoryOfFailedLookupWatch { 116 dir: string; 117 dirPath: Path; 118 nonRecursive?: boolean; 119} 120 121/** @internal */ 122export function removeIgnoredPath(path: Path): Path | undefined { 123 // Consider whole staging folder as if node_modules or oh_modules changed. 124 if (endsWith(path, "/node_modules/.staging") || endsWith(path, "/oh_modules/.staging")) { 125 return removeSuffix(path, "/.staging") as Path; 126 } 127 128 return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ? 129 undefined : 130 path; 131} 132 133/** 134 * Filter out paths like 135 * "/", "/user", "/user/username", "/user/username/folderAtRoot", 136 * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" 137 * @param dirPath 138 * 139 * @internal 140 */ 141export function canWatchDirectoryOrFile(dirPath: Path) { 142 const rootLength = getRootLength(dirPath); 143 if (dirPath.length === rootLength) { 144 // Ignore "/", "c:/" 145 return false; 146 } 147 148 let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); 149 if (nextDirectorySeparator === -1) { 150 // ignore "/user", "c:/users" or "c:/folderAtRoot" 151 return false; 152 } 153 154 let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1); 155 const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash; 156 if (isNonDirectorySeparatorRoot && 157 dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths 158 pathPartForUserCheck.search(/[a-zA-Z]\$\//) === 0) { // Dos style nextPart 159 nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1); 160 if (nextDirectorySeparator === -1) { 161 // ignore "//vda1cs4850/c$/folderAtRoot" 162 return false; 163 } 164 165 pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1); 166 } 167 168 if (isNonDirectorySeparatorRoot && 169 pathPartForUserCheck.search(/users\//i) !== 0) { 170 // Paths like c:/folderAtRoot/subFolder are allowed 171 return true; 172 } 173 174 for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { 175 searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; 176 if (searchIndex === 0) { 177 // Folder isnt at expected minimum levels 178 return false; 179 } 180 } 181 return true; 182} 183 184type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> = 185 (resolution: T) => R | undefined; 186 187/** @internal */ 188export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { 189 let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; 190 let filesWithInvalidatedResolutions: Set<Path> | undefined; 191 let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap<Path, readonly string[]> | undefined; 192 const nonRelativeExternalModuleResolutions = createMultiMap<ResolutionWithFailedLookupLocations>(); 193 194 const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = []; 195 const resolutionsWithOnlyAffectingLocations: ResolutionWithFailedLookupLocations[] = []; 196 const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>(); 197 const impliedFormatPackageJsons = new Map<Path, readonly string[]>(); 198 199 let hasChangedAutomaticTypeDirectiveNames = false; 200 let affectingPathChecksForFile: Set<string> | undefined; 201 let affectingPathChecks: Set<string> | undefined; 202 let failedLookupChecks: Set<Path> | undefined; 203 let startsWithPathChecks: Set<Path> | undefined; 204 let isInDirectoryChecks: Set<Path> | undefined; 205 206 const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217 207 const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost(); 208 209 // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. 210 // The key in the map is source file's path. 211 // The values are Map of resolutions with key being name lookedup. 212 const resolvedModuleNames = new Map<Path, ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>>(); 213 const perDirectoryResolvedModuleNames: CacheWithRedirects<ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>> = createCacheWithRedirects(); 214 const nonRelativeModuleNameCache: CacheWithRedirects<PerModuleNameCache> = createCacheWithRedirects(); 215 const moduleResolutionCache = createModuleResolutionCache( 216 getCurrentDirectory(), 217 resolutionHost.getCanonicalFileName, 218 /*options*/ undefined, 219 perDirectoryResolvedModuleNames, 220 nonRelativeModuleNameCache, 221 ); 222 223 const resolvedTypeReferenceDirectives = new Map<Path, ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>(); 224 const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects<ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>> = createCacheWithRedirects(); 225 const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( 226 getCurrentDirectory(), 227 resolutionHost.getCanonicalFileName, 228 /*options*/ undefined, 229 moduleResolutionCache.getPackageJsonInfoCache(), 230 perDirectoryResolvedTypeReferenceDirectives 231 ); 232 233 /** 234 * These are the extensions that failed lookup files will have by default, 235 * any other extension of failed lookup will be store that path in custom failed lookup path 236 * This helps in not having to comb through all resolutions when files are added/removed 237 * Note that .d.ts file also has .d.ts extension hence will be part of default extensions 238 */ 239 const failedLookupDefaultExtensions = resolutionHost.getCompilationSettings().ets 240 ? [Extension.Ets, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json] 241 : [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json, Extension.Ets]; 242 const customFailedLookupPaths = new Map<string, number>(); 243 244 const directoryWatchesOfFailedLookups = new Map<string, DirectoryWatchesOfFailedLookup>(); 245 const fileWatchesOfAffectingLocations = new Map<string, FileWatcherOfAffectingLocation>(); 246 const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); 247 const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 248 const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; 249 250 // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames 251 const typeRootsWatches = new Map<string, FileWatcher>(); 252 253 return { 254 getModuleResolutionCache: () => moduleResolutionCache, 255 startRecordingFilesWithChangedResolutions, 256 finishRecordingFilesWithChangedResolutions, 257 // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update 258 // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) 259 startCachingPerDirectoryResolution, 260 finishCachingPerDirectoryResolution, 261 resolveModuleNames, 262 getResolvedModuleWithFailedLookupLocationsFromCache, 263 resolveTypeReferenceDirectives, 264 removeResolutionsFromProjectReferenceRedirects, 265 removeResolutionsOfFile, 266 hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames, 267 invalidateResolutionOfFile, 268 invalidateResolutionsOfFailedLookupLocations, 269 setFilesWithInvalidatedNonRelativeUnresolvedImports, 270 createHasInvalidatedResolutions, 271 isFileWithInvalidatedNonRelativeUnresolvedImports, 272 updateTypeRootsWatch, 273 closeTypeRootsWatch, 274 clear 275 }; 276 277 function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) { 278 return resolution.resolvedModule; 279 } 280 281 function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) { 282 return resolution.resolvedTypeReferenceDirective; 283 } 284 285 function isInDirectoryPath(dir: Path | undefined, file: Path) { 286 if (dir === undefined || file.length <= dir.length) { 287 return false; 288 } 289 return startsWith(file, dir) && file[dir.length] === directorySeparator; 290 } 291 292 function clear() { 293 clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); 294 clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf); 295 customFailedLookupPaths.clear(); 296 nonRelativeExternalModuleResolutions.clear(); 297 closeTypeRootsWatch(); 298 resolvedModuleNames.clear(); 299 resolvedTypeReferenceDirectives.clear(); 300 resolvedFileToResolution.clear(); 301 resolutionsWithFailedLookups.length = 0; 302 resolutionsWithOnlyAffectingLocations.length = 0; 303 failedLookupChecks = undefined; 304 startsWithPathChecks = undefined; 305 isInDirectoryChecks = undefined; 306 affectingPathChecks = undefined; 307 affectingPathChecksForFile = undefined; 308 moduleResolutionCache.clear(); 309 typeReferenceDirectiveResolutionCache.clear(); 310 impliedFormatPackageJsons.clear(); 311 hasChangedAutomaticTypeDirectiveNames = false; 312 } 313 314 function startRecordingFilesWithChangedResolutions() { 315 filesWithChangedSetOfUnresolvedImports = []; 316 } 317 318 function finishRecordingFilesWithChangedResolutions() { 319 const collected = filesWithChangedSetOfUnresolvedImports; 320 filesWithChangedSetOfUnresolvedImports = undefined; 321 return collected; 322 } 323 324 function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean { 325 if (!filesWithInvalidatedNonRelativeUnresolvedImports) { 326 return false; 327 } 328 329 // Invalidated if file has unresolved imports 330 const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); 331 return !!value && !!value.length; 332 } 333 334 function createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions { 335 // Ensure pending resolutions are applied 336 invalidateResolutionsOfFailedLookupLocations(); 337 const collected = filesWithInvalidatedResolutions; 338 filesWithInvalidatedResolutions = undefined; 339 return path => customHasInvalidatedResolutions(path) || 340 !!collected?.has(path) || 341 isFileWithInvalidatedNonRelativeUnresolvedImports(path); 342 } 343 344 function startCachingPerDirectoryResolution() { 345 moduleResolutionCache.clearAllExceptPackageJsonInfoCache(); 346 typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache(); 347 // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update 348 // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution) 349 nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); 350 nonRelativeExternalModuleResolutions.clear(); 351 } 352 353 function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) { 354 filesWithInvalidatedNonRelativeUnresolvedImports = undefined; 355 nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); 356 nonRelativeExternalModuleResolutions.clear(); 357 // Update file watches 358 if (newProgram !== oldProgram) { 359 newProgram?.getSourceFiles().forEach(newFile => { 360 const expected = isExternalOrCommonJsModule(newFile) ? newFile.packageJsonLocations?.length ?? 0 : 0; 361 const existing = impliedFormatPackageJsons.get(newFile.path) ?? emptyArray; 362 for (let i = existing.length; i < expected; i++) { 363 createFileWatcherOfAffectingLocation(newFile.packageJsonLocations![i], /*forResolution*/ false); 364 } 365 if (existing.length > expected) { 366 for (let i = expected; i < existing.length; i++) { 367 fileWatchesOfAffectingLocations.get(existing[i])!.files--; 368 } 369 } 370 if (expected) impliedFormatPackageJsons.set(newFile.path, newFile.packageJsonLocations!); 371 else impliedFormatPackageJsons.delete(newFile.path); 372 }); 373 impliedFormatPackageJsons.forEach((existing, path) => { 374 if (!newProgram?.getSourceFileByPath(path)) { 375 existing.forEach(location => fileWatchesOfAffectingLocations.get(location)!.files--); 376 impliedFormatPackageJsons.delete(path); 377 } 378 }); 379 } 380 directoryWatchesOfFailedLookups.forEach((watcher, path) => { 381 if (watcher.refCount === 0) { 382 directoryWatchesOfFailedLookups.delete(path); 383 watcher.watcher.close(); 384 } 385 }); 386 fileWatchesOfAffectingLocations.forEach((watcher, path) => { 387 if (watcher.files === 0 && watcher.resolutions === 0) { 388 fileWatchesOfAffectingLocations.delete(path); 389 watcher.watcher.close(); 390 } 391 }); 392 393 hasChangedAutomaticTypeDirectiveNames = false; 394 } 395 396 function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations { 397 const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode); 398 // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts 399 if (!resolutionHost.getGlobalCache) { 400 return primaryResult; 401 } 402 403 // otherwise try to load typings from @types 404 const globalCache = resolutionHost.getGlobalCache(); 405 if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { 406 // create different collection of failed lookup locations for second pass 407 // if it will fail and we've already found something during the first pass - we don't want to pollute its results 408 const { resolvedModule, failedLookupLocations, affectingLocations } = loadModuleFromGlobalCache( 409 Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), 410 resolutionHost.projectName, 411 compilerOptions, 412 host, 413 globalCache, 414 moduleResolutionCache, 415 ); 416 if (resolvedModule) { 417 // Modify existing resolution so its saved in the directory cache as well 418 (primaryResult.resolvedModule as any) = resolvedModule; 419 primaryResult.failedLookupLocations.push(...failedLookupLocations); 420 primaryResult.affectingLocations.push(...affectingLocations); 421 return primaryResult; 422 } 423 } 424 425 // Default return the result from the first pass 426 return primaryResult; 427 } 428 429 function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { 430 return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode); 431 } 432 433 interface ResolveNamesWithLocalCacheInput<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName> { 434 names: readonly string[] | readonly FileReference[]; 435 containingFile: string; 436 redirectedReference: ResolvedProjectReference | undefined; 437 cache: ESMap<Path, ModeAwareCache<T>>; 438 perDirectoryCacheWithRedirects: CacheWithRedirects<ModeAwareCache<T>>; 439 loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T; 440 getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>; 441 shouldRetryResolution: (t: T) => boolean; 442 reusedNames?: readonly string[]; 443 logChanges?: boolean; 444 containingSourceFile?: SourceFile; 445 containingSourceFileMode?: SourceFile["impliedNodeFormat"]; 446 } 447 function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({ 448 names, containingFile, redirectedReference, 449 cache, perDirectoryCacheWithRedirects, 450 loader, getResolutionWithResolvedFileName, 451 shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode 452 }: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] { 453 const path = resolutionHost.toPath(containingFile); 454 const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; 455 const dirPath = getDirectoryPath(path); 456 const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); 457 let perDirectoryResolution = perDirectoryCache.get(dirPath); 458 if (!perDirectoryResolution) { 459 perDirectoryResolution = createModeAwareCache(); 460 perDirectoryCache.set(dirPath, perDirectoryResolution); 461 } 462 const resolvedModules: (R | undefined)[] = []; 463 const compilerOptions = resolutionHost.getCompilationSettings(); 464 const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); 465 466 // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect 467 const program = resolutionHost.getCurrentProgram(); 468 const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile); 469 const unmatchedRedirects = oldRedirect ? 470 !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : 471 !!redirectedReference; 472 473 const seenNamesInFile = createModeAwareCache<true>(); 474 let i = 0; 475 for (const entry of names) { 476 const name = isString(entry) ? entry : entry.fileName.toLowerCase(); 477 // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant 478 // they require calculating the mode for a given import from it's position in the resolution table, since a given 479 // import's syntax may override the file's default mode. 480 // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains 481 // a default file mode override if applicable. 482 const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) : 483 containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined; 484 i++; 485 let resolution = resolutionsInFile.get(name, mode); 486 // Resolution is valid if it is present and not invalidated 487 if (!seenNamesInFile.has(name, mode) && 488 unmatchedRedirects || !resolution || resolution.isInvalidated || 489 // If the name is unresolved import that was invalidated, recalculate 490 (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { 491 const existingResolution = resolution; 492 const resolutionInDirectory = perDirectoryResolution.get(name, mode); 493 if (resolutionInDirectory) { 494 resolution = resolutionInDirectory; 495 const host = resolutionHost.getCompilerHost?.() || resolutionHost; 496 if (isTraceEnabled(compilerOptions, host)) { 497 const resolved = getResolutionWithResolvedFileName(resolution); 498 trace( 499 host, 500 loader === resolveModuleName as unknown ? 501 resolved?.resolvedFileName ? 502 resolved.packagetId ? 503 Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4: 504 Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3: 505 Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved : 506 resolved?.resolvedFileName ? 507 resolved.packagetId ? 508 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 : 509 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 : 510 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved, 511 name, 512 containingFile, 513 getDirectoryPath(containingFile), 514 resolved?.resolvedFileName, 515 resolved?.packagetId && packageIdToString(resolved.packagetId) 516 ); 517 } 518 } 519 else { 520 resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode); 521 perDirectoryResolution.set(name, mode, resolution); 522 if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { 523 resolutionHost.onDiscoveredSymlink(); 524 } 525 } 526 resolutionsInFile.set(name, mode, resolution); 527 watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); 528 if (existingResolution) { 529 stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); 530 } 531 532 if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { 533 filesWithChangedSetOfUnresolvedImports.push(path); 534 // reset log changes to avoid recording the same file multiple times 535 logChanges = false; 536 } 537 } 538 else { 539 const host = resolutionHost.getCompilerHost?.() || resolutionHost; 540 if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { 541 const resolved = getResolutionWithResolvedFileName(resolution); 542 trace( 543 host, 544 loader === resolveModuleName as unknown ? 545 resolved?.resolvedFileName ? 546 resolved.packagetId ? 547 Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : 548 Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : 549 Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved : 550 resolved?.resolvedFileName ? 551 resolved.packagetId ? 552 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 : 553 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 : 554 Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved, 555 name, 556 containingFile, 557 resolved?.resolvedFileName, 558 resolved?.packagetId && packageIdToString(resolved.packagetId) 559 ); 560 } 561 } 562 Debug.assert(resolution !== undefined && !resolution.isInvalidated); 563 seenNamesInFile.set(name, mode, true); 564 resolvedModules.push(getResolutionWithResolvedFileName(resolution)); 565 } 566 567 // Stop watching and remove the unused name 568 resolutionsInFile.forEach((resolution, name, mode) => { 569 if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { 570 stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); 571 resolutionsInFile.delete(name, mode); 572 } 573 }); 574 575 return resolvedModules; 576 577 function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean { 578 if (oldResolution === newResolution) { 579 return true; 580 } 581 if (!oldResolution || !newResolution) { 582 return false; 583 } 584 const oldResult = getResolutionWithResolvedFileName(oldResolution); 585 const newResult = getResolutionWithResolvedFileName(newResolution); 586 if (oldResult === newResult) { 587 return true; 588 } 589 if (!oldResult || !newResult) { 590 return false; 591 } 592 return oldResult.resolvedFileName === newResult.resolvedFileName; 593 } 594 } 595 596 function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] { 597 return resolveNamesWithLocalCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective>({ 598 names: typeDirectiveNames, 599 containingFile, 600 redirectedReference, 601 cache: resolvedTypeReferenceDirectives, 602 perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives, 603 loader: resolveTypeReferenceDirective, 604 getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, 605 shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, 606 containingSourceFileMode: containingFileMode 607 }); 608 } 609 610 function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { 611 return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({ 612 names: moduleNames, 613 containingFile, 614 redirectedReference, 615 cache: resolvedModuleNames, 616 perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames, 617 loader: resolveModuleName, 618 getResolutionWithResolvedFileName: getResolvedModule, 619 shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), 620 reusedNames, 621 logChanges: logChangesWhenResolvingModule, 622 containingSourceFile, 623 }); 624 } 625 626 function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { 627 const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); 628 if (!cache) return undefined; 629 return cache.get(moduleName, resolutionMode); 630 } 631 632 function isNodeModulesAtTypesDirectory(dirPath: Path) { 633 return endsWith(dirPath, "/node_modules/@types"); 634 } 635 636 function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { 637 if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { 638 // Ensure failed look up is normalized path 639 failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); 640 const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator); 641 const failedLookupSplit = failedLookupLocation.split(directorySeparator); 642 Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`); 643 if (failedLookupPathSplit.length > rootSplitLength + 1) { 644 // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution 645 return { 646 dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator), 647 dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path 648 }; 649 } 650 else { 651 // Always watch root directory non recursively 652 return { 653 dir: rootDir!, 654 dirPath: rootPath, 655 nonRecursive: false 656 }; 657 } 658 } 659 660 return getDirectoryToWatchFromFailedLookupLocationDirectory( 661 getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())), 662 getDirectoryPath(failedLookupLocationPath) 663 ); 664 } 665 666 function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined { 667 // If directory path contains node module, get the most parent node_modules or oh_modules directory for watching 668 const isOHModules: boolean = isOhpm(resolutionHost.getCompilationSettings().packageManagerType); 669 while (isOHModules ? pathContainsOHModules(dirPath) : pathContainsNodeModules(dirPath)) { 670 dir = getDirectoryPath(dir); 671 dirPath = getDirectoryPath(dirPath); 672 } 673 674 // If the directory is node_modules or oh_modules use it to watch, always watch it recursively 675 if (isTargetModulesDerectory(dirPath)) { 676 return canWatchDirectoryOrFile(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined; 677 } 678 679 let nonRecursive = true; 680 // Use some ancestor of the root directory 681 let subDirectoryPath: Path | undefined, subDirectory: string | undefined; 682 if (rootPath !== undefined) { 683 while (!isInDirectoryPath(dirPath, rootPath)) { 684 const parentPath = getDirectoryPath(dirPath); 685 if (parentPath === dirPath) { 686 break; 687 } 688 nonRecursive = false; 689 subDirectoryPath = dirPath; 690 subDirectory = dir; 691 dirPath = parentPath; 692 dir = getDirectoryPath(dir); 693 } 694 } 695 696 return canWatchDirectoryOrFile(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined; 697 } 698 699 function isPathWithDefaultFailedLookupExtension(path: Path) { 700 return fileExtensionIsOneOf(path, failedLookupDefaultExtensions); 701 } 702 703 function watchFailedLookupLocationsOfExternalModuleResolutions<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>( 704 name: string, 705 resolution: T, 706 filePath: Path, 707 getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>, 708 ) { 709 if (resolution.refCount) { 710 resolution.refCount++; 711 Debug.assertIsDefined(resolution.files); 712 } 713 else { 714 resolution.refCount = 1; 715 Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet 716 if (isExternalModuleNameRelative(name)) { 717 watchFailedLookupLocationOfResolution(resolution); 718 } 719 else { 720 nonRelativeExternalModuleResolutions.add(name, resolution); 721 } 722 const resolved = getResolutionWithResolvedFileName(resolution); 723 if (resolved && resolved.resolvedFileName) { 724 resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution); 725 } 726 } 727 (resolution.files || (resolution.files = [])).push(filePath); 728 } 729 730 function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) { 731 Debug.assert(!!resolution.refCount); 732 733 const { failedLookupLocations, affectingLocations } = resolution; 734 if (!failedLookupLocations.length && !affectingLocations.length) return; 735 if (failedLookupLocations.length) resolutionsWithFailedLookups.push(resolution); 736 737 let setAtRoot = false; 738 for (const failedLookupLocation of failedLookupLocations) { 739 const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); 740 const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); 741 if (toWatch) { 742 const { dir, dirPath, nonRecursive } = toWatch; 743 // If the failed lookup location path is not one of the supported extensions, 744 // store it in the custom path 745 if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) { 746 const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0; 747 customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1); 748 } 749 if (dirPath === rootPath) { 750 Debug.assert(!nonRecursive); 751 setAtRoot = true; 752 } 753 else { 754 setDirectoryWatcher(dir, dirPath, nonRecursive); 755 } 756 } 757 } 758 759 if (setAtRoot) { 760 // This is always non recursive 761 setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217 762 } 763 watchAffectingLocationsOfResolution(resolution, !failedLookupLocations.length); 764 } 765 766 function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) { 767 Debug.assert(!!resolution.refCount); 768 const { affectingLocations } = resolution; 769 if (!affectingLocations.length) return; 770 if (addToResolutionsWithOnlyAffectingLocations) resolutionsWithOnlyAffectingLocations.push(resolution); 771 // Watch package json 772 for (const affectingLocation of affectingLocations) { 773 createFileWatcherOfAffectingLocation(affectingLocation, /*forResolution*/ true); 774 } 775 } 776 777 function createFileWatcherOfAffectingLocation(affectingLocation: string, forResolution: boolean) { 778 const fileWatcher = fileWatchesOfAffectingLocations.get(affectingLocation); 779 if (fileWatcher) { 780 if (forResolution) fileWatcher.resolutions++; 781 else fileWatcher.files++; 782 return; 783 } 784 let locationToWatch = affectingLocation; 785 if (resolutionHost.realpath) { 786 locationToWatch = resolutionHost.realpath(affectingLocation); 787 if (affectingLocation !== locationToWatch) { 788 const fileWatcher = fileWatchesOfAffectingLocations.get(locationToWatch); 789 if (fileWatcher) { 790 if (forResolution) fileWatcher.resolutions++; 791 else fileWatcher.files++; 792 fileWatcher.paths.add(affectingLocation); 793 fileWatchesOfAffectingLocations.set(affectingLocation, fileWatcher); 794 return; 795 } 796 } 797 } 798 const paths = new Set<string>(); 799 paths.add(locationToWatch); 800 let actualWatcher = canWatchDirectoryOrFile(resolutionHost.toPath(locationToWatch)) ? 801 resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => { 802 cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind); 803 const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); 804 paths.forEach(path => { 805 if (watcher.resolutions) (affectingPathChecks ??= new Set()).add(path); 806 if (watcher.files) (affectingPathChecksForFile ??= new Set()).add(path); 807 packageJsonMap?.delete(resolutionHost.toPath(path)); 808 }); 809 resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); 810 }) : noopFileWatcher; 811 const watcher: FileWatcherOfAffectingLocation = { 812 watcher: actualWatcher !== noopFileWatcher ? { 813 close: () => { 814 actualWatcher.close(); 815 // Ensure when watching symlinked package.json, we can close the actual file watcher only once 816 actualWatcher = noopFileWatcher; 817 } 818 } : actualWatcher, 819 resolutions: forResolution ? 1 : 0, 820 files: forResolution ? 0 : 1, 821 paths, 822 }; 823 fileWatchesOfAffectingLocations.set(locationToWatch, watcher); 824 if (affectingLocation !== locationToWatch) { 825 fileWatchesOfAffectingLocations.set(affectingLocation, watcher); 826 paths.add(affectingLocation); 827 } 828 } 829 830 function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) { 831 const program = resolutionHost.getCurrentProgram(); 832 if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) { 833 resolutions.forEach(watchFailedLookupLocationOfResolution); 834 } 835 else { 836 resolutions.forEach(resolution => watchAffectingLocationsOfResolution(resolution, /*addToResolutionWithOnlyAffectingLocations*/ true)); 837 } 838 } 839 840 function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) { 841 const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath); 842 if (dirWatcher) { 843 Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive); 844 dirWatcher.refCount++; 845 } 846 else { 847 directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive }); 848 } 849 } 850 851 function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>( 852 resolution: T, 853 filePath: Path, 854 getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>, 855 ) { 856 unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); 857 resolution.refCount!--; 858 if (resolution.refCount) { 859 return; 860 } 861 const resolved = getResolutionWithResolvedFileName(resolution); 862 if (resolved && resolved.resolvedFileName) { 863 resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution); 864 } 865 866 const { failedLookupLocations, affectingLocations } = resolution; 867 if (unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) { 868 let removeAtRoot = false; 869 for (const failedLookupLocation of failedLookupLocations) { 870 const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation); 871 const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); 872 if (toWatch) { 873 const { dirPath } = toWatch; 874 const refCount = customFailedLookupPaths.get(failedLookupLocationPath); 875 if (refCount) { 876 if (refCount === 1) { 877 customFailedLookupPaths.delete(failedLookupLocationPath); 878 } 879 else { 880 Debug.assert(refCount > 1); 881 customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1); 882 } 883 } 884 885 if (dirPath === rootPath) { 886 removeAtRoot = true; 887 } 888 else { 889 removeDirectoryWatcher(dirPath); 890 } 891 } 892 } 893 if (removeAtRoot) { 894 removeDirectoryWatcher(rootPath); 895 } 896 } 897 else if (affectingLocations.length) { 898 unorderedRemoveItem(resolutionsWithOnlyAffectingLocations, resolution); 899 } 900 901 for (const affectingLocation of affectingLocations) { 902 const watcher = fileWatchesOfAffectingLocations.get(affectingLocation)!; 903 watcher.resolutions--; 904 } 905 } 906 907 function removeDirectoryWatcher(dirPath: string) { 908 const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!; 909 // Do not close the watcher yet since it might be needed by other failed lookup locations. 910 dirWatcher.refCount--; 911 } 912 913 function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) { 914 return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => { 915 const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); 916 if (cachedDirectoryStructureHost) { 917 // Since the file existence changed, update the sourceFiles cache 918 cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); 919 } 920 921 scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); 922 }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive); 923 } 924 925 function removeResolutionsOfFileFromCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>( 926 cache: ESMap<string, ModeAwareCache<T>>, 927 filePath: Path, 928 getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>, 929 ) { 930 // Deleted file, stop watching failed lookups for all the resolutions in the file 931 const resolutions = cache.get(filePath); 932 if (resolutions) { 933 resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName)); 934 cache.delete(filePath); 935 } 936 } 937 938 function removeResolutionsFromProjectReferenceRedirects(filePath: Path) { 939 if (!fileExtensionIs(filePath, Extension.Json)) return; 940 941 const program = resolutionHost.getCurrentProgram(); 942 if (!program) return; 943 944 // If this file is input file for the referenced project, get it 945 const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath); 946 if (!resolvedProjectReference) return; 947 948 // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution 949 resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f))); 950 } 951 952 function removeResolutionsOfFile(filePath: Path) { 953 removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule); 954 removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective); 955 } 956 957 function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) { 958 if (!resolutions) return false; 959 let invalidated = false; 960 for (const resolution of resolutions) { 961 if (resolution.isInvalidated || !canInvalidate(resolution)) continue; 962 resolution.isInvalidated = invalidated = true; 963 for (const containingFilePath of Debug.checkDefined(resolution.files)) { 964 (filesWithInvalidatedResolutions ??= new Set()).add(containingFilePath); 965 // When its a file with inferred types resolution, invalidate type reference directive resolution 966 hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); 967 } 968 } 969 return invalidated; 970 } 971 972 function invalidateResolutionOfFile(filePath: Path) { 973 removeResolutionsOfFile(filePath); 974 // Resolution is invalidated if the resulting file name is same as the deleted file path 975 const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; 976 if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) && 977 hasChangedAutomaticTypeDirectiveNames && 978 !prevHasChangedAutomaticTypeDirectiveNames) { 979 resolutionHost.onChangedAutomaticTypeDirectiveNames(); 980 } 981 } 982 983 function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap<Path, readonly string[]>) { 984 Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); 985 filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; 986 } 987 988 function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { 989 if (isCreatingWatchedDirectory) { 990 // Watching directory is created 991 // Invalidate any resolution has failed lookup in this directory 992 (isInDirectoryChecks ||= new Set()).add(fileOrDirectoryPath); 993 } 994 else { 995 // If something to do with folder/file starting with "." in node_modules folder, skip it 996 const updatedPath = removeIgnoredPath(fileOrDirectoryPath); 997 if (!updatedPath) return false; 998 fileOrDirectoryPath = updatedPath; 999 1000 // prevent saving an open file from over-eagerly triggering invalidation 1001 if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) { 1002 return false; 1003 } 1004 1005 // Some file or directory in the watching directory is created 1006 // Return early if it does not have any of the watching extension or not the custom failed lookup path 1007 const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); 1008 const isOHModules = isOhpm(resolutionHost.getCompilationSettings().packageManagerType); 1009 if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || (isOHModules && isOHModulesAtTypesDirectory(fileOrDirectoryPath)) || 1010 isNodeModulesDirectory(fileOrDirectoryPath) || isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || (isOHModules && 1011 isOHModulesAtTypesDirectory(dirOfFileOrDirectory)) || isNodeModulesDirectory(dirOfFileOrDirectory)) { 1012 // Invalidate any resolution from this directory 1013 (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); 1014 (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); 1015 } 1016 else { 1017 if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) { 1018 return false; 1019 } 1020 // Ignore emits from the program 1021 if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) { 1022 return false; 1023 } 1024 // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created 1025 (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); 1026 1027 // If the invalidated file is from a node_modules package, invalidate everything else 1028 // in the package since we might not get notifications for other files in the package. 1029 // This hardens our logic against unreliable file watchers. 1030 const packagePath = parseModuleFromPath(fileOrDirectoryPath); 1031 if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); 1032 } 1033 } 1034 resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); 1035 } 1036 1037 function invalidateResolutionsOfFailedLookupLocations() { 1038 let invalidated = false; 1039 if (affectingPathChecksForFile) { 1040 resolutionHost.getCurrentProgram()?.getSourceFiles().forEach(f => { 1041 if (some(f.packageJsonLocations, location => affectingPathChecksForFile!.has(location))) { 1042 (filesWithInvalidatedResolutions ??= new Set()).add(f.path); 1043 invalidated = true; 1044 } 1045 }); 1046 affectingPathChecksForFile = undefined; 1047 } 1048 1049 if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks && !affectingPathChecks) { 1050 return invalidated; 1051 } 1052 1053 invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution) || invalidated; 1054 const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); 1055 if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) { 1056 packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined); 1057 } 1058 failedLookupChecks = undefined; 1059 startsWithPathChecks = undefined; 1060 isInDirectoryChecks = undefined; 1061 invalidated = invalidateResolutions(resolutionsWithOnlyAffectingLocations, canInvalidatedFailedLookupResolutionWithAffectingLocation) || invalidated; 1062 affectingPathChecks = undefined; 1063 return invalidated; 1064 } 1065 1066 function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { 1067 if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true; 1068 if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false; 1069 return resolution.failedLookupLocations.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))); 1070 } 1071 1072 function isInvalidatedFailedLookup(locationPath: Path) { 1073 return failedLookupChecks?.has(locationPath) || 1074 firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || 1075 firstDefinedIterator(isInDirectoryChecks?.keys() || emptyIterator, fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath) ? true : undefined); 1076 } 1077 1078 function canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution: ResolutionWithFailedLookupLocations) { 1079 return !!affectingPathChecks && resolution.affectingLocations.some(location => affectingPathChecks!.has(location)); 1080 } 1081 1082 function closeTypeRootsWatch() { 1083 clearMap(typeRootsWatches, closeFileWatcher); 1084 } 1085 1086 function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined { 1087 if (isInDirectoryPath(rootPath, typeRootPath)) { 1088 return rootPath; 1089 } 1090 const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath); 1091 return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined; 1092 } 1093 1094 function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher { 1095 // Create new watch and recursive info 1096 return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { 1097 const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory); 1098 if (cachedDirectoryStructureHost) { 1099 // Since the file existence changed, update the sourceFiles cache 1100 cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); 1101 } 1102 1103 // For now just recompile 1104 // We could potentially store more data here about whether it was/would be really be used or not 1105 // and with that determine to trigger compilation but for now this is enough 1106 hasChangedAutomaticTypeDirectiveNames = true; 1107 resolutionHost.onChangedAutomaticTypeDirectiveNames(); 1108 1109 // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered 1110 // So handle to failed lookup locations here as well to ensure we are invalidating resolutions 1111 const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath); 1112 if (dirPath) { 1113 scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath); 1114 } 1115 }, WatchDirectoryFlags.Recursive); 1116 } 1117 1118 /** 1119 * Watches the types that would get added as part of getAutomaticTypeDirectiveNames 1120 * To be called when compiler options change 1121 */ 1122 function updateTypeRootsWatch() { 1123 const options = resolutionHost.getCompilationSettings(); 1124 if (options.types) { 1125 // No need to do any watch since resolution cache is going to handle the failed lookups 1126 // for the types added by this 1127 closeTypeRootsWatch(); 1128 return; 1129 } 1130 1131 // we need to assume the directories exist to ensure that we can get all the type root directories that get included 1132 // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them 1133 const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory }); 1134 if (typeRoots) { 1135 mutateMap( 1136 typeRootsWatches, 1137 arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)), 1138 { 1139 createNewValue: createTypeRootsWatch, 1140 onDeleteValue: closeFileWatcher 1141 } 1142 ); 1143 } 1144 else { 1145 closeTypeRootsWatch(); 1146 } 1147 } 1148 1149 /** 1150 * Use this function to return if directory exists to get type roots to watch 1151 * If we return directory exists then only the paths will be added to type roots 1152 * Hence return true for all directories except root directories which are filtered from watching 1153 */ 1154 function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) { 1155 const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory)); 1156 const dirPath = resolutionHost.toPath(dir); 1157 return dirPath === rootPath || canWatchDirectoryOrFile(dirPath); 1158 } 1159} 1160 1161function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { 1162 return !!( 1163 (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || 1164 (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath 1165 ); 1166} 1167