Repository navigation
perf: sign typed-lint results in one pass over the import graph - #395
Conversation
Each file's signature walked everything it imports, however indirectly, again for every file, so a project whose files import a barrel that imports them back paid for the whole project once per file. Files are now signed by what the files they import are signed by, and the files of an import cycle together, in one pass over the graph. Over 6,000 such modules, signatures took 27 s a build and now take 0.13-0.57 s. Refs #374. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013FhutQipYi25fJ4rs9WE1c
One or more co-authors of this pull request were not found. You must specify co-authors in commit message trailer via: Supported
Alternatively, if the co-author should not be included, remove the Please update your commit message(s) by doing |
Findings
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #395 +/- ##
========================================
Coverage 99.68% 99.69%
========================================
Files 21 21
Lines 6450 6552 +102
========================================
+ Hits 6430 6532 +102
Misses 20 20 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
On Windows, webpack's watcher can rebuild right as the watch starts, reading the fixtures just written as changed. That rebuild lints every file, and the test took it for the one its edit caused a second later: it failed in about 100 ms with 2 files linted where 1 was expected, on main as well as here. Rebuilds before the edit is written are skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013FhutQipYi25fJ4rs9WE1c
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
2f623d81-10d1-4fc9-ad28-2f16e7dbe57e
Files selected for processing: 1-
test/incremental.test.js
Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 0 remain after this review.
-
… before it A rebuild already running when the edit is written can still reach the watch callback once the edit is done, if the write lands while its done hooks run. Its start time, not when its callback arrives, now tells it from the rebuild the edit caused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013FhutQipYi25fJ4rs9WE1c
Refs #374.
Under typed linting, every build checks which saved ESLint results still hold by giving each file a signature: a hash of the file and of everything it imports, however indirectly.
signatures()walked that whole import tree again for every file. When files import a barrel (anindex.tsre-exporting everything), each file's tree is the whole project, so the cost grew with the square of the file count, on cold and warm builds alike.The fix
Each file is now signed from its own contents plus the signatures of the files it imports, in one pass over the graph. Files that import each other in a cycle reach the same files, so they share one signature, computed once per strongly connected component. A change anywhere below a file still changes its signature, so results are kept no longer than before. Saved results are re-linted once after upgrading, because the signature format changed.
Tests
test/import-graph.test.jsadds three cycle cases: a change in a cycle reaches every file in it and its importers, a change below a cycle reaches the cycle, and a change above a cycle leaves it alone. They pass onmaintoo, so the new code keeps the old behavior.npm run lintand the full suite (367 tests) pass. One test,should keep ESLint's cache when asked for and no TypeScript can follow imports, fails in my sandbox onmainas well, because a TypeScript install there resolves from/tmpthroughNODE_PATH. It passes withNODE_PATHunset.Not covered here
use: 'typescript'also enabled, the plugin is still about 3 s slower than ESLint plusfork-ts-checker-webpack-pluginon the project above. Its peak heap in webpack's process is also higher, about 1,160 MiB vs 460 MiB, though fork-ts-checker's own process isn't counted in that number.threads: false(16 min) as without it (8 min). I haven't looked into that.Generated by Claude Code
Summary by CodeRabbit