[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview `FileEnumerator` class.
|
---|
| 3 | *
|
---|
| 4 | * `FileEnumerator` class has two responsibilities:
|
---|
| 5 | *
|
---|
| 6 | * 1. Find target files by processing glob patterns.
|
---|
| 7 | * 2. Tie each target file and appropriate configuration.
|
---|
| 8 | *
|
---|
| 9 | * It provides a method:
|
---|
| 10 | *
|
---|
| 11 | * - `iterateFiles(patterns)`
|
---|
| 12 | * Iterate files which are matched by given patterns together with the
|
---|
| 13 | * corresponded configuration. This is for `CLIEngine#executeOnFiles()`.
|
---|
| 14 | * While iterating files, it loads the configuration file of each directory
|
---|
| 15 | * before iterate files on the directory, so we can use the configuration
|
---|
| 16 | * files to determine target files.
|
---|
| 17 | *
|
---|
| 18 | * @example
|
---|
| 19 | * const enumerator = new FileEnumerator();
|
---|
| 20 | * const linter = new Linter();
|
---|
| 21 | *
|
---|
| 22 | * for (const { config, filePath } of enumerator.iterateFiles(["*.js"])) {
|
---|
| 23 | * const code = fs.readFileSync(filePath, "utf8");
|
---|
| 24 | * const messages = linter.verify(code, config, filePath);
|
---|
| 25 | *
|
---|
| 26 | * console.log(messages);
|
---|
| 27 | * }
|
---|
| 28 | *
|
---|
| 29 | * @author Toru Nagashima <https://github.com/mysticatea>
|
---|
| 30 | */
|
---|
| 31 | "use strict";
|
---|
| 32 |
|
---|
| 33 | //------------------------------------------------------------------------------
|
---|
| 34 | // Requirements
|
---|
| 35 | //------------------------------------------------------------------------------
|
---|
| 36 |
|
---|
| 37 | const fs = require("fs");
|
---|
| 38 | const path = require("path");
|
---|
| 39 | const getGlobParent = require("glob-parent");
|
---|
| 40 | const isGlob = require("is-glob");
|
---|
| 41 | const escapeRegExp = require("escape-string-regexp");
|
---|
| 42 | const { Minimatch } = require("minimatch");
|
---|
| 43 |
|
---|
| 44 | const {
|
---|
| 45 | Legacy: {
|
---|
| 46 | IgnorePattern,
|
---|
| 47 | CascadingConfigArrayFactory
|
---|
| 48 | }
|
---|
| 49 | } = require("@eslint/eslintrc");
|
---|
| 50 | const debug = require("debug")("eslint:file-enumerator");
|
---|
| 51 |
|
---|
| 52 | //------------------------------------------------------------------------------
|
---|
| 53 | // Helpers
|
---|
| 54 | //------------------------------------------------------------------------------
|
---|
| 55 |
|
---|
| 56 | const minimatchOpts = { dot: true, matchBase: true };
|
---|
| 57 | const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;
|
---|
| 58 | const NONE = 0;
|
---|
| 59 | const IGNORED_SILENTLY = 1;
|
---|
| 60 | const IGNORED = 2;
|
---|
| 61 |
|
---|
| 62 | // For VSCode intellisense
|
---|
| 63 | /** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
|
---|
| 64 |
|
---|
| 65 | /**
|
---|
| 66 | * @typedef {Object} FileEnumeratorOptions
|
---|
| 67 | * @property {CascadingConfigArrayFactory} [configArrayFactory] The factory for config arrays.
|
---|
| 68 | * @property {string} [cwd] The base directory to start lookup.
|
---|
| 69 | * @property {string[]} [extensions] The extensions to match files for directory patterns.
|
---|
| 70 | * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
---|
| 71 | * @property {boolean} [ignore] The flag to check ignored files.
|
---|
| 72 | * @property {string[]} [rulePaths] The value of `--rulesdir` option.
|
---|
| 73 | */
|
---|
| 74 |
|
---|
| 75 | /**
|
---|
| 76 | * @typedef {Object} FileAndConfig
|
---|
| 77 | * @property {string} filePath The path to a target file.
|
---|
| 78 | * @property {ConfigArray} config The config entries of that file.
|
---|
| 79 | * @property {boolean} ignored If `true` then this file should be ignored and warned because it was directly specified.
|
---|
| 80 | */
|
---|
| 81 |
|
---|
| 82 | /**
|
---|
| 83 | * @typedef {Object} FileEntry
|
---|
| 84 | * @property {string} filePath The path to a target file.
|
---|
| 85 | * @property {ConfigArray} config The config entries of that file.
|
---|
| 86 | * @property {NONE|IGNORED_SILENTLY|IGNORED} flag The flag.
|
---|
| 87 | * - `NONE` means the file is a target file.
|
---|
| 88 | * - `IGNORED_SILENTLY` means the file should be ignored silently.
|
---|
| 89 | * - `IGNORED` means the file should be ignored and warned because it was directly specified.
|
---|
| 90 | */
|
---|
| 91 |
|
---|
| 92 | /**
|
---|
| 93 | * @typedef {Object} FileEnumeratorInternalSlots
|
---|
| 94 | * @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays.
|
---|
| 95 | * @property {string} cwd The base directory to start lookup.
|
---|
| 96 | * @property {RegExp|null} extensionRegExp The RegExp to test if a string ends with specific file extensions.
|
---|
| 97 | * @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
---|
| 98 | * @property {boolean} ignoreFlag The flag to check ignored files.
|
---|
| 99 | * @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files.
|
---|
| 100 | */
|
---|
| 101 |
|
---|
| 102 | /** @type {WeakMap<FileEnumerator, FileEnumeratorInternalSlots>} */
|
---|
| 103 | const internalSlotsMap = new WeakMap();
|
---|
| 104 |
|
---|
| 105 | /**
|
---|
| 106 | * Check if a string is a glob pattern or not.
|
---|
| 107 | * @param {string} pattern A glob pattern.
|
---|
| 108 | * @returns {boolean} `true` if the string is a glob pattern.
|
---|
| 109 | */
|
---|
| 110 | function isGlobPattern(pattern) {
|
---|
| 111 | return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern);
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | /**
|
---|
| 115 | * Get stats of a given path.
|
---|
| 116 | * @param {string} filePath The path to target file.
|
---|
| 117 | * @throws {Error} As may be thrown by `fs.statSync`.
|
---|
| 118 | * @returns {fs.Stats|null} The stats.
|
---|
| 119 | * @private
|
---|
| 120 | */
|
---|
| 121 | function statSafeSync(filePath) {
|
---|
| 122 | try {
|
---|
| 123 | return fs.statSync(filePath);
|
---|
| 124 | } catch (error) {
|
---|
| 125 |
|
---|
| 126 | /* c8 ignore next */
|
---|
| 127 | if (error.code !== "ENOENT") {
|
---|
| 128 | throw error;
|
---|
| 129 | }
|
---|
| 130 | return null;
|
---|
| 131 | }
|
---|
| 132 | }
|
---|
| 133 |
|
---|
| 134 | /**
|
---|
| 135 | * Get filenames in a given path to a directory.
|
---|
| 136 | * @param {string} directoryPath The path to target directory.
|
---|
| 137 | * @throws {Error} As may be thrown by `fs.readdirSync`.
|
---|
| 138 | * @returns {import("fs").Dirent[]} The filenames.
|
---|
| 139 | * @private
|
---|
| 140 | */
|
---|
| 141 | function readdirSafeSync(directoryPath) {
|
---|
| 142 | try {
|
---|
| 143 | return fs.readdirSync(directoryPath, { withFileTypes: true });
|
---|
| 144 | } catch (error) {
|
---|
| 145 |
|
---|
| 146 | /* c8 ignore next */
|
---|
| 147 | if (error.code !== "ENOENT") {
|
---|
| 148 | throw error;
|
---|
| 149 | }
|
---|
| 150 | return [];
|
---|
| 151 | }
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | /**
|
---|
| 155 | * Create a `RegExp` object to detect extensions.
|
---|
| 156 | * @param {string[] | null} extensions The extensions to create.
|
---|
| 157 | * @returns {RegExp | null} The created `RegExp` object or null.
|
---|
| 158 | */
|
---|
| 159 | function createExtensionRegExp(extensions) {
|
---|
| 160 | if (extensions) {
|
---|
| 161 | const normalizedExts = extensions.map(ext => escapeRegExp(
|
---|
| 162 | ext.startsWith(".")
|
---|
| 163 | ? ext.slice(1)
|
---|
| 164 | : ext
|
---|
| 165 | ));
|
---|
| 166 |
|
---|
| 167 | return new RegExp(
|
---|
| 168 | `.\\.(?:${normalizedExts.join("|")})$`,
|
---|
| 169 | "u"
|
---|
| 170 | );
|
---|
| 171 | }
|
---|
| 172 | return null;
|
---|
| 173 | }
|
---|
| 174 |
|
---|
| 175 | /**
|
---|
| 176 | * The error type when no files match a glob.
|
---|
| 177 | */
|
---|
| 178 | class NoFilesFoundError extends Error {
|
---|
| 179 |
|
---|
| 180 | /**
|
---|
| 181 | * @param {string} pattern The glob pattern which was not found.
|
---|
| 182 | * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled.
|
---|
| 183 | */
|
---|
| 184 | constructor(pattern, globDisabled) {
|
---|
| 185 | super(`No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`);
|
---|
| 186 | this.messageTemplate = "file-not-found";
|
---|
| 187 | this.messageData = { pattern, globDisabled };
|
---|
| 188 | }
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | /**
|
---|
| 192 | * The error type when there are files matched by a glob, but all of them have been ignored.
|
---|
| 193 | */
|
---|
| 194 | class AllFilesIgnoredError extends Error {
|
---|
| 195 |
|
---|
| 196 | /**
|
---|
| 197 | * @param {string} pattern The glob pattern which was not found.
|
---|
| 198 | */
|
---|
| 199 | constructor(pattern) {
|
---|
| 200 | super(`All files matched by '${pattern}' are ignored.`);
|
---|
| 201 | this.messageTemplate = "all-files-ignored";
|
---|
| 202 | this.messageData = { pattern };
|
---|
| 203 | }
|
---|
| 204 | }
|
---|
| 205 |
|
---|
| 206 | /**
|
---|
| 207 | * This class provides the functionality that enumerates every file which is
|
---|
| 208 | * matched by given glob patterns and that configuration.
|
---|
| 209 | */
|
---|
| 210 | class FileEnumerator {
|
---|
| 211 |
|
---|
| 212 | /**
|
---|
| 213 | * Initialize this enumerator.
|
---|
| 214 | * @param {FileEnumeratorOptions} options The options.
|
---|
| 215 | */
|
---|
| 216 | constructor({
|
---|
| 217 | cwd = process.cwd(),
|
---|
| 218 | configArrayFactory = new CascadingConfigArrayFactory({
|
---|
| 219 | cwd,
|
---|
| 220 | getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended,
|
---|
| 221 | getEslintAllConfig: () => require("@eslint/js").configs.all
|
---|
| 222 | }),
|
---|
| 223 | extensions = null,
|
---|
| 224 | globInputPaths = true,
|
---|
| 225 | errorOnUnmatchedPattern = true,
|
---|
| 226 | ignore = true
|
---|
| 227 | } = {}) {
|
---|
| 228 | internalSlotsMap.set(this, {
|
---|
| 229 | configArrayFactory,
|
---|
| 230 | cwd,
|
---|
| 231 | defaultIgnores: IgnorePattern.createDefaultIgnore(cwd),
|
---|
| 232 | extensionRegExp: createExtensionRegExp(extensions),
|
---|
| 233 | globInputPaths,
|
---|
| 234 | errorOnUnmatchedPattern,
|
---|
| 235 | ignoreFlag: ignore
|
---|
| 236 | });
|
---|
| 237 | }
|
---|
| 238 |
|
---|
| 239 | /**
|
---|
| 240 | * Check if a given file is target or not.
|
---|
| 241 | * @param {string} filePath The path to a candidate file.
|
---|
| 242 | * @param {ConfigArray} [providedConfig] Optional. The configuration for the file.
|
---|
| 243 | * @returns {boolean} `true` if the file is a target.
|
---|
| 244 | */
|
---|
| 245 | isTargetPath(filePath, providedConfig) {
|
---|
| 246 | const {
|
---|
| 247 | configArrayFactory,
|
---|
| 248 | extensionRegExp
|
---|
| 249 | } = internalSlotsMap.get(this);
|
---|
| 250 |
|
---|
| 251 | // If `--ext` option is present, use it.
|
---|
| 252 | if (extensionRegExp) {
|
---|
| 253 | return extensionRegExp.test(filePath);
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 | // `.js` file is target by default.
|
---|
| 257 | if (filePath.endsWith(".js")) {
|
---|
| 258 | return true;
|
---|
| 259 | }
|
---|
| 260 |
|
---|
| 261 | // use `overrides[].files` to check additional targets.
|
---|
| 262 | const config =
|
---|
| 263 | providedConfig ||
|
---|
| 264 | configArrayFactory.getConfigArrayForFile(
|
---|
| 265 | filePath,
|
---|
| 266 | { ignoreNotFoundError: true }
|
---|
| 267 | );
|
---|
| 268 |
|
---|
| 269 | return config.isAdditionalTargetPath(filePath);
|
---|
| 270 | }
|
---|
| 271 |
|
---|
| 272 | /**
|
---|
| 273 | * Iterate files which are matched by given glob patterns.
|
---|
| 274 | * @param {string|string[]} patternOrPatterns The glob patterns to iterate files.
|
---|
| 275 | * @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern.
|
---|
| 276 | * @returns {IterableIterator<FileAndConfig>} The found files.
|
---|
| 277 | */
|
---|
| 278 | *iterateFiles(patternOrPatterns) {
|
---|
| 279 | const { globInputPaths, errorOnUnmatchedPattern } = internalSlotsMap.get(this);
|
---|
| 280 | const patterns = Array.isArray(patternOrPatterns)
|
---|
| 281 | ? patternOrPatterns
|
---|
| 282 | : [patternOrPatterns];
|
---|
| 283 |
|
---|
| 284 | debug("Start to iterate files: %o", patterns);
|
---|
| 285 |
|
---|
| 286 | // The set of paths to remove duplicate.
|
---|
| 287 | const set = new Set();
|
---|
| 288 |
|
---|
| 289 | for (const pattern of patterns) {
|
---|
| 290 | let foundRegardlessOfIgnored = false;
|
---|
| 291 | let found = false;
|
---|
| 292 |
|
---|
| 293 | // Skip empty string.
|
---|
| 294 | if (!pattern) {
|
---|
| 295 | continue;
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | // Iterate files of this pattern.
|
---|
| 299 | for (const { config, filePath, flag } of this._iterateFiles(pattern)) {
|
---|
| 300 | foundRegardlessOfIgnored = true;
|
---|
| 301 | if (flag === IGNORED_SILENTLY) {
|
---|
| 302 | continue;
|
---|
| 303 | }
|
---|
| 304 | found = true;
|
---|
| 305 |
|
---|
| 306 | // Remove duplicate paths while yielding paths.
|
---|
| 307 | if (!set.has(filePath)) {
|
---|
| 308 | set.add(filePath);
|
---|
| 309 | yield {
|
---|
| 310 | config,
|
---|
| 311 | filePath,
|
---|
| 312 | ignored: flag === IGNORED
|
---|
| 313 | };
|
---|
| 314 | }
|
---|
| 315 | }
|
---|
| 316 |
|
---|
| 317 | // Raise an error if any files were not found.
|
---|
| 318 | if (errorOnUnmatchedPattern) {
|
---|
| 319 | if (!foundRegardlessOfIgnored) {
|
---|
| 320 | throw new NoFilesFoundError(
|
---|
| 321 | pattern,
|
---|
| 322 | !globInputPaths && isGlob(pattern)
|
---|
| 323 | );
|
---|
| 324 | }
|
---|
| 325 | if (!found) {
|
---|
| 326 | throw new AllFilesIgnoredError(pattern);
|
---|
| 327 | }
|
---|
| 328 | }
|
---|
| 329 | }
|
---|
| 330 |
|
---|
| 331 | debug(`Complete iterating files: ${JSON.stringify(patterns)}`);
|
---|
| 332 | }
|
---|
| 333 |
|
---|
| 334 | /**
|
---|
| 335 | * Iterate files which are matched by a given glob pattern.
|
---|
| 336 | * @param {string} pattern The glob pattern to iterate files.
|
---|
| 337 | * @returns {IterableIterator<FileEntry>} The found files.
|
---|
| 338 | */
|
---|
| 339 | _iterateFiles(pattern) {
|
---|
| 340 | const { cwd, globInputPaths } = internalSlotsMap.get(this);
|
---|
| 341 | const absolutePath = path.resolve(cwd, pattern);
|
---|
| 342 | const isDot = dotfilesPattern.test(pattern);
|
---|
| 343 | const stat = statSafeSync(absolutePath);
|
---|
| 344 |
|
---|
| 345 | if (stat && stat.isDirectory()) {
|
---|
| 346 | return this._iterateFilesWithDirectory(absolutePath, isDot);
|
---|
| 347 | }
|
---|
| 348 | if (stat && stat.isFile()) {
|
---|
| 349 | return this._iterateFilesWithFile(absolutePath);
|
---|
| 350 | }
|
---|
| 351 | if (globInputPaths && isGlobPattern(pattern)) {
|
---|
| 352 | return this._iterateFilesWithGlob(pattern, isDot);
|
---|
| 353 | }
|
---|
| 354 |
|
---|
| 355 | return [];
|
---|
| 356 | }
|
---|
| 357 |
|
---|
| 358 | /**
|
---|
| 359 | * Iterate a file which is matched by a given path.
|
---|
| 360 | * @param {string} filePath The path to the target file.
|
---|
| 361 | * @returns {IterableIterator<FileEntry>} The found files.
|
---|
| 362 | * @private
|
---|
| 363 | */
|
---|
| 364 | _iterateFilesWithFile(filePath) {
|
---|
| 365 | debug(`File: ${filePath}`);
|
---|
| 366 |
|
---|
| 367 | const { configArrayFactory } = internalSlotsMap.get(this);
|
---|
| 368 | const config = configArrayFactory.getConfigArrayForFile(filePath);
|
---|
| 369 | const ignored = this._isIgnoredFile(filePath, { config, direct: true });
|
---|
| 370 | const flag = ignored ? IGNORED : NONE;
|
---|
| 371 |
|
---|
| 372 | return [{ config, filePath, flag }];
|
---|
| 373 | }
|
---|
| 374 |
|
---|
| 375 | /**
|
---|
| 376 | * Iterate files in a given path.
|
---|
| 377 | * @param {string} directoryPath The path to the target directory.
|
---|
| 378 | * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
|
---|
| 379 | * @returns {IterableIterator<FileEntry>} The found files.
|
---|
| 380 | * @private
|
---|
| 381 | */
|
---|
| 382 | _iterateFilesWithDirectory(directoryPath, dotfiles) {
|
---|
| 383 | debug(`Directory: ${directoryPath}`);
|
---|
| 384 |
|
---|
| 385 | return this._iterateFilesRecursive(
|
---|
| 386 | directoryPath,
|
---|
| 387 | { dotfiles, recursive: true, selector: null }
|
---|
| 388 | );
|
---|
| 389 | }
|
---|
| 390 |
|
---|
| 391 | /**
|
---|
| 392 | * Iterate files which are matched by a given glob pattern.
|
---|
| 393 | * @param {string} pattern The glob pattern to iterate files.
|
---|
| 394 | * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
|
---|
| 395 | * @returns {IterableIterator<FileEntry>} The found files.
|
---|
| 396 | * @private
|
---|
| 397 | */
|
---|
| 398 | _iterateFilesWithGlob(pattern, dotfiles) {
|
---|
| 399 | debug(`Glob: ${pattern}`);
|
---|
| 400 |
|
---|
| 401 | const { cwd } = internalSlotsMap.get(this);
|
---|
| 402 | const directoryPath = path.resolve(cwd, getGlobParent(pattern));
|
---|
| 403 | const absolutePath = path.resolve(cwd, pattern);
|
---|
| 404 | const globPart = absolutePath.slice(directoryPath.length + 1);
|
---|
| 405 |
|
---|
| 406 | /*
|
---|
| 407 | * recursive if there are `**` or path separators in the glob part.
|
---|
| 408 | * Otherwise, patterns such as `src/*.js`, it doesn't need recursive.
|
---|
| 409 | */
|
---|
| 410 | const recursive = /\*\*|\/|\\/u.test(globPart);
|
---|
| 411 | const selector = new Minimatch(absolutePath, minimatchOpts);
|
---|
| 412 |
|
---|
| 413 | debug(`recursive? ${recursive}`);
|
---|
| 414 |
|
---|
| 415 | return this._iterateFilesRecursive(
|
---|
| 416 | directoryPath,
|
---|
| 417 | { dotfiles, recursive, selector }
|
---|
| 418 | );
|
---|
| 419 | }
|
---|
| 420 |
|
---|
| 421 | /**
|
---|
| 422 | * Iterate files in a given path.
|
---|
| 423 | * @param {string} directoryPath The path to the target directory.
|
---|
| 424 | * @param {Object} options The options to iterate files.
|
---|
| 425 | * @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default.
|
---|
| 426 | * @param {boolean} [options.recursive] If `true` then it dives into sub directories.
|
---|
| 427 | * @param {InstanceType<Minimatch>} [options.selector] The matcher to choose files.
|
---|
| 428 | * @returns {IterableIterator<FileEntry>} The found files.
|
---|
| 429 | * @private
|
---|
| 430 | */
|
---|
| 431 | *_iterateFilesRecursive(directoryPath, options) {
|
---|
| 432 | debug(`Enter the directory: ${directoryPath}`);
|
---|
| 433 | const { configArrayFactory } = internalSlotsMap.get(this);
|
---|
| 434 |
|
---|
| 435 | /** @type {ConfigArray|null} */
|
---|
| 436 | let config = null;
|
---|
| 437 |
|
---|
| 438 | // Enumerate the files of this directory.
|
---|
| 439 | for (const entry of readdirSafeSync(directoryPath)) {
|
---|
| 440 | const filePath = path.join(directoryPath, entry.name);
|
---|
| 441 | const fileInfo = entry.isSymbolicLink() ? statSafeSync(filePath) : entry;
|
---|
| 442 |
|
---|
| 443 | if (!fileInfo) {
|
---|
| 444 | continue;
|
---|
| 445 | }
|
---|
| 446 |
|
---|
| 447 | // Check if the file is matched.
|
---|
| 448 | if (fileInfo.isFile()) {
|
---|
| 449 | if (!config) {
|
---|
| 450 | config = configArrayFactory.getConfigArrayForFile(
|
---|
| 451 | filePath,
|
---|
| 452 |
|
---|
| 453 | /*
|
---|
| 454 | * We must ignore `ConfigurationNotFoundError` at this
|
---|
| 455 | * point because we don't know if target files exist in
|
---|
| 456 | * this directory.
|
---|
| 457 | */
|
---|
| 458 | { ignoreNotFoundError: true }
|
---|
| 459 | );
|
---|
| 460 | }
|
---|
| 461 | const matched = options.selector
|
---|
| 462 |
|
---|
| 463 | // Started with a glob pattern; choose by the pattern.
|
---|
| 464 | ? options.selector.match(filePath)
|
---|
| 465 |
|
---|
| 466 | // Started with a directory path; choose by file extensions.
|
---|
| 467 | : this.isTargetPath(filePath, config);
|
---|
| 468 |
|
---|
| 469 | if (matched) {
|
---|
| 470 | const ignored = this._isIgnoredFile(filePath, { ...options, config });
|
---|
| 471 | const flag = ignored ? IGNORED_SILENTLY : NONE;
|
---|
| 472 |
|
---|
| 473 | debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`);
|
---|
| 474 | yield {
|
---|
| 475 | config: configArrayFactory.getConfigArrayForFile(filePath),
|
---|
| 476 | filePath,
|
---|
| 477 | flag
|
---|
| 478 | };
|
---|
| 479 | } else {
|
---|
| 480 | debug(`Didn't match: ${entry.name}`);
|
---|
| 481 | }
|
---|
| 482 |
|
---|
| 483 | // Dive into the sub directory.
|
---|
| 484 | } else if (options.recursive && fileInfo.isDirectory()) {
|
---|
| 485 | if (!config) {
|
---|
| 486 | config = configArrayFactory.getConfigArrayForFile(
|
---|
| 487 | filePath,
|
---|
| 488 | { ignoreNotFoundError: true }
|
---|
| 489 | );
|
---|
| 490 | }
|
---|
| 491 | const ignored = this._isIgnoredFile(
|
---|
| 492 | filePath + path.sep,
|
---|
| 493 | { ...options, config }
|
---|
| 494 | );
|
---|
| 495 |
|
---|
| 496 | if (!ignored) {
|
---|
| 497 | yield* this._iterateFilesRecursive(filePath, options);
|
---|
| 498 | }
|
---|
| 499 | }
|
---|
| 500 | }
|
---|
| 501 |
|
---|
| 502 | debug(`Leave the directory: ${directoryPath}`);
|
---|
| 503 | }
|
---|
| 504 |
|
---|
| 505 | /**
|
---|
| 506 | * Check if a given file should be ignored.
|
---|
| 507 | * @param {string} filePath The path to a file to check.
|
---|
| 508 | * @param {Object} options Options
|
---|
| 509 | * @param {ConfigArray} [options.config] The config for this file.
|
---|
| 510 | * @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default.
|
---|
| 511 | * @param {boolean} [options.direct] If `true` then this is a direct specified file.
|
---|
| 512 | * @returns {boolean} `true` if the file should be ignored.
|
---|
| 513 | * @private
|
---|
| 514 | */
|
---|
| 515 | _isIgnoredFile(filePath, {
|
---|
| 516 | config: providedConfig,
|
---|
| 517 | dotfiles = false,
|
---|
| 518 | direct = false
|
---|
| 519 | }) {
|
---|
| 520 | const {
|
---|
| 521 | configArrayFactory,
|
---|
| 522 | defaultIgnores,
|
---|
| 523 | ignoreFlag
|
---|
| 524 | } = internalSlotsMap.get(this);
|
---|
| 525 |
|
---|
| 526 | if (ignoreFlag) {
|
---|
| 527 | const config =
|
---|
| 528 | providedConfig ||
|
---|
| 529 | configArrayFactory.getConfigArrayForFile(
|
---|
| 530 | filePath,
|
---|
| 531 | { ignoreNotFoundError: true }
|
---|
| 532 | );
|
---|
| 533 | const ignores =
|
---|
| 534 | config.extractConfig(filePath).ignores || defaultIgnores;
|
---|
| 535 |
|
---|
| 536 | return ignores(filePath, dotfiles);
|
---|
| 537 | }
|
---|
| 538 |
|
---|
| 539 | return !direct && defaultIgnores(filePath, dotfiles);
|
---|
| 540 | }
|
---|
| 541 | }
|
---|
| 542 |
|
---|
| 543 | //------------------------------------------------------------------------------
|
---|
| 544 | // Public Interface
|
---|
| 545 | //------------------------------------------------------------------------------
|
---|
| 546 |
|
---|
| 547 | module.exports = { FileEnumerator };
|
---|