-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
perf: cli/lookup-files.js, refactor, add jsdoc, reduce intermediary variables #5489
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
base: main
Are you sure you want to change the base?
Conversation
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.
Pull Request Overview
This PR refactors the lookupFiles
function to improve performance by reducing redundant operations and intermediate variables. The main optimization involves normalizing file extensions once at the beginning rather than repeatedly during recursive calls.
- Extracted
normalizeFileExtensions
function to handle extension normalization once per run - Replaced array helper methods with manual loops to improve performance
- Used
fs.statSync
withthrowIfNoEntry: false
to avoid expensive try-catch blocks
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
a8f7451
to
918523e
Compare
…alot of this overhead and improve the performance of big test suites. To improve the performance, we normalize the file extensions once at the beginning and dont need to do it again. This means that we can avoid again normalizing the array in `hasMatchingFileExtension` (before: `hasMatchingExtname`). Also we avoid the performance bottleneck of Array.helper functions like `.map` and `.some()`. `fs.statSync` is called with `throwIfNoEntry`, which exists since node 14.17.0, set to `false`. We can avoid "expensive" errors and the try catch blocks. We just check if stat is undefined, and if so, well, the path has an issue. Using `recursive` and `withFileTypes` option of readdirsync, we can avoid using recursive calling of `lookupFiles`. Added `jsdoc` for better type resolution. Also if you would put `// @ts-check` at the beginning of the file, it would pass.
102cc3c
to
145344c
Compare
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.
I like the idea of this, but before adding & changing logic for a performance need, let's validate the need?
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.
performance
@Uzlopak this looks like a lot of good optimizations, but I'm not aware of any known performance woes with file lookups. Can you demonstrate that there are issues for users? A reproduction case + measurement script, etc.?
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.
This is my main concern as well. Reviewing a PR like this takes a lot of time. I don't want this work to languish but I don't want to ignore potentially bigger issues with Mocha (pipeline failures, known bugs, easy requested features, etc.) I think we should follow the perf issue template and discuss before taking up significant work like this so we're on the same page with review timelines and priority
const { | ||
createNoFilesMatchPatternError, | ||
createMissingArgumentError | ||
} = require('../errors'); |
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.
Can you separate out these unrelated changes, like var
-> const
? I'm in favor of them in general but the diff is already pretty big.
If you want to holistically change most/all var
s to const
s separately I'd be very in favor.
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.
First pass looks good, I'll have to review code coverage and triple check things before I'm comfortable signing off though
return []; | ||
} | ||
|
||
for (var i = 0; i < exts.length; i++) { |
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.
for (var i = 0; i < exts.length; i++) { | |
for (let i = 0; i < exts.length; i++) { |
* @param {FileExtension[]|string[]|undefined|null} exts | ||
* @returns {FileExtension[]} | ||
*/ | ||
const normalizeFileExtensions = (exts) => { |
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.
is the expected behavior for normalizeFileExtensions(['hello.world'])
really to return [.hello.world']
? extensions should just be the stuff after the last .
. Maybe we are careful about this in the callers but it's something to consider.
That said, I haven't read the rest of the PR yet!
module.exports = function lookupFiles( | ||
filepath, | ||
extensions = [], | ||
fileExtensions, |
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.
let's make the code match the comment. I know normalizeFileExtensions
does this for us but explicit is a bit more readable
fileExtensions, | |
fileExtensions = [], |
// Handle directory | ||
const dirEnts = fs.readdirSync(filepath, { recursive, withFileTypes: true }); | ||
|
||
for (var i = 0; i < dirEnts.length; i++) { |
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.
for (var i = 0; i < dirEnts.length; i++) { | |
for (const dirEnt of dirEnts) { |
const pathname = dirEnt.parentPath | ||
? path.join(dirEnt.parentPath, dirEnt.name) | ||
: path.join(filepath, dirEnt.name); |
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.
const pathname = dirEnt.parentPath | |
? path.join(dirEnt.parentPath, dirEnt.name) | |
: path.join(filepath, dirEnt.name); | |
const pathToJoin = dirEnt.parentPath ?? filepath | |
const pathname = path.join(pathToJoin, dirEnt.name) |
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.
This is my main concern as well. Reviewing a PR like this takes a lot of time. I don't want this work to languish but I don't want to ignore potentially bigger issues with Mocha (pipeline failures, known bugs, easy requested features, etc.) I think we should follow the perf issue template and discuss before taking up significant work like this so we're on the same page with review timelines and priority
lookupFiles
repeats various operations over and over. We can avoid alot of this overhead and improve the performance of big test suites.To improve the performance, we normalize the file extensions once at the beginning and dont need to do it again.
This means that we can avoid again normalizing the array in
hasMatchingFileExtension
(before:hasMatchingExtname
). Also we avoid the performance bottleneck of Array.helper functions like.map
and.some()
.fs.statSync
is called withthrowIfNoEntry
, which exists since node 14.17.0, set tofalse
. We can avoid "expensive" errors and the try catch blocks. We just check if stat is undefined, and if so, well, the path has an issue.Using
recursive
andwithFileTypes
option of readdirsync, we can avoid using recursive calling oflookupFiles
.Added
jsdoc
for better type resolution. Also if you would put// @ts-check
at the beginning of the file, it would pass.