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