11'use strict' ;
22
33const {
4+ ArrayIsArray,
45 ArrayPrototypePush,
56 ArrayPrototypeShift,
67 Error,
78 FunctionPrototypeCall,
89 ObjectDefineProperty,
910 ObjectSetPrototypeOf,
1011 PromiseWithResolvers,
12+ RegExpPrototypeExec,
1113 Symbol,
1214} = primordials ;
1315
@@ -22,6 +24,8 @@ const {
2224
2325const {
2426 kEmptyObject,
27+ isWindows,
28+ isMacOS,
2529} = require ( 'internal/util' ) ;
2630
2731const {
@@ -48,6 +52,7 @@ const { toNamespacedPath } = require('path');
4852const {
4953 validateAbortSignal,
5054 validateBoolean,
55+ validateIgnoreOption,
5156 validateObject,
5257 validateUint32,
5358 validateInteger,
@@ -60,6 +65,8 @@ const {
6065 } ,
6166} = require ( 'buffer' ) ;
6267
68+ const { isRegExp } = require ( 'internal/util/types' ) ;
69+
6370const assert = require ( 'internal/assert' ) ;
6471
6572const kOldStatus = Symbol ( 'kOldStatus' ) ;
@@ -71,6 +78,54 @@ const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
7178const KFSStatWatcherMaxRefCount = Symbol ( 'KFSStatWatcherMaxRefCount' ) ;
7279const kFSStatWatcherAddOrCleanRef = Symbol ( 'kFSStatWatcherAddOrCleanRef' ) ;
7380
81+ let minimatch ;
82+ function lazyMinimatch ( ) {
83+ minimatch ??= require ( 'internal/deps/minimatch/index' ) ;
84+ return minimatch ;
85+ }
86+
87+ /**
88+ * Creates an ignore matcher function from the ignore option.
89+ * @param {string | RegExp | Function | Array } ignore - The ignore patterns
90+ * @returns {Function | null } A function that returns true if filename should be ignored
91+ */
92+ function createIgnoreMatcher ( ignore ) {
93+ if ( ignore == null ) return null ;
94+ const matchers = ArrayIsArray ( ignore ) ? ignore : [ ignore ] ;
95+ const compiled = [ ] ;
96+
97+ for ( let i = 0 ; i < matchers . length ; i ++ ) {
98+ const matcher = matchers [ i ] ;
99+ if ( typeof matcher === 'string' ) {
100+ const mm = new ( lazyMinimatch ( ) . Minimatch ) ( matcher , {
101+ __proto__ : null ,
102+ nocase : isWindows || isMacOS ,
103+ windowsPathsNoEscape : true ,
104+ nonegate : true ,
105+ nocomment : true ,
106+ optimizationLevel : 2 ,
107+ platform : process . platform ,
108+ // matchBase allows patterns without slashes to match the basename
109+ // e.g., '*.log' matches 'subdir/file.log'
110+ matchBase : true ,
111+ } ) ;
112+ ArrayPrototypePush ( compiled , ( filename ) => mm . match ( filename ) ) ;
113+ } else if ( isRegExp ( matcher ) ) {
114+ ArrayPrototypePush ( compiled , ( filename ) => RegExpPrototypeExec ( matcher , filename ) !== null ) ;
115+ } else {
116+ // Function
117+ ArrayPrototypePush ( compiled , matcher ) ;
118+ }
119+ }
120+
121+ return ( filename ) => {
122+ for ( let i = 0 ; i < compiled . length ; i ++ ) {
123+ if ( compiled [ i ] ( filename ) ) return true ;
124+ }
125+ return false ;
126+ } ;
127+ }
128+
74129function emitStop ( self ) {
75130 self . emit ( 'stop' ) ;
76131}
@@ -199,6 +254,7 @@ function FSWatcher() {
199254
200255 this . _handle = new FSEvent ( ) ;
201256 this . _handle [ owner_symbol ] = this ;
257+ this . _ignoreMatcher = null ;
202258
203259 this . _handle . onchange = ( status , eventType , filename ) => {
204260 // TODO(joyeecheung): we may check self._handle.initialized here
@@ -219,6 +275,10 @@ function FSWatcher() {
219275 error . filename = filename ;
220276 this . emit ( 'error' , error ) ;
221277 } else {
278+ // Filter events if ignore matcher is set and filename is available
279+ if ( filename != null && this . _ignoreMatcher ?. ( filename ) ) {
280+ return ;
281+ }
222282 this . emit ( 'change' , eventType , filename ) ;
223283 }
224284 } ;
@@ -235,7 +295,8 @@ ObjectSetPrototypeOf(FSWatcher, EventEmitter);
235295FSWatcher . prototype [ kFSWatchStart ] = function ( filename ,
236296 persistent ,
237297 recursive ,
238- encoding ) {
298+ encoding ,
299+ ignore ) {
239300 if ( this . _handle === null ) { // closed
240301 return ;
241302 }
@@ -246,6 +307,10 @@ FSWatcher.prototype[kFSWatchStart] = function(filename,
246307
247308 filename = getValidatedPath ( filename , 'filename' ) ;
248309
310+ // Validate and create the ignore matcher
311+ validateIgnoreOption ( ignore , 'options.ignore' ) ;
312+ this . _ignoreMatcher = createIgnoreMatcher ( ignore ) ;
313+
249314 const err = this . _handle . start ( toNamespacedPath ( filename ) ,
250315 persistent ,
251316 recursive ,
@@ -319,13 +384,15 @@ async function* watch(filename, options = kEmptyObject) {
319384 maxQueue = 2048 ,
320385 overflow = 'ignore' ,
321386 signal,
387+ ignore,
322388 } = options ;
323389
324390 validateBoolean ( persistent , 'options.persistent' ) ;
325391 validateBoolean ( recursive , 'options.recursive' ) ;
326392 validateInteger ( maxQueue , 'options.maxQueue' ) ;
327393 validateOneOf ( overflow , 'options.overflow' , [ 'ignore' , 'error' ] ) ;
328394 validateAbortSignal ( signal , 'options.signal' ) ;
395+ validateIgnoreOption ( ignore , 'options.ignore' ) ;
329396
330397 if ( encoding && ! isEncoding ( encoding ) ) {
331398 const reason = 'is invalid encoding' ;
@@ -336,6 +403,7 @@ async function* watch(filename, options = kEmptyObject) {
336403 throw new AbortError ( undefined , { cause : signal . reason } ) ;
337404
338405 const handle = new FSEvent ( ) ;
406+ const ignoreMatcher = createIgnoreMatcher ( ignore ) ;
339407 let { promise, resolve } = PromiseWithResolvers ( ) ;
340408 const queue = [ ] ;
341409 const oncancel = ( ) => {
@@ -361,6 +429,10 @@ async function* watch(filename, options = kEmptyObject) {
361429 resolve ( ) ;
362430 return ;
363431 }
432+ // Filter events if ignore matcher is set and filename is available
433+ if ( filename != null && ignoreMatcher ?. ( filename ) ) {
434+ return ;
435+ }
364436 if ( queue . length < maxQueue ) {
365437 ArrayPrototypePush ( queue , { __proto__ : null , eventType, filename } ) ;
366438 resolve ( ) ;
@@ -409,6 +481,7 @@ async function* watch(filename, options = kEmptyObject) {
409481}
410482
411483module . exports = {
484+ createIgnoreMatcher,
412485 FSWatcher,
413486 StatWatcher,
414487 kFSWatchStart,
0 commit comments