1/* @internal */ 2namespace ts { 3 /** 4 * Partial interface of the System thats needed to support the caching of directory structure 5 */ 6 export interface DirectoryStructureHost { 7 fileExists(path: string): boolean; 8 readFile(path: string, encoding?: string): string | undefined; 9 10 // TODO: GH#18217 Optional methods are frequently used as non-optional 11 directoryExists?(path: string): boolean; 12 getDirectories?(path: string): string[]; 13 readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 14 realpath?(path: string): string; 15 16 createDirectory?(path: string): void; 17 writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; 18 } 19 20 interface FileAndDirectoryExistence { 21 fileExists: boolean; 22 directoryExists: boolean; 23 } 24 25 export interface CachedDirectoryStructureHost extends DirectoryStructureHost { 26 useCaseSensitiveFileNames: boolean; 27 28 getDirectories(path: string): string[]; 29 readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 30 31 /** Returns the queried result for the file exists and directory exists if at all it was done */ 32 addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): FileAndDirectoryExistence | undefined; 33 addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; 34 clearCache(): void; 35 } 36 37 interface MutableFileSystemEntries { 38 readonly files: string[]; 39 readonly directories: string[]; 40 } 41 42 export function createCachedDirectoryStructureHost(host: DirectoryStructureHost, currentDirectory: string, useCaseSensitiveFileNames: boolean): CachedDirectoryStructureHost | undefined { 43 if (!host.getDirectories || !host.readDirectory) { 44 return undefined; 45 } 46 47 const cachedReadDirectoryResult = new Map<string, MutableFileSystemEntries>(); 48 const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 49 return { 50 useCaseSensitiveFileNames, 51 fileExists, 52 readFile: (path, encoding) => host.readFile(path, encoding), 53 directoryExists: host.directoryExists && directoryExists, 54 getDirectories, 55 readDirectory, 56 createDirectory: host.createDirectory && createDirectory, 57 writeFile: host.writeFile && writeFile, 58 addOrDeleteFileOrDirectory, 59 addOrDeleteFile, 60 clearCache, 61 realpath: host.realpath && realpath 62 }; 63 64 function toPath(fileName: string) { 65 return ts.toPath(fileName, currentDirectory, getCanonicalFileName); 66 } 67 68 function getCachedFileSystemEntries(rootDirPath: Path): MutableFileSystemEntries | undefined { 69 return cachedReadDirectoryResult.get(ensureTrailingDirectorySeparator(rootDirPath)); 70 } 71 72 function getCachedFileSystemEntriesForBaseDir(path: Path): MutableFileSystemEntries | undefined { 73 return getCachedFileSystemEntries(getDirectoryPath(path)); 74 } 75 76 function getBaseNameOfFileName(fileName: string) { 77 return getBaseFileName(normalizePath(fileName)); 78 } 79 80 function createCachedFileSystemEntries(rootDir: string, rootDirPath: Path) { 81 const resultFromHost: MutableFileSystemEntries = { 82 files: map(host.readDirectory!(rootDir, /*extensions*/ undefined, /*exclude*/ undefined, /*include*/["*.*"]), getBaseNameOfFileName) || [], 83 directories: host.getDirectories!(rootDir) || [] 84 }; 85 86 cachedReadDirectoryResult.set(ensureTrailingDirectorySeparator(rootDirPath), resultFromHost); 87 return resultFromHost; 88 } 89 90 /** 91 * If the readDirectory result was already cached, it returns that 92 * Otherwise gets result from host and caches it. 93 * The host request is done under try catch block to avoid caching incorrect result 94 */ 95 function tryReadDirectory(rootDir: string, rootDirPath: Path): MutableFileSystemEntries | undefined { 96 rootDirPath = ensureTrailingDirectorySeparator(rootDirPath); 97 const cachedResult = getCachedFileSystemEntries(rootDirPath); 98 if (cachedResult) { 99 return cachedResult; 100 } 101 102 try { 103 return createCachedFileSystemEntries(rootDir, rootDirPath); 104 } 105 catch (_e) { 106 // If there is exception to read directories, dont cache the result and direct the calls to host 107 Debug.assert(!cachedReadDirectoryResult.has(ensureTrailingDirectorySeparator(rootDirPath))); 108 return undefined; 109 } 110 } 111 112 function fileNameEqual(name1: string, name2: string) { 113 return getCanonicalFileName(name1) === getCanonicalFileName(name2); 114 } 115 116 function hasEntry(entries: readonly string[], name: string) { 117 return some(entries, file => fileNameEqual(file, name)); 118 } 119 120 function updateFileSystemEntry(entries: string[], baseName: string, isValid: boolean) { 121 if (hasEntry(entries, baseName)) { 122 if (!isValid) { 123 return filterMutate(entries, entry => !fileNameEqual(entry, baseName)); 124 } 125 } 126 else if (isValid) { 127 return entries.push(baseName); 128 } 129 } 130 131 function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { 132 const path = toPath(fileName); 133 const result = getCachedFileSystemEntriesForBaseDir(path); 134 if (result) { 135 updateFilesOfFileSystemEntry(result, getBaseNameOfFileName(fileName), /*fileExists*/ true); 136 } 137 return host.writeFile!(fileName, data, writeByteOrderMark); 138 } 139 140 function fileExists(fileName: string): boolean { 141 const path = toPath(fileName); 142 const result = getCachedFileSystemEntriesForBaseDir(path); 143 return result && hasEntry(result.files, getBaseNameOfFileName(fileName)) || 144 host.fileExists(fileName); 145 } 146 147 function directoryExists(dirPath: string): boolean { 148 const path = toPath(dirPath); 149 return cachedReadDirectoryResult.has(ensureTrailingDirectorySeparator(path)) || host.directoryExists!(dirPath); 150 } 151 152 function createDirectory(dirPath: string) { 153 const path = toPath(dirPath); 154 const result = getCachedFileSystemEntriesForBaseDir(path); 155 const baseFileName = getBaseNameOfFileName(dirPath); 156 if (result) { 157 updateFileSystemEntry(result.directories, baseFileName, /*isValid*/ true); 158 } 159 host.createDirectory!(dirPath); 160 } 161 162 function getDirectories(rootDir: string): string[] { 163 const rootDirPath = toPath(rootDir); 164 const result = tryReadDirectory(rootDir, rootDirPath); 165 if (result) { 166 return result.directories.slice(); 167 } 168 return host.getDirectories!(rootDir); 169 } 170 171 function readDirectory(rootDir: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { 172 const rootDirPath = toPath(rootDir); 173 const result = tryReadDirectory(rootDir, rootDirPath); 174 if (result) { 175 return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries, realpath); 176 } 177 return host.readDirectory!(rootDir, extensions, excludes, includes, depth); 178 179 function getFileSystemEntries(dir: string): FileSystemEntries { 180 const path = toPath(dir); 181 if (path === rootDirPath) { 182 return result!; 183 } 184 return tryReadDirectory(dir, path) || emptyFileSystemEntries; 185 } 186 } 187 188 function realpath(s: string) { 189 return host.realpath ? host.realpath(s) : s; 190 } 191 192 function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) { 193 const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath); 194 if (existingResult) { 195 // Just clear the cache for now 196 // For now just clear the cache, since this could mean that multiple level entries might need to be re-evaluated 197 clearCache(); 198 return undefined; 199 } 200 201 const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath); 202 if (!parentResult) { 203 return undefined; 204 } 205 206 // This was earlier a file (hence not in cached directory contents) 207 // or we never cached the directory containing it 208 209 if (!host.directoryExists) { 210 // Since host doesnt support directory exists, clear the cache as otherwise it might not be same 211 clearCache(); 212 return undefined; 213 } 214 215 const baseName = getBaseNameOfFileName(fileOrDirectory); 216 const fsQueryResult: FileAndDirectoryExistence = { 217 fileExists: host.fileExists(fileOrDirectoryPath), 218 directoryExists: host.directoryExists(fileOrDirectoryPath) 219 }; 220 if (fsQueryResult.directoryExists || hasEntry(parentResult.directories, baseName)) { 221 // Folder added or removed, clear the cache instead of updating the folder and its structure 222 clearCache(); 223 } 224 else { 225 // No need to update the directory structure, just files 226 updateFilesOfFileSystemEntry(parentResult, baseName, fsQueryResult.fileExists); 227 } 228 return fsQueryResult; 229 230 } 231 232 function addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind) { 233 if (eventKind === FileWatcherEventKind.Changed) { 234 return; 235 } 236 237 const parentResult = getCachedFileSystemEntriesForBaseDir(filePath); 238 if (parentResult) { 239 updateFilesOfFileSystemEntry(parentResult, getBaseNameOfFileName(fileName), eventKind === FileWatcherEventKind.Created); 240 } 241 } 242 243 function updateFilesOfFileSystemEntry(parentResult: MutableFileSystemEntries, baseName: string, fileExists: boolean) { 244 updateFileSystemEntry(parentResult.files, baseName, fileExists); 245 } 246 247 function clearCache() { 248 cachedReadDirectoryResult.clear(); 249 } 250 } 251 252 export enum ConfigFileProgramReloadLevel { 253 None, 254 /** Update the file name list from the disk */ 255 Partial, 256 /** Reload completely by re-reading contents of config file from disk and updating program */ 257 Full 258 } 259 260 export interface SharedExtendedConfigFileWatcher<T> extends FileWatcher { 261 fileWatcher: FileWatcher; 262 projects: Set<T>; 263 } 264 265 /** 266 * Updates the map of shared extended config file watches with a new set of extended config files from a base config file of the project 267 */ 268 export function updateSharedExtendedConfigFileWatcher<T>( 269 projectPath: T, 270 parsed: ParsedCommandLine | undefined, 271 extendedConfigFilesMap: ESMap<Path, SharedExtendedConfigFileWatcher<T>>, 272 createExtendedConfigFileWatch: (extendedConfigPath: string, extendedConfigFilePath: Path) => FileWatcher, 273 toPath: (fileName: string) => Path, 274 ) { 275 const extendedConfigs = arrayToMap(parsed?.options.configFile?.extendedSourceFiles || emptyArray, toPath); 276 // remove project from all unrelated watchers 277 extendedConfigFilesMap.forEach((watcher, extendedConfigFilePath) => { 278 if (!extendedConfigs.has(extendedConfigFilePath)) { 279 watcher.projects.delete(projectPath); 280 watcher.close(); 281 } 282 }); 283 // Update the extended config files watcher 284 extendedConfigs.forEach((extendedConfigFileName, extendedConfigFilePath) => { 285 const existing = extendedConfigFilesMap.get(extendedConfigFilePath); 286 if (existing) { 287 existing.projects.add(projectPath); 288 } 289 else { 290 // start watching previously unseen extended config 291 extendedConfigFilesMap.set(extendedConfigFilePath, { 292 projects: new Set([projectPath]), 293 fileWatcher: createExtendedConfigFileWatch(extendedConfigFileName, extendedConfigFilePath), 294 close: () => { 295 const existing = extendedConfigFilesMap.get(extendedConfigFilePath); 296 if (!existing || existing.projects.size !== 0) return; 297 existing.fileWatcher.close(); 298 extendedConfigFilesMap.delete(extendedConfigFilePath); 299 }, 300 }); 301 } 302 }); 303 } 304 305 /** 306 * Updates the existing missing file watches with the new set of missing files after new program is created 307 */ 308 export function updateMissingFilePathsWatch( 309 program: Program, 310 missingFileWatches: ESMap<Path, FileWatcher>, 311 createMissingFileWatch: (missingFilePath: Path) => FileWatcher, 312 ) { 313 const missingFilePaths = program.getMissingFilePaths(); 314 // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` 315 const newMissingFilePathMap = arrayToMap(missingFilePaths, identity, returnTrue); 316 // Update the missing file paths watcher 317 mutateMap( 318 missingFileWatches, 319 newMissingFilePathMap, 320 { 321 // Watch the missing files 322 createNewValue: createMissingFileWatch, 323 // Files that are no longer missing (e.g. because they are no longer required) 324 // should no longer be watched. 325 onDeleteValue: closeFileWatcher 326 } 327 ); 328 } 329 330 export interface WildcardDirectoryWatcher { 331 watcher: FileWatcher; 332 flags: WatchDirectoryFlags; 333 } 334 335 /** 336 * Updates the existing wild card directory watches with the new set of wild card directories from the config file 337 * after new program is created because the config file was reloaded or program was created first time from the config file 338 * Note that there is no need to call this function when the program is updated with additional files without reloading config files, 339 * as wildcard directories wont change unless reloading config file 340 */ 341 export function updateWatchingWildcardDirectories( 342 existingWatchedForWildcards: ESMap<string, WildcardDirectoryWatcher>, 343 wildcardDirectories: ESMap<string, WatchDirectoryFlags>, 344 watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher 345 ) { 346 mutateMap( 347 existingWatchedForWildcards, 348 wildcardDirectories, 349 { 350 // Create new watch and recursive info 351 createNewValue: createWildcardDirectoryWatcher, 352 // Close existing watch thats not needed any more 353 onDeleteValue: closeFileWatcherOf, 354 // Close existing watch that doesnt match in the flags 355 onExistingValue: updateWildcardDirectoryWatcher 356 } 357 ); 358 359 function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher { 360 // Create new watch and recursive info 361 return { 362 watcher: watchDirectory(directory, flags), 363 flags 364 }; 365 } 366 367 function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { 368 // Watcher needs to be updated if the recursive flags dont match 369 if (existingWatcher.flags === flags) { 370 return; 371 } 372 373 existingWatcher.watcher.close(); 374 existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); 375 } 376 } 377 378 export interface IsIgnoredFileFromWildCardWatchingInput { 379 watchedDirPath: Path; 380 fileOrDirectory: string; 381 fileOrDirectoryPath: Path; 382 configFileName: string; 383 options: CompilerOptions; 384 program: BuilderProgram | Program | undefined; 385 extraFileExtensions?: readonly FileExtensionInfo[]; 386 currentDirectory: string; 387 useCaseSensitiveFileNames: boolean; 388 writeLog: (s: string) => void; 389 } 390 /* @internal */ 391 export function isIgnoredFileFromWildCardWatching({ 392 watchedDirPath, fileOrDirectory, fileOrDirectoryPath, 393 configFileName, options, program, extraFileExtensions, 394 currentDirectory, useCaseSensitiveFileNames, 395 writeLog, 396 }: IsIgnoredFileFromWildCardWatchingInput): boolean { 397 const newPath = removeIgnoredPath(fileOrDirectoryPath); 398 if (!newPath) { 399 writeLog(`Project: ${configFileName} Detected ignored path: ${fileOrDirectory}`); 400 return true; 401 } 402 403 fileOrDirectoryPath = newPath; 404 if (fileOrDirectoryPath === watchedDirPath) return false; 405 406 // If the the added or created file or directory is not supported file name, ignore the file 407 // But when watched directory is added/removed, we need to reload the file list 408 if (hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, options, extraFileExtensions)) { 409 writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`); 410 return true; 411 } 412 413 if (isExcludedFile(fileOrDirectory, options.configFile!.configFileSpecs!, getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), useCaseSensitiveFileNames, currentDirectory)) { 414 writeLog(`Project: ${configFileName} Detected excluded file: ${fileOrDirectory}`); 415 return true; 416 } 417 418 if (!program) return false; 419 420 // We want to ignore emit file check if file is not going to be emitted next to source file 421 // In that case we follow config file inclusion rules 422 if (options.outFile || options.outDir) return false; 423 424 // File if emitted next to input needs to be ignored 425 if (isDeclarationFileName(fileOrDirectoryPath)) { 426 // If its declaration directory: its not ignored if not excluded by config 427 if (options.declarationDir) return false; 428 } 429 else if (!fileExtensionIsOneOf(fileOrDirectoryPath, supportedJSExtensions)) { 430 return false; 431 } 432 433 // just check if sourceFile with the name exists 434 const filePathWithoutExtension = removeFileExtension(fileOrDirectoryPath); 435 const realProgram = isBuilderProgram(program) ? program.getProgramOrUndefined() : program; 436 if (hasSourceFile((filePathWithoutExtension + Extension.Ts) as Path) || 437 hasSourceFile((filePathWithoutExtension + Extension.Tsx) as Path) || 438 hasSourceFile((filePathWithoutExtension + Extension.Ets) as Path)) { 439 writeLog(`Project: ${configFileName} Detected output file: ${fileOrDirectory}`); 440 return true; 441 } 442 return false; 443 444 function hasSourceFile(file: Path) { 445 return realProgram ? 446 !!realProgram.getSourceFileByPath(file) : 447 (program as BuilderProgram).getState().fileInfos.has(file); 448 } 449 } 450 451 function isBuilderProgram<T extends BuilderProgram>(program: Program | T): program is T { 452 return !!(program as T).getState; 453 } 454 455 export function isEmittedFileOfProgram(program: Program | undefined, file: string) { 456 if (!program) { 457 return false; 458 } 459 460 return program.isEmittedFile(file); 461 } 462 463 export enum WatchLogLevel { 464 None, 465 TriggerOnly, 466 Verbose 467 } 468 469 export interface WatchFactoryHost { 470 watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; 471 watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; 472 getCurrentDirectory?(): string; 473 useCaseSensitiveFileNames: boolean | (() => boolean); 474 } 475 476 export interface WatchFactory<X, Y = undefined> { 477 watchFile: (file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher; 478 watchDirectory: (directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher; 479 } 480 481 export type GetDetailWatchInfo<X, Y> = (detailInfo1: X, detailInfo2: Y | undefined) => string; 482 export function getWatchFactory<X, Y = undefined>(host: WatchFactoryHost, watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo<X, Y>): WatchFactory<X, Y> { 483 setSysLog(watchLogLevel === WatchLogLevel.Verbose ? log : noop); 484 const plainInvokeFactory: WatchFactory<X, Y> = { 485 watchFile: (file, callback, pollingInterval, options) => host.watchFile(file, callback, pollingInterval, options), 486 watchDirectory: (directory, callback, flags, options) => host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0, options), 487 }; 488 const triggerInvokingFactory: WatchFactory<X, Y> | undefined = watchLogLevel !== WatchLogLevel.None ? 489 { 490 watchFile: createTriggerLoggingAddWatch("watchFile"), 491 watchDirectory: createTriggerLoggingAddWatch("watchDirectory") 492 } : 493 undefined; 494 const factory = watchLogLevel === WatchLogLevel.Verbose ? 495 { 496 watchFile: createFileWatcherWithLogging, 497 watchDirectory: createDirectoryWatcherWithLogging 498 } : 499 triggerInvokingFactory || plainInvokeFactory; 500 const excludeWatcherFactory = watchLogLevel === WatchLogLevel.Verbose ? 501 createExcludeWatcherWithLogging : 502 returnNoopFileWatcher; 503 504 return { 505 watchFile: createExcludeHandlingAddWatch("watchFile"), 506 watchDirectory: createExcludeHandlingAddWatch("watchDirectory") 507 }; 508 509 function createExcludeHandlingAddWatch<T extends keyof WatchFactory<X, Y>>(key: T): WatchFactory<X, Y>[T] { 510 return ( 511 file: string, 512 cb: FileWatcherCallback | DirectoryWatcherCallback, 513 flags: PollingInterval | WatchDirectoryFlags, 514 options: WatchOptions | undefined, 515 detailInfo1: X, 516 detailInfo2?: Y 517 ) => !matchesExclude(file, key === "watchFile" ? options?.excludeFiles : options?.excludeDirectories, useCaseSensitiveFileNames(), host.getCurrentDirectory?.() || "") ? 518 factory[key].call(/*thisArgs*/ undefined, file, cb, flags, options, detailInfo1, detailInfo2) : 519 excludeWatcherFactory(file, flags, options, detailInfo1, detailInfo2); 520 } 521 522 function useCaseSensitiveFileNames() { 523 return typeof host.useCaseSensitiveFileNames === "boolean" ? 524 host.useCaseSensitiveFileNames : 525 host.useCaseSensitiveFileNames(); 526 } 527 528 function createExcludeWatcherWithLogging( 529 file: string, 530 flags: PollingInterval | WatchDirectoryFlags, 531 options: WatchOptions | undefined, 532 detailInfo1: X, 533 detailInfo2?: Y 534 ) { 535 log(`ExcludeWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`); 536 return { 537 close: () => log(`ExcludeWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`) 538 }; 539 } 540 541 function createFileWatcherWithLogging( 542 file: string, 543 cb: FileWatcherCallback, 544 flags: PollingInterval, 545 options: WatchOptions | undefined, 546 detailInfo1: X, 547 detailInfo2?: Y 548 ): FileWatcher { 549 log(`FileWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`); 550 const watcher = triggerInvokingFactory!.watchFile(file, cb, flags, options, detailInfo1, detailInfo2); 551 return { 552 close: () => { 553 log(`FileWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`); 554 watcher.close(); 555 } 556 }; 557 } 558 559 function createDirectoryWatcherWithLogging( 560 file: string, 561 cb: DirectoryWatcherCallback, 562 flags: WatchDirectoryFlags, 563 options: WatchOptions | undefined, 564 detailInfo1: X, 565 detailInfo2?: Y 566 ): FileWatcher { 567 const watchInfo = `DirectoryWatcher:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`; 568 log(watchInfo); 569 const start = timestamp(); 570 const watcher = triggerInvokingFactory!.watchDirectory(file, cb, flags, options, detailInfo1, detailInfo2); 571 const elapsed = timestamp() - start; 572 log(`Elapsed:: ${elapsed}ms ${watchInfo}`); 573 return { 574 close: () => { 575 const watchInfo = `DirectoryWatcher:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`; 576 log(watchInfo); 577 const start = timestamp(); 578 watcher.close(); 579 const elapsed = timestamp() - start; 580 log(`Elapsed:: ${elapsed}ms ${watchInfo}`); 581 } 582 }; 583 } 584 585 function createTriggerLoggingAddWatch<T extends keyof WatchFactory<X, Y>>(key: T): WatchFactory<X, Y>[T] { 586 return ( 587 file: string, 588 cb: FileWatcherCallback | DirectoryWatcherCallback, 589 flags: PollingInterval | WatchDirectoryFlags, 590 options: WatchOptions | undefined, 591 detailInfo1: X, 592 detailInfo2?: Y 593 ) => plainInvokeFactory[key].call(/*thisArgs*/ undefined, file, (...args: any[]) => { 594 const triggerredInfo = `${key === "watchFile" ? "FileWatcher" : "DirectoryWatcher"}:: Triggered with ${args[0]} ${args[1] !== undefined ? args[1] : ""}:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`; 595 log(triggerredInfo); 596 const start = timestamp(); 597 cb.call(/*thisArg*/ undefined, ...args); 598 const elapsed = timestamp() - start; 599 log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`); 600 }, flags, options, detailInfo1, detailInfo2); 601 } 602 603 function getWatchInfo<T>(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) { 604 return `WatchInfo: ${file} ${flags} ${JSON.stringify(options)} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`; 605 } 606 } 607 608 export function getFallbackOptions(options: WatchOptions | undefined): WatchOptions { 609 const fallbackPolling = options?.fallbackPolling; 610 return { 611 watchFile: fallbackPolling !== undefined ? 612 fallbackPolling as unknown as WatchFileKind : 613 WatchFileKind.PriorityPollingInterval 614 }; 615 } 616 617 export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) { 618 objWithWatcher.watcher.close(); 619 } 620} 621