-
Notifications
You must be signed in to change notification settings - Fork 6k
fix(ext/node): support ignore option in fs.promises.watch
#33606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3220,19 +3220,110 @@ | |
| return fsWatcher; | ||
| } | ||
|
|
||
| // Mirrors Node's `validateIgnoreOption` / | ||
| // `createIgnoreMatcher` from `lib/internal/fs/watchers.js`. | ||
| // Accepts a string (minimatch glob), RegExp, function, or array of those. | ||
| // Returns a function `(filename) => boolean` (or `null` if `ignore` is nullish). | ||
| type IgnoreOption = | ||
| | string | ||
| | RegExp | ||
| | ((filename: string) => boolean) | ||
| | (string | RegExp | ((filename: string) => boolean))[] | ||
| | undefined | ||
| | null; | ||
|
|
||
| let _lazyMinimatch: any = null; | ||
| function getMinimatch() { | ||
| _lazyMinimatch ??= core.createLazyLoader("ext:deno_node/deps/minimatch.js"); | ||
| return _lazyMinimatch(); | ||
| } | ||
|
|
||
| function validateIgnoreOptionElement(value: unknown, 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; | ||
|
Check failure on line 3252 in ext/node/polyfills/fs.ts
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lint blocker —
The file already imports Full failure: https://github.com/denoland/deno/actions/runs/25018807704/job/73273702258 #33574 had the exact same pattern fixed in the same way; you can crib that fix verbatim. |
||
| if (typeof value === "function") return; | ||
| throw new ERR_INVALID_ARG_TYPE( | ||
| name, | ||
| ["string", "RegExp", "Function"], | ||
| value, | ||
| ); | ||
| } | ||
|
|
||
| function validateIgnoreOption(value: unknown, name: string) { | ||
| if (value == null) return; | ||
| if (Array.isArray(value)) { | ||
|
Check failure on line 3263 in ext/node/polyfills/fs.ts
|
||
| for (let i = 0; i < value.length; i++) { | ||
| validateIgnoreOptionElement(value[i], `${name}[${i}]`); | ||
| } | ||
| return; | ||
| } | ||
| validateIgnoreOptionElement(value, name); | ||
| } | ||
|
|
||
| function createIgnoreMatcher( | ||
| ignore: IgnoreOption, | ||
| ): ((filename: string) => boolean) | null { | ||
| if (ignore == null) return null; | ||
| const matchers = Array.isArray(ignore) ? ignore : [ignore]; | ||
|
Check failure on line 3276 in ext/node/polyfills/fs.ts
|
||
| const compiled: Array<(filename: string) => boolean> = []; | ||
|
|
||
| for (let i = 0; i < matchers.length; i++) { | ||
| const matcher = matchers[i]; | ||
| if (typeof matcher === "string") { | ||
| const { Minimatch } = getMinimatch().default; | ||
| const mm = new Minimatch(matcher, { | ||
| nocase: isMacOS || isWindows, | ||
| windowsPathsNoEscape: true, | ||
| nonegate: true, | ||
| nocomment: true, | ||
| optimizationLevel: 2, | ||
| platform: isWindows ? "win32" : "posix", | ||
| // Allow patterns without slashes to match the basename | ||
| // e.g. '*.log' matches 'subdir/file.log'. | ||
| matchBase: true, | ||
| }); | ||
| compiled.push((filename: string) => mm.match(filename)); | ||
|
Check failure on line 3294 in ext/node/polyfills/fs.ts
|
||
| } else if (matcher instanceof RegExp) { | ||
|
Check failure on line 3295 in ext/node/polyfills/fs.ts
|
||
| compiled.push((filename: string) => matcher.test(filename)); | ||
|
Check failure on line 3296 in ext/node/polyfills/fs.ts
|
||
| } else { | ||
| // Function | ||
| compiled.push(matcher as (filename: string) => boolean); | ||
|
Check failure on line 3299 in ext/node/polyfills/fs.ts
|
||
| } | ||
| } | ||
|
|
||
| return (filename: string) => { | ||
| for (let i = 0; i < compiled.length; i++) { | ||
| if (compiled[i](filename)) return true; | ||
| } | ||
| return false; | ||
| }; | ||
| } | ||
|
|
||
| function watchPromise( | ||
| filename: string | Buffer | URL, | ||
| options?: { | ||
| persistent?: boolean; | ||
| recursive?: boolean; | ||
| encoding?: string; | ||
| signal?: AbortSignal; | ||
| ignore?: IgnoreOption; | ||
| }, | ||
| ): AsyncIterable<{ eventType: string; filename: string | Buffer | null }> { | ||
| // deno-lint-ignore prefer-primordials | ||
| const watchPath = getValidatedPath(filename).toString(); | ||
|
|
||
| const recursive = options?.recursive ?? false; | ||
| validateIgnoreOption(options?.ignore, "options.ignore"); | ||
| const ignoreMatcher = createIgnoreMatcher(options?.ignore); | ||
| const watcher = Deno.watchFs(watchPath, { | ||
| recursive, | ||
| }); | ||
|
|
@@ -3255,20 +3346,25 @@ | |
| async next(): Promise< | ||
| IteratorResult<{ eventType: string; filename: string | Buffer | null }> | ||
| > { | ||
| // deno-lint-ignore prefer-primordials | ||
| const iterResult = await fsIterable.next(); | ||
| if (iterResult.done) return iterResult; | ||
| while (true) { | ||
| // deno-lint-ignore prefer-primordials | ||
| const iterResult = await fsIterable.next(); | ||
| if (iterResult.done) return iterResult; | ||
|
|
||
| const eventType = convertDenoFsEventToNodeFsEvent( | ||
| iterResult.value.kind, | ||
| ); | ||
| const fname = recursive | ||
| ? relative(resolvedWatchPath, iterResult.value.paths[0]) | ||
| : basename(iterResult.value.paths[0]); | ||
| return { | ||
| value: { eventType, filename: fname }, | ||
| done: false, | ||
| }; | ||
| const eventType = convertDenoFsEventToNodeFsEvent( | ||
| iterResult.value.kind, | ||
| ); | ||
| const fname = recursive | ||
| ? relative(resolvedWatchPath, iterResult.value.paths[0]) | ||
| : basename(iterResult.value.paths[0]); | ||
| if (ignoreMatcher !== null && ignoreMatcher(fname)) { | ||
| continue; | ||
| } | ||
| return { | ||
| value: { eventType, filename: fname }, | ||
| done: false, | ||
| }; | ||
| } | ||
| }, | ||
| // deno-lint-ignore no-explicit-any | ||
| return(value?: any): Promise<IteratorResult<any>> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor —
let _lazyMinimatch: any = nullis usinganyfor the lazy-loader handle. This is contagious:_lazyMinimatch ??= core.createLazyLoader(...)then_lazyMinimatch()returnsany, andgetMinimatch().defaultis thereforeanytoo.Not blocking, but typing it as something narrower (e.g.
() => typeof import("...")or just() => unknownand then asserting at the destructure site) would catch a mistyped property access at compile time. The current shape would silently break ifMinimatchgot renamed in the dep.