'use strict';
/**
* Contains `lookupFiles`, which takes some globs/dirs/options and returns a list of files.
* @module
* @private
*/
var fs = require('fs');
var path = require('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
})
);
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;
};