The `Option<>` wrap for `Vec<LinterCodeAction>` is not needed, after each lint-run we save an entry (even if empty) to the map.
There is no different code branch for `None` as a value.
## Summary
- update workspace test dependencies to Vitest 5.0.0
- pin Vitest and related tooling to Vite 8.2.2
- migrate removed sequential and benchmark APIs
- update the pnpm lockfile
## Summary
- keep UTF-8 translation offsets absolute when the converter is built
from source with a trimmed BOM or partial-source prefix
- extend the Oxlint BOM fixture with the reported comment-before-Unicode
case
- assert the JS plugin API returns the complete comment range and source
slice
Without the fix, the integration snapshot reports an end offset of 4 and
slices `"/*x*"`. With the fix, it reports an end offset of 5 and slices
`"/*x*/"`.
Fixes#26162
*Update*: #26173 fixed the same bug in the same way, and has been
merged. This PR adds more tests, so merging it on top.
Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
Aligned the handling of line comments immediately following an opening `{`.
(For GraphQL only,) previously kept their own line, now matches other languages by inserting a line break and treating them as leading comments for the next element.
This brings it in line with Prettier's style. The only drawback is that the behavior of `-line` style comments. However, since most of these can be replaced with `-next-line` style comments, we are going to standardize on this consistent behavior for now.
prettier/prettier#1267 (won't fix)
`Lexer::finalize_tokens` discards the last collected token - the `Eof` that terminates every token stream - and debug asserted that it was `Eof`.
Turns out that assumption does not always hold. Where the lexer cannot lex any further it emits `Kind::Undetermined`, and no `Eof` ever follows, so the last token is `Undetermined` and the debug assertion fails. This can be triggered by an unterminated string, for example:
```js
foo();
'unterminated
```
Like `Eof`, `Undetermined` is not a "real" token, so either way it's fine to remove it from the token stream. The fix is just to relax the debug assertion to accept either.
Release behaviour is unchanged - it was only a debug assert. Only came across this while running the conformance tests which compare current lexer to the new one (`oxc_lexer`).
We had at the beginning some rules' diagnostics like `<rule-name>: diagnostic message`, from that message we did create a code action `Fix this <rule-name> problem`, but this is buggy.
I searched in `crates/oxc_linter` for `OxcDiagnostic::\w+\(".*:` and found only `no-invalid-regexp` to be a useful case.
For other rules this could be wrong like:
- Unexpected `javascript:` url
- `<Document />` from `next/document` should not be imported outside of `pages/_document.js`. See: https:/..`
- Always prefer `const x: T = { ... }`.
## Summary
- preserve plugin names when tracking default and enabled rules
- prevent same-named rules from inheriting another plugin’s state in Markdown and JSON output
- add regression coverage for all known rule-name collisions
Fixes#26209.
## Summary
- add the ESLint-compatible `checkConditionalExpressions` option to `no-unmodified-loop-condition`, defaulting to `false`
- port tests added by eslint/eslint#21175 and add regression coverage for repeated references within and across binary groups
Closes#26182.
# Oxlint
### 🚀 Features
- 60b945d linter/nextjs/no-typos: Implement suggestion (#26091) (Mikhail Baev)
### 🐛 Bug Fixes
- 33ac4b0 linter/lsp: Prevent tsgolint from holding onto processes (#25570) (Adrian Schaedle)
- baf4b1e linter/import/no-empty-named-blocks: Make empty value import removal a suggestion (#26155) (camc314)
- 77cfaec linter/eslint/object-shorthand: Preserve `__proto__` semantics (#26154) (camc314)
- fa3c082 linter/unicorn/prefer-set-size: Ignore shadowed `Set` constructors (#26153) (camc314)
- e412cf2 linter: Clamp invalid JS plugin locations (#26144) (camc314)
- d86c113 linter: Normalize reversed JS plugin locations (#26138) (camc314)
- 73c09b2 linter/eslint/no-use-before-define: Run on JS, JSX files (#26114) (camc314)
- 03ef0f2 linter/unicorn/no-useless-spread: Treat typed arrays as a distinct value hint (#26067) (Aadharsh Pannirselvam)
- bd15905 linter/react/no-unstable-nested-components: Check nested component object property names (#26101) (camc314)
- 3910e2b linter/eslint/no-unassigned-vars: Skip Svelte and Vue files (#26042) (Hamody We)
- 047f7ca linter/plugins: Fix interaction between JS plugins and Vue rules (#26080) (overlookmotel)
- 8531b9b linter/plugins: Alter method for obtaining mutable `Program` when sending AST to JS plugins (#26077) (overlookmotel)
- dc464ff linter/unicorn/prefer-math-min-max: Avoid unsafe autofix (#26060) (camc314)
### 📚 Documentation
- 464ddd1 linter: Support a shared short description for jest/vitest rules (#26186) (connorshea)
- 9db5ad3 linter: Add short description to `vue/no-dupe-keys` (#26183) (connorshea)
- db66f58 linter: Correct export/import mismatch in bar and foo example (#25927) (billychannnnnn)
- d5be037 linter/typescript/switch-exhaustiveness-check: Clarify default case comment pattern (#26100) (camc314)
# Oxfmt
### 🚀 Features
- 1fb37b6 formatter/css: Format declaration-shaped raw-prelude rules (postcss nested config blocks) (#26194) (leaysgur)
### 🐛 Bug Fixes
- 57e8e22 formatter_css: Space a folded sign after a call in Less operations (#26134) (leaysgur)
- e4298fb formatter/jsdoc: Follow CommonMark for interrupting lists and guard wrapping from creating them (#26098) (leaysgur)
- a1e21c2 formatter: Apply head body policy everywhere (#26074) (leaysgur)
- 676b7e1 formatter: Keep comments in their for-head slot, before an empty-statement body and do-while (#26073) (leaysgur)
- 3bbcea6 formatter: Keep comments between a head and its body out of the braces across all constructs (#26071) (leaysgur)
- 57537a2 formatter: Place comments between a head and open paren (#26058) (leaysgur)
- e2d53e7 formatter: Place comments between a statement head and its body (#26043) (leaysgur)
- 662556c formatter: Print the idempotent placement for comments in dropped parens (#26041) (leaysgur)
- faf11d9 formatter_yaml: Keep trailing whitespace in block scalars (#26072) (leaysgur)
### ⚡ Performance
- ac4785a diagnostics: Use fixed ANSI styles (#26130) (Boshen)
### 📚 Documentation
- b879608 formatter,formatter_graphql,formatter_yaml: Annotate own-line comment inlining as known policy violation (#26131) (leaysgur)
- 181953b oxfmt,formatter_core,formatter,formatter_yaml,formatter_css,formatter_graphql: Extract `DIVERGENCES.md` out from `AGENTS.md` (#26121) (leaysgur)
- a67cb9d formatter,formatter_core: Document comment moving policy (#26075) (leaysgur)
- fc175b0 formatter_core: Clarify idempotency test infra (#26069) (leaysgur)
**Before:**
Converting `Fix` to `FixContent` did convert `Cow` into `String` and stored into the `ServerLinter.code_action` Map, to get requested later by the client, where we convert `FixContent` to `CodeAction` (where we need the `String` format)
**After:**
Consume directly `Fix` `Cow` fields, and delay to conversion to `String` until the `CodeAction` is requested to be converted.
So only downside I see, is that `textDocument/codeAction` is requested more times than a `textDocument/diagnostic` request. So the conversion does happen more times, but get released quickly.
Fixes#26165
- `side_effect` is predefined selector, it cannot be used as custom group name
- respect custom group which uses `side_effect` as side-effect-import correctly
- fix metadata detection bug
Follow-up to #26138, closes#25867.
External plugins can report a node range past the source text. The default formatter rejects that invalid span and previously panicked. Clamp external diagnostic offsets to the source's UTF-16 length before normalizing reversed ranges.
The end-to-end fixture now covers:
- reversed locations
- negative loc values
- negative node ranges
- an out-of-range node range in Unicode source text
- Extract "known divergences" section from `AGENTS.md` to explicit `DIVERGENCES.md` with code example
- Update all anchors
- Add missing pinned fixtures
Replace dynamic diagnostic styles with fixed ANSI prefixes, reducing `oxfmt` and `oxlint` release text size.
AI assistance: Codex was used. The contributor remains responsible for this change.
## Summary
- Add an end-to-end regression fixture for unused disable directives in Vue files when a JavaScript plugin is enabled.
- Exercise the original #25892 source and snapshot the unused rule-name diagnostics instead of panicking.
## Testing
- Confirmed the same fixture aborts with the reported out-of-bounds panic on detached pre-fix commit 9d97da14c8.
Closes#25892Closes#25922
Originally fixed by #25280.
## Summary
- clarify that `defaultCaseCommentPattern` recognizes a trailing comment in place of an omitted `default` clause
- document the required location, absence of a live `default`, and the `considerDefaultExhaustiveForUnions` requirement for union switches
- regenerate the JSON schema and TypeScript configuration types
Fixes#26087
## Summary
Closes#26038.
`no-unassigned-vars` is a `correctness` rule, so this fires by default
with no config:
```svelte
<script lang="ts">
let dialog: HTMLDialogElement;
function openDialog() {
dialog.showModal();
}
</script>
<dialog bind:this={dialog}>Hello</dialog>
```
```text
repro.svelte:2:7: warning eslint(no-unassigned-vars): 'dialog' is always 'undefined' because it's never assigned.
```
## Why the rule cannot get this right today
The rule's decision is `has_read && no reference is_write()`, taken over
the symbol's references in the current script block. Oxlint reaches
`.svelte`/`.vue` through `PartialLoader`, which extracts the `<script>`
blocks and throws the markup away before parsing — so the template's
write is not in the AST the rule sees, and no reference on `dialog` is
ever a write. Every ingredient of the diagnostic is satisfied, and the
diagnostic is still wrong: Svelte assigns the element to `dialog` on
mount.
The same shape exists in Vue, where a `<script setup>` `let` is a
`setup-let` binding that `v-model="x"` and inline handlers such as
`@click="x = 1"` compile to direct assignments against.
This is not a matter of tightening the check — the write is in a part of
the file the linter never parses.
## The change
`prefer-const` hit exactly this and already skips both extensions
([#25148](https://github.com/oxc-project/oxc/pull/25148)):
```rust
fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
// ignore svelte/vue: their templates can reassign a binding, which we can't see.
!ctx.file_extension().is_some_and(|ext| ext == "svelte" || ext == "vue")
}
```
This applies the sibling rule's guard rather than inventing one, and
documents it in the rule docs under an `#### Ignored Files` heading,
matching `prefer-const`'s wording.
`.astro` is deliberately **not** included: an Astro template renders a
frontmatter binding but has no construct that writes back to it, so the
false positive does not arise there. `prefer-const` drew the same line.
## Scope, stated honestly
This trades a false positive for lost coverage: a genuinely unassigned
variable inside a `.svelte`/`.vue` `<script>` block is no longer
reported. That matches the precedent and the reporter's own request, and
a `correctness` false positive that fires on the documented Svelte idiom
costs more than the missed report. Restoring coverage needs
template-aware analysis, which is the language-support effort tracked
separately (#13017, #17991).
## Verification
- `cargo test -p oxc_linter --lib` — **1254 passed, 0 failed**.
- Two new tests, `test_svelte` and `test_vue`, cover the reported repro,
`bind:value`, a two-`<script>` Svelte component, `v-model`, and an
inline `@click` handler.
- **Revert-checked:** with `should_run` removed and the tests kept,
exactly those two tests fail and the rest pass. The failure output
reproduces the reported diagnostic verbatim, including the `let shared`
case from the two-script component — so the tests fail for the right
reason, not because the fixture path changed.
- `cargo clippy -p oxc_linter --lib --all-features` — no new findings
(the two `FromIterator::from_iter` warnings are pre-existing, in
`tsgolint.rs` and `lib.rs`).
- `cargo fmt --check` clean.
- The rule is not part of `docs_rule_pages.snap`, so the doc addition
needs no snapshot regeneration.
Built and tested on Windows with the `1.97.1-x86_64-pc-windows-gnu`
toolchain rather than the pinned `1.98.0`, and with `RUSTFLAGS`
overridden to drop the MSVC-only link args that `.cargo/config.toml`
applies to every `target_os = "windows"` target. Nothing in the
repository was changed for that.
---
Written with AI assistance (Claude Code), reviewed and tested by me
before submitting, per AGENTS.md.
---------
Signed-off-by: hamodywe <iosapk.org@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#26076 updated Oxlint JS plugin conformance snapshots, revealing that one ESLint Stylistic test was failing.
Turns out the cause was #25154 which (correctly) made it illegal for a `TSIndexSignature` to contain more than one parameter. The code in this test case is invalid. So just skip the test.
While working on #26077, Claude spotted another related bug.
JS plugins have to run last because they need to mutate the AST, and in the process they destroy the stored AST.
For files with multiple sections (Vue, Astro, Svelte), we ran native rules then JS rules for each section in turn. That appeared fine - JS rules do run last. But the problem is that some rules e.g. `vue/valid-define-emits` also access _earlier_ sections. Because JS plugins already ran on those previous sections, their ASTs have been discarded already, leading to incorrect results.
Instead, lint in 3 separate passes which each run on _all_ sections before going on to the next pass:
1. Native rules.
2. JS rules.
3. Unused directives.
i.e.:
- Before: section 1 native, section 1 JS, section 2 native, section 2 JS.
- After: section 1 native, section 2 native, section 1 JS, section 2 JS.
Unused directives check has to be in a separate final pass, as it requires both native rules and JS rules to have run before it.
Looping over sections is cheap compared to the work that happens in each turn of the loops, so I very much doubt this has a measurable impact on perf.
This change does have one visible effect: The order of diagnostics will change for multi-section files. Previously diagnostics would be output in section order, now they're in rule type (native/JS) order. I don't _think_ that matters.
Conformance snapshots had got out of sync. At some point 1 test for ESLint Stylistic's `no-comma` rule has got broken (unclear when or how).
This PR doesn't attempt to fix the bug, but just makes sure the snapshot matches reality.
`eslint(no-control-regex)` correctly flags this regex
```ts
const fnKeyRe = /^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
```
`'U+001B' is a control character.` and shows in help text, `help: Avoid
matching control characters in regular expressions. If intentional,
consider using a Unicode escape instead.`
but the help text is misleading, all forms of unicode escapes are also
flagged.
```ts
const a = /\x1b/;
const b = /\u001b/;
const c = /\u{1b}/u;
const d = /\u{001b}/u;
```
This behaviour is consistent with upstream eslint rule, just the help
text is misleading, so I have changed the help text to say to disable
this rule when its intentional
Serialize `FormalParameterRest` with its outer span so its range includes parameter decorators. Regenerate the raw-transfer deserializers and remove `sourceMapValidationDecorators.ts` from the ESTree mismatch baseline.
Fixes#26011.
> ## Pull request overview
>
> Refactors the language-server tool initialization/restart plumbing to support emitting multiple `ClientMessage`s (instead of a single optional message), so tools can surface more than one warning/error to the client during startup and config changes.
>
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [rust](https://rust-lang.org/)
([source](https://redirect.github.com/rust-lang/rust),
[changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md))
| toolchain | minor | `1.97.1` → `1.98.0` |
---
### Configuration
📅 **Schedule**: (in timezone Asia/Shanghai)
- Branch creation
- "before 10am on monday"
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
Add a shared non-generic JS-style token update entry point so `oxc_linter` and `oxlint` do not each instantiate the ESTree token visitor.
On `aarch64-apple-darwin`, `cargo build -p oxlint --release --features allocator` reduces the `oxlint` binary by 49,616 bytes (48.5 KiB, 0.39%), from 12,756,960 to 12,707,344 bytes.
Validated with `just ready`.
AI assistance was used for implementation and measurement.
Share a non-generic JSON deserialization entry point across `oxc_linter` and `oxlint`, avoiding duplicate monomorphizations. Also deserialize well-known settings without deep-cloning the raw JSON value.
Validated with `just ready`.
AI assistance was used for implementation and measurement.