22
33const {
44 ArrayFrom,
5+ ArrayIsArray,
56 ArrayPrototypeAt,
67 ArrayPrototypeFlatMap,
78 ArrayPrototypeMap,
@@ -24,12 +25,18 @@ const {
2425 isMacOS,
2526} = require ( 'internal/util' ) ;
2627const {
27- validateFunction,
2828 validateObject,
2929 validateString,
3030 validateStringArray,
3131} = require ( 'internal/validators' ) ;
3232const { DirentFromStats } = require ( 'internal/fs/utils' ) ;
33+ const {
34+ codes : {
35+ ERR_INVALID_ARG_TYPE ,
36+ } ,
37+ hideStackFrames,
38+ } = require ( 'internal/errors' ) ;
39+ const assert = require ( 'internal/assert' ) ;
3340
3441let minimatch ;
3542function lazyMinimatch ( ) {
@@ -63,6 +70,45 @@ function getDirentSync(path) {
6370 return new DirentFromStats ( basename ( path ) , stat , dirname ( path ) ) ;
6471}
6572
73+ /**
74+ * @callback validateStringArrayOrFunction
75+ * @param {* } value
76+ * @param {string } name
77+ */
78+ const validateStringArrayOrFunction = hideStackFrames ( ( value , name ) => {
79+ if ( ArrayIsArray ( value ) ) {
80+ for ( let i = 0 ; i < value . length ; ++ i ) {
81+ if ( typeof value [ i ] !== 'string' ) {
82+ throw new ERR_INVALID_ARG_TYPE ( `${ name } [${ i } ]` , 'string' , value [ i ] ) ;
83+ }
84+ }
85+ return ;
86+ }
87+ if ( typeof value !== 'function' ) {
88+ throw new ERR_INVALID_ARG_TYPE ( name , [ 'string[]' , 'function' ] , value ) ;
89+ }
90+ } ) ;
91+
92+ /**
93+ * @param {string } pattern
94+ * @param {options } options
95+ * @returns {Minimatch }
96+ */
97+ function createMatcher ( pattern , options = kEmptyObject ) {
98+ const opts = {
99+ __proto__ : null ,
100+ nocase : isWindows || isMacOS ,
101+ windowsPathsNoEscape : true ,
102+ nonegate : true ,
103+ nocomment : true ,
104+ optimizationLevel : 2 ,
105+ platform : process . platform ,
106+ nocaseMagicOnly : true ,
107+ ...options ,
108+ } ;
109+ return new ( lazyMinimatch ( ) . Minimatch ) ( pattern , opts ) ;
110+ }
111+
66112class Cache {
67113 #cache = new SafeMap ( ) ;
68114 #statsCache = new SafeMap ( ) ;
@@ -188,24 +234,56 @@ class Pattern {
188234 }
189235}
190236
237+ class ResultSet extends SafeSet {
238+ #root = '.' ;
239+ #isExcluded = ( ) => false ;
240+ constructor ( i ) { super ( i ) ; } // eslint-disable-line no-useless-constructor
241+
242+ setup ( root , isExcludedFn ) {
243+ this . #root = root ;
244+ this . #isExcluded = isExcludedFn ;
245+ }
246+
247+ add ( value ) {
248+ if ( this . #isExcluded( resolve ( this . #root, value ) ) ) {
249+ return false ;
250+ }
251+ super . add ( value ) ;
252+ return true ;
253+ }
254+ }
255+
191256class Glob {
192257 #root;
193258 #exclude;
194259 #cache = new Cache ( ) ;
195- #results = new SafeSet ( ) ;
260+ #results = new ResultSet ( ) ;
196261 #queue = [ ] ;
197262 #subpatterns = new SafeMap ( ) ;
198263 #patterns;
199264 #withFileTypes;
265+ #isExcluded = ( ) => false ;
200266 constructor ( pattern , options = kEmptyObject ) {
201267 validateObject ( options , 'options' ) ;
202268 const { exclude, cwd, withFileTypes } = options ;
203- if ( exclude != null ) {
204- validateFunction ( exclude , 'options.exclude' ) ;
205- }
206269 this . #root = cwd ?? '.' ;
207- this . #exclude = exclude ;
208270 this . #withFileTypes = ! ! withFileTypes ;
271+ if ( exclude != null ) {
272+ validateStringArrayOrFunction ( exclude , 'options.exclude' ) ;
273+ if ( ArrayIsArray ( exclude ) ) {
274+ assert ( typeof this . #root === 'string' ) ;
275+ // Convert the path part of exclude patterns to absolute paths for
276+ // consistent comparison before instantiating matchers.
277+ const matchers = exclude
278+ . map ( ( pattern ) => resolve ( this . #root, pattern ) )
279+ . map ( ( pattern ) => createMatcher ( pattern ) ) ;
280+ this . #isExcluded = ( value ) =>
281+ matchers . some ( ( matcher ) => matcher . match ( value ) ) ;
282+ this . #results. setup ( this . #root, this . #isExcluded) ;
283+ } else {
284+ this . #exclude = exclude ;
285+ }
286+ }
209287 let patterns ;
210288 if ( typeof pattern === 'object' ) {
211289 validateStringArray ( pattern , 'patterns' ) ;
@@ -214,17 +292,7 @@ class Glob {
214292 validateString ( pattern , 'patterns' ) ;
215293 patterns = [ pattern ] ;
216294 }
217- this . matchers = ArrayPrototypeMap ( patterns , ( pattern ) => new ( lazyMinimatch ( ) . Minimatch ) ( pattern , {
218- __proto__ : null ,
219- nocase : isWindows || isMacOS ,
220- windowsPathsNoEscape : true ,
221- nonegate : true ,
222- nocomment : true ,
223- optimizationLevel : 2 ,
224- platform : process . platform ,
225- nocaseMagicOnly : true ,
226- } ) ) ;
227-
295+ this . matchers = ArrayPrototypeMap ( patterns , ( pattern ) => createMatcher ( pattern ) ) ;
228296 this . #patterns = ArrayPrototypeFlatMap ( this . matchers , ( matcher ) => ArrayPrototypeMap ( matcher . set ,
229297 ( pattern , i ) => new Pattern (
230298 pattern ,
@@ -255,6 +323,9 @@ class Glob {
255323 ) ;
256324 }
257325 #addSubpattern( path , pattern ) {
326+ if ( this . #isExcluded( path ) ) {
327+ return ;
328+ }
258329 if ( ! this . #subpatterns. has ( path ) ) {
259330 this . #subpatterns. set ( path , [ pattern ] ) ;
260331 } else {
@@ -273,6 +344,9 @@ class Glob {
273344 const isLast = pattern . isLast ( isDirectory ) ;
274345 const isFirst = pattern . isFirst ( ) ;
275346
347+ if ( this . #isExcluded( fullpath ) ) {
348+ return ;
349+ }
276350 if ( isFirst && isWindows && typeof pattern . at ( 0 ) === 'string' && StringPrototypeEndsWith ( pattern . at ( 0 ) , ':' ) ) {
277351 // Absolute path, go to root
278352 this . #addSubpattern( `${ pattern . at ( 0 ) } \\` , pattern . child ( new SafeSet ( ) . add ( 1 ) ) ) ;
@@ -461,6 +535,9 @@ class Glob {
461535 const isLast = pattern . isLast ( isDirectory ) ;
462536 const isFirst = pattern . isFirst ( ) ;
463537
538+ if ( this . #isExcluded( fullpath ) ) {
539+ return ;
540+ }
464541 if ( isFirst && isWindows && typeof pattern . at ( 0 ) === 'string' && StringPrototypeEndsWith ( pattern . at ( 0 ) , ':' ) ) {
465542 // Absolute path, go to root
466543 this . #addSubpattern( `${ pattern . at ( 0 ) } \\` , pattern . child ( new SafeSet ( ) . add ( 1 ) ) ) ;
@@ -489,8 +566,9 @@ class Glob {
489566 if ( stat && ( p || isDirectory ) ) {
490567 const result = join ( path , p ) ;
491568 if ( ! this . #results. has ( result ) ) {
492- this . #results. add ( result ) ;
493- yield this . #withFileTypes ? stat : result ;
569+ if ( this . #results. add ( result ) ) {
570+ yield this . #withFileTypes ? stat : result ;
571+ }
494572 }
495573 }
496574 if ( pattern . indexes . size === 1 && pattern . indexes . has ( last ) ) {
@@ -501,8 +579,9 @@ class Glob {
501579 // If pattern ends with **, add to results
502580 // if path is ".", add it only if pattern starts with "." or pattern is exactly "**"
503581 if ( ! this . #results. has ( path ) ) {
504- this . #results. add ( path ) ;
505- yield this . #withFileTypes ? stat : path ;
582+ if ( this . #results. add ( path ) ) {
583+ yield this . #withFileTypes ? stat : path ;
584+ }
506585 }
507586 }
508587
@@ -551,8 +630,9 @@ class Glob {
551630 } else if ( ! fromSymlink && index === last ) {
552631 // If ** is last, add to results
553632 if ( ! this . #results. has ( entryPath ) ) {
554- this . #results. add ( entryPath ) ;
555- yield this . #withFileTypes ? entry : entryPath ;
633+ if ( this . #results. add ( entryPath ) ) {
634+ yield this . #withFileTypes ? entry : entryPath ;
635+ }
556636 }
557637 }
558638
@@ -562,8 +642,9 @@ class Glob {
562642 if ( nextMatches && nextIndex === last && ! isLast ) {
563643 // If next pattern is the last one, add to results
564644 if ( ! this . #results. has ( entryPath ) ) {
565- this . #results. add ( entryPath ) ;
566- yield this . #withFileTypes ? entry : entryPath ;
645+ if ( this . #results. add ( entryPath ) ) {
646+ yield this . #withFileTypes ? entry : entryPath ;
647+ }
567648 }
568649 } else if ( nextMatches && entry . isDirectory ( ) ) {
569650 // Pattern matched, meaning two patterns forward
@@ -598,15 +679,17 @@ class Glob {
598679 if ( ! this . #cache. seen ( path , pattern , nextIndex ) ) {
599680 this . #cache. add ( path , pattern . child ( new SafeSet ( ) . add ( nextIndex ) ) ) ;
600681 if ( ! this . #results. has ( path ) ) {
601- this . #results. add ( path ) ;
602- yield this . #withFileTypes ? this . #cache. statSync ( fullpath ) : path ;
682+ if ( this . #results. add ( path ) ) {
683+ yield this . #withFileTypes ? this . #cache. statSync ( fullpath ) : path ;
684+ }
603685 }
604686 }
605687 if ( ! this . #cache. seen ( path , pattern , nextIndex ) || ! this . #cache. seen ( parent , pattern , nextIndex ) ) {
606688 this . #cache. add ( parent , pattern . child ( new SafeSet ( ) . add ( nextIndex ) ) ) ;
607689 if ( ! this . #results. has ( parent ) ) {
608- this . #results. add ( parent ) ;
609- yield this . #withFileTypes ? this . #cache. statSync ( join ( this . #root, parent ) ) : parent ;
690+ if ( this . #results. add ( parent ) ) {
691+ yield this . #withFileTypes ? this . #cache. statSync ( join ( this . #root, parent ) ) : parent ;
692+ }
610693 }
611694 }
612695 }
@@ -621,8 +704,9 @@ class Glob {
621704 // If current pattern is ".", proceed to test next pattern
622705 if ( nextIndex === last ) {
623706 if ( ! this . #results. has ( entryPath ) ) {
624- this . #results. add ( entryPath ) ;
625- yield this . #withFileTypes ? entry : entryPath ;
707+ if ( this . #results. add ( entryPath ) ) {
708+ yield this . #withFileTypes ? entry : entryPath ;
709+ }
626710 }
627711 } else {
628712 subPatterns . add ( nextIndex + 1 ) ;
@@ -634,8 +718,9 @@ class Glob {
634718 // add next pattern to potential patterns, or to results if it's the last pattern
635719 if ( index === last ) {
636720 if ( ! this . #results. has ( entryPath ) ) {
637- this . #results. add ( entryPath ) ;
638- yield this . #withFileTypes ? entry : entryPath ;
721+ if ( this . #results. add ( entryPath ) ) {
722+ yield this . #withFileTypes ? entry : entryPath ;
723+ }
639724 }
640725 } else if ( entry . isDirectory ( ) ) {
641726 subPatterns . add ( nextIndex ) ;
0 commit comments