File size: 4,331 Bytes
5c2ed06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
'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;
};