From 3bd962a6d7f61239c020e2dbbeb7341e5b842dd1 Mon Sep 17 00:00:00 2001
From: WXL <wl_5969728@163.com>
Date: 星期二, 21 四月 2026 11:46:41 +0800
Subject: [PATCH] 推送

---
 node_modules/watchpack/lib/DirectoryWatcher.js |  454 +++++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 314 insertions(+), 140 deletions(-)

diff --git a/node_modules/watchpack/lib/DirectoryWatcher.js b/node_modules/watchpack/lib/DirectoryWatcher.js
index 9a5eeb9..a529e67 100644
--- a/node_modules/watchpack/lib/DirectoryWatcher.js
+++ b/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;

--
Gitblit v1.9.3