Commit Graph

20310 Commits

Author SHA1 Message Date
Dunqing 6610acd483 refactor(str): simplify JavaScript string append slow path
Rechecking properties already established by the UTF-8 fast path
obscures the slow path's precondition.

Implemented with AI assistance.
2026-09-10 11:00:22 +08:00
Dunqing 114482ca6a refactor(str): remove unused JavaScript string APIs
Keep the initial public API limited to operations needed by consumers.
Test fixtures can build values through a private helper; unused
convenience conversions can be added when callers need them.

Implemented with AI assistance.
2026-09-09 10:35:49 +08:00
Dunqing 5aabfe309f refactor(str): align JavaScript string API names
Follow the existing string builder and Rust naming conventions so the
new API is familiar before callers adopt it.

Implemented with AI assistance.
2026-09-08 16:16:50 +08:00
Dunqing 62f1ecb27d refactor(str): simplify JavaScript string primitives
Defer metadata packing and benchmarks until a concrete performance need
arises. Keep the initial API review focused on correctness and safety.
2026-09-08 15:16:53 +08:00
Dunqing 75dd063560 feat(str): add JavaScript string primitives
JavaScript strings can contain lone surrogates that Rust str cannot
represent. Introduce arena-backed primitives with explicit encoding
invariants before migrating AST consumers.

