feat(linter): bulk suppression#19328
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
17607b3 to
506bd73
Compare
e3d0507 to
e9640be
Compare
|
Amazing work! Going to look at this on monday (or tomorow if i have time) |
f989ce1 to
110e14f
Compare
camc314
left a comment
There was a problem hiding this comment.
We should also add tests for the following scenarios:
- supressions with cutom plugins
- supression with an invalid (malformed) supression file
- combining
--fixwith--suppress--allto make sure that we end up with the correct supression count - Not sure how we can test this, but have you considered/thought about how we're going to handle paths on different machines? e.g. windows/Mac path separator differences
Correct me If I'm wrong, but I think this test case is covered here https://github.com/oxc-project/oxc/pull/19328/changes#diff-d6f62b74c4fcbe284a4c8b21f90cdf6200f2d41ecde269fcfeefd0a4d3c71174
Noted it.
My idea was to normalize the path as POSIX format, and reading the code I think I missed it this part. We don't need a full solution to handle the differences between Windows and Linux and Mac, our routes are relatives to
This afternoon I will have a computer with Windows to be able to manually test it. If you see it's good enough @camc314, I can convert the PR in draft. Once I have all the changes and new tests, I will mark it as ready and ping you? Also this week I'm a bit busy at work, and in the weekend I'm out. |
|
I will set the PR to draft.
This test is failing because I'm filtering the messages with fix, no matter if the fix option is turned on. Probably I will add test for
I will install a VM, because the manual test is taking forever in the computer they have lend me.
I have applied all PR comments. Once the previous points are solved, I will refactor the code again, mostly to improve readibility. |
d155b8b to
4dfc17f
Compare
Two targeted changes to the bulk-suppression machinery introduced in oxc-project#19328 (oxlint-suppressions.json support). **Change 1 — Eliminate the startup clone in `concurrent_map()`** `SuppressionManager::concurrent_map()` previously constructed a `papaya::HashMap` by iterating the suppression file and cloning every key and value into it. Workers only ever *read* one entry per file, so the copy was pure overhead: ~10–15 ms sequential startup cost for 10k files / 20k suppressions. Fix: wrap `AllSuppressionsMap` in `Arc` at load time. `concurrent_map()` now calls `Arc::clone()` (a reference-count bump), making the hand-off to rayon workers zero-copy. Side effect: the `papaya` dependency can be removed from the worker path in `service/runtime.rs`; `papaya::HashMap::pin()` is replaced by a plain `FxHashMap::get`. The `&SuppressionFile::new(…)` dangling- reference pattern is also fixed while touching that code. **Change 2 — Reduce per-message `RuleName` allocations** `build_suppression_map` called `message.into()` twice per message (once for `get_mut`, once for `insert`). Replaced with `.entry().or_insert()` so the key is computed once. The filter closure in `suppress_lint_diagnostics` also called `message.into()` twice per message (two separate `.get()` calls). Replaced with `let key = RuleName::from(message)` so both lookups share one allocation. Net result: 1–2 heap allocations per message instead of 3–4. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
d3730ac to
1488445
Compare
I didn't think on this before, also will help to have more easy git diffs if the file is update, and only show the keys that have been deleted or added.
My two cents, if the runtime doesn't match the saved, it should fail to force the user to update the file, or handle the lint error before move forward. It will help better to track the history of updates of the file. For example, you have a open PR, and the merge police is rebase always. Failing the lint, will force to update the file before merge it. Thank you for the review, and I'm happy what I did work as a base. |
…ccess when using --suppress-all
Agreed: I've made it so that unpruned suppressions will return with a failure exit code, but running with
Sorry it ended up being a lot more code pushed than I expected! Having a structure to edit and tests to run while refactoring has been really helpful, and you already completed most of the work on supporting I'm still working away at this, most of my changes at this point are based on things I've identified that we diverge from ESLint.
I would say the major changes are probably mostly done. At this point the plan (which could change) is to do some final fixes, one more review and then hopefully merge. |
|
Great work to everyone who's involved, I can't wait to release this. |
|
@camc314 This is ready to review! I would say this is like 90% compatible with the ESLint functionality (ignoring functionality not yet added like more CLI options). The main differences are differences in exit codes (ESLint uses exit code 2 for unpruned suppressions, we just use exit code 1), or the rules names stored in the file, e.g. Otherwise though, this should be ready to test in some real projects I think and get feedback from the community. Things that are on my mind to work on for this next that I don't think we need to handle in this PR:
|
Co-authored-by: Copilot <copilot@github.com>
|
I think it's time on this one. 😄 |
caused by #19328 just merging this to get CI green. but the test should be fixed as well
# Oxlint ### 💥 BREAKING CHANGES - 00ce512 oxlint/lsp: [**BREAKING**] Don't fix suggestions on fixAll code actions & command (#22195) (Sysix) ### 🚀 Features - 0eeceaf linter/no-unused-vars: Rename parameter with initializer (#22308) (camc314) - fa0232b linter/no-unused-vars: Add param rename suggestion (#22285) (Ryota Misumi) - ae59305 linter/promise/no-promise-in-callback: Add `exemptDeclarations` option (#22275) (Mikhail Baev) - 60bed4a linter: Extends `no-redundant-roles` and `prefer-tag-over-role` support roles (#22069) (mehm8128) - 545c80f linter/eslint: Implement `prefer-regex-literals` rule (#22192) (Mikhail Baev) - cf86d7a linter: Bulk suppression (#19328) (Said Atrahouch) - 23abd22 linter/jsx-a11y: Implement no-noninteractive-element-to-interactive-role (#21264) (Pedro Tainha) - fbb8f22 linter: Support `ignores` in overrides (#22148) (camc314) - 5a4414d oxlint/lsp: Support `rulesCustomization` lsp option (#21858) (Sysix) ### 🐛 Bug Fixes - 610f4c7 linter/no-unused-vars: Avoid renaming captured vars (#22310) (camc314) - 6b50f23 oxlint/cli: Load root config by searching up parent directories (#22272) (Sysix) - 31a5de7 linter: Rename override `ignores` to `excludeFiles` (#22283) (camc314) - 26d5d7b linter: Add missing vitest/valid-describe-callback functionality (#22279) (camchenry) - 784530f linter: `valid-title`: detect `String.raw` strings (#22271) (Sysix) - 080d90e linter: Move `no-debugger` fix to suggestion (#22256) (Sysix) - 25b7017 linter: Undocument override `ignores` option (#22213) (camc314) - 7bb00dd linter: Fix role-has-required-aria-props (#22097) (mehm8128) - d25279e linter/disable-directives: Improve parsing of names, descriptions (#22184) (camc314) - a59e447 linter/disable-directives: Ignore invalid enable suffixes (#22179) (camc314) - aafef0f ci: Disable bulk supression test on big endian (#22175) (camc314) - 281daec linter/vue/define-props-destructuring: Add `only-when-assigned` config opt (#22142) (camc314) - 46ab679 linter/plugins: Trim leading newline for partial sources (#20928) (bab) - 29ff6d9 linter: Update docs for no_alias_methods rule to be Vitest-specific and add toThrowError alias (#22129) (camchenry) ### ⚡ Performance - 9414bee linter/role-has-required-aria-props: Avoid intermediate vec (#22212) (camc314) - 3883ea3 linter/no-useless-escape: Drop unnecessary Vec collect (#22171) (connorshea) - 42c3029 linter/check-property-names: Replace split-collect-pop-join with rfind (#22172) (connorshea) - 9551d53 linter: Remove unnecessary Vec collect in CFG edge traversal (#22167) (connorshea) - 26fa2fc linter/aria-role: Remove unnecessary string allocations in run method (#22168) (connorshea) - c9ce045 linter/getter-return: Remove unnecessary Vec collect in CFG edge traversal (#22166) (connorshea) - 72bd846 linter/no-this-in-sfc: Reorder cheap name check, avoid String allocation (#22164) (connorshea) ### 📚 Documentation - 4da212a linter/no-unused-vars: Add docs to `rename_unused_function_parameter` (#22311) (camc314) - 27c4628 linter/forbid-dom-props: Escape jsx examples in lint rule docs (#22254) (4MBL) - 3f81147 linter: Improve the `react/jsx-key` rule docs. (#22162) (connorshea) - 07f03cc linter/consistent-return: Add note about `noImplicitReturns` coverage (#22156) (camc314) - 7c1e049 oxlint/lsp: Improve autogenerated lsp docs (#22154) (Sysix) - 87b3e38 linter: Update docs to be vitest-specific for consistent-test-it (#22128) (camchenry) # Oxfmt ### 💥 BREAKING CHANGES - 5c6c390 oxfmt: [**BREAKING**] Respect more git ignore options, align with Oxlint (#22210) (leaysgur) ### 🚀 Features - 6e8e818 oxfmt: Experimental .svelte support (#21700) (leaysgur) ### 🐛 Bug Fixes - e2a20b6 formatter: Add space after commas in import attributes (#22274) (Leonabcd123) ### ⚡ Performance - b756682 oxfmt: Optimize nested config prescan (#22232) (Jovi De Croock) - f14e81e formatter/sort_imports: Skip sort for single import runs (#22204) (leaysgur) - 32255b1 formatter: Process `ImportDeclaration`s in a run (#22079) (overlookmotel) ### 📚 Documentation - 4da6f4c formatter: Correct comment (#22217) (overlookmotel) - ef3507d formatter/sort_imports: Refresh docs (#22203) (leaysgur) Co-authored-by: Cameron <cameron.clark@hey.com>
AI Disclosure
I have used AI to:
Apart from these situations, the code has been written without AI assistance. As mentioned in my first PR in Oxlint, these contributions help improve Oxlint while at the same time helping me improve my Rust skills.
Description
This PR implements
bulk-suppressionsfor Oxlint. I had in mind the goals mentioned in #10549 (comment)Implementation details
It is split into sections with all the decisions that I have taken to implement this PR.
Suppression Manager
The code is located in
crates/oxc_linter/src/suppression/mod.rsand it is split into three files:tracking.rs: Contains all the code that handles the loading, saving, and updating ofoxlint-suppressions.json.mod.rs: Implements theSuppressionManager. It also acts as a middleman betweenoxlintandtsgolintandtrackingto update the information, anddiffto execute the diff operations.diff.rs: Hides all the complexity of how the diff works from the rest of the project.Filtering Diagnostics
The Suppression Manager only filters diagnostics, that have a valid
OxcCode, otherwise the message is ignored as we can't scope it to a rule or plugin.The messages are only filtered if the new count is lower or equal than the registered one; otherwise, they are displayed. Same behavior as ESLint.
Sharing and updating the data
Oxlint uses
rayon::scopeto create as many workers as there are available threads. Those workers do the heavy linting work, and this is the data that I need for filtering or creating the file.The semantics of
Send + Syncin Rust make it hard, for good reason, to share information between threads. So the quick approach is for each worker to read the whole file just to pick a subset of the data. On top of that, the feature is probably more oriented toward medium/large projects, so this approach doesn't scale.So the implemented approach is the following:
SuppressionManagerduplicates the data into aUpdate: The data is wrapped in aconcurrent hash map. This map is safe to be shared between threads and is only used in read mode.Arcinstead of duplicating it. It reduces a level of indirection. Thanks @wagenet for the suggestion.DiffManager, this struct contains all data necessary for his operations.DiffManagercalculate a diff based on the args provided and ifbulk-suppressionsfile exists:OxcDiagnosticto be reported.Once the linting is completed and we are back in the main thread, we consume the channel with the diffs sent by the workers. If we are in
updateorcreatemode and no diffs have been emitted, then we do nothing. Otherwise, we mutate the map in theSuppressionManagerwith the operations sent by the workers and save it.In a few words, I'm trading memory usage for what is, in my opinion, a simpler process. The option that I discarded was relying on
interior mutability, but that would mean ensuring that all operations are safe and that no worker panics.TSGo lint errors are the only recorded errors.
Rather than completely changing how fixable and non-fixable errors are emitted, I have attached to the existing flow. Type errors and related errors are ignored and not recorded.
CLI Args
Currently, both commands update the file, no matter the diff found. The only situation that doesn't work the same is when the file
oxlint-suppressions.jsondoes not exist. The only way to create it is by runningoxlint --suppress-all.Comments
suppressionfolder.UPDATE: The fix commands are handle.--fixor related commands do not update the file.SuppressionTestthat implementsDropto handle the cleanup of this tests.PostHog

Oxlint React Compiler Rules

Fixes