2 * @fileoverview Tests for FileEnumerator class.
3 * @author Toru Nagashima <https://github.com/mysticatea>
7 const fs
= require("fs");
8 const path
= require("path");
9 const os
= require("os");
10 const { assert
} = require("chai");
11 const sh
= require("shelljs");
12 const { CascadingConfigArrayFactory
} =
13 require("@eslint/eslintrc/lib/cascading-config-array-factory");
14 const { createCustomTeardown
} = require("../../_utils");
15 const { FileEnumerator
} = require("../../../lib/cli-engine/file-enumerator");
17 describe("FileEnumerator", () => {
18 describe("'iterateFiles(patterns)' method should iterate files and configs.", () => {
19 describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
20 const root
= path
.join(os
.tmpdir(), "eslint/file-enumerator");
22 "lib/nested/one.js": "",
23 "lib/nested/two.js": "",
24 "lib/nested/parser.js": "",
25 "lib/nested/.eslintrc.yml": "parser: './parser'",
30 "test/.eslintrc.yml": "env: { mocha: true }",
31 ".eslintignore": "/lib/nested/parser.js",
32 ".eslintrc.json": JSON
.stringify({
35 "no-unused-vars": "error"
39 const { prepare
, cleanup
, getPath
} = createCustomTeardown({ cwd
: root
, files
});
41 /** @type {FileEnumerator} */
44 beforeEach(async () => {
46 enumerator
= new FileEnumerator({ cwd
: getPath() });
51 it("should ignore empty strings.", () => {
52 Array
.from(enumerator
.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error.
55 describe("if 'lib/*.js' was given,", () => {
57 /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
61 list
= [...enumerator
.iterateFiles("lib/*.js")];
64 it("should list two files.", () => {
65 assert
.strictEqual(list
.length
, 2);
68 it("should list 'lib/one.js' and 'lib/two.js'.", () => {
69 assert
.deepStrictEqual(
70 list
.map(entry
=> entry
.filePath
),
72 path
.join(root
, "lib/one.js"),
73 path
.join(root
, "lib/two.js")
78 it("should use the config '.eslintrc.json' for both files.", () => {
79 assert
.strictEqual(list
[0].config
, list
[1].config
);
80 assert
.strictEqual(list
[0].config
.length
, 3);
81 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
82 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
83 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, ".eslintignore"));
87 describe("if 'lib/**/*.js' was given,", () => {
89 /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
93 list
= [...enumerator
.iterateFiles("lib/**/*.js")];
96 it("should list four files.", () => {
97 assert
.strictEqual(list
.length
, 4);
100 it("should list 'lib/nested/one.js', 'lib/nested/two.js', 'lib/one.js', 'lib/two.js'.", () => {
101 assert
.deepStrictEqual(
102 list
.map(entry
=> entry
.filePath
),
104 path
.join(root
, "lib/nested/one.js"),
105 path
.join(root
, "lib/nested/two.js"),
106 path
.join(root
, "lib/one.js"),
107 path
.join(root
, "lib/two.js")
112 it("should use the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' for 'lib/nested/one.js' and 'lib/nested/two.js'.", () => {
113 assert
.strictEqual(list
[0].config
, list
[1].config
);
114 assert
.strictEqual(list
[0].config
.length
, 4);
115 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
116 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
117 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, "lib/nested/.eslintrc.yml"));
118 assert
.strictEqual(list
[0].config
[3].filePath
, path
.join(root
, ".eslintignore"));
121 it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => {
122 assert
.strictEqual(list
[2].config
, list
[3].config
);
123 assert
.strictEqual(list
[2].config
.length
, 3);
124 assert
.strictEqual(list
[2].config
[0].name
, "DefaultIgnorePattern");
125 assert
.strictEqual(list
[2].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
126 assert
.strictEqual(list
[2].config
[2].filePath
, path
.join(root
, ".eslintignore"));
130 describe("if 'lib/*.js' and 'test/*.js' were given,", () => {
132 /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
136 list
= [...enumerator
.iterateFiles(["lib/*.js", "test/*.js"])];
139 it("should list four files.", () => {
140 assert
.strictEqual(list
.length
, 4);
143 it("should list 'lib/one.js', 'lib/two.js', 'test/one.js', 'test/two.js'.", () => {
144 assert
.deepStrictEqual(
145 list
.map(entry
=> entry
.filePath
),
147 path
.join(root
, "lib/one.js"),
148 path
.join(root
, "lib/two.js"),
149 path
.join(root
, "test/one.js"),
150 path
.join(root
, "test/two.js")
155 it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => {
156 assert
.strictEqual(list
[0].config
, list
[1].config
);
157 assert
.strictEqual(list
[0].config
.length
, 3);
158 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
159 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
160 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, ".eslintignore"));
163 it("should use the merged config of '.eslintrc.json' and 'test/.eslintrc.yml' for 'test/one.js' and 'test/two.js'.", () => {
164 assert
.strictEqual(list
[2].config
, list
[3].config
);
165 assert
.strictEqual(list
[2].config
.length
, 4);
166 assert
.strictEqual(list
[2].config
[0].name
, "DefaultIgnorePattern");
167 assert
.strictEqual(list
[2].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
168 assert
.strictEqual(list
[2].config
[2].filePath
, path
.join(root
, "test/.eslintrc.yml"));
169 assert
.strictEqual(list
[2].config
[3].filePath
, path
.join(root
, ".eslintignore"));
174 // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases.
175 describe("with 'tests/fixtures/glob-utils' files", () => {
179 * Returns the path inside of the fixture directory.
180 * @param {...string} args file path segments.
181 * @returns {string} The path inside the fixture directory.
184 function getFixturePath(...args
) {
185 return path
.join(fs
.realpathSync(fixtureDir
), ...args
);
189 * List files as a compatible shape with glob-utils.
190 * @param {string|string[]} patterns The patterns to list files.
191 * @param {Object} options The option for FileEnumerator.
192 * @returns {{filename:string,ignored:boolean}[]} The listed files.
194 function listFiles(patterns
, options
) {
198 configArrayFactory
: new CascadingConfigArrayFactory({
201 // Disable "No Configuration Found" error.
204 }).iterateFiles(patterns
),
205 ({ filePath
, ignored
}) => ({ filename
: filePath
, ignored
})
212 * GitHub Actions Windows and macOS runners occasionally
213 * exhibit extremely slow filesystem operations, during which
214 * copying fixtures exceeds the default test timeout, so raise
215 * it just for this hook. Mocha uses `this` to set timeouts on
216 * an individual hook level.
218 this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
219 fixtureDir
= `${os.tmpdir()}/eslint/tests/fixtures/`;
220 sh
.mkdir("-p", fixtureDir
);
221 sh
.cp("-r", "./tests/fixtures/*", fixtureDir
);
225 sh
.rm("-r", fixtureDir
);
228 describe("listFilesToProcess()", () => {
229 it("should return an array with a resolved (absolute) filename", () => {
230 const patterns
= [getFixturePath("glob-util", "one-js-file", "**/*.js")];
231 const result
= listFiles(patterns
, {
232 cwd
: getFixturePath()
235 const file1
= getFixturePath("glob-util", "one-js-file", "baz.js");
237 assert
.isArray(result
);
238 assert
.deepStrictEqual(result
, [{ filename
: file1
, ignored
: false }]);
241 it("should return all files matching a glob pattern", () => {
242 const patterns
= [getFixturePath("glob-util", "two-js-files", "**/*.js")];
243 const result
= listFiles(patterns
, {
244 cwd
: getFixturePath()
247 const file1
= getFixturePath("glob-util", "two-js-files", "bar.js");
248 const file2
= getFixturePath("glob-util", "two-js-files", "foo.js");
250 assert
.strictEqual(result
.length
, 2);
251 assert
.deepStrictEqual(result
, [
252 { filename
: file1
, ignored
: false },
253 { filename
: file2
, ignored
: false }
257 it("should return all files matching multiple glob patterns", () => {
259 getFixturePath("glob-util", "two-js-files", "**/*.js"),
260 getFixturePath("glob-util", "one-js-file", "**/*.js")
262 const result
= listFiles(patterns
, {
263 cwd
: getFixturePath()
266 const file1
= getFixturePath("glob-util", "two-js-files", "bar.js");
267 const file2
= getFixturePath("glob-util", "two-js-files", "foo.js");
268 const file3
= getFixturePath("glob-util", "one-js-file", "baz.js");
270 assert
.strictEqual(result
.length
, 3);
271 assert
.deepStrictEqual(result
, [
272 { filename
: file1
, ignored
: false },
273 { filename
: file2
, ignored
: false },
274 { filename
: file3
, ignored
: false }
278 it("should ignore hidden files for standard glob patterns", () => {
279 const patterns
= [getFixturePath("glob-util", "hidden", "**/*.js")];
281 assert
.throws(() => {
282 listFiles(patterns
, {
283 cwd
: getFixturePath()
285 }, `All files matched by '${patterns[0]}' are ignored.`);
288 it("should return hidden files if included in glob pattern", () => {
289 const patterns
= [getFixturePath("glob-util", "hidden", "**/.*.js")];
290 const result
= listFiles(patterns
, {
291 cwd
: getFixturePath()
294 const file1
= getFixturePath("glob-util", "hidden", ".foo.js");
296 assert
.strictEqual(result
.length
, 1);
297 assert
.deepStrictEqual(result
, [
298 { filename
: file1
, ignored
: false }
302 it("should ignore default ignored files if not passed explicitly", () => {
303 const directory
= getFixturePath("glob-util", "hidden");
304 const patterns
= [directory
];
306 assert
.throws(() => {
307 listFiles(patterns
, {
308 cwd
: getFixturePath()
310 }, `All files matched by '${directory}' are ignored.`);
313 it("should ignore and warn for default ignored files when passed explicitly", () => {
314 const filename
= getFixturePath("glob-util", "hidden", ".foo.js");
315 const patterns
= [filename
];
316 const result
= listFiles(patterns
, {
317 cwd
: getFixturePath()
320 assert
.strictEqual(result
.length
, 1);
321 assert
.deepStrictEqual(result
[0], { filename
, ignored
: true });
324 it("should ignore default ignored files if not passed explicitly even if ignore is false", () => {
325 const directory
= getFixturePath("glob-util", "hidden");
326 const patterns
= [directory
];
328 assert
.throws(() => {
329 listFiles(patterns
, {
330 cwd
: getFixturePath(),
333 }, `All files matched by '${directory}' are ignored.`);
336 it("should not ignore default ignored files when passed explicitly if ignore is false", () => {
337 const filename
= getFixturePath("glob-util", "hidden", ".foo.js");
338 const patterns
= [filename
];
339 const result
= listFiles(patterns
, {
340 cwd
: getFixturePath(),
344 assert
.strictEqual(result
.length
, 1);
345 assert
.deepStrictEqual(result
[0], { filename
, ignored
: false });
348 it("should throw an error for a file which does not exist", () => {
349 const filename
= getFixturePath("glob-util", "hidden", "bar.js");
350 const patterns
= [filename
];
352 assert
.throws(() => {
353 listFiles(patterns
, {
354 cwd
: getFixturePath(),
355 allowMissingGlobs
: true
357 }, `No files matching '${filename}' were found.`);
360 it("should throw if a folder that does not have any applicable files is linted", () => {
361 const filename
= getFixturePath("glob-util", "empty");
362 const patterns
= [filename
];
364 assert
.throws(() => {
365 listFiles(patterns
, {
366 cwd
: getFixturePath()
368 }, `No files matching '${filename}' were found.`);
371 it("should throw if only ignored files match a glob", () => {
372 const pattern
= getFixturePath("glob-util", "ignored");
373 const options
= { ignore
: true, ignorePath
: getFixturePath("glob-util", "ignored", ".eslintignore") };
375 assert
.throws(() => {
376 listFiles([pattern
], options
);
377 }, `All files matched by '${pattern}' are ignored.`);
380 it("should throw an error if no files match a glob", () => {
382 // Relying here on the .eslintignore from the repo root
383 const patterns
= ["tests/fixtures/glob-util/ignored/**/*.js"];
385 assert
.throws(() => {
387 }, `All files matched by '${patterns[0]}' are ignored.`);
390 it("should return an ignored file, if ignore option is turned off", () => {
391 const options
= { ignore
: false };
392 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
393 const result
= listFiles(patterns
, options
);
395 assert
.strictEqual(result
.length
, 1);
398 it("should ignore a file from a glob if it matches a pattern in an ignore file", () => {
399 const options
= { ignore
: true, ignorePath
: getFixturePath("glob-util", "ignored", ".eslintignore") };
400 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
402 assert
.throws(() => {
403 listFiles(patterns
, options
);
404 }, `All files matched by '${patterns[0]}' are ignored.`);
407 it("should ignore a file from a glob if matching a specified ignore pattern", () => {
408 const options
= { ignore
: true, cliConfig
: { ignorePatterns
: ["foo.js"] }, cwd
: getFixturePath() };
409 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
411 assert
.throws(() => {
412 listFiles(patterns
, options
);
413 }, `All files matched by '${patterns[0]}' are ignored.`);
416 it("should return a file only once if listed in more than 1 pattern", () => {
418 getFixturePath("glob-util", "one-js-file", "**/*.js"),
419 getFixturePath("glob-util", "one-js-file", "baz.js")
421 const result
= listFiles(patterns
, {
422 cwd
: path
.join(fixtureDir
, "..")
425 const file1
= getFixturePath("glob-util", "one-js-file", "baz.js");
427 assert
.isArray(result
);
428 assert
.deepStrictEqual(result
, [
429 { filename
: file1
, ignored
: false }
433 it("should set 'ignored: true' for files that are explicitly specified but ignored", () => {
434 const options
= { ignore
: true, cliConfig
: { ignorePatterns
: ["foo.js"] }, cwd
: getFixturePath() };
435 const filename
= getFixturePath("glob-util", "ignored", "foo.js");
436 const patterns
= [filename
];
437 const result
= listFiles(patterns
, options
);
439 assert
.strictEqual(result
.length
, 1);
440 assert
.deepStrictEqual(result
, [
441 { filename
, ignored
: true }
445 it("should not return files from default ignored folders", () => {
446 const options
= { cwd
: getFixturePath("glob-util") };
447 const glob
= getFixturePath("glob-util", "**/*.js");
448 const patterns
= [glob
];
449 const result
= listFiles(patterns
, options
);
450 const resultFilenames
= result
.map(resultObj
=> resultObj
.filename
);
452 assert
.notInclude(resultFilenames
, getFixturePath("glob-util", "node_modules", "dependency.js"));
455 it("should return unignored files from default ignored folders", () => {
456 const options
= { cliConfig
: { ignorePatterns
: ["!/node_modules/dependency.js"] }, cwd
: getFixturePath("glob-util") };
457 const glob
= getFixturePath("glob-util", "**/*.js");
458 const patterns
= [glob
];
459 const result
= listFiles(patterns
, options
);
460 const unignoredFilename
= getFixturePath("glob-util", "node_modules", "dependency.js");
462 assert
.includeDeepMembers(result
, [{ filename
: unignoredFilename
, ignored
: false }]);
465 it("should return unignored files from folders unignored in .eslintignore", () => {
466 const options
= { cwd
: getFixturePath("glob-util", "unignored"), ignore
: true };
467 const glob
= getFixturePath("glob-util", "unignored", "**/*.js");
468 const patterns
= [glob
];
469 const result
= listFiles(patterns
, options
);
471 const filename
= getFixturePath("glob-util", "unignored", "dir", "foo.js");
473 assert
.strictEqual(result
.length
, 1);
474 assert
.deepStrictEqual(result
, [{ filename
, ignored
: false }]);
477 it("should return unignored files from folders unignored in .eslintignore for explicitly specified folder", () => {
478 const options
= { cwd
: getFixturePath("glob-util", "unignored"), ignore
: true };
479 const dir
= getFixturePath("glob-util", "unignored", "dir");
480 const patterns
= [dir
];
481 const result
= listFiles(patterns
, options
);
483 const filename
= getFixturePath("glob-util", "unignored", "dir", "foo.js");
485 assert
.strictEqual(result
.length
, 1);
486 assert
.deepStrictEqual(result
, [{ filename
, ignored
: false }]);
492 // https://github.com/eslint/eslint/issues/13789
493 describe("constructor default values when config extends eslint:recommended", () => {
494 const root
= path
.join(os
.tmpdir(), "eslint/file-enumerator");
497 ".eslintrc.json": JSON
.stringify({
498 extends: ["eslint:recommended"]
501 const { prepare
, cleanup
, getPath
} = createCustomTeardown({ cwd
: root
, files
});
504 /** @type {FileEnumerator} */
507 beforeEach(async () => {
509 enumerator
= new FileEnumerator({ cwd
: getPath() });
514 it("should not throw an exception iterating files", () => {
515 Array
.from(enumerator
.iterateFiles(["."]));