11'use strict' ;
22
33const {
4+ ArrayFrom,
45 ArrayIsArray,
56 ArrayPrototypeFilter,
67 ArrayPrototypeFind,
@@ -17,7 +18,6 @@ const {
1718 ObjectAssign,
1819 PromisePrototypeThen,
1920 PromiseWithResolvers,
20- RegExpPrototypeSymbolReplace,
2121 SafeMap,
2222 SafePromiseAll,
2323 SafePromiseAllReturnVoid,
@@ -36,7 +36,7 @@ const {
3636const { spawn } = require ( 'child_process' ) ;
3737const { statSync } = require ( 'fs' ) ;
3838const { finished } = require ( 'internal/streams/end-of-stream' ) ;
39- const { resolve, sep, isAbsolute } = require ( 'path' ) ;
39+ const { resolve, sep, isAbsolute, relative } = require ( 'path' ) ;
4040const { DefaultDeserializer, DefaultSerializer } = require ( 'v8' ) ;
4141const { getOptionValue, getOptionsAsFlagsFromBinding } = require ( 'internal/options' ) ;
4242const { Interface } = require ( 'internal/readline/interface' ) ;
@@ -155,39 +155,54 @@ class WorkerIdPool {
155155
156156function createTestFileList ( patterns , cwd ) {
157157 const hasUserSuppliedPattern = patterns != null ;
158+ const directories = new SafeMap ( ) ;
158159 if ( ! patterns || patterns . length === 0 ) {
159160 patterns = [ kDefaultPattern ] ;
160161 } else {
161- patterns = ArrayPrototypeMap ( patterns , ( pattern ) => {
162- // A directory argument is expanded to search for the default test files
163- // within it, matching the behavior from before glob patterns were
164- // supported. Glob patterns always use forward slashes, so a trailing
165- // path separator (of either kind) is stripped before appending.
162+ patterns = ArrayPrototypeFilter ( patterns , ( pattern ) => {
166163 const resolved = isAbsolute ( pattern ) ? pattern : resolve ( cwd , pattern ) ;
167164 try {
168165 if ( statSync ( resolved ) . isDirectory ( ) ) {
169- return `${ RegExpPrototypeSymbolReplace ( / [ \\ / ] + $ / , pattern , '' ) } /${ kDefaultPattern } ` ;
166+ directories . set ( pattern , resolved ) ;
167+ return false ;
170168 }
171169 } catch {
172170 // Not a directory or not accessible; leave the pattern as-is so the
173171 // existing "Could not find" handling still applies.
174172 }
175- return pattern ;
173+ return true ;
176174 } ) ;
177175 }
178176 const glob = new Glob ( patterns , {
179177 __proto__ : null ,
180178 cwd,
181179 exclude : ( name ) => name === 'node_modules' ,
182180 } ) ;
183- const results = glob . globSync ( ) ;
181+ // De-duplicate by absolute path while preserving the paths used in reports.
182+ const results = new SafeMap ( ArrayPrototypeMap ( glob . globSync ( ) , ( file ) => [ resolve ( cwd , file ) , file ] ) ) ;
183+ for ( const { 0 : pattern , 1 : directory } of directories ) {
184+ // Preserve the exclusion of node_modules in the directory argument itself.
185+ if ( ! isAbsolute ( pattern ) && relative ( cwd , directory ) === 'node_modules' ) continue ;
186+ // Search from the directory so its name is never interpreted as a glob.
187+ const directoryGlob = new Glob ( kDefaultPattern , {
188+ __proto__ : null ,
189+ cwd : directory ,
190+ exclude : ( name ) => name === 'node_modules' ,
191+ } ) ;
192+ for ( const file of directoryGlob . globSync ( ) ) {
193+ const absolutePath = resolve ( directory , file ) ;
194+ if ( ! results . has ( absolutePath ) ) {
195+ results . set ( absolutePath , isAbsolute ( pattern ) ? absolutePath : relative ( cwd , absolutePath ) ) ;
196+ }
197+ }
198+ }
184199
185- if ( hasUserSuppliedPattern && results . length === 0 && ! glob . hasMagic ) {
200+ if ( hasUserSuppliedPattern && results . size === 0 && directories . size === 0 && ! glob . hasMagic ) {
186201 console . error ( `Could not find '${ ArrayPrototypeJoin ( patterns , ', ' ) } '` ) ;
187202 process . exit ( kGenericUserError ) ;
188203 }
189204
190- return ArrayPrototypeSort ( results ) ;
205+ return ArrayPrototypeSort ( ArrayFrom ( results . values ( ) ) ) ;
191206}
192207
193208function filterExecArgv ( arg , i , arr ) {
0 commit comments