Async generator methods must return async iterators, but the method
visitor sent them through async-to-generator and changed their return
value to a Promise.
Match the other function paths and Babel by leaving these methods for
the async-generator-functions transform.
Async method bodies moved into generated generator functions lose their
receiver when arrow-function lowering is enabled at the same time.
Track the two async transforms separately so ordinary async methods and
async generator methods capture `this` only when their owning transform
actually moves the body.
## Summary
- recognize references returned from arrow functions stored on properties
- treat arrow functions passed to calls and constructors as independently callable
- preserve self-use detection for immediately invoked arrow functions
- add regression coverage based on VSC-NeuroPilot/neuropilot
# Oxlint
### 🚀 Features
- 60b945d linter/nextjs/no-typos: Implement suggestion (#26091) (Mikhail Baev)
### 🐛 Bug Fixes
- 33ac4b0 linter/lsp: Prevent tsgolint from holding onto processes (#25570) (Adrian Schaedle)
- baf4b1e linter/import/no-empty-named-blocks: Make empty value import removal a suggestion (#26155) (camc314)
- 77cfaec linter/eslint/object-shorthand: Preserve `__proto__` semantics (#26154) (camc314)
- fa3c082 linter/unicorn/prefer-set-size: Ignore shadowed `Set` constructors (#26153) (camc314)
- e412cf2 linter: Clamp invalid JS plugin locations (#26144) (camc314)
- d86c113 linter: Normalize reversed JS plugin locations (#26138) (camc314)
- 73c09b2 linter/eslint/no-use-before-define: Run on JS, JSX files (#26114) (camc314)
- 03ef0f2 linter/unicorn/no-useless-spread: Treat typed arrays as a distinct value hint (#26067) (Aadharsh Pannirselvam)
- bd15905 linter/react/no-unstable-nested-components: Check nested component object property names (#26101) (camc314)
- 3910e2b linter/eslint/no-unassigned-vars: Skip Svelte and Vue files (#26042) (Hamody We)
- 047f7ca linter/plugins: Fix interaction between JS plugins and Vue rules (#26080) (overlookmotel)
- 8531b9b linter/plugins: Alter method for obtaining mutable `Program` when sending AST to JS plugins (#26077) (overlookmotel)
- dc464ff linter/unicorn/prefer-math-min-max: Avoid unsafe autofix (#26060) (camc314)
### 📚 Documentation
- 464ddd1 linter: Support a shared short description for jest/vitest rules (#26186) (connorshea)
- 9db5ad3 linter: Add short description to `vue/no-dupe-keys` (#26183) (connorshea)
- db66f58 linter: Correct export/import mismatch in bar and foo example (#25927) (billychannnnnn)
- d5be037 linter/typescript/switch-exhaustiveness-check: Clarify default case comment pattern (#26100) (camc314)
# Oxfmt
### 🚀 Features
- 1fb37b6 formatter/css: Format declaration-shaped raw-prelude rules (postcss nested config blocks) (#26194) (leaysgur)
### 🐛 Bug Fixes
- 57e8e22 formatter_css: Space a folded sign after a call in Less operations (#26134) (leaysgur)
- e4298fb formatter/jsdoc: Follow CommonMark for interrupting lists and guard wrapping from creating them (#26098) (leaysgur)
- a1e21c2 formatter: Apply head body policy everywhere (#26074) (leaysgur)
- 676b7e1 formatter: Keep comments in their for-head slot, before an empty-statement body and do-while (#26073) (leaysgur)
- 3bbcea6 formatter: Keep comments between a head and its body out of the braces across all constructs (#26071) (leaysgur)
- 57537a2 formatter: Place comments between a head and open paren (#26058) (leaysgur)
- e2d53e7 formatter: Place comments between a statement head and its body (#26043) (leaysgur)
- 662556c formatter: Print the idempotent placement for comments in dropped parens (#26041) (leaysgur)
- faf11d9 formatter_yaml: Keep trailing whitespace in block scalars (#26072) (leaysgur)
### ⚡ Performance
- ac4785a diagnostics: Use fixed ANSI styles (#26130) (Boshen)
### 📚 Documentation
- b879608 formatter,formatter_graphql,formatter_yaml: Annotate own-line comment inlining as known policy violation (#26131) (leaysgur)
- 181953b oxfmt,formatter_core,formatter,formatter_yaml,formatter_css,formatter_graphql: Extract `DIVERGENCES.md` out from `AGENTS.md` (#26121) (leaysgur)
- a67cb9d formatter,formatter_core: Document comment moving policy (#26075) (leaysgur)
- fc175b0 formatter_core: Clarify idempotency test infra (#26069) (leaysgur)
```js
// input (semi: true)
// oxfmt-ignore
const a = x
// before
// oxfmt-ignore
const a = x
// after
// oxfmt-ignore
const a = x;
```
```js
// input (semi: false)
let a = 1;
// oxfmt-ignore
[b].sort();
// before
let a = 1
// oxfmt-ignore
[b].sort(); // <- ASI failure
// after
let a = 1
// oxfmt-ignore
;[b].sort() // <- OK
```
Fixes#26165
- `side_effect` is predefined selector, it cannot be used as custom group name
- respect custom group which uses `side_effect` as side-effect-import correctly
- fix metadata detection bug
Fixes#26202
---
The trigger for this issue is the presence of a suppressed typecast.
```js
// oxfmt-ignore
const cast = /** c */ (x)
```
Because a comment had already been printed in a subsequent typecast check without going through the casting mechanism, it failed to find its own cast comment.
Refine comment position preserving rules, for statements and binary casts. Fixes idempotency issues for several sites.
---
Trailing is the same-line run only.
A newline-preceded comment is never trailing. It defers to the next node's leading pass, staying own-line with its blank lines.
Comments around `as`/`satisfies` keep their source side and line-start side, within the grammar-defined slots (line terminator is not allowed between `as` and before).
#26099 resolves parameter and signature references before visiting the function body, so they cannot bind to declarations that only exist inside the body. That early pass only ran when the function had parameters or a return type. Type parameter constraints and a `this` parameter type are visited before that check, so a function whose signature has references only in those positions skipped the pass. Its references then resolved after the body was visited and bound to body-local types.
## Example
```ts
type U = number;
function f<T extends U>() {
type U = string;
return null! as T;
}
```
Before this change, `U` in the constraint resolved to the body-local `type U = string`. TypeScript scopes local types to the function body, so it must resolve to the outer alias. The same happened for `function f(this: U) { type U = string }` and for arrow functions with type parameters.
The early pass now runs for every function and arrow function. It returns immediately when the signature produced no references, so the `has_parameter() || return_type.is_some()` condition, which approximated that check, is no longer needed.
Also adds a regression test for the compaction in `resolve_references_for_current_scope`: a resolved reference followed by an unresolved one must neither drop nor duplicate entries, and no existing test covered that ordering.
Verified with the semantic and transformer unit tests, Clippy, `cargo coverage -- semantic`, and transform conformance; no snapshots changed.
AI assistance: Claude Code was used to investigate, implement, and verify this change. The contributor is responsible for the submitted change.
> Adjusts the Oxc language server startup to scale Tower LSP request
handling concurrency based on the machine’s available parallelism,
aiming to improve throughput on higher-core systems without fully
saturating the host.
The saturation is from personal feeling and did not get benchmarked.
---------
Signed-off-by: Alexander S. <sysix@sysix-coding.de>
Signed-off-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
AI Disclosure: Generated with Claude Code, Fable 5. Reviewed by me. This sets up the short_description to be added to the various rules shared between jest and vitest.
`declare_oxc_lint!` now accepts a path to a shared constant for `short_description`, mirroring how `docs = SHARED_DOCUMENTATION` works. Like `documentation`, the handling is gated behind the `ruledocs` feature.
A shared short description is added to `expect-expect`, `no-disabled-tests`, and `valid-expect`.
Rules where the wording legitimately differs per framework (e.g. `no-alias-methods`) can keep per-plugin string literals, which remain supported. Or we can opt to add the raw strings on every one of these rules and drop the shared short desc idea, I don't have a strong preference either way.
Once we decide and merge this, we can add the short descriptions for all the other remaining shared rules. We'll also need to split up the two remaining rules that are problematic (https://github.com/oxc-project/oxc/issues/23186), then we can expose the `short_description` field in the JSON rules formatter for use on the website's rules list view.
The example incorrectly used a named import for a default export.
Changed `export default function bar()` to `export function bar()` to
align with the `import { bar }` statement.
Signed-off-by: billychannnnnn <billychannnnnn@foxmail.com>
Attempts to address:
- oxc-project/oxc#17070
- oxc-project/oxc#21402,
- oxc-project/oxc-vscode#264
I recently started enabling the type-aware rules as part of my editor's
linter (zed). I quickly started hitting resource exhaustion limits on
macOS. Listing processes showed thousands of zombie tsgolint entries
with oxlint as the parent.
Doing a little digging, I found the above issues listing similar
resource exhaustion. oxc-project/oxc#24670 attempted to solve this issue
by calling `child.kill` on the tsgolint process, but this is not enough
on macOS. From [the `Child`
docs](https://doc.rust-lang.org/std/process/struct.Child.html):
> On some systems, calling wait or similar is necessary for the OS to
release resources. A process that terminated but has not been waited on
is still around as a “zombie”. Leaving too many zombies around may
exhaust global resources (for example process IDs).
>
> The standard library does not automatically wait on child processes
(not even if the Child is dropped), it is up to the application
developer to do so. As a consequence, dropping Child handles without
waiting on them first is not recommended in long-running applications.
This change follows this advice and adds a `wait` after the existing
`kill`.
improve negation of 1 and 0 to 0 and 1 in boolean context
previously only `!!0` and `!!1` could be inlined to `1` and `0` and now
`!0` and `!1` will also be inlined,
this change allows us to flip numeric values in-place instead of having
to first to create unary and remove it afterwards
```
!0 was converted before to 1 in some cases:
1. negate 1
2. !1 - add unary
3. 0 - remove unary
now:
1. negate 1
2. 0
```
expands leading assignment inlining into variable declarations for
`return`, `switch`, `if`, `throw`, `for`, `for in`, `for of`
```js
// return
var a; return a = b(), c;
var a = b(); return c;
// throw
var a; throw a = b(), c;
var a = b(); throw c;
// switch
var a; switch (a = b(), c) {}
var a = b(); switch (c) {}
// for
var a; for (a = b(), c; ;) d;
var a = b(); for (c; ;) d;
// if
var a; if (a = b(), c) d;
var a = b(); if (c) d;
```
fixes#25949
## Summary
- follow chained labels to their terminal statement when validating labeled `continue`
- reject label chains that terminate in a non-iteration statement
- add a focused negative coverage fixture and update the parser coverage snapshot
Expand negation to include move of `!` operator to inner children
```js
!(a == b && c == 3 && a)
a != b || c != 3 | !a
```
delta calculation is dependent on boolean context
- if we expect truthly/falsly value negation of `!a` will be converted
to `a` (-1 delta)
- in ambiguous context `!a` will be converted to `!!a` (+1 delta)
```js
a = !!(!b || !c);
// ^ ^ ^ -3
a = !(b || c)
// ^ ^ ^ +1 - if we could detect if parens can be removed it would be -1
// ^^^ ^^ -1 - if we would know that we can remove parens
```
## Summary
- classify removal of empty value imports as a suggestion because deleting them can drop module evaluation side effects
- keep removal of empty type-only imports as an automatic fix
- preserve the rule’s existing deletion behavior
## Summary
- verify that new Set constructors resolve to the intrinsic global before reporting or fixing
- preserve detection for direct constructions and const-bound Set instances
- add regression coverage for a locally shadowed constructor that returns an array
## Summary
- Apply `ToString(undefined)` when `String.prototype.indexOf` or `lastIndexOf` is called without a search argument.
- Keep folding strings without the resulting `"undefined"` substring to `-1`.
- Add focused helper and minifier regression coverage.
```js
"undefined".indexOf(); // 0
"undefined".lastIndexOf(); // 0
"abcdef".indexOf(); // -1
"abcdef".lastIndexOf(); // -1
```
## Summary
`StringToNumber` previously removed ECMAScript whitespace only from the beginning of a string. Legal trailing whitespace therefore remained in the value passed to Rust’s numeric parsers, causing valid JavaScript numeric strings to produce `NaN`.
For example:
```js
"1 " * 2 // incorrectly folded to NaN
"0x10 " - 0 // incorrectly folded to NaN
```
This changes `trim_start_matches` to `trim_matches`, applying the existing ECMAScript whitespace predicate to both ends of the string.
After the change:
```js
"1 " * 2 // folds to 2
"0x10 " - 0 // folds to 16
```
The custom predicate remains important because ECMAScript and Rust define whitespace differently. Tests cover every ECMAScript whitespace character and verify that non-ECMAScript whitespace such as U+0085 is not accepted.
## Summary
Fix ES2018 object-rest lowering when one variable declaration contains multiple object-rest declarators.
### Before
Given:
```js
let { a, ...r } = x, { b, ...s } = y;
```
Oxc produced:
```js
let { a } = x, { b } = y,
s = babelHelpers.objectWithoutProperties(y, ["b"]),
{} = null;
```
The `r` binding was lost.
Each replacement was recorded using its original declarator index. Replacing the first declarator expanded it into multiple entries, shifting the second declarator. The subsequent replacement still used the old index and overwrote part of the first expansion.
### After
Oxc now produces:
```js
let { a } = x,
r = babelHelpers.objectWithoutProperties(x, ["a"]),
{ b } = y,
s = babelHelpers.objectWithoutProperties(y, ["b"]);
```
Queued replacements are applied from right to left. Replacing a later declarator cannot change the index of an earlier one, so every recorded index remains valid.
Regression coverage includes multiple `let`, `const`, and `var` declarators.
## Summary
Fix a parser panic when a string-named export decodes to a literal `\u` sequence.
Lone-surrogate validation now uses the metadata captured by the lexer instead of rescanning decoded string contents. This also prevents false diagnostics for names such as `"\\uD800"`.
## Before:
```
thread 'main' (10990283) panicked at crates/oxc_ast/src/ast_impl/literal.rs:89:42:
end byte index 4 is out of bounds for string of length 0
stack backtrace:
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: core::str::slice_error_fail_rt
3: core::str::slice_error_fail
4: <core::ops::range::RangeTo<usize> as core::slice::index::SliceIndex<str>>::index
5: <str as core::ops::index::Index<core::ops::range::RangeTo<usize>>>::index
6: <oxc_ast::ast::literal::StringLiteral>::is_string_well_formed_unicode
7: <oxc_parser::ParserImpl<oxc_parser::config::NoTokensParserConfig>>::parse_module_export_name
```
Follow-up to #26138, closes#25867.
External plugins can report a node range past the source text. The default formatter rejects that invalid span and previously panicked. Clamp external diagnostic offsets to the source's UTF-16 length before normalizing reversed ranges.
The end-to-end fixture now covers:
- reversed locations
- negative loc values
- negative node ranges
- an out-of-range node range in Unicode source text
Function parameters, TypeScript signatures, and function bodies currently share one Oxc semantic scope. A reference in a parameter can therefore bind to a declaration inside the body, even though JavaScript does not make body declarations visible there. Rolldown may then treat a global as local, skip a configured define, or remove live code.
## Example
```js
export function load(value = typeof window !== "undefined") {
var window;
return value;
}
```
The body `var window` is not visible to the default value. Before this change, Oxc resolved `window` in the default value to that body declaration. It should remain a global reference.
The workaround resolves parameter and signature references before visiting the body, while only parameter bindings are present. If a reference is still unresolved, its `lookup_scope_id` advances to the function's parent. Final resolution resumes there, so declarations added while visiting the body are skipped. References created in the body still begin at the function scope and resolve normally.
```text
Before: parameter reference -> completed function scope -> body binding (wrong)
After: parameter reference -> parameter bindings -> parent scopes
body reference -> completed function scope -> parent scopes
```
Parameter decorators already begin in the enclosing class scope, so they are left for normal final resolution. This emulates separate parameter and body scopes until https://github.com/oxc-project/backlog/issues/176 is implemented.
The corrected semantic data also exposes the transformer case covered by #26105.
Verified with semantic and transformer-plugin tests, Clippy, coverage, allocation snapshots, and diff checks. `just ready` completed workspace checks but stopped at a codegen snapshot recorded with Node.js v26.5.0; the local version is v25.8.1.
Closes#20427Closes#22783
Downstream reports:
- https://github.com/rolldown/rolldown/issues/10722
- https://github.com/rolldown/rolldown/issues/10779
AI assistance: OpenAI Codex was used to investigate, implement, and test this change. The contributor is responsible for the submitted change.
- Extract "known divergences" section from `AGENTS.md` to explicit `DIVERGENCES.md` with code example
- Update all anchors
- Add missing pinned fixtures
Replace dynamic diagnostic styles with fixed ANSI prefixes, reducing `oxfmt` and `oxlint` release text size.
AI assistance: Codex was used. The contributor remains responsible for this change.
Fixes#26113.
The rule was moved from the TypeScript plugin to the ESLint plugin in 79fc6d7e9f, but its TypeScript-only execution gate was retained. As a result, eslint/no-use-before-define never ran for JavaScript or JSX files.Remove the gate and add a JavaScript regression test for a temporal-dead-zone reference.
Match upstream's catchable missing-identifier invariant in `PruneNonEscapingScopes`, preventing a later misleading `PreserveManualMemo` diagnostic. Adds the original-shaped named function expression regression.
AI-assisted and reviewed by the contributor.
Closes#25868.
## What
`unicorn/no-useless-spread` inferred "returns a plain array" from the
method name
alone, so `[...new Uint8Array(buf).slice(0, 12)]` was reported and
`--fix` removed
the spread. That spread is the conversion to `number[]`, so the
following `.map()`
kept running as `TypedArray.prototype.map` and coerced every callback
result back
into the element type. #25868 has the measurements and the seven
affected methods.
## Approach
Rather than guard the one call site, this adds a
`ValueHint::NewTypedArray` variant
beside the existing `NewArray` / `NewObject` / `NewIterable`.
The reason is chains. A one-off receiver check handles
`new Uint8Array(buf).slice(0)` and misses `new
Uint8Array(buf).slice(0).map(f)`,
where the receiver is itself a call. Carrying the typed-array fact
through
`const_eval` means the existing recursion does that work, and
`[...new Uint8Array(buf).slice(0, 12).map(f)]` passes with no extra
code.
`is_array()` stays false for the new variant, so nothing is reported.
Three places
feed it:
- `NewExpression::const_eval`, via the existing `is_new_typed_array`
- `is_typed_array_from`, for `Uint8Array.from(x)`
- `is_typed_array_method`, for a clone method whose receiver already
evaluates to
`NewTypedArray`
## One deviation from the fix sketched in the issue
The issue proposed splitting the typed-array names out of
`is_array_from`. That
would regress an existing `fail` vector, because `is_array_from` has a
second
consumer doing a different job.
`mod.rs:362` uses it to decide whether a spread passed *into* `from` is
useless,
which it is for every constructor in that list, since they all accept an
iterable.
`BigUint64Array.from([...iterable])` is in the `fail` vectors today and
should stay
there. Only the *result*-type inference in `const_eval` was wrong.
So `is_array_from` keeps its full list and gains a doc comment saying
which of the
two questions it answers, and `const_eval` drops its call to it.
`Array.from(x)`
keeps evaluating to `NewArray` through `is_array_factory`, which already
matches
`Array.{from,of}` with any argument count.
## Behaviour
| expression | before | after |
|---|---|---|
| `[...new Uint8Array(b).slice(0, 12)]` | reported, fixed | passes |
| `[...new Uint8Array(b).map(f)]` | reported, fixed | passes |
| `[...new Uint8Array(b).filter(f)]` | reported, fixed | passes |
| `[...new Uint8Array(b).toReversed()]` | reported, fixed | passes |
| `[...new Uint8Array(b).toSorted()]` | reported, fixed | passes |
| `[...new Uint8Array(b).with(0, 1)]` | reported, fixed | passes |
| `[...Uint8Array.from([1, 2, 3])]` | reported, fixed | passes |
| `[...new Uint8Array(b).slice(0).map(f)]` | reported, fixed | passes |
| `[...[1, 2, 3].slice(0, 2)]` | reported, fixed | unchanged |
| `[...Array.from(foo)]` | reported, fixed | unchanged |
| `BigUint64Array.from([...iterable])` | reported, fixed | unchanged |
| `[...new Uint8Array([1, 2, 3])]` (#7936) | passes | unchanged |
## Tests
19 `pass` vectors added under a `#25868` comment next to the existing
`#7936` pair:
the seven methods from the issue, a chained case, and all 11 typed array
constructors. The `fail` vectors are untouched, which is the regression
check that
plain arrays and `Array.from` still report.
## Known limits, both deliberate
**Variable receivers still report.** `[...view.slice(0, 12)]` where
`view` is a
`Uint8Array` needs type information. #24107 asked for that general case
and was
closed on the grounds that upstream's `isArray` is type-aware and this
rule is not
moving to tsgolint. This PR stays inside the syntactic subset, where the
receiver is
literally `new <TypedArray>(...)` or `<TypedArray>.from(...)`.
**A shadowed constructor is a false negative.** With `class Uint8Array`
in scope,
`[...new Uint8Array().slice(0)]` is a genuine useless spread and is no
longer
reported. This matches how `is_new_typed_array` and every other name
matcher in the
file already behave, so it introduces no new inconsistency. Happy to add
a
global-reference check if you'd rather have it here.
## Verification
```
cargo check -p oxc_linter clean
cargo test -p oxc_linter no_useless_spread rules::unicorn::no_useless_spread::test ... ok
```
The snapshot is unchanged, so no existing `fail` case flipped.
Control, to show the tests have teeth: reverting `const_eval.rs` to its
previous
state while keeping the new vectors fails the test with 19 diagnostics,
one per new
vector.
## AI usage disclosure
Per the AI usage policy: this patch was drafted with Claude Code,
including the
`ValueHint::NewTypedArray` approach and the test vectors. Everything in
the
verification section was run locally against this branch rather than
asserted, and
the control run is there because a test that passes with and without the
fix would
prove nothing. Happy to take the change in a different direction if
you'd prefer the
narrower receiver guard described in #25868.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>