From cbba68fe79cbae39049b3ea8d7245466836ba229 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 12:59:22 +0000 Subject: [PATCH 1/2] fix(ext/node): validate fs.watch options.ignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match Node's `internal/validators.js#validateIgnoreOption` / `validateIgnoreOptionElement`, used by `fs.watch` and `fs/promises.watch`: - Element must be string, RegExp, or function — otherwise `ERR_INVALID_ARG_TYPE`. - An empty string raises `ERR_INVALID_ARG_VALUE` ("must be a non-empty string"). - Arrays validate each element; null/undefined are accepted as no-op. Previously `fs.watch` silently accepted any value (numbers, empty strings) and only later quietly ignored them. Enables `parallel/test-fs-watch-ignore-invalid.js`. --- ext/node/polyfills/fs.ts | 40 ++++++++++++++++++++++++++++++++++ tests/node_compat/config.jsonc | 1 + 2 files changed, 41 insertions(+) diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts index 100359344dc6a0..609b0be6d5f91e 100644 --- a/ext/node/polyfills/fs.ts +++ b/ext/node/polyfills/fs.ts @@ -141,6 +141,7 @@ import { import { ERR_FS_RMDIR_ENOTDIR, ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, uvException, } from "ext:deno_node/internal/errors.ts"; import { toUnixTimestamp } from "ext:deno_node/internal/fs/utils.mjs"; @@ -3159,10 +3160,47 @@ type watchOptions = { persistent?: boolean; recursive?: boolean; encoding?: string; + // deno-lint-ignore no-explicit-any + ignore?: string | RegExp | ((path: string) => boolean) | any[]; }; type watchListener = (eventType: string, filename: string) => void; +// Mirrors Node's `validateIgnoreOption` / +// `validateIgnoreOptionElement` in lib/internal/validators.js. +// deno-lint-ignore no-explicit-any +function validateIgnoreOptionElement(value: any, name: string) { + if (typeof value === "string") { + if (value.length === 0) { + throw new ERR_INVALID_ARG_VALUE( + name, + value, + "must be a non-empty string", + ); + } + return; + } + if (value instanceof RegExp) return; + if (typeof value === "function") return; + throw new ERR_INVALID_ARG_TYPE( + name, + ["string", "RegExp", "Function"], + value, + ); +} + +// deno-lint-ignore no-explicit-any +function validateIgnoreOption(value: any, name: string) { + if (value == null) return; + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + validateIgnoreOptionElement(value[i], `${name}[${i}]`); + } + return; + } + validateIgnoreOptionElement(value, name); +} + function watch( filename: string | URL, options: watchOptions, @@ -3193,6 +3231,8 @@ function watch( ? optionsOrListener2 : undefined; + validateIgnoreOption(options?.ignore, "options.ignore"); + // deno-lint-ignore prefer-primordials const watchPath = getValidatedPath(filename).toString(); diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index e810c46265d11e..0c3b2910fad4e7 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -1178,6 +1178,7 @@ "parallel/test-fs-utimes-y2K38.js": {}, "parallel/test-fs-utimes.js": {}, "parallel/test-fs-watch-file-enoent-after-deletion.js": {}, + "parallel/test-fs-watch-ignore-invalid.js": {}, "parallel/test-fs-watch-recursive-add-file-to-existing-subfolder.js": {}, "parallel/test-fs-watch-recursive-add-folder.js": {}, "parallel/test-fs-watch-recursive-delete.js": {}, From 6508877833af12a7908b55646066af74ad0a054d Mon Sep 17 00:00:00 2001 From: Nathan Whitaker Date: Mon, 27 Apr 2026 16:44:54 +0000 Subject: [PATCH 2/2] fix(ext/node): use primordials in fs.watch ignore validators --- ext/node/polyfills/fs.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts index 609b0be6d5f91e..e51a1a35b79eec 100644 --- a/ext/node/polyfills/fs.ts +++ b/ext/node/polyfills/fs.ts @@ -158,6 +158,7 @@ import { core, primordials } from "ext:core/mod.js"; const { ArrayBufferIsView, + ArrayIsArray, BigInt, DatePrototypeGetTime, DateUTC, @@ -178,6 +179,7 @@ const { Promise, PromisePrototypeThen, PromiseResolve, + RegExpPrototype, SafeMap, StringPrototypeToString, SymbolAsyncIterator, @@ -3180,7 +3182,7 @@ function validateIgnoreOptionElement(value: any, name: string) { } return; } - if (value instanceof RegExp) return; + if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) return; if (typeof value === "function") return; throw new ERR_INVALID_ARG_TYPE( name, @@ -3192,7 +3194,7 @@ function validateIgnoreOptionElement(value: any, name: string) { // deno-lint-ignore no-explicit-any function validateIgnoreOption(value: any, name: string) { if (value == null) return; - if (Array.isArray(value)) { + if (ArrayIsArray(value)) { for (let i = 0; i < value.length; i++) { validateIgnoreOptionElement(value[i], `${name}[${i}]`); }