Commit Graph

915 Commits

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

AI-assisted: Codex was used to implement and validate this change.
2026-08-16 12:39:27 +00:00
Boshen 8c4552dfa6 feat(linter)!: split react/react-compiler into per-category rules (#25500)
Splits the experimental `react/react-compiler` rule into 24 category-specific rules matching `eslint-plugin-react-hooks` v7.

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

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

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

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

Closes #25626
2026-08-14 13:16:16 +00:00
Mikhail Baev 285820eed6 fix(linter/no-large-snapshots): precompile and document allowed snapshot matchers (#25611)
issue #23594

- Move regex compilation to configuration parsing to avoid recompiling
patterns for every snapshot entry
- Document the matching behavior of allowedSnapshots (first interpret as
regex, if not valid match as string)
- Fix a small typo where both example sections were labeled “incorrect"
- Replace manual config parsing with DefaultRuleConfig
2026-08-14 10:47:51 +01:00
camc314 0c68b7ff8b fix(estree): emit decorators on FormalParameterRest (#25582)
This PR adds support for emitting `decorators` when serializing an ast to ESTree (both with/without raw transfer).

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

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

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

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

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

Closes #25556
2026-08-12 16:44:01 +00:00
Boshen 4df5835fc3 fix(linter): allow capitalized built-in calls (#25516)
Fixes #24405.

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

Validated with `just ready`.

AI-assisted: Codex was used to investigate and implement this change.
2026-08-12 03:51:52 +00:00
oxc-guard c42d6397ea release(apps): oxlint v1.78.0 && oxfmt v0.63.0 (#25473)
# Oxlint
### 💥 BREAKING CHANGES

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

### 🚀 Features

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

### 🐛 Bug Fixes

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

### ⚡ Performance

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

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

### 🚀 Features

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

### 🐛 Bug Fixes

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

### ⚡ Performance

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

### 📚 Documentation

- 6eae5c9 formatter,oxfmt: Record embed-layer decisions in place (#25422) (leaysgur)
- 51224a7 formatter_yaml: Pin EOF blank lines divergence (#25269) (leaysgur)
2026-08-10 10:03:34 +00:00
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
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
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
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
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
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
Mikhail Baev 5c0fa61ddb fix(linter/eslint/no-warning-comments): unify config structs and remove manual options docs (#25151)
this PR unifies 2 config structs for `eslint/no-warning-comments` and
removes the manually written options doc, related issue: #14743.

Doc after fix:

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

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

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

## Breaking Change

The Rust AST changes from:

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

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

to:

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

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

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

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

## Why?

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

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

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

## How to migrate

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

Common field migrations:

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

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

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

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

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

## Breaking Change

The Rust AST changes from:

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

to:

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

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

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

## Why?

The previous representation required consumers to maintain this implicit invariant:

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

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

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

## How to migrate

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

Common field migrations:

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

closes https://github.com/oxc-project/backlog/issues/211
2026-07-29 13:32:32 +00:00
oxc-guard 65fe65d842 release(apps): oxlint v1.76.0 && oxfmt v0.61.0 (#24976)
# Oxlint
### 🚀 Features

- 8d31dfa linter: Verify eslint/no-restricted-globals config schema (#24598) (vigneshwar)
- 7069621 linter: Verify jest/vitest prefer-lowercase-title config schema (#24724) (Bartok)
- 016cf2a linter/oxc: Add bad-match-all-arg rule (#24900) (camc314)
- cdc941e linter/n: Implement `exports-style` rule (#24087) (Mikhail Baev)
- 1ad6f6c linter/eslint: Implement `id-denylist` rule (#24632) (Mikhail Baev)
- 678bbb5 linter/typescript-eslint: Implement suggestion for `class-literal-property-style` rule (#24766) (Mikhail Baev)

### 🐛 Bug Fixes

- a5f3a0e linter: Mark missing suggestions as pending (#24956) (Mikhail Baev)
- a065946 oxlint: Absolutize explicit CLI paths even with --no-ignore (#24646) (Bez Sahara)
- 17688bb linter/jsx-a11y/interactive-supports-focus: Match eslint behavior for custom components (#24780) (Cole Ellison)
- 0aef19e linter/typescript/no-confusing-non-null-assertion: Report `in` and `instanceof` (#24825) (Mikhail Baev)
- 361dce9 linter: Unicorn/no-array-reverse and no-array-sort replacements should be suggestions (#24848) (klem)
- 9213560 config: Preserve defineConfig field JSDoc (#24326) (周星星同学)
- 334d884 linter/node/no-top-level-await: Clarify diagnostic (#24803) (camc314)
- b9fc6af linter/eslint/func-names: Ignore overloads (#24801) (camc314)
- e80574f estree: Handle empty spans serializing `ImportMeta` and `NewTarget` (#24775) (overlookmotel)

### ⚡ Performance

- c75dc60 linter: Avoid `Vec` allocations via `bound_names` (#24959) (connorshea)
- 9f9f39d linter/eslint/no-const-assign: Avoid `Vec` allocation per `const` declarator (#24953) (connorshea)
- 10a621e linter/typescript/adjacent-overload-signatures: Avoid collecting vec in some cases (#24874) (connorshea)
- ed167e1 linter/node/exports-style: Iterate once over global references (#24877) (connorshea)
- 69908fe linter: Avoid allocations in jest/no-deprecated-functions (#24866) (Connor Shea)
- a3585d7 linter: Speed up jest/vitest prefer-each (#24872) (Connor Shea)
- 62fe640 linter/eslint/prefer-rest-params: Run once per file (#24873) (Connor Shea)
- 7a7aa54 linter/eslint/no-this-before-super: Skip files without classes (#24869) (Connor Shea)
- 4ad0c94 linter/plugins: Faster copying comments to JS allocator (#24858) (overlookmotel)
- 4c25b91 linter/plugins: Test booleans with explicit `=== false` (#24830) (overlookmotel)
- b7add00 linter/plugins: Remove check in loop in `getDisableDirectives` (#24833) (overlookmotel)

### 📚 Documentation

- 3ff2e0e linter: Clarify config extends types (#24936) (Boshen)
# Oxfmt
### 🚀 Features

- eabfe01 oxfmt/lsp: Support `oxc.fmt.disableNestedConfig` (#24965) (leaysgur)
- 2357a10 formatter_yaml: Implement YAML formatter (#24534) (leaysgur)

### 🐛 Bug Fixes

- 761a882 formatter_css: Keep leading `+` in An+B to enable idempotency check (#24973) (leaysgur)
- e037709 formatter: Stop preserving indent for embedded template exprs (#24972) (leaysgur)
- ae1a39b formatter_css: Align wrapped selector-arg indent (#24971) (leaysgur)
- 143fd1f formatter: Align quoted `new` signature (#24883) (leaysgur)
- 07ff12f formatter_yaml: Fix indent, blank, and spacing issues (#24837) (leaysgur)
- d076b40 formatter: Check calee chain is_simple (#24871) (leaysgur)
- fb34196 formatter_css: Keep non-ASCII strings with quotes (#24870) (leaysgur)
- 9213560 config: Preserve defineConfig field JSDoc (#24326) (周星星同学)
- 31783e9 formatter_css: Print nested SCSS map with consistent indent (#24789) (leaysgur)
- 8eb9eca formatter/jsdoc: Respect `singleQuote` option in `@import` (#24787) (leaysgur)
- 6f05ac4 formatter: Fix template literal formatting (#24786) (leaysgur)

### ⚡ Performance

- bb73b23 formatter_core: Bound the thread-local scratch cache (#24793) (leaysgur)
- a5f7b15 formatter: Stage assignment-like left hand side on the heap (#24613) (leaysgur)
- 94de05f formatter: Accumulate JSX child-list builders on the heap (#24585) (leaysgur)
- 7810e8a formatter_core: Share one thread-cached scratch vector across staging buffers (#24583) (leaysgur)
- c191f51 formatter_core: Stage IR buffers on the heap to reduce arena memory (#24582) (leaysgur)

### 📚 Documentation

- 69126a0 formatter_css,fomatter_graphql,formatter_yaml: Update AGENTS.md (#24821) (leaysgur)
2026-07-27 15:17:12 +00:00
Boshen 3ff2e0e6fa docs(linter): clarify config extends types (#24936)
The generated config reference only described the JSON schema's `string[]` form. Clarify that `oxlint.config.ts` accepts imported `OxlintConfig[]`, add an example and public JSDoc, and regenerate the derived artifacts.

Closes #20238.

Validated with the website generator suite, targeted TypeScript checks, and the relevant Oxlint tests. The full `just ready` run reached an unrelated parallel suppression-fixture cleanup race; the failing test passes in isolation.

AI-assisted.
2026-07-27 01:51:16 +00:00
vigneshwar 8d31dfa393 feat(linter): verify eslint/no-restricted-globals config schema (#24598)
Part of #22955. Follow-up to the resolved #23617.

## What

`eslint/no-restricted-globals` stayed in `NO_VERIFIED_VALID_SCHEMA`
after #23617 was resolved, because the derived schema described the
internal `NoRestrictedGlobalsConfig` struct (a `globals` hash map)
rather than the user-facing positional-array format the rule actually
parses.

This PR adds a hand-written `JsonSchema` following the existing
`no-restricted-imports` pattern, describing the three accepted
positional item forms:

1. `"event"` — a global name string
2. `{ "name": "event", "message": "…" }` — a name/message object
3. `{ "globals": [ … ], "checkGlobalObject": true, "globalObjects": [ …
] }` — the object form added in #23617

Generated TS declaration:

```ts
"no-restricted-globals"?:
  RuleNoConfig | [AllowWarnDeny, NoRestrictedGlobalsConfigEnum, ...NoRestrictedGlobalsConfigEnum[]];
export type NoRestrictedGlobalsConfigEnum = string | RestrictedGlobal | NoRestrictedGlobalsObjectConfig;
```

Verified against the #22955 checklist: reflects all accepted user
configuration forms, denies unknown fields on object forms, and produces
no `[k: string]: unknown` in the TS declaration. `from_configuration`
and rule behavior are unchanged; rule tests pass.

Note: this branch removes only the `no-restricted-globals` entry from
`NO_VERIFIED_VALID_SCHEMA`; #24586 (separate PR) removes the
`filename-case` entry, so whichever merges second will have a trivial
conflict in that array.

## Disclosure

This contribution was developed with assistance from Claude Code
(Anthropic); I have reviewed and tested all changes.

---------

Signed-off-by: Alexander S. <sysix@sysix-coding.de>
Co-authored-by: Alexander S. <sysix@sysix-coding.de>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-26 16:55:55 +02:00
Bartok 7069621199 feat(linter): verify jest/vitest prefer-lowercase-title config schema (#24724)
## Summary

- Unskip `jest/prefer-lowercase-title` and
`vitest/prefer-lowercase-title` from `NO_VERIFIED_VALID_SCHEMA`.
- PreferLowercaseTitleConfig already derives `Deserialize` +
`JsonSchema` with `deny_unknown_fields` and docs for all public options,
so the generated schema/types can describe the real config.
- Regenerates `npm/oxlint/configuration_schema.json` and
`apps/oxlint/src-js/package/config.generated.ts`.

## Motivation

Part of #22955. These two plugin names shared a clean config struct
already used at runtime; they were still emit-as-`DummyRule` only
because of the broader #23186 split note (mainly about `valid-title`
option divergence). Leaving `valid-title` skipped.

Generated config now exposes:

- `allowedPrefixes: string[]` (default `[]`)
- `ignore: string[]` (default `[]`)
- `ignoreTopLevelDescribe: boolean` (default `false`)
- `lowercaseFirstCharacterOnly: boolean` (default `true`)
- `additionalProperties: false` / no `[k: string]: unknown`

## Verification

- `cargo test -p website_linter` — 8 passed
- `cargo test -p oxc_linter --features ruledocs
test_rules_with_custom_configuration_have_schema` — passed
- `cargo test -p oxc_linter prefer_lowercase_title --lib` — 2 passed
(jest + vitest)
- Inspected generated schema/TS: not DummyRule; no index signature

## AI disclosure

Assisted by AI (Hermes/Sera). I reviewed the existing
PreferLowercaseTitleConfig against the GFI checklist, regenerated
schema/types locally, and ran the tests above.

---
**Agent-Owner:** `sera` · **Platform:** hermes · **Claim:** `sera`

---
<!-- fleet-pr-claim: agent_id=sera platform=hermes -->
**Agent-Owner:** `sera` · **Platform:** hermes · **Claim-TTL:** 24h

---------

Co-authored-by: Bartok9 <259807879+Bartok9@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-26 10:30:37 +08:00
camc314 016cf2acd6 feat(linter/oxc): add bad-match-all-arg rule (#24900)
## Summary

- add oxc/bad-match-all-arg for non-global regular expressions passed to matchAll
- share cycle-safe RegExp flag resolution with bad-replace-all-arg
- support callable and global-qualified RegExp constructors
- update generated metadata, diagnostics, timings, and CLI rule-count snapshots

Closes #24823.
2026-07-24 14:48:45 +00:00
overlookmotel 99d263b123 refactor(linter/plugins): simplify getDisableDirectives (#24852)
Pure refactor, except for fixing a TS type, and correcting some comments. Runtime behavior is unaltered.

- `LABEL_PATTERN` captures the directive type as well as the label, so the multi-line check is a
single comparison, and `type` no longer comes from `label.slice(7)`.
- Numbered capture groups instead of named ones, so the match check is just `match === null`.
- `match` is no longer reused for a second, unrelated regex.
- `justification` is always set, so it's not optional - correct the type def accordingly.
- Simplify splitting off `justification`.
- Doc comment referenced `match_disable_directive` / `match_enable_directive`. The Rust function is
`match_directive` - correct the comment.
2026-07-23 16:59:45 +00:00
overlookmotel c126eebef6 refactor(linter/plugins): remove dead code in getDisableDirectives (#24851)
`LINE_DIRECTIVE_PATTERN` had a stray hyphen (`disable-(?:-next)?-line`), so it only matched
`disable--line` / `disable--next-line` - strings `LABEL_PATTERN` can never produce. Therefore it was dead code - remove it.

If the regex _was_ correctly written, the behavior would be wrong. Looks like this is a hangover from the code being taken from ESLint.
2026-07-23 16:59:44 +00:00
overlookmotel 3446ee7ebc test(linter/plugins): check comments and tokens within bounds of source text (#24832)
Add more debug assertions to debug build-only code which checks the validity of comments and tokens.

Make sure all comments and tokens have spans which are wihin bounds of source text. In particular, this will surface any problems where the source text contains a BOM, which causes us to adjust spans on Rust side when trimming off the BOM.
2026-07-23 09:29:53 +00:00
overlookmotel f5b9e01cf6 refactor(linter/plugins): remove unnecessary type asserts (#24831)
Pure refactor. Remove some type assertions which were unnecessary - TS already knows these vars can't be `null`.
2026-07-23 09:29:53 +00:00
overlookmotel 4c25b91c64 perf(linter/plugins): test booleans with explicit === false (#24830)
Tiny perf optimization to linter plugins.

`if (x === false)` is longer than `if (!x)`, but is more performant. The 1st compiles down to a simple equality check between `<value x>` and `kFalse` constant, whereas the 2nd is more like `coerceToBoolean(<value x>) === kFalse` - a more expensive operation.

This follows our convention in all JS plugins code of always using explicit `=== false` / `=== true` comparisons.
2026-07-23 09:29:53 +00:00
overlookmotel f85e47d7e6 refactor(linter/plugins): add debug asserts (#24829)
Pure refactor. Use debug asserts instead of type assertions in code for deserializing comments and tokns. This gives better error messages if we break the invariants in tests. Makes no difference in release build.
2026-07-23 09:29:52 +00:00
overlookmotel b7add00c28 perf(linter/plugins): remove check in loop in getDisableDirectives (#24833)
Small optimization to `context.sourceCode.getDisableDirectives`.

`Shebang` comment can only be the first comment in a file, so there's no need to check "is it a `Shebang`?" for every comment in a loop. Instead, just check the first comment before the loop.

Also add type annotation for the function's return type.
2026-07-23 09:10:58 +00:00
Mikhail Baev cdc941e5f8 feat(linter/n): implement exports-style rule (#24087)
this PR implements `n/exports-style` rule. Issue #493

---------

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-22 16:50:23 +01:00
周星星同学 9213560fe7 fix(config): preserve defineConfig field JSDoc (#24326)
## Summary

- contextually type oxlint and oxfmt `defineConfig` object arguments as
their config interfaces
- keep the identity-helper return type so literal inference is preserved
- restore field-level JSDoc hover for config object properties in
editors

## Validation

Manual expansion of `just ready` because `just` is not installed in my
local shell:

- `pnpm install`
- `typos`
- `cargo lintgen`
- `cargo shear --fix --check-test-targets` (returned 0; emitted an
unrelated existing redundant-ignore warning for `oxc_lexer` in workspace
metadata)
- `cargo fmt`
- `node --run fmt`
- `cargo ck`
- `env -u NO_COLOR cargo test --all-features`
- `cargo lint -- --deny warnings`
- `RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
--document-private-items --all-features`
- `cargo run -p oxc_ast_tools`

TypeScript language-service probe using VS Code's bundled TypeScript
SDK:

- old `defineConfig<T extends Config>(config: T): T` returned no docs
for object fields
- new `defineConfig<T extends Config>(config: T & Config): T` returned
JSDoc for `oxlint.ignorePatterns` and `oxfmt.semi`
- invalid property/type probes still reported TS2561 and TS2322
- return literal inference stayed unchanged for representative
oxlint/oxfmt config objects

## AI Assistance

This PR was prepared with AI assistance. I reviewed the change,
validation approach, and PR contents before submission.

Co-authored-by: cixiangtao <cixiangtao@kanyun.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-22 16:41:41 +01:00
Mikhail Baev 1ad6f6c823 feat(linter/eslint): implement id-denylist rule (#24632)
this PR implements `eslint/id-denylist`, issue #479
2026-07-22 16:30:20 +01:00
overlookmotel e80574fdf0 fix(estree): handle empty spans serializing ImportMeta and NewTarget (#24775)
Follow-on after #24557. Handle when `ImportMeta` / `NewTarget` has an empty span (`SPAN`). This code path is not exercised at present, but it seems a good idea to be prepared for when we may serialize AST after transformation.
2026-07-22 08:05:57 +00:00
oxc-guard[bot] 83abe3b49c release(apps): oxlint v1.75.0 && oxfmt v0.60.0 (#24713)
# Oxlint
### 💥 BREAKING CHANGES

- 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and
`NewTarget` (#24557) (camc314)

### 🚀 Features

- 7b045cd minfier: Drop last break from last switch case (#24673)
(Armano)
- dd18383 linter/node: Implement no-top-level-await rule (#24634)
(Connor Shea)
- 16a65f2 linter/react: Implement function-component-definition rule
(#24471) (Cole Ellison)
- 7f1f585 linter: Reuse `jest/padding-around-test-blocks` for
`vitest/padding-around-test-blocks` (#24519) (Mikhail Baev)
- 99978a8 linter/import/consistent-type-specifier-style: Support
`prefer-top-level-if-only-type-imports` option (#24502) (camc314)

### 🐛 Bug Fixes

- 0184ad6 linter/unicorn/no-useless-undefined: Preserve valid parameter
defaults (#24686) (camc314)
- 8694167 linter/eslint/prefer-destructuring: Handle typed declarations
(#24616) (camc314)
- 477cf0f linter/eslint/no-throw-literal: Handle assigned errors
(#24561) (Cole Ellison)
- ac9200a linter: Detect React components from returned JSX (#24521)
(camc314)
- c0a6522 linter/eslint/no-useless-computed-key: Allow TS syntax in
computed keys (#24524) (Cole Ellison)

### ⚡ Performance

- 346eed1 linter/unicorn/prefer-event-target: Only run on `Class` and
`NewExpression` nodes (#24685) (Mikhail Baev)
- 7be5cf0 oxlint/lsp: Only invoke lint on code actions when document is
not opened (#24676) (Sysix)
- d3f07a0 diagnostics: Box OxcDiagnosticInner to reduce binary size
(#24665) (Boshen)
- 90ae040 linter/reporter/stylish: Compute diagnostic Info once per
diagnostic (#24525) (connorshea)

### 📚 Documentation

- e6f7174 linter/valid-expect: Fix correct example being identical to
incorrect one (#24468) (mkan0141)
# Oxfmt
### 💥 BREAKING CHANGES

- 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and
`NewTarget` (#24557) (camc314)

### 🚀 Features

- 3d22307 parser: Add `ParseOptions::enable_ident_hashes` (#24491)
(Boshen)

### 🐛 Bug Fixes

- 6fe866a oxfmt: Keep tailwind classes glued to template expr with
`preserveWhitespace` (#24609) (leaysgur)
- 33e32d8 formatter_css: Use `line_suffix` for EOL line comment (#24580)
(leaysgur)
- 5f76998 formatter_graphql: Keep same line comments pending across
intervening tokens (#24579) (leaysgur)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-21 16:10:25 +01:00
Armano 7b045cd417 feat(minfier): drop last break from last switch case (#24673)
After many trial and errors, this seem to be the best way to remove
break stmts in switch cases,

This change targets only last break in last case of switch smts if its
unlabelled

this change differs from #23914 as its no longer targets all branches as
that should not be no longer necessary if all cases are correctly
rewritten

https://github.com/oxc-project/oxc/issues/24672

-----

cases like `switch (a) { case 3: if (b) break; }` are not changed

-----

ref: #17544
2026-07-20 13:21:03 +08:00
Connor Shea dd18383ee1 feat(linter/node): implement no-top-level-await rule (#24634)
This implements
[`no-top-level-await`](https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-top-level-await.md)
from the Node ESLint plugin (#493).

AI Disclosure: Generated with Claude Code. Reviewed and tested by me.

I have an alternative implementation using a Program visitor but it made
no difference performance-wise on vscode or in benchmarking outside
extreme cases, so I don't think it's really worth the trouble vs the
simpler implementation here. It's still in the commit history if that
implementation is preferred.

There is one notable difference between this and the original
implementation, which is that the `convertPath` option is not
implemented, and the checking of `package.json`/`.npmignore` is not
implemented (it was used for checking if the given module was actually
exposed in the library) as there's no way to check against those in
oxlint. There's an argument to be made that this makes the rule not
worth implementing, and I'm open to that argument if we'd prefer to mark
this rule as unsupported instead.

All tests were ported from the original rule (excluding those reliant on
the mentioned config options/checks that were skipped). I've also had
Claude add a few additional test cases (see commit
40418f7e63) on top to cover behavior that
wasn't tested previously. I have confirmed that all of these tests are
identical to the behavior of the upstream rule, but I can remove them if
we don't want to have any divergence from the upstream test suite.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 14:10:52 +01:00
camc314 8694167be7 fix(linter/eslint/prefer-destructuring): handle typed declarations (#24616)
## Summary

- skip prefer-destructuring diagnostics for variable declarations with type annotations by default
- add enforceForDeclarationWithTypeAnnotation for typescript-eslint compatibility
- report without an autofix when annotated declarations are explicitly enforced, preserving the declared type
- cover object, computed-property, array, renamed-property, and reporter configuration cases

This matches the current `@typescript-eslint/prefer-`destructuring behavior.

Fixes #24615.
2026-07-17 14:32:23 +00:00
camc314 54cc121250 feat(ast)!: split MetaProperty into ImportMeta and NewTarget (#24557)
## Summary
- replace the generic Rust MetaProperty AST node with dedicated ImportMeta and NewTarget nodes
- preserve the ESTree MetaProperty shape and generated public TypeScript types
- update parser, formatter, codegen, lint, transform, minifier, traversal, and React compiler consumers

## Summary

Remove `MetaProperty` AST node, in favour of two separate nodes (`ImportMeta` and `NewTarget`)

## Breaking Change

The Rust AST field changes from:

```rust
pub struct MetaProperty<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub meta: IdentifierName<'a>,
    pub property: IdentifierName<'a>,
}
```

to:

```rust
/// /// `import.meta` in `console.log(import.meta);`.
pub struct ImportMeta {
    pub node_id: Cell<NodeId>,
    pub span: Span,
}

/// `new.target` in `function F() { return new.target; }`.
pub struct NewTarget {
    pub node_id: Cell<NodeId>,
    pub span: Span,
}
```

## Why?

The current AST could represent shapes such as:

```
foo.bar
```

By constructing the node manually. However this doesn't make sense, and is invalid.

## How to migrate?

1. Replace any `Expression::MetaProperty` matches/checks with the dedicated `Expression::ImportMeta` and `Expression::NewTarget` varients
2. Replace any `new_meta_property` construction with the new `new_import_meta` or `new_new_target` APIs.
3. Inspecting the `meta`/`property` identifier names is no longer required.
2026-07-15 14:48:32 +00:00
Cole Ellison 16a65f269c feat(linter/react): implement function-component-definition rule (#24471)
## Summary

Adds a native Oxlint implementation of the [`eslint-plugin-react`
`function-component-definition`
rule](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md).
This implementation is a port of the [rule
source](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/lib/rules/function-component-definition.js),
test suite, and autofix logic.

Related #1022

## AI Disclosure

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

---------

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-14 18:34:00 +01:00
Mikhail Baev 7f1f585b86 feat(linter): reuse jest/padding-around-test-blocks for vitest/padding-around-test-blocks (#24519)
this PR moves `jest/padding-around-test-blocks` to the shared
jest_vitest rules, so it can be reused by both the jest and vitest
rules.

issue #4656

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-14 18:22:11 +01:00
camc314 99978a8bbb feat(linter/import/consistent-type-specifier-style): support prefer-top-level-if-only-type-imports option (#24502)
## Summary

- Re-port upstream support for prefer-top-level-if-only-type-imports.
- Allow mixed value/type imports while requiring top-level syntax for imports containing only types.
- Preserve Oxlint's existing default and declaration-file behavior.
- Regenerate the JSON schema and TypeScript configuration types.

## Reference

https://github.com/oxc-project/oxc/issues/1117#issuecomment-4966827537
2026-07-14 09:25:16 +00:00
oxc-guard[bot] 2d4e8d2064 release(apps): oxlint v1.74.0 && oxfmt v0.59.0 (#24454)
# Oxlint
### 🚀 Features

- 0433a83 linter/eslint/no-inner-declarations: Add `namespaces` option
(#24044) (Boshen)
- 92f154a oxlint,oxfmt: Auto-discover `.mts` config files (#24357)
(camc314)
- 8c1d74b linter/import/no-duplicates: Add autofix logic (#24273) (Cole
Ellison)

### 🐛 Bug Fixes

- 0b086de linter/jest/prefer-lowercase-title: False positive when
`lowercaseFirstCharacterOnly` is false (#24414) (Connor Shea)
- 097cb95 linter: Allow `vite-plus/test` and `@effect as Vitest source
(#24196) (Liang)
- 8337835 linter: Error on `ignorePatterns` that cannot match files
aoutside the config directory (#24341) (leaysgur)
- 9ba30e5 linter/oxc/bad-replace-all-arg: Add note to enhance diagnostic
(#24346) (camc314)
- 2ce5a33 linter: Resolve `ignorePatterns` relative to the config dir
(#24339) (leaysgur)
- ab90eed linter/eslint/no-loop-func: Do not error on catch variables
(#24316) (Chris Opperwall)
- b67f0a6 linter/eslint/no-unused-vars: Count default parameter updates
as usage (#24323) (camc314)
- d193f8e linter: Detect Junie agent env vars (#24277) (Jeevan Mohan
Pawar)
- 2aecf60 linter/eslint/no-unreachable: Handle `break` in switch stmts
correctly (#24260) (camc314)

### ⚡ Performance

- 7f80cac linter/vue/prop-name-casing: Precompile `ignoreProps` regex
pattern (#24413) (connorshea)
- 6272051 linter/typescript/no-require-imports: Compile allow patterns
once (#24417) (connorshea)
- fb1edf1 linter: Compute comment fix span only for directive comments
(#24419) (connorshea)
- 33805b9 linter/jsdoc/require-param: Compile checkTypesPattern regex
once (#24420) (connorshea)
- 8de6fca linter/jest/valid-title: Compile disallowedWords regex once
(#24412) (Connor Shea)
- 4a0d8dc linter/eslint/no-underscore-dangle: Avoid String clone per
identifier (#24371) (Ian Macalinao)
- f3ab04c linter/typescript/consistent-type-imports: Remove redundant
Vec per violation (#24370) (Ian Macalinao)
- 4d2d78d linter/typescript/prefer-ts-expect-error: Avoid String clone
per comment (#24369) (Ian Macalinao)
# Oxfmt
### 🚀 Features

- 3a7fe74 formatter_css: Update oxc-css-parser to 0.0.7 (#24434)
(leaysgur)
- 0173cd3 formatter_css: Format Less :extend and merge props (#24358)
(leaysgur)
- 92f154a oxlint,oxfmt: Auto-discover `.mts` config files (#24357)
(camc314)
- df250df formatter: Support `quoteProps` for TS enum and methods
(#24309) (leaysgur)
- a9a5cd6 formatter_core: Expose `SourceText::as_str()` (#24281)
(leaysgur)

### 🐛 Bug Fixes

- 162bddf formatter: Add required parens for conditional type in type
parameter constraint (#24450) (leaysgur)
- 2d22a91 formatter: Determine type cast target from span instead of
lexical scan (#24447) (leaysgur)
- 25306e9 formatter: Do not add extra parens with type cast comment
(#24444) (leaysgur)
- bd6edfe formatter: Break arrow signature that exactly fills the line
when cond body may hug (#24440) (leaysgur)
- a99ef41 formatter: Keep quotes on method signature named new (#24432)
(leaysgur)
- fcc28df formatter_css: Keep glued-braket-value tight (#24352)
(leaysgur)
- 8337835 linter: Error on `ignorePatterns` that cannot match files
aoutside the config directory (#24341) (leaysgur)
- b7c7e15 formatter: Add parens for import and private field in new
callee chain (#24320) (leaysgur)
- 0c8f6e4 formatter: Update detect_code_removal for #24309 (#24314)
(leaysgur)
- a85aad0 formatter: Fix member-chain and non-null parens (#24312)
(leaysgur)
- 1c29c73 formatter: Preserve `TSNonNullExpression` in chain expression
(#24311) (leaysgur)
- 8933c0e formatter: Keep comment inside of empty `switch` block
(#24308) (leaysgur)
- ec26af2 formatter: Preserve blank lines between JSX attrs (#24290)
(leaysgur)
- 70bd54d formatter: Keep arrow function body comment (#24287)
(leaysgur)
- 415fe1e oxfmt: Error on ignorePatterns that cannot match files outside
the config directory (#24286) (leaysgur)
- eeabc4a formatter_css: Bail on EOF-recovered parse errors (#24282)
(leaysgur)
- 42ec8de formatter: Keep comments inside surviving parens and
suppressed statement terminators (#24253) (leaysgur)
- 1343779 formatter: Keep comment inline for empty statements (#24249)
(leaysgur)
- b996579 formatter: Print ; before trailing comments part 2 (#24246)
(leaysgur)
- 4f86e8c formatter: Print `;` before trailing comments (#24244)
(leaysgur)
- 01252e4 formatter: Add or remove parens for `let` declaration (#24215)
(leaysgur)

### ⚡ Performance

- eeb1913 formatter_core: Avoid per-call `Vec` work-stack in soft-line
removal (#23775) (Marius Schulz)
- a2f255b formatter: Use `SmallVec` for `MemberChain` collections
(#23776) (Marius Schulz)

### 📚 Documentation

- b52d0f5 formatter: Add TODO comment about unsound code (#24372)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-07-14 15:02:14 +08:00
connorshea 7f80cac4fd perf(linter/vue/prop-name-casing): precompile ignoreProps regex pattern (#24413)
`is_ignored` in `vue/prop-name-casing` recompiled each slash-wrapped `ignoreProps` pattern with `Regex::new` for every prop it checked. This compiles the patterns once in `from_configuration` into an `Exact`/`Pattern(Regex)` matcher enum stored on the options (`serde`/`schemars`-skipped field), and matches against that.

Invalid regex patterns are dropped at compile time, which preserves the previous behavior (they could never match before either, and were not compared as literals).

### Benchmark

Interleaved paired-sample benchmark (N=40, alternating order, `--silent`), release binaries built from this branch and its merge-base.

Fixture: 220 Vue SFCs / 35,200 props / 36.5K lines, single-rule oxlintrc with `ignoreProps: ["ignored_prop", "/^legacy/", "/_deprecated$/", "/^(alpha|beta)_/", "/[/"]` (one exact pattern, three regexes, one invalid regex).

| | baseline | this branch |
|---|---|---|
| rule on | 66.87 ms | 16.69 ms |
| parse only | 8.47 ms | 8.41 ms |

Paired Δ of **+50.17 ms** (sd 2.18, t=146, faster in 40/40 pairs). The parse-only differential is null (+0.06 ms, t=0.2), so the saving is attributable to the rule and not to binary layout.

That works out to 4.0× on this fixture, but the multiplier is fixture-dependent — the work removed scales with `regex_pattern_count × prop_count`, so denser configs show a larger ratio. The ~50 ms saved across 35K props is the more transferable number.

### Correctness

Verified with `--format=json` (**without** `--silent`, which empties the JSON reporter's `diagnostics` array): both binaries emit the same 10,560 diagnostics, identical after sorting — the array order varies between runs because files are linted in parallel. The fixture includes canary props that are only ignored via the regex patterns, plus an invalid regex; the ignore semantics are unchanged.

### Disclosure

This change was implemented and benchmarked with AI assistance (Claude Code), and has been reviewed by the submitter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-13 09:42:13 +00:00