Commit Graph

2615 Commits

Author SHA1 Message Date
leaysgur 36d5e13fc2 refactor(formatter_core,formatter,oxfmt): rename embedded meta to child_context (#25428)
`parent_context` <-> `child_context` is more intuitive.
2026-08-09 14:59:36 +00:00
leaysgur 6eae5c9803 docs(formatter,oxfmt): record embed-layer decisions in place (#25422)
Just notes.
2026-08-09 14:49:15 +00:00
leaysgur ab52a593f3 fix(formatter): format xxx-in-js inside JSDoc js fence (#25414)
Such as CSS-in-JS-in-JSDoc_Markdown-in-JS. 😇
2026-08-09 14:49:15 +00:00
leaysgur 1a2c64a356 fix(formatter,oxfmt): apply effective print width for JSDoc fence (#25413)
As JSDoc is always contained within the ` * ` fenced comments, the `printWidth` option should not have been used as is.

The dedicated `jsdocPrintWidth` option has not yet been implemented. #24633
2026-08-09 14:49:14 +00:00
leaysgur ce29325cfe refactor(oxfmt): consolidate embedded routing and service profiles (#25396)
Consolidated the criteria for determining which languages are handled by?

- natively by Rust
- passed to Prettier

when formatting embedded code, as these were previously scattered across various places.
2026-08-09 14:32:11 +00:00
leaysgur 8213e72f37 refactor(formatter,oxfmt): flatten embedded dispatch contract (#25391)
Due to historical reasons, the IR was passed as `[Doc, (Doc, ...Doc)]` only when processing `gql-in-js`, whilst for other cases such as `css-in-js` and `html-in-js`, it was always `[Doc]`.

Now, every result is single `Doc`.
2026-08-09 14:32:11 +00:00
leaysgur 465b723d04 refactor(formatter,oxfmt): unify host services as SessionServices on FormatSession (#25388)
- dispatcher
- string_embedder
- tailwind_sorter

Group these 3 together as `SessionServices`, make them available to `FormatSession`, and pass them around.
2026-08-09 14:32:10 +00:00
leaysgur aa1334f1c8 refactor(oxfmt): use dispatcher for non-napi xxx-in-js (#25387)
Although everything was ready in #25331, it was still behind the `napi` gate, so removed it.
2026-08-09 14:32:10 +00:00
leaysgur 2eaede9b0b fix(formatter_core): unify leading-BOM handlings (#25340)
Follow up on #25333.
2026-08-09 14:32:09 +00:00
leaysgur fd02a8956e feat(oxfmt): dispatch yaml-in-css(frontmatter) to oxc_formatter_yaml (#25336)
Using the `Dispatcher` set up so far, detect the YAML front matter and process it using `oxc_formatter_yaml`.
2026-08-09 14:15:05 +00:00
leaysgur 567089f3c5 refactor(oxfmt): route JSDoc fence through the dispatcher (#25334)
Markdown code fences can also be written in JSDoc, and other languages can be formatted as inline code within them.
2026-08-09 13:54:12 +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
leaysgur 52b146f025 refactor(formatter_core): share split_bom helper (#25333)
In order to standardise the handling of front matter, it was also necessary to standardise the handling of the BOM, which may appear before the FM.
2026-08-09 13:06:35 +00:00
leaysgur b3ad060cfd refactor(formatter): migrate embedded entries to FormatSession (#25332)
Apply `FormatSession` everywhere introduced in #25329.
2026-08-09 13:06:35 +00:00
leaysgur fb8affe04c refactor(oxfmt): make native dispatch registry available without napi (#25331)
Previously, as Prettier was required for embedded formatting, so embedding was only supported in `napi` builds.

However, as the number of rewritten Rust components has increased recently, we have enabled embedded formatting in `pure` builds as well, thereby reducing the scope of the `napi` gate.
2026-08-09 13:06:35 +00:00
leaysgur 11e0645dc9 refactor(oxfmt,formatter_core): add DispatchOutcome with recursion limit (#25330)
In the embedding format, issuing a `DispatchRequest` returns a `DispatchOutcome`.

Additionally, by managing the nesting depth, made it possible to limit the number of recursion.
2026-08-09 13:06:34 +00:00
leaysgur 8e779c946b refactor(formatter_core): finalize documents once at print time (#25328)
Call `propagate_expand()` inside `Formatted`, here is the only place to finalize IRs.
2026-08-09 12:33:39 +00: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
leaysgur c9d1a5b603 perf(oxfmt): spawn tinypool lazily (#25298)
Partially fixes #25196

- Before: create `child_process` worker pool on startup
  - Spawned even when nothing needs JS side delegation (e.g. JS/TS-only projects)
  - For LSP, the idle workers stayed resident for the whole session
- After: create the pool on the first format that actually delegates to JS side
  - Runs that never reach JS side spawn zero workers
  - As more formatters are rewritten in Rust, the share of such runs keeps growing

Trade-off: the first JS-delegated format in an LSP session now pays the spawn cost.
But that is what every one-shot CLI run already pays, so it is far from slow, and subsequent formats are unchanged.
2026-08-05 04:25:24 +00:00
leaysgur ab12665f34 feat(formatter_core): add hardlineWithoutBreakParent equivalent IR (#25273)
builder: `hard_line_break().without_expand_parent()`
2026-08-04 04:31:56 +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
leaysgur eaa7c692d2 docs(formatter_core): extract FORMATTER_POLICY (#25233) 2026-08-03 05:56:42 +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
leaysgur ee47f21ce4 refactor(oxfmt): move Doc-to-IR postprocess() under prettier_compat (#25141)
Just relocated.
2026-07-31 08:20:38 +00:00
leaysgur acfbf46767 refactor(formatter_core): add CoreFormatOptions and FormatOptions::apply_core (#25138)
These common, core format options:

- LineEnding
- LineWidth
- IndentWidth
- IndentStyle

should be defined in core, and to be shared.
2026-07-31 08:11:23 +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
leaysgur a9e76e9e1f fix(oxfmt): rework tsx-in-vue support (#25106)
Follow-up to #25063.

tsx-in-vue was supported in #25063 with a parse-retry (ts fails → retry as tsx), chosen to keep the common ts path single-parse.

However, this leaves a hole: a `lang="tsx"` block without JSX parses fine as plain ts, so the retry never fires and the lone generic comma is removed:

```vue
<script setup lang="tsx">
const identity = <T,>(x: T) => x; // → <T>(x: T) => x, breaks as TS17008
</script>
```

Since parse failure fundamentally cannot detect this, the only fix is to scan the `lang` declaration itself.

The JS host now scans the SFC once per file and passes `_oxfmtVueScriptLang: "tsx"` down to `textToDoc()`. This makes the retry obsolete, so it is removed.

This PR also settles the comma policy: keep it only where it is load-bearing.

- JSX grammar (`<T>` would be a JSX opener)
- and `.mts/.cts` (TS7060, #18928)

and remove it everywhere else, keyed on the grammar, regardless of context.

Deliberate divergence: Prettier keys on filepath and keeps the comma in ts-in-vue/svelte (while its own ts-in-md embed uses `dummy.ts` as `filepath` and removes it). Plain-TS sources now behave identically wherever they live.
2026-07-30 05:27:07 +00:00