Commit Graph

2006 Commits

Author SHA1 Message Date
Boshen 892238149b refactor(linter): remove inactive react config rule (#25740)
Remove the inactive `react/config` rule and its generated metadata. Oxlint uses fixed, valid React Compiler options, so the rule cannot produce actionable diagnostics.

AI-assisted: Codex was used to implement and validate this change.
2026-08-16 12:39:27 +00:00
Boshen 2dd7446b8f perf(diagnostics): preallocate graphical output (#25721)
Preallocate graphical reporter output from observed per-diagnostic size.

Criterion: single ASCII -13.7%, batch ASCII -5.7%, Unicode/tab -2.0%.

AI-assisted.

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-16 19:06:17 +08:00
Boshen 9c8abab7a7 perf(diagnostics): batch graphical reports (#25710)
Batch graphical diagnostics so reports sharing a source reuse line
indexing, and route oxlint diagnostic batches through the new API.

AI assistance: Codex was used. The contributor remains responsible for
this change.

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a></sub>
2026-08-16 19:06:16 +08:00
Boshen 8c4552dfa6 feat(linter)!: split react/react-compiler into per-category rules (#25500)
Splits the experimental `react/react-compiler` rule into 24 category-specific rules matching `eslint-plugin-react-hooks` v7.

- Runs React Compiler once per file and routes structured diagnostics to each enabled rule.
- Removes `react/react-compiler` and `reportAllBailouts`, with rules categorized by user-facing lint intent.
- Adds per-rule documentation and deduplicates shared React lint URLs.
- Preserves standardized compiler diagnostics while reporting each finding under its Oxlint rule name.

Breaking change: configurations using `react/react-compiler` must migrate to the category-specific rules.

Written with Claude Code and Codex.
2026-08-15 14:56:58 +00:00
camc314 beb724dce2 fix(linter/eslint/no-unused-vars): report bare underscore parameters (#25663)
## Summary

- report a bare underscore parameter when no args ignore pattern is configured
- preserve the optimized underscore-prefix behavior for explicit argsIgnorePattern configuration
- update focused rule coverage and generated documentation

Closes #25626
2026-08-14 13:16:16 +00:00
camc314 4004c101ca fix(linter/eslint/no-irregular-whitespace): check comments by default (#25660)
## 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.
2026-08-14 12:59:55 +00:00
Mikhail Baev 285820eed6 fix(linter/no-large-snapshots): precompile and document allowed snapshot matchers (#25611)
issue #23594

- Move regex compilation to configuration parsing to avoid recompiling
patterns for every snapshot entry
- Document the matching behavior of allowedSnapshots (first interpret as
regex, if not valid match as string)
- Fix a small typo where both example sections were labeled “incorrect"
- Replace manual config parsing with DefaultRuleConfig
2026-08-14 10:47:51 +01:00
leaysgur a38f7b3d57 fix(oxlint,oxfmt): apply .gitignore only to walk targets, not explicitly named files (#25531)
Possibly fixes #25259, and partial revert of #25133.

---

- `.gitignore` applies only to discovery (walking directory targets, cwd, globs)
- An explicitly named file target is linted / formatted even when gitignored
  - = Restoring the pre-[#25133]() behavior for files, while fully keeping the [#25107]() fix for walk roots

| Target | Current | This PR |
| --- | --- | --- |
| Explicit file (`oxlint dist/a.js`) | skipped | linted |
| Explicit directory (`oxlint dist`) | skipped | skipped |
| cwd inside ignored tree | skipped | skipped |

The underlying principle:

> `.gitignore` is a message addressed to git, not to tools.
> Borrowing it to scope discovery is a useful heuristic, and a heuristic may narrow what the walk finds, but never refuse what the user names.
> Only tool's own ignore config can refuse. (which remain hard for the husky staged-file case).

This also re-aligns the CLI with stdin and LSP, which deliberately do not consult `.gitignore` (#19206).
👉🏻 This is the main motivation.

### Why not full revert

A directory target names where to search, not what to process.
The files under it are still discovered by the walk, so discovery rules apply.
Reverting that would bring back [#25107](), whose old behavior was not even a statable spec: whether running inside an ignored tree processed anything depended on the ignore pattern's form (bare `generated` did, `generated/**` didn't), an artifact of walker pruning, not a design.

### Regarding to `--no-ignore`

This flag remains orthogonal as a walk-level opt-out (`--no-ignore=vcs dist`) and for filling gap between existing tools.

However, with explicit files working again, I'm inclined to close those PRs and revisit if demand shows up.

For [#25259]()'s use case: name the files, e.g. `oxlint dist/foo.js` (or shell-expanded glob).
2026-08-14 03:35:34 +00:00
camc314 0c68b7ff8b fix(estree): emit decorators on FormalParameterRest (#25582)
This PR adds support for emitting `decorators` when serializing an ast to ESTree (both with/without raw transfer).

Given the following code:
```
class C { method(@dec ...args) {} }
```

Before we emitted the following ESTree shape:
```json
"params": [
  {
    "type": "RestElement",
    "decorators": [],
    "argument": {
      "type": "Identifier",
      "decorators": [],
      "name": "args",
      "optional": false,
      "typeAnnotation": null,
    },
    "optional": false,
    "typeAnnotation": null,
    "value": null,
  }
],
```

Now we emit:
```json
"params": [
  {
    "type": "RestElement",
    "decorators": [
      {
        "type": "Decorator",
        "expression": { "type": "Identifier", "name": "dec" }
      }
    ],
    "argument": {
      "type": "Identifier",
      "decorators": [],
      "name": "args",
      "optional": false,
      "typeAnnotation": null,
    },
    "optional": false,
    "typeAnnotation": null,
    "value": null,
  },
],
```

fixes https://github.com/oxc-project/oxc/issues/18981
closes https://github.com/oxc-project/oxc/pull/24736
2026-08-13 10:43:14 +00:00
camc314 1d22912cb4 fix(linter/plugins): parse rule-less directive justifications (#25559)
## Summary

- extract directive justifications before parsing directive labels, matching ESLint
- return an empty rule value for rule-less directives while preserving their justification
- cover block, enable, line, and next-line directives with both eslint and oxlint prefixes

Closes #25556
2026-08-12 16:44:01 +00:00
leemr 9771ee151c test(linter): give the prune test its own suppression fixture directory (#25445)
`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.
2026-08-12 10:18:27 +01:00
Boshen 4df5835fc3 fix(linter): allow capitalized built-in calls (#25516)
Fixes #24405.

The React Compiler global registry omitted `BigInt` and `Symbol`, causing `CapitalizedCalls` false positives. Register both primitives and expose the upstream-compatible `environment.validateNoCapitalizedCalls` option.

Validated with `just ready`.

AI-assisted: Codex was used to investigate and implement this change.
2026-08-12 03:51:52 +00:00
Boshen 82c924496c refactor(diagnostics): clean up vendored renderer (#25501)
Clean up the diagnostics renderer vendored after removing `oxc-miette`: simplify source attachment and multiline labels, tighten visibility, and correct JSON escaping and debug formatting.

Validated with `just ready`.

AI-assisted; contributor review is required before merge.
2026-08-11 12:57:08 +00:00
Boshen 00e1b762e0 refactor(diagnostics): vendor oxc-miette (#25499)
We no longer need to maintain the `oxc-miette` repo as most of the code is now removed, the remaining code is minimal and can be vendored.
2026-08-11 08:04:02 +00:00
renovate[bot] 2d7a75d46b chore(deps): update oxc to ^0.144.0 (#25483)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [@oxc-project/types](https://oxc.rs)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types))
| [`^0.143.0` →
`^0.144.0`](https://renovatebot.com/diffs/npm/@oxc-project%2ftypes/0.143.0/0.144.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@oxc-project%2ftypes/0.144.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@oxc-project%2ftypes/0.144.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@oxc-project%2ftypes/0.143.0/0.144.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@oxc-project%2ftypes/0.143.0/0.144.0?slim=true)
|
| [oxc-parser](https://oxc.rs/docs/guide/usage/parser)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/napi/parser))
| [`^0.143.0` →
`^0.144.0`](https://renovatebot.com/diffs/npm/oxc-parser/0.143.0/0.144.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/oxc-parser/0.144.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxc-parser/0.144.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxc-parser/0.143.0/0.144.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxc-parser/0.143.0/0.144.0?slim=true)
|

---

### Release Notes

<details>
<summary>oxc-project/oxc (@&#8203;oxc-project/types)</summary>

###
[`v0.144.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxc-types/CHANGELOG.md#01440---2026-08-10)

##### 💥 BREAKING CHANGES

-
[`44fd320`](https://redirect.github.com/oxc-project/oxc/commit/44fd320)
ast: \[**BREAKING**] Split TS external modules & Namespace Declarations
([#&#8203;25284](https://redirect.github.com/oxc-project/oxc/issues/25284))
(camc314)

</details>

<details>
<summary>oxc-project/oxc (oxc-parser)</summary>

###
[`v0.144.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/napi/parser/CHANGELOG.md#01440---2026-08-10)

##### 💥 BREAKING CHANGES

-
[`a33788e`](https://redirect.github.com/oxc-project/oxc/commit/a33788e)
ast: \[**BREAKING**] Group class heritage into `ClassHeritage`
([#&#8203;25360](https://redirect.github.com/oxc-project/oxc/issues/25360))
(camc314)
-
[`5c5cdcd`](https://redirect.github.com/oxc-project/oxc/commit/5c5cdcd)
ast: \[**BREAKING**] Narrow `TSInterfaceHeritage::expression` to
TSTypeName
([#&#8203;24360](https://redirect.github.com/oxc-project/oxc/issues/24360))
(camc314)
-
[`6be314f`](https://redirect.github.com/oxc-project/oxc/commit/6be314f)
ast: \[**BREAKING**] Remove duplicated `VariableDeclarator::kind`
([#&#8203;25319](https://redirect.github.com/oxc-project/oxc/issues/25319))
(camc314)
-
[`44fd320`](https://redirect.github.com/oxc-project/oxc/commit/44fd320)
ast: \[**BREAKING**] Split TS external modules & Namespace Declarations
([#&#8203;25284](https://redirect.github.com/oxc-project/oxc/issues/25284))
(camc314)

##### 🐛 Bug Fixes

-
[`2a1058a`](https://redirect.github.com/oxc-project/oxc/commit/2a1058a)
napi: Omit parser header from WASI declarations
([#&#8203;25254](https://redirect.github.com/oxc-project/oxc/issues/25254))
(camc314)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMi4wIiwidXBkYXRlZEluVmVyIjoiNDQuMTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-10 13:52:38 +00:00
oxc-guard c42d6397ea release(apps): oxlint v1.78.0 && oxfmt v0.63.0 (#25473)
# Oxlint
### 💥 BREAKING CHANGES

- a33788e ast: [**BREAKING**] Group class heritage into `ClassHeritage` (#25360) (camc314)
- 5c5cdcd ast: [**BREAKING**] Narrow `TSInterfaceHeritage::expression` to TSTypeName (#24360) (camc314)
- 6be314f ast: [**BREAKING**] Remove duplicated `VariableDeclarator::kind` (#25319) (camc314)
- 44fd320 ast: [**BREAKING**] Split TS external modules & Namespace Declarations (#25284) (camc314)

### 🚀 Features

- ccb8fe8 linter/jsdoc: Implement `no-blank-blocks` rule (#25207) (Mikhail Baev)
- d4a897c linter/eslint: Implement `one-var` rule (#24470) (Cole Ellison)
- 5ab9340 linter/jsx-a11y/anchor-has-content: Add options to match eslint (#24571) (Cole Ellison)

### 🐛 Bug Fixes

- b746e00 linter/eslint/no-implicit-coercion: Preserve template coercion whitespace (#25470) (camc314)
- a92c541 linter: Preserve source text for JS plugin ignore fixes (#25280) (Norcleeh)
- 675c840 linter/eslint/prefer-promise-reject-errors: Handle parenthesized calls (#25378) (camc314)
- 1703739 linter/unicorn/new-for-builtins: Ignore optional chains (#25411) (tanakalucky)
- 95ece63 linter/unicorn/prefer-code-point: Downgrade the auto-fix to dangerous (#25412) (leemr)
- c451a0e linter/vitest: Validate `consistent-test-filename` regex patterns (#25408) (Mikhail Baev)
- 937825c react_compiler: Disable exhaustive memo validation by default (#25417) (Boshen)
- f0f7dae linter/eslint/no-unused-vars: Report invalid regex options (#25380) (Cameron)
- 44e73fd linter/unicorn/prefer-array-flat: Fix `concat.apply` suggestions (#25373) (Cameron)
- 6846a9a linter/react/rules-of-hooks: Detect constructor callbacks (#25377) (camc314)
- b247a9d linter/unicorn/new-for-builtins: Support `Float16Array` (#25382) (tanakalucky)
- 19109cd linter/unicorn/error-message: Support `SuppressedError` messages (#25375) (camc314)
- 9c13f5e linter: Assert token lookup invariants (#25368) (camc314)
- bc35f83 linter/eslint/no-unused-vars: Bound catch parameter lookup (#25367) (camc314)
- c159fb9 linter/unicorn/switch-case-braces: Bound token lookup (#25363) (camc314)
- 03b2eb2 linter/unicorn/no-static-only-class: Bound token lookup (#25361) (camc314)
- 0afc59e linter/unicorn/empty-brace-spaces: Bound token lookup (#25353) (camc314)
- 2963d98 linter/eslint/no-unreachable-loop: Do not report loops whose body has a finally block (#25335) (Todor Andonov)
- 589e5fb linter/eslint/no-param-reassign: Validate `ignorePropertyModificationsForRegex` property (#25346) (Mikhail Baev)
- aae5d8b linter/eslint/no-throw-literal: False positive on variable declared without initializer (#25275) (cjnoname)
- 6b1c479 oxlint: Normalize customized rule names (#25316) (camc314)
- d494eb5 linter/unicorn/consistent-existence-index-check: Bound token lookup (#25325) (camc314)
- 4266037 linter/typescript/prefer-namespace-keyword: Bound token lookup (#25322) (camc314)
- 4745b4e linter/typescript/no-namespace: Bound token lookup (#25321) (camc314)
- 648a481 linter/eslint/one-var: Avoid joining exported declarations (#25314) (camc314)
- 9573937 linter/typescript: Validate `ban-ts-comment` description_format (#25320) (Mikhail Baev)
- ebf7d18 linter/typescript/consistent-type-definitions: Bound token lookup (#25281) (camc314)
- 1501ccf linter/typescript/consistent-generic-constructors: Bound token lookup (#25258) (camc314)

### ⚡ Performance

- 8f784f3 linter: Reduce rule config dispatch size (#25461) (Boshen)
- 2de4ec2 linter: Reduce visitor code size (#25441) (Boshen)
- 6fb7f47 linter/unicorn/prefer-export-from: Narrow `ExportFromDeclaration` lookup (#25381) (camc314)
- e3f6263 linter/unicorn/prefer-default-parameters: Avoid reference allocation  (#25379) (camc314)
- d863473 linter/vue/max-props: Narrow AST dispatch (#25372) (camc314)
- 273d867 linter: Avoid diagnostic sorting after applying fixes (#25079) (Sysix)
- 4ec9189 oxlint/lsp: Avoid second lock for getting/removing unused directives (#25350) (Sysix)
- 6c0d01b oxlint/lsp: Preallocate fix-content vec (#25351) (Sysix)
- 3a94055 linter: Avoid per-call heap allocations in jest and unicorn helpers (#25210) (Connor Shea)
- 4abff11 linter: Avoid redundant message work in the agent reporter (#25315) (Connor Shea)
- 6bb5421 linter: Hoist `current_dir` out of the stylish reporter loop (#25313) (Connor Shea)
- 9a7c323 linter: Compute diagnostic `Info` once per diagnostic in junit reporter (#25312) (Connor Shea)
- 1cf7dde oxlint: Render JSON report into a single buffer (#25295) (connorshea)
- 8492cfd linter/typescript/ban-ts-comment: Bail early with substring guard (#25301) (Jacob Asper)
- 7607f04 linter/typescript/ban-tslint-comment: Replace regex with manual parser (#25299) (Jacob Asper)
# Oxfmt
### 💥 BREAKING CHANGES

- a33788e ast: [**BREAKING**] Group class heritage into `ClassHeritage` (#25360) (camc314)
- 5c5cdcd ast: [**BREAKING**] Narrow `TSInterfaceHeritage::expression` to TSTypeName (#24360) (camc314)
- 6be314f ast: [**BREAKING**] Remove duplicated `VariableDeclarator::kind` (#25319) (camc314)
- 44fd320 ast: [**BREAKING**] Split TS external modules & Namespace Declarations (#25284) (camc314)

### 🚀 Features

- fd02a89 oxfmt: Dispatch yaml-in-css(frontmatter) to `oxc_formatter_yaml` (#25336) (leaysgur)
- ab12665 formatter_core: Add `hardlineWithoutBreakParent` equivalent IR (#25273) (leaysgur)

### 🐛 Bug Fixes

- 95dc917 oxfmt: Drop IR Space at line start for js-in-xxx (#25460) (leaysgur)
- b63eccc formatter: Keep comments after TS this_param (#25459) (leaysgur)
- ab52a59 formatter: Format xxx-in-js inside JSDoc js fence (#25414) (leaysgur)
- 1a2c64a formatter,oxfmt: Apply effective print width for JSDoc fence (#25413) (leaysgur)
- 2eaede9 formatter_core: Unify leading-BOM handlings (#25340) (leaysgur)
- ef1d04b formatter: Break mapped type brackets (#25297) (leaysgur)
- 4e6f3f1 formatter: Break index signature brackets (#25296) (leaysgur)
- e23dccf formatter_css: Bump oxc-css-parser to accept unknown at-rule with interpolated (#25277) (leaysgur)
- c29b587 formatter_core: Measure decided-flat fill separator as flat during group re-measure (#25276) (leaysgur)
- f3c6953 formatter_yaml: Don't rewrite overflowing key to implicit (#25274) (leaysgur)

### ⚡ Performance

- c9d1a5b oxfmt: Spawn tinypool lazily (#25298) (leaysgur)

### 📚 Documentation

- 6eae5c9 formatter,oxfmt: Record embed-layer decisions in place (#25422) (leaysgur)
- 51224a7 formatter_yaml: Pin EOF blank lines divergence (#25269) (leaysgur)
2026-08-10 10:03:34 +00:00
Boshen 030857b509 refactor(diagnostics): update oxc-miette to v4 (#25395)
Remove the miette `Report` wrapper and update oxc-miette to v4's narrowed diagnostic and source protocol.

AI-assisted by OpenAI Codex.
2026-08-10 07:48:02 +00:00
Norcleeh a92c5410a1 fix(linter): preserve source text for JS plugin ignore fixes (#25280)
Closes #25278

## Overview

This PR resolves an oxlint language server crash that could occur when
requesting code actions for diagnostics reported by JS plugins,
particularly when the diagnostic appears after the beginning of a file.

JS plugin diagnostics can now safely provide regular fixes as well as
line-level and file-level disable actions.

## Root cause

1. In `run_external_rules()`, the `semantic` is being taken to run the
external JS plugin rules, leaving the former `semantic` inside the
context host empty:

https://github.com/oxc-project/oxc/blob/e23dccf338d568ac0c2be3b1223f213c797923f7/crates/oxc_linter/src/lib.rs#L539
2. The language server enables `with_ignore_fixes` to generate ignore
fixes, but inside the `add_ignore_fix()`, it doesn't check the
`section_source_text`.
3. The context host with the empty `semantic` passes the empty source
text to the `add_ignore_fix()` as `section_source_text`, and the empty
string just causes the crash.

https://github.com/oxc-project/oxc/blob/e23dccf338d568ac0c2be3b1223f213c797923f7/crates/oxc_linter/src/context/host.rs#L329-L334

## What changed

A new property `source_text` is added to the context sub host, which
stores a reference to the real source text string. Even if the
`semantic` is taken and becomes empty, the reference can still get the
real string and make things work.

```diff
pub struct ContextSubHost<'a> {
    /// Semantic information about the file being linted, which includes scopes, symbols and AST nodes.
    /// See [`Semantic`].
    pub(super) semantic: Semantic<'a>,
    // ...
+   /// Stable source text for this script section
+   /// which remains available even after `semantic` is taken while running JS plugins.
+   pub(super) source_text: &'a str,
    /// The source text offset of the sub host
    pub(super) source_text_offset: u32,
}
```

And I added a new utility method to get the `source_text` from the
context sub host.
```rs
#[inline]
pub fn source_text(&self) -> &'a str {
    self.current_sub_host().source_text()
}
```

When passing source text to `add_ignore_fix()`, just use the new
`source_text()` method.
```diff
if self.with_ignore_fixes {
-   let source_text = self.semantic().source_text();
+   let source_text = self.source_text();
    for diagnostic in &mut diagnostics {
        diagnostic.add_ignore_fix(self.current_sub_host().source_text_offset, source_text);
    }
}
// (the same as other occurs
```

Also, I added the necessary check for the `section_source_text` in
`add_ignore_fix()`.

## Notes

This is my first contribution to oxc project. Thanks for reviewing!
2026-08-09 14:42:57 +01:00
Alexander S. c7583238a0 refactor(lsp): add ToolBuildResult struct for optional client message (#25386)
Added a new `ToolBuildResult` struct, so an optional `ClientMessage` can
be passed to `Backend` (in a later PR).
This allows us later to send the custom message to the clients interface
when some config errors happened on the (re)creation.

There is no message sent yet, this will be integrated in another PR too.
This PR is just the base for the `ToolInterface`.
2026-08-08 17:02:23 +00:00
camc314 a33788e13e feat(ast)!: group class heritage into ClassHeritage (#25360)
## Summary

- Groups `Class::super_class` and `Class::super_type_arguments` into `Class::heritage`.
- Introduces `ClassHeritage` containing the superclass expression and its optional TypeScript type arguments.
- Makes superclass type arguments without a superclass expression unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct Class<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub r#type: ClassType,
    pub decorators: Vec<'a, Decorator<'a>>,
    pub id: Option<BindingIdentifier<'a>>,
    #[ts]
    pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
    pub super_class: Option<Expression<'a>>,
    #[ts]
    pub super_type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
    #[ts]
    pub implements: Vec<'a, TSClassImplements<'a>>,
    pub body: Box<'a, ClassBody<'a>>,
    #[ts]
    pub r#abstract: bool,
    #[ts]
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}
```

to:

```rust
pub struct Class<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub r#type: ClassType,
    pub decorators: Vec<'a, Decorator<'a>>,
    pub id: Option<BindingIdentifier<'a>>,
    #[ts]
    pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
    pub heritage: Option<ClassHeritage<'a>>,
    #[ts]
    pub implements: Vec<'a, TSClassImplements<'a>>,
    pub body: Box<'a, ClassBody<'a>>,
    #[ts]
    pub r#abstract: bool,
    #[ts]
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}

pub struct ClassHeritage<'a> {
    pub expression: Expression<'a>,
    #[ts]
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```

## Why?

ECMAScript defines the complete `extends` clause as `ClassHeritage`:

```text
ClassHeritage : extends LeftHandSideExpression
```

TypeScript additionally permits type arguments on the heritage expression:

```ts
class Derived extends Base<T> {}
```

Previously, the AST stored the expression and type arguments independently:

```text
Class {
    super_class: Some(Base),
    super_type_arguments: Some(<T>),
    ...
}
```

This allowed an invalid state to be constructed:

```text
Class {
    super_class: None,
    super_type_arguments: Some(<T>),
    ...
}
```

Superclass type arguments cannot exist without an `extends` expression.

The parser already treated these fields as a pair. It parsed a class heritage entry and then split its expression and type arguments into two independent fields. Builders and transforms therefore had to preserve their relationship manually.

Grouping them into `ClassHeritage` makes the syntactic dependency explicit and prevents consumers from observing or constructing contradictory class heritage.

## Class heritage forms

A class without an `extends` clause has no heritage:

```js
class Base {}
```

```text
Class {
    heritage: None,
    ...
}
```

A class extending an expression has heritage without type arguments:

```js
class Derived extends Base {}
```

```text
Class {
    heritage: Some(ClassHeritage {
        expression: Base,
        type_arguments: None,
    }),
    ...
}
```

TypeScript type arguments belong to the same heritage structure:

```ts
class Derived extends Base<T> {}
```

```text
Class {
    heritage: Some(ClassHeritage {
        expression: Base,
        type_arguments: Some(<T>),
    }),
    ...
}
```

The heritage expression may be any valid class heritage expression:

```js
class Derived extends mixin(Base) {}
class NullDerived extends null {}
```

## How to migrate

Code that previously accessed the superclass expression directly:

```rust
if let Some(super_class) = &class.super_class {
    // ...
}
```

can use the compatibility accessor:

```rust
if let Some(super_class) = class.super_class() {
    // ...
}
```

Code that needs both parts should access the grouped heritage:

```rust
if let Some(heritage) = &class.heritage {
    let expression = &heritage.expression;
    let type_arguments = heritage.type_arguments.as_deref();
}
```

Mutating consumers should update the nested fields:

```rust
if let Some(heritage) = &mut class.heritage {
    heritage.type_arguments = None;
}
```

Common migrations:

| Previous representation | New representation |
| --- | --- |
| `class.super_class` | `class.heritage.as_ref().map(\|h\| &h.expression)` or `class.super_class()` |
| `class.super_type_arguments` | `class.heritage.as_ref()?.type_arguments` or `class.super_type_arguments()` |
| `class.super_class.is_some()` | `class.heritage.is_some()` |
| Mutate `class.super_class` | Mutate `class.heritage.expression` |
| Clear `class.super_type_arguments` | Clear `class.heritage.type_arguments` |
| Pass superclass and type arguments separately to a `Class` builder | Construct and pass `ClassHeritage` |
| Clone both fields independently | Clone `class.heritage` |

```ts
superClass: Expression | null;
superTypeArguments?: TSTypeParameterInstantiation | null;
```

No `heritage` property or `ClassHeritage` node is added to the ESTree output.

fixes https://github.com/oxc-project/backlog/issues/216
2026-08-07 14:18:59 +00:00
Sysix 4ec9189b26 perf(oxlint/lsp): avoid second lock for getting/removing unused directives (#25350)
Avoids a second lock for `.map` inside `DirectivesStore` when consuming the directives on `--lsp` side.
2026-08-07 10:00:46 +00:00
Sysix 6c0d01b8de perf(oxlint/lsp): preallocate fix-content vec (#25351)
After https://github.com/oxc-project/oxc/pull/24958 we know the size of `Vec<FixedContent>` , because we are only consuming now `Messages.fixes` which are directly mapped into `FixedContent`
2026-08-07 09:45:27 +00:00
camc314 5c5cdcd522 feat(ast)!: narrow TSInterfaceHeritage::expression to TSTypeName (#24360)
## Summary

Narrow `TSInterfaceHeritage` from a general JavaScript `Expression` to `TSTypeName`.

```rust
pub struct TSInterfaceHeritage<'a> {
    pub type_name: TSTypeName<'a>,
    pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```

## Breaking Change

The Rust AST field changes from:

```rust
expression: Expression<'a>
```

to:

```rust
type_name: TSTypeName<'a>
```

## Why?

The current AST can represent shapes such as:

```ts
interface A extends foo() {}
interface A extends A + B {}
interface A extends new Foo() {}
interface A extends true {}
```

Which is an overly wide type since all of the above variants are invalid.

This PR tightens up the AST, making these invalid variants irrepresentable.

## How to migrate?

This should be a trivial migration:
1. instead of accessing `expression` on `TSInterfaceHeritage`, now access `type_name`
2. change any pattern matching on the `type_name` field to use `TypeName` (this is a smaller subset so should allow deleting code!)

closes https://github.com/oxc-project/backlog/issues/215
2026-08-07 06:00:42 +00:00
camc314 6b1c4795b2 fix(oxlint): normalize customized rule names (#25316)
## Summary

- normalize LSP `rulesCustomization` keys using the linter config rule-name parser
- support `eslint/` prefixes and existing plugin aliases such as `@typescript-eslint`
- cover prefixed severity and autofix customization paths

Closes #25311
2026-08-06 11:04:43 +00:00
Mikhail Baev ccb8fe89db feat(linter/jsdoc): implement no-blank-blocks rule (#25207)
this PR implements `jsdoc/no-blank-blocks` rule, issue #1170
2026-08-05 22:38:24 +01:00
Mikhail Baev 9573937df3 fix(linter/typescript): validate ban-ts-comment description_format (#25320)
adds validation for the `descriptionFormat` regex option from #23594 and
replaces manual config parsing with `DefaultRuleConfig`
2026-08-05 20:21:36 +01:00
camc314 6be314f226 feat(ast)!: remove duplicated VariableDeclarator::kind (#25319)
## Summary

- Removes `VariableDeclarator::kind`.
- Makes `VariableDeclaration::kind` the single source of truth for every declarator in its declaration list.
- Makes contradictory parent and child declaration kinds unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct VariableDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: VariableDeclarationKind,
    pub declarations: Vec<'a, VariableDeclarator<'a>>,
    #[ts]
    pub declare: bool,
}

pub struct VariableDeclarator<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    #[estree(skip)]
    pub kind: VariableDeclarationKind,
    #[estree(via = VariableDeclaratorId)]
    pub id: BindingPattern<'a>,
    #[ts]
    #[estree(skip)]
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
    pub init: Option<Expression<'a>>,
    #[ts]
    pub definite: bool,
}
```

to:

```rust
pub struct VariableDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: VariableDeclarationKind,
    pub declarations: Vec<'a, VariableDeclarator<'a>>,
    #[ts]
    pub declare: bool,
}

pub struct VariableDeclarator<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    #[estree(via = VariableDeclaratorId)]
    pub id: BindingPattern<'a>,
    #[ts]
    #[estree(skip)]
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
    pub init: Option<Expression<'a>>,
    #[ts]
    pub definite: bool,
}
```

## Why?

A variable declaration has one declaration kind shared by every declarator in its declaration list:

```js
const first = 1, second = 2;
```

Previously, the AST stored that kind on both the parent declaration and every child declarator:

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { kind: Const, ... },
        VariableDeclarator { kind: Const, ... },
    ],
}
```

This allowed contradictory ASTs to be constructed:

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { kind: Var, ... },
    ],
}
```

Different consumers read different copies:

- Code generation used `VariableDeclaration::kind`.
- Semantic binding used `VariableDeclarator::kind`.
- Lint rules, transforms, and minifier passes used a mixture of both.

As a result, one AST could be emitted as `const` while its binding was treated as function-scoped `var`.

Transforms also had to keep the copies synchronized manually. For example, const-to-let normalization updated the parent and then looped through every declarator to update each child. Explicit resource management performed similar paired mutations for `using` declarations.

Keeping the kind only on `VariableDeclaration` removes this synchronization requirement and prevents consumers from observing conflicting declaration semantics.

## Declaration forms

The declaration kind now belongs exclusively to the node that owns the declaration list:

```js
let value;
```

```text
VariableDeclaration {
    kind: Let,
    declarations: [
        VariableDeclarator { id: value, ... },
    ],
}
```

Multiple declarators share the same parent kind:

```js
const first = 1, second = 2;
```

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { id: first, ... },
        VariableDeclarator { id: second, ... },
    ],
}
```

## How to migrate

Code that previously read the kind from a declarator:

```rust
if declarator.kind.is_const() {
    // ...
}
```

should now read it from the containing declaration:

```rust
if declaration.kind.is_const() {
    // ...
}
```

Consumers operating through an AST node store should retrieve the parent `VariableDeclaration`:

```rust
let AstKind::VariableDeclaration(declaration) =
    nodes.parent_kind(declarator.node_id())
else {
    unreachable!();
};

let kind = declaration.kind;
```

Common migrations:

| Previous representation | New representation |
| --- | --- |
| `declarator.kind` | Containing `declaration.kind` |
| Match `VariableDeclarator::kind` during semantic binding | Read the parent `VariableDeclaration` from ancestry |
| Pass `kind` to `VariableDeclarator::new` | Remove the `kind` argument |
| Update parent and every child kind | Update only `VariableDeclaration::kind` |
| Recover kind after detaching a declarator | Capture or pass the parent kind before detaching it |
| Inspect a symbol’s declarator kind in lint rules | Resolve its parent declaration through the node store |

When constructing declarators, use the generated kindless `VariableDeclarator` builders and place them inside a `VariableDeclaration` carrying the required kind.

fixes https://github.com/oxc-project/backlog/issues/237
2026-08-05 16:31:46 +00:00
Connor Shea 4abff11c80 perf(linter): avoid redundant message work in the agent reporter (#25315)
Generated with Claude Code, Opus 5. Reviewed and tested by me. Mainly
just avoiding extra, unneeded allocations per-diagnostic.

This shouldn't matter much at normal scales, but given that many people
are using Claude Code, Codex, etc for setting up their linter config or
to mass-resolve lint violations, I think the tradeoff in extra code here
is probably worth it because there are definite cases where this will be
getting triggered with large diagnostic counts. I will admit the
`is_already_compact` method here is jank, though.

---

Two small changes to `format_agent`, both removing work that was already
done.

**1. Borrow already-compact messages.** `compact_message` rebuilt every
message into a fresh `String` by splitting on whitespace and rejoining,
even though nearly every rule message is already compact and comes out
byte-for-byte identical. It now checks first and borrows when there is
nothing to collapse, falling back to the rebuild otherwise.

**2. Reuse the message `Info` already rendered.** `Info::new` calls
`diagnostic.to_string()` internally; `format_agent` discarded that and
rendered it a second time. It now takes the message from `Info`. `Info`
only fills the message in when the diagnostic has a resolvable label, so
the direct render is kept for the label-less case — which the existing
`reporter_error_without_label` test covers.

## Allocations

Measured with a counting global allocator, `--threads=1` for
deterministic counts, on a corpus of 20k small files producing exactly
2,400,000 diagnostics. `unix` is a control (untouched by this PR) and
`--silent` is the no-formatter baseline.

| format | main allocs | branch allocs | diff | change |
| --- | ---: | ---: | ---: | ---: |
| silent (baseline) | 55,672,894 | 55,672,894 | 0 | 0.00% |
| unix (control) | 91,353,791 | 91,353,791 | 0 | 0.00% |
| agent | 124,894,389 | 104,194,389 | -20,700,000 | -16.57% |

Subtracting the baseline to isolate the formatter itself:

| | main | branch | change |
| --- | ---: | ---: | ---: |
| agent formatter allocations | 69,221,495 | 48,521,495 | **-29.9%** |
| per diagnostic | 28.84 | 20.22 | -8.62 |
| bytes requested | 8.39 GB | 7.66 GB | -729.7 MB |

Worth noting that the saving is much larger than the two `String`s the
diff visibly removes. `diagnostic.to_string()` is not one allocation —
rendering an `Error` goes through miette formatting, which allocates
repeatedly — so removing the duplicate render is worth around 8
allocations per diagnostic, not one (we should maybe consider figuring
out if we can fix that separately to reduce the allocations per
diagnostic, but usually there aren't too many diagnostics so it's a
minor improvement).

## Timing

Paired-difference measurement: samples interleaved one-for-one between
the two binaries and differenced pairwise, so drift cancels. `unix`
again as control.

| corpus | format | main | branch | diff | 95% CI | change |
| --- | --- | ---: | ---: | ---: | :---: | ---: |
| 20k small files (2.4M diagnostics) | agent | 3837ms | 3272ms |
-564.4ms | [-590.7, -538.2]ms | -14.7% |
| 20k small files (2.4M diagnostics) | unix (control) | 2846ms | 2832ms
| -14.2ms | [-39.4, +10.9]ms | -0.5% |
| vscode `-D all` (1.37M diagnostics) | agent | 13721ms | 13381ms |
-339.8ms | [-511.6, -168.0]ms | -2.5% |
| vscode `-D all` (1.37M diagnostics) | unix (control) | 12794ms |
12781ms | -12.9ms | [-70.1, +44.4]ms | -0.1% |

## Equivalence

Confirmed byte-identical output from the formatter on main vs this
branch over VS Code.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 16:38:04 +01:00
Connor Shea 6bb5421c4a perf(linter): hoist current_dir out of the stylish reporter loop (#25313)
AI Disclosure: Generated with Claude Code, reviewed and tested by me.
Just a micro-optimization.

The stylish reporter calls `std::env::current_dir()` once per file
group, in order to canonicalize each filename for display. That is a
syscall, and the result is identical for every group, so this reads it
once before the loop and borrows it.

Output is unchanged — `current_dir()` is not expected to vary mid-run,
and the value is used the same way.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:53:36 +01:00
camc314 44fd320324 feat(ast)!: split TS external modules & Namespace Declarations (#25284)
## Summary

- Splits `TSModuleDeclaration` into two dedicated declaration nodes:
  - `TSExternalModuleDeclaration` for string-literal modules and module augmentations.
  - `TSNamespaceDeclaration` for identifier-based `module` and `namespace` declarations.
- Removes `TSModuleDeclaration`, `TSModuleDeclarationName`, `TSModuleDeclarationBody`, and `TSModuleDeclarationKind`.
- Makes invalid combinations of names, bodies, and declaration kinds unrepresentable.
- Ensures only namespace declarations introduce a normal binding.
- Updates all AST consumers, generators, traversal, formatting, code generation, semantic analysis, lint rules, transforms, and serialization.
- Keeps normal and eager-raw ESTree output compatible: both declarations remain `TSModuleDeclaration` nodes.
- Allows experimental lazy raw deserialization to expose the two new node types directly.

## Breaking Change

The Rust AST changes from:

```rust
pub enum Declaration<'a> {
    VariableDeclaration(Box<'a, VariableDeclaration<'a>>) = 32,
    FunctionDeclaration(Box<'a, Function<'a>>) = 33,
    ClassDeclaration(Box<'a, Class<'a>>) = 34,

    TSTypeAliasDeclaration(Box<'a, TSTypeAliasDeclaration<'a>>) = 35,
    TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>) = 36,
    TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>) = 37,
    TSModuleDeclaration(Box<'a, TSModuleDeclaration<'a>>) = 38,
    TSGlobalDeclaration(Box<'a, TSGlobalDeclaration<'a>>) = 39,
    TSImportEqualsDeclaration(Box<'a, TSImportEqualsDeclaration<'a>>) = 40,
}

pub struct TSModuleDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub id: TSModuleDeclarationName<'a>,
    pub body: Option<TSModuleDeclarationBody<'a>>,
    pub kind: TSModuleDeclarationKind,
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}
```

to:

```rust
pub enum Declaration<'a> {
    VariableDeclaration(Box<'a, VariableDeclaration<'a>>) = 32,
    FunctionDeclaration(Box<'a, Function<'a>>) = 33,
    ClassDeclaration(Box<'a, Class<'a>>) = 34,

    TSTypeAliasDeclaration(Box<'a, TSTypeAliasDeclaration<'a>>) = 35,
    TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>) = 36,
    TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>) = 37,
    TSExternalModuleDeclaration(Box<'a, TSExternalModuleDeclaration<'a>>) = 38,
    TSNamespaceDeclaration(Box<'a, TSNamespaceDeclaration<'a>>) = 39,
    TSGlobalDeclaration(Box<'a, TSGlobalDeclaration<'a>>) = 40,
    TSImportEqualsDeclaration(Box<'a, TSImportEqualsDeclaration<'a>>) = 41,
}

pub struct TSExternalModuleDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub id: StringLiteral<'a>,
    pub body: Option<Box<'a, TSModuleBlock<'a>>>,
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}

pub struct TSNamespaceDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub id: BindingIdentifier<'a>,
    pub body: TSNamespaceDeclarationBody<'a>,
    pub kind: TSNamespaceDeclarationKind,
    pub declare: bool,
    pub scope_id: Cell<Option<ScopeId>>,
}

pub enum TSNamespaceDeclarationBody<'a> {
    TSNamespaceDeclaration(Box<'a, TSNamespaceDeclaration<'a>>) = 0,
    TSModuleBlock(Box<'a, TSModuleBlock<'a>>) = 1,
}

pub enum TSNamespaceDeclarationKind {
    Module = 0,
    Namespace = 1,
}
```

The corresponding inherited `Statement` variants, `AstKind` variants, builders, and visitor methods have also changed.

## Why?

`TSModuleDeclaration` previously represented two declaration families with different syntax and semantics:

```ts
declare module "foo" {}
declare module "*.css";

module Foo {}
namespace Foo {}
namespace Foo.Bar {}
```

The shared representation allowed invalid states such as:

- A namespace with a string-literal name.
- An external module with an identifier name.
- An identifier namespace without a body.
- A dotted external module.
- An external module containing another module declaration as its body.
- A declaration whose `kind` disagreed with its name.

Consumers also had to inspect `id` before determining whether the node introduced a binding or represented an external module.

The two declaration families have stronger independent invariants:

### External modules

- Always have a string-literal name.
- Never introduce a normal binding identifier.
- May omit their body.
- Cannot use dotted names.
- Use module-body import and export rules.

### Namespace declarations

- Always have a binding identifier.
- Always have a body.
- May contain nested namespace declarations for dotted names.
- Explicitly preserve the `module` or `namespace` spelling.
- Introduce a namespace/module symbol.
- Use namespace-body restrictions.

Dedicated nodes encode these differences directly.

## Declaration forms

External modules use `TSExternalModuleDeclaration`:

```ts
declare module "foo" {}
declare module "*.css";
module "foo" {}
```

Identifier-based declarations use `TSNamespaceDeclaration`:

```ts
module Foo {}
namespace Foo {}
```

Dotted namespaces are represented as nested namespace declarations:

```ts
namespace Foo.Bar {}
```

```rust
TSNamespaceDeclaration {
    id: Foo,
    body: TSNamespaceDeclarationBody::TSNamespaceDeclaration(
        TSNamespaceDeclaration {
            id: Bar,
            body: TSNamespaceDeclarationBody::TSModuleBlock(...),
            // ...
        }
    ),
    // ...
}
```

Only `TSExternalModuleDeclaration::body` is optional.

## ESTree compatibility

Normal serialization and eager raw deserialization continue to emit the existing ESTree shape:

```ts
interface TSModuleDeclaration {
  type: "TSModuleDeclaration";
  id: BindingIdentifier | StringLiteral | TSQualifiedName;
  body: TSModuleBlock | null;
  kind: "module" | "namespace";
  declare: boolean;
  global: false;
}
```

Dotted namespaces continue to serialize as a single `TSModuleDeclaration` with a `TSQualifiedName`.

Experimental lazy raw deserialization is not compatibility-preserving. It now exposes `TSExternalModuleDeclaration` and `TSNamespaceDeclaration` directly, with separate constructors, layouts, and visitor type IDs.

## How to migrate

Code that previously matched all forms through one variant:

```rust
if let Declaration::TSModuleDeclaration(declaration) = declaration {
    // ...
}
```

should now match the required declaration family:

```rust
match declaration {
    Declaration::TSExternalModuleDeclaration(declaration) => {
        // String-literal external module
    }
    Declaration::TSNamespaceDeclaration(declaration) => {
        // Identifier-based module or namespace
    }
    _ => {}
}
```

The same applies to `Statement` and `AstKind`:

```rust
Statement::TSExternalModuleDeclaration(declaration)
Statement::TSNamespaceDeclaration(declaration)

AstKind::TSExternalModuleDeclaration(declaration)
AstKind::TSNamespaceDeclaration(declaration)
```

Common field migrations:

| Previous representation | New representation |
| --- | --- |
| `TSModuleDeclarationName::StringLiteral(id)` | `TSExternalModuleDeclaration::id` |
| `TSModuleDeclarationName::Identifier(id)` | `TSNamespaceDeclaration::id` |
| `body: None` | Only valid on `TSExternalModuleDeclaration` |
| `TSModuleDeclarationBody::TSModuleBlock` | External `body` or `TSNamespaceDeclarationBody::TSModuleBlock` |
| `TSModuleDeclarationBody::TSModuleDeclaration` | `TSNamespaceDeclarationBody::TSNamespaceDeclaration` |
| `TSModuleDeclarationKind::Module` | External module, or `TSNamespaceDeclarationKind::Module` |
| `TSModuleDeclarationKind::Namespace` | `TSNamespaceDeclarationKind::Namespace` |
| Inspect `id` to determine binding behavior | Match `TSNamespaceDeclaration` |
| `declaration.id()` for string modules | Now returns `None` |
| `visit_ts_module_declaration` | `visit_ts_external_module_declaration` or `visit_ts_namespace_declaration` |

When constructing nodes, use the generated `TSExternalModuleDeclaration` or `TSNamespaceDeclaration` builders rather than recreating the previous shared representation.

fixes https://github.com/oxc-project/backlog/issues/218
2026-08-05 14:37:35 +00:00
Connor Shea 9a7c323500 perf(linter): compute diagnostic Info once per diagnostic in junit reporter (#25312)
AI Disclosure: Generated with Claude Code, Opus 5. Tested and reviewed
by me for correctness.

---

`format_junit` built an `Info` for every diagnostic twice: once to group
diagnostics by filename, then again inside the render loop. `Info::new`
reads the span out of the source, so this doubled the most expensive
part of the reporter. Build it once and group by index instead.

Also drop two other duplicated pieces of work in the same loop: the
message was `xml_escape`d twice (once for the attribute, once for the
description), and the rule name was re-derived via `code().to_string()`
even though `Info` already carries it as `rule_id`. And the nested
`Vec<String>` + `join` is now a single output buffer.

Benchmarked with 3 runs x 2 format options (checkstyle as the control) x
3 repos, shows small reductions on junit in RSS thanks to this change,
and 40-47% drops in runtime (`oxlint -D all --format=NAME`):

| repo | diagnostics | format | main | branch | delta | change | RSS
main | RSS branch |
| --- | ---: | --- | ---: | ---: | ---: | ---: | ---: | ---: |
| vscode | 1,366,572 | checkstyle | 11832ms | 11645ms | -187ms | -1.6% |
3272 MB | 3273 MB |
| vscode | 1,366,572 | **junit** | 22022ms | 11611ms | -10410ms |
**-47.3%** | 3325 MB | 3287 MB |
| gitlab | 363,895 | checkstyle | 1749ms | 1723ms | -26ms | -1.5% | 966
MB | 971 MB |
| gitlab | 363,895 | **junit** | 2895ms | 1718ms | -1177ms | **-40.7%**
| 989 MB | 948 MB |
| canvas-lms | 350,637 | checkstyle | 2207ms | 2155ms | -52ms | -2.3% |
917 MB | 915 MB |
| canvas-lms | 350,637 | **junit** | 3944ms | 2139ms | -1805ms |
**-45.8%** | 921 MB | 886 MB |

On a more reasonable number of diagnostics (running on canvas-lms with
the default ruleset) the numbers are less pronounced but still show a
17% drop:

| repo | ruleset | diagnostics | format | main | branch | delta | change
|
| --- | --- | ---: | --- | ---: | ---: | ---: | ---: |
| canvas-lms | default | 3,149 | checkstyle | 218ms | 219ms | +1ms |
+0.5% |
| canvas-lms | default | 3,149 | junit | 265ms | 219ms | -46ms | -17.3%
|

On a repo with 0 violations, the runtime difference for junit on main
vs. this branch is entirely noise.

Realistically this won't have much of an impact in a user's actual run,
as most of the time a lint run results in one of:

- 0 diagnostics
- a single digit number of diagnostics in a junit run due to new
violations introduced by code changes
- or there are only a few hundred to a few thousand max (due to, for
example, warnings which are considered safe and are only used for a
"transition" period where a rule is soft-enabled until all existing
violations of it are fixed).

But I think it's worthwhile regardless to help on smaller #s of
diagnostics.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 15:37:01 +01:00
Cole Ellison d4a897ce22 feat(linter/eslint): implement one-var rule (#24470)
## Summary

Adds a native Oxlint implementation of [ESLint's `one-var`
rule](https://eslint.org/docs/latest/rules/one-var). This implementation
is a port of the [eslint rule
source](https://github.com/eslint/eslint/blob/main/lib/rules/one-var.js),
test suite, and autofix logic.

Related #479

## AI Disclosure

GPT 5.6 Sol was used to assist with implementation of this rule.

---------

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-05 15:10:29 +01:00
Cole Ellison 5ab9340637 feat(linter/jsx-a11y/anchor-has-content): add options to match eslint (#24571)
## Description

Eslint supports [a `components`
option](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-has-content.md)
for `jsx-a11y/anchor-has-content` that specified additional components
to treat as anchor elements. This adds equivalent support for that
option.

Note: I am not certain if this is something that should instead be read
off of
[`settings.jsx-a11y.components`](https://oxc.rs/docs/guide/usage/linter/config-file-reference.html#settings-jsx-a11y-components),
as that could list `{ MyAnchor: 'a' }`. Doing that would enable implicit
application of rules to custom components based on rendered entity, but
that may be the wrong use case, as the custom component definition
itself could be responsible for rendering required children.

## AI disclosure

I used GPT 5.6 Sol to assist in implementation of this fix. I've
reviewed all generated code and test coverage.

---------

Signed-off-by: Cameron <cameron.clark@hey.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-05 11:57:23 +01:00
connorshea 1cf7ddea46 perf(oxlint): render JSON report into a single buffer (#25295)
AI Disclosure: Generated with Claude Code, Opus 5. Reviewed and tested by me.

## What

`format_json` rendered each diagnostic into its own `String`, collected them into a `Vec<String>`, then `join`ed them. That allocates once per diagnostic and copies the entire report a second time to build the joined result.

This renders straight into one output buffer instead.

## Why

On the vscode corpus with `-D all` — 1.37M diagnostics, ~500 MB of JSON output:

| | before | after |
| --- | --- | --- |
| `--format=json` wall clock | 13.3 s | 12.6 s |
| peak RSS | 4668 MB | 3576 MB |

Measured as best-of-3 with the two binaries interleaved, output redirected to `/dev/null`.

## Correctness

Output is byte-identical before and after. Verified on `apps/oxlint/fixtures/cli/output_formatter_diagnostic` and on a 365k-diagnostic subset of vscode (170 MB of JSON compared byte-for-byte). Note that oxlint's diagnostic ordering is nondeterministic run-to-run under parallelism, so the comparison was run with `--threads=1`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-05 09:52:40 +00:00
renovate[bot] 72bbbe240a chore(deps): update dependency oxc-parser to ^0.143.0 (#25260) 2026-08-03 22:09:41 +01:00
renovate[bot] cc01bd7f0f chore(deps): update dependency @oxc-project/types to ^0.143.0 (#25255)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [@oxc-project/types](https://oxc.rs)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxc-types))
| [`^0.142.0` →
`^0.143.0`](https://renovatebot.com/diffs/npm/@oxc-project%2ftypes/0.142.0/0.143.0)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@oxc-project%2ftypes/0.143.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@oxc-project%2ftypes/0.143.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@oxc-project%2ftypes/0.142.0/0.143.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@oxc-project%2ftypes/0.142.0/0.143.0?slim=true)
|

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, 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:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-08-03 13:25:45 +00:00
oxc-guard 9a423f2f48 release(apps): oxlint v1.77.0 && oxfmt v0.62.0 (#25251)
# Oxlint
### 💥 BREAKING CHANGES

- 4120da0 semantic: [**BREAKING**] Return borrowed JSDoc so the parse cache works (#25186) (Connor Shea)
- 067da8c ast: [**BREAKING**] Store single parameter in `TSIndexSignature::parameter` (#25154) (camc314)
- 1bdedd1 ast: [**BREAKING**] Introduce `ExportDeclaration`, `ExportFromDeclaration` (#25095) (camc314)
- c917f20 ast: [**BREAKING**] Introduce `ArrowFunctionBody` enum (#24987) (camc314)

### 🚀 Features

- 0b9f83c linter/oxc/bad-char-at-comparison: Expand coverage (#25001) (Mikhail Baev)
- 1fb3d70 oxlint: Show file:line:col in github format annotation messages (#25020) (Faris Tristan Fabian)

### 🐛 Bug Fixes

- 9d4592c linter: Reject duplicate JS plugin names (#25243) (camc314)
- 3213e7a linter: Ignore unsigned shifts in prefer-math-trunc (#25240) (camc314)
- 06579da linter/eslint/prefer-promise-reject-errors: Handle ambient error variables (#25238) (camc314)
- 6a7cd90 linter/oxc/no-async-await: Bound token lookup (#25222) (camc314)
- d8e390a linter/oxc/bad-bitwise-operator: Bound token lookups (#25220) (camc314)
- 351a777 linter/import/no-duplicates: Bound token lookup (#25219) (camc314)
- 4f70fb6 linter/import/consistent-type-specifier-style: Bound token lookup (#25218) (camc314)
- b89113c linter/eslint/yoda: Bound token lookup (#25202) (camc314)
- 7c7cda7 linter/oxc/no-map-spread: Avoid recursive declaration traversal (#25200) (camc314)
- 99efb30 linter/eslint/prefer-promise-reject-errors: Move category to `pedantic` (#25201) (camc314)
- 8edad2a linter/eslint/require-await: Bound token lookup (#25199) (camc314)
- 5aaec85 linter/eslint/prefer-template: Bound token lookup (#25197) (camc314)
- 0ea1af4 linter/unicorn/new-for-builtins: Align Date() diagnostic message with upstream (#25166) (tanakalucky)
- f6c8575 linter/eslint/prefer-object-spread: Bound token lookup (#25195) (camc314)
- 20f68e7 oxlint,oxfmt: Respect `.gitignore` for walk targets and explicit paths (#25133) (leaysgur)
- b83993b linter/eslint/prefer-const: Bound token lookup (#25194) (camc314)
- aeabf63 linter/eslint/object-shorthand: Bound token lookups (#25193) (camc314)
- 48f302c linter/eslint/no-var: Bound token lookup (#25192) (camc314)
- 1f595de linter/eslint/no-unused-vars: Bound token lookups (#25191) (camc314)
- e52f4c4 linter/eslint/no-unexpected-multiline: Use bounded token lookup (#25190) (camc314)
- ca80338 linter/eslint/eqeqeq: Use bounded token lookup (#25189) (camc314)
- df91684 linter: Make the remaining token lookups comment-aware (#25182) (connorshea)
- 2401bb8 linter/eslint/prefer-const: Skip Svelte and Vue files (#25148) (Pitiș Radu)
- b870897 linter: Don't match tokens inside comments when locating spans (#25181) (connorshea)
- b474da1 linter/typescript/consistent-type-imports: Make token lookups comment-aware (#25180) (connorshea)
- 81f82db linter: Don't insert an ASI semicolon into an unbraced statement body (#25160) (Vladexy88x)
- 31333be linter/no-magic-array-flat-depth: Scan for `(` by byte offset instead of character count (#25130) (connorshea)
- 56e9c83 linter: Index source text by byte offset instead of char count (#25131) (connorshea)
- 5c0fa61 linter/eslint/no-warning-comments: Unify config structs and remove manual options docs (#25151) (Mikhail Baev)
- a922355 docs: Incorrect quotations in `role_supports_aria_props.rs` (#25077) (Maximilian Franzke)
- e8c4c05 linter/eslint/no-unreachable-loop: Handle caught errors correctly (#25071) (camc314)
- 2b02711 oxlint: Correct --warn flag help text (#25059) (Faris Tristan Fabian)
- 97f084f linter/eslint/no-control-regex: Allow null escape (#25070) (camc314)

### ⚡ Performance

- 224c21a linter/vue/no-side-effects-in-computed-properties: Borrow property names in the mutation walk (#25212) (connorshea)
- d36e9c9 linter/unicorn/prefer-export-from: Skip non-violating imports early (#25183) (connorshea)
- 6f2b933 linter/import/no-cycle: Drop two Vec allocations per graph edge (#25004) (Connor Shea)
- 79ddb18 linter/import/no-cycle: Skip the graph walk for acyclic modules (#25003) (Connor Shea)

### 📚 Documentation

- f8392cd linter/eslint/prefer-promise-reject-errors: Recommend type aware rule (#25241) (camc314)
- 9dc7756 linter/typescript/no-unnecessary-condition: Clarify options (#25110) (camc314)
# Oxfmt
### 💥 BREAKING CHANGES

- 067da8c ast: [**BREAKING**] Store single parameter in `TSIndexSignature::parameter` (#25154) (camc314)
- a0c7788 ast: [**BREAKING**] Change `TSIndexSignatureName::name` to `Ident` (#25150) (camc314)
- 1bdedd1 ast: [**BREAKING**] Introduce `ExportDeclaration`, `ExportFromDeclaration` (#25095) (camc314)
- c917f20 ast: [**BREAKING**] Introduce `ArrowFunctionBody` enum (#24987) (camc314)
- 0c16cb5 oxfmt: [**BREAKING**] Format parser:yaml files by oxc_formatter_yaml (#24890) (leaysgur)

### 🚀 Features

- 1b57f78 oxfmt: Support tsx-in-vue (#25063) (leaysgur)
- be7f6da oxfmt: Colorize diff file list in check mode (#25061) (leaysgur)

### 🐛 Bug Fixes

- 68146bd formatter_css: Preserve glued plugin words in grid values (#25225) (leaysgur)
- 20f68e7 oxlint,oxfmt: Respect `.gitignore` for walk targets and explicit paths (#25133) (leaysgur)
- a9e76e9 oxfmt: Rework tsx-in-vue support (#25106) (leaysgur)
- dd28596 formatter_css: Bump oxc-css-parser for substituted at-rule preludes (#25104) (leaysgur)
- ca1ba71 formatter: Hug arrows with type-reference return annotations (#25044) (Pitiș Radu)
- f56009a oxfmt: Correct prose about comment width in fits measurement (#25054) (leaysgur)
- 22fdda4 oxfmt/conformance: Fix typo (#25053) (leaysgur)
- ee344bd formatter_yaml: Break long keys off a block scalar header (#25014) (leaysgur)
- d5e3476 formatter_yaml: Let ancestor collection values claim end comments after block scalars (#24897) (leaysgur)
- ec05297 formatter_yaml: Align sequence container end comments to the dash width (#24891) (leaysgur)
- e6e584b oxfmt: Type jsdoc. enum options (#25008) (leaysgur)

### ⚡ Performance

- b91d5a8 formatter_css,formatter_graphql,formatter_yaml,formatter_json: Pre alloc IR buffers (#25234) (leaysgur)

### 📚 Documentation

- eaa7c69 formatter_core: Extract FORMATTER_POLICY (#25233) (leaysgur)
- a1842e2 formatter_json: Fix outdated separeted.rs comments (#25144) (leaysgur)
- 193e16f formatter/jsdoc: Correct `str_width` attribution (#25142) (leaysgur)
2026-08-03 11:15:35 +00:00
renovate[bot] b937e3e2f1 chore(deps): update napi (#25019)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) | Type |
Update |
|---|---|---|---|---|---|---|---|
| [@napi-rs/cli](https://napi.rs/)
([source](https://redirect.github.com/napi-rs/napi-rs)) | [`3.7.4` →
`3.8.2`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/3.7.4/3.8.2) |
![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fcli/3.8.2?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fcli/3.8.2?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fcli/3.7.4/3.8.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fcli/3.7.4/3.8.2?slim=true)
| pnpm.catalog.default | minor |
| [napi](https://redirect.github.com/napi-rs/napi-rs) | `3.11.0` →
`3.12.0` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi/3.12.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi/3.12.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi/3.11.0/3.12.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi/3.11.0/3.12.0?slim=true)
| workspace.dependencies | minor |
| [napi-build](https://redirect.github.com/napi-rs/napi-rs) | `2.3.2` →
`2.4.0` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi-build/2.4.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-build/2.4.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-build/2.3.2/2.4.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-build/2.3.2/2.4.0?slim=true)
| workspace.dependencies | minor |
| [napi-derive](https://redirect.github.com/napi-rs/napi-rs) | `3.6.0` →
`3.6.2` |
![age](https://developer.mend.io/api/mc/badges/age/crate/napi-derive/3.6.2?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-derive/3.6.2?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-derive/3.6.0/3.6.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-derive/3.6.0/3.6.2?slim=true)
| workspace.dependencies | patch |

---

### Release Notes

<details>
<summary>napi-rs/napi-rs (@&#8203;napi-rs/cli)</summary>

###
[`v3.8.2`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.8.1...@napi-rs/cli@3.8.2)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.8.1...@napi-rs/cli@3.8.2)

###
[`v3.8.1`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.8.0...@napi-rs/cli@3.8.1)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.8.0...@napi-rs/cli@3.8.1)

###
[`v3.8.0`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.7.4...@napi-rs/cli@3.8.0)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.7.4...@napi-rs/cli@3.8.0)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-03 11:21:03 +01:00
camc314 9d4592c72f fix(linter): reject duplicate JS plugin names (#25243)
## Summary
- reject JS plugins whose normalized names collide
- keep registered plugin names isolated per LSP workspace
- add a CLI regression fixture covering two plugins that expose the same rule name

Fixes #25164
2026-08-03 09:40:27 +00:00
renovate[bot] d0c45aaf7c chore(deps): update npm packages (#25205)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Adoption](https://docs.renovatebot.com/merge-confidence/) |
[Passing](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [@napi-rs/wasm-runtime](https://napi.rs/)
([source](https://redirect.github.com/napi-rs/napi-rs/tree/HEAD/wasm-runtime))
| [`1.1.6` →
`1.2.1`](https://renovatebot.com/diffs/npm/@napi-rs%2fwasm-runtime/1.1.6/1.2.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fwasm-runtime/1.2.1?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fwasm-runtime/1.2.1?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fwasm-runtime/1.1.6/1.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fwasm-runtime/1.1.6/1.2.1?slim=true)
|
| [degit](https://redirect.github.com/Rich-Harris/degit) | [`3.6.2` →
`3.6.5`](https://renovatebot.com/diffs/npm/degit/3.6.2/3.6.5) |
![age](https://developer.mend.io/api/mc/badges/age/npm/degit/3.6.5?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/degit/3.6.5?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/degit/3.6.2/3.6.5?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/degit/3.6.2/3.6.5?slim=true)
|
| [eslint](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint)) | [`10.7.0` →
`10.8.0`](https://renovatebot.com/diffs/npm/eslint/10.7.0/10.8.0) |
![age](https://developer.mend.io/api/mc/badges/age/npm/eslint/10.8.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint/10.8.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint/10.7.0/10.8.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint/10.7.0/10.8.0?slim=true)
|
| [playwright](https://playwright.dev)
([source](https://redirect.github.com/microsoft/playwright)) | [`1.61.1`
→ `1.62.1`](https://renovatebot.com/diffs/npm/playwright/1.61.1/1.62.1)
|
![age](https://developer.mend.io/api/mc/badges/age/npm/playwright/1.62.1?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/playwright/1.62.1?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/playwright/1.61.1/1.62.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/playwright/1.61.1/1.62.1?slim=true)
|
| [svelte](https://svelte.dev)
([source](https://redirect.github.com/sveltejs/svelte/tree/HEAD/packages/svelte))
| [`5.56.7` →
`5.56.8`](https://renovatebot.com/diffs/npm/svelte/5.56.7/5.56.8) |
![age](https://developer.mend.io/api/mc/badges/age/npm/svelte/5.56.8?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/svelte/5.56.8?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/svelte/5.56.7/5.56.8?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/svelte/5.56.7/5.56.8?slim=true)
|

---

### Release Notes

<details>
<summary>napi-rs/napi-rs (@&#8203;napi-rs/wasm-runtime)</summary>

###
[`v1.2.1`](https://redirect.github.com/napi-rs/napi-rs/blob/HEAD/wasm-runtime/CHANGELOG.md#121-2026-07-30)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.2.0...@napi-rs/wasm-runtime@1.2.1)

##### Bug Fixes

- **wasm-runtime:** preserve emnapi v1 compatibility
([#&#8203;3426](https://redirect.github.com/napi-rs/napi-rs/issues/3426))
([856326b](https://redirect.github.com/napi-rs/napi-rs/commit/856326b3051028b0d9992227f02167227f46b07b))

###
[`v1.2.0`](https://redirect.github.com/napi-rs/napi-rs/blob/HEAD/wasm-runtime/CHANGELOG.md#120-2026-07-28)

[Compare
Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.6...@napi-rs/wasm-runtime@1.2.0)

##### Features

- **cli:** support non-threaded WASI targets
([#&#8203;3353](https://redirect.github.com/napi-rs/napi-rs/issues/3353))
([b5ecfa3](https://redirect.github.com/napi-rs/napi-rs/commit/b5ecfa3d6a9fcf8e66c59e05dd4027b832b44d61)),
closes
[#&#8203;3352](https://redirect.github.com/napi-rs/napi-rs/issues/3352)
[#&#8203;3400](https://redirect.github.com/napi-rs/napi-rs/issues/3400)
[#&#8203;3405](https://redirect.github.com/napi-rs/napi-rs/issues/3405)

####
[1.1.6](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.5...@napi-rs/wasm-runtime@1.1.6)
(2026-06-24)

**Note:** Version bump only for package
[@&#8203;napi-rs/wasm-runtime](https://redirect.github.com/napi-rs/wasm-runtime)

####
[1.1.5](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.4...@napi-rs/wasm-runtime@1.1.5)
(2026-06-10)

##### Bug Fixes

- **wasm-runtime:** preserve Buffer return values in fs-proxy
([#&#8203;3250](https://redirect.github.com/napi-rs/napi-rs/issues/3250))
([c70f205](https://redirect.github.com/napi-rs/napi-rs/commit/c70f205a071c39acbd792b071bb95b2f5d2f39e8))

####
[1.1.4](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.3...@napi-rs/wasm-runtime@1.1.4)
(2026-04-15)

##### Bug Fixes

- **wasm-runtime:** preserve full bigint values in fs proxy codec
([#&#8203;3233](https://redirect.github.com/napi-rs/napi-rs/issues/3233))
([0cb8eef](https://redirect.github.com/napi-rs/napi-rs/commit/0cb8eefad83938daa6a0f377f2708890d9f4b6bf))

####
[1.1.3](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.2...@napi-rs/wasm-runtime@1.1.3)
(2026-04-08)

**Note:** Version bump only for package
[@&#8203;napi-rs/wasm-runtime](https://redirect.github.com/napi-rs/wasm-runtime)

####
[1.1.2](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.1...@napi-rs/wasm-runtime@1.1.2)
(2026-03-28)

##### Bug Fixes

- ensure emnapi version is synced
([#&#8203;3151](https://redirect.github.com/napi-rs/napi-rs/issues/3151))
([bc728d0](https://redirect.github.com/napi-rs/napi-rs/commit/bc728d0a289360b199d9dbd3b512a975f4d75d5a))

####
[1.1.1](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/wasm-runtime@1.1.0...@napi-rs/wasm-runtime@1.1.1)
(2025-12-30)

**Note:** Version bump only for package
[@&#8203;napi-rs/wasm-runtime](https://redirect.github.com/napi-rs/wasm-runtime)

</details>

<details>
<summary>Rich-Harris/degit (degit)</summary>

###
[`v3.6.5`](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.4...v3.6.5)

[Compare
Source](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.4...v3.6.5)

###
[`v3.6.4`](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.3...v3.6.4)

[Compare
Source](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.3...v3.6.4)

###
[`v3.6.3`](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.2...v3.6.3)

[Compare
Source](https://redirect.github.com/Rich-Harris/degit/compare/v3.6.2...v3.6.3)

</details>

<details>
<summary>eslint/eslint (eslint)</summary>

###
[`v10.8.0`](https://redirect.github.com/eslint/eslint/releases/tag/v10.8.0)

[Compare
Source](https://redirect.github.com/eslint/eslint/compare/v10.7.0...v10.8.0)

#### Features

-
[`2fee9bb`](https://redirect.github.com/eslint/eslint/commit/2fee9bb7416116cbed4d8c8100b1ae713b6356a1)
feat: export `ConfigObject` from `eslint/config`
([#&#8203;21082](https://redirect.github.com/eslint/eslint/issues/21082))
(sethamus)

#### Bug Fixes

-
[`6b8d2f7`](https://redirect.github.com/eslint/eslint/commit/6b8d2f7589b8a7c8b91b8ca2a2ef6d46178760d8)
fix: escape reserved characters in rule id in `html` formatter
([#&#8203;21129](https://redirect.github.com/eslint/eslint/issues/21129))
(Francesco Trotta)
-
[`9091071`](https://redirect.github.com/eslint/eslint/commit/90910715011211a20d011d807d398a7005127f35)
fix: prevent `no-unreachable-loop` crash when all loop types are ignored
([#&#8203;21116](https://redirect.github.com/eslint/eslint/issues/21116))
(Pixel)
-
[`e23fafe`](https://redirect.github.com/eslint/eslint/commit/e23fafe8d4b15355adef6cdebef414c3a2019454)
fix: prefer-object-spread add semicolon when adding parenthesis
([#&#8203;21081](https://redirect.github.com/eslint/eslint/issues/21081))
(synthex-byte)
-
[`20b5ad0`](https://redirect.github.com/eslint/eslint/commit/20b5ad052360a443786a202e94624a3f81846511)
fix: quadratic-time regex in `prefer-template`
([#&#8203;21096](https://redirect.github.com/eslint/eslint/issues/21096))
(Milos Djermanovic)
-
[`8b6f6c0`](https://redirect.github.com/eslint/eslint/commit/8b6f6c0b33411f34485512456d94f221daf7321f)
fix: apply ignore configs to computed methods in class-methods-use-this
([#&#8203;21094](https://redirect.github.com/eslint/eslint/issues/21094))
(Pixel)
-
[`b2c608c`](https://redirect.github.com/eslint/eslint/commit/b2c608c014a396800a24a89343265d0616bee2d8)
fix: NewExpression with parenthesized callee in `preserve-caught-error`
([#&#8203;21083](https://redirect.github.com/eslint/eslint/issues/21083))
(Francesco Trotta)

#### Documentation

-
[`6ddf858`](https://redirect.github.com/eslint/eslint/commit/6ddf8587e5cfeeb7328332601bb76d98b8a198ea)
docs: fix broken Specify Parser Options anchor link
([#&#8203;21106](https://redirect.github.com/eslint/eslint/issues/21106))
(Minsu)
-
[`784dfbe`](https://redirect.github.com/eslint/eslint/commit/784dfbe98f0222426fd200153a5ce2e5624151f0)
docs: Clarify `no-eq-null` description
([#&#8203;21120](https://redirect.github.com/eslint/eslint/issues/21120))
(Park Harin)
-
[`7ec733a`](https://redirect.github.com/eslint/eslint/commit/7ec733a3ba5acb053a73ac656030a3879aa48fda)
docs: Fix typos and grammar in glossary
([#&#8203;21095](https://redirect.github.com/eslint/eslint/issues/21095))
(Marry (Subin Yang))
-
[`92bb13f`](https://redirect.github.com/eslint/eslint/commit/92bb13f13065db72d62454b9830d3ae52db15c76)
docs: replace quake link
([#&#8203;21108](https://redirect.github.com/eslint/eslint/issues/21108))
(Jung Hyeon Jun)
-
[`68eb4a5`](https://redirect.github.com/eslint/eslint/commit/68eb4a57572409dbdcfeb3bc5c92ddbba46bc770)
docs: fix broken Specify Globals anchor links in rule pages
([#&#8203;21103](https://redirect.github.com/eslint/eslint/issues/21103))
(Minsu)
-
[`d28f697`](https://redirect.github.com/eslint/eslint/commit/d28f697e0d32d7446c825fd89c362db028450134)
docs: replace Code Climate CLI links with Qlty CLI links
([#&#8203;21099](https://redirect.github.com/eslint/eslint/issues/21099))
(Jung Hyeon Jun)
-
[`eccc68d`](https://redirect.github.com/eslint/eslint/commit/eccc68d42564e46bc9020d19ec52d2988f3b7bfa)
docs: correct --suppressions-location option description
([#&#8203;21093](https://redirect.github.com/eslint/eslint/issues/21093))
(Ga eun Lee)
-
[`c5963f7`](https://redirect.github.com/eslint/eslint/commit/c5963f74bfa82a5b7ccc0607dcdcc695b8c97a31)
docs: Update README (GitHub Actions Bot)

#### Chores

-
[`4fbf46d`](https://redirect.github.com/eslint/eslint/commit/4fbf46d140120ae838db08cbe49544c985956490)
test: pin `webpack` version to 5.108.4
([#&#8203;21137](https://redirect.github.com/eslint/eslint/issues/21137))
(Francesco Trotta)
-
[`2d063e2`](https://redirect.github.com/eslint/eslint/commit/2d063e224e9bb0ccb0a28c120f7e380a8d62f320)
chore: update HTTP URLs to HTTPS in JSDoc and comments
([#&#8203;21101](https://redirect.github.com/eslint/eslint/issues/21101))
(Bo Hyun Kim)
-
[`eccbe7b`](https://redirect.github.com/eslint/eslint/commit/eccbe7bc9257a6c99880468e452c8dfafbf47f74)
test: add error locations to `no-class-assign`
([#&#8203;21123](https://redirect.github.com/eslint/eslint/issues/21123))
(devoil)
-
[`e7d1e43`](https://redirect.github.com/eslint/eslint/commit/e7d1e4373bf626f8da76d62ce024c28928bc99bc)
ci: bump actions/setup-go from 6 to 7
([#&#8203;21118](https://redirect.github.com/eslint/eslint/issues/21118))
(dependabot\[bot])
-
[`e9d66d0`](https://redirect.github.com/eslint/eslint/commit/e9d66d0cde1a7752c29dee53ab7b62632835a87a)
ci: bump actions/setup-node from 6 to 7
([#&#8203;21119](https://redirect.github.com/eslint/eslint/issues/21119))
(dependabot\[bot])
-
[`ee225b6`](https://redirect.github.com/eslint/eslint/commit/ee225b60c26f0cc5f4d71641888a5bd2ec5626d6)
test: Add error location details to `no-eq-null` rule
([#&#8203;21117](https://redirect.github.com/eslint/eslint/issues/21117))
(Park Harin)
-
[`044a627`](https://redirect.github.com/eslint/eslint/commit/044a627fa3e28ee1410d515acc5378eb4b49f8ba)
chore: update minimatch to ^10.2.5
([#&#8203;21107](https://redirect.github.com/eslint/eslint/issues/21107))
(김채영)
-
[`fb09aa8`](https://redirect.github.com/eslint/eslint/commit/fb09aa8ff09730d3ccf68859e065f99666b52466)
chore: update ecosystem plugins
([#&#8203;21115](https://redirect.github.com/eslint/eslint/issues/21115))
(ESLint Bot)
-
[`5abd878`](https://redirect.github.com/eslint/eslint/commit/5abd878740fe417fd8a910cac3741f2a0317b365)
test: add error locations to `no-proto`
([#&#8203;21114](https://redirect.github.com/eslint/eslint/issues/21114))
(Gihyeon Jeong / 정기현)
-
[`9715887`](https://redirect.github.com/eslint/eslint/commit/9715887ec94a5ff936447d7b680d039fbe2f0541)
test: Add error location details to `no-div-regex`
([#&#8203;21110](https://redirect.github.com/eslint/eslint/issues/21110))
(Park Harin)
-
[`a746ec6`](https://redirect.github.com/eslint/eslint/commit/a746ec6ea4a2249b1eb3f512264da5f4d5f7c886)
test: add error locations to `no-new-wrappers`
([#&#8203;21109](https://redirect.github.com/eslint/eslint/issues/21109))
(Gihyeon Jeong / 정기현)
-
[`8dde645`](https://redirect.github.com/eslint/eslint/commit/8dde64570e240f3ef8af873d59a752db6d8519aa)
test: add error locations to `no-ex-assign`
([#&#8203;21102](https://redirect.github.com/eslint/eslint/issues/21102))
(devoil)
-
[`13ab0ec`](https://redirect.github.com/eslint/eslint/commit/13ab0ec447650c079d8a5e5d67222f79e69c741e)
test: add error locations to `no-label-var`
([#&#8203;21098](https://redirect.github.com/eslint/eslint/issues/21098))
(Gihyeon Jeong / 정기현)
-
[`a99906f`](https://redirect.github.com/eslint/eslint/commit/a99906ffb8342dde03336b5c6372b5658f21d5f2)
test: Add error location details to `no-delete-var` rule
([#&#8203;21105](https://redirect.github.com/eslint/eslint/issues/21105))
(Park Harin)
-
[`c47e8dc`](https://redirect.github.com/eslint/eslint/commit/c47e8dc9bbfba797430db45ae08cb6f7392cfc9d)
chore: add missing backticks to `languages/js/index.js`
([#&#8203;21104](https://redirect.github.com/eslint/eslint/issues/21104))
(beeen)
-
[`0174428`](https://redirect.github.com/eslint/eslint/commit/0174428dd7543d118cf67a823b98dd97032fe68c)
chore: add missing backticks to `translate-cli-options.js`
([#&#8203;21097](https://redirect.github.com/eslint/eslint/issues/21097))
(dongkyu lee)
-
[`3d36589`](https://redirect.github.com/eslint/eslint/commit/3d36589a4917326500f0707bb41745160f387d00)
chore: add missing backticks to `serialization.js`
([#&#8203;21091](https://redirect.github.com/eslint/eslint/issues/21091))
(이규환)
-
[`dcc9312`](https://redirect.github.com/eslint/eslint/commit/dcc9312c1081931de0fe1b555fbb6aa82fe696b9)
test: add error locations to `eqeqeq`
([#&#8203;21090](https://redirect.github.com/eslint/eslint/issues/21090))
(Ga eun Lee)
-
[`2710b18`](https://redirect.github.com/eslint/eslint/commit/2710b182472cd1a95e4aff6186cb2c4fbadb4ee0)
ci: Add explicit permissions to rebuild-docs-sites workflow
([#&#8203;21089](https://redirect.github.com/eslint/eslint/issues/21089))
(Marry (Subin Yang))
-
[`5d2f866`](https://redirect.github.com/eslint/eslint/commit/5d2f8663ee60701e9036b0b0933a12efd9d93269)
chore: update dependency prettier to v3.9.5
([#&#8203;21086](https://redirect.github.com/eslint/eslint/issues/21086))
(renovate\[bot])
-
[`d584e31`](https://redirect.github.com/eslint/eslint/commit/d584e31098f262563b97dc5d5f23a63a187879a0)
chore: fix failing ecosystem test for `eslint-plugin-unicorn`
([#&#8203;21084](https://redirect.github.com/eslint/eslint/issues/21084))
(Francesco Trotta)
-
[`bf3eda0`](https://redirect.github.com/eslint/eslint/commit/bf3eda049bd690f1e9a5a0c4520a6329b3e4ec85)
chore: update ecosystem plugins
([#&#8203;21079](https://redirect.github.com/eslint/eslint/issues/21079))
(ESLint Bot)

</details>

<details>
<summary>microsoft/playwright (playwright)</summary>

###
[`v1.62.1`](https://redirect.github.com/microsoft/playwright/compare/v1.62.0...26a9e470a7b3c7822084b09fb7f13902c5f37b51)

[Compare
Source](https://redirect.github.com/microsoft/playwright/compare/v1.62.0...v1.62.1)

###
[`v1.62.0`](https://redirect.github.com/microsoft/playwright/releases/tag/v1.62.0)

[Compare
Source](https://redirect.github.com/microsoft/playwright/compare/v1.61.1...v1.62.0)

##### 🧱 New component testing model

[Component testing](https://playwright.dev/docs/test-components) moves
to a **stories and galleries** model.
A **story** wraps your component in one specific scenario — hard-coded
props, mock data, providers — and a

**gallery** page that you serve renders stories on demand. The new
[fixtures.mount()](https://playwright.dev/docs/api/class-fixtures#fixtures-mount)
fixture navigates
to the gallery, mounts a story by id, and returns a
[Locator](https://playwright.dev/docs/api/class-locator) scoped to the
story's root element:

```js
test('click should expand', async ({ mount }) => {
  const component = await mount('components/Expandable/Stateful');
  await component.getByRole('button').click();
  await expect(component.getByTestId('expanded')).toHaveValue('true');
});
```

Pass a story type as a template argument to type-check its props, and
use `update(props)` /
`unmount()` on the returned locator to re-render or tear down within a
test.

##### 🛑 Cancel operations with AbortSignal

Most operations and web-first assertions now accept a `signal` option
that takes an

[`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal),
letting you
cancel long-running actions, navigations, waits, and assertions:

```js
const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

await page.getByRole('button', { name: 'Submit' }).click({ signal: controller.signal });
await expect(page.getByText('Done')).toBeVisible({ signal: controller.signal });
```

Providing a signal does not disable the default timeout; pass `timeout:
0` to disable it.

##### 🖼️ WebP screenshots


[expect(page).toHaveScreenshot()](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-screenshot-1)
and
[expect(locator).toHaveScreenshot()](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-screenshot-1)
can now store snapshots in the WebP format — just give the snapshot a
`.webp` name:

```js
// Visual comparisons store the golden snapshot as lossless WebP.
await expect(page).toHaveScreenshot('homepage.webp');

// Standalone screenshots can trade quality for size with lossy WebP.
await page.screenshot({ path: 'homepage.webp', quality: 50 });
```


[page.screenshot()](https://playwright.dev/docs/api/class-page#page-screenshot)
and
[locator.screenshot()](https://playwright.dev/docs/api/class-locator#locator-screenshot)
also accept `webp` as a `type`,
where quality `100` (the default) is lossless and lower values use lossy
compression.

##### 🧩 Custom test filtering with Reporter.preprocess()

New
[reporter.preprocess()](https://playwright.dev/docs/api/class-reporter#reporter-preprocess)
hook runs after the configuration is resolved and before

[reporter.onBegin()](https://playwright.dev/docs/api/class-reporter#reporter-on-begin),
letting a reporter mark individual tests as skipped, excluded,
fixed, or failing through a
[TestRun](https://playwright.dev/docs/api/class-testrun) object:

```js
class MyReporter {
  async preprocess({ config, suite, testRun }) {
    for (const test of suite.allTests()) {
      if (shouldSkip(test))
        testRun.skip(test);
    }
  }
}
```

##### 🔁 Isolated retries

New
[testConfig.retryStrategy](https://playwright.dev/docs/api/class-testconfig#test-config-retry-strategy)
controls when failed tests are retried. The default
`'immediate'` retries as soon as a worker is free; `'isolated'` runs all
retries at the end,
one by one in a single worker, to minimize interference with the rest of
the suite:

```js
// playwright.config.ts
export default defineConfig({
  retries: 2,
  retryStrategy: 'isolated',
});
```

##### New APIs

##### Browser and Context

- New option
[`credentials`](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state-option-credentials)
includes the context's virtual WebAuthn
[Credentials](https://playwright.dev/docs/api/class-credentials)
(passkeys) in the storage state, so they can be persisted and re-seeded
into later contexts.

##### Actions

- New `scroll` option (`"auto"` | `"none"`) on actions to opt out of
Playwright's automatic scroll-into-view.

##### Network

- New
[apiResponse.timing()](https://playwright.dev/docs/api/class-apiresponse#api-response-timing)
returns resource timing information for an API response.

##### Evaluation

- New
[locator.waitForFunction()](https://playwright.dev/docs/api/class-locator#locator-wait-for-function)
waits until a function — called with the matching element — returns a
truthy value.
-
[page.evaluate()](https://playwright.dev/docs/api/class-page#page-evaluate)
and related methods now accept functions as evaluate arguments.
-
[page.addInitScript()](https://playwright.dev/docs/api/class-page#page-add-init-script)
/
[browserContext.addInitScript()](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script)
now accept functions as init-script arguments.

##### Command line & MCP

- Playwright now bundles the [Playwright
MCP](https://playwright.dev/docs/getting-started-mcp) server and
[`playwright-cli`](https://playwright.dev/docs/getting-started-cli),
runnable via `npx playwright mcp` and `npx playwright cli`.

##### Reporters

- The HTML report's **Merge files** grouping — previously only a UI
toggle — can now be enabled from the config with the new `mergeFiles`
reporter option:

```js
// playwright.config.ts
export default defineConfig({
  reporter: [['html', { mergeFiles: true }]],
});
```

##### Announcements

- ⚠️ Debian 11 is not supported anymore.

##### Browser Versions

- Chromium 151.0.7922.34
- Mozilla Firefox 153.0
- WebKit 26.5

This version was also tested against the following stable channels:

- Google Chrome 151
- Microsoft Edge 151

</details>

<details>
<summary>sveltejs/svelte (svelte)</summary>

###
[`v5.56.8`](https://redirect.github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5568)

[Compare
Source](https://redirect.github.com/sveltejs/svelte/compare/svelte@5.56.7...svelte@5.56.8)

##### Patch Changes

- fix: call `onerror` and provide a working `reset` when hydrating a
failed boundary
([#&#8203;18556](https://redirect.github.com/sveltejs/svelte/pull/18556))

- fix: preserve select selection when spread attributes omit value
([#&#8203;18561](https://redirect.github.com/sveltejs/svelte/pull/18561))

</details>

---

### Configuration

📅 **Schedule**: (in timezone Asia/Shanghai)

- Branch creation
  - "before 10am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0NC4zLjIiLCJ1cGRhdGVkSW5WZXIiOiI0NC4zLjIiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-02 20:17:03 +01:00
leaysgur 20f68e74a3 fix(oxlint,oxfmt): respect .gitignore for walk targets and explicit paths (#25133)
Fixes #25107.

The `ignore` crate walker enforces gitignore's "everything under an ignored directory is ignored" rule only by pruning ignored directories during traversal.

And it never filters its walk roots.
So when a walk target (based on cwd or an explicit CLI target path) is already inside a gitignored tree, pruning never happens and a bare directory pattern like `generated` matches nothing file-level. (even though `git check-ignore` says they are ignored)

See also BurntSushi/ripgrep#2595

### Changes

- oxc_config: added `GitignoreChecker`
  - a pre-walk check that replays git's resolution order over the ancestor chain
  - Matchers and `.git`/`.jj` probes are cached per directory
- oxlint / oxfmt: filter all CLI targets through this check before walking

### Behavior changes

- Explicitly passed gitignored files are now skipped (previously always linted/formatted)
- `oxlint --no-ignore` no longer force-lints gitignored targets
  - The same behavior as `--no-ignore` does not disable during walk `.gitignore`

<details>
<summary>FYI: Current our ignore model overview</summary>

Two independent families, AND-ed (a path excluded by any layer is skipped):

| Layer                                        | Source                                                 | oxlint                                                                                         | oxfmt                                                |
| -------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| git-derived (always on, not user-disablable) | nested `.gitignore`, `.git/info/exclude` (during walk) | walker via shared `configure_walk_builder`                                                     | same (shared)                                        |
|                                              | walk roots / explicit CLI targets                      | `GitignoreChecker` (this PR)                                                                   | `GitignoreChecker` (this PR)                         |
| tool-owned                                   | ignore file                                            | `.eslintignore` / `--ignore-path`; disabled by `--no-ignore`                                   | `.prettierignore` / `--ignore-path`; no disable flag |
|                                              | CLI patterns                                           | `--ignore-pattern`; disabled by `--no-ignore`                                                  | `!pattern` path args                                 |
|                                              | config `ignorePatterns`                                | `LintIgnoreMatcher` (already containment-correct via `matched_path_or_any_parents`); always on | scope-local `is_path_ignored` (same); always on      |

NOTE: `ignorePatterns` never had this bug, only the walker-backed gitignore layer did; this PR brings it to the same level.
A `!` negation in `ignorePatterns` cannot re-include gitignored paths (unchanged; the layers are independent).

</details>
2026-08-02 13:11:06 +00:00
Mikhail Baev 5c0fa61ddb fix(linter/eslint/no-warning-comments): unify config structs and remove manual options docs (#25151)
this PR unifies 2 config structs for `eslint/no-warning-comments` and
removes the manually written options doc, related issue: #14743.

Doc after fix:

<img width="919" height="1295" alt="image"
src="https://github.com/user-attachments/assets/dab8f999-fffa-4a77-8d6d-39746cfa3860"
/>
2026-08-01 19:26:30 +01:00
camc314 067da8c4e7 feat(ast)!: store single parameter in TSIndexSignature::parameter (#25154) 2026-07-31 20:35:37 +00:00
camc314 1bdedd11ca feat(ast)!: introduce ExportDeclaration, ExportFromDeclaration (#25095)
## Summary

Split the overloaded Rust `ExportNamedDeclaration` into syntax-specific AST nodes:

- `ExportDeclaration` for exported declarations
- `ExportNamedDeclaration` for local exports
- `ExportFromDeclaration` for re-exports

## Breaking Change

The Rust AST changes from:

```rust
pub struct ExportNamedDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub declaration: Option<Declaration<'a>>,
    pub specifiers: Vec<'a, ExportSpecifier<'a>>,
    pub source: Option<StringLiteral<'a>>,
    pub export_kind: ImportOrExportKind,
    pub with_clause: Option<Box<'a, WithClause<'a>>>,
}

pub enum ModuleDeclaration<'a> {
    // ...
    ExportNamedDeclaration(Box<'a, ExportNamedDeclaration<'a>>),
    // ...
}
```

to:

```rust
pub struct ExportDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub declaration: Declaration<'a>,
}

pub struct ExportNamedDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub specifiers: Vec<'a, ExportSpecifier<'a>>,
    pub export_kind: ImportOrExportKind,
}

pub struct ExportFromDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub specifiers: Vec<'a, ExportSpecifier<'a>>,
    pub source: StringLiteral<'a>,
    pub export_kind: ImportOrExportKind,
    pub with_clause: Option<Box<'a, WithClause<'a>>>,
}

pub enum ModuleDeclaration<'a> {
    // ...
    ExportDeclaration(Box<'a, ExportDeclaration<'a>>),
    ExportNamedDeclaration(Box<'a, ExportNamedDeclaration<'a>>),
    ExportFromDeclaration(Box<'a, ExportFromDeclaration<'a>>),
    // ...
}
```

## Why?

The previous node represented three distinct syntax forms through optional and mutually exclusive fields:

```text
export const value = 1;       -> ExportDeclaration
export { value };             -> ExportNamedDeclaration
export { value } from "mod";  -> ExportFromDeclaration
```

This allowed invalid combinations such as declarations with specifiers or sources, and import attributes without a source. The new nodes make these states unrepresentable and remove repeated `declaration`/`source` branching from consumers.

## How to migrate

Match the syntax-specific `ModuleDeclaration`, `Statement`, or `AstKind` variant instead of inspecting optional fields. Generated builders, visitors, traversal ancestors, and formatter wrappers now expose corresponding APIs for all three nodes.

Common field migrations:

| Previous representation | New representation |
| --- | --- |
| `declaration: Some(declaration)` | `ExportDeclaration { declaration }` |
| `declaration.as_ref()` | `&export.declaration` |
| `source.is_none()` for local exports | Match `ExportNamedDeclaration` |
| `source: Some(source)` | `ExportFromDeclaration { source }` |
| `source.as_ref().unwrap()` | `&export.source` |
| `with_clause` | Only available on `ExportFromDeclaration` |
| Stored `export_kind` on exported declarations | `ExportDeclaration::export_kind()` |
| `ExportNamedDeclaration::boxed_plain_declaration(...)` | `ExportDeclaration::boxed(...)` |
| `ExportNamedDeclaration::boxed(..., None, specifiers, None, ...)` | `ExportNamedDeclaration::boxed(..., specifiers, ...)` |
| `ExportNamedDeclaration::boxed(..., None, specifiers, Some(source), ...)` | `ExportFromDeclaration::boxed(..., specifiers, source, ...)` |

closes https://github.com/oxc-project/backlog/issues/219
2026-07-31 10:25:47 +00:00
camc314 9dc7756729 docs(linter/typescript/no-unnecessary-condition): clarify options (#25110)
## Summary

- document the exact allowConstantLoopConditions behavior, including the four directly allowed literals
- explain how always differs from only-allowed-literals
- add a checkTypePredicates example and regenerate the published configuration schema

Closes #25098
2026-07-30 10:29:51 +00:00
camc314 c917f204a7 feat(ast)!: introduce ArrowFunctionBody enum (#24987)
## Summary

- Introduces `ArrowFunctionBody` to represent concise expression and block bodies explicitly.
- Removes the `ArrowFunctionExpression::expression` boolean.
- Makes invalid arrow body combinations unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct ArrowFunctionExpression<'a> {
    pub expression: bool,
    pub body: Box<'a, FunctionBody<'a>>,
}
```

to:

```rust
pub enum ArrowFunctionBody<'a> {
    FunctionBody(Box<'a, FunctionBody<'a>>) = 64,
    INHERIT(Expression<'a>),
}

pub struct ArrowFunctionExpression<'a> {
    pub body: ArrowFunctionBody<'a>,
}
```

Concise arrow bodies are now stored directly as expressions instead of synthetic `FunctionBody` and `ExpressionStatement` nodes. Block bodies continue to use `FunctionBody`.

## Why?

The previous representation required consumers to maintain this implicit invariant:

```text
expression == true
→ no directives
→ exactly one statement
→ that statement is an ExpressionStatement
```

Invalid combinations could cause panics, skipped work, or fallback behavior. The enum encodes the grammar directly and makes those invalid states unrepresentable.

Concise expressions also now have `ArrowFunctionExpression` as their direct traversal ancestor, without synthetic `FunctionBody` or `ExpressionStatement` nodes.

## How to migrate

Use the arrow body accessors or match on `ArrowFunctionBody` rather than inspecting `expression` and synthetic statements.

Common field migrations:

| Previous representation | New representation |
| --- | --- |
| `arrow.expression` | `arrow.is_expression()` |
| `arrow.get_expression()` | `arrow.get_expression()` |
| `&arrow.body.statements` | `arrow.get_function_body().map(\|body\| &body.statements)` |
| `&mut arrow.body.statements` | `arrow.get_function_body_mut().map(\|body\| &mut body.statements)` |
| Synthetic expression statement in `arrow.body` | Direct `ArrowFunctionBody` expression variant |
| `arrow.body = function_body` | `arrow.body = ArrowFunctionBody::FunctionBody(function_body)` |
| Expression-to-block conversion | Create a `FunctionBody` containing a return statement |
| Block-to-expression conversion | Assign the expression directly to `arrow.body` |
| Builder `(expression, body)` arguments | Pass a single `ArrowFunctionBody` |

closes https://github.com/oxc-project/backlog/issues/211
2026-07-29 13:32:32 +00:00
Faris Tristan Fabian 1fb3d7073c feat(oxlint): show file:line:col in github format annotation messages (#25020)
In GitHub Actions the annotation parameters (`file=`, `line=`, `col=`)
only feed the annotations panel, so the log line itself has no location:

```
Error: Function 'foo' is declared but never used.
```

A teammate hit this in my repo's CI and could not tell which file broke
the lint job. My workaround was re-running oxlint with
`--format=default` on failure, same as csvn in #21574.

This prefixes the message with the location, same layout as [Ruff's
GitHub
renderer](https://github.com/astral-sh/ruff/blob/main/crates/ruff_db/src/diagnostic/render/github.rs):

```
::error file=test.js,line=5,endLine=5,col=1,endColumn=10,title=eslint(no-debugger)::test.js:5:1: `debugger` statement is not allowed
```

The location does get duplicated in the annotation bubble, which is
already anchored to the line. Ruff accepted that trade for a readable
log. The filename is escaped twice on purpose, property rules for
`file=` and data rules for the message text, with a unit test covering
the difference.

Related to #21574 but this does not implement multi-format output.

Updated the unit tests, the CLI snapshots, and the github e2e fixture.
`cargo test -p oxlint` passes (396) and so does the apps/oxlint vitest
suite.

I used Claude Code to help investigate and draft this. I reviewed the
change and ran the tests myself.
2026-07-29 11:56:05 +01:00
Sysix 2ff0ccc594 refactor(oxlint/linter/lsp): extend "ignore fixes" on diagnostic creation (#24958)
This PR refactors the "ignore fixes" code, to be created on diagnostic creation.
With an extra flag in `LintOptions` and `LintRunner`, the caller can include automatically the fix for ignoring the diagnostic.
This flag is only enabled on `oxlint --lsp` side and is not effected by normal CLI usage.

Important changes:
- The "fixes" are working now with `Spans` instead of `Position`
- The calculation of the right span is not depending on the partial source text, instead of the full source text
- new `FixKind` for the `ignore` fixes.

Generated by GPT-5.3-Codex 🤖 Reviewed by myself
2026-07-29 10:17:49 +00:00
Faris Tristan Fabian 2b027111db fix(oxlint): correct --warn flag help text (#25059)
The `-W, --warn` help text says "Deny the rule or category (emit a
warning)", copied from the `-D, --deny` entry.

With that wording, `--warn` and `--deny` look like they do the same
thing in `--help`, but `-W` reports a warning and `-D` reports an error.

Evidence why I think this is not intentional, and instead a typo:

- The other two entries start with their own flag's verb ("Allow the
rule...", "Deny the rule..."). Only the `-W` entry starts with its
sibling's verb, in a sentence identical to the `-D` one except the
parenthetical.
- These flags mirror rustc and clippy's `-A`/`-W`/`-D`, where warn and
deny are two different levels and deny specifically means "make it an
error".
- In this same file, `-W` maps to `AllowWarnDeny::Warn` and `-D` maps to
`AllowWarnDeny::Deny` (`into_tuple`). The internal name for what `-W`
does is warn, not deny.

What changed:

- The `-W` help text is now "Warn on the rule or category (emit a
warning)"
- Updated the two website_linter snapshots, so the generated CLI docs
page picks up the fix

I used Claude Code to help find and fix this.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-29 11:05:18 +01:00
Sysix 7807304a70 refactor(oxlint/linter/lsp): move ignore logic to oxc_linter (#24951)
The start for fixing https://github.com/oxc-project/oxc/issues/24570

At the moment the "ignore this line" / "ignore this section" logic is living inside `oxlint/lsp` directory.
Because we now need to know the AST parents, the code should live inside `oxc_linter`.

This PR refactors the 2 methods into a new `oxc_linter` file.
2026-07-29 09:40:58 +00:00