| 'use strict'; |
| /** |
| * Contains `lookupFiles`, which takes some globs/dirs/options and returns a list of files. |
| * @module |
| * @private |
| */ |
| |
| var fs = require('node:fs'); |
| var path = require('node:path'); |
| var glob = require('glob'); |
| var errors = require('../errors'); |
| var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError; |
| var createMissingArgumentError = errors.createMissingArgumentError; |
| const debug = require('debug')('mocha:cli:lookup-files'); |
| |
| /** |
| * Determines if pathname would be a "hidden" file (or directory) on UN*X. |
| * |
| * @description |
| * On UN*X, pathnames beginning with a full stop (aka dot) are hidden during |
| * typical usage. Dotfiles, plain-text configuration files, are prime examples. |
| * |
| * @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names} |
| * |
| * @private |
| * @param {string} pathname - Pathname to check for match. |
| * @return {boolean} whether pathname would be considered a hidden file. |
| * @example |
| * isHiddenOnUnix('.profile'); // => true |
| */ |
| const isHiddenOnUnix = pathname => path.basename(pathname).startsWith('.'); |
| |
| /** |
| * Determines if pathname has a matching file extension. |
| * |
| * Supports multi-part extensions. |
| * |
| * @private |
| * @param {string} pathname - Pathname to check for match. |
| * @param {string[]} exts - List of file extensions, w/-or-w/o leading period |
| * @return {boolean} `true` if file extension matches. |
| * @example |
| * hasMatchingExtname('foo.html', ['js', 'css']); // false |
| * hasMatchingExtname('foo.js', ['.js']); // true |
| * hasMatchingExtname('foo.js', ['js']); // ture |
| */ |
| const hasMatchingExtname = (pathname, exts = []) => |
| exts |
| .map(ext => (ext.startsWith('.') ? ext : `.${ext}`)) |
| .some(ext => pathname.endsWith(ext)); |
| |
| /** |
| * Lookup file names at the given `path`. |
| * |
| * @description |
| * Filenames are returned in _traversal_ order by the OS/filesystem. |
| * **Make no assumption that the names will be sorted in any fashion.** |
| * |
| * @public |
| * @alias module:lib/cli.lookupFiles |
| * @param {string} filepath - Base path to start searching from. |
| * @param {string[]} [extensions=[]] - File extensions to look for. |
| * @param {boolean} [recursive=false] - Whether to recurse into subdirectories. |
| * @return {string[]} An array of paths. |
| * @throws {Error} if no files match pattern. |
| * @throws {TypeError} if `filepath` is directory and `extensions` not provided. |
| */ |
| module.exports = function lookupFiles( |
| filepath, |
| extensions = [], |
| recursive = false |
| ) { |
| const files = []; |
| let stat; |
| |
| if (!fs.existsSync(filepath)) { |
| let pattern; |
| if (glob.hasMagic(filepath, {windowsPathsNoEscape: true})) { |
| // Handle glob as is without extensions |
| pattern = filepath; |
| } else { |
| // glob pattern e.g. 'filepath+(.js|.ts)' |
| const strExtensions = extensions |
| .map(ext => (ext.startsWith('.') ? ext : `.${ext}`)) |
| .join('|'); |
| pattern = `${filepath}+(${strExtensions})`; |
| debug('looking for files using glob pattern: %s', pattern); |
| } |
| files.push( |
| ...glob |
| .sync(pattern, { |
| nodir: true, |
| windowsPathsNoEscape: true |
| }) |
| // glob@8 and earlier sorted results in en; glob@9 depends on OS sorting. |
| // This preserves the older glob behavior. |
| // https://github.com/mochajs/mocha/pull/5250/files#r1840469747 |
| .sort((a, b) => a.localeCompare(b, 'en')) |
| ); |
| if (!files.length) { |
| throw createNoFilesMatchPatternError( |
| `Cannot find any files matching pattern "${filepath}"`, |
| filepath |
| ); |
| } |
| return files; |
| } |
| |
| // Handle file |
| try { |
| stat = fs.statSync(filepath); |
| if (stat.isFile()) { |
| return filepath; |
| } |
| } catch (err) { |
| // ignore error |
| return; |
| } |
| |
| // Handle directory |
| fs.readdirSync(filepath).forEach(dirent => { |
| const pathname = path.join(filepath, dirent); |
| let stat; |
| |
| try { |
| stat = fs.statSync(pathname); |
| if (stat.isDirectory()) { |
| if (recursive) { |
| files.push(...lookupFiles(pathname, extensions, recursive)); |
| } |
| return; |
| } |
| } catch (ignored) { |
| return; |
| } |
| if (!extensions.length) { |
| throw createMissingArgumentError( |
| `Argument '${extensions}' required when argument '${filepath}' is a directory`, |
| 'extensions', |
| 'array' |
| ); |
| } |
| |
| if ( |
| !stat.isFile() || |
| !hasMatchingExtname(pathname, extensions) || |
| isHiddenOnUnix(pathname) |
| ) { |
| return; |
| } |
| files.push(pathname); |
| }); |
| |
| return files; |
| }; |