Skip to content

Commit

Permalink
feat: Swap out Globby for custom globbing solution.
Browse files Browse the repository at this point in the history
Removes globby due to numerous issues with ignoring files and returning
the correct files.

Fixes #16354
Fixes #16340
Fixes #16300
  • Loading branch information
nzakas committed Oct 10, 2022
1 parent 94ba68d commit afb10e6
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 14 deletions.
130 changes: 119 additions & 11 deletions lib/eslint/eslint-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ const path = require("path");
const fs = require("fs");
const fsp = fs.promises;
const isGlob = require("is-glob");
const globby = require("globby");
const hash = require("../cli-engine/hash");
const minimatch = require("minimatch");
const util = require("util");
const fswalk = require("@nodelib/fs.walk");

//-----------------------------------------------------------------------------
// Fix fswalk
//-----------------------------------------------------------------------------

const doFsWalk = util.promisify(fswalk.walk);

//-----------------------------------------------------------------------------
// Errors
Expand Down Expand Up @@ -97,6 +104,105 @@ function isGlobPattern(pattern) {
return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
}

/**
* Searches a directory looking for matching glob patterns. This uses
* the config array's logic to determine if a directory or file should
* be ignored, so it is consistent with how ignoring works throughout
* ESLint.
* @param {Object} options The options for this function.
* @param {string} options.cwd The directory to search.
* @param {Array<string>} options.patterns An array of glob patterns
* to match.
* @param {FlatConfigArray} options.configs The config array to use for
* determining what to ignore.
* @returns {Promise<Array<string>>} An array of matching file paths
* or an empty array if there are no matches.
*/
async function globSearch({ cwd, patterns, configs }) {

if (patterns.length === 0) {
return [];
}

const matchers = patterns.map(pattern => new minimatch.Minimatch(pattern));

return (await doFsWalk(cwd, {

deepFilter(entry) {
return !configs.isDirectoryIgnored(entry.path);
},

entryFilter(entry) {

// entries may be directories or files so filter out directories
if (entry.dirent.isDirectory()) {
return false;
}

const posixName = normalizeToPosix(path.relative(cwd, entry.path));
const matchesPattern = matchers.some(matcher => {

/*
* Patterns can either be relative, like `foo/bar` or
* absolute like `c:/foo/bar/**` and so we need to apply
* the match either against the relative or absolute filename,
* respectively.
*/
if (path.isAbsolute(matcher.pattern)) {
return matcher.match(entry.path);
}

return matcher.match(posixName);
});

return matchesPattern && !configs.isFileIgnored(entry.path);
}
})).map(entry => entry.path);

}

/**
* Determines if a given glob pattern will return any results.
* Used primarily to help with useful error messages.
* @param {Object} options The options for the function.
* @param {string} options.cwd The directory to search.
* @param {string} options.pattern A glob pattern to match.
* @returns {Promise<boolean>} True if there is a glob match, false if not.
*/
async function globMatch({ cwd, pattern }) {

let found = false;

const fsWalkSettings = {

deepFilter() {
return !found;
},

entryFilter(entry) {
if (found) {
return false;
}

const posixName = normalizeToPosix(path.relative(cwd, entry.path));

if (minimatch(path.isAbsolute(pattern) ? entry.path : posixName, pattern)) {
found = true;
return true;
}

return false;
}
};

/* eslint-disable-next-line no-unreachable-loop -- Don't need the value */
for await (const entry of fswalk.walkStream(cwd, fsWalkSettings)) {
return true;
}

return false;
}

/**
* Finds all files matching the options specified.
* @param {Object} args The arguments objects.
Expand Down Expand Up @@ -226,32 +332,34 @@ async function findFiles({
});

// note: globbyPatterns can be an empty array
const globbyResults = (await globby(globbyPatterns, {
const globbyResults = await globSearch({
cwd,
absolute: true,
ignore: configs.ignores.filter(matcher => typeof matcher === "string")
}));
patterns: globbyPatterns,
configs,
shouldIgnore: true
});

// if there are no results, tell the user why
if (!results.length && !globbyResults.length) {

// try globby without ignoring anything
/* eslint-disable no-unreachable-loop -- We want to exit early. */
for (const globbyPattern of globbyPatterns) {

/* eslint-disable-next-line no-unused-vars -- Want to exit early. */
for await (const filePath of globby.stream(globbyPattern, { cwd, absolute: true })) {
// check if there are any matches at all
const patternHasMatch = await globMatch({
cwd,
pattern: globbyPattern
});

// files were found but ignored
if (patternHasMatch) {
throw new AllFilesIgnoredError(globbyPattern);
}

// no files were found
// otherwise no files were found
if (errorOnUnmatchedPattern) {
throw new NoFilesFoundError(globbyPattern, globInputPaths);
}
}
/* eslint-enable no-unreachable-loop -- Go back to normal. */

}

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@
"bugs": "https://github.com/eslint/eslint/issues/",
"dependencies": {
"@eslint/eslintrc": "^1.3.3",
"@humanwhocodes/config-array": "^0.10.5",
"@humanwhocodes/config-array": "^0.11.1",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
Expand All @@ -75,7 +76,6 @@
"find-up": "^5.0.0",
"glob-parent": "^6.0.1",
"globals": "^13.15.0",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
Expand Down
3 changes: 2 additions & 1 deletion tests/lib/eslint/flat-eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,8 @@ describe("FlatESLint", () => {

it("should throw an error when given a directory with all eslint excluded files in the directory", async () => {
eslint = new FlatESLint({
overrideConfigFile: getFixturePath("eslint.config_with_ignores.js")
cwd: getFixturePath(),
ignorePath: getFixturePath(".eslintignore")
});

await assert.rejects(async () => {
Expand Down

0 comments on commit afb10e6

Please sign in to comment.