• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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