WXL
3 天以前 3bd962a6d7f61239c020e2dbbeb7341e5b842dd1
node_modules/watchpack/lib/DirectoryWatcher.js
@@ -4,12 +4,23 @@
*/
"use strict";
const EventEmitter = require("events").EventEmitter;
const fs = require("graceful-fs");
const { EventEmitter } = require("events");
const path = require("path");
const fs = require("graceful-fs");
const watchEventSource = require("./watchEventSource");
/** @typedef {import("./index").IgnoredFunction} IgnoredFunction */
/** @typedef {import("./index").EventType} EventType */
/** @typedef {import("./index").TimeInfoEntries} TimeInfoEntries */
/** @typedef {import("./index").Entry} Entry */
/** @typedef {import("./index").ExistenceOnlyTimeEntry} ExistenceOnlyTimeEntry */
/** @typedef {import("./index").OnlySafeTimeEntry} OnlySafeTimeEntry */
/** @typedef {import("./index").EventMap} EventMap */
/** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */
/** @typedef {import("./watchEventSource").Watcher} EventSourceWatcher */
/** @type {ExistenceOnlyTimeEntry} */
const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({});
let FS_ACCURACY = 2000;
@@ -17,45 +28,130 @@
const IS_OSX = require("os").platform() === "darwin";
const IS_WIN = require("os").platform() === "win32";
const WATCHPACK_POLLING = process.env.WATCHPACK_POLLING;
const { WATCHPACK_POLLING } = process.env;
const FORCE_POLLING =
   // @ts-expect-error avoid additional checks
   `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING
      ? +WATCHPACK_POLLING
      : !!WATCHPACK_POLLING && WATCHPACK_POLLING !== "false";
      : Boolean(WATCHPACK_POLLING) && WATCHPACK_POLLING !== "false";
/**
 * @param {string} str string
 * @returns {string} lower cased string
 */
function withoutCase(str) {
   return str.toLowerCase();
}
/**
 * @param {number} times times
 * @param {() => void} callback callback
 * @returns {() => void} result
 */
function needCalls(times, callback) {
   return function() {
   return function needCallsCallback() {
      if (--times === 0) {
         return callback();
      }
   };
}
/**
 * @param {Entry} entry entry
 */
function fixupEntryAccuracy(entry) {
   if (entry.accuracy > FS_ACCURACY) {
      entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
      entry.accuracy = FS_ACCURACY;
   }
}
/**
 * @param {number=} mtime mtime
 */
function ensureFsAccuracy(mtime) {
   if (!mtime) return;
   if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
   else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
   else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
   else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
}
/**
 * @typedef {object} FileWatcherEvents
 * @property {(type: EventType) => void} initial-missing initial missing event
 * @property {(mtime: number, type: EventType, initial: boolean) => void} change change event
 * @property {(type: EventType) => void} remove remove event
 * @property {() => void} closed closed event
 */
/**
 * @typedef {object} DirectoryWatcherEvents
 * @property {(type: EventType) => void} initial-missing initial missing event
 * @property {((file: string, mtime: number, type: EventType, initial: boolean) => void)} change change event
 * @property {(type: EventType) => void} remove remove event
 * @property {() => void} closed closed event
 */
/**
 * @template {EventMap} T
 * @extends {EventEmitter<{ [K in keyof T]: Parameters<T[K]> }>}
 */
class Watcher extends EventEmitter {
   constructor(directoryWatcher, filePath, startTime) {
   /**
    * @param {DirectoryWatcher} directoryWatcher a directory watcher
    * @param {string} target a target to watch
    * @param {number=} startTime start time
    */
   constructor(directoryWatcher, target, startTime) {
      super();
      this.directoryWatcher = directoryWatcher;
      this.path = filePath;
      this.path = target;
      this.startTime = startTime && +startTime;
   }
   /**
    * @param {number} mtime mtime
    * @param {boolean} initial true when initial, otherwise false
    * @returns {boolean} true of start time less than mtile, otherwise false
    */
   checkStartTime(mtime, initial) {
      const startTime = this.startTime;
      const { startTime } = this;
      if (typeof startTime !== "number") return !initial;
      return startTime <= mtime;
   }
   close() {
      // @ts-expect-error bad typing in EventEmitter
      this.emit("closed");
   }
}
/** @typedef {Set<string>} InitialScanRemoved */
/**
 * @typedef {object} WatchpackEvents
 * @property {(target: string, mtime: string, type: EventType, initial: boolean) => void} change change event
 * @property {() => void} closed closed event
 */
/**
 * @typedef {object} DirectoryWatcherOptions
 * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false
 * @property {IgnoredFunction=} ignored ignore some files from watching (glob pattern or regexp)
 * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false
 */
/**
 * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>}
 */
class DirectoryWatcher extends EventEmitter {
   constructor(watcherManager, directoryPath, options) {
   /**
    * @param {WatcherManager} watcherManager a watcher manager
    * @param {string} directoryPath directory path
    * @param {DirectoryWatcherOptions=} options options
    */
   constructor(watcherManager, directoryPath, options = {}) {
      super();
      if (FORCE_POLLING) {
         options.poll = FORCE_POLLING;
@@ -65,28 +161,35 @@
      this.path = directoryPath;
      // safeTime is the point in time after which reading is safe to be unchanged
      // timestamp is a value that should be compared with another timestamp (mtime)
      /** @type {Map<string, { safeTime: number, timestamp: number }} */
      /** @type {Map<string, Entry>} */
      this.files = new Map();
      /** @type {Map<string, number>} */
      this.filesWithoutCase = new Map();
      /** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */
      this.directories = new Map();
      this.lastWatchEvent = 0;
      this.initialScan = true;
      this.ignored = options.ignored || (() => false);
      this.nestedWatching = false;
      /** @type {number | false} */
      this.polledWatching =
         typeof options.poll === "number"
            ? options.poll
            : options.poll
            ? 5007
            : false;
               ? 5007
               : false;
      /** @type {undefined | NodeJS.Timeout} */
      this.timeout = undefined;
      /** @type {null | InitialScanRemoved} */
      this.initialScanRemoved = new Set();
      /** @type {undefined | number} */
      this.initialScanFinished = undefined;
      /** @type {Map<string, Set<Watcher>>} */
      /** @type {Map<string, Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>>} */
      this.watchers = new Map();
      /** @type {Watcher<FileWatcherEvents> | null} */
      this.parentWatcher = null;
      this.refs = 0;
      /** @type {Map<string, boolean>} */
      this._activeEvents = new Map();
      this.closed = false;
      this.scanning = false;
@@ -100,19 +203,22 @@
   createWatcher() {
      try {
         if (this.polledWatching) {
            this.watcher = {
            /** @type {EventSourceWatcher} */
            (this.watcher) = /** @type {EventSourceWatcher} */ ({
               close: () => {
                  if (this.timeout) {
                     clearTimeout(this.timeout);
                     this.timeout = undefined;
                  }
               }
            };
               },
            });
         } else {
            if (IS_OSX) {
               this.watchInParentDirectory();
            }
            this.watcher = watchEventSource.watch(this.path);
            this.watcher =
               /** @type {EventSourceWatcher} */
               (watchEventSource.watch(this.path));
            this.watcher.on("change", this.onWatchEvent.bind(this));
            this.watcher.on("error", this.onWatcherError.bind(this));
         }
@@ -121,6 +227,11 @@
      }
   }
   /**
    * @template {(watcher: Watcher<EventMap>) => void} T
    * @param {string} path path
    * @param {T} fn function
    */
   forEachWatcher(path, fn) {
      const watchers = this.watchers.get(withoutCase(path));
      if (watchers !== undefined) {
@@ -130,20 +241,28 @@
      }
   }
   /**
    * @param {string} itemPath an item path
    * @param {boolean} initial true when initial, otherwise false
    * @param {EventType} type even type
    */
   setMissing(itemPath, initial, type) {
      if (this.initialScan) {
         this.initialScanRemoved.add(itemPath);
         /** @type {InitialScanRemoved} */
         (this.initialScanRemoved).add(itemPath);
      }
      const oldDirectory = this.directories.get(itemPath);
      if (oldDirectory) {
         if (this.nestedWatching) oldDirectory.close();
         if (this.nestedWatching) {
            /** @type {Watcher<DirectoryWatcherEvents>} */
            (oldDirectory).close();
         }
         this.directories.delete(itemPath);
         this.forEachWatcher(itemPath, w => w.emit("remove", type));
         this.forEachWatcher(itemPath, (w) => w.emit("remove", type));
         if (!initial) {
            this.forEachWatcher(this.path, w =>
               w.emit("change", itemPath, null, type, initial)
            this.forEachWatcher(this.path, (w) =>
               w.emit("change", itemPath, null, type, initial),
            );
         }
      }
@@ -152,30 +271,38 @@
      if (oldFile) {
         this.files.delete(itemPath);
         const key = withoutCase(itemPath);
         const count = this.filesWithoutCase.get(key) - 1;
         const count = /** @type {number} */ (this.filesWithoutCase.get(key)) - 1;
         if (count <= 0) {
            this.filesWithoutCase.delete(key);
            this.forEachWatcher(itemPath, w => w.emit("remove", type));
            this.forEachWatcher(itemPath, (w) => w.emit("remove", type));
         } else {
            this.filesWithoutCase.set(key, count);
         }
         if (!initial) {
            this.forEachWatcher(this.path, w =>
               w.emit("change", itemPath, null, type, initial)
            this.forEachWatcher(this.path, (w) =>
               w.emit("change", itemPath, null, type, initial),
            );
         }
      }
   }
   setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) {
   /**
    * @param {string} target a target to set file time
    * @param {number} mtime mtime
    * @param {boolean} initial true when initial, otherwise false
    * @param {boolean} ignoreWhenEqual true to ignore when equal, otherwise false
    * @param {EventType} type type
    */
   setFileTime(target, mtime, initial, ignoreWhenEqual, type) {
      const now = Date.now();
      if (this.ignored(filePath)) return;
      if (this.ignored(target)) return;
      const old = this.files.get(filePath);
      const old = this.files.get(target);
      let safeTime, accuracy;
      let safeTime;
      let accuracy;
      if (initial) {
         safeTime = Math.min(now, mtime) + FS_ACCURACY;
         accuracy = FS_ACCURACY;
@@ -194,14 +321,14 @@
      if (ignoreWhenEqual && old && old.timestamp === mtime) return;
      this.files.set(filePath, {
      this.files.set(target, {
         safeTime,
         accuracy,
         timestamp: mtime
         timestamp: mtime,
      });
      if (!old) {
         const key = withoutCase(filePath);
         const key = withoutCase(target);
         const count = this.filesWithoutCase.get(key);
         this.filesWithoutCase.set(key, (count || 0) + 1);
         if (count !== undefined) {
@@ -213,27 +340,33 @@
            this.doScan(false);
         }
         this.forEachWatcher(filePath, w => {
         this.forEachWatcher(target, (w) => {
            if (!initial || w.checkStartTime(safeTime, initial)) {
               w.emit("change", mtime, type);
            }
         });
      } else if (!initial) {
         this.forEachWatcher(filePath, w => w.emit("change", mtime, type));
         this.forEachWatcher(target, (w) => w.emit("change", mtime, type));
      }
      this.forEachWatcher(this.path, w => {
      this.forEachWatcher(this.path, (w) => {
         if (!initial || w.checkStartTime(safeTime, initial)) {
            w.emit("change", filePath, safeTime, type, initial);
            w.emit("change", target, safeTime, type, initial);
         }
      });
   }
   /**
    * @param {string} directoryPath directory path
    * @param {number} birthtime birthtime
    * @param {boolean} initial true when initial, otherwise false
    * @param {EventType} type even type
    */
   setDirectory(directoryPath, birthtime, initial, type) {
      if (this.ignored(directoryPath)) return;
      if (directoryPath === this.path) {
         if (!initial) {
            this.forEachWatcher(this.path, w =>
               w.emit("change", directoryPath, birthtime, type, initial)
            this.forEachWatcher(this.path, (w) =>
               w.emit("change", directoryPath, birthtime, type, initial),
            );
         }
      } else {
@@ -247,19 +380,14 @@
               this.directories.set(directoryPath, true);
            }
            let safeTime;
            if (initial) {
               safeTime = Math.min(now, birthtime) + FS_ACCURACY;
            } else {
               safeTime = now;
            }
            const safeTime = initial ? Math.min(now, birthtime) + FS_ACCURACY : now;
            this.forEachWatcher(directoryPath, w => {
            this.forEachWatcher(directoryPath, (w) => {
               if (!initial || w.checkStartTime(safeTime, false)) {
                  w.emit("change", birthtime, type);
               }
            });
            this.forEachWatcher(this.path, w => {
            this.forEachWatcher(this.path, (w) => {
               if (!initial || w.checkStartTime(safeTime, initial)) {
                  w.emit("change", directoryPath, safeTime, type, initial);
               }
@@ -268,43 +396,57 @@
      }
   }
   /**
    * @param {string} directoryPath directory path
    */
   createNestedWatcher(directoryPath) {
      const watcher = this.watcherManager.watchDirectory(directoryPath, 1);
      watcher.on("change", (filePath, mtime, type, initial) => {
         this.forEachWatcher(this.path, w => {
      watcher.on("change", (target, mtime, type, initial) => {
         this.forEachWatcher(this.path, (w) => {
            if (!initial || w.checkStartTime(mtime, initial)) {
               w.emit("change", filePath, mtime, type, initial);
               w.emit("change", target, mtime, type, initial);
            }
         });
      });
      this.directories.set(directoryPath, watcher);
   }
   /**
    * @param {boolean} flag true when nested, otherwise false
    */
   setNestedWatching(flag) {
      if (this.nestedWatching !== !!flag) {
         this.nestedWatching = !!flag;
      if (this.nestedWatching !== Boolean(flag)) {
         this.nestedWatching = Boolean(flag);
         if (this.nestedWatching) {
            for (const directory of this.directories.keys()) {
               this.createNestedWatcher(directory);
            }
         } else {
            for (const [directory, watcher] of this.directories) {
               watcher.close();
               /** @type {Watcher<DirectoryWatcherEvents>} */
               (watcher).close();
               this.directories.set(directory, true);
            }
         }
      }
   }
   watch(filePath, startTime) {
      const key = withoutCase(filePath);
   /**
    * @param {string} target a target to watch
    * @param {number=} startTime start time
    * @returns {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} watcher
    */
   watch(target, startTime) {
      const key = withoutCase(target);
      let watchers = this.watchers.get(key);
      if (watchers === undefined) {
         watchers = new Set();
         this.watchers.set(key, watchers);
      }
      this.refs++;
      const watcher = new Watcher(this, filePath, startTime);
      const watcher =
         /** @type {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} */
         (new Watcher(this, target, startTime));
      watcher.on("closed", () => {
         if (--this.refs <= 0) {
            this.close();
@@ -313,12 +455,12 @@
         watchers.delete(watcher);
         if (watchers.size === 0) {
            this.watchers.delete(key);
            if (this.path === filePath) this.setNestedWatching(false);
            if (this.path === target) this.setNestedWatching(false);
         }
      });
      watchers.add(watcher);
      let safeTime;
      if (filePath === this.path) {
      if (target === this.path) {
         this.setNestedWatching(true);
         safeTime = this.lastWatchEvent;
         for (const entry of this.files.values()) {
@@ -326,7 +468,7 @@
            safeTime = Math.max(safeTime, entry.safeTime);
         }
      } else {
         const entry = this.files.get(filePath);
         const entry = this.files.get(target);
         if (entry) {
            fixupEntryAccuracy(entry);
            safeTime = entry.safeTime;
@@ -335,38 +477,47 @@
         }
      }
      if (safeTime) {
         if (safeTime >= startTime) {
         if (startTime && safeTime >= startTime) {
            process.nextTick(() => {
               if (this.closed) return;
               if (filePath === this.path) {
                  watcher.emit(
               if (target === this.path) {
                  /** @type {Watcher<DirectoryWatcherEvents>} */
                  (watcher).emit(
                     "change",
                     filePath,
                     target,
                     safeTime,
                     "watch (outdated on attach)",
                     true
                     true,
                  );
               } else {
                  watcher.emit(
                  /** @type {Watcher<FileWatcherEvents>} */
                  (watcher).emit(
                     "change",
                     safeTime,
                     "watch (outdated on attach)",
                     true
                     true,
                  );
               }
            });
         }
      } else if (this.initialScan) {
         if (this.initialScanRemoved.has(filePath)) {
         if (
            /** @type {InitialScanRemoved} */
            (this.initialScanRemoved).has(target)
         ) {
            process.nextTick(() => {
               if (this.closed) return;
               watcher.emit("remove");
            });
         }
      } else if (
         filePath !== this.path &&
         !this.directories.has(filePath) &&
         watcher.checkStartTime(this.initialScanFinished, false)
         target !== this.path &&
         !this.directories.has(target) &&
         watcher.checkStartTime(
            /** @type {number} */
            (this.initialScanFinished),
            false,
         )
      ) {
         process.nextTick(() => {
            if (this.closed) return;
@@ -376,6 +527,10 @@
      return watcher;
   }
   /**
    * @param {EventType} eventType event type
    * @param {string=} filename filename
    */
   onWatchEvent(eventType, filename) {
      if (this.closed) return;
      if (!filename) {
@@ -387,15 +542,15 @@
         return;
      }
      const filePath = path.join(this.path, filename);
      if (this.ignored(filePath)) return;
      const target = path.join(this.path, filename);
      if (this.ignored(target)) return;
      if (this._activeEvents.get(filename) === undefined) {
         this._activeEvents.set(filename, false);
         const checkStats = () => {
            if (this.closed) return;
            this._activeEvents.set(filename, false);
            fs.lstat(filePath, (err, stats) => {
            fs.lstat(target, (err, stats) => {
               if (this.closed) return;
               if (this._activeEvents.get(filename) === true) {
                  process.nextTick(checkStats);
@@ -411,35 +566,28 @@
                     err.code !== "EBUSY"
                  ) {
                     this.onStatsError(err);
                  } else {
                     if (filename === path.basename(this.path)) {
                        // This may indicate that the directory itself was removed
                        if (!fs.existsSync(this.path)) {
                           this.onDirectoryRemoved("stat failed");
                        }
                     }
                  } else if (
                     filename === path.basename(this.path) && // This may indicate that the directory itself was removed
                     !fs.existsSync(this.path)
                  ) {
                     this.onDirectoryRemoved("stat failed");
                  }
               }
               this.lastWatchEvent = Date.now();
               if (!stats) {
                  this.setMissing(filePath, false, eventType);
                  this.setMissing(target, false, eventType);
               } else if (stats.isDirectory()) {
                  this.setDirectory(
                     filePath,
                     +stats.birthtime || 1,
                     false,
                     eventType
                  );
                  this.setDirectory(target, +stats.birthtime || 1, false, eventType);
               } else if (stats.isFile() || stats.isSymbolicLink()) {
                  if (stats.mtime) {
                     ensureFsAccuracy(stats.mtime);
                     ensureFsAccuracy(+stats.mtime);
                  }
                  this.setFileTime(
                     filePath,
                     target,
                     +stats.mtime || +stats.ctime || 1,
                     false,
                     false,
                     eventType
                     eventType,
                  );
               }
            });
@@ -450,25 +598,42 @@
      }
   }
   /**
    * @param {unknown=} err error
    */
   onWatcherError(err) {
      if (this.closed) return;
      if (err) {
         if (err.code !== "EPERM" && err.code !== "ENOENT") {
            console.error("Watchpack Error (watcher): " + err);
         if (
            /** @type {NodeJS.ErrnoException} */
            (err).code !== "EPERM" &&
            /** @type {NodeJS.ErrnoException} */
            (err).code !== "ENOENT"
         ) {
            // eslint-disable-next-line no-console
            console.error(`Watchpack Error (watcher): ${err}`);
         }
         this.onDirectoryRemoved("watch error");
      }
   }
   /**
    * @param {Error | NodeJS.ErrnoException=} err error
    */
   onStatsError(err) {
      if (err) {
         console.error("Watchpack Error (stats): " + err);
         // eslint-disable-next-line no-console
         console.error(`Watchpack Error (stats): ${err}`);
      }
   }
   /**
    * @param {Error | NodeJS.ErrnoException=} err error
    */
   onScanError(err) {
      if (err) {
         console.error("Watchpack Error (initial scan): " + err);
         // eslint-disable-next-line no-console
         console.error(`Watchpack Error (initial scan): ${err}`);
      }
      this.onScanFinished();
   }
@@ -482,18 +647,21 @@
      }
   }
   /**
    * @param {string} reason a reason
    */
   onDirectoryRemoved(reason) {
      if (this.watcher) {
         this.watcher.close();
         this.watcher = null;
      }
      this.watchInParentDirectory();
      const type = `directory-removed (${reason})`;
      const type = /** @type {EventType} */ (`directory-removed (${reason})`);
      for (const directory of this.directories.keys()) {
         this.setMissing(directory, null, type);
         this.setMissing(directory, false, type);
      }
      for (const file of this.files.keys()) {
         this.setMissing(file, null, type);
         this.setMissing(file, false, type);
      }
   }
@@ -505,7 +673,8 @@
         if (path.dirname(parentDir) === parentDir) return;
         this.parentWatcher = this.watcherManager.watchFile(this.path, 1);
         this.parentWatcher.on("change", (mtime, type) => {
         /** @type {Watcher<FileWatcherEvents>} */
         (this.parentWatcher).on("change", (mtime, type) => {
            if (this.closed) return;
            // On non-osx platforms we don't need this watcher to detect
@@ -520,17 +689,21 @@
               this.doScan(false);
               // directory was created so we emit an event
               this.forEachWatcher(this.path, w =>
                  w.emit("change", this.path, mtime, type, false)
               this.forEachWatcher(this.path, (w) =>
                  w.emit("change", this.path, mtime, type, false),
               );
            }
         });
         this.parentWatcher.on("remove", () => {
         /** @type {Watcher<FileWatcherEvents>} */
         (this.parentWatcher).on("remove", () => {
            this.onDirectoryRemoved("parent directory removed");
         });
      }
   }
   /**
    * @param {boolean} initial true when initial, otherwise false
    */
   doScan(initial) {
      if (this.scanning) {
         if (this.scanAgain) {
@@ -564,7 +737,7 @@
                        if (watcher.checkStartTime(this.initialScanFinished, false)) {
                           watcher.emit(
                              "initial-missing",
                              "scan (parent directory missing in initial scan)"
                              "scan (parent directory missing in initial scan)",
                           );
                        }
                     }
@@ -579,7 +752,7 @@
               return;
            }
            const itemPaths = new Set(
               items.map(item => path.join(this.path, item.normalize("NFC")))
               items.map((item) => path.join(this.path, item.normalize("NFC"))),
            );
            for (const file of this.files.keys()) {
               if (!itemPaths.has(file)) {
@@ -613,7 +786,7 @@
                        if (watcher.checkStartTime(this.initialScanFinished, false)) {
                           watcher.emit(
                              "initial-missing",
                              "scan (missing in initial scan)"
                              "scan (missing in initial scan)",
                           );
                        }
                     }
@@ -639,7 +812,7 @@
                        // TODO https://github.com/libuv/libuv/pull/4566
                        (err2.code === "EINVAL" && IS_WIN)
                     ) {
                        this.setMissing(itemPath, initial, "scan (" + err2.code + ")");
                        this.setMissing(itemPath, initial, `scan (${err2.code})`);
                     } else {
                        this.onScanError(err2);
                     }
@@ -648,23 +821,25 @@
                  }
                  if (stats.isFile() || stats.isSymbolicLink()) {
                     if (stats.mtime) {
                        ensureFsAccuracy(stats.mtime);
                        ensureFsAccuracy(+stats.mtime);
                     }
                     this.setFileTime(
                        itemPath,
                        +stats.mtime || +stats.ctime || 1,
                        initial,
                        true,
                        "scan (file)"
                        "scan (file)",
                     );
                  } else if (stats.isDirectory()) {
                     if (!initial || !this.directories.has(itemPath))
                        this.setDirectory(
                           itemPath,
                           +stats.birthtime || 1,
                           initial,
                           "scan (dir)"
                        );
                  } else if (
                     stats.isDirectory() &&
                     (!initial || !this.directories.has(itemPath))
                  ) {
                     this.setDirectory(
                        itemPath,
                        +stats.birthtime || 1,
                        initial,
                        "scan (dir)",
                     );
                  }
                  itemFinished();
               });
@@ -674,6 +849,9 @@
      });
   }
   /**
    * @returns {Record<string, number>} times
    */
   getTimes() {
      const obj = Object.create(null);
      let safeTime = this.lastWatchEvent;
@@ -684,7 +862,9 @@
      }
      if (this.nestedWatching) {
         for (const w of this.directories.values()) {
            const times = w.directoryWatcher.getTimes();
            const times =
               /** @type {Watcher<DirectoryWatcherEvents>} */
               (w).directoryWatcher.getTimes();
            for (const file of Object.keys(times)) {
               const time = times[file];
               safeTime = Math.max(safeTime, time);
@@ -696,7 +876,7 @@
      if (!this.initialScan) {
         for (const watchers of this.watchers.values()) {
            for (const watcher of watchers) {
               const path = watcher.path;
               const { path } = watcher;
               if (!Object.prototype.hasOwnProperty.call(obj, path)) {
                  obj[path] = null;
               }
@@ -706,6 +886,11 @@
      return obj;
   }
   /**
    * @param {TimeInfoEntries} fileTimestamps file timestamps
    * @param {TimeInfoEntries} directoryTimestamps directory timestamps
    * @returns {number} safe time
    */
   collectTimeInfoEntries(fileTimestamps, directoryTimestamps) {
      let safeTime = this.lastWatchEvent;
      for (const [file, entry] of this.files) {
@@ -717,23 +902,25 @@
         for (const w of this.directories.values()) {
            safeTime = Math.max(
               safeTime,
               w.directoryWatcher.collectTimeInfoEntries(
               /** @type {Watcher<DirectoryWatcherEvents>} */
               (w).directoryWatcher.collectTimeInfoEntries(
                  fileTimestamps,
                  directoryTimestamps
               )
                  directoryTimestamps,
               ),
            );
         }
         fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
         directoryTimestamps.set(this.path, {
            safeTime
            safeTime,
         });
      } else {
         for (const dir of this.directories.keys()) {
            // No additional info about this directory
            // but maybe another DirectoryWatcher has info
            fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
            if (!directoryTimestamps.has(dir))
            if (!directoryTimestamps.has(dir)) {
               directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY);
            }
         }
         fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
         directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY);
@@ -741,7 +928,7 @@
      if (!this.initialScan) {
         for (const watchers of this.watchers.values()) {
            for (const watcher of watchers) {
               const path = watcher.path;
               const { path } = watcher;
               if (!fileTimestamps.has(path)) {
                  fileTimestamps.set(path, null);
               }
@@ -760,7 +947,8 @@
      }
      if (this.nestedWatching) {
         for (const w of this.directories.values()) {
            w.close();
            /** @type {Watcher<DirectoryWatcherEvents>} */
            (w).close();
         }
         this.directories.clear();
      }
@@ -774,18 +962,4 @@
module.exports = DirectoryWatcher;
module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY;
function fixupEntryAccuracy(entry) {
   if (entry.accuracy > FS_ACCURACY) {
      entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY;
      entry.accuracy = FS_ACCURACY;
   }
}
function ensureFsAccuracy(mtime) {
   if (!mtime) return;
   if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1;
   else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10;
   else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100;
   else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000;
}
module.exports.Watcher = Watcher;