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