## Summary
- align the skipComments default with ESLint
- report irregular whitespace in line and block comments by default
- preserve the skipComments: true opt-out
- verify suppressed comment diagnostics count disable directives as used
- regenerate the published configuration schema
Fixes#25616.
`test_suppressing_errors_update_the_file_when_errors_are_decreased` and
`test_prunning_errors_update_the_file_when_errors_are_decreased` both
point at `fixtures/suppression/with_arg_and_decreased_errors`. Every
other `SuppressionTester` fixture has a single owner. Both of these run
oxlint in that directory, which rewrites `oxlint-suppressions.json`, and
both then delete it and restore it from the backup in `Drop`.
It showed up as an intermittent red in `just ready` while I was working
on something unrelated, and it cost enough re-runs that I went looking.
I flagged it at the end of #25412. This is that fix.
Measured with `cargo test -p oxlint suppress`, ten runs per setting, on
an 18 core machine:
```
--test-threads failures
1 0/10
12 0/10
16 0/10
17 0/10
18 3/10
20 0/10
24 0/10
```
18 is `hw.ncpu` there, and it is what `cargo test` picks by default. 20
and 24 are more parallel and never failed, so this is not load and it is
not machine speed. The two tests have to land in the same dispatch wave,
which depends on the divisor. The `Test Linux` job runs `cargo test
--all-features` and stays green, which I read as a different core count
rather than anything about the runner.
Three distinct failure modes came out of the one shared directory:
```
tester.rs:249 one test's opening assert runs while the other sits
between remove_file and fs::copy
tester.rs:295 both Drop calls delete the same path, and the second gets ENOENT
tester.rs:274 one test reads the file while the other is inside a truncating write
```
Before settling on the fixture split I checked whether repairing `Drop`
would be enough, since deleting before copying is what opens the widest
window. It is not enough. Guarding the delete with `&&
!self.have_backup_file`, so the file is never absent, left the rate
where it was and only changed the message:
```
Drop shape directory failures
delete then copy, as today shared 3/20
copy only shared 4/40
unchanged separate 0/40
```
To find out what the reader was actually getting in that third mode, I
patched the panic at `tester.rs:274` to print the observed content. That
patch was throwaway and is not in this diff:
```
PROBE observed len=0 serde=EOF while parsing a value at line 1 column 0 first80=""
```
Length zero, not a torn file. `SuppressionFile::save` uses `fs::write`,
which is `File::create` plus `write_all`, and `File::create` opens with
`O_TRUNC`. The truncate commits at open time and the bytes arrive on the
next syscall, so the file is briefly empty. A separate directory is the
only change that closes both that window and the `Drop` one.
Calling out one thing up front: the new directory is a byte for byte
copy of the old one. `--suppress-all` and `--prune-suppressions`
converge in the decreased case, so the expected content is the same for
both tests. The directory exists for isolation, not for content.
`with_arg_and_increased_errors` and
`with_arg_and_increased_errors_prune` are the same split for the case
where the two commands diverge, so five of six files match there and six
of six match here.
This is the same shape as #20717, where `--init` writes leaked across
parallel tests.
With the change in, the suppression module is 0/20 at 18 threads and
0/10 at the default. The rest of `just ready` is green as well: `typos`,
`cargo lintgen`, `just fmt`, `just check`, `just test`, `just lint`,
`just doc` and `just ast`, with 4446 tests passing under
`--test-threads=1`.
Three other suppression fixtures have more than one owner, and I left
all three alone. `fixed_violations_are_reported` has two readers.
`type_check_only_with_regular_rule` also has two, and although the
second passes `--suppress-all` and `--prune-suppressions`, `lint.rs:426`
rejects that combination before any lint runs, so nothing writes.
`diagnostics_filtered_if_count_is_the_same` is the only other one where
a write really happens, so it carries the truncating write window in
principle, but that window is a single `fs::write` over a few hundred
bytes, where the one this PR fixes spanned a whole `Drop`
delete-then-copy plus the other test's entire run. Forty runs with those
two tests forced to overlap found nothing. I did not want to change
something I cannot show is broken. Happy to fold it in if the whole
class should close in one go.
Disclosure, per CONTRIBUTING.md: I used Claude for the investigation and
for the patch. I have reviewed and tested all of it myself.
Fixes#24612
- Update `fast-glob` to `1.1.0` which exposes `validate()` api
- Use it in `oxc_config`, apps will be validated through `deserialize`
- Oxfmt supports globs for CLI paths, also validated
## Summary
- skip bulk suppression finalization in `--type-check-only` mode
- reject `--suppress-all` and `--prune-suppressions` when combined with
`--type-check-only`
- add a CLI regression fixture for a still-valid regular lint
suppression
## Root cause
`--type-check-only` intentionally skips regular lint rules, but bulk
suppression finalization still compared the full baseline against the
partial runtime rule map. A file processed by type checking was marked
as seen without regular rule counts, so valid regular suppressions were
interpreted as stale.
Suppression update operations are also unsafe with this partial result
because they could remove or replace entries for rules that did not run.
## Validation
- `just ready` with Node.js 24.14.0
- verified the original Dify single-file reproduction with both regular
lint and `--type-check-only`
## AI usage
I used OpenAI Codex to help investigate, implement, test, and prepare
this pull request. I reviewed the changes and take responsibility for
the submission.
Fixes#24460
Co-authored-by: Cameron <cameron.clark@hey.com>
## Summary
- remove the platform-dependent CLI snapshot test for parent-directory ignore patterns
- remove the dedicated CLI fixtures and snapshot
- rely on the existing `oxc_config` unit tests for pattern semantics and the existing `ConfigStoreBuilder` test for Oxlint integration
Fixes#24310
Root config's `ignorePatterns` are now resolved relative to the directory containing the config file, instead of CWD.
Also fixes a latent issue: a `--config <path>` containing `..` silently disabled all `ignorePatterns` (the unnormalized parent dir never prefix-matches lint target paths). Explicit config paths are now lexically normalized, same as Oxfmt.
### Behavior change
The new anchoring applies in all cases, including `--config` / LSP `configPath`. This intentionally differs from ESLint, which resolves alternate-config ignore patterns relative to CWD:
- `extends`, `overrides[].files`, and JS plugin specifiers are already config-file-relative, including via `--config`
- rooting only `ignorePatterns` at CWD would give one file two coordinate systems
- Wrappers like Vite+ pass `--config <workspace-root config>` as an implementation detail while CWD stays at the invocation directory, so "`--config` means CWD-relative" does not hold in practice
- #24276
Impact should be small: bare names (`dist`, `*.min.js`) and `**/`-prefixed patterns are unaffected by the anchor.
The updated `config_ignore_patterns/with_oxlintrc` snapshot shows the `--config` case.
Fixes#22758.
The LSP path was not materializing the default plugin set before resolving a root config with `extends`, while the CLI path already did that before building the config. That meant a child config launched as the LSP workspace root could extend a parent config with an explicit `plugins` array and accidentally lose default plugins like `typescript`, even though `oxlint` CLI and `--print-config` showed those plugins as active.
This moves the default-plugin materialization into a shared helper used by both CLI and LSP config loading, so both paths resolve the same effective root config before `extends` are applied.
I added a regression fixture for the reported nested config shape and snapshot coverage for the missing TypeScript diagnostic.
Fixes#22713
This updates the LSP handling for `unusedDisableDirectives: "allow"` so it disables unused directive reporting instead of producing warning diagnostics and remove-directive code actions.
The behavior now matches the CLI path: only `warn` and `deny` enable unused-disable reporting, while `allow`/`off` are treated as disabled.
Added an LSP snapshot regression test covering the `"allow"` case.
`console.log` and `debugger;` are utils while developing.
`console.log` is already a suggestion (aligned with ESLint), but `no-debugger` would be autofixed too.
ESLint does not have a fixer for his rule, but we do.
This is a part of #22195, where do not autofix suggestions on the editor side, aligning with ESLint.
`debugger;` should not be autofixed on save.
# AI Disclosure
I have used AI to:
* Fix all my grammar mistakes in this PR description, as English isn't
my first language.
* Iterate on some ideas that I had during development, for example
whether it is better to modify the concurrent hash map or send diffs
through a channel.
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-suppressions` for Oxlint. I had in mind the
goals mentioned in
[https://github.com/oxc-project/oxc/issues/10549#issuecomment-3102429390](https://github.com/oxc-project/oxc/issues/10549#issuecomment-3102429390)
# 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.rs` and it
is split into three files:
* `tracking.rs`: Contains all the code that handles the loading, saving,
and updating of `oxlint-suppressions.json`.
* `mod.rs`: Implements the `SuppressionManager`. It also acts as a
middleman between `oxlint` and `tsgolint` and `tracking` to update the
information, and `diff` to 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::scope` to 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 + Sync` in 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:
* `SuppressionManager` ~~duplicates the data into a `concurrent hash
map`. This map is safe to be shared between threads and is only used in
read mode.~~ **Update:** The data is wrapped in a `Arc` instead of
duplicating it. It reduces a level of indirection. Thanks @wagenet for
the suggestion.
* Each worker holds a reference to a `DiffManager`, this struct contains
all data necessary for his operations.
* The file is linted. The errors emitted by the lint are used to track
the number of detected errors.
* `DiffManager` calculate a diff based on the args provided and if
`bulk-suppressions` file exists:
* If we have diffs and no CLI args are provided, then the diffs are
converted to `OxcDiagnostic` to be reported.
* If we have diffs and at least one of the CLI args is provided, then
the diffs are sent to the parent through a channel.
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
`update` or `create` mode and no diffs have been emitted, then we do
nothing. Otherwise, we mutate the map in the `SuppressionManager` with
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.json` does not exist. The only way to create it is
by running `oxlint --suppress-all`.
# Comments
* ~130 files from this PR are file in the fixtures folders.
* The current state of the PR handle lint errors emited by Oxlint,
JSPlugin and TSGO.
* All suppression test fixtures are self contained in the `suppression`
folder.
* Only the Default Output is rendered if the file has been created or
updated.
* ~~`--fix` or related commands do not update the file.~~ **UPDATE**:
The fix commands are handle.
* I have manually tested the feature in
[PostHog](https://github.com/PostHog/posthog) and in
https://github.com/TheAlexLichter/oxlint-react-compiler-rules
* Added a `SuppressionTest` that implements `Drop` to handle the cleanup
of this tests.
* Thanks @Sysix for you manually test in Windows, and mentioned how to
run the test in Windows using the CI.
* Windows CI are green
https://github.com/oxc-project/oxc/actions/runs/22840798852.
PostHog
<img width="790" height="155" alt="imagen"
src="https://github.com/user-attachments/assets/fe16b2a6-e98a-492f-a9c5-dd92d57c2091"
/>
Oxlint React Compiler Rules
<img width="1297" height="757" alt="imagen"
src="https://github.com/user-attachments/assets/fc9ac9b1-bb08-4ff8-825b-ca91d107f4dc"
/>
# Fixes
*
[https://github.com/oxc-project/oxc/issues/10549](https://github.com/oxc-project/oxc/issues/10549)
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cam McHenry <camchenry@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
Co-authored-by: Copilot <copilot@github.com>
> ## Pull request overview
> Updates the oxlint LSP “fix all” / “fix all dangerous” code actions to respect `rulesCustomization.<rule>.autofix = false`, so rules opted out of autofix aren’t included in fix-all edits.
> ## Pull request overview
> This PR updates the oxlint LSP pipeline so that per-rule `rulesCustomization.severity` can override the severity of emitted LSP diagnostics (and suppress diagnostics entirely when set to `off`),
>## Pull request overview
> Splits the previously shared `no-negated-condition` implementation into distinct `eslint/no-negated-condition` and `unicorn/no-negated-condition` rules while reusing a shared implementation module, and removes the now-unneeded unicorn↔eslint aliasing in config and rule-status tooling.
Question: Should we support `unicorn/no-named-default`? Because it is marked as not supported because it is already implemented in `import/no-named-default`.
See unicorn issue: https://github.com/oxc-project/oxc/issues/684
## Summary
- parse Svelte <script> attributes instead of broad substring checks
- detect both module and context="module" scripts when creating partial
JS sources
- add unit and CLI coverage for module/instance scripts in both JS and
TS
## Validation
- mise x -- cargo test -p oxlint lint_svelte_
- mise x -- cargo test -p oxc_linter partial_loader::svelte
## AI usage disclosure
This PR was prepared with AI assistance (Codex). I reviewed and take
responsibility for the final changes.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
## Summary
- add a root-only `options.respectEslintDisableDirectives` config option
- make directive parsing and `reportUnusedDisableDirectives` honor that
switch
- preserve current behavior by default with `true`
- regenerate the schema / generated TS config types and add CLI + unit
coverage
## Proposal
Use a boolean option to control whether oxlint recognizes ESLint-style
disable directives:
```json
{
"options": {
"respectEslintDisableDirectives": false
}
}
```
Behavior:
- unset or `true`: oxlint recognizes both `oxlint-*` and `eslint-*`
- `false`: oxlint only recognizes `oxlint-*`
This also applies to `reportUnusedDisableDirectives`.
## Notes
- unused-directive diagnostics now report the actual matched prefix
(`oxlint-disable` vs `eslint-disable`) instead of always saying
`eslint-disable`
- the option is root-only, consistent with the other global `options.*`
switches in the config loader
closes https://github.com/oxc-project/oxc/pull/20447
fixes https://github.com/oxc-project/oxc/issues/20375
---------
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
Add the TypeScript rule config surface for the new `allow` specifier option and extend the oxlint CLI fixture to verify the option end to end.
Tested with:
- `cargo test -p oxc_linter prefer_promise_reject_errors -- --nocapture`
- `cargo test -p oxlint test_tsgolint_rule_options -- --nocapture`
- `node --run fmt`
AI usage disclosure: Codex was used to implement the config support, add the CLI fixture coverage, and prepare this PR. The changes were reviewed in the local workspace before submission.
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionTriggerKind
> /**
* Code actions were explicitly requested by the user or by an extension.
*/
export const Invoked: 1 = 1;
/**
* Code actions were requested automatically.
*
* This typically happens when current selection in a file changes, but can
* also be triggered when file content changes.
*/
export const Automatic: 2 = 2;
---
> ## Pull request overview
> This PR optimizes the oxlint LSP server by avoiding unnecessary linting when code action requests are triggered automatically (e.g., on cursor move or file open) rather than explicitly invoked by the user. Previously, `get_code_actions_for_uri` would always run the linter if no cached actions existed; now it only does so when `trigger_kind` is `INVOKED`.
@nrayburn-tech When your code action from file explorer does not work anymore, you can blame this PR 😅
VSCode sends "automatic" for most of the time, only when opening the code action pop over it will send "invoked". At this time we already should have diagnostics because. Most triggers of code actions are diagnostics.
See #15972.
## Summary
- Adds `options.reportUnusedDisableDirectives` to `.oxlintrc.json`,
following the same pattern as `typeAware` (#19614) and `typeCheck`
(#19764)
- Root-config-only — nested configs are rejected with a clear diagnostic
error
- CLI `--report-unused-disable-directives` /
`--report-unused-disable-directives-severity` take precedence over the
config file value when both are set
- Schema (`npm/oxlint/configuration_schema.json`), TypeScript types
(`config.generated.ts`), and website snapshots regenerated
## Test plan
- [ ] Unit tests in `oxlintrc.rs`: deserialization, `is_empty`, `merge`
- [ ] Unit tests in `config_store.rs`: accessor returns value / `None`
by default
- [ ] Unit tests in `config_loader.rs`: nested config rejection
- [ ] Integration tests in `config_builder.rs`: `extends` inheritance
and override precedence
- [ ] `cargo test -p oxc_linter && cargo test -p oxlint`
## Note on AI usage
This PR was implemented with Claude Code assistance and has been
reviewed and tested by the contributor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wagenet <9835+wagenet@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Add snapshot tests for all the formatters with this case to make sure we don't regress on this for any of them in the future.
Basically, we were being overly aggressive in the stripping of the rule ID from the message, which caused messages with colons in them (e.g. parser errors like `Expected `;` but found `:``) to be truncated to just the character after the first colon.
We don't actually need to do this anymore, as the `diagnostic.to_string()` method doesn't include the rule ID (if it ever did), so we can just use the full string as the message without any additional parsing.
Generated with Claude Code.
Fixes https://github.com/oxc-project/oxc/issues/19588.