Part of #26242.
2026-09-08 14:58:00 +08:00
leaysgur a7f02a320c refactor(formatter): share the gap-segment walk and cursor helpers in Comments (#26433)
Pure refactoring;
2026-09-08 06:02:03 +00:00
leaysgur 44694ddc43 refactor(formatter): group Comments methods by cursor-based, position-based, and state (#26432)
Just reordering `impl` methods, and also update comments.
2026-09-08 06:02:02 +00:00
leaysgur 70918a3448 refactor(formatter): make is_cast_target position-based (#26431)
We use `preserve_parens: false`, need to detect typecast `()` from source, always.
2026-09-08 06:02:02 +00:00
leaysgur b26bf56cd5 refactor(formatter): route every assignment RHS shape rule through a cast-aware accessor (#26430)
Follow up on #26429, apply cast-aware behavior to assignment call sites.
2026-09-08 06:02:01 +00:00
leaysgur 33cdc841ac fix(formatter): keep cast-target assignment and commented parameter patterns (#26429)
Fixes #26425, #26426.

- Exclude cast target from never-break check
- Use position based comment check
2026-09-08 06:02:01 +00:00
leaysgur 876fbe30f1 fix(oxfmt): expand Fill parts in tsx-in-vue (#26427)
Fixes #26398, closes #26399.

---

Our Rust printer knows how to expand `Fill` with `Interned`, but Prettier printer does not.
2026-09-08 01:29:38 +00:00
camc314 32d00c55bc fix(codegen): preserve instantiation expression precedence (#26424)
Preserve parentheses around TypeScript instantiation expressions in postfix contexts. Previously, `(f<T>).x` and `(f<T>)[x]` were emitted as `f<T>.x` and `f<T>[x]`, producing invalid syntax or changing how the expression is parsed.

The printer now respects the surrounding precedence while retaining grouping for lower-precedence operands.
2026-09-07 18:20:11 +00:00
camc314 cfa47ab396 fix(parser): allow in expressions in class static blocks (#26423)
Class static blocks inherit the disabled `in` context of a surrounding `for` initializer, causing valid code such as `for (class { static { a in b; } };;);` to report a missing semicolon at `in`. Enable `Context::In` while parsing the static block, with the existing context helper restoring the surrounding flags afterward.
2026-09-07 18:06:57 +00:00
camc314 598618793c fix(packages/codegen): preserve private-in right operand precedence (#26420)
`packages/codegen` drops required parentheses around the right operand of a private-in expression when that operand is another relational expression. This changes which value the private-field check receives and can change observable behavior.

For example, this input throws a `TypeError`:

```js
class C {
  #x;
  test(a, b) {
    return #x in (a instanceof b);
  }
}
new C().test({}, Object);
```

`a instanceof b` evaluates to `true`, but a private-field check requires an object on its right-hand side.

The JavaScript printer previously emitted:

```js
return #x in a instanceof b;
```

Relational operators associate to the left, so that output is parsed as `(#x in a) instanceof b`. The private-field check on `{}` produces `false`, and `false instanceof Object` returns `false` instead of throwing.

The printer now preserves the original grouping:

```js
return #x in (a instanceof b);
```

`printPrivateInExpression` now prints its right operand with `PREC_COMPARE` instead of `PREC_EQUALS`. The expression printers wrap operands whose precedence is equal to or lower than the requested precedence, so the comparison threshold retains parentheses around `<`, `<=`, `>`, `>=`, `instanceof`, and nested private-in expressions. The now-unused `PREC_EQUALS` import is removed.

Equality and lower-precedence operands continue to retain their required grouping. Shift, arithmetic, and exponentiation operands can still omit unnecessary parentheses. The existing `CTX_FORBID_IN` handling remains in place.

This applies to both ordinary and source-map-enabled printer builds and brings the JavaScript implementation in line with the Rust fix in #26411. It addresses grouping of the private-in right operand, separately from the left-operand correction in #26403.
2026-09-07 17:54:59 +00:00
camc314 f8e6c6ccf8 fix(packages/codegen): preserve in restriction through yield arguments (#26421)
`packages/codegen` can turn valid JavaScript into unparseable output when a `yield` expression appears in a classic `for` loop initializer and its argument contains an `in` operator.

For example, this input is valid:

```js
function* g(o) {
  for (yield (1 in o); false;);
}
```

The JavaScript printer previously dropped the required grouping:

```js
function* g(o) {
  for (yield 1 in o; false;);
}
```

An unparenthesized `in` is not allowed at this position in a classic `for` initializer. The same problem affects `yield*`, nested yields, and assignments inside the yield argument.

The printer now preserves the parentheses:

```js
function* g(o) {
  for (yield (1 in o); false;);
}
```

The `for` printer already marks its initializer with `CTX_FORBID_IN`, but `printYieldExpression` discarded that context and printed its argument with `CTX_NONE`. The yield dispatch now passes its incoming context to `printYieldExpression`, which forwards only `CTX_FORBID_IN` to the argument when the yield expression is unwrapped. The argument retains its existing `PREC_YIELD` precedence.

When precedence requires parentheses around the entire yield expression, those parentheses already permit `in` inside. The argument context is cleared in that case, avoiding redundant inner parentheses:

```js
// Input
function* g(o) {
  for ((yield (1 in o)) + 1; false;);
}

// Output
function* g(o) {
  for ((yield 1 in o) + 1; false;);
}
```

Both `yield` and `yield*` use this path. Outside a restricted initializer, ordinary yields continue to omit unnecessary grouping: `yield (1 in o)` prints as `yield 1 in o`.

The correction applies to both ordinary and source-map-enabled builds and brings the JavaScript printer in line with the Rust fix in #26413.
2026-09-07 17:47:59 +00:00
camc314 d61e3bf887 fix(parser): validate TS named tuple rest elements (#26419)
## Problem

Named tuple rest elements bypass the parser's existing checks for optional elements after an array rest and for multiple array rests. For example, both of these declarations currently parse successfully even though TypeScript rejects them:

```ts
type OptionalAfterRest = [...a: string[], b?: number];
type RestAfterRest = [...a: string[], ...b: number[]];
```

The equivalent unnamed forms already produce TS1266 and TS1265, respectively. Mixed named and unnamed array rests have the same gap when either rest is named.

## Cause and change

`parse_tuple_type` inspects `TSRestType.type_annotation` to decide whether an element is an array rest. For a named element, that annotation is a `TSNamedTupleMember`, and the actual type is stored in its `element_type`. Matching directly on the outer annotation therefore skips the array check and fails to track the rest element.

Unwrap the named member with `as_ts_type()` before applying the existing classification. Named and unnamed elements then share the same validation and existing diagnostics:

- An optional element after an array rest reports TS1266.
- A second array rest reports TS1265, including mixed named and unnamed forms.

The diagnostic spans continue to use the complete tuple elements, including their labels. The change borrows the underlying type without changing the AST representation or introducing allocations.

## Preserved behavior

A rest marker alone does not make a spread an unbounded array. Fixed-tuple and generic variadic spreads continue to be accepted, including:

```ts
type FixedBeforeOptional = [...a: [string], b?: number];
type RestBeforeFixed = [...a: string[], ...b: [number]];
type VariadicBeforeOptional<T extends unknown[]> = [...a: T, b?: number];
type RestBeforeVariadic<T extends unknown[]> = [...a: string[], ...b: T];
type MultipleVariadic<T extends unknown[], U extends unknown[]> = [...a: T, ...b: U];
```

This change preserves the existing type-reference classification; it does not add type resolution or change how shadowed `Array` references are handled.
2026-09-07 17:43:20 +00:00
camc314 5cd6da4464 perf(ci): skip Minification cache setup when checks are skipped (#26418)
Skip Namespace Rust cache setup when change detection skips the Minification Size check.
2026-09-07 17:32:28 +00:00
camc314 ae6c386ed5 fix(codegen): preserve in restriction through yield arguments (#26413)
## Problem

A `yield` expression in a classic `for` loop initializer can lose the parentheses required around an `in` operator in its argument. This turns valid JavaScript into output that cannot be parsed, in both default and minified code generation.

For example, this input is valid:

```js
function *g(o) {
  for (yield (1 in o); false;);
}
```

Previously, codegen emitted:

```js
// Default output
function* g(o) {
  for (yield 1 in o; false;);
}

// Minified output
function*g(o){for(yield 1 in o;false;);}
```

The unparenthesized `in` is not allowed at this position in a classic `for` initializer. The same restriction applies to the argument of `yield*`, and can also be lost through nested yields or an assignment inside the yield argument.

## Change

`ForStatement` already prints its initializer with `Context::FORBID_IN`. However, `YieldExpression::gen_expr` ignored its incoming context and always printed its argument with `Context::empty()`.

Preserve `FORBID_IN` when printing the argument of an unwrapped yield expression. This lets the existing expression printers retain the required grouping:

```js
// Default output
function* g(o) {
  for (yield (1 in o); false;);
}

// Minified output
function*g(o){for(yield(1 in o);false;);}
```

Only the relevant `FORBID_IN` flag is forwarded; the argument's existing precedence remains `Precedence::Yield`. Both `yield` and `yield*` use this path.

## Parentheses around yield

When precedence requires parentheses around the entire yield expression, those parentheses already allow `in` in the argument. Clear the restriction in that case to avoid redundant inner grouping:

```js
// Input
function *g(o) {
  for ((yield (1 in o)) + 1; false;);
}

// Default output
function* g(o) {
  for ((yield 1 in o) + 1; false;);
}

// Minified output
function*g(o){for((yield 1 in o)+1;false;);}
```

Outside a restricted initializer, ordinary yield expressions continue to omit unnecessary parentheses: `yield (1 in o)` prints as `yield 1 in o`.
2026-09-07 17:16:18 +00:00
camc314 42ac916b21 fix(codegen): preserve private-in right operand precedence (#26411)
Private-in expressions can lose required parentheses around their right-hand operand in both default and minified code generation. When the operand is another relational expression, removing the grouping changes which value the private-field check receives and can change observable behavior.

For example:

```js
class D {
  #x;
  test(a, b) {
    return #x in (a instanceof b);
  }
}

new D().test({}, Object);
```

The original throws a `TypeError`: `a instanceof b` evaluates to `true`, and the private-field check requires an object as its right-hand operand. Previously, codegen emitted:

```js
return #x in a instanceof b;
```

Relational operators associate to the left, so this is parsed as `(#x in a) instanceof b`. The private-field check on `{}` produces `false`, and `false instanceof Object` then returns `false` instead of throwing.

The corrected output preserves the grouping in both modes:

```js
// Default output
return #x in (a instanceof b);

// Minified output
return#x in (a instanceof b)
```

`PrivateInExpression::gen_expr` now prints its right-hand operand with `Precedence::Compare` instead of `Precedence::Equals`. The printer wraps expressions whose precedence is equal to or lower than the requested precedence. Using `Compare` therefore preserves parentheses around relational operands, including `<`, `<=`, `>`, `>=`, `instanceof`, and nested private-in expressions. Equality and lower-precedence expressions continue to retain their required grouping, while shift, arithmetic, and exponentiation operands can still omit unnecessary parentheses.

This addresses the RHS grouping issue separately from #26383, which fixed the precedence passed to private-in expressions appearing as the left operand of another binary expression. The change is confined to the Rust `oxc_codegen` crate.
2026-09-07 17:10:19 +00:00
camc314 81fd4825a1 perf(ci): skip Allocations cache setup when checks are skipped (#26410)
Skip Namespace Rust cache setup when change detection skips the Allocations check.
2026-09-07 16:10:09 +00:00
camc314 10521b288f fix(parser): allow escaped type default import bindings (#26409)
TypeScript allows a default import binding named `type` to contain Unicode escapes. Oxc currently reports `Keywords cannot contain escape characters` for these valid imports:

```ts
import t\u0079pe from "m";
import t\u0079pe, { x } from "m";
import t\u0079pe, * as ns from "m";
```

In each example, `t\u0079pe` is an identifier whose decoded name is `type`, and the declaration is a value import.

The parser initially recognizes the decoded `type` token before it has enough context to distinguish a default binding from a type-only import modifier. The escape check ran at that point, so it rejected the binding even when the following tokens established that this was a value import.

Move the escape check into the two branches that interpret `type` as a modifier: before a named or namespace import, and before another binding identifier after ruling out `import type from "m"`. The latter check also runs before the early return for a type-only import-equals declaration.

Escaped modifiers remain invalid, including these forms:

```ts
import t\u0079pe Foo = require("m");
import t\u0079pe { x } from "m";
import t\u0079pe * as ns from "m";
import { t\u0079pe Bar } from "m";
```

The change is limited to when the declaration-level escape diagnostic is emitted; import classification and specifier parsing retain their existing behavior.
2026-09-07 16:03:31 +00:00
renovate[bot] f4eacfcfef chore(deps): update oxc to ^0.149.0 (#26412)
This PR contains the following updates:

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

---

### Release Notes

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

###
[`v0.149.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/napi/parser/CHANGELOG.md#01490---2026-09-07)

##### 🐛 Bug Fixes

-
[`c966aca`](https://redirect.github.com/oxc-project/oxc/commit/c966aca)
parser: Do not omit `<` token opening type argument list
([#&#8203;26328](https://redirect.github.com/oxc-project/oxc/issues/26328))
(overlookmotel)

</details>

---

### Configuration

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

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

🚦 **Automerge**: Enabled.

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

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC42OS4xIiwidXBkYXRlZEluVmVyIjoiNDQuNjkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-07 16:03:12 +00:00
camc314 72cb5e3539 fix(parser): reject rest parameters in getters (#26400)
## Summary

Getter accessors must have no formal parameters, but Oxc currently accepts a getter whose only parameter is a rest parameter. This affects class and object getters, as well as getter signatures in TypeScript interfaces and type literals.

```ts
class C { get x(...args) {} }
({ get x(...args) {} });
interface I { get x(...args: any[]): string }
type T = { get x(...args: any[]): string };
```

Each declaration above previously parsed without a diagnostic. It now reports the existing error: `A 'get' accessor must not have any formal parameters.`

## Cause and correction

`FormalParameters` stores ordinary parameters in `items` and the rest parameter separately in `rest`. Both getter checks inspected only whether `items` was empty, so a rest-only parameter list looked like a zero-parameter list.

Use `parameters_count() != 0` in both checks so the count includes the rest parameter. The shared `check_getter` path covers class and object getters; `parse_getter_setter_signature_member` covers interface and type-literal getter signatures.

The diagnostic still points at the full parameter list. Zero-parameter getters remain valid, ordinary getter parameters retain their existing diagnostic, and class/object accessors with type parameters retain the current type-parameter diagnostic precedence. Setter validation is unchanged.
2026-09-07 15:34:10 +00:00
camc314 6e15ad5cfb fix(packages/codegen): print matching quoted import names as identifiers (#26404)
Oxc codegen npm package emits invalid JavaScript when a quoted imported name matches its local binding. The printer compares the decoded names and omits the alias, but leaves the imported name quoted. A quoted import name requires an explicit `as` binding.

For this valid input:

```js
import { "foo" as foo } from "m";
```

The JavaScript printer previously emitted:

```js
import { "foo" } from "m";
```

It now prints the matching local binding as identifier shorthand:

```js
import { foo } from "m";
```

`printImportDeclaration` checks for a string-literal imported name whose decoded value equals the local binding name before printing the imported name. In that case, it prints the local identifier directly, including its source-map mapping. The local binding already supplies a valid identifier, so this requires neither a separate identifier-validity check nor an AST mutation.

Comparing the decoded value also handles escaped and Unicode import names:

```js
// Input
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";

// Output
import { ab } from "m";
import { π } from "m";
```

Declaration-level and inline TypeScript type imports use the same shorthand:

```ts
// Input
import type { "Foo" as Foo } from "m";
import { type "Bar" as Bar } from "m";

// Output
import type { Foo } from "m";
import { type Bar } from "m";
```

Quoted names that differ from their local bindings retain their quotes and aliases, including `"foo-bar" as foo`, `"" as foo`, and `"default" as foo`. Ordinary identifier imports keep their existing shorthand behavior. The correction applies to both the ordinary and source-map-enabled builds and brings the JavaScript printer in line with the Rust codegen fix in #26386.
2026-09-07 15:28:07 +00:00
camc314 bbbb4bc5c9 fix(packages/codegen): preserve private-in left operand precedence (#26403)
The codegen npm package drops required parentheses when a private-in expression is the left operand of a higher-precedence binary operator. The generated JavaScript can evaluate a different expression and throw a runtime error.

For example, this input returns `2`:

```js
class C {
  #x;
  test(o) {
    return (#x in o) + 1;
  }
}
console.log(new C().test(new C()));
```

The JavaScript printer previously emitted the return expression as:

```js
return #x in o + 1;
```

Because `+` binds more tightly than `in`, that output is parsed as `#x in (o + 1)`. Adding `1` to the object produces a primitive, so the private-field check throws a `TypeError` instead of producing the boolean used by the original addition.

The printer now preserves the grouping:

```js
return (#x in o) + 1;
```

The iterative binary-expression printer already computes the precedence required for each left operand. Its private-in special case discarded that value and passed `PREC_LOWEST` to `printPrivateInExpression`. Passing `v.leftPrecedence` instead lets the existing private-in printer insert the required parentheses.

This preserves grouping for arithmetic, exponentiation, and shift operators. Equal- and lower-precedence operators, such as `<`, `===`, and `&&`, continue to omit unnecessary parentheses. The correction applies to both the ordinary and source-map-enabled JavaScript printer builds and brings this path in line with the Rust codegen fix in #26383.
2026-09-07 15:21:31 +00:00
renovate[bot] 7e9508d1e1 chore(deps): update dependency oxfmt to ^0.67.0 (#26407)
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/) |
|---|---|---|---|---|---|
| [oxfmt](https://oxc.rs/docs/guide/usage/formatter)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt))
| [`^0.66.0` →
`^0.67.0`](https://renovatebot.com/diffs/npm/oxfmt/0.66.0/0.67.0) |
![age](https://developer.mend.io/api/mc/badges/age/npm/oxfmt/0.67.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxfmt/0.67.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxfmt/0.66.0/0.67.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxfmt/0.66.0/0.67.0?slim=true)
|

---

### Release Notes

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

###
[`v0.67.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxfmt/CHANGELOG.md#0670---2026-09-07)

##### 🛡️ Security

-
[`9a612d5`](https://redirect.github.com/oxc-project/oxc/commit/9a612d5)
deps: Update npm packages
([#&#8203;26178](https://redirect.github.com/oxc-project/oxc/issues/26178))
(renovate\[bot])

</details>

---

### Configuration

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

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

🚦 **Automerge**: Enabled.

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-07 15:21:06 +00:00
camc314 a111b5b340 fix(packages/codegen): print accessibility modifiers before abstract (#26402)
`packages/codegen` emits invalid TypeScript for abstract accessors with an explicit accessibility modifier. The JavaScript printer writes `abstract` before `public` or `protected`, but TypeScript requires accessibility to come first and reports TS1029 for the generated declaration.

For example, this valid input:

```ts
abstract class C {
  public abstract accessor x: number;
  protected abstract accessor y: string;
}
```

was printed as:

```ts
abstract class C {
  abstract public accessor x: number;
  abstract protected accessor y: string;
}
```

The printer now preserves the valid modifier order:

```ts
abstract class C {
  public abstract accessor x: number;
  protected abstract accessor y: string;
}
```

`printAccessorProperty` now emits accessibility before `abstract`, matching the neighboring method and property printers. The same ordering also handles declarations such as `protected abstract override accessor x: number` and applies to both the ordinary and source-map-enabled builds.

This brings the JavaScript printer in line with the Rust codegen correction in #26392. The change is confined to accessor modifier ordering; subsequent `static`, `override`, and `accessor` emission and source-map anchoring retain their existing behavior.
2026-09-07 15:13:55 +00:00
camc314 4e76602fdd fix(parser): allow in in arrow block bodies within for initializers (#26395)
## Summary

Allow the `in` operator inside an arrow function’s block body when the arrow appears in a `for` initializer:

```js
for (() => { a in b; };;);
```

This is valid JavaScript, but the parser previously reported a missing semicolon at `in`. The block now parses successfully, including async arrows and arrows with a single unparenthesized parameter.

## Cause and fix

A `for` initializer disables `Context::In` so that an unparenthesized `in` can delimit a `for…in` statement. Arrow block bodies inherited that disabled context through `parse_arrow_function_expression_body`, which called `parse_function_body` directly.

The block-body branch now calls `parse_function_body` through `context_add(Context::In, ...)`. This enables `in` throughout the function body and restores the enclosing context afterward. It also permits nested concise arrows inside that block to use `in`.

The change is confined to block bodies because concise expression bodies inherit the surrounding grammar’s `In` parameter. For example, this remains invalid:

```js
for (var f = () => a in b;;);
```

Restoring the enclosing context also keeps this invalid, rather than accidentally treating `a in b` as a regular initializer expression after parsing the first arrow:

```js
for (var f = () => {}, value = a in b;;);
```
2026-09-07 15:07:31 +00:00
renovate[bot] 3cb3dc840b chore(deps): update dependency oxlint to v1.82.0 (#26405)
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/) |
|---|---|---|---|---|---|
| [oxlint](https://oxc.rs/docs/guide/usage/linter)
([source](https://redirect.github.com/oxc-project/oxc/tree/HEAD/npm/oxlint))
| [`1.81.0` →
`1.82.0`](https://renovatebot.com/diffs/npm/oxlint/1.81.0/1.82.0) |
![age](https://developer.mend.io/api/mc/badges/age/npm/oxlint/1.82.0?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/oxlint/1.82.0?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/oxlint/1.81.0/1.82.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/oxlint/1.81.0/1.82.0?slim=true)
|

---

### Release Notes

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

###
[`v1.82.0`](https://redirect.github.com/oxc-project/oxc/blob/HEAD/npm/oxlint/CHANGELOG.md#1820---2026-09-07)

##### 🚀 Features

-
[`6a0e19c`](https://redirect.github.com/oxc-project/oxc/commit/6a0e19c)
linter/eslint/no-unmodified-loop-condition: Support
`checkConditionalExpressions` option
([#&#8203;26249](https://redirect.github.com/oxc-project/oxc/issues/26249))
(camc314)

</details>

---

### Configuration

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

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

🚦 **Automerge**: Enabled.

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

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-07 15:06:27 +00:00
camc314 3d0de4e2a1 perf(ci): skip Miri cache setup when checks are skipped (#26401)
Skip Namespace Rust cache setup when change detection skips the Miri checks, avoiding unnecessary cache initialization.
2026-09-07 14:54:56 +00:00
camc314 b20fc1950c fix(parser): reject partially parenthesized mixed coalesce expressions (#26394)
The parser currently accepts mixed nullish and logical expressions when parentheses cover only the first operand of the logical expression:

```js
(a) || b ?? c;
a ?? (b) || c;
(a) && b ?? c;
a ?? (b) && c;
```

These expressions require a syntax error because `??` cannot be mixed directly with `||` or `&&`. Parentheses around `a` or `b` do not group the complete logical expression. This change reports the existing mixed-coalesce diagnostic for all four forms.

Fully grouped operands remain valid:

```js
(a || b) ?? c;
a ?? (b || c);
(a && b) ?? c;
a ?? (b && c);
```

### Why the existing check misses these expressions

`parse_binary_expression_rest` used `lhs_parenthesized` and `rhs_parenthesized` flags captured before parsing an operand. Those flags describe whether the initial token is `(`, but the parser can subsequently accumulate a larger logical expression outside those parentheses. Applying the initial flag to that complete expression incorrectly suppresses the diagnostic.

### Complete-operand checks

Check both operands independently for an `And` or `Or` logical expression whose span starts at the operand's starting position. A logical expression accumulated outside initial parentheses starts at that position and must be rejected. Checking both operands also prevents a grouped right operand from hiding an invalid left operand.

This works with either `preserve_parens` setting. When parentheses are preserved, a fully grouped operand is a `ParenthesizedExpression`. When they are omitted, its inner logical expression retains a span beginning after the opening `(`, so its start differs from the operand start and it remains allowed. For `(a) || b`, the complete logical expression instead starts at the opening `(` and is correctly rejected.
2026-09-07 14:34:52 +00:00
oxc-guard 60fa13878c release(crates): oxc v0.149.0 (#26397)
### 💥 BREAKING CHANGES

- 66744f0 parser: [**BREAKING**] Rename `panicked` to `fatal_error` in `ParserReturn` (#26382) (overlookmotel)
- 2c9a947 parser: [**BREAKING**] Reduce `MAX_LEN` to 256 bytes below `u32::MAX` (#26352) (overlookmotel)

### 🚀 Features

- 32bdc5b allocator: Construct `ArenaHashSet` with any `Default` hasher (#26372) (Dunqing)
- a17de58 minifier: Process of return/throw in non last position and fix async * inlining (#26214) (Armano)
- bfb4c57 parser: Distinguish unapplied no-side-effects comments (#26120) (碳苯 Carbon)
- 1d9b9d3 ast: Add `GetNodeId` trait (#26145) (camc314)

### 🐛 Bug Fixes

- b156333 codegen: Order accessibility before abstract on accessors (#26392) (camc314)
- 116c8b8 transformer/class-properties: Keep private methods in class-expression scope (#26308) (Changsu Seong)
- 1400a0f parser: Require a string source after `export ... from` (#26389) (camc314)
- 2da73b7 codegen: Print matching quoted import names as identifiers (#26386) (camc314)
- 0a81d29 codegen: Preserve private-in left operand precedence (#26383) (camc314)
- 9a02337 parser: Correctly round large nondecimal literals (#26379) (camc314)
- 3d95477 transformer: Gate async-only rewrites by owning transform (#26326) (Dunqing)
- f45a9ee transformer: Limit arguments capture to lowered async functions (#26317) (Dunqing)
- 789969f transformer: Gate this capture by owning async transform (#26227) (Dunqing)
- affeb14 minifier: Mangle class private members in Node API (#26118) (碳苯 Carbon)
- 8b35abd semantic: Detect duplicate private class elements (#26361) (camc314)
- 457ed57 semantic: Reject await and yield in rest parameter defaults (#26359) (camc314)
- 4b89c7b semantic: Reject jumps across arrow functions (#26357) (camc314)
- c966aca parser: Do not omit `<` token opening type argument list (#26328) (overlookmotel)
- f07dca5 ast_visit: Add the trimmed prefix length to translation table offsets (#26173) (Bharadwaj Pendyala)
- 3ab9bd1 parser: Do not re-lex template substitution tail after fatal error (#26230) (overlookmotel)
- 07851b9 parser: Fix debug assert failure when lexer error with tokens enabled (#26229) (overlookmotel)
- 00dea7a allocator: Gate `Allocator::data_end_ptr` behind fixed_size feature (#26248) (camc314)
- 853ffab ecmascript: Avoid `charAt` panic on 32-bit (#26244) (camc314)
- 34feccb runtime: Remove stale regenerator exports (#26243) (camc314)

### ⚡ Performance

- 34a242e minifier: Use `Ident` for property mangler (#26334) (sapphi-red)
- 5303f5c minifier: Unify merging of last expression into target sequence (#26264) (Armano)
- 2ca7d29 minifier: Consume nodes in minimize_statements in reverse order (#26258) (Armano)
- d2354b4 mangler: Use `Ident` for variable names to keep (#26333) (sapphi-red)
- 5b3e335 minifier: Use `Ident` instead of `Str` in `KeepVar` (#26332) (sapphi-red)
- 51366fb minifier: Use `IdentHashSet` in `PrivateMemberUsageStack` (#26331) (sapphi-red)
- d19c42a minifier: Merge nested if stmt in place instead of creating dummies (#26351) (Armano)
- 356def6 parser: Shrink annotation comment ranges (#26356) (overlookmotel)
- a5be474 parser: Shave instruction off `parse_jsx_element_name` (#26355) (overlookmotel)
- 9780663 parser: Remove fatal error guard from `parse_jsx_element_name` (#26354) (overlookmotel)
- 766e12f parser: Remove `token` field from `LexerCheckpoint` (#26350) (overlookmotel)

### 📚 Documentation

- d4b4e61 transformer: Document ES2015 target floor (#26378) (Dunqing)
- 67dd054 ast_visit: Correct out-of-date comments on `Utf8ToUtf16` converter (#26285) (overlookmotel)
- a8ed2a7 minifier: Fix stale validation instructions (#26251) (camc314)
crates_v0.149.0
2026-09-07 14:29:34 +00:00
oxc-guard b4da00b621 release(apps): oxlint v1.82.0 && oxfmt v0.67.0 (#26384)
# Oxlint
### 🚀 Features

- 6a0e19c linter/eslint/no-unmodified-loop-condition: Support `checkConditionalExpressions` option (#26249) (camc314)

### 🐛 Bug Fixes

- 562dd14 jsx-a11y/anchor-has-content: Allow anchors passed as custom component props (#26360) (Mikhail Baev)
- 253cdb2 linter/eslint/id-length: Exempt TS interface/type-literal members with never (#26310) (Pearce Ropion)
- 8c7ea76 ast_visit: Account for trimmed source offsets (#26223) (Cameron)
- 5f9cffc linter/typescript/no-explicit-any: Fix rest parameter to `unknown[]` (#26279) (camc314)
- 4de9fa3 linter/react/jsx-no-literals: Check nested JSX expression literals (#26253) (camc314)
- 07851b9 parser: Fix debug assert failure when lexer error with tokens enabled (#26229) (overlookmotel)
- 9cee146 oxlint/lsp: Don't try to parse fix message, always fallback (#26204) (Sysix)
- a662c40 linter/eslint/no-useless-assignment: Handle destructuring assignments (#25925) (camc314)
- 304bd9a linter/eslint/no-array-constructor: Handle ASI hazards in fixer (#26166) (Hamody We)
- b41bb35 linter: Qualify rules in rules output (#26250) (camc314)
- 895c685 linter/typescript/no-extraneous-class: Avoid deleting class expressions (#26231) (camc314)
- 4ba33c8 linter/eslint/no-unused-vars: Handle stored arrow references (#26225) (camc314)

### 🛡️ Security

- 9a612d5 deps: Update npm packages (#26178) (renovate[bot])
# Oxfmt
### 💥 BREAKING CHANGES

- 2c9a947 parser: [**BREAKING**] Reduce `MAX_LEN` to 256 bytes below `u32::MAX` (#26352) (overlookmotel)

### 🚀 Features

- 68e12ab formatter_css: Follow the oxc-css-parser's acceptance line (#26337) (leaysgur)

### 🐛 Bug Fixes

- f7acdc0 formatter: Treat a JSDoc cast target as opaque in chain layouts (#26375) (leaysgur)
- 104061b formatter: Keep JSDoc cast parens with a comment inside them (#26374) (leaysgur)
- 3d00a76 formatter_css: Keep comment inside important and simple-vars colon (#26370) (leaysgur)
- 172ddb6 formatter_css: Keep a same-line line comment on its line (#26321) (leaysgur)
- cafaf32 formatter_css: Never drop comments inside an at-rule prelude (#26320) (leaysgur)
- 534a0fe formatter_css: Keep a line comment on its comma's line (#26319) (leaysgur)
- 99cfd75 formatter_css: Hide raw comment printing API, use strcut instead (#26316) (leaysgur)
- 2028896 formatter_css: Preserve comments around SCSS/Less variable values (#26276) (leaysgur)
- 6752abc formatter_css: Keep leading comments before (#26272) (leaysgur)
- 3b054de formatter_graphql: Align comment printing after open `{` (#26271) (leaysgur)
- b3c287a formatter_css: Fix SCSS bracket list printing (#26270) (leaysgur)
- bac770a formatter_css: Preserve comment in SCSS paren comma list (#26269) (leaysgur)
- c7b57ef formatter_css: Print SCSS map consisntent with and without comment (#26268) (leaysgur)
- 063e2b6 formatter_css: Preserve trailing comma in single-item scss lists (#26245) (leaysgur)
- 5f9580d oxfmt: Bump prettier-plugin-tailwindcss (#26273) (leaysgur)

### ⚡ Performance

- 1cad286 oxfmt: Match editorconfig once per file (#26239) (Yuji Sugiura)
- 260c5eb oxfmt: Match `overrides` once per file (#26238) (leaysgur)
- 61565bc oxfmt: Share resolved `FormatConfig` via `Arc` instead of cloning per file (#26237) (leaysgur)

### 📚 Documentation

- 32084d4 formatter_core: Clarify printer hard_line compression (#26312) (leaysgur)
- 4fabdcc formatter,formatter_css,formatter_graphql,formatter_yaml: Update DIVERGENCES.md (#26267) (leaysgur)
- 26a1fa4 formatter_core: Refine FORMATTER_POLICY (#26266) (leaysgur)

### 🛡️ Security

- 9a612d5 deps: Update npm packages (#26178) (renovate[bot])
oxlint_v1.82.0 apps_v1.82.0 oxfmt_v0.67.0
2026-09-07 14:13:57 +00:00
camc314 aa38ddfbea fix(linter/unicorn/numeric-separators-style): correct schema defaults (#26393)
## Summary

Fix the generated defaults for `unicorn/numeric-separators-style`. The rule already uses `number.minimumDigits: 5`, matching Unicorn, but the generated documentation and JSON schema advertise `0`. The same schema issue also advertises `groupLength: 0` for every numeric base.

## Why the defaults are wrong

`NumericBaseConfig` is shared by binary, hexadecimal, octal, and decimal configuration. Its derived `Default` initializes both numeric fields to zero. The rule supplies the actual defaults in `NumericSeparatorsStyleConfig::default()` and `NumericNumberConfig::default()`.

Schema generation uses the shared type's defaults for its individual properties. In particular, flattening `NumericBaseConfig` into `NumericNumberConfig` preserves the zero-valued property defaults instead of the decimal defaults. This makes the documented threshold disagree with the rule's existing behavior:

```js
1234;   // Allowed with the default configuration.
12345;  // Reported; the fix is 12_345.
```

## Change

Add a single parameterized schema helper for `NumericBaseConfig`, then select the appropriate defaults with `schemars(schema_with = ...)` on each field. The helper retains the derived schema's property descriptions, types, and constraints, and overrides only the `groupLength` and `minimumDigits` default metadata.

The resulting defaults are:

| Configuration | `groupLength` | `minimumDigits` |
| --- | --- | --- |
| `binary` | 4 | 0 |
| `hexadecimal` | 2 | 0 |
| `octal` | 4 | 0 |
| `number` | 3 | 5 |

The schema annotation on the flattened decimal base also corrects `number.minimumDigits`. `number.fractionGroupLength` remains unchanged, and the documentation continues to display its default as `Infinity`.

Regenerate the JSON configuration schema and the TypeScript configuration types from it. The generated TypeScript types inline the binary, hexadecimal, and octal option objects because those properties now have individual schemas; their accepted configuration shapes are unchanged.

This changes schema and documentation metadata only. Runtime defaults, option parsing, diagnostics, and autofixes are unchanged.

Fixes #26391
2026-09-07 13:48:04 +00:00
camc314 b156333a23 fix(codegen): order accessibility before abstract on accessors (#26392)
Codegen emits invalid TypeScript when an abstract accessor has an explicit accessibility modifier. Both normal and minified output put `abstract` before `public` or `protected`, causing TS1029 because accessibility must precede `abstract`.

For this valid input:

```ts
abstract class C {
  public abstract accessor x: number;
  protected abstract accessor y: string;
}
```

The previous output reordered the members to:

```ts
abstract public accessor x: number;
abstract protected accessor y: string;
```

`AccessorProperty::gen` now prints accessibility before `abstract`, matching the neighboring method and property emitters and preserving the input's valid modifier order. The same ordering applies in minified output:

```ts
abstract class C{public abstract accessor x:number;protected abstract accessor y:string}
```

The change is confined to accessor modifier ordering; subsequent `static`, `override`, and `accessor` emission remains unchanged.
2026-09-07 13:27:26 +00:00
sapphi-red 34a242e134 perf(minifier): use Ident for property mangler (#26334)
Use `Ident` in the property mangler. This reduces the `Set::contains` operation faster as the hashing doesn't happen.

The property names are not always an "ident" so it may not make sense to use the `Ident` type. But currently the `Ident` type allows characters that are not allowed in identifiers so the code works.

I used AI to generate the code and then reviewed the code and iterated a bit.
2026-09-07 12:58:02 +00:00
Changsu Seong 116c8b8303 fix(transformer/class-properties): keep private methods in class-expression scope (#26308)
Private methods lowered from a class expression inside a concise arrow
were inserted next to the arrow's outer statement, while private field
storage remained inside the arrow. Starting with Oxc 0.143, this could
leave the generated helper unable to resolve the private field and throw
a `ReferenceError` at runtime.

Concise arrow bodies have no statement list where the helper declaration
can be inserted. In that context, the helper is now emitted as an
assignment in the class-expression sequence, with its `var` binding
inside the expanded arrow body. Ordinary statement-backed class
expressions keep block-local function declarations so classes created by
repeated block evaluation retain their own captured values. The
generated symbol flags and scope parents match the emitted form in both
cases.

## Example

```js
const makeClass = () => class {
  #field = 41;
  #read() { return this.#field; }
  run() { return this.#read() + 1; }
};

new (makeClass())().run();
```

Before Oxc emitted a helper outside `makeClass`, and the call threw
`ReferenceError: _field is not defined`. After this change it returns
`42`.

Verified with the private-method transform conformance suite, runtime
regressions for concise arrows and repeated block evaluation, `cargo
test -p oxc_transformer`, and transformer clippy.

Fixes the upstream cause of
[rolldown/rolldown#10818](https://github.com/rolldown/rolldown/issues/10818).

AI assistance: Cursor was used for the original patch; OpenAI Codex was
used for follow-up review, fixes, and testing.

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Dunqing <dengqing0821@gmail.com>
2026-09-07 20:47:40 +08:00
camc314 1400a0f8be fix(parser): require a string source after export ... from (#26389)
Named exports must have a string literal module source once `from` is present. The parser previously combined consuming `from` with checking whether the next token was a literal:

```rust
if self.eat(Kind::From) && self.cur_kind().is_literal()
```

When the next token was not a literal, `from` had already been consumed, but parsing fell back to a local export. This incorrectly accepted `export {} from;`, `export {} from` at EOF, and this input:

```js
export {} from
foo();
```

In the last case, the newline allowed automatic semicolon insertion, so the parser accepted a local empty export followed by `foo()` despite the incomplete `from` clause.

Remove the literal-kind guard so consuming `from` always leads to `parse_literal_string()`. That helper already requires a string literal and handles malformed input through the existing parser diagnostics. A missing source now produces a syntax error instead of becoming a local export, while unterminated strings and comments retain the standard lexer diagnostics.

Valid local exports such as `export { foo };` and re-exports such as `export { foo } from "./module.js";` keep their existing behavior, including a module source placed on the next line.
2026-09-07 12:41:32 +00:00
Armano 5303f5c884 perf(minifier): unify merging of last expression into target sequence (#26264)
adds new helper `merge_last_expression_into_sequence` and uses it
everywhere when we want to merge last expression to target expression
`a; b;` -> `a, b;`

this change avoids creation of 2 dummy nodes and unnecessary
`drop_statement` on dummy stmt per merge
2026-09-07 20:40:49 +08:00
camc314 2ef8e266e1 fix(ci): install Windows dependencies after copying to Dev Drive (#26388)
Windows CI first failed in 1bb1c8c9e9 (#26381), which upgraded pnpm to v12. The [preceding Windows job passed](https://github.com/oxc-project/oxc/actions/runs/34096054585/job/101659861287); the [first failing job](https://github.com/oxc-project/oxc/actions/runs/34098876784/job/101668505666) could not load `E:\oxc\node_modules\oxlint-tsgolint\bin\tsgolint.js`.

pnpm 12 creates Windows directory symlinks, but the Dev Drive action's `fs-extra` copy recreates links without preserving their directory type. Copy the workspace before installing dependencies, then install directly in the Dev Drive workspace for all four Windows jobs.
2026-09-07 11:20:34 +00:00
Shanon Jackson fed8ff8753 fix(lexer): close remaining grammar gaps in context-dependent tokenization (#26377)
Closes out remaining spec compliance gaps in the lexer, Regex vs
Division cases, `>>` cases, + many more.

Fully passes on Test262, All Typescript tests, Oxc tests, SWC tests,
VSCode (codebase), And 1_000 machine hours of differential fuzzing;
Confidence is high that it's either 'correct' or very close.

This PR specifically needs AI Disclosure in both finding and fixing the
cases now because the bitwise logic is complex; Something that I'll
start addressing once 'correctness' is locked in (and performance).

---------

Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
2026-09-07 11:12:57 +01:00
camc314 2da73b7824 fix(codegen): print matching quoted import names as identifiers (#26386)
Codegen omitted the alias whenever an imported name matched its local binding, comparing the decoded names without accounting for whether the imported name was a string literal. That produces invalid syntax for quoted import names, which require an explicit `as` binding:

```js
// Input
import { "foo" as foo } from "m";

// Before
import { "foo" } from "m";

// After
import { foo } from "m";

// After, minified
import{foo}from"m";
```

When a string-literal import name matches the emitted local binding name, print the local binding directly and use identifier shorthand. The matching binding already supplies a valid identifier, so this does not need a separate identifier-validity check or an AST mutation. The conversion applies in both default and minified output.

The comparison uses the decoded string value, so escaped names and Unicode names can also use shorthand. Declaration-level and inline type imports follow the same rule:

```ts
// Input
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";
import type { "Foo" as Foo } from "types";
import { type "Bar" as Bar } from "types";

// After
import { ab } from "m";
import { π } from "m";
import type { Foo } from "types";
import { type Bar } from "types";
```

Quoted names that differ from their local binding retain their quotes and aliases, including `"foo-bar" as foo`, `"" as foo`, and `"default" as foo`. Ordinary identifier imports retain the existing shorthand behavior.
2026-09-07 09:58:17 +00:00
camc314 646c27cae3 docs(ci): discourage speculative compiler errors in copilot AI PR reviews (#26387)
Prompted by [this Copilot review comment](https://github.com/oxc-project/oxc/pull/26386#discussion_r3948410351), which incorrectly claimed a variant check moves a non-`Copy` field out of a borrow. The wildcard pattern does not bind or move the payload. All code is tested/checked in CI, so a compile error should fail there.
2026-09-07 09:53:14 +00:00
overlookmotel 66744f0eb9 feat(parser)!: rename panicked to fatal_error in ParserReturn (#26382)
`ParserReturn` contains a property `panicked`.

The name `panicked` is misleading. The parser did _not_ panic. When `true`, it means that the parser encountered a fatal error and is unable to parse the source, because it contains an unrecoverable syntax error.

Rename this property to `fatal_error`, to better describe what it means.

This is a breaking change, but one that's trivial to fix in downstream code - the property doesn't change its meaning or behavior, just its name.

All the changes in this PR in other crates are just reacting to this change and making everywhere use the term "fatal error" instead of "panicked", so the codebase is consistent.
2026-09-07 09:41:52 +00:00
camc314 0a81d293f9 fix(codegen): preserve private-in left operand precedence (#26383)
Private-in expressions lose required parentheses when they appear on the left of a higher-precedence binary operator. This affects both default and minified code generation and can change the result or cause a runtime error.

For example:

```js
class C {
  #x;
  test(o) {
    return (#x in o) + 1;
  }
}
console.log(new C().test(new C()));
```

The input prints `2`. Previously, codegen emitted this return expression:

```js
return #x in o + 1;
```

Because `+` binds more tightly than `in`, this is parsed as `#x in (o + 1)`. The addition converts the object to a primitive, so the private-field check throws a `TypeError` instead of producing the boolean used by the original addition.

With this change, the required grouping is preserved:

```js
// Default output
return (#x in o) + 1;

// Minified output
return(#x in o)+1
```

The binary expression visitor already computes the precedence required for its left operand. However, its `PrivateInExpression` special case discarded that value and called `gen_expr` with `Precedence::Lowest` and an empty context. Passing `self.left_precedence` and `self.ctx` lets the existing private-in printer apply its parentheses rule using the same inputs as other left operands.

This preserves parentheses for arithmetic, exponentiation, and shift operators. Equal- and lower-precedence operators, such as `<`, `===`, and `&&`, continue to omit unnecessary parentheses.
2026-09-07 09:30:28 +00:00
Armano 2ca7d29b7a perf(minifier): consume nodes in minimize_statements in reverse order (#26258)
changes `minimize_statement`s to consume statement via a single
`reverse()` + `pop()` pass instead of repeatedly calling `take_in` on
each slot.

potential pitfall: result is in forward order, old stmts are in reverse,
thus we have to be careful in places where we want to take all of them

| File | Arena allocs | Delta | Arena size | Delta |
|---|---:|---:|---:|---:|
| `checker.ts` | `144024 -> 77557` | `-66467` | `19.13 MB -> 17.52 MB` |
`-1.61 MB` |
| `App.tsx` | `15675 -> 9622` | `-6053` | `2.88 MB -> 2.74 MB` | `-0.14
MB` |
| `RadixUIAdoptionSection.jsx` | `29 -> 16` | `-13` | `35.63 kB -> 35.63
kB` | `~0` |
| `pdf.mjs` | `44801 -> 26527` | `-18274` | `6.31 MB -> 5.89 MB` |
`-0.42 MB` |
| `antd.js` | `351645 -> 222308` | `-129337` | `48.77 MB -> 45.76 MB` |
`-3.01 MB` |
| `binder.ts` | `6809 -> 4124` | `-2685` | `1.16 MB -> 1.10 MB` | `-0.06
MB` |
| `kitchen-sink.tsx` | `64956 -> 34571` | `-30385` | `9.39 MB -> 8.74
MB` | `-0.65 MB` |

---------

Co-authored-by: Dunqing <dengqing0821@gmail.com>
2026-09-07 17:24:05 +08:00
camc314 9a023379c6 fix(parser): correctly round large nondecimal literals (#26379)
Large hexadecimal, binary, and octal literals can round incorrectly when their digits are accumulated with repeated `f64::mul_add` calls. For example, `0x10000000000000801` currently becomes `2^64` instead of the correctly rounded `2^64 + 4096`.

Preserve the leading significant bits and a sticky bit in the nondecimal slow paths, then round once and scale by a power of two. This also covers numeric separators and legacy octal literals, without allocating or changing the existing integer fast paths.
2026-09-07 09:06:48 +00:00
renovate[bot] 1bb1c8c9e9 chore(deps): update pnpm to v12 (#26381)
> ℹ️ **Note**
> 
> This PR body was truncated due to platform limits.

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/) |
|---|---|---|---|---|---|
| [pnpm](https://redirect.github.com/pnpm/pnpm/tree/main/pnpm)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm/npm/pnpm))
| [`11.25.0` →
`12.3.2`](https://renovatebot.com/diffs/npm/pnpm/11.25.0/12.3.2) |
![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/12.3.2?slim=true)
|
![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/pnpm/12.3.2?slim=true)
|
![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/pnpm/11.25.0/12.3.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/11.25.0/12.3.2?slim=true)
|

---

### Release Notes

<details>
<summary>pnpm/pnpm (pnpm)</summary>

###
[`v12.3.2`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.3.2):
pnpm 12.3.2

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.3.1...v12.3.2)

#### Patch Changes

- `pnpm audit --fix update` no longer aborts when a vulnerable package
has no safe version inside its declared range
[#&#8203;14508](https://redirect.github.com/pnpm/pnpm/issues/14508). The
run updates every package it can and lists the rest as remaining.

- `pnpm install` no longer reruns root lifecycle scripts when the global
virtual store contains an unfinished-build marker in a package slot that
the current lockfile does not use
[pnpm/pnpm#14485](https://redirect.github.com/pnpm/pnpm/issues/14485).

- Sped up installs that have no lockfile. pnpm now links packages whose
dependency subtree has no peer dependencies into the virtual store while
resolution is still running.

- `pnpm run` and `pnpm exec` now start without reinstalling on
filesystems that keep sub-millisecond mtimes, such as NTFS. Previously,
every run on those filesystems reinstalled first
[pnpm/pnpm#14486](https://redirect.github.com/pnpm/pnpm/issues/14486).

- `pnpm import` now keeps the versions recorded in `package-lock.json`,
`npm-shrinkwrap.json`, or `yarn.lock` when it generates
`pnpm-lock.yaml`. A range in `package.json`, a catalog, or an override
still decides which versions are eligible, and the recorded version is
preferred among them. The generated lockfile previously could pin newer
versions than the source lockfile
[#&#8203;14476](https://redirect.github.com/pnpm/pnpm/issues/14476).

`pnpm import` in a workspace now imports every workspace project into
the shared lockfile. It previously imported only the project in the
current directory.

`pnpm import` now fails with `ERR_PNPM_LOCKFILE_NOT_FOUND` when none of
the three source lockfiles is present. It also fails with
`ERR_PNPM_YARN_LOCKFILE_PARSE_FAILED` when it cannot parse `yarn.lock`.
It previously generated a lockfile from scratch in both cases.

`pnpm import` always resolves locally. It warns when `--pnpr-server` or
the `pnpr-server` setting is given and does not use the server.

- Sped up installs in large workspaces. Discovering the workspace
projects no longer enumerates every matched directory to learn which
manifest files it holds
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Sped up installs in large workspaces. The resolver and the peer pass
allocate less for every dependency edge
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- `pnpm self-update`, `pnpm with`, and automatic package-manager version
switching no longer wait through registry retry delays when a configured
registry has no signatures and `registry.npmjs.org` is unavailable
[#&#8203;14483](https://redirect.github.com/pnpm/pnpm/issues/14483).

- Sped up installs in large workspaces. Saving the lockfile is faster,
and the install finishes without waiting for memory cleanup
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- `pnpm install` now relinks workspace packages when
`publishConfig.linkDirectory` changes. Frozen installs report an
outdated lockfile until it is regenerated
[pnpm/pnpm#14488](https://redirect.github.com/pnpm/pnpm/issues/14488).

- The pnpm npm wrapper keeps its placeholder shebang-less so pnpm 11 can
install pnpm 12 through the version store. Wrapper installs must allow
lifecycle scripts to install the native binary
[#&#8203;14502](https://redirect.github.com/pnpm/pnpm/issues/14502).

- Sped up dependency resolution when there is no lockfile, and for the
dependencies a lockfile does not cover.

- Sped up installs in large workspaces. Workspace `link:` targets and
importer ids are now derived from the paths' suffixes under the
workspace root
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- `pnpm install` now reports "Already up to date" when local tarball
dependencies have not changed
[#&#8203;14495](https://redirect.github.com/pnpm/pnpm/issues/14495).

- `pnpm update` now accepts `--ignore-scripts` and skips lifecycle
scripts during the update
[pnpm/pnpm#14512](https://redirect.github.com/pnpm/pnpm/issues/14512).

- Sped up installs that restore a deleted `node_modules` from a warm
global virtual store. pnpm no longer re-links packages that are already
fully present in the global virtual store
[#&#8203;14510](https://redirect.github.com/pnpm/pnpm/issues/14510).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
<a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/openai_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/openai_light.svg" />
<img src="https://pnpm.io/img/users/openai_dark.svg" width="160"
alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/notion.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/notion_light.svg" />
<img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"
/>
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity"
/>
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220"
alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160"
alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220"
alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190"
alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190"
alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/latitude.svg" width="160"
alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

###
[`v12.3.1`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.3.1):
pnpm 12.3.1

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.3.0...v12.3.1)

#### Patch Changes

- Sped up installs in large workspaces: the anchor for re-rendering
workspace `link:` targets is now derived once per project instead of
once per dependency edge, and project ordering hashes paths by their raw
bytes
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- After a self-update from pnpm 12.2 to 12.3, global commands such as
`node`, `npm`, and `yarn` failed with `unexpected argument '--shim'
found`. Global commands now launch normally, and their first launch
migrates the global bin directory to native shims. When self-update
downgrades to pnpm 12.2 or older, it keeps the newer native shims so
those commands continue to work.

- Sped up installs in large workspaces. The check that verifies each
project against the lockfile now runs the projects in parallel
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
<a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/openai_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/openai_light.svg" />
<img src="https://pnpm.io/img/users/openai_dark.svg" width="160"
alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/notion.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/notion_light.svg" />
<img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"
/>
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity"
/>
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220"
alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160"
alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220"
alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190"
alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190"
alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/latitude.svg" width="160"
alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

###
[`v12.3.0`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.3.0):
pnpm 12.3

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.2.1...v12.3.0)

#### Minor Changes

- Every context-aware global command (`node`, `deno`, `bun`, and the
shims created with `pnpm shim add`) is now a native executable on every
platform, so environment variables whose names are not valid shell
identifiers reach these commands. On Windows, `<name>.exe` replaces the
`.cmd` and `.ps1` shims for them. Shims written by earlier pnpm 12
releases are migrated on the next global install or self-update.

- `pnpm remove` and `pnpm update` now accept `--trust-lockfile`,
`--no-trust-lockfile`, `--trust-policy`, `--trust-policy-exclude` and
`--trust-policy-ignore-after`, the same flags `pnpm install` and `pnpm
add` take, so the supply-chain settings can be overridden for a single
run. `pnpm remove` verifies the lockfile against the active policies the
way `pnpm install` does, and `--trust-lockfile` skips that pass for
every entry, not only the package being removed.

`pnpm` now also honors `--config.trust-lockfile=<value>`, and accepts
the bare `--trust-lockfile` / `--no-trust-lockfile` spelling on the
commands that previously took the setting from the config file alone.

#### Patch Changes

- `pnpm add <local directory>`, `pnpm add <local tarball>`, `pnpm add
file:<path>` and `pnpm add <tarball URL>` work again. A specifier given
without a `<name>@` prefix is no longer read as a registry package name
and rejected with `ERR_PNPM_PACKAGE_MANAGER_ADD_RESOLVE_LATEST`
[#&#8203;14437](https://redirect.github.com/pnpm/pnpm/issues/14437).

- Fixed `pnpm deploy --legacy` ignoring `allowUnusedPatches` supplied
through `--config.allow-unused-patches` or the
`PNPM_CONFIG_ALLOW_UNUSED_PATCHES` environment variable
[pnpm/pnpm#14450](https://redirect.github.com/pnpm/pnpm/issues/14450).

- Fixed `pnpm install --lockfile-only` writing a lockfile that
referenced a missing peer-suffixed snapshot when an npm-aliased
dependency took part in a cyclic peer dependency graph. The following
`pnpm install --frozen-lockfile` failed with
`ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY`
[#&#8203;14449](https://redirect.github.com/pnpm/pnpm/issues/14449).

- `pnpm config` now accepts `-g`/`--global`, `--location`, and `--json`
before its subcommand
[pnpm/pnpm#14421](https://redirect.github.com/pnpm/pnpm/issues/14421).

- `pnpm dedupe` now converges in one pass when it re-resolves a lockfile
created by pnpm 11, so a second run no longer changes the lockfile
[#&#8203;14455](https://redirect.github.com/pnpm/pnpm/issues/14455).

- Fixed detached child processes being terminated on Windows when
another program launches `pnpm` directly, without a shell, as `nr` from
`@antfu/ni` does
[#&#8203;14447](https://redirect.github.com/pnpm/pnpm/issues/14447).

- Fixed `pnpm docs <package>@<version>` ignoring the requested version.
It now opens the selected version's homepage and reports a missing
version instead of opening the package-level homepage
[pnpm/pnpm#14428](https://redirect.github.com/pnpm/pnpm/issues/14428).

- Sped up installs in large workspaces. `pnpm-lock.yaml` is now read
while the workspace projects are being discovered
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Fixed filtered and recursive `pnpm run` and `pnpm exec` hanging when a
script reads from the terminal. Interactive prompts work again in a
script that pnpm never runs alongside another one, such as a single
`--filter`ed project, `--workspace-concurrency=1`, a dependency chain,
or a task declaring `concurrency: 1`
[#&#8203;14397](https://redirect.github.com/pnpm/pnpm/issues/14397).

- Fixed false unmet peer errors for auto-installed peers in linked
workspace packages.

- Fixed npm global installs on Windows so the PowerShell shims invoke
`pnpm.exe`.

- Fixed `pnpm with current <command>` when global options precede it,
such as `pnpm --workspace-root with current --version`
[pnpm/pnpm#14413](https://redirect.github.com/pnpm/pnpm/issues/14413).

A short-option cluster that mixes a global flag with an option owned by
the command, such as `pnpm -ro dist pack-app`, is now parsed like the
same options written after the command.

An option written before the command name is now reported as an unknown
option unless that command accepts it, instead of being taken for the
command to run. `pnpm -P exec echo` and `pnpm -z exec echo` fail the way
`pnpm --tag next exec echo` does.

- Apply pure insertions in zero-context patches at the correct line
instead of one line early.

- Improved peer dependency resolution performance when many packages
reuse the same peer ranges.

- `pnpm outdated` and `pnpm update` now follow local actions and
reusable workflows referenced with GitHub's self-repository syntax
(`uses: $/.github/actions/setup`) when looking for outdated GitHub
Actions, the same way they follow `./` references.

- The `pnpm install --help` descriptions of `--prod` and `--dev` no
longer claim that the flags take precedence over `NODE_ENV`. pnpm does
not read `NODE_ENV` when selecting which dependency groups to install
[#&#8203;14445](https://redirect.github.com/pnpm/pnpm/issues/14445).

- Sped up installs in large workspaces. The check that decides whether
the lockfile needs updating no longer compares every project against
every lockfile entry
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Sped up dependency resolution in large workspaces that use `link:`
dependencies
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- On Linux, pnpm now resolves registry hostnames through the system
resolver (`getaddrinfo`), as it already does on macOS and Windows and as
pnpm 11 did. Previously, an `/etc/resolv.conf` containing an option the
bundled pure-Rust resolver did not recognize, such as `options
no_tld_query`, made pnpm ignore the configured nameservers and silently
query Google's public DNS instead
[#&#8203;14469](https://redirect.github.com/pnpm/pnpm/issues/14469).

- Sped up dependency resolution in large workspaces. The resolver builds
fewer lookup keys for each dependency
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- `catalogMode` and `--save-catalog` no longer move a local path,
tarball, or `workspace:<path>` specifier into a catalog. Such a
specifier is resolved against the project that declares it, so one
catalog entry cannot mean the same directory for every project that
references it
[#&#8203;14437](https://redirect.github.com/pnpm/pnpm/issues/14437).

- Sped up installs in large workspaces. The workspace dependency graph
is now built once per run instead of twice
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Sped up writing `pnpm-lock.yaml` in large workspaces
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Fixed non-frozen installs through a pnpr server failing instead of
regenerating a conflicted lockfile.

- `pnpm update --interactive` renders its checklist the way pnpm 11
does. Group headings and column headers are separators the cursor skips
instead of checkboxes that select nothing. The columns of one group line
up with the next. `a` toggles all and `i` inverts the selection. The
confirmed selection is echoed as a list of package names
[#&#8203;14423](https://redirect.github.com/pnpm/pnpm/issues/14423).

- Fixed `pnpm config` commands targeting global configuration to skip
project package manager version switching, allowing registry
authentication to be configured before pnpm downloads a project-pinned
version
[pnpm/pnpm#14463](https://redirect.github.com/pnpm/pnpm/issues/14463).

- Fixed pnpm retaining the surrounding quotes in `.npmrc` values,
including auth tokens expanded from environment variables. This restores
authentication with registries configured using `:_authToken="${TOKEN}"`
[pnpm/pnpm#14427](https://redirect.github.com/pnpm/pnpm/issues/14427).

- Fetch and tarball errors no longer print the secrets of the URL they
name. Inline `user:pass@` credentials and the query string or fragment
of a signed URL are hidden, so a failed install or `pnpm add <url>`
cannot leak them into terminal scrollback or CI logs.

- When `dist-tags.latest` names a version whose manifest pnpm cannot
read, the error now names that version and the field it could not
decode, instead of reporting the tag as empty.

- Retry transient Windows file-lock errors, including sharing
violations, while linking dependencies with the default (isolated)
`nodeLinker`. This fixes
[pnpm/pnpm#14407](https://redirect.github.com/pnpm/pnpm/issues/14407).

- `pnpm run`, `pnpm exec`, `pnpm rebuild`, and the script shortcuts such
as `pnpm test` now load the pnpmfile, so `updateConfig` hook settings
such as `extraEnv` and `extraBinPaths` reach the scripts they spawn
[#&#8203;14433](https://redirect.github.com/pnpm/pnpm/issues/14433).

- The `pnpm` executable of the npm package now works when the package
was installed without running its install scripts, as under
`--ignore-scripts` or the default build-script block of pnpm and Bun
[#&#8203;14346](https://redirect.github.com/pnpm/pnpm/issues/14346). In
that case it runs through Node.js and, in a terminal, says how to switch
to the native binary.

- Sped up installs in large workspaces. The resolver no longer copies
the whole lockfile before resolving
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- `minimumReleaseAgeStrict` now defaults to `true` when
`minimumReleaseAge` is explicitly configured, whether in
`pnpm-workspace.yaml`, the global `config.yaml`, a `PNPM_CONFIG_*`
variable, or a CLI flag. The built-in 1440-minute default stays
non-strict. Previously an explicit cutoff was treated as non-strict, so
immature versions were silently added to `minimumReleaseAgeExclude`
instead of being gated with a prompt
[#&#8203;14409](https://redirect.github.com/pnpm/pnpm/issues/14409).

- Preserve environment variables whose names are not valid shell
identifiers when launching Node.js installed by `pnpm runtime set node
--global` on Unix
[pnpm/pnpm#14417](https://redirect.github.com/pnpm/pnpm/issues/14417).

- Fixed `pnpm repo` and `pnpm docs` failing to open the Windows browser
from WSL
[pnpm/pnpm#14467](https://redirect.github.com/pnpm/pnpm/issues/14467).

- `pnpm link`, `pnpm outdated`, and `pnpm import` now apply pnpmfile
`updateConfig` hooks before resolving dependencies.

- Fixed standalone installations to preserve the bundled `node-gyp`
files used to build native dependencies.

- Fixed resolution against registries whose version manifests carry
`_npmUser`, `dist.attestations`, `dist.unpackedSize`, `dist.fileCount`,
or `peerDependenciesMeta` in a shape npm does not use. Such a version
was skipped as though it had never been published, so `pnpm add` could
fail with "no version found for the latest tag" even though the registry
served it.

- `pnpm unpublish` now completes the two-factor authentication a
registry asks for instead of failing with `ERR_PNPM_UNAUTHORIZED` while
logged in. A 401 that is an OTP challenge starts the web-based
authentication flow, or prompts for a classic one-time password. The
obtained password is reused by every request of the run
[#&#8203;14464](https://redirect.github.com/pnpm/pnpm/issues/14464).

- On Windows, pnpm now resolves host names through the system resolver
instead of its own DNS client. The built-in client bound a UDP socket
for every lookup, which made Windows Defender Firewall ask to allow
`pnpm.exe` again after every `pnpm self-update`
[#&#8203;14405](https://redirect.github.com/pnpm/pnpm/issues/14405).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
<a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/openai_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/openai_light.svg" />
<img src="https://pnpm.io/img/users/openai_dark.svg" width="160"
alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/notion.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/notion_light.svg" />
<img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"
/>
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity"
/>
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220"
alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160"
alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220"
alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190"
alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190"
alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/latitude.svg" width="160"
alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

###
[`v12.2.1`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.2.1):
pnpm 12.2.1

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.2.0...v12.2.1)

#### Patch Changes

- Restored the `pnpm` executable target without a file extension so pnpm
12.1 and earlier can upgrade to newer pnpm 12 releases on POSIX systems.

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
<a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/openai_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/openai_light.svg" />
<img src="https://pnpm.io/img/users/openai_dark.svg" width="160"
alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/notion.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/notion_light.svg" />
<img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"
/>
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity"
/>
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220"
alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160"
alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220"
alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190"
alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190"
alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/latitude.svg" width="160"
alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

###
[`v12.2.0`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.2.0):
pnpm 12.2

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.1.0...v12.2.0)

#### Minor Changes

- Catalogs can now resolve workspace dependencies through the
`workspace:` protocol.

#### Patch Changes

- Fixed `pnpm audit --fix` failing with `ERR_PNPM_INVALID_FIX_OPTION`
when used without a value, including when another flag follows it, as in
`pnpm audit --fix --json`
[#&#8203;13261](https://redirect.github.com/pnpm/pnpm/issues/13261).
Fixed `pnpm audit --fix=override` ignoring the `saveExact` and
`savePrefix` settings when writing vulnerability overrides
[#&#8203;11523](https://redirect.github.com/pnpm/pnpm/issues/11523).

- Authenticate Node.js runtime downloads from `nodeDownloadMirrors` with
URL-scoped npm registry credentials, including bearer tokens, basic
auth, and `tokenHelper`
[pnpm/pnpm#14334](https://redirect.github.com/pnpm/pnpm/issues/14334).

- Fixed detached child processes being terminated after successful
commands on Windows.

- Sped up installs in large workspaces by resolving each named
`workspace:` dependency (`workspace:*`, `workspace:^`,
`workspace:1.2.3`) once and reusing it across every project that
declares it, instead of re-resolving it per project.

- Fixed `pnpm install --fix-lockfile` to derive its repair and
filtered-merge views from one lockfile snapshot.

- Load pnpmfile `updateConfig` hooks before packing so hook-provided
catalogs resolve in `pnpm pack`, `pnpm publish`, and `pnpm stage
publish`
[pnpm/pnpm#14377](https://redirect.github.com/pnpm/pnpm/issues/14377).

- `pnpm deploy` no longer requires `injectWorkspacePackages` to be
enabled. A linked workspace dependency is rewritten to a `file:`
dependency in the dedicated deploy lockfile, and the peer dependencies
it declares are bound to the deployed graph's own resolution.

When a peer resolves to more than one version in that graph the binding
is ambiguous, and choosing between the candidates is exactly what
injecting the package would have decided, so the deploy still fails —
now with `ERR_PNPM_DEPLOY_AMBIGUOUS_PEER`, which names the package, the
peer, and the competing versions, instead of refusing every non-injected
workspace up front, and suggests pinning the peer to one version with an
`overrides` entry as the way to keep deploying without injection
[#&#8203;9386](https://redirect.github.com/pnpm/pnpm/issues/9386).

- Fixed global virtual store hashes for dependency cycles. Every package
that transitively depends on an allowed build now includes the engine in
its store path, independent of traversal order
[pnpm/pnpm#14341](https://redirect.github.com/pnpm/pnpm/issues/14341).

- Fixed `ERR_PNPM_CMD_SHIM_CHMOD` when several installs run at once
against a shared global virtual store. One install could remove a
command shim while another was making it executable
([pnpm/pnpm#14353](https://redirect.github.com/pnpm/pnpm/issues/14353)).

- Fixed the PowerShell shim generated by `npm install -g pnpm` on
Windows so it invokes the native `pnpm.exe` binary
[pnpm/pnpm#14362](https://redirect.github.com/pnpm/pnpm/issues/14362).

- Fixed context-aware global shims on WSL2 so native Linux installations
dispatch through the project runtime.

- `pnpm install` no longer writes global `minimumReleaseAgeExclude`
entries to the project's `pnpm-workspace.yaml`
[pnpm/pnpm#14347](https://redirect.github.com/pnpm/pnpm/issues/14347).

- Fixed `catalog:` ranges in workspace package peer dependencies being
reported as unmet
[pnpm/pnpm#14361](https://redirect.github.com/pnpm/pnpm/issues/14361).

- `globalDir` and `globalBinDir` are honored wherever they are set, so
`pnpm add -g` no longer fails with `ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH`
after `pnpm config set -g global-bin-dir`
[#&#8203;14336](https://redirect.github.com/pnpm/pnpm/issues/14336). The
global `config.yaml` is read again, `PNPM_CONFIG_GLOBAL_DIR` /
`PNPM_CONFIG_GLOBAL_BIN_DIR` reach the directories derived from them,
and a leading `~/` is expanded before that derivation. A project's
`pnpm-workspace.yaml` still cannot set either key.

- Fixed the install progress line reporting `added 0` under `nodeLinker:
hoisted`, even when packages were linked into `node_modules`
[#&#8203;14348](https://redirect.github.com/pnpm/pnpm/issues/14348).

- An auto-installed optional peer is now resolved to a version its
declared peer range accepts, even when the workspace root depends on
that package at a version outside the range. Previously the root's
version was used and then reported as an unmet optional peer
[#&#8203;13867](https://redirect.github.com/pnpm/pnpm/issues/13867).

- Fixed `pnpm run "/pattern/"` running matching scripts one at a time in
a single project. Matching scripts now run concurrently up to
`workspaceConcurrency`, and their output is prefixed so concurrent lines
remain distinguishable [pnpm discussion
14357](https://redirect.github.com/orgs/pnpm/discussions/14357).

- Fixed a slowdown at the end of a resolving install in a large
workspace. The peer-dependency report now inspects only the projects the
resolution flagged, rather than every project in the lockfile
([pnpm/pnpm#14359](https://redirect.github.com/pnpm/pnpm/issues/14359)).

- Speed up workspace discovery for literal directories and conventional
trailing-star patterns.

Workspace patterns now follow the same dot-directory rule as pnpm 11: a
wildcard no longer matches a dot-prefixed directory, so `packages/*` and
`**` skip `packages/.cache` and `.git`. A pattern that names a
dot-prefixed directory still matches it, as `packages/.cache` and
`packages/.*` do.

- `pnpm audit` now ends its output with a trailing newline, including
the `--json`, `--fix`, and `--ignore` output.

- Retry transient Windows file-lock errors while replacing hoisted
packages during installation.
This fixes
[pnpm/pnpm#14349](https://redirect.github.com/pnpm/pnpm/issues/14349).

- Fixed command-line `--side-effects-cache` overrides being ignored when
`pnpm-workspace.yaml` uses the object form of `sideEffectsCache`
[pnpm/pnpm#14338](https://redirect.github.com/pnpm/pnpm/issues/14338).

- Speed up workspace project discovery in large monorepos: workspace
patterns are now probed concurrently and the discovered projects'
`package.json` files are read in parallel
[#&#8203;14352](https://redirect.github.com/pnpm/pnpm/issues/14352).

- Fixed repeated `pnpm dedupe` runs alternating between peer resolutions
when a peer is provided through an npm alias.

- Fixed `pnpm repo <package>` and `pnpm docs <package>` resolving bare
package names through the `latest` tag, and prevented malformed package
ranges from crashing registry selection.

- Fixed non-ASCII characters in configuration values being mangled
during environment-variable substitution. Paths such as `storeDir:
./café-store` are now preserved
[#&#8203;14383](https://redirect.github.com/pnpm/pnpm/issues/14383).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
      <td align="center" valign="middle">
<a href="https://openai.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/openai_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/openai_light.svg" />
<img src="https://pnpm.io/img/users/openai_dark.svg" width="160"
alt="OpenAI" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://notion.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/notion.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/notion_light.svg" />
<img src="https://pnpm.io/img/users/notion.svg" width="80" alt="Notion"
/>
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
<a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/sanity.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/sanity_light.svg" />
<img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity"
/>
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/discord.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/discord_light.svg" />
<img src="https://pnpm.io/img/users/discord.svg" width="220"
alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/serpapi_light.svg" />
<img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160"
alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/coderabbit.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
<img src="https://pnpm.io/img/users/coderabbit.svg" width="220"
alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a
href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/stackblitz.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
<img src="https://pnpm.io/img/users/stackblitz.svg" width="190"
alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
<a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/workleap.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/workleap_light.svg" />
<img src="https://pnpm.io/img/users/workleap.svg" width="190"
alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer">
          <picture>
<source media="(prefers-color-scheme: light)"
srcset="https://pnpm.io/img/users/nx.svg" />
<source media="(prefers-color-scheme: dark)"
srcset="https://pnpm.io/img/users/nx_light.svg" />
<img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
<a href="https://latitude.so/?utm_source=pnpm&utm_medium=release_notes"
target="_blank" rel="noopener noreferrer"><img
src="https://pnpm.io/img/users/latitude.svg" width="160"
alt="Latitude"></a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

###
[`v12.1.0`](https://redirect.github.com/pnpm/pnpm/releases/tag/v12.1.0):
pnpm 12.1

[Compare
Source](https://redirect.github.com/pnpm/pnpm/compare/v12.0.0...v12.1.0)

#### Minor Changes

- `pnpm login` and `pnpm adduser` now record the granted token in the
global `config.yaml`, under the `_auth` setting, with `--scope`'s scope
routed to that registry under `registries`. `pnpm logout` removes it
from there, and still from an `auth.ini` an earlier version wrote.
Tokens already in `auth.ini` keep working.

- A `scope` set in a project's `pnpm-workspace.yaml` is now ignored,
with a warning naming where to set it instead. `pnpm login` records the
scope as a `@scope:registry` route in the machine-global `auth.ini`,
which outranks `~/.npmrc` in every project — so a repository-committed
file could redirect a scope such as `@acme` for all of a user's other
projects after one routine login. Use `--scope`, the `PNPM_CONFIG_SCOPE`
environment variable, or the global config file instead
[#&#8203;13557](https://redirect.github.com/pnpm/pnpm/issues/13557).

- Verified remote build artifacts are persisted in the shared store with
their signed origin metadata. Later installs reverify the artifact
against current trust, policy, platform, and source before reuse, while
invalid remote variants are quarantined per channel
([pnpm/pnpm#13771](https://redirect.github.com/pnpm/pnpm/issues/13771)).

- Persist completed recursive tasks so `--resume-from` skips exactly the
work that passed during a matching interrupted or failed `pnpm -r run` /
`pnpm -r exec` invocation. When no compatible state exists, pnpm retains
its graph-based resume behavior.

- Workspace install, rebuild, pack, publish, stage, and lifecycle work
now starts as soon as its dependencies finish instead of waiting for an
unrelated topological group.

- Added per-task concurrency limits to workspace task orchestration. Set
`tasks.<name>.concurrency` in `pnpm-workspace.yaml` to limit how many
instances of that task may run across workspace projects at once:

  ```yaml
  tasks:
    build:
      concurrency: 2
  ```

- `sideEffectsCache` now declares the whole of how a package's build
output is reused — whether one is restored, whether one is saved, and
the remote tier tha

> ✂ **Note**
> 
> PR body was truncated to here.


</details>

---

### Configuration

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

- Branch creation
  - "before 10am on the first day of the month"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, 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:eyJjcmVhdGVkSW5WZXIiOiI0NC42MS4zIiwidXBkYXRlZEluVmVyIjoiNDQuNjEuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-07 08:06:59 +00:00
sapphi-red d2354b4a4f perf(mangler): use Ident for variable names to keep (#26333)
Use `Ident` in the mangler for the variable names to keep. This reduces the `Set::contains` operation faster as the hashing doesn't happen.

I used AI to generate the code and then reviewed the code.
2026-09-07 07:28:38 +00:00
Dunqing d4b4e61cf6 docs(transformer): document ES2015 target floor (#26378)
Oxc Transformer supports output down to ES2015, but the contributor guidance
did not state that floor and the Rust API docs still listed `es5`. This made
plugin-only transform combinations look like supported target configurations.

Document the ES2015 floor and require target-dependent regressions to be
reproducible through public target selection. Legacy runtime targets may still
select available transforms, but do not imply complete compatibility with
pre-ES2015 engines.

Stacked on #26326.

Verified with `cargo fmt --check`, `just doc`, and
`cargo test -p oxc_transformer`. `just ready` completed formatting and workspace
checks, but stopped in the test phase on the existing Node-version-only
`stacktrace_is_correct` snapshot difference (`v25.8.1` locally versus `v26.5.0`
recorded).

AI assistance was used to investigate the documentation gap, edit the guidance,
and run verification. I remain responsible for reviewing and understanding the
changes before merge.
2026-09-07 07:21:48 +00:00