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,9 @@ const {
2224
2325const {
2426 kEmptyObject,
27+ getLazy,
28+ isWindows,
29+ isMacOS,
2530} = require ( 'internal/util' ) ;
2631
2732const {
@@ -48,6 +53,7 @@ const { toNamespacedPath } = require('path');
4853const {
4954 validateAbortSignal,
5055 validateBoolean,
56+ validateIgnoreOption,
5157 validateObject,
5258 validateUint32,
5359 validateInteger,
@@ -60,6 +66,8 @@ const {
6066 } ,
6167} = require ( 'buffer' ) ;
6268
69+ const { isRegExp } = require ( 'internal/util/types' ) ;
70+
6371const assert = require ( 'internal/assert' ) ;
6472
6573const kOldStatus = Symbol ( 'kOldStatus' ) ;
@@ -71,6 +79,50 @@ const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
7179const KFSStatWatcherMaxRefCount = Symbol ( 'KFSStatWatcherMaxRefCount' ) ;
7280const kFSStatWatcherAddOrCleanRef = Symbol ( 'kFSStatWatcherAddOrCleanRef' ) ;
7381
82+ const lazyMinimatch = getLazy ( ( ) => require ( 'internal/deps/minimatch/index' ) ) ;
83+
84+ /**
85+ * Creates an ignore matcher function from the ignore option.
86+ * @param {string | RegExp | Function | Array } ignore - The ignore patterns
87+ * @returns {Function | null } A function that returns true if filename should be ignored
88+ */
89+ function createIgnoreMatcher ( ignore ) {
90+ if ( ignore == null ) return null ;
91+ const matchers = ArrayIsArray ( ignore ) ? ignore : [ ignore ] ;
92+ const compiled = [ ] ;
93+
94+ for ( let i = 0 ; i < matchers . length ; i ++ ) {
95+ const matcher = matchers [ i ] ;
96+ if ( typeof matcher === 'string' ) {
97+ const mm = new ( lazyMinimatch ( ) . Minimatch ) ( matcher , {
98+ __proto__ : null ,
99+ nocase : isWindows || isMacOS ,
100+ windowsPathsNoEscape : true ,
101+ nonegate : true ,
102+ nocomment : true ,
103+ optimizationLevel : 2 ,
104+ platform : process . platform ,
105+ // matchBase allows patterns without slashes to match the basename
106+ // e.g., '*.log' matches 'subdir/file.log'
107+ matchBase : true ,
108+ } ) ;
109+ ArrayPrototypePush ( compiled , ( filename ) => mm . match ( filename ) ) ;
110+ } else if ( isRegExp ( matcher ) ) {
111+ ArrayPrototypePush ( compiled , ( filename ) => RegExpPrototypeExec ( matcher , filename ) !== null ) ;
112+ } else {
113+ // Function
114+ ArrayPrototypePush ( compiled , matcher ) ;
115+ }
116+ }
117+
118+ return ( filename ) => {
119+ for ( let i = 0 ; i < compiled . length ; i ++ ) {
120+ if ( compiled [ i ] ( filename ) ) return true ;
121+ }
122+ return false ;
123+ } ;
124+ }
125+
74126function emitStop ( self ) {
75127 self . emit ( 'stop' ) ;
76128}
@@ -199,6 +251,7 @@ function FSWatcher() {
199251
200252 this . _handle = new FSEvent ( ) ;
201253 this . _handle [ owner_symbol ] = this ;
254+ this . _ignoreMatcher = null ;
202255
203256 this . _handle . onchange = ( status , eventType , filename ) => {
204257 // TODO(joyeecheung): we may check self._handle.initialized here
@@ -219,6 +272,10 @@ function FSWatcher() {
219272 error . filename = filename ;
220273 this . emit ( 'error' , error ) ;
221274 } else {
275+ // Filter events if ignore matcher is set and filename is available
276+ if ( filename != null && this . _ignoreMatcher ?. ( filename ) ) {
277+ return ;
278+ }
222279 this . emit ( 'change' , eventType , filename ) ;
223280 }
224281 } ;
@@ -235,7 +292,8 @@ ObjectSetPrototypeOf(FSWatcher, EventEmitter);
235292FSWatcher . prototype [ kFSWatchStart ] = function ( filename ,
236293 persistent ,
237294 recursive ,
238- encoding ) {
295+ encoding ,
296+ ignore ) {
239297 if ( this . _handle === null ) { // closed
240298 return ;
241299 }
@@ -246,6 +304,10 @@ FSWatcher.prototype[kFSWatchStart] = function(filename,
246304
247305 filename = getValidatedPath ( filename , 'filename' ) ;
248306
307+ // Validate and create the ignore matcher
308+ validateIgnoreOption ( ignore , 'options.ignore' ) ;
309+ this . _ignoreMatcher = createIgnoreMatcher ( ignore ) ;
310+
249311 const err = this . _handle . start ( toNamespacedPath ( filename ) ,
250312 persistent ,
251313 recursive ,
@@ -319,13 +381,15 @@ async function* watch(filename, options = kEmptyObject) {
319381 maxQueue = 2048 ,
320382 overflow = 'ignore' ,
321383 signal,
384+ ignore,
322385 } = options ;
323386
324387 validateBoolean ( persistent , 'options.persistent' ) ;
325388 validateBoolean ( recursive , 'options.recursive' ) ;
326389 validateInteger ( maxQueue , 'options.maxQueue' ) ;
327390 validateOneOf ( overflow , 'options.overflow' , [ 'ignore' , 'error' ] ) ;
328391 validateAbortSignal ( signal , 'options.signal' ) ;
392+ validateIgnoreOption ( ignore , 'options.ignore' ) ;
329393
330394 if ( encoding && ! isEncoding ( encoding ) ) {
331395 const reason = 'is invalid encoding' ;
@@ -336,6 +400,7 @@ async function* watch(filename, options = kEmptyObject) {
336400 throw new AbortError ( undefined , { cause : signal . reason } ) ;
337401
338402 const handle = new FSEvent ( ) ;
403+ const ignoreMatcher = createIgnoreMatcher ( ignore ) ;
339404 let { promise, resolve } = PromiseWithResolvers ( ) ;
340405 const queue = [ ] ;
341406 const oncancel = ( ) => {
@@ -361,6 +426,10 @@ async function* watch(filename, options = kEmptyObject) {
361426 resolve ( ) ;
362427 return ;
363428 }
429+ // Filter events if ignore matcher is set and filename is available
430+ if ( filename != null && ignoreMatcher ?. ( filename ) ) {
431+ return ;
432+ }
364433 if ( queue . length < maxQueue ) {
365434 ArrayPrototypePush ( queue , { __proto__ : null , eventType, filename } ) ;
366435 resolve ( ) ;
@@ -409,6 +478,7 @@ async function* watch(filename, options = kEmptyObject) {
409478}
410479
411480module . exports = {
481+ createIgnoreMatcher,
412482 FSWatcher,
413483 StatWatcher,
414484 kFSWatchStart,
0 commit comments