1import { 2 AssertionLevel, 3 closeFileWatcher, 4 closeFileWatcherOf, 5 combinePaths, 6 Comparison, 7 contains, 8 containsPath, 9 createGetCanonicalFileName, 10 createMultiMap, 11 Debug, 12 directorySeparator, 13 emptyArray, 14 emptyFileSystemEntries, 15 endsWith, 16 enumerateInsertsAndDeletes, 17 ESMap, 18 FileSystemEntries, 19 getDirectoryPath, 20 getFallbackOptions, 21 getNormalizedAbsolutePath, 22 getRelativePathToDirectoryOrUrl, 23 getRootLength, 24 getStringComparer, 25 isArray, 26 isNodeLikeSystem, 27 isString, 28 Map, 29 mapDefined, 30 matchesExclude, 31 matchFiles, 32 memoize, 33 noop, 34 normalizePath, 35 normalizeSlashes, 36 orderedRemoveItem, 37 Path, 38 perfLogger, 39 PollingWatchKind, 40 RequireResult, 41 resolveJSModule, 42 some, 43 startsWith, 44 stringContains, 45 timestamp, 46 unorderedRemoveItem, 47 WatchDirectoryKind, 48 WatchFileKind, 49 WatchOptions, 50 writeFileEnsuringDirectories, 51} from "./_namespaces/ts"; 52 53declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any; 54declare function clearTimeout(handle: any): void; 55 56/** 57 * djb2 hashing algorithm 58 * http://www.cse.yorku.ca/~oz/hash.html 59 * 60 * @internal 61 */ 62export function generateDjb2Hash(data: string): string { 63 let acc = 5381; 64 for (let i = 0; i < data.length; i++) { 65 acc = ((acc << 5) + acc) + data.charCodeAt(i); 66 } 67 return acc.toString(); 68} 69 70/** 71 * Set a high stack trace limit to provide more information in case of an error. 72 * Called for command-line and server use cases. 73 * Not called if TypeScript is used as a library. 74 * 75 * @internal 76 */ 77export function setStackTraceLimit() { 78 if ((Error as any).stackTraceLimit < 100) { // Also tests that we won't set the property if it doesn't exist. 79 (Error as any).stackTraceLimit = 100; 80 } 81} 82 83export enum FileWatcherEventKind { 84 Created, 85 Changed, 86 Deleted 87} 88 89export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void; 90export type DirectoryWatcherCallback = (fileName: string) => void; 91interface WatchedFile { 92 readonly fileName: string; 93 readonly callback: FileWatcherCallback; 94 mtime: Date; 95} 96 97/** @internal */ 98export enum PollingInterval { 99 High = 2000, 100 Medium = 500, 101 Low = 250 102} 103 104/** @internal */ 105export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher; 106/** @internal */ 107export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher; 108 109/** @internal */ 110export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time 111 112/** @internal */ 113export function getModifiedTime(host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; }, fileName: string) { 114 return host.getModifiedTime(fileName) || missingFileModifiedTime; 115} 116 117interface Levels { 118 Low: number; 119 Medium: number; 120 High: number; 121} 122 123function createPollingIntervalBasedLevels(levels: Levels) { 124 return { 125 [PollingInterval.Low]: levels.Low, 126 [PollingInterval.Medium]: levels.Medium, 127 [PollingInterval.High]: levels.High 128 }; 129} 130 131const defaultChunkLevels: Levels = { Low: 32, Medium: 64, High: 256 }; 132let pollingChunkSize = createPollingIntervalBasedLevels(defaultChunkLevels); 133/** @internal */ 134export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels); 135 136function setCustomPollingValues(system: System) { 137 if (!system.getEnvironmentVariable) { 138 return; 139 } 140 const pollingIntervalChanged = setCustomLevels("TSC_WATCH_POLLINGINTERVAL", PollingInterval); 141 pollingChunkSize = getCustomPollingBasedLevels("TSC_WATCH_POLLINGCHUNKSIZE", defaultChunkLevels) || pollingChunkSize; 142 unchangedPollThresholds = getCustomPollingBasedLevels("TSC_WATCH_UNCHANGEDPOLLTHRESHOLDS", defaultChunkLevels) || unchangedPollThresholds; 143 144 function getLevel(envVar: string, level: keyof Levels) { 145 return system.getEnvironmentVariable(`${envVar}_${level.toUpperCase()}`); 146 } 147 148 function getCustomLevels(baseVariable: string) { 149 let customLevels: Partial<Levels> | undefined; 150 setCustomLevel("Low"); 151 setCustomLevel("Medium"); 152 setCustomLevel("High"); 153 return customLevels; 154 155 function setCustomLevel(level: keyof Levels) { 156 const customLevel = getLevel(baseVariable, level); 157 if (customLevel) { 158 (customLevels || (customLevels = {}))[level] = Number(customLevel); 159 } 160 } 161 } 162 163 function setCustomLevels(baseVariable: string, levels: Levels) { 164 const customLevels = getCustomLevels(baseVariable); 165 if (customLevels) { 166 setLevel("Low"); 167 setLevel("Medium"); 168 setLevel("High"); 169 return true; 170 } 171 return false; 172 173 function setLevel(level: keyof Levels) { 174 levels[level] = customLevels![level] || levels[level]; 175 } 176 } 177 178 function getCustomPollingBasedLevels(baseVariable: string, defaultLevels: Levels) { 179 const customLevels = getCustomLevels(baseVariable); 180 return (pollingIntervalChanged || customLevels) && 181 createPollingIntervalBasedLevels(customLevels ? { ...defaultLevels, ...customLevels } : defaultLevels); 182 } 183} 184 185interface WatchedFileWithIsClosed extends WatchedFile { 186 isClosed?: boolean; 187} 188function pollWatchedFileQueue<T extends WatchedFileWithIsClosed>( 189 host: { getModifiedTime: NonNullable<System["getModifiedTime"]>; }, 190 queue: (T | undefined)[], 191 pollIndex: number, chunkSize: number, 192 callbackOnWatchFileStat?: (watchedFile: T, pollIndex: number, fileChanged: boolean) => void 193) { 194 let definedValueCopyToIndex = pollIndex; 195 // Max visit would be all elements of the queue 196 for (let canVisit = queue.length; chunkSize && canVisit; nextPollIndex(), canVisit--) { 197 const watchedFile = queue[pollIndex]; 198 if (!watchedFile) { 199 continue; 200 } 201 else if (watchedFile.isClosed) { 202 queue[pollIndex] = undefined; 203 continue; 204 } 205 206 // Only files polled count towards chunkSize 207 chunkSize--; 208 const fileChanged = onWatchedFileStat(watchedFile, getModifiedTime(host, watchedFile.fileName)); 209 if (watchedFile.isClosed) { 210 // Closed watcher as part of callback 211 queue[pollIndex] = undefined; 212 continue; 213 } 214 215 callbackOnWatchFileStat?.(watchedFile, pollIndex, fileChanged); 216 // Defragment the queue while we are at it 217 if (queue[pollIndex]) { 218 // Copy this file to the non hole location 219 if (definedValueCopyToIndex < pollIndex) { 220 queue[definedValueCopyToIndex] = watchedFile; 221 queue[pollIndex] = undefined; 222 } 223 definedValueCopyToIndex++; 224 } 225 } 226 227 // Return next poll index 228 return pollIndex; 229 230 function nextPollIndex() { 231 pollIndex++; 232 if (pollIndex === queue.length) { 233 if (definedValueCopyToIndex < pollIndex) { 234 // There are holes from definedValueCopyToIndex to end of queue, change queue size 235 queue.length = definedValueCopyToIndex; 236 } 237 pollIndex = 0; 238 definedValueCopyToIndex = 0; 239 } 240 } 241} 242 243interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed { 244 unchangedPolls: number; 245} 246function createDynamicPriorityPollingWatchFile(host: { 247 getModifiedTime: NonNullable<System["getModifiedTime"]>; 248 setTimeout: NonNullable<System["setTimeout"]>; 249}): HostWatchFile { 250 interface PollingIntervalQueue extends Array<WatchedFileWithUnchangedPolls> { 251 pollingInterval: PollingInterval; 252 pollIndex: number; 253 pollScheduled: boolean; 254 } 255 256 const watchedFiles: WatchedFileWithUnchangedPolls[] = []; 257 const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = []; 258 const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low); 259 const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium); 260 const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High); 261 return watchFile; 262 263 function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher { 264 const file: WatchedFileWithUnchangedPolls = { 265 fileName, 266 callback, 267 unchangedPolls: 0, 268 mtime: getModifiedTime(host, fileName) 269 }; 270 watchedFiles.push(file); 271 272 addToPollingIntervalQueue(file, defaultPollingInterval); 273 return { 274 close: () => { 275 file.isClosed = true; 276 // Remove from watchedFiles 277 unorderedRemoveItem(watchedFiles, file); 278 // Do not update polling interval queue since that will happen as part of polling 279 } 280 }; 281 } 282 283 function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue { 284 const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue; 285 queue.pollingInterval = pollingInterval; 286 queue.pollIndex = 0; 287 queue.pollScheduled = false; 288 return queue; 289 } 290 291 function pollPollingIntervalQueue(queue: PollingIntervalQueue) { 292 queue.pollIndex = pollQueue(queue, queue.pollingInterval, queue.pollIndex, pollingChunkSize[queue.pollingInterval]); 293 // Set the next polling index and timeout 294 if (queue.length) { 295 scheduleNextPoll(queue.pollingInterval); 296 } 297 else { 298 Debug.assert(queue.pollIndex === 0); 299 queue.pollScheduled = false; 300 } 301 } 302 303 function pollLowPollingIntervalQueue(queue: PollingIntervalQueue) { 304 // Always poll complete list of changedFilesInLastPoll 305 pollQueue(changedFilesInLastPoll, PollingInterval.Low, /*pollIndex*/ 0, changedFilesInLastPoll.length); 306 307 // Finally do the actual polling of the queue 308 pollPollingIntervalQueue(queue); 309 // Schedule poll if there are files in changedFilesInLastPoll but no files in the actual queue 310 // as pollPollingIntervalQueue wont schedule for next poll 311 if (!queue.pollScheduled && changedFilesInLastPoll.length) { 312 scheduleNextPoll(PollingInterval.Low); 313 } 314 } 315 316 function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) { 317 return pollWatchedFileQueue( 318 host, 319 queue, 320 pollIndex, 321 chunkSize, 322 onWatchFileStat 323 ); 324 325 function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) { 326 if (fileChanged) { 327 watchedFile.unchangedPolls = 0; 328 // Changed files go to changedFilesInLastPoll queue 329 if (queue !== changedFilesInLastPoll) { 330 queue[pollIndex] = undefined; 331 addChangedFileToLowPollingIntervalQueue(watchedFile); 332 } 333 } 334 else if (watchedFile.unchangedPolls !== unchangedPollThresholds[pollingInterval]) { 335 watchedFile.unchangedPolls++; 336 } 337 else if (queue === changedFilesInLastPoll) { 338 // Restart unchangedPollCount for unchanged file and move to low polling interval queue 339 watchedFile.unchangedPolls = 1; 340 queue[pollIndex] = undefined; 341 addToPollingIntervalQueue(watchedFile, PollingInterval.Low); 342 } 343 else if (pollingInterval !== PollingInterval.High) { 344 watchedFile.unchangedPolls++; 345 queue[pollIndex] = undefined; 346 addToPollingIntervalQueue(watchedFile, pollingInterval === PollingInterval.Low ? PollingInterval.Medium : PollingInterval.High); 347 } 348 } 349 } 350 351 function pollingIntervalQueue(pollingInterval: PollingInterval) { 352 switch (pollingInterval) { 353 case PollingInterval.Low: 354 return lowPollingIntervalQueue; 355 case PollingInterval.Medium: 356 return mediumPollingIntervalQueue; 357 case PollingInterval.High: 358 return highPollingIntervalQueue; 359 } 360 } 361 362 function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) { 363 pollingIntervalQueue(pollingInterval).push(file); 364 scheduleNextPollIfNotAlreadyScheduled(pollingInterval); 365 } 366 367 function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) { 368 changedFilesInLastPoll.push(file); 369 scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low); 370 } 371 372 function scheduleNextPollIfNotAlreadyScheduled(pollingInterval: PollingInterval) { 373 if (!pollingIntervalQueue(pollingInterval).pollScheduled) { 374 scheduleNextPoll(pollingInterval); 375 } 376 } 377 378 function scheduleNextPoll(pollingInterval: PollingInterval) { 379 pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval)); 380 } 381} 382 383function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile { 384 // One file can have multiple watchers 385 const fileWatcherCallbacks = createMultiMap<FileWatcherCallback>(); 386 const dirWatchers = new Map<string, DirectoryWatcher>(); 387 const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames); 388 return nonPollingWatchFile; 389 390 function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher { 391 const filePath = toCanonicalName(fileName); 392 fileWatcherCallbacks.add(filePath, callback); 393 const dirPath = getDirectoryPath(filePath) || "."; 394 const watcher = dirWatchers.get(dirPath) || 395 createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions); 396 watcher.referenceCount++; 397 return { 398 close: () => { 399 if (watcher.referenceCount === 1) { 400 watcher.close(); 401 dirWatchers.delete(dirPath); 402 } 403 else { 404 watcher.referenceCount--; 405 } 406 fileWatcherCallbacks.remove(filePath, callback); 407 } 408 }; 409 } 410 411 function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) { 412 const watcher = fsWatch( 413 dirName, 414 FileSystemEntryKind.Directory, 415 (_eventName: string, relativeFileName, modifiedTime) => { 416 // When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined" 417 if (!isString(relativeFileName)) return; 418 const fileName = getNormalizedAbsolutePath(relativeFileName, dirName); 419 // Some applications save a working file via rename operations 420 const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName)); 421 if (callbacks) { 422 for (const fileCallback of callbacks) { 423 fileCallback(fileName, FileWatcherEventKind.Changed, modifiedTime); 424 } 425 } 426 }, 427 /*recursive*/ false, 428 PollingInterval.Medium, 429 fallbackOptions 430 ) as DirectoryWatcher; 431 watcher.referenceCount = 0; 432 dirWatchers.set(dirPath, watcher); 433 return watcher; 434 } 435} 436 437function createFixedChunkSizePollingWatchFile(host: { 438 getModifiedTime: NonNullable<System["getModifiedTime"]>; 439 setTimeout: NonNullable<System["setTimeout"]>; 440}): HostWatchFile { 441 const watchedFiles: (WatchedFileWithIsClosed | undefined)[] = []; 442 let pollIndex = 0; 443 let pollScheduled: any; 444 return watchFile; 445 446 function watchFile(fileName: string, callback: FileWatcherCallback): FileWatcher { 447 const file: WatchedFileWithIsClosed = { 448 fileName, 449 callback, 450 mtime: getModifiedTime(host, fileName) 451 }; 452 watchedFiles.push(file); 453 scheduleNextPoll(); 454 return { 455 close: () => { 456 file.isClosed = true; 457 unorderedRemoveItem(watchedFiles, file); 458 } 459 }; 460 } 461 462 function pollQueue() { 463 pollScheduled = undefined; 464 pollIndex = pollWatchedFileQueue(host, watchedFiles, pollIndex, pollingChunkSize[PollingInterval.Low]); 465 scheduleNextPoll(); 466 } 467 468 function scheduleNextPoll() { 469 if (!watchedFiles.length || pollScheduled) return; 470 pollScheduled = host.setTimeout(pollQueue, PollingInterval.High); 471 } 472} 473 474interface SingleFileWatcher<T extends FileWatcherCallback | FsWatchCallback>{ 475 watcher: FileWatcher; 476 callbacks: T[]; 477} 478function createSingleWatcherPerName<T extends FileWatcherCallback | FsWatchCallback>( 479 cache: Map<SingleFileWatcher<T>>, 480 useCaseSensitiveFileNames: boolean, 481 name: string, 482 callback: T, 483 createWatcher: (callback: T) => FileWatcher, 484): FileWatcher { 485 const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 486 const path = toCanonicalFileName(name); 487 const existing = cache.get(path); 488 if (existing) { 489 existing.callbacks.push(callback); 490 } 491 else { 492 cache.set(path, { 493 watcher: createWatcher(( 494 // Cant infer types correctly so lets satisfy checker 495 (param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3)) 496 ) as T), 497 callbacks: [callback] 498 }); 499 } 500 501 return { 502 close: () => { 503 const watcher = cache.get(path); 504 // Watcher is not expected to be undefined, but if it is normally its because 505 // exception was thrown somewhere else and watch state is not what it should be 506 if (!watcher) return; 507 if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return; 508 cache.delete(path); 509 closeFileWatcherOf(watcher); 510 } 511 }; 512} 513 514/** 515 * Returns true if file status changed 516 */ 517function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean { 518 const oldTime = watchedFile.mtime.getTime(); 519 const newTime = modifiedTime.getTime(); 520 if (oldTime !== newTime) { 521 watchedFile.mtime = modifiedTime; 522 // Pass modified times so tsc --build can use it 523 watchedFile.callback(watchedFile.fileName, getFileWatcherEventKind(oldTime, newTime), modifiedTime); 524 return true; 525 } 526 527 return false; 528} 529 530/** @internal */ 531export function getFileWatcherEventKind(oldTime: number, newTime: number) { 532 return oldTime === 0 533 ? FileWatcherEventKind.Created 534 : newTime === 0 535 ? FileWatcherEventKind.Deleted 536 : FileWatcherEventKind.Changed; 537} 538 539/** @internal */ 540export const ignoredPaths = ["/node_modules/.", "/oh_modules/.", "/.git", "/.#"]; 541 542let curSysLog: (s: string) => void = noop; // eslint-disable-line prefer-const 543 544/** @internal */ 545export function sysLog(s: string) { 546 return curSysLog(s); 547} 548 549/** @internal */ 550export function setSysLog(logger: typeof sysLog) { 551 curSysLog = logger; 552} 553 554interface RecursiveDirectoryWatcherHost { 555 watchDirectory: HostWatchDirectory; 556 useCaseSensitiveFileNames: boolean; 557 getCurrentDirectory: System["getCurrentDirectory"]; 558 getAccessibleSortedChildDirectories(path: string): readonly string[]; 559 fileSystemEntryExists: FileSystemEntryExists; 560 realpath(s: string): string; 561 setTimeout: NonNullable<System["setTimeout"]>; 562 clearTimeout: NonNullable<System["clearTimeout"]>; 563} 564 565/** 566 * Watch the directory recursively using host provided method to watch child directories 567 * that means if this is recursive watcher, watch the children directories as well 568 * (eg on OS that dont support recursive watch using fs.watch use fs.watchFile) 569 */ 570function createDirectoryWatcherSupportingRecursive({ 571 watchDirectory, 572 useCaseSensitiveFileNames, 573 getCurrentDirectory, 574 getAccessibleSortedChildDirectories, 575 fileSystemEntryExists, 576 realpath, 577 setTimeout, 578 clearTimeout 579}: RecursiveDirectoryWatcherHost): HostWatchDirectory { 580 interface ChildDirectoryWatcher extends FileWatcher { 581 dirName: string; 582 } 583 type ChildWatches = readonly ChildDirectoryWatcher[]; 584 interface HostDirectoryWatcher { 585 watcher: FileWatcher; 586 childWatches: ChildWatches; 587 refCount: number; 588 } 589 590 const cache = new Map<string, HostDirectoryWatcher>(); 591 const callbackCache = createMultiMap<Path, { dirName: string; callback: DirectoryWatcherCallback; }>(); 592 const cacheToUpdateChildWatches = new Map<Path, { dirName: string; options: WatchOptions | undefined; fileNames: string[]; }>(); 593 let timerToUpdateChildWatches: any; 594 595 const filePathComparer = getStringComparer(!useCaseSensitiveFileNames); 596 const toCanonicalFilePath = createGetCanonicalFileName(useCaseSensitiveFileNames); 597 598 return (dirName, callback, recursive, options) => recursive ? 599 createDirectoryWatcher(dirName, options, callback) : 600 watchDirectory(dirName, callback, recursive, options); 601 602 /** 603 * Create the directory watcher for the dirPath. 604 */ 605 function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher { 606 const dirPath = toCanonicalFilePath(dirName) as Path; 607 let directoryWatcher = cache.get(dirPath); 608 if (directoryWatcher) { 609 directoryWatcher.refCount++; 610 } 611 else { 612 directoryWatcher = { 613 watcher: watchDirectory(dirName, fileName => { 614 if (isIgnoredPath(fileName, options)) return; 615 616 if (options?.synchronousWatchDirectory) { 617 // Call the actual callback 618 invokeCallbacks(dirPath, fileName); 619 620 // Iterate through existing children and update the watches if needed 621 updateChildWatches(dirName, dirPath, options); 622 } 623 else { 624 nonSyncUpdateChildWatches(dirName, dirPath, fileName, options); 625 } 626 }, /*recursive*/ false, options), 627 refCount: 1, 628 childWatches: emptyArray 629 }; 630 cache.set(dirPath, directoryWatcher); 631 updateChildWatches(dirName, dirPath, options); 632 } 633 634 const callbackToAdd = callback && { dirName, callback }; 635 if (callbackToAdd) { 636 callbackCache.add(dirPath, callbackToAdd); 637 } 638 639 return { 640 dirName, 641 close: () => { 642 const directoryWatcher = Debug.checkDefined(cache.get(dirPath)); 643 if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd); 644 directoryWatcher.refCount--; 645 646 if (directoryWatcher.refCount) return; 647 648 cache.delete(dirPath); 649 closeFileWatcherOf(directoryWatcher); 650 directoryWatcher.childWatches.forEach(closeFileWatcher); 651 } 652 }; 653 } 654 655 type InvokeMap = ESMap<Path, string[] | true>; 656 function invokeCallbacks(dirPath: Path, fileName: string): void; 657 function invokeCallbacks(dirPath: Path, invokeMap: InvokeMap, fileNames: string[] | undefined): void; 658 function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | InvokeMap, fileNames?: string[]) { 659 let fileName: string | undefined; 660 let invokeMap: InvokeMap | undefined; 661 if (isString(fileNameOrInvokeMap)) { 662 fileName = fileNameOrInvokeMap; 663 } 664 else { 665 invokeMap = fileNameOrInvokeMap; 666 } 667 // Call the actual callback 668 callbackCache.forEach((callbacks, rootDirName) => { 669 if (invokeMap && invokeMap.get(rootDirName) === true) return; 670 if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) { 671 if (invokeMap) { 672 if (fileNames) { 673 const existing = invokeMap.get(rootDirName); 674 if (existing) { 675 (existing as string[]).push(...fileNames); 676 } 677 else { 678 invokeMap.set(rootDirName, fileNames.slice()); 679 } 680 } 681 else { 682 invokeMap.set(rootDirName, true); 683 } 684 } 685 else { 686 callbacks.forEach(({ callback }) => callback(fileName!)); 687 } 688 } 689 }); 690 } 691 692 function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { 693 // Iterate through existing children and update the watches if needed 694 const parentWatcher = cache.get(dirPath); 695 if (parentWatcher && fileSystemEntryExists(dirName, FileSystemEntryKind.Directory)) { 696 // Schedule the update and postpone invoke for callbacks 697 scheduleUpdateChildWatches(dirName, dirPath, fileName, options); 698 return; 699 } 700 701 // Call the actual callbacks and remove child watches 702 invokeCallbacks(dirPath, fileName); 703 removeChildWatches(parentWatcher); 704 } 705 706 function scheduleUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) { 707 const existing = cacheToUpdateChildWatches.get(dirPath); 708 if (existing) { 709 existing.fileNames.push(fileName); 710 } 711 else { 712 cacheToUpdateChildWatches.set(dirPath, { dirName, options, fileNames: [fileName] }); 713 } 714 if (timerToUpdateChildWatches) { 715 clearTimeout(timerToUpdateChildWatches); 716 timerToUpdateChildWatches = undefined; 717 } 718 timerToUpdateChildWatches = setTimeout(onTimerToUpdateChildWatches, 1000); 719 } 720 721 function onTimerToUpdateChildWatches() { 722 timerToUpdateChildWatches = undefined; 723 sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`); 724 const start = timestamp(); 725 const invokeMap = new Map<Path, string[]>(); 726 727 while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) { 728 const result = cacheToUpdateChildWatches.entries().next(); 729 Debug.assert(!result.done); 730 const { value: [dirPath, { dirName, options, fileNames }] } = result; 731 cacheToUpdateChildWatches.delete(dirPath); 732 // Because the child refresh is fresh, we would need to invalidate whole root directory being watched 733 // to ensure that all the changes are reflected at this time 734 const hasChanges = updateChildWatches(dirName, dirPath, options); 735 invokeCallbacks(dirPath, invokeMap, hasChanges ? undefined : fileNames); 736 } 737 738 sysLog(`sysLog:: invokingWatchers:: Elapsed:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`); 739 callbackCache.forEach((callbacks, rootDirName) => { 740 const existing = invokeMap.get(rootDirName); 741 if (existing) { 742 callbacks.forEach(({ callback, dirName }) => { 743 if (isArray(existing)) { 744 existing.forEach(callback); 745 } 746 else { 747 callback(dirName); 748 } 749 }); 750 } 751 }); 752 753 const elapsed = timestamp() - start; 754 sysLog(`sysLog:: Elapsed:: ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`); 755 } 756 757 function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) { 758 if (!parentWatcher) return; 759 const existingChildWatches = parentWatcher.childWatches; 760 parentWatcher.childWatches = emptyArray; 761 for (const childWatcher of existingChildWatches) { 762 childWatcher.close(); 763 removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName))); 764 } 765 } 766 767 function updateChildWatches(parentDir: string, parentDirPath: Path, options: WatchOptions | undefined) { 768 // Iterate through existing children and update the watches if needed 769 const parentWatcher = cache.get(parentDirPath); 770 if (!parentWatcher) return false; 771 let newChildWatches: ChildDirectoryWatcher[] | undefined; 772 const hasChanges = enumerateInsertsAndDeletes<string, ChildDirectoryWatcher>( 773 fileSystemEntryExists(parentDir, FileSystemEntryKind.Directory) ? mapDefined(getAccessibleSortedChildDirectories(parentDir), child => { 774 const childFullName = getNormalizedAbsolutePath(child, parentDir); 775 // Filter our the symbolic link directories since those arent included in recursive watch 776 // which is same behaviour when recursive: true is passed to fs.watch 777 return !isIgnoredPath(childFullName, options) && filePathComparer(childFullName, normalizePath(realpath(childFullName))) === Comparison.EqualTo ? childFullName : undefined; 778 }) : emptyArray, 779 parentWatcher.childWatches, 780 (child, childWatcher) => filePathComparer(child, childWatcher.dirName), 781 createAndAddChildDirectoryWatcher, 782 closeFileWatcher, 783 addChildDirectoryWatcher 784 ); 785 parentWatcher.childWatches = newChildWatches || emptyArray; 786 return hasChanges; 787 788 /** 789 * Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list 790 */ 791 function createAndAddChildDirectoryWatcher(childName: string) { 792 const result = createDirectoryWatcher(childName, options); 793 addChildDirectoryWatcher(result); 794 } 795 796 /** 797 * Add child directory watcher to the new ChildDirectoryWatcher list 798 */ 799 function addChildDirectoryWatcher(childWatcher: ChildDirectoryWatcher) { 800 (newChildWatches || (newChildWatches = [])).push(childWatcher); 801 } 802 } 803 804 function isIgnoredPath(path: string, options: WatchOptions | undefined) { 805 return some(ignoredPaths, searchPath => isInPath(path, searchPath)) || 806 isIgnoredByWatchOptions(path, options, useCaseSensitiveFileNames, getCurrentDirectory); 807 } 808 809 function isInPath(path: string, searchPath: string) { 810 if (stringContains(path, searchPath)) return true; 811 if (useCaseSensitiveFileNames) return false; 812 return stringContains(toCanonicalFilePath(path), searchPath); 813 } 814} 815 816/** @internal */ 817export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined, modifiedTime?: Date) => void; 818/** @internal */ 819export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher; 820/** @internal */ 821export interface FsWatchWorkerWatcher extends FileWatcher { 822 on(eventName: string, listener: () => void): void; 823} 824/** @internal */ 825export type FsWatchWorker = (fileOrDirectory: string, recursive: boolean, callback: FsWatchCallback) => FsWatchWorkerWatcher; 826/** @internal */ 827export const enum FileSystemEntryKind { 828 File, 829 Directory, 830} 831 832function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback { 833 return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime); 834} 835 836function createFsWatchCallbackForFileWatcherCallback( 837 fileName: string, 838 callback: FileWatcherCallback, 839 getModifiedTime: NonNullable<System["getModifiedTime"]> 840): FsWatchCallback { 841 return (eventName, _relativeFileName, modifiedTime) => { 842 if (eventName === "rename") { 843 // Check time stamps rather than file system entry checks 844 modifiedTime ||= getModifiedTime(fileName) || missingFileModifiedTime; 845 callback(fileName, modifiedTime !== missingFileModifiedTime ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted, modifiedTime); 846 } 847 else { 848 // Change 849 callback(fileName, FileWatcherEventKind.Changed, modifiedTime); 850 } 851 }; 852} 853 854function isIgnoredByWatchOptions( 855 pathToCheck: string, 856 options: WatchOptions | undefined, 857 useCaseSensitiveFileNames: boolean, 858 getCurrentDirectory: System["getCurrentDirectory"], 859) { 860 return (options?.excludeDirectories || options?.excludeFiles) && ( 861 matchesExclude(pathToCheck, options?.excludeFiles, useCaseSensitiveFileNames, getCurrentDirectory()) || 862 matchesExclude(pathToCheck, options?.excludeDirectories, useCaseSensitiveFileNames, getCurrentDirectory()) 863 ); 864} 865 866function createFsWatchCallbackForDirectoryWatcherCallback( 867 directoryName: string, 868 callback: DirectoryWatcherCallback, 869 options: WatchOptions | undefined, 870 useCaseSensitiveFileNames: boolean, 871 getCurrentDirectory: System["getCurrentDirectory"], 872): FsWatchCallback { 873 return (eventName, relativeFileName) => { 874 // In watchDirectory we only care about adding and removing files (when event name is 875 // "rename"); changes made within files are handled by corresponding fileWatchers (when 876 // event name is "change") 877 if (eventName === "rename") { 878 // When deleting a file, the passed baseFileName is null 879 const fileName = !relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)); 880 if (!relativeFileName || !isIgnoredByWatchOptions(fileName, options, useCaseSensitiveFileNames, getCurrentDirectory)) { 881 callback(fileName); 882 } 883 } 884 }; 885} 886 887/** @internal */ 888export type FileSystemEntryExists = (fileorDirectrory: string, entryKind: FileSystemEntryKind) => boolean; 889 890/** @internal */ 891export interface CreateSystemWatchFunctions { 892 // Polling watch file 893 pollingWatchFileWorker: HostWatchFile; 894 // For dynamic polling watch file 895 getModifiedTime: NonNullable<System["getModifiedTime"]>; 896 setTimeout: NonNullable<System["setTimeout"]>; 897 clearTimeout: NonNullable<System["clearTimeout"]>; 898 // For fs events : 899 fsWatchWorker: FsWatchWorker; 900 fileSystemEntryExists: FileSystemEntryExists; 901 useCaseSensitiveFileNames: boolean; 902 getCurrentDirectory: System["getCurrentDirectory"]; 903 fsSupportsRecursiveFsWatch: boolean; 904 getAccessibleSortedChildDirectories(path: string): readonly string[]; 905 realpath(s: string): string; 906 // For backward compatibility environment variables 907 tscWatchFile: string | undefined; 908 useNonPollingWatchers?: boolean; 909 tscWatchDirectory: string | undefined; 910 inodeWatching: boolean; 911 sysLog: (s: string) => void; 912} 913 914/** @internal */ 915export function createSystemWatchFunctions({ 916 pollingWatchFileWorker, 917 getModifiedTime, 918 setTimeout, 919 clearTimeout, 920 fsWatchWorker, 921 fileSystemEntryExists, 922 useCaseSensitiveFileNames, 923 getCurrentDirectory, 924 fsSupportsRecursiveFsWatch, 925 getAccessibleSortedChildDirectories, 926 realpath, 927 tscWatchFile, 928 useNonPollingWatchers, 929 tscWatchDirectory, 930 inodeWatching, 931 sysLog, 932}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } { 933 const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>(); 934 const fsWatches = new Map<string, SingleFileWatcher<FsWatchCallback>>(); 935 const fsWatchesRecursive = new Map<string, SingleFileWatcher<FsWatchCallback>>(); 936 let dynamicPollingWatchFile: HostWatchFile | undefined; 937 let fixedChunkSizePollingWatchFile: HostWatchFile | undefined; 938 let nonPollingWatchFile: HostWatchFile | undefined; 939 let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined; 940 let hitSystemWatcherLimit = false; 941 return { 942 watchFile, 943 watchDirectory 944 }; 945 946 function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher { 947 options = updateOptionsForWatchFile(options, useNonPollingWatchers); 948 const watchFileKind = Debug.checkDefined(options.watchFile); 949 switch (watchFileKind) { 950 case WatchFileKind.FixedPollingInterval: 951 return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined); 952 case WatchFileKind.PriorityPollingInterval: 953 return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined); 954 case WatchFileKind.DynamicPriorityPolling: 955 return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined); 956 case WatchFileKind.FixedChunkSizePolling: 957 return ensureFixedChunkSizePollingWatchFile()(fileName, callback, /* pollingInterval */ undefined!, /*options*/ undefined); 958 case WatchFileKind.UseFsEvents: 959 return fsWatch( 960 fileName, 961 FileSystemEntryKind.File, 962 createFsWatchCallbackForFileWatcherCallback(fileName, callback, getModifiedTime), 963 /*recursive*/ false, 964 pollingInterval, 965 getFallbackOptions(options) 966 ); 967 case WatchFileKind.UseFsEventsOnParentDirectory: 968 if (!nonPollingWatchFile) { 969 nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames); 970 } 971 return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options)); 972 default: 973 Debug.assertNever(watchFileKind); 974 } 975 } 976 977 function ensureDynamicPollingWatchFile() { 978 return dynamicPollingWatchFile ||= createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }); 979 } 980 981 function ensureFixedChunkSizePollingWatchFile() { 982 return fixedChunkSizePollingWatchFile ||= createFixedChunkSizePollingWatchFile({ getModifiedTime, setTimeout }); 983 } 984 985 function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions { 986 if (options && options.watchFile !== undefined) return options; 987 switch (tscWatchFile) { 988 case "PriorityPollingInterval": 989 // Use polling interval based on priority when create watch using host.watchFile 990 return { watchFile: WatchFileKind.PriorityPollingInterval }; 991 case "DynamicPriorityPolling": 992 // Use polling interval but change the interval depending on file changes and their default polling interval 993 return { watchFile: WatchFileKind.DynamicPriorityPolling }; 994 case "UseFsEvents": 995 // Use notifications from FS to watch with falling back to fs.watchFile 996 return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options); 997 case "UseFsEventsWithFallbackDynamicPolling": 998 // Use notifications from FS to watch with falling back to dynamic watch file 999 return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options); 1000 case "UseFsEventsOnParentDirectory": 1001 useNonPollingWatchers = true; 1002 // fall through 1003 default: 1004 return useNonPollingWatchers ? 1005 // Use notifications from FS to watch with falling back to fs.watchFile 1006 generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) : 1007 // Default to using fs events 1008 { watchFile: WatchFileKind.UseFsEvents }; 1009 } 1010 } 1011 1012 function generateWatchFileOptions( 1013 watchFile: WatchFileKind, 1014 fallbackPolling: PollingWatchKind, 1015 options: WatchOptions | undefined 1016 ): WatchOptions { 1017 const defaultFallbackPolling = options?.fallbackPolling; 1018 return { 1019 watchFile, 1020 fallbackPolling: defaultFallbackPolling === undefined ? 1021 fallbackPolling : 1022 defaultFallbackPolling 1023 }; 1024 } 1025 1026 function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { 1027 if (fsSupportsRecursiveFsWatch) { 1028 return fsWatch( 1029 directoryName, 1030 FileSystemEntryKind.Directory, 1031 createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), 1032 recursive, 1033 PollingInterval.Medium, 1034 getFallbackOptions(options) 1035 ); 1036 } 1037 1038 if (!hostRecursiveDirectoryWatcher) { 1039 hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({ 1040 useCaseSensitiveFileNames, 1041 getCurrentDirectory, 1042 fileSystemEntryExists, 1043 getAccessibleSortedChildDirectories, 1044 watchDirectory: nonRecursiveWatchDirectory, 1045 realpath, 1046 setTimeout, 1047 clearTimeout 1048 }); 1049 } 1050 return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options); 1051 } 1052 1053 function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher { 1054 Debug.assert(!recursive); 1055 const watchDirectoryOptions = updateOptionsForWatchDirectory(options); 1056 const watchDirectoryKind = Debug.checkDefined(watchDirectoryOptions.watchDirectory); 1057 switch (watchDirectoryKind) { 1058 case WatchDirectoryKind.FixedPollingInterval: 1059 return pollingWatchFile( 1060 directoryName, 1061 () => callback(directoryName), 1062 PollingInterval.Medium, 1063 /*options*/ undefined 1064 ); 1065 case WatchDirectoryKind.DynamicPriorityPolling: 1066 return ensureDynamicPollingWatchFile()( 1067 directoryName, 1068 () => callback(directoryName), 1069 PollingInterval.Medium, 1070 /*options*/ undefined 1071 ); 1072 case WatchDirectoryKind.FixedChunkSizePolling: 1073 return ensureFixedChunkSizePollingWatchFile()( 1074 directoryName, 1075 () => callback(directoryName), 1076 /* pollingInterval */ undefined!, 1077 /*options*/ undefined 1078 ); 1079 case WatchDirectoryKind.UseFsEvents: 1080 return fsWatch( 1081 directoryName, 1082 FileSystemEntryKind.Directory, 1083 createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback, options, useCaseSensitiveFileNames, getCurrentDirectory), 1084 recursive, 1085 PollingInterval.Medium, 1086 getFallbackOptions(watchDirectoryOptions) 1087 ); 1088 default: 1089 Debug.assertNever(watchDirectoryKind); 1090 } 1091 } 1092 1093 function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions { 1094 if (options && options.watchDirectory !== undefined) return options; 1095 switch (tscWatchDirectory) { 1096 case "RecursiveDirectoryUsingFsWatchFile": 1097 // Use polling interval based on priority when create watch using host.watchFile 1098 return { watchDirectory: WatchDirectoryKind.FixedPollingInterval }; 1099 case "RecursiveDirectoryUsingDynamicPriorityPolling": 1100 // Use polling interval but change the interval depending on file changes and their default polling interval 1101 return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling }; 1102 default: 1103 const defaultFallbackPolling = options?.fallbackPolling; 1104 return { 1105 watchDirectory: WatchDirectoryKind.UseFsEvents, 1106 fallbackPolling: defaultFallbackPolling !== undefined ? 1107 defaultFallbackPolling : 1108 undefined 1109 }; 1110 } 1111 } 1112 1113 function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) { 1114 return createSingleWatcherPerName( 1115 pollingWatches, 1116 useCaseSensitiveFileNames, 1117 fileName, 1118 callback, 1119 cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options), 1120 ); 1121 } 1122 function fsWatch( 1123 fileOrDirectory: string, 1124 entryKind: FileSystemEntryKind, 1125 callback: FsWatchCallback, 1126 recursive: boolean, 1127 fallbackPollingInterval: PollingInterval, 1128 fallbackOptions: WatchOptions | undefined 1129 ): FileWatcher { 1130 return createSingleWatcherPerName( 1131 recursive ? fsWatchesRecursive : fsWatches, 1132 useCaseSensitiveFileNames, 1133 fileOrDirectory, 1134 callback, 1135 cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions), 1136 ); 1137 } 1138 1139 function fsWatchHandlingExistenceOnHost( 1140 fileOrDirectory: string, 1141 entryKind: FileSystemEntryKind, 1142 callback: FsWatchCallback, 1143 recursive: boolean, 1144 fallbackPollingInterval: PollingInterval, 1145 fallbackOptions: WatchOptions | undefined 1146 ): FileWatcher { 1147 let lastDirectoryPartWithDirectorySeparator: string | undefined; 1148 let lastDirectoryPart: string | undefined; 1149 if (inodeWatching) { 1150 lastDirectoryPartWithDirectorySeparator = fileOrDirectory.substring(fileOrDirectory.lastIndexOf(directorySeparator)); 1151 lastDirectoryPart = lastDirectoryPartWithDirectorySeparator.slice(directorySeparator.length); 1152 } 1153 /** Watcher for the file system entry depending on whether it is missing or present */ 1154 let watcher: FileWatcher | undefined = !fileSystemEntryExists(fileOrDirectory, entryKind) ? 1155 watchMissingFileSystemEntry() : 1156 watchPresentFileSystemEntry(); 1157 return { 1158 close: () => { 1159 // Close the watcher (either existing file system entry watcher or missing file system entry watcher) 1160 if (watcher) { 1161 watcher.close(); 1162 watcher = undefined; 1163 } 1164 } 1165 }; 1166 1167 function updateWatcher(createWatcher: () => FileWatcher) { 1168 // If watcher is not closed, update it 1169 if (watcher) { 1170 sysLog(`sysLog:: ${fileOrDirectory}:: Changing watcher to ${createWatcher === watchPresentFileSystemEntry ? "Present" : "Missing"}FileSystemEntryWatcher`); 1171 watcher.close(); 1172 watcher = createWatcher(); 1173 } 1174 } 1175 1176 /** 1177 * Watch the file or directory that is currently present 1178 * and when the watched file or directory is deleted, switch to missing file system entry watcher 1179 */ 1180 function watchPresentFileSystemEntry(): FileWatcher { 1181 if (hitSystemWatcherLimit) { 1182 sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to watchFile`); 1183 return watchPresentFileSystemEntryWithFsWatchFile(); 1184 } 1185 try { 1186 const presentWatcher = fsWatchWorker( 1187 fileOrDirectory, 1188 recursive, 1189 inodeWatching ? 1190 callbackChangingToMissingFileSystemEntry : 1191 callback 1192 ); 1193 // Watch the missing file or directory or error 1194 presentWatcher.on("error", () => { 1195 callback("rename", ""); 1196 updateWatcher(watchMissingFileSystemEntry); 1197 }); 1198 return presentWatcher; 1199 } 1200 catch (e) { 1201 // Catch the exception and use polling instead 1202 // Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point 1203 // so instead of throwing error, use fs.watchFile 1204 hitSystemWatcherLimit ||= e.code === "ENOSPC"; 1205 sysLog(`sysLog:: ${fileOrDirectory}:: Changing to watchFile`); 1206 return watchPresentFileSystemEntryWithFsWatchFile(); 1207 } 1208 } 1209 1210 function callbackChangingToMissingFileSystemEntry(event: "rename" | "change", relativeName: string | undefined) { 1211 // In some scenarios, file save operation fires event with fileName.ext~ instead of fileName.ext 1212 // To ensure we see the file going missing and coming back up (file delete and then recreated) 1213 // and watches being updated correctly we are calling back with fileName.ext as well as fileName.ext~ 1214 // The worst is we have fired event that was not needed but we wont miss any changes 1215 // especially in cases where file goes missing and watches wrong inode 1216 let originalRelativeName: string | undefined; 1217 if (relativeName && endsWith(relativeName, "~")) { 1218 originalRelativeName = relativeName; 1219 relativeName = relativeName.slice(0, relativeName.length - 1); 1220 } 1221 // because relativeName is not guaranteed to be correct we need to check on each rename with few combinations 1222 // Eg on ubuntu while watching app/node_modules the relativeName is "node_modules" which is neither relative nor full path 1223 if (event === "rename" && 1224 (!relativeName || 1225 relativeName === lastDirectoryPart || 1226 endsWith(relativeName, lastDirectoryPartWithDirectorySeparator!))) { 1227 const modifiedTime = getModifiedTime(fileOrDirectory) || missingFileModifiedTime; 1228 if (originalRelativeName) callback(event, originalRelativeName, modifiedTime); 1229 callback(event, relativeName, modifiedTime); 1230 if (inodeWatching) { 1231 // If this was rename event, inode has changed means we need to update watcher 1232 updateWatcher(modifiedTime === missingFileModifiedTime ? watchMissingFileSystemEntry : watchPresentFileSystemEntry); 1233 } 1234 else if (modifiedTime === missingFileModifiedTime) { 1235 updateWatcher(watchMissingFileSystemEntry); 1236 } 1237 } 1238 else { 1239 if (originalRelativeName) callback(event, originalRelativeName); 1240 callback(event, relativeName); 1241 } 1242 } 1243 1244 /** 1245 * Watch the file or directory using fs.watchFile since fs.watch threw exception 1246 * Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point 1247 */ 1248 function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher { 1249 return watchFile( 1250 fileOrDirectory, 1251 createFileWatcherCallback(callback), 1252 fallbackPollingInterval, 1253 fallbackOptions 1254 ); 1255 } 1256 1257 /** 1258 * Watch the file or directory that is missing 1259 * and switch to existing file or directory when the missing filesystem entry is created 1260 */ 1261 function watchMissingFileSystemEntry(): FileWatcher { 1262 return watchFile( 1263 fileOrDirectory, 1264 (_fileName, eventKind, modifiedTime) => { 1265 if (eventKind === FileWatcherEventKind.Created) { 1266 modifiedTime ||= getModifiedTime(fileOrDirectory) || missingFileModifiedTime; 1267 if (modifiedTime !== missingFileModifiedTime) { 1268 callback("rename", "", modifiedTime); 1269 // Call the callback for current file or directory 1270 // For now it could be callback for the inner directory creation, 1271 // but just return current directory, better than current no-op 1272 updateWatcher(watchPresentFileSystemEntry); 1273 } 1274 } 1275 }, 1276 fallbackPollingInterval, 1277 fallbackOptions 1278 ); 1279 } 1280 } 1281} 1282 1283/** 1284 * patch writefile to create folder before writing the file 1285 * 1286 * @internal 1287 */ 1288export function patchWriteFileEnsuringDirectory(sys: System) { 1289 // patch writefile to create folder before writing the file 1290 const originalWriteFile = sys.writeFile; 1291 sys.writeFile = (path, data, writeBom) => 1292 writeFileEnsuringDirectories( 1293 path, 1294 data, 1295 !!writeBom, 1296 (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), 1297 path => sys.createDirectory(path), 1298 path => sys.directoryExists(path)); 1299} 1300 1301/** @internal */ 1302export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; 1303 1304/** @internal */ 1305export interface NodeBuffer extends Uint8Array { 1306 constructor: any; 1307 write(str: string, encoding?: BufferEncoding): number; 1308 write(str: string, offset: number, encoding?: BufferEncoding): number; 1309 write(str: string, offset: number, length: number, encoding?: BufferEncoding): number; 1310 toString(encoding?: string, start?: number, end?: number): string; 1311 toJSON(): { type: "Buffer"; data: number[] }; 1312 equals(otherBuffer: Uint8Array): boolean; 1313 compare( 1314 otherBuffer: Uint8Array, 1315 targetStart?: number, 1316 targetEnd?: number, 1317 sourceStart?: number, 1318 sourceEnd?: number 1319 ): number; 1320 copy(targetBuffer: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; 1321 slice(begin?: number, end?: number): Buffer; 1322 subarray(begin?: number, end?: number): Buffer; 1323 writeUIntLE(value: number, offset: number, byteLength: number): number; 1324 writeUIntBE(value: number, offset: number, byteLength: number): number; 1325 writeIntLE(value: number, offset: number, byteLength: number): number; 1326 writeIntBE(value: number, offset: number, byteLength: number): number; 1327 readUIntLE(offset: number, byteLength: number): number; 1328 readUIntBE(offset: number, byteLength: number): number; 1329 readIntLE(offset: number, byteLength: number): number; 1330 readIntBE(offset: number, byteLength: number): number; 1331 readUInt8(offset: number): number; 1332 readUInt16LE(offset: number): number; 1333 readUInt16BE(offset: number): number; 1334 readUInt32LE(offset: number): number; 1335 readUInt32BE(offset: number): number; 1336 readInt8(offset: number): number; 1337 readInt16LE(offset: number): number; 1338 readInt16BE(offset: number): number; 1339 readInt32LE(offset: number): number; 1340 readInt32BE(offset: number): number; 1341 readFloatLE(offset: number): number; 1342 readFloatBE(offset: number): number; 1343 readDoubleLE(offset: number): number; 1344 readDoubleBE(offset: number): number; 1345 reverse(): this; 1346 swap16(): Buffer; 1347 swap32(): Buffer; 1348 swap64(): Buffer; 1349 writeUInt8(value: number, offset: number): number; 1350 writeUInt16LE(value: number, offset: number): number; 1351 writeUInt16BE(value: number, offset: number): number; 1352 writeUInt32LE(value: number, offset: number): number; 1353 writeUInt32BE(value: number, offset: number): number; 1354 writeInt8(value: number, offset: number): number; 1355 writeInt16LE(value: number, offset: number): number; 1356 writeInt16BE(value: number, offset: number): number; 1357 writeInt32LE(value: number, offset: number): number; 1358 writeInt32BE(value: number, offset: number): number; 1359 writeFloatLE(value: number, offset: number): number; 1360 writeFloatBE(value: number, offset: number): number; 1361 writeDoubleLE(value: number, offset: number): number; 1362 writeDoubleBE(value: number, offset: number): number; 1363 readBigUInt64BE?(offset?: number): bigint; 1364 readBigUInt64LE?(offset?: number): bigint; 1365 readBigInt64BE?(offset?: number): bigint; 1366 readBigInt64LE?(offset?: number): bigint; 1367 writeBigInt64BE?(value: bigint, offset?: number): number; 1368 writeBigInt64LE?(value: bigint, offset?: number): number; 1369 writeBigUInt64BE?(value: bigint, offset?: number): number; 1370 writeBigUInt64LE?(value: bigint, offset?: number): number; 1371 fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this; 1372 indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; 1373 lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number; 1374 entries(): IterableIterator<[number, number]>; 1375 includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean; 1376 keys(): IterableIterator<number>; 1377 values(): IterableIterator<number>; 1378} 1379 1380/** @internal */ 1381export interface Buffer extends NodeBuffer { } 1382 1383// TODO: GH#18217 Methods on System are often used as if they are certainly defined 1384export interface System { 1385 args: string[]; 1386 newLine: string; 1387 useCaseSensitiveFileNames: boolean; 1388 write(s: string): void; 1389 writeOutputIsTTY?(): boolean; 1390 getWidthOfTerminal?(): number; 1391 readFile(path: string, encoding?: string): string | undefined; 1392 getFileSize?(path: string): number; 1393 writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; 1394 1395 /** 1396 * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that 1397 * use native OS file watching 1398 */ 1399 watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher; 1400 watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher; 1401 resolvePath(path: string): string; 1402 fileExists(path: string): boolean; 1403 directoryExists(path: string): boolean; 1404 createDirectory(path: string): void; 1405 getExecutingFilePath(): string; 1406 getCurrentDirectory(): string; 1407 getDirectories(path: string): string[]; 1408 readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 1409 getModifiedTime?(path: string): Date | undefined; 1410 setModifiedTime?(path: string, time: Date): void; 1411 deleteFile?(path: string): void; 1412 /** 1413 * A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm) 1414 */ 1415 createHash?(data: string): string; 1416 /** This must be cryptographically secure. Only implement this method using `crypto.createHash("sha256")`. */ 1417 createSHA256Hash?(data: string): string; 1418 getMemoryUsage?(): number; 1419 exit(exitCode?: number): void; 1420 /** @internal */ enableCPUProfiler?(path: string, continuation: () => void): boolean; 1421 /** @internal */ disableCPUProfiler?(continuation: () => void): boolean; 1422 /** @internal */ cpuProfilingEnabled?(): boolean; 1423 realpath?(path: string): string; 1424 /** @internal */ getEnvironmentVariable(name: string): string; 1425 /** @internal */ tryEnableSourceMapsForHost?(): void; 1426 /** @internal */ debugMode?: boolean; 1427 setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; 1428 clearTimeout?(timeoutId: any): void; 1429 clearScreen?(): void; 1430 /** @internal */ setBlocking?(): void; 1431 base64decode?(input: string): string; 1432 base64encode?(input: string): string; 1433 /** @internal */ bufferFrom?(input: string, encoding?: string): Buffer; 1434 /** @internal */ require?(baseDir: string, moduleName: string): RequireResult; 1435 1436 // For testing 1437 /** @internal */ now?(): Date; 1438 /** @internal */ disableUseFileVersionAsSignature?: boolean; 1439 /** @internal */ storeFilesChangingSignatureDuringEmit?: boolean; 1440} 1441 1442export interface FileWatcher { 1443 close(): void; 1444} 1445 1446interface DirectoryWatcher extends FileWatcher { 1447 referenceCount: number; 1448} 1449 1450declare const require: any; 1451declare const process: any; 1452declare const global: any; 1453declare const __filename: string; 1454declare const __dirname: string; 1455 1456export function getNodeMajorVersion(): number | undefined { 1457 if (typeof process === "undefined") { 1458 return undefined; 1459 } 1460 const version: string = process.version; 1461 if (!version) { 1462 return undefined; 1463 } 1464 const dot = version.indexOf("."); 1465 if (dot === -1) { 1466 return undefined; 1467 } 1468 return parseInt(version.substring(1, dot)); 1469} 1470 1471// TODO: GH#18217 this is used as if it's certainly defined in many places. 1472// eslint-disable-next-line prefer-const 1473export let sys: System = (() => { 1474 // NodeJS detects "\uFEFF" at the start of the string and *replaces* it with the actual 1475 // byte order mark from the specified encoding. Using any other byte order mark does 1476 // not actually work. 1477 const byteOrderMarkIndicator = "\uFEFF"; 1478 1479 function getNodeSystem(): System { 1480 const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/; 1481 const _fs: typeof import("fs") = require("fs"); 1482 const _path: typeof import("path") = require("path"); 1483 const _os = require("os"); 1484 // crypto can be absent on reduced node installations 1485 let _crypto: typeof import("crypto") | undefined; 1486 try { 1487 _crypto = require("crypto"); 1488 } 1489 catch { 1490 _crypto = undefined; 1491 } 1492 let activeSession: import("inspector").Session | "stopping" | undefined; 1493 let profilePath = "./profile.cpuprofile"; 1494 1495 const Buffer: { 1496 new (input: string, encoding?: string): any; 1497 from?(input: string, encoding?: string): any; 1498 } = require("buffer").Buffer; 1499 1500 const nodeVersion = getNodeMajorVersion(); 1501 const isNode4OrLater = nodeVersion! >= 4; 1502 const isLinuxOrMacOs = process.platform === "linux" || process.platform === "darwin"; 1503 1504 const platform: string = _os.platform(); 1505 const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); 1506 const fsRealpath = !!_fs.realpathSync.native ? process.platform === "win32" ? fsRealPathHandlingLongPath : _fs.realpathSync.native : _fs.realpathSync; 1507 1508 // If our filename is "sys.js", then we are executing unbundled on the raw tsc output. 1509 // In that case, simulate a faked path in the directory where a bundle would normally 1510 // appear (e.g. the directory containing lib.*.d.ts files). 1511 // 1512 // Note that if we ever emit as files like cjs/mjs, this check will be wrong. 1513 const executingFilePath = __filename.endsWith("sys.js") ? _path.join(_path.dirname(__dirname), "__fake__.js") : __filename; 1514 1515 const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin"); 1516 const getCurrentDirectory = memoize(() => process.cwd()); 1517 const { watchFile, watchDirectory } = createSystemWatchFunctions({ 1518 pollingWatchFileWorker: fsWatchFileWorker, 1519 getModifiedTime, 1520 setTimeout, 1521 clearTimeout, 1522 fsWatchWorker, 1523 useCaseSensitiveFileNames, 1524 getCurrentDirectory, 1525 fileSystemEntryExists, 1526 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows 1527 // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) 1528 fsSupportsRecursiveFsWatch, 1529 getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories, 1530 realpath, 1531 tscWatchFile: process.env.TSC_WATCHFILE, 1532 useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER, 1533 tscWatchDirectory: process.env.TSC_WATCHDIRECTORY, 1534 inodeWatching: isLinuxOrMacOs, 1535 sysLog, 1536 }); 1537 const nodeSystem: System = { 1538 args: process.argv.slice(2), 1539 newLine: _os.EOL, 1540 useCaseSensitiveFileNames, 1541 write(s: string): void { 1542 process.stdout.write(s); 1543 }, 1544 getWidthOfTerminal() { 1545 return process.stdout.columns; 1546 }, 1547 writeOutputIsTTY() { 1548 return process.stdout.isTTY; 1549 }, 1550 readFile, 1551 writeFile, 1552 watchFile, 1553 watchDirectory, 1554 resolvePath: path => _path.resolve(path), 1555 fileExists, 1556 directoryExists, 1557 createDirectory(directoryName: string) { 1558 if (!nodeSystem.directoryExists(directoryName)) { 1559 // Wrapped in a try-catch to prevent crashing if we are in a race 1560 // with another copy of ourselves to create the same directory 1561 try { 1562 _fs.mkdirSync(directoryName); 1563 } 1564 catch (e) { 1565 if (e.code !== "EEXIST") { 1566 // Failed for some other reason (access denied?); still throw 1567 throw e; 1568 } 1569 } 1570 } 1571 }, 1572 getExecutingFilePath() { 1573 return executingFilePath; 1574 }, 1575 getCurrentDirectory, 1576 getDirectories, 1577 getEnvironmentVariable(name: string) { 1578 return process.env[name] || ""; 1579 }, 1580 readDirectory, 1581 getModifiedTime, 1582 setModifiedTime, 1583 deleteFile, 1584 createHash: _crypto ? createSHA256Hash : generateDjb2Hash, 1585 createSHA256Hash: _crypto ? createSHA256Hash : undefined, 1586 getMemoryUsage() { 1587 if (global.gc) { 1588 global.gc(); 1589 } 1590 return process.memoryUsage().heapUsed; 1591 }, 1592 getFileSize(path) { 1593 try { 1594 const stat = statSync(path); 1595 if (stat?.isFile()) { 1596 return stat.size; 1597 } 1598 } 1599 catch { /*ignore*/ } 1600 return 0; 1601 }, 1602 exit(exitCode?: number): void { 1603 disableCPUProfiler(() => process.exit(exitCode)); 1604 }, 1605 enableCPUProfiler, 1606 disableCPUProfiler, 1607 cpuProfilingEnabled: () => !!activeSession || contains(process.execArgv, "--cpu-prof") || contains(process.execArgv, "--prof"), 1608 realpath, 1609 debugMode: !!process.env.NODE_INSPECTOR_IPC || !!process.env.VSCODE_INSPECTOR_OPTIONS || some(process.execArgv as string[], arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)), 1610 tryEnableSourceMapsForHost() { 1611 try { 1612 (require("source-map-support") as typeof import("source-map-support")).install(); 1613 } 1614 catch { 1615 // Could not enable source maps. 1616 } 1617 }, 1618 setTimeout, 1619 clearTimeout, 1620 clearScreen: () => { 1621 process.stdout.write("\x1Bc"); 1622 }, 1623 setBlocking: () => { 1624 if (process.stdout && process.stdout._handle && process.stdout._handle.setBlocking) { 1625 process.stdout._handle.setBlocking(true); 1626 } 1627 }, 1628 bufferFrom, 1629 base64decode: input => bufferFrom(input, "base64").toString("utf8"), 1630 base64encode: input => bufferFrom(input).toString("base64"), 1631 require: (baseDir, moduleName) => { 1632 try { 1633 const modulePath = resolveJSModule(moduleName, baseDir, nodeSystem); 1634 return { module: require(modulePath), modulePath, error: undefined }; 1635 } 1636 catch (error) { 1637 return { module: undefined, modulePath: undefined, error }; 1638 } 1639 } 1640 }; 1641 return nodeSystem; 1642 1643 /** 1644 * `throwIfNoEntry` was added so recently that it's not in the node types. 1645 * This helper encapsulates the mitigating usage of `any`. 1646 * See https://github.com/nodejs/node/pull/33716 1647 */ 1648 function statSync(path: string): import("fs").Stats | undefined { 1649 // throwIfNoEntry will be ignored by older versions of node 1650 return (_fs as any).statSync(path, { throwIfNoEntry: false }); 1651 } 1652 1653 /** 1654 * Uses the builtin inspector APIs to capture a CPU profile 1655 * See https://nodejs.org/api/inspector.html#inspector_example_usage for details 1656 */ 1657 function enableCPUProfiler(path: string, cb: () => void) { 1658 if (activeSession) { 1659 cb(); 1660 return false; 1661 } 1662 const inspector: typeof import("inspector") = require("inspector"); 1663 if (!inspector || !inspector.Session) { 1664 cb(); 1665 return false; 1666 } 1667 const session = new inspector.Session(); 1668 session.connect(); 1669 1670 session.post("Profiler.enable", () => { 1671 session.post("Profiler.start", () => { 1672 activeSession = session; 1673 profilePath = path; 1674 cb(); 1675 }); 1676 }); 1677 return true; 1678 } 1679 1680 /** 1681 * Strips non-TS paths from the profile, so users with private projects shouldn't 1682 * need to worry about leaking paths by submitting a cpu profile to us 1683 */ 1684 function cleanupPaths(profile: import("inspector").Profiler.Profile) { 1685 let externalFileCounter = 0; 1686 const remappedPaths = new Map<string, string>(); 1687 const normalizedDir = normalizeSlashes(_path.dirname(executingFilePath)); 1688 // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls 1689 const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`; 1690 for (const node of profile.nodes) { 1691 if (node.callFrame.url) { 1692 const url = normalizeSlashes(node.callFrame.url); 1693 if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) { 1694 node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true); 1695 } 1696 else if (!nativePattern.test(url)) { 1697 node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!; 1698 externalFileCounter++; 1699 } 1700 } 1701 } 1702 return profile; 1703 } 1704 1705 function disableCPUProfiler(cb: () => void) { 1706 if (activeSession && activeSession !== "stopping") { 1707 const s = activeSession; 1708 activeSession.post("Profiler.stop", (err, { profile }) => { 1709 if (!err) { 1710 try { 1711 if (statSync(profilePath)?.isDirectory()) { 1712 profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`); 1713 } 1714 } 1715 catch { 1716 // do nothing and ignore fallible fs operation 1717 } 1718 try { 1719 _fs.mkdirSync(_path.dirname(profilePath), { recursive: true }); 1720 } 1721 catch { 1722 // do nothing and ignore fallible fs operation 1723 } 1724 _fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile))); 1725 } 1726 activeSession = undefined; 1727 s.disconnect(); 1728 cb(); 1729 }); 1730 activeSession = "stopping"; 1731 return true; 1732 } 1733 else { 1734 cb(); 1735 return false; 1736 } 1737 } 1738 1739 function bufferFrom(input: string, encoding?: string): Buffer { 1740 // See https://github.com/Microsoft/TypeScript/issues/25652 1741 return Buffer.from && (Buffer.from as Function) !== Int8Array.from 1742 ? Buffer.from(input, encoding) 1743 : new Buffer(input, encoding); 1744 } 1745 1746 function isFileSystemCaseSensitive(): boolean { 1747 // win32\win64 are case insensitive platforms 1748 if (platform === "win32" || platform === "win64") { 1749 return false; 1750 } 1751 // If this file exists under a different case, we must be case-insensitve. 1752 return !fileExists(swapCase(__filename)); 1753 } 1754 1755 /** Convert all lowercase chars to uppercase, and vice-versa */ 1756 function swapCase(s: string): string { 1757 return s.replace(/\w/g, (ch) => { 1758 const up = ch.toUpperCase(); 1759 return ch === up ? ch.toLowerCase() : up; 1760 }); 1761 } 1762 1763 function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher { 1764 _fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged); 1765 let eventKind: FileWatcherEventKind; 1766 return { 1767 close: () => _fs.unwatchFile(fileName, fileChanged) 1768 }; 1769 1770 function fileChanged(curr: import("fs").Stats, prev: import("fs").Stats) { 1771 // previous event kind check is to ensure we recongnize the file as previously also missing when it is restored or renamed twice (that is it disappears and reappears) 1772 // In such case, prevTime returned is same as prev time of event when file was deleted as per node documentation 1773 const isPreviouslyDeleted = +prev.mtime === 0 || eventKind === FileWatcherEventKind.Deleted; 1774 if (+curr.mtime === 0) { 1775 if (isPreviouslyDeleted) { 1776 // Already deleted file, no need to callback again 1777 return; 1778 } 1779 eventKind = FileWatcherEventKind.Deleted; 1780 } 1781 else if (isPreviouslyDeleted) { 1782 eventKind = FileWatcherEventKind.Created; 1783 } 1784 // If there is no change in modified time, ignore the event 1785 else if (+curr.mtime === +prev.mtime) { 1786 return; 1787 } 1788 else { 1789 // File changed 1790 eventKind = FileWatcherEventKind.Changed; 1791 } 1792 callback(fileName, eventKind, curr.mtime); 1793 } 1794 } 1795 1796 function fsWatchWorker( 1797 fileOrDirectory: string, 1798 recursive: boolean, 1799 callback: FsWatchCallback, 1800 ) { 1801 // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows 1802 // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) 1803 return _fs.watch( 1804 fileOrDirectory, 1805 fsSupportsRecursiveFsWatch ? 1806 { persistent: true, recursive: !!recursive } : { persistent: true }, 1807 callback 1808 ); 1809 } 1810 1811 function readFileWorker(fileName: string, _encoding?: string): string | undefined { 1812 let buffer: Buffer; 1813 try { 1814 buffer = _fs.readFileSync(fileName); 1815 } 1816 catch (e) { 1817 return undefined; 1818 } 1819 let len = buffer.length; 1820 if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) { 1821 // Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js, 1822 // flip all byte pairs and treat as little endian. 1823 len &= ~1; // Round down to a multiple of 2 1824 for (let i = 0; i < len; i += 2) { 1825 const temp = buffer[i]; 1826 buffer[i] = buffer[i + 1]; 1827 buffer[i + 1] = temp; 1828 } 1829 return buffer.toString("utf16le", 2); 1830 } 1831 if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { 1832 // Little endian UTF-16 byte order mark detected 1833 return buffer.toString("utf16le", 2); 1834 } 1835 if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { 1836 // UTF-8 byte order mark detected 1837 return buffer.toString("utf8", 3); 1838 } 1839 // Default is UTF-8 with no byte order mark 1840 return buffer.toString("utf8"); 1841 } 1842 1843 function readFile(fileName: string, _encoding?: string): string | undefined { 1844 perfLogger.logStartReadFile(fileName); 1845 const file = readFileWorker(fileName, _encoding); 1846 perfLogger.logStopReadFile(); 1847 return file; 1848 } 1849 1850 function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { 1851 perfLogger.logEvent("WriteFile: " + fileName); 1852 // If a BOM is required, emit one 1853 if (writeByteOrderMark) { 1854 data = byteOrderMarkIndicator + data; 1855 } 1856 1857 let fd: number | undefined; 1858 1859 try { 1860 fd = _fs.openSync(fileName, "w"); 1861 _fs.writeSync(fd, data, /*position*/ undefined, "utf8"); 1862 } 1863 finally { 1864 if (fd !== undefined) { 1865 _fs.closeSync(fd); 1866 } 1867 } 1868 } 1869 1870 function getAccessibleFileSystemEntries(path: string): FileSystemEntries { 1871 perfLogger.logEvent("ReadDir: " + (path || ".")); 1872 try { 1873 const entries = _fs.readdirSync(path || ".", { withFileTypes: true }); 1874 const files: string[] = []; 1875 const directories: string[] = []; 1876 for (const dirent of entries) { 1877 // withFileTypes is not supported before Node 10.10. 1878 const entry = typeof dirent === "string" ? dirent : dirent.name; 1879 1880 // This is necessary because on some file system node fails to exclude 1881 // "." and "..". See https://github.com/nodejs/node/issues/4002 1882 if (entry === "." || entry === "..") { 1883 continue; 1884 } 1885 1886 let stat: any; 1887 if (typeof dirent === "string" || dirent.isSymbolicLink()) { 1888 const name = combinePaths(path, entry); 1889 1890 try { 1891 stat = statSync(name); 1892 if (!stat) { 1893 continue; 1894 } 1895 } 1896 catch (e) { 1897 continue; 1898 } 1899 } 1900 else { 1901 stat = dirent; 1902 } 1903 1904 if (stat.isFile()) { 1905 files.push(entry); 1906 } 1907 else if (stat.isDirectory()) { 1908 directories.push(entry); 1909 } 1910 } 1911 files.sort(); 1912 directories.sort(); 1913 return { files, directories }; 1914 } 1915 catch (e) { 1916 return emptyFileSystemEntries; 1917 } 1918 } 1919 1920 function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { 1921 return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); 1922 } 1923 1924 function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { 1925 // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve 1926 // the CPU time performance. 1927 const originalStackTraceLimit = Error.stackTraceLimit; 1928 Error.stackTraceLimit = 0; 1929 1930 try { 1931 const stat = statSync(path); 1932 if (!stat) { 1933 return false; 1934 } 1935 switch (entryKind) { 1936 case FileSystemEntryKind.File: return stat.isFile(); 1937 case FileSystemEntryKind.Directory: return stat.isDirectory(); 1938 default: return false; 1939 } 1940 } 1941 catch (e) { 1942 return false; 1943 } 1944 finally { 1945 Error.stackTraceLimit = originalStackTraceLimit; 1946 } 1947 } 1948 1949 function fileExists(path: string): boolean { 1950 return fileSystemEntryExists(path, FileSystemEntryKind.File); 1951 } 1952 1953 function directoryExists(path: string): boolean { 1954 return fileSystemEntryExists(path, FileSystemEntryKind.Directory); 1955 } 1956 1957 function getDirectories(path: string): string[] { 1958 return getAccessibleFileSystemEntries(path).directories.slice(); 1959 } 1960 1961 function fsRealPathHandlingLongPath(path: string): string { 1962 return path.length < 260 ? _fs.realpathSync.native(path) : _fs.realpathSync(path); 1963 } 1964 1965 function realpath(path: string): string { 1966 try { 1967 return fsRealpath(path); 1968 } 1969 catch { 1970 return path; 1971 } 1972 } 1973 1974 function getModifiedTime(path: string) { 1975 // Since the error thrown by fs.statSync isn't used, we can avoid collecting a stack trace to improve 1976 // the CPU time performance. 1977 const originalStackTraceLimit = Error.stackTraceLimit; 1978 Error.stackTraceLimit = 0; 1979 try { 1980 return statSync(path)?.mtime; 1981 } 1982 catch (e) { 1983 return undefined; 1984 } 1985 finally { 1986 Error.stackTraceLimit = originalStackTraceLimit; 1987 } 1988 } 1989 1990 function setModifiedTime(path: string, time: Date) { 1991 try { 1992 _fs.utimesSync(path, time, time); 1993 } 1994 catch (e) { 1995 return; 1996 } 1997 } 1998 1999 function deleteFile(path: string) { 2000 try { 2001 return _fs.unlinkSync(path); 2002 } 2003 catch (e) { 2004 return; 2005 } 2006 } 2007 2008 function createSHA256Hash(data: string): string { 2009 const hash = _crypto!.createHash("sha256"); 2010 hash.update(data); 2011 return hash.digest("hex"); 2012 } 2013 } 2014 2015 let sys: System | undefined; 2016 if (isNodeLikeSystem()) { 2017 sys = getNodeSystem(); 2018 } 2019 if (sys) { 2020 // patch writefile to create folder before writing the file 2021 patchWriteFileEnsuringDirectory(sys); 2022 } 2023 return sys!; 2024})(); 2025 2026/** @internal */ 2027export function setSys(s: System) { 2028 sys = s; 2029} 2030 2031if (sys && sys.getEnvironmentVariable) { 2032 setCustomPollingValues(sys); 2033 Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) 2034 ? AssertionLevel.Normal 2035 : AssertionLevel.None); 2036} 2037if (sys && sys.debugMode) { 2038 Debug.isDebugging = true; 2039} 2040