New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Swap out Globby for custom globbing solution. #16369
Changes from all commits
afb10e6
e595cc8
0ef41f0
324fc0a
15fc4b6
c447b9f
15e0f79
2e937a2
24a9cef
371b4eb
6814258
39c5ecd
054670f
e5813fb
5e699df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,7 @@ exports.defaultConfig = [ | |
{ | ||
ignores: [ | ||
"**/node_modules/**", | ||
".git/**" | ||
".git/" | ||
] | ||
}, | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,9 +13,17 @@ 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"); | ||
|
||
//----------------------------------------------------------------------------- | ||
// Fixup references | ||
//----------------------------------------------------------------------------- | ||
|
||
const doFsWalk = util.promisify(fswalk.walk); | ||
const Minimatch = minimatch.Minimatch; | ||
|
||
//----------------------------------------------------------------------------- | ||
// Errors | ||
|
@@ -97,6 +105,120 @@ 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 => { | ||
const patternToUse = path.isAbsolute(pattern) | ||
? normalizeToPosix(path.relative(cwd, pattern)) | ||
: pattern; | ||
|
||
return new minimatch.Minimatch(patternToUse); | ||
}); | ||
|
||
return (await doFsWalk(cwd, { | ||
|
||
deepFilter(entry) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const relativePath = normalizeToPosix(path.relative(cwd, entry.path)); | ||
const matchesPattern = matchers.some(matcher => matcher.match(relativePath, true)); | ||
|
||
return matchesPattern && !configs.isDirectoryIgnored(entry.path); | ||
}, | ||
entryFilter(entry) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const relativePath = normalizeToPosix(path.relative(cwd, entry.path)); | ||
|
||
// entries may be directories or files so filter out directories | ||
if (entry.dirent.isDirectory()) { | ||
return false; | ||
} | ||
|
||
const matchesPattern = matchers.some(matcher => matcher.match(relativePath)); | ||
|
||
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. | ||
*/ | ||
function globMatch({ cwd, pattern }) { | ||
|
||
let found = false; | ||
const patternToUse = path.isAbsolute(pattern) | ||
? normalizeToPosix(path.relative(cwd, pattern)) | ||
: pattern; | ||
|
||
const matcher = new Minimatch(patternToUse); | ||
|
||
const fsWalkSettings = { | ||
|
||
deepFilter(entry) { | ||
const relativePath = normalizeToPosix(path.relative(cwd, entry.path)); | ||
|
||
return !found && matcher.match(relativePath, true); | ||
}, | ||
|
||
entryFilter(entry) { | ||
if (found || entry.dirent.isDirectory()) { | ||
return false; | ||
} | ||
|
||
const relativePath = normalizeToPosix(path.relative(cwd, entry.path)); | ||
|
||
if (matcher.match(relativePath)) { | ||
found = true; | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
}; | ||
|
||
return new Promise(resolve => { | ||
|
||
// using a stream so we can exit early because we just need one match | ||
const globStream = fswalk.walkStream(cwd, fsWalkSettings); | ||
|
||
globStream.on("data", () => { | ||
globStream.destroy(); | ||
resolve(true); | ||
}); | ||
|
||
// swallow errors as they're not important here | ||
globStream.on("error", () => {}); | ||
|
||
globStream.on("end", () => { | ||
resolve(false); | ||
}); | ||
globStream.read(); | ||
}); | ||
|
||
} | ||
|
||
/** | ||
* Finds all files matching the options specified. | ||
* @param {Object} args The arguments objects. | ||
|
@@ -142,7 +264,7 @@ async function findFiles({ | |
if (stat.isFile()) { | ||
results.push({ | ||
filePath, | ||
ignored: configs.isIgnored(filePath) | ||
ignored: configs.isFileIgnored(filePath) | ||
}); | ||
} | ||
|
||
|
@@ -226,32 +348,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. */ | ||
|
||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
ignores: ["subdir/subsubdir"] | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module.exports = [ | ||
{ | ||
ignores: ["a.js"] | ||
} | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
ignores: ["**/ignores-self/**"] | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Starting from
cwd
makes it impossible to lint files outsidecwd
.For example, do
cd lib
and thennpx eslint "../*.js"
.Expected result is to lint
Makefile.js
,karma.conf.js
, and other files in the root of the eslint/eslint project. It used to work before this change, and it works with eslintrc.Actual result:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue #16413