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