Repository navigation
perf(ban-dependencies): look up import paths instead of scanning all mappings - #156
Conversation
|
@webpro wdyt? This had a big win on our downstream repo. Happy to iterate if you have feedback. |
|
Thanks, Tony! I can confirm it runs faster and without issues (against two real-world consumers). No findings in the implementation either. |
|
just working through my pile of notifications. ill get to this soon 👍 |
Replace the Set pre-check plus ordered scan with a Map from module name to its mapping and manifest position. Walk the import's parent paths once and keep the earliest-listed match, preserving existing precedence.
| let candidate = source; | ||
| while (true) { | ||
| const entry = manifest.mappings.get(candidate); | ||
| if (entry && (!match || entry.index < match.index)) { |
There was a problem hiding this comment.
this still doesn't seem quite right 🙈
- im not sure we care what the "index" is, this doesn't seem to gain us anything
- if
foo/barcomes beforefoo/bar/bazin the manifest, this loop continues until it reaches thefoo/barone rather than stopping
at this point im wondering if this is actually just Object.entries(mappings). i.e. this loop just iterates the entries (for...of) and breaks when it finds a match, slices on / if it doesn't.
remembering this PR is two things in one:
- ability to have nested mappings
- some performance gain
for 2, do we still have such a gain now that we're just turning an object into a map or array?
There was a problem hiding this comment.
Apologies for the back and forth on this and thanks for the patient reviews.
My intent w/ this PR is only the speedup since it's quite significant in our repo (as you can see). I think I got a little carried away w/ the changes before.
Here's a smaller diff that's as close to the original as I can keep it and still get the full benefit. Please take a look at the rewritten PR description for full explaination.
…ings Replace the per-import scan of every manifest entry with direct lookups of the import path and its parent paths in the existing mappings object.
Summary
This is a performance-only change to
ban-dependencies. It does not add features or change which imports are flagged.On
main, each import is compared against every mapping in every manifest withObject.entries(...)plusstartsWith. Most imports (react,./foo, and so on) match nothing, so each one pays for about 876 preset comparisons before the rule gives up. This runs for every import in every linted file.This PR replaces that scan with direct key lookups. For an import such as
foo/bar/baz,findMappingchecksfoo/bar/baz, thenfoo/bar, thenfooin the existingmappingsobject. That is 1–4 lookups per import, no matter how large the manifests are.Reviewer guide
findMappingis the only new logic. Itsforloop ends becauseendonly decreases.Object.hasOwnprevents inherited object properties such asconstructorfrom matching.mainalready matches subpaths throughstartsWith(`${moduleName}/`).modulesoverlap (for example,fooandfoo/bar),foo/bar/baznow reports the more specific name,foo/bar. It is still flagged either way. The built-in presets have no overlapping keys, so they are not affected.New tests
The new test cases add coverage. They do not test new features.
mainand this PR: unknown subpaths, allowed package subpaths, built-in package subpaths, and overlapping custommoduleslisted as['oogabooga/subpath', 'oogabooga'].moduleslisted as['oogabooga', 'oogabooga/subpath']. Onmain, it reportsoogabooga. On this PR, it reportsoogabooga/subpath.Benchmark
Oxlint 1.83.0 with 12 threads on a 17,654-file TypeScript workspace, with only
e18e/ban-dependenciesenabled. Median of 5 interleaved runs:mainAll runs reported the same diagnostics.
Validation
npm run build,npm run lint, andnpm test(1,011 tests) pass.ban-dependencies.tsis not lower: branches stay at 85.71% (36/42), and statements go from 96.49% to 96.82%.mainand this PR report the same 4 diagnostics at the same locations, with the same messages.— sent via Glean Tau