152 lines
4.2 KiB
JavaScript
152 lines
4.2 KiB
JavaScript
|
'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 {format} = require('util');
|
||
|
var errors = require('../errors');
|
||
|
var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
|
||
|
var createMissingArgumentError = errors.createMissingArgumentError;
|
||
|
var {sQuote, dQuote} = require('../utils');
|
||
|
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)) {
|
||
|
// 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}));
|
||
|
if (!files.length) {
|
||
|
throw createNoFilesMatchPatternError(
|
||
|
'Cannot find any files matching pattern ' + dQuote(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(
|
||
|
format(
|
||
|
'Argument %s required when argument %s is a directory',
|
||
|
sQuote('extensions'),
|
||
|
sQuote('filepath')
|
||
|
),
|
||
|
'extensions',
|
||
|
'array'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (
|
||
|
!stat.isFile() ||
|
||
|
!hasMatchingExtname(pathname, extensions) ||
|
||
|
isHiddenOnUnix(pathname)
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
files.push(pathname);
|
||
|
});
|
||
|
|
||
|
return files;
|
||
|
};
|