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){
6370return 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 ;
200266constructor ( pattern , options = kEmptyObject ) {
201267validateObject ( options , 'options' ) ;
202268const { exclude, cwd, withFileTypes } = options ;
203- if ( exclude != null ) {
204- validateFunction ( exclude , 'options.exclude' ) ;
205- }
206269this . #root = cwd ?? '.' ;
207- this . #exclude = exclude ;
208270this . #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+ }
209287let patterns ;
210288if ( typeof pattern === 'object' ) {
211289validateStringArray ( pattern , 'patterns' ) ;
@@ -214,17 +292,7 @@ class Glob{
214292validateString ( pattern , 'patterns' ) ;
215293patterns = [ 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 ) ) ;
228296this . #patterns = ArrayPrototypeFlatMap ( this . matchers , ( matcher ) => ArrayPrototypeMap ( matcher . set ,
229297( pattern , i ) => new Pattern (
230298pattern ,
@@ -255,6 +323,9 @@ class Glob{
255323) ;
256324}
257325 #addSubpattern( path , pattern ) {
326+ if ( this . #isExcluded( path ) ) {
327+ return ;
328+ }
258329if ( ! this . #subpatterns. has ( path ) ) {
259330this . #subpatterns. set ( path , [ pattern ] ) ;
260331} else {
@@ -273,6 +344,9 @@ class Glob{
273344const isLast = pattern . isLast ( isDirectory ) ;
274345const isFirst = pattern . isFirst ( ) ;
275346
347+ if ( this . #isExcluded( fullpath ) ) {
348+ return ;
349+ }
276350if ( isFirst && isWindows && typeof pattern . at ( 0 ) === 'string' && StringPrototypeEndsWith ( pattern . at ( 0 ) , ':' ) ) {
277351// Absolute path, go to root
278352this . #addSubpattern( `${ pattern . at ( 0 ) } \\` , pattern . child ( new SafeSet ( ) . add ( 1 ) ) ) ;
@@ -461,6 +535,9 @@ class Glob{
461535const isLast = pattern . isLast ( isDirectory ) ;
462536const isFirst = pattern . isFirst ( ) ;
463537
538+ if ( this . #isExcluded( fullpath ) ) {
539+ return ;
540+ }
464541if ( isFirst && isWindows && typeof pattern . at ( 0 ) === 'string' && StringPrototypeEndsWith ( pattern . at ( 0 ) , ':' ) ) {
465542// Absolute path, go to root
466543this . #addSubpattern( `${ pattern . at ( 0 ) } \\` , pattern . child ( new SafeSet ( ) . add ( 1 ) ) ) ;
@@ -489,8 +566,9 @@ class Glob{
489566if ( stat && ( p || isDirectory ) ) {
490567const result = join ( path , p ) ;
491568if ( ! 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}
496574if ( 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 "**"
503581if ( ! 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
553632if ( ! 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{
562642if ( nextMatches && nextIndex === last && ! isLast ) {
563643// If next pattern is the last one, add to results
564644if ( ! 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{
598679if ( ! this . #cache. seen ( path , pattern , nextIndex ) ) {
599680this . #cache. add ( path , pattern . child ( new SafeSet ( ) . add ( nextIndex ) ) ) ;
600681if ( ! 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}
605687if ( ! this . #cache. seen ( path , pattern , nextIndex ) || ! this . #cache. seen ( parent , pattern , nextIndex ) ) {
606688this . #cache. add ( parent , pattern . child ( new SafeSet ( ) . add ( nextIndex ) ) ) ;
607689if ( ! 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
622705if ( nextIndex === last ) {
623706if ( ! 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 {
628712subPatterns . 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
635719if ( index === last ) {
636720if ( ! 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 ( ) ) {
641726subPatterns . add ( nextIndex ) ;
0 commit comments