2 * @fileoverview Tests for FileEnumerator class.
3 * @author Toru Nagashima <https://github.com/mysticatea>
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const fs
= require("fs");
12 const path
= require("path");
13 const os
= require("os");
14 const { assert
} = require("chai");
15 const sh
= require("shelljs");
18 CascadingConfigArrayFactory
20 } = require("@eslint/eslintrc");
21 const { createCustomTeardown
} = require("../../_utils");
22 const { FileEnumerator
} = require("../../../lib/cli-engine/file-enumerator");
24 //------------------------------------------------------------------------------
26 //------------------------------------------------------------------------------
28 describe("FileEnumerator", () => {
29 describe("'iterateFiles(patterns)' method should iterate files and configs.", () => {
30 describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
31 const root
= path
.join(os
.tmpdir(), "eslint/file-enumerator");
33 "lib/nested/one.js": "",
34 "lib/nested/two.js": "",
35 "lib/nested/parser.js": "",
36 "lib/nested/.eslintrc.yml": "parser: './parser'",
41 "test/.eslintrc.yml": "env: { mocha: true }",
42 ".eslintignore": "/lib/nested/parser.js",
43 ".eslintrc.json": JSON
.stringify({
46 "no-unused-vars": "error"
50 const { prepare
, cleanup
, getPath
} = createCustomTeardown({ cwd
: root
, files
});
52 /** @type {FileEnumerator} */
55 beforeEach(async () => {
57 enumerator
= new FileEnumerator({ cwd
: getPath() });
62 it("should ignore empty strings.", () => {
63 Array
.from(enumerator
.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error.
66 describe("if 'lib/*.js' was given,", () => {
68 /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
72 list
= [...enumerator
.iterateFiles("lib/*.js")];
75 it("should list two files.", () => {
76 assert
.strictEqual(list
.length
, 2);
79 it("should list 'lib/one.js' and 'lib/two.js'.", () => {
80 assert
.deepStrictEqual(
81 list
.map(entry
=> entry
.filePath
),
83 path
.join(root
, "lib/one.js"),
84 path
.join(root
, "lib/two.js")
89 it("should use the config '.eslintrc.json' for both files.", () => {
90 assert
.strictEqual(list
[0].config
, list
[1].config
);
91 assert
.strictEqual(list
[0].config
.length
, 3);
92 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
93 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
94 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, ".eslintignore"));
98 describe("if 'lib/**/*.js' was given,", () => {
100 /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
104 list
= [...enumerator
.iterateFiles("lib/**/*.js")];
107 it("should list four files.", () => {
108 assert
.strictEqual(list
.length
, 4);
111 it("should list 'lib/nested/one.js', 'lib/nested/two.js', 'lib/one.js', 'lib/two.js'.", () => {
112 assert
.deepStrictEqual(
113 list
.map(entry
=> entry
.filePath
),
115 path
.join(root
, "lib/nested/one.js"),
116 path
.join(root
, "lib/nested/two.js"),
117 path
.join(root
, "lib/one.js"),
118 path
.join(root
, "lib/two.js")
123 it("should use the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' for 'lib/nested/one.js' and 'lib/nested/two.js'.", () => {
124 assert
.strictEqual(list
[0].config
, list
[1].config
);
125 assert
.strictEqual(list
[0].config
.length
, 4);
126 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
127 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
128 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, "lib/nested/.eslintrc.yml"));
129 assert
.strictEqual(list
[0].config
[3].filePath
, path
.join(root
, ".eslintignore"));
132 it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => {
133 assert
.strictEqual(list
[2].config
, list
[3].config
);
134 assert
.strictEqual(list
[2].config
.length
, 3);
135 assert
.strictEqual(list
[2].config
[0].name
, "DefaultIgnorePattern");
136 assert
.strictEqual(list
[2].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
137 assert
.strictEqual(list
[2].config
[2].filePath
, path
.join(root
, ".eslintignore"));
141 describe("if 'lib/*.js' and 'test/*.js' were given,", () => {
143 /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
147 list
= [...enumerator
.iterateFiles(["lib/*.js", "test/*.js"])];
150 it("should list four files.", () => {
151 assert
.strictEqual(list
.length
, 4);
154 it("should list 'lib/one.js', 'lib/two.js', 'test/one.js', 'test/two.js'.", () => {
155 assert
.deepStrictEqual(
156 list
.map(entry
=> entry
.filePath
),
158 path
.join(root
, "lib/one.js"),
159 path
.join(root
, "lib/two.js"),
160 path
.join(root
, "test/one.js"),
161 path
.join(root
, "test/two.js")
166 it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => {
167 assert
.strictEqual(list
[0].config
, list
[1].config
);
168 assert
.strictEqual(list
[0].config
.length
, 3);
169 assert
.strictEqual(list
[0].config
[0].name
, "DefaultIgnorePattern");
170 assert
.strictEqual(list
[0].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
171 assert
.strictEqual(list
[0].config
[2].filePath
, path
.join(root
, ".eslintignore"));
174 it("should use the merged config of '.eslintrc.json' and 'test/.eslintrc.yml' for 'test/one.js' and 'test/two.js'.", () => {
175 assert
.strictEqual(list
[2].config
, list
[3].config
);
176 assert
.strictEqual(list
[2].config
.length
, 4);
177 assert
.strictEqual(list
[2].config
[0].name
, "DefaultIgnorePattern");
178 assert
.strictEqual(list
[2].config
[1].filePath
, path
.join(root
, ".eslintrc.json"));
179 assert
.strictEqual(list
[2].config
[2].filePath
, path
.join(root
, "test/.eslintrc.yml"));
180 assert
.strictEqual(list
[2].config
[3].filePath
, path
.join(root
, ".eslintignore"));
185 // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases.
186 describe("with 'tests/fixtures/glob-utils' files", () => {
190 * Returns the path inside of the fixture directory.
191 * @param {...string} args file path segments.
192 * @returns {string} The path inside the fixture directory.
195 function getFixturePath(...args
) {
196 return path
.join(fs
.realpathSync(fixtureDir
), ...args
);
200 * List files as a compatible shape with glob-utils.
201 * @param {string|string[]} patterns The patterns to list files.
202 * @param {Object} options The option for FileEnumerator.
203 * @returns {{filename:string,ignored:boolean}[]} The listed files.
205 function listFiles(patterns
, options
) {
209 configArrayFactory
: new CascadingConfigArrayFactory({
212 // Disable "No Configuration Found" error.
215 }).iterateFiles(patterns
),
216 ({ filePath
, ignored
}) => ({ filename
: filePath
, ignored
})
223 * GitHub Actions Windows and macOS runners occasionally
224 * exhibit extremely slow filesystem operations, during which
225 * copying fixtures exceeds the default test timeout, so raise
226 * it just for this hook. Mocha uses `this` to set timeouts on
227 * an individual hook level.
229 this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API
230 fixtureDir
= `${os.tmpdir()}/eslint/tests/fixtures/`;
231 sh
.mkdir("-p", fixtureDir
);
232 sh
.cp("-r", "./tests/fixtures/*", fixtureDir
);
236 sh
.rm("-r", fixtureDir
);
239 describe("listFilesToProcess()", () => {
240 it("should return an array with a resolved (absolute) filename", () => {
241 const patterns
= [getFixturePath("glob-util", "one-js-file", "**/*.js")];
242 const result
= listFiles(patterns
, {
243 cwd
: getFixturePath()
246 const file1
= getFixturePath("glob-util", "one-js-file", "baz.js");
248 assert
.isArray(result
);
249 assert
.deepStrictEqual(result
, [{ filename
: file1
, ignored
: false }]);
252 it("should return all files matching a glob pattern", () => {
253 const patterns
= [getFixturePath("glob-util", "two-js-files", "**/*.js")];
254 const result
= listFiles(patterns
, {
255 cwd
: getFixturePath()
258 const file1
= getFixturePath("glob-util", "two-js-files", "bar.js");
259 const file2
= getFixturePath("glob-util", "two-js-files", "foo.js");
261 assert
.strictEqual(result
.length
, 2);
262 assert
.deepStrictEqual(result
, [
263 { filename
: file1
, ignored
: false },
264 { filename
: file2
, ignored
: false }
268 it("should return all files matching multiple glob patterns", () => {
270 getFixturePath("glob-util", "two-js-files", "**/*.js"),
271 getFixturePath("glob-util", "one-js-file", "**/*.js")
273 const result
= listFiles(patterns
, {
274 cwd
: getFixturePath()
277 const file1
= getFixturePath("glob-util", "two-js-files", "bar.js");
278 const file2
= getFixturePath("glob-util", "two-js-files", "foo.js");
279 const file3
= getFixturePath("glob-util", "one-js-file", "baz.js");
281 assert
.strictEqual(result
.length
, 3);
282 assert
.deepStrictEqual(result
, [
283 { filename
: file1
, ignored
: false },
284 { filename
: file2
, ignored
: false },
285 { filename
: file3
, ignored
: false }
289 it("should ignore hidden files for standard glob patterns", () => {
290 const patterns
= [getFixturePath("glob-util", "hidden", "**/*.js")];
292 assert
.throws(() => {
293 listFiles(patterns
, {
294 cwd
: getFixturePath()
296 }, `All files matched by '${patterns[0]}' are ignored.`);
299 it("should return hidden files if included in glob pattern", () => {
300 const patterns
= [getFixturePath("glob-util", "hidden", "**/.*.js")];
301 const result
= listFiles(patterns
, {
302 cwd
: getFixturePath()
305 const file1
= getFixturePath("glob-util", "hidden", ".foo.js");
307 assert
.strictEqual(result
.length
, 1);
308 assert
.deepStrictEqual(result
, [
309 { filename
: file1
, ignored
: false }
313 it("should ignore default ignored files if not passed explicitly", () => {
314 const directory
= getFixturePath("glob-util", "hidden");
315 const patterns
= [directory
];
317 assert
.throws(() => {
318 listFiles(patterns
, {
319 cwd
: getFixturePath()
321 }, `All files matched by '${directory}' are ignored.`);
324 it("should ignore and warn for default ignored files when passed explicitly", () => {
325 const filename
= getFixturePath("glob-util", "hidden", ".foo.js");
326 const patterns
= [filename
];
327 const result
= listFiles(patterns
, {
328 cwd
: getFixturePath()
331 assert
.strictEqual(result
.length
, 1);
332 assert
.deepStrictEqual(result
[0], { filename
, ignored
: true });
335 it("should ignore default ignored files if not passed explicitly even if ignore is false", () => {
336 const directory
= getFixturePath("glob-util", "hidden");
337 const patterns
= [directory
];
339 assert
.throws(() => {
340 listFiles(patterns
, {
341 cwd
: getFixturePath(),
344 }, `All files matched by '${directory}' are ignored.`);
347 it("should not ignore default ignored files when passed explicitly if ignore is false", () => {
348 const filename
= getFixturePath("glob-util", "hidden", ".foo.js");
349 const patterns
= [filename
];
350 const result
= listFiles(patterns
, {
351 cwd
: getFixturePath(),
355 assert
.strictEqual(result
.length
, 1);
356 assert
.deepStrictEqual(result
[0], { filename
, ignored
: false });
359 it("should throw an error for a file which does not exist", () => {
360 const filename
= getFixturePath("glob-util", "hidden", "bar.js");
361 const patterns
= [filename
];
363 assert
.throws(() => {
364 listFiles(patterns
, {
365 cwd
: getFixturePath(),
366 allowMissingGlobs
: true
368 }, `No files matching '${filename}' were found.`);
371 it("should throw if a folder that does not have any applicable files is linted", () => {
372 const filename
= getFixturePath("glob-util", "empty");
373 const patterns
= [filename
];
375 assert
.throws(() => {
376 listFiles(patterns
, {
377 cwd
: getFixturePath()
379 }, `No files matching '${filename}' were found.`);
382 it("should throw if only ignored files match a glob", () => {
383 const pattern
= getFixturePath("glob-util", "ignored");
384 const options
= { ignore
: true, ignorePath
: getFixturePath("glob-util", "ignored", ".eslintignore") };
386 assert
.throws(() => {
387 listFiles([pattern
], options
);
388 }, `All files matched by '${pattern}' are ignored.`);
391 it("should throw an error if no files match a glob", () => {
393 // Relying here on the .eslintignore from the repo root
394 const patterns
= ["tests/fixtures/glob-util/ignored/**/*.js"];
396 assert
.throws(() => {
398 }, `All files matched by '${patterns[0]}' are ignored.`);
401 it("should return an ignored file, if ignore option is turned off", () => {
402 const options
= { ignore
: false };
403 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
404 const result
= listFiles(patterns
, options
);
406 assert
.strictEqual(result
.length
, 1);
409 it("should ignore a file from a glob if it matches a pattern in an ignore file", () => {
410 const options
= { ignore
: true, ignorePath
: getFixturePath("glob-util", "ignored", ".eslintignore") };
411 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
413 assert
.throws(() => {
414 listFiles(patterns
, options
);
415 }, `All files matched by '${patterns[0]}' are ignored.`);
418 it("should ignore a file from a glob if matching a specified ignore pattern", () => {
419 const options
= { ignore
: true, cliConfig
: { ignorePatterns
: ["foo.js"] }, cwd
: getFixturePath() };
420 const patterns
= [getFixturePath("glob-util", "ignored", "**/*.js")];
422 assert
.throws(() => {
423 listFiles(patterns
, options
);
424 }, `All files matched by '${patterns[0]}' are ignored.`);
427 it("should return a file only once if listed in more than 1 pattern", () => {
429 getFixturePath("glob-util", "one-js-file", "**/*.js"),
430 getFixturePath("glob-util", "one-js-file", "baz.js")
432 const result
= listFiles(patterns
, {
433 cwd
: path
.join(fixtureDir
, "..")
436 const file1
= getFixturePath("glob-util", "one-js-file", "baz.js");
438 assert
.isArray(result
);
439 assert
.deepStrictEqual(result
, [
440 { filename
: file1
, ignored
: false }
444 it("should set 'ignored: true' for files that are explicitly specified but ignored", () => {
445 const options
= { ignore
: true, cliConfig
: { ignorePatterns
: ["foo.js"] }, cwd
: getFixturePath() };
446 const filename
= getFixturePath("glob-util", "ignored", "foo.js");
447 const patterns
= [filename
];
448 const result
= listFiles(patterns
, options
);
450 assert
.strictEqual(result
.length
, 1);
451 assert
.deepStrictEqual(result
, [
452 { filename
, ignored
: true }
456 it("should not return files from default ignored folders", () => {
457 const options
= { cwd
: getFixturePath("glob-util") };
458 const glob
= getFixturePath("glob-util", "**/*.js");
459 const patterns
= [glob
];
460 const result
= listFiles(patterns
, options
);
461 const resultFilenames
= result
.map(resultObj
=> resultObj
.filename
);
463 assert
.notInclude(resultFilenames
, getFixturePath("glob-util", "node_modules", "dependency.js"));
466 it("should return unignored files from default ignored folders", () => {
467 const options
= { cliConfig
: { ignorePatterns
: ["!/node_modules/dependency.js"] }, cwd
: getFixturePath("glob-util") };
468 const glob
= getFixturePath("glob-util", "**/*.js");
469 const patterns
= [glob
];
470 const result
= listFiles(patterns
, options
);
471 const unignoredFilename
= getFixturePath("glob-util", "node_modules", "dependency.js");
473 assert
.includeDeepMembers(result
, [{ filename
: unignoredFilename
, ignored
: false }]);
476 it("should return unignored files from folders unignored in .eslintignore", () => {
477 const options
= { cwd
: getFixturePath("glob-util", "unignored"), ignore
: true };
478 const glob
= getFixturePath("glob-util", "unignored", "**/*.js");
479 const patterns
= [glob
];
480 const result
= listFiles(patterns
, options
);
482 const filename
= getFixturePath("glob-util", "unignored", "dir", "foo.js");
484 assert
.strictEqual(result
.length
, 1);
485 assert
.deepStrictEqual(result
, [{ filename
, ignored
: false }]);
488 it("should return unignored files from folders unignored in .eslintignore for explicitly specified folder", () => {
489 const options
= { cwd
: getFixturePath("glob-util", "unignored"), ignore
: true };
490 const dir
= getFixturePath("glob-util", "unignored", "dir");
491 const patterns
= [dir
];
492 const result
= listFiles(patterns
, options
);
494 const filename
= getFixturePath("glob-util", "unignored", "dir", "foo.js");
496 assert
.strictEqual(result
.length
, 1);
497 assert
.deepStrictEqual(result
, [{ filename
, ignored
: false }]);
502 describe("if contains symbolic links", async () => {
503 const root
= path
.join(os
.tmpdir(), "eslint/file-enumerator");
508 ".eslintrc.json": JSON
.stringify({ rules
: {} })
510 const dir2
= path
.join(root
, "dir2");
511 const { prepare
, cleanup
} = createCustomTeardown({ cwd
: root
, files
});
513 beforeEach(async () => {
516 fs
.symlinkSync(path
.join(root
, "top-level.js"), path
.join(dir2
, "top.js"), "file");
517 fs
.symlinkSync(path
.join(root
, "dir1"), path
.join(dir2
, "nested"), "dir");
522 it("should resolve", () => {
523 const enumerator
= new FileEnumerator({ cwd
: root
});
524 const list
= Array
.from(enumerator
.iterateFiles(["dir2/**/*.js"])).map(({ filePath
}) => filePath
);
526 assert
.deepStrictEqual(list
, [
527 path
.join(dir2
, "nested", "1.js"),
528 path
.join(dir2
, "nested", "2.js"),
529 path
.join(dir2
, "top.js")
533 it("should ignore broken links", () => {
534 fs
.unlinkSync(path
.join(root
, "top-level.js"));
536 const enumerator
= new FileEnumerator({ cwd
: root
});
537 const list
= Array
.from(enumerator
.iterateFiles(["dir2/**/*.js"])).map(({ filePath
}) => filePath
);
539 assert
.deepStrictEqual(list
, [
540 path
.join(dir2
, "nested", "1.js"),
541 path
.join(dir2
, "nested", "2.js")
547 // https://github.com/eslint/eslint/issues/13789
548 describe("constructor default values when config extends eslint:recommended", () => {
549 const root
= path
.join(os
.tmpdir(), "eslint/file-enumerator");
552 ".eslintrc.json": JSON
.stringify({
553 extends: ["eslint:recommended"]
556 const { prepare
, cleanup
, getPath
} = createCustomTeardown({ cwd
: root
, files
});
559 /** @type {FileEnumerator} */
562 beforeEach(async () => {
564 enumerator
= new FileEnumerator({ cwd
: getPath() });
569 it("should not throw an exception iterating files", () => {
570 Array
.from(enumerator
.iterateFiles(["."]));