Commit Graph

265 Commits

Author SHA1 Message Date
Dunqing a3d33dda7c fix(semantic): skip body bindings in type parameters and this param (#26169)
#26099 resolves parameter and signature references before visiting the function body, so they cannot bind to declarations that only exist inside the body. That early pass only ran when the function had parameters or a return type. Type parameter constraints and a `this` parameter type are visited before that check, so a function whose signature has references only in those positions skipped the pass. Its references then resolved after the body was visited and bound to body-local types.

## Example

```ts
type U = number;
function f<T extends U>() {
  type U = string;
  return null! as T;
}
```

Before this change, `U` in the constraint resolved to the body-local `type U = string`. TypeScript scopes local types to the function body, so it must resolve to the outer alias. The same happened for `function f(this: U) { type U = string }` and for arrow functions with type parameters.

The early pass now runs for every function and arrow function. It returns immediately when the signature produced no references, so the `has_parameter() || return_type.is_some()` condition, which approximated that check, is no longer needed.

Also adds a regression test for the compaction in `resolve_references_for_current_scope`: a resolved reference followed by an unresolved one must neither drop nor duplicate entries, and no existing test covered that ordering.

Verified with the semantic and transformer unit tests, Clippy, `cargo coverage -- semantic`, and transform conformance; no snapshots changed.

AI assistance: Claude Code was used to investigate, implement, and verify this change. The contributor is responsible for the submitted change.
2026-08-31 09:23:18 +00:00
Dunqing c3dedc9b0c fix(semantic): skip function body bindings in parameters (#26099)
Function parameters, TypeScript signatures, and function bodies currently share one Oxc semantic scope. A reference in a parameter can therefore bind to a declaration inside the body, even though JavaScript does not make body declarations visible there. Rolldown may then treat a global as local, skip a configured define, or remove live code.

## Example

```js
export function load(value = typeof window !== "undefined") {
  var window;
  return value;
}
```

The body `var window` is not visible to the default value. Before this change, Oxc resolved `window` in the default value to that body declaration. It should remain a global reference.

The workaround resolves parameter and signature references before visiting the body, while only parameter bindings are present. If a reference is still unresolved, its `lookup_scope_id` advances to the function's parent. Final resolution resumes there, so declarations added while visiting the body are skipped. References created in the body still begin at the function scope and resolve normally.

```text
Before: parameter reference -> completed function scope -> body binding (wrong)

After:  parameter reference -> parameter bindings -> parent scopes
        body reference      -> completed function scope -> parent scopes
```

Parameter decorators already begin in the enclosing class scope, so they are left for normal final resolution. This emulates separate parameter and body scopes until https://github.com/oxc-project/backlog/issues/176 is implemented.

The corrected semantic data also exposes the transformer case covered by #26105.

Verified with semantic and transformer-plugin tests, Clippy, coverage, allocation snapshots, and diff checks. `just ready` completed workspace checks but stopped at a codegen snapshot recorded with Node.js v26.5.0; the local version is v25.8.1.

Closes #20427
Closes #22783

Downstream reports:

- https://github.com/rolldown/rolldown/issues/10722
- https://github.com/rolldown/rolldown/issues/10779

AI assistance: OpenAI Codex was used to investigate, implement, and test this change. The contributor is responsible for the submitted change.
2026-08-27 17:23:18 +00:00
camc314 059784d96b fix(semantic): respect shadowed Infinity and NaN in enum evaluation (#25604)
## Summary

Fix enum evaluation to resolve identifiers before treating `Infinity` and `NaN` as unbound globals.
2026-08-14 02:36:24 +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
camc314 0c9b980cb1 fix(semantic): report module declaration diagnostics (#25285)
## Summary

- report TS1234 for function-local ambient module declarations
- preserve TS1235 for function-local namespace and internal module declarations
- report TS2435 for ambient modules nested in namespaces while allowing module augmentation inside ambient external modules

## Babel Conformance Snapshot Parse Failure

This intentionally introduces one known Babel conformance false positive: `typescript/scope/redeclaration-in-nested-module/input.ts`. The Babel harness supplies `sourceType: "module"`, so Oxc reports TS2435 for its nested ambient module. This affects one unique fixture in both the parser and semantic Babel summaries. This change aligns with TS, and TS expects this error.
2026-08-04 15:06:10 +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
camc314 76ec104498 fix(semantic): resolve import-equals namespaces (#22696)
Fixes `import =` semantic reference resolution so internal module references are resolved as namespace lookups instead of ordinary value reads.

TypeScript treats the RHS of an internal `import =` specially. In the checker, `getTargetOfImportEqualsDeclaration` delegates internal module references to `getSymbolOfPartOfRightHandSideOfImportEquals`, which resolves bare RHS names and intermediate qualified names with `SymbolFlags.Namespace` rather than value meaning:

- `getTargetOfImportEqualsDeclaration`: https://github.com/microsoft/TypeScript/blob/f350b52331494b68c90ab02e2b6d0828d2a22a74/src/compiler/checker.ts#L3708-L3733
- `getSymbolOfPartOfRightHandSideOfImportEquals`: https://github.com/microsoft/TypeScript/blob/f350b52331494b68c90ab02e2b6d0828d2a22a74/src/compiler/checker.ts#L4480-L4499
- `getSymbolOfNameOrPropertyAccessExpression` also routes names on the RHS of import/export assignments through that same helper: https://github.com/microsoft/TypeScript/blob/f350b52331494b68c90ab02e2b6d0828d2a22a74/src/compiler/checker.ts#L50014-L50019

Before this change, Oxc could count plain values as references from `import =`:

```ts
const x = 1;
import foo = x;
```

Before:

```text
x references: 1
```

After:

```text
x references: 0
```

That matches TypeScript’s behavior because `x` is only a value symbol, not a namespace-capable symbol.

The same applies to qualified names:

```ts
const x = { y: 1 };
import foo = x.y;
```

Before:

```text
x references: 1
```

After:

```text
x references: 0
```

But namespace-capable declarations still resolve:

```ts
namespace x {}
import foo = x;
```

After:

```text
x references: 1
```

and:

```ts
namespace x {
  export namespace y {}
}

import foo = x.y;
```

After:

```text
x references: 1
```

This updates the semantic snapshots to reflect the corrected reference/declaration matching.
2026-07-20 13:10:54 +00:00
camc314 ffd2765ffc fix(semantic): mark declared computed MethodDefinitions as type references (#24296)
## Summary

- mark computed method keys in ambient and abstract classes as type references
- preserve runtime read flags for concrete computed methods
- bound declare-class ancestry checks to the containing class
- add semantic snapshot coverage and update the TypeScript conformance snapshot

Follows the work done in 588d9971ac,  9b95632179
2026-07-08 11:42:29 +00:00
camc314 42d00d383e fix(semantic): mark declared class heritage as type references (#24237)
## Summary

- resolve declared class heritage expressions through ValueAsType so their final references are Type
- allow type-only namespace imports in declared heritage while preserving Read for runtime class heritage
- add regression coverage for type-only namespace imports, value namespace imports, local bases, and a runtime control
- update semantic conformance snapshots, yielding two additional TypeScript passes
2026-07-06 21:58:56 +00:00
camc314 588d9971ac fix(semantic): mark TS PropertyDefinitions computed fields as type references (#24233)
## Summary

- resolve computed declare class-field keys through ValueAsType so their final references are Type
- allow declared keys to resolve type-only import bindings while preserving Read for ordinary runtime class fields
- add a red/green semantic snapshot and update conformance snapshots, yielding one additional TypeScript semantic pass and one additional transform-conformance pass
2026-07-06 16:28:50 +00:00
camc314 9b95632179 fix(semantic): mark computed method keys in TSMethodSignatures as type references (#24232)
## Summary

- resolve computed TypeScript method, getter, and setter keys through ValueAsType so their final references are Type
- allow those keys to resolve value bindings and type-only import bindings consistently with computed property signatures and type queries
- add regression coverage and update semantic conformance snapshots, yielding one additional Babel pass and six additional TypeScript passes

Handled for TSPropertySignature via https://github.com/oxc-project/oxc/commit/d8b9909bd8df193306de7ff6f1204f3ac0963076
2026-07-06 16:10:05 +00:00
camc314 869ac200a4 fix(semantic/cfg): connect for update exit to loop test (#23791)
## Summary

Correct the CFG generated for `for` statements whose update expression spans multiple basic blocks, such as conditional or logical expressions.

This addresses the malformed CFG underlying #23789.

## Problem

`update_graph_ix` identifies the entry of the update expression. After visiting the expression, `cfg.current_node_ix` identifies its exit block.

Previously, the loop backedge to the test originated from the update entry. This allowed control flow to bypass the update expression and left its exit block disconnected.

### Before

```mermaid
flowchart LR
    Test["Loop test"] -->|truthy| Body["Loop body"]
    Test -->|falsy| After["After loop"]

    Body -->|backedge| Entry["Update entry"]
    Entry --> Update["Update expression blocks"]
    Update --> Exit["Update exit"]

    Entry -->|backedge| Test
```

### After

```mermaid
flowchart LR
    Test["Loop test"] -->|truthy| Body["Loop body"]
    Test -->|falsy| After["After loop"]

    Body -->|backedge| Entry["Update entry"]
    Entry --> Update["Update expression blocks"]
    Update --> Exit["Update exit"]
    Exit -->|backedge| Test
```

## Implementation

Record `cfg.current_node_ix` after visiting the update expression as `after_update_graph_ix`. The backedge to the loop test now originates from that exit block.

The loop body backedge and `continue` target still point to `update_graph_ix`, ensuring the complete update expression runs before the next test.

A dedicated CFG fixture covers a conditional update expression:

```js
forward ? index++ : index--
```
2026-06-25 17:25:58 +00:00
camc314 d3e92d5258 fix(semantic/cfg): connect while branches from condition exit (#23790)
Correct the CFG generated for `while` statements whose test expression creates multiple basic blocks, such as logical or conditional expressions.

Previously, the loop body and exit edges originated from the test expression's entry block. They now originate from its exit block after the complete expression has been evaluated.

## Before

The body and exit edges could bypass the test expression, leaving its exit block disconnected.

```mermaid
flowchart LR
    Entry["Test entry"] --> Test["Test expression"]
    Test --> Exit["Test exit"]

    Entry -->|true| Body["Loop body"]
    Entry -->|false| After["After while"]
    Body -->|backedge| Entry
```
## After

The complete test expression is evaluated before control branches to the loop body or exit.

```mermaid
flowchart LR
    Entry["Test entry"] --> Test["Test expression"]
    Test --> Exit["Test exit"]

    Exit -->|true| Body["Loop body"]
    Exit -->|false| After["After while"]
    Body -->|backedge| Entry
```

The implementation records cfg.current_node_ix after visiting the test expression as after_test_graph_ix, then uses that block for the body and exit edges. The loop backedge and continue target remain connected to the test entry so each iteration evaluates the expression again.
2026-06-25 17:19:17 +00:00
Boshen 777f02ae10 refactor(diagnostics)!: return a Diagnostics type instead of Vec<OxcDiagnostic> (#23217)
## Summary

Introduces `oxc_diagnostics::Diagnostics` — a `Vec<OxcDiagnostic>` newtype that knows the difference between errors and warnings — and makes it the standard diagnostics return value across oxc.

- **New type**: `Diagnostics` with `has_errors()`, `has_warnings()`, `errors()`, `warnings()`, plus `Vec`-like ergonomics (Deref, `IntoIterator`, `Extend`, `From`/`Into<Vec>`) so most call sites are unchanged.
- **Return types**: `ParserReturn`, `SemanticBuilderReturn`, `IsolatedDeclarationsReturn`, the React Compiler `TransformResult`/`LintResult`, and `TransformerReturn` now expose `diagnostics: Diagnostics` instead of `errors: Vec<OxcDiagnostic>` (the React Compiler's separate `errors`/`warnings` are merged into one severity-tagged list).
- **Behaviour**: a React Compiler **error** now stops the transform (no output), while **warnings** flow through and the transform continues. `compile()` aborts codegen only when `diagnostics.has_errors()` — warnings alone no longer abort (previously any diagnostic did).

## Breaking change

Consumers reading `*.errors` on these return values must read `*.diagnostics`. React Compiler consumers that read `.errors`/`.warnings` separately should use `diagnostics.has_errors()` / `diagnostics.errors()` / `diagnostics.warnings()`.

Parser/semantic/isolated-declarations fatality is otherwise unchanged. Builds on the React Compiler transform feature (#23201).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-10 14:46:52 +00:00
Boshen 1260f03b4f refactor(semantic): don't build the AstNodes store by default (#23005)
## What

Make the full `AstNodes` store **opt-in**, so the compiler pipeline (transformer / codegen / minifier) can run semantic analysis without building it at all.

Previously every `SemanticBuilder` run allocated and populated the full `AstNodes` store, even for consumers that only need scoping + the syntax checker. This separates the node-related builder state into three independent pieces:

- a standalone `NodeId` counter — the source of truth for node ids, valid in either mode;
- an **ancestry stack** — an upward-walk-only stack of the current node's ancestors, the only ancestry the syntax checker and binder actually read;
- the full **`AstNodes`** store — random access to every node, needed by the linter and mangler.

## How

- A new `AstNodeStore` (in `crates/oxc_semantic/src/node/store.rs`) bundles the node-id counter, the current-node cursor/flags, and the storage. The storage is an enum (`AstNodeStoreKind`) with two variants — `Full(AstNodes)` and `Ancestry(AncestryStack)` — so the full store and the ancestry stack are **mutually exclusive by construction**; they can never both be maintained at once.
- The checker and binder read ancestry through an `Ancestry` view (`node/ancestry.rs`) that dispatches between the full store (linter / mangler) and the stack (compiler pipeline). They no longer do random `NodeId` access into the store — all ancestry access is upward-walk-only.
- `SemanticBuilder` gains `with_build_nodes(bool)`. The default is **off** (ancestry stack only). `new_linter()` turns it on (the linter needs the full store); `new_compiler()` leaves it off.
- `CompilerInterface::build_semantic_nodes()` lets `oxc::Compiler` consumers opt back in if their `after_semantic` hook reads `Semantic::nodes`.

## Notes

- Behaviour-neutral: semantic conformance snapshots are unchanged.
- Allocation snapshots updated — the default (ancestry) path allocates slightly less (the growing `Stack` trades a couple of fixed allocations for a few reallocs).
- Builds on the already-merged `SymbolFlags`-based redeclaration checks (#23060) and the `new_linter`/`new_compiler` preset adoption (#23057), which removed the last random node reads from the redeclaration path so the checker works without the store.
2026-06-08 16:13:56 +00:00
Boshen d16f456ce9 refactor(semantic): track function kind in SymbolFlags for redeclaration checks (#23060)
Encode whether a function binding is a named function expression or an `async`/generator function as two internal `SymbolFlags` bits (`FunctionExpression`, `AsyncOrGeneratorFunction`), set at bind time.

The redeclaration checks now read these flags instead of the AST node:

- `check_redeclaration` checks `FunctionExpression` rather than fetching the declaration node to ask `Function::is_expression`.
- `check_redeclared_function` (Annex B.3.3) checks `AsyncOrGeneratorFunction` on each previous declaration, replacing the build-only `async_or_generator_function_node_ids` Vec and its linear scan.

The transformer keeps the flags accurate when it rewrites functions, via a new `sync_function_symbol_flags` helper applied at the function-rewriting sites in `async_to_generator` and class private-method extraction.

Behaviour-neutral: semantic conformance snapshots are unchanged.
2026-06-07 08:04:12 +00:00
Boshen 336413bf5d refactor(semantic): use new_linter/new_compiler presets at callsites (#23037)
Replaces ad-hoc `SemanticBuilder::new().with_*()` chains with the `new_linter()` / `new_compiler()` presets where the preset already expands to the exact same option set, so the config lives in one place.

- linter example + linter benchmark → `new_linter()` (cfg + class table + syntax)
- semantic benchmark, cfg example, integration test util → `new_compiler()` (syntax)

Behavior-neutral — each preset matches the options the callsite set by hand. Carved out of the larger store-optional refactor so it can land independently on `main` first.
2026-06-06 19:09:57 +00:00
Boshen 324c8ae137 refactor(semantic): add SemanticBuilder::new_compiler / new_linter presets (#23019)
## Summary

Adds two named constructor presets to `SemanticBuilder` that bake in the options each use case needs, so the configuration lives in one place instead of being repeated at call sites:

- **`SemanticBuilder::new_compiler()`** — compiler pipeline. Enables syntax-error checking; leaves linter-only analyses (control flow graph, class table) off.
- **`SemanticBuilder::new_linter()`** — linter. Enables syntax-error checking, the control flow graph, and the class table.

The presets are applied at the real pipeline entry points:
- `new_linter()` → the linter service build.
- `new_compiler()` → `oxc::Compiler`, and the napi parser/transform bindings.

Everywhere else keeps `SemanticBuilder::new()` and turns on only the features it needs (mangler, minifier, formatter, transformer plugins, examples, tests, ...). Individual options can still be overridden after a preset (e.g. the linter service overrides `check_syntax_error` with its runtime flag).

Behavior is preserved at every migrated site. Node-store access is intentionally untouched in this PR.

## Verified

- Builds across the affected crates + napi; clippy clean; `oxc_linter` tests pass; full `cargo coverage` with no snapshot changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-06 15:44:18 +00:00
camc314 26b9396dbd fix(semantic): resolve parameter decorators outside parameter scope (#22623)
fixes https://github.com/oxc-project/oxc/issues/22622
2026-05-23 16:10:00 +00:00
Dunqing dfa931d3bc fix(semantic): propagate unresolved auto-increment enum value instead of defaulting to 0 (#22646)
## Summary

- When a TypeScript enum member with no initializer follows one whose initializer could not be evaluated to a constant, the auto-increment value previously fell back to `0`. It now propagates "unresolved" (`None`) like the existing `Some(String) => None` arm, matching tsc 6.0.3 (which errors on this construct and emits `void 0`, never `0`).
- The bug surfaced via rolldown's cross-module enum inliner (rolldown/rolldown#9494). Rolldown reads `Scoping::get_enum_member_value` to populate its `enum_member_value_map`; once the wrong `0` landed in the map, every call-site reference to the affected member was inlined to `0`. After this fix the map omits the entry and rolldown leaves the reference alone — matching tsc's call-site behavior.
- Adopts the existing `prev = Some(Number(-1.0))` sentinel pattern from `oxc_isolated_declarations/src/enum.rs` and `oxc_transformer/src/typescript/enum.rs`, so the first-member-defaults-to-0 case still resolves naturally and the auto-increment branch shrinks to two arms.

## Example

Input:

```ts
import { External } from './external'; // External.X = 0, External.Y = 1
enum Local { A = External.X, B = External.Y, C }
```

`Scoping::get_enum_member_value` before:

- A → None, B → None, C → Some(Number(0.0))  (wrong)

After:

- A → None, B → None, C → None

Covered by new test `crates/oxc_semantic/tests/integration/enum_values.rs::auto_increment_after_unresolvable_fails`; existing `auto_increment` (first member defaults to 0) and `auto_increment_after_string_fails` continue to pass.

AI disclosure: drafted with Claude Code, reviewed manually.
2026-05-22 10:02:55 +00:00
camc314 a7c3e22707 fix(semantic): clear member write target for computed keys (#22302)
- clear `MemberWriteTarget` before visiting computed member keys
- add semantic snapshot coverage for computed keys versus computed objects

Fixes #22300
2026-05-11 08:49:18 +00:00
Dunqing 6dd061c70e feat(semantic): extend MemberWriteTarget to cover all property modification patterns (#21205)
## Summary

- Extend `MemberWriteTarget` flag to cover compound assignments (`+=`), update expressions (`++/--`), and `delete` — previously only simple `=` assignments were covered
- Add `visit_unary_expression` override to set `Write` for `delete` on member expressions
- Change `is_write_only()` to `is_write()` in `visit_member_expression` so all write contexts trigger the flag
- Fix `MemberWriteTarget` leaking into conditional test positions (`(a ? x : y).foo = 1` was incorrectly marking `a`)
- Add snapshot test covering compound assignments, delete, and conditional expressions
- Update doc comments to reflect broader scope

## Test plan

- [x] New snapshot test `member-write-target.js` covering compound assignments, delete, conditional test/consequent/alternate
- [x] Existing semantic snapshot tests updated
- [x] `just ready` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-11 14:31:07 +00:00
Dunqing 75663c077d feat(semantic): add enum member value evaluation for const enum support (#20602)
## Summary

Part of https://github.com/oxc-project/oxc/issues/6073 and #11844

- Add `ConstantValue` type in `oxc_syntax` for representing enum member values (number/string)
- Implement enum member value evaluation in `oxc_semantic` supporting numeric auto-increment, string values, binary/unary expressions, cross-member and cross-enum references, template literals, and merged enum declarations
- Store evaluated enum member values in `Scoping` via `EnumData` struct with getter/setter APIs and enum body scope tracking
- All enum evaluation code is gated behind the `enum_eval` Cargo feature (enabled by `oxc_transformer`)

### Feature gate

The `enum_eval` feature controls:
- `ts_enum` module (evaluator + data types)
- `EnumData` field and enum-related methods on `Scoping`
- `oxc_syntax/to_js_string` dependency

Consumers that don't need enum values (linter, language server) pay zero cost.

### Babel alignment

Unary operators on strings follow Babel's JS-coercion semantics (`+"s"` → `"s"`, `-"s"` → `NaN`, `~"s"` → `-1`), which differs from TypeScript (unevaluated). This is documented in the source.

## Test plan

- [x] Integration tests in `crates/oxc_semantic/tests/integration/enum_values.rs`
- [x] Semantic TypeScript conformance snapshot updated
- [x] `cargo check -p oxc_semantic` passes (without feature)
- [x] `cargo check -p oxc_semantic --features enum_eval` passes (with feature)
- [x] `cargo check -p oxc_transformer` passes (enables feature transitively)
2026-04-07 08:52:19 +00:00
Dunqing 3cfe8ede95 feat(semantic): add MemberWriteTarget flag to ReferenceFlags (#20772)
## Summary

Add a new `MemberWriteTarget` bit to `ReferenceFlags` ~~Store member write target references in a separate `FxHashSet<ReferenceId>` on `Semantic` rather than adding a flag to `ReferenceFlags`.~~

This marks identifier references read as the object of a member expression in a simple assignment target position (e.g., `A` in `A.foo = 1`).

- Set during `visit_member_expression` when flags indicate write-only context (simple `=`, not compound `+=` or update `++`)
- Stored in `Semantic::member_write_references` (`FxHashSet<ReferenceId>`)
- Queryable via `Semantic::is_member_write_reference(reference_id)` and `Semantic::member_write_references()`
- Does not affect existing `ReferenceFlags` — no changes to the shared bitflags type

## Motivation

Distinguishing "reads that exist only to serve a property write" from "real reads" is broadly useful:

### Minifier: dead code elimination of property-write-only symbols

```js
function A() {}
A.from = () => {}  // A's only reference is a member write target
// → both can be eliminated when property_write_side_effects: false
```

Without this info, the minifier sees `A` as "referenced" and cannot remove the function declaration. With it, the minifier knows all of `A`'s reads are property-write targets and can safely drop them. (See stacked PR #20773 — will be updated to use the new API in a follow-up)

### Linter: `no_unused_vars` improvement

```js
const config = {};
config.debug = true;
config.verbose = false;
// config is never read — only mutated via property writes
```

Currently `config` is considered "used" because `config.debug = ...` creates a read reference. With member write target tracking, `no_unused_vars` could detect that `config`'s only reads are property-write targets and report it as unused.

### Linter: simplify `no_param_reassign` property detection

`no_param_reassign` has a complex `is_modifying_property()` function that manually walks the ancestor chain to detect property modifications like `param.foo = 1`. With member write target tracking, this can be checked directly on the reference.

### Rolldown: replace `MemberExprIsWrite` traversal state

Rolldown already maintains its own [`MemberExprIsWrite` flag](https://github.com/rolldown/rolldown/blob/eec7d7320cc991e1efd5ed8264d02c6889a223c3/crates/rolldown/src/ast_scanner/mod.rs#L130-L131) as local traversal state in the AST scanner to [detect member expressions in write context](https://github.com/rolldown/rolldown/blob/eec7d7320cc991e1efd5ed8264d02c6889a223c3/crates/rolldown/src/ast_scanner/impl_visit.rs#L55-L73). Having this on `Semantic` means Rolldown can consume it directly instead of re-detecting it during scanning. There's also a [TODO in Rolldown's treeshake options](https://github.com/rolldown/rolldown/blob/main/crates/rolldown_common/src/inner_bundler_options/types/treeshake.rs#L255-L259) to wire `property_write_side_effects` to oxc_minifier — our stacked PR #20773 provides that.

### General: escape analysis for local objects

Any analysis that needs to know "is this object only mutated locally, never passed elsewhere?" benefits from knowing which reads are purely for property writes vs. reads that pass the value to other functions.

## Test plan

- [x] Semantic snapshot tests updated
- [x] Conformance snapshots updated
- [x] Clippy clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-07 06:11:34 +00:00
Boshen bfb15a3651 fix(semantic): make multi_index_vec clone panic-safe (#19299)
## Summary
Follow-up to #19138 after merge.

This PR fixes panic-safety in `multi_index_vec!` clone and keeps strict lint clean for `oxc_semantic` tests.

### Changes
- Make macro-generated `Clone` panic-safe by cloning row-by-row through `push`.
  - Avoids setting `len` before all elements are initialized.
  - Prevents dropping uninitialized elements if a field `clone()` panics.
- Add `#[expect(clippy::unused_self)]` to cfg-disabled integration helper methods to keep `cargo clippy -p oxc_semantic --lib --tests -D warnings` green.

## Validation
- `cargo clippy -p oxc_semantic --lib --tests -- -D warnings`
- `just lint`

## AI disclosure
AI-assisted: I used Codex to draft and apply this patch, then ran lint checks and reviewed the result before opening this PR.
2026-02-12 08:54:07 +00:00
Boshen 0f1b50bc4d refactor(semantic): accept Ident instead of &str in binding/symbol APIs (#19089)
## Summary

- Change `Scoping` methods (`get_binding`, `find_binding`,
`scope_has_binding`, `get_root_binding`) to accept `Ident` instead of
`&str`
- Propagate `Ident` through `TraverseScoping`/`TraverseCtx` methods
(`create_reference`, `create_unbound_reference`,
`create_reference_in_current_scope`, `delete_reference`)
- Update all callers across semantic, traverse, transformer, linter,
minifier, and transformer_plugins crates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-02-07 09:25:18 +08:00
Boshen f32ea19e15 fix(semantic): report redeclaration error for import bindings conflicting with value declarations (#19068)
## Summary

- In JavaScript modules, `import { Foo } from './foo.js'; var Foo;` (and `let`, `const`, `class`, `function`) should report `"Identifier 'Foo' has already been declared"` per the [ES spec](https://tc39.es/ecma262/#sec-imports-static-semantics-early-errors)
- The existing excludes-based conflict detection in the binder doesn't catch this because `Import` is not in the `Value` bitset
- Added a checker function that runs at `Program` leave, scanning root scope bindings for symbols with both `Import` and a value flag, and reports a redeclaration error
- TypeScript is excluded since it has its own declaration merging rules

Closes #15961

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-02-06 15:03:47 +00:00
Boshen 3aeba7a7e6 fix(semantic): report redeclaration error for function a() {} var a in module mode (#19041)
## Summary

Fixes #18719.

- In module mode, function declarations are lexically scoped (part of `LexicallyDeclaredNames`), so `function a() {} var a = ''` is a redeclaration error per the ECMAScript spec
- The checker was only reporting the error when the `var` appeared **before** the function (`var a = ''; function a() {}`) but not the other way around, due to AST visit ordering
- `check_variable_declarator_redeclaration` unconditionally skipped the check at `Top | Function` scopes — now it falls through to the redeclaration check when in module mode at the top scope

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-02-06 09:12:12 +00:00
Boshen f53f0f017e refactor(span)!: SourceType::with_module(false) no longer sets script mode (#18861)
## Summary

- Add `SourceType::script()` constructor for creating script source types
- `with_module(bool)` now only sets module mode when `true`, does nothing when `false`
- This aligns with the pattern of other `with_*` methods like `with_script()`, `with_jsx()`, etc.

### Migration

Before:
```rust
// This used to set script mode when is_module is false
let source_type = SourceType::default().with_module(is_module);
```

After:
```rust
// Start from script and upgrade to module when needed
let source_type = SourceType::script().with_module(is_module);
```

🤖 Generated with [Claude Code](https://claude.ai/code)
2026-02-02 17:09:16 +00:00
renovate b67ab40408 chore(deps): update dependency rust to v1.93.0 (#18406)
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [rust](https://redirect.github.com/rust-lang/rust) | minor | `1.92.0` → `1.93.0` |

---

### Release Notes

<details>
<summary>rust-lang/rust (rust)</summary>

### [`v1.93.0`](https://redirect.github.com/rust-lang/rust/blob/HEAD/RELEASES.md#Version-1930-2026-01-22)

[Compare Source](https://redirect.github.com/rust-lang/rust/compare/1.92.0...1.93.0)

\==========================

<a id="1.93.0-Language"></a>

## Language

- [Stabilize several s390x `vector`-related target features and the `is_s390x_feature_detected!` macro](https://redirect.github.com/rust-lang/rust/pull/145656)
- [Stabilize declaration of C-style variadic functions for the `system` ABI](https://redirect.github.com/rust-lang/rust/pull/145954)
- [Emit error when using some keyword as a `cfg` predicate](https://redirect.github.com/rust-lang/rust/pull/146978)
- [Stabilize `asm_cfg`](https://redirect.github.com/rust-lang/rust/pull/147736)
- [During const-evaluation, support copying pointers byte-by-byte](https://redirect.github.com/rust-lang/rust/pull/148259)
- [LUB coercions now correctly handle function item types, and functions with differing safeties](https://redirect.github.com/rust-lang/rust/pull/148602)
- [Allow `const` items that contain mutable references to `static` (which is *very* unsafe, but not *always* UB)](https://redirect.github.com/rust-lang/rust/pull/148746)
- [Add warn-by-default `const_item_interior_mutations` lint to warn against calls which mutate interior mutable `const` items](https://redirect.github.com/rust-lang/rust/pull/148407)
- [Add warn-by-default `function_casts_as_integer` lint](https://redirect.github.com/rust-lang/rust/pull/141470)

<a id="1.93.0-Compiler"></a>

## Compiler

- [Stabilize `-Cjump-tables=bool`](https://redirect.github.com/rust-lang/rust/pull/145974). The flag was previously called `-Zno-jump-tables`.

<a id="1.93.0-Platform-Support"></a>

## Platform Support

- [Promote `riscv64a23-unknown-linux-gnu` to Tier 2 (without host tools)](https://redirect.github.com/rust-lang/rust/pull/148435)

Refer to Rust's [platform support page][platform-support-doc]
for more information on Rust's tiered platform support.

[platform-support-doc]: https://doc.rust-lang.org/rustc/platform-support.html

<a id="1.93.0-Libraries"></a>

## Libraries

- [Stop internally using `specialization` on the `Copy` trait as it is unsound in the presence of lifetime dependent `Copy` implementations. This may result in some performance regressions as some standard library APIs may now call `Clone::clone` instead of performing bitwise copies](https://redirect.github.com/rust-lang/rust/pull/135634)
- [Allow the global allocator to use thread-local storage and `std::thread::current()`](https://redirect.github.com/rust-lang/rust/pull/144465)
- [Make `BTree::append` not update existing keys when appending an entry which already exists](https://redirect.github.com/rust-lang/rust/pull/145628)
- [Don't require `T: RefUnwindSafe` for `vec::IntoIter<T>: UnwindSafe`](https://redirect.github.com/rust-lang/rust/pull/145665)

<a id="1.93.0-Stabilized-APIs"></a>

## Stabilized APIs

- [`<[MaybeUninit<T>]>::assume_init_drop`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_drop)
- [`<[MaybeUninit<T>]>::assume_init_ref`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_ref)
- [`<[MaybeUninit<T>]>::assume_init_mut`](https://doc.rust-lang.org/stable/core/primitive.slice.html#method.assume_init_mut)
- [`<[MaybeUninit<T>]>::write_copy_of_slice`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.write_copy_of_slice)
- [`<[MaybeUninit<T>]>::write_clone_of_slice`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.write_clone_of_slice)
- [`String::into_raw_parts`](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.into_raw_parts)
- [`Vec::into_raw_parts`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.into_raw_parts)
- [`<iN>::unchecked_neg`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_neg)
- [`<iN>::unchecked_shl`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_shl)
- [`<iN>::unchecked_shr`](https://doc.rust-lang.org/stable/std/primitive.isize.html#method.unchecked_shr)
- [`<uN>::unchecked_shl`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.unchecked_shl)
- [`<uN>::unchecked_shr`](https://doc.rust-lang.org/stable/std/primitive.usize.html#method.unchecked_shr)
- [`<[T]>::as_array`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.as_array)
- [`<[T]>::as_array_mut`](https://doc.rust-lang.org/stable/std/primitive.slice.html#method.as_mut_array)
- [`<*const [T]>::as_array`](https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.as_array)
- [`<*mut [T]>::as_array_mut`](https://doc.rust-lang.org/stable/std/primitive.pointer.html#method.as_mut_array)
- [`VecDeque::pop_front_if`](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.pop_front_if)
- [`VecDeque::pop_back_if`](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.pop_back_if)
- [`Duration::from_nanos_u128`](https://doc.rust-lang.org/stable/std/time/struct.Duration.html#method.from_nanos_u128)
- [`char::MAX_LEN_UTF8`](https://doc.rust-lang.org/stable/std/primitive.char.html#associatedconstant.MAX_LEN_UTF8)
- [`char::MAX_LEN_UTF16`](https://doc.rust-lang.org/stable/std/primitive.char.html#associatedconstant.MAX_LEN_UTF16)
- [`std::fmt::from_fn`](https://doc.rust-lang.org/stable/std/fmt/fn.from_fn.html)
- [`std::fmt::FromFn`](https://doc.rust-lang.org/stable/std/fmt/struct.FromFn.html)

<a id="1.93.0-Cargo"></a>

## Cargo

- [Enable CARGO\_CFG\_DEBUG\_ASSERTIONS in build scripts based on profile](https://redirect.github.com/rust-lang/cargo/pull/16160/)
- [In `cargo tree`, support long forms for `--format` variables](https://redirect.github.com/rust-lang/cargo/pull/16204/)
- [Add `--workspace` to `cargo clean`](https://redirect.github.com/rust-lang/cargo/pull/16263/)

<a id="1.93.0-Rustdoc"></a>

## Rustdoc

- [Remove `#![doc(document_private_items)]`](https://redirect.github.com/rust-lang/rust/pull/146495)
- [Include attribute and derive macros in search filters for "macros"](https://redirect.github.com/rust-lang/rust/pull/148176)
- [Include extern crates in search filters for `import`](https://redirect.github.com/rust-lang/rust/pull/148301)
- [Validate usage of crate-level doc attributes](https://redirect.github.com/rust-lang/rust/pull/149197).  This means if any of `html_favicon_url`, `html_logo_url`, `html_playground_url`, `issue_tracker_base_url`, or `html_no_source` either has a missing value, an unexpected value, or a value of the wrong type, rustdoc will emit the deny-by-default lint `rustdoc::invalid_doc_attributes`.

<a id="1.93.0-Compatibility-Notes"></a>

## Compatibility Notes

- [Introduce `pin_v2` into the builtin attributes namespace](https://redirect.github.com/rust-lang/rust/pull/139751)
- [Update bundled musl to 1.2.5](https://redirect.github.com/rust-lang/rust/pull/142682)
- [On Emscripten, the unwinding ABI used when compiling with `panic=unwind` was changed from the JS exception handling ABI to the wasm exception handling ABI.](https://redirect.github.com/rust-lang/rust/pull/147224) If linking C/C++ object files with Rust objects, `-fwasm-exceptions` must be passed to the linker now. On nightly Rust, it is possible to get the old behavior with `-Zwasm-emscripten-eh=false -Zbuild-std`, but it will be removed in a future release.
- The `#[test]` attribute, used to define tests, was previously ignored in various places where it had no meaning (e.g on trait methods or types). Putting the `#[test]` attribute in these places is no longer ignored, and will now result in an error; this may also result in errors when generating rustdoc. [Error when `test` attribute is applied to structs](https://redirect.github.com/rust-lang/rust/pull/147841)
- Cargo now sets the `CARGO_CFG_DEBUG_ASSERTIONS` environment variable in more situations. This will cause crates depending on `static-init` versions 1.0.1 to 1.0.3 to fail compilation with "failed to resolve: use of unresolved module or unlinked crate `parking_lot`". See [the linked issue](https://redirect.github.com/rust-lang/rust/issues/150646#issuecomment-3718964342) for details.
- [User written types in the `offset_of!` macro are now checked to be well formed.](https://redirect.github.com/rust-lang/rust/issues/150465/)
- `cargo publish` no longer emits `.crate` files as a final artifact for user access when the `build.build-dir` config is unset
- [Upgrade the `deref_nullptr` lint from warn-by-default to deny-by-default](https://redirect.github.com/rust-lang/rust/pull/148122)
- [Add future-incompatibility warning for `...` function parameters without a pattern outside of `extern` blocks](https://redirect.github.com/rust-lang/rust/pull/143619)
- [Introduce future-compatibility warning for `repr(C)` enums whose discriminant values do not fit into a `c_int` or `c_uint`](https://redirect.github.com/rust-lang/rust/pull/147017)
- [Introduce future-compatibility warning against ignoring `repr(C)` types as part of `repr(transparent)`](https://redirect.github.com/rust-lang/rust/pull/147185)

</details>

---

### Configuration

📅 **Schedule**: 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:eyJjcmVhdGVkSW5WZXIiOiI0Mi44NS4xIiwidXBkYXRlZEluVmVyIjoiNDIuODUuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-01-22 16:21:00 +00:00
Dunqing 22dec6ace5 feat(semantic)!: remove Scoping::scope_build_child_ids and all related APIs (#18362)
Remove `Scoping::scope_build_child_ids` and all related APIs to make the semantics cleaner. This could also make it easier for us to sync Scoping data since we don't need to worry about child scope IDs.

This is a breaking change, but it is okay because all use of `scope_build_child_ids` in the Oxc and Rolldown has been removed.
2026-01-22 07:46:26 +00:00
camc314 808b7c3b62 fix(semantic): resolve qualified names to namespaces, not shadowing type parameters (#17929)
fixes https://github.com/oxc-project/oxc/issues/17928
2026-01-19 09:42:34 +00:00
Dunqing e2221e683e feat(semantic): store scope_id in Reference (#18053)
https://github.com/rolldown/rolldown/pull/7936 relies on this. You can see how Rolldown uses this at description of that PR.

Additionally, it can also simplify the use case when you want to get the scope ID where the `Reference` occurs. See follow-up PR #18059
2026-01-19 04:54:05 +00:00
Boshen 4f45a2845a refactor(ast)!: replace TSMappedType.type_parameter with dedicated key and constraint fields (#18185)
## Summary

- Replace `type_parameter: Box<'a, TSTypeParameter<'a>>` with two dedicated fields:
  - `key: BindingIdentifier<'a>` - the parameter name (e.g., `P` in `[P in keyof T]`)
  - `constraint: TSType<'a>` - the constraint type (e.g., `keyof T`)
- Improves type safety by preventing invalid AST states - `TSTypeParameter` contains `const`, `in`, `out`, and `default` fields that are syntactically invalid in mapped types
- Matches TSESTree structure more closely

Closes #15101

## Test plan

- [x] All parser, codegen, semantic, and linter tests pass
- [x] Conformance tests show no regressions
- [x] Semantic snapshots updated to reflect the new structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-01-18 14:49:37 +00:00
Boshen 66b8c022ab feat(parser): implement unambiguous module parsing for JS/TS files (#18124)
## Summary

- Implements speculative/unambiguous parsing for `.js`, `.jsx`, `.ts`, and `.tsx` files
- Instead of assuming module or script upfront, parses with `ModuleKind::Unambiguous` and upgrades to module mode when ESM syntax is detected
- ESM indicators: `import`, `export`, `import.meta` (NOT top-level await alone)
- Defers top-level await errors until module type is resolved
- Vue loader upgrades unambiguous scripts to module mode
- Updates linter tests to use explicit ESM extensions (`.mjs`/`.mts`) where needed

## Test plan

- [x] All parser tests pass
- [x] All linter tests pass
- [x] All semantic tests pass
- [x] Coverage conformance tests updated

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-01-17 13:40:28 +00:00
Boshen 9135b0b2f3 feat(ast)!: move scope from TSEnumDeclaration to TSEnumBody (#18058)
## Summary

- Move `scope_id: Cell<Option<ScopeId>>` from `TSEnumDeclaration` to `TSEnumBody`
- Since `TSEnumBody` was introduced as a dedicated type in PR #10284, the scope logically belongs on the body rather than on the parent declaration node

Closes #10331

## Test plan

- [x] Unit tests pass (`just test`)
- [x] Conformance tests pass (`just conformance`)
- [x] Updated semantic test snapshots reflect scope now on `TSEnumBody`

🤖 Generated with [Claude Code](https://claude.ai/code)
2026-01-16 13:30:08 +00:00
Boshen eee03429bd chore(ci): test 32-bit (#17385)
closes #16099

---------

Signed-off-by: Boshen <boshenc@gmail.com>
2026-01-14 20:21:14 +08:00
camc314 3984750c69 refactor(all)!: remove binding pattern wierdness (#15925)
fixes #11489

Note, in quite a few places i changed the span we report on to look something like this, the only reason for this is to reduce the diff in some of the snapshots
```
decl.type_annotation.as_ref().map_or_else(
                        || decl.id.span(),
                        |type_annotation| decl.id.span().merge(type_annotation.span()),
                    ),
```
2025-12-22 10:04:20 +00:00
copilot-swe-agent 74cf5720bc feat(ast)!: Make source field of TSImportType a StringLiteral (#16114)
- [x] Change `source` field type in `TSImportType` from `TSType<'a>` to `StringLiteral<'a>` in `crates/oxc_ast/src/ast/ts.rs`
- [x] Remove `#[estree(via = TSImportTypeSource)]` attribute from the field
- [x] Run `just ast` to regenerate all derived code
- [x] Update the parser in `crates/oxc_parser/src/ts/types.rs` to parse a `StringLiteral` directly and emit errors for non-string literals
- [x] Delete the dead code `TSImportTypeSource` serializer from `crates/oxc_ast/src/serialize/ts.rs`
- [x] Add `ts_string_literal_expected` diagnostic (TS1141)
- [x] Update semantic snapshot for `import-type-with-type-params.snap`
- [x] Add test case for invalid import type source
- [x] Verify all tests pass
- [x] Simplify error recovery - just report error and create dummy string literal (per review feedback)
- [x] Fix spurious "Expected ')'" error by skipping invalid tokens in error recovery

<!-- START COPILOT CODING AGENT SUFFIX -->

<details>

<summary>Original prompt</summary>

>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>AST: Make `source` field of `TSImportType` a `StringLiteral`</issue_title>
> <issue_description>Continuation of oxc-project/oxc#16074.
>
> The expression inside `import(...)` in TS context (`TSImportType`) cannot legally be anything other than a string literal.
>
> Our parser is too liberal. We currently accept all kind of crazy stuff like:
>
> ```ts
> type A = typeof import(`react`);
> type B = typeof import(A);
> type C = typeof import(`${A} ${B}`);
> type D = typeof import(typeof import('react'));
> ```
>
> [Playground](https://playground.oxc.rs/#eNptU9tOGzEQ/ZXIQqJIqUSRKqFUfQjQSJVooaUqL31g1jsbDF7bHc/molX+nfEmdkDhyfaZ+5njXmk1UbwOOJqOvo7SxTcj0wZP/OGBEDQ/nHz55waPiwOPabFdHkYf9dPN6Ki/2OwzXB14vX0dDxWPT1KAGiuvJr2izqXDGsdq0oCNOFaNpxaYkQrCBC4muCAmeguM9RVqCwRsvIvFuHw0jDGAxgK14OZ2/9S+DYRxHxK1D2Jm6tJj3Vbe5pdu5ju3zVgFoJga6xWuGF2UuoniKPOAtX75G7kjd9NxNDXOOqdTZzlTKom0wFsgCc3oEPf3/LtjMpJQF0NE6ZqN/kbkaYdKC4mroQW5v6KqV13EP1DtZ2Ko7k3Nj2pyNlbo6pvm2jgZUtlG2g1SjXf281OpZhJDvzrPe5qe4uruHfh/et2SD1JLQfzoEGusJafsyUh780vftpBs1goqc5gyrMyyzPOLfQnrxF1FoJ+R72RnEp6dMwotbjvf1ffVE2q+JwiSI5OaCg29TlmIrDrpEOlNGK4CkmnRMdgbuQJ7uvXRbFeUGFJCadHallQGmqNoU2E8O/30WaoIzVfYSOKZp0sLMc4M2rps7XUVUacfyuytreGC/kCGGliY2q32fVX3KspEQQSC5EB0mcWofY1zHD6QS0Iokn2KtZevv6PcOc9DroxYnGffIUv+C0lR+aPI5D5c4wJL0mfE8FNWkQU2hIpovZ2JgFPEAqnyMROe5nFpUclUbhuB64G+BJfbJuGJZoisNi/WFa5W)
>
> TypeScript says these are all errors: [TS Playground](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAglC8VSQPYDMoEsC2YUCdgAKAA3wgEMBjYEgSgG4AoZaAIQSXAnS1wOIxGLblADCnVrxx5CpACQBvGAF8oStivrNWUACKTu0-nKkYZAogHJy1YFbrCgA)
>
> ### AST change
>
> We should alter the type of the `source` field from `TSType<'a>` to `StringLiteral<'a>`:
>
> https://github.com/oxc-project/oxc/blob/35d0ee491cbcd271ebceaac7e25cff0490ded5c4/crates/oxc_ast/src/ast/ts.rs#L1382-L1389
>
> ```diff
> pub struct TSImportType<'a> {
>     pub span: Span,
> -   #[estree(via = TSImportTypeSource)]
> -   pub source: TSType<'a>,
> +   pub source: StringLiteral<'a>,
>     pub options: Option<Box<'a, ObjectExpression<'a>>>,
>     pub qualifier: Option<TSImportTypeQualifier<'a>>,
>     pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
> }
> ```
>
> ### Parser changes
>
> If we change the AST first and run `just ast` to regenerate traits code, then all the places which rely on the type of `source` field will become apparent. We'll then need to alter the parser to throw an error if it encounters something that isn't a string literal.
>
> ### `ESTree` serialization
>
> Currently there's a custom deserializer to alter the AST on JS side from `TSType` to `StringLiteral`:
>
> https://github.com/oxc-project/oxc/blob/91eb3f2be509cef11cffeff51fac61a3a4fbd12c/crates/oxc_ast/src/serialize/ts.rs#L506-L538
>
> Once we've changed the field type in AST, this code can be deleted - it's dead code once `#[estree(via = TSImportTypeSource)]` attribute is removed from the `source` field in AST struct.</issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>

</details>

- Fixes oxc-project/oxc#16111

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
2025-11-25 18:51:16 +00:00
overlookmotel cbb27fd51b fix(ast)!: add TSGlobalDeclaration type (#15712)
Fix #14860.

### The problem

`TSModuleDeclaration` type was used to represent 4 different syntaxes:

* `namespace X {}`
* `module X {}`
* `module "x" {}`
* `global {}`

(and all the above prefixed with `declare`)

`global {}` is quite different from the others in a few ways:

* It has no identifier (`global` is a keyword, not an identifier).
* It has no symbol.
* The `body` block is not optional.
* Directives are not allowed in the `body` block.
* It is always ambient context (regardless of whether it has `declare` modifier).

In particular, it was causing panics because `symbol_id` field for `TSModuleDeclaration`s representing `global {}` was `None`.

### Solution

Rather than trying to make `TSModuleDeclaration` type encode all these differences, and make Oxc's code reason about them, split `global {}` into a new type `TSGlobalDeclaration`.

The rest of the diff is just adjusting code to also handle `TSGlobalDeclaration` where it was dealing with `TSModuleDeclaration`s.

This change simplifies the parser quite a bit.

`oxc_semantic`'s `SymbolDeclarationTest` tester now tests that all `TSModuleDeclaration`s with `id: BindingIdentifier` do have a symbol:

https://github.com/oxc-project/oxc/pull/15712/files#diff-ce544d71b839faa898887ac920237e563e59a708e7fca86b53023498ab67a611L101-R110

### Formatter bug

There is one regression in the formatter for `typescript/module/global.ts` case.

https://github.com/oxc-project/oxc/pull/15712/files#diff-f248b1d49b8c482864810055632d7322cdebc895dd4dfe05fbefcf5e23ba12c9R51

Input:

```ts
declare /* module */ global {}
declare /* namespace */ global {}
```

Output:

```diff
- declare /* module */ global {}
- declare /* namespace */ global {}
+ declare global /* module */ {}
+ declare global /* namespace */ {}
```

I assume this is happening because there's no longer a `BindingIdentifier` for the `global` keyword for the comment to get attached to.

I am not sure how to fix this. Maybe the `global_span` field can help?

@Dunqing This PR touches a *lot* of files. Can I propose that we merge it before it gets conflicts, and if you can give me some guidance, I'll do a follow-up PR to fix it?
2025-11-17 19:25:24 +00:00
Tyler Earls b2af6b5edf refactor(linter): Remove AstKind for Argument (#13902)
Part of https://github.com/oxc-project/oxc/issues/11490

### Motivation

`AstKind::Argument` was an architectural inconsistency. The AstKind system is designed for struct types, but `Argument` is an enum (`SpreadElement | Expression`). This created unnecessary complexity in the AST traversal system.

### Solution

Replace AST-based argument detection with span-based utilities. Instead of traversing the tree to find `AstKind::Argument` nodes, we now use direct span containment checks.

### New Utilities

**Added to `oxc_linter/src/ast_util.rs`:**

```rust
pub fn is_node_exact_call_argument<'a>(
    node: &AstNode<'a>,
    ctx: &LintContext<'a>
) -> bool
```
Checks if a node is exactly a direct argument to its parent call
expression by checking if the parent is a call and the node's span
matches one of the argument spans.

```rust
pub fn is_node_within_call_argument<'a>(
    node: &AstNode<'a>,
    call: &CallExpression<'a>,
    target_arg_index: usize,
) -> bool
```
Checks if a node is nested within a specific argument at the given index
of the provided call expression.

**Added to `oxc_ast/src/ast_kind_impl.rs`:**

```rust
pub fn has_argument_with_span(&self, span: Span) -> bool
```
Checks if this `AstKind` is a call expression with an argument matching
the given span.

```rust
pub fn is_callee_with_span(&self, span: Span) -> bool
```
Checks if this `AstKind` is a call expression whose callee matches the
given span.

### Architecture Change

**Before:**
```rust
// Required parent traversal and state management
match parent.kind() {
    AstKind::Argument { .. } => {
        // Check grandparent for call context
    }
}
```

**After:**
```rust
// Direct span-based detection
if is_node_exact_call_argument(node, ctx) {
    // Node is a direct call argument
}

if is_node_within_call_argument(node, call, 0) {
    // Node is within first argument
}
```

---------

Co-authored-by: Cameron Clark <cameron.clark@hey.com>
2025-11-11 15:55:53 +00:00
camc314 109f4520de refactor(semantic): eliminate manual current_node_ix manipulation in try statements (#14858)
fixes the `TODO`​ comments left in the code
2025-10-21 16:35:34 +00:00
camc314 cdf2d07e6a fix(semantic): add condition basic blocks to CFG for logical expressions (#14671)
Add explicit condition basic blocks in the control flow graph for logical
expressions and logical assignment operators. This improves CFG accuracy by
representing condition evaluation as distinct nodes.

Changes:
- Create condition basic blocks before evaluating left operands of logical expressions (&&, ||, ??)
- Record and attach AST nodes to condition blocks for better traceability
- Add edges from preceding blocks to condition blocks
- Apply same treatment to logical assignment operators (&&=, ||=, ??=)

This affects CFG generation for:
- LogicalExpression nodes
- AssignmentExpression nodes with logical operators
2025-10-17 10:46:39 +00:00
camc314 ae2003c656 feat(semantic): add symbol_id to ts function type binding idents (#14673)
fixes #14668
2025-10-17 04:30:29 +00:00
camc314 bec7a7da62 feat(semantic): add scope to TSConstructorType (#14676) 2025-10-17 01:59:44 +00:00
camc314 f45d2f0b23 feat(semantic): add scope to TSCallSignatureDeclaration (#14672)
This is required else:

```
interface X {
    (a): void
    (a): void
}
```

will result in a semantic error or `a`​ being declared twice
2025-10-16 16:09:53 +00:00
Boshen be94bfd401 fix(semantic): add scope tracking for with statements (#14652)
Fixes #8365

## Summary

The `with` statement was not creating a scope in the semantic analysis, causing `is_global_reference` to incorrectly return `true` for references inside `with` blocks. This could lead to incorrect minification.

## Problem

According to the ECMAScript specification, `with` statements should create an Object Environment Record that extends the lexical environment. Without proper scope tracking:

- `is_global_reference()` incorrectly returns `true` for identifiers inside `with` blocks
- This can cause minification to incorrectly treat local references as global
- The scope tree doesn't accurately represent the program structure

## Solution

### Changes Made

1. **AST Definition** (`crates/oxc_ast/src/ast/js.rs`):
   - Added `#[scope]` attribute to `WithStatement` struct
   - Added `scope_id: Cell<Option<ScopeId>>` field

2. **Semantic Builder** (`crates/oxc_semantic/src/builder.rs`):
   - Modified `visit_with_statement` to call `enter_scope()` before visiting the body
   - Added `leave_scope()` call after visiting the body
   - Uses `ScopeFlags::empty()` similar to block statements

3. **Test Coverage** (`crates/oxc_semantic/tests/integration/scopes.rs`):
   - Added `test_with_statement` to verify scope creation
   - Tests both simple expressions and block statement bodies
   - Verifies proper scope tree structure

### Example

```javascript
const foo = {
  Object: class {
    constructor() { console.log('Constructor') }
  }
}
with (foo) {
  console.log(new Object()) // should print "Constructor"
}
```

Before this fix, `Object` inside the `with` block would be incorrectly identified as a global reference, potentially causing minification errors.

## Implementation Notes

While this implementation doesn't fully model the complex object environment semantics (where property lookups can dynamically affect identifier resolution at runtime), it ensures:

- References inside `with` blocks are no longer incorrectly marked as global
- The scope tree properly represents the lexical structure
- Provides a foundation for future improvements to object environment handling

## Testing

All existing tests pass, plus the new test verifies:
- `with` statements create scopes
- Scopes are properly nested in the scope tree
- Works correctly with both simple and block statement bodies
2025-10-16 10:02:06 +00:00
Boshen 3656908d0e feat(rust): oxc-index-vec v4.0 (#14254)
Closes https://github.com/oxc-project/backlog/issues/109

Also did a bunch of macro stuff to reduce the boilerplates.
2025-10-04 04:58:42 +00:00
Boshen 08c05df60c perf(semantic): make CFG construction a compile-time feature (#13678)
part of #13646

## Summary

This PR converts the Control Flow Graph (CFG) functionality in `oxc_semantic` from runtime checks to a compile-time cargo feature, eliminating runtime overhead when CFG is not needed.

## Changes

- Added `cfg` cargo feature to `oxc_semantic/Cargo.toml` with `oxc_cfg` as optional dependency
- Feature-gated all CFG-related code using `#[cfg(feature = "cfg")]`
- Updated `oxc_linter` to enable the `cfg` feature (since it requires CFG)
- Made the `control_flow!` macro work correctly with and without the feature

## Benefits

- **Performance**: Eliminates runtime `Option<CFG>` checks when feature is disabled
- **Binary size**: Smaller binaries for crates that don't need CFG
- **Cleaner API**: CFG methods only available when feature is enabled

## Test Plan

- [x] All existing tests pass with `cargo test -p oxc_semantic --features cfg`
- [x] `oxc_linter` builds and works correctly (it enables the feature)
- [x] Feature is backward compatible - existing code continues to work

🤖 Generated with [Claude Code](https://claude.ai/code)
2025-09-11 12:44:19 +00:00
overlookmotel c072e0120d refactor(all): add missing lifetimes in function return types (#12895)
Where a type being returned by a function has lifetimes, specify them with `'_` instead of omitting them entirely. `Cow<str>` -> `Cow<'_, str>`.

This is split out from Rust upgrade PR #12873, so can see the diff in that PR more clearly.

Note: This is very slightly different from what was in #12873. Clippy auto-fix introduced a couple of unnecessary and weird-looking anonymous lifetimes in function params, which this PR skips.
2025-08-08 08:40:13 +00:00