Applying `@__PURE__` and `@__NO_SIDE_EFFECTS__` comments is rare, so move it into `#[cold]` helpers to keep `with_pure_comments`, `parse_assignment_expression` and `parse_statement_list_item` small.
Fixes#28128
`array-callback-return` only checked array method calls with exactly one
argument, so a callback followed by anything else was skipped. That's
why `reduce`/`reduceRight` with an initial value were never reported.
The same thing happened to `foo.map(cb, thisArg)` and `Array.from(x, cb,
thisArg)`.
ESLint's rule checks the first argument (the second for
`Array.from`/`Array.fromAsync`) no matter how many arguments follow it.
This PR does the same.
```js
[1, 2].reduce((total, x) => {
if (x > 1) {
return total + x;
}
}, 0); // now reported, same as ESLint
```
Tests: new pass/fail cases for `reduce`/`reduceRight` with an initial
value, `map` with `thisArg`, `Array.from` with `thisArg`, and a callback
that isn't the first argument (`foo.reduce(bar, function() {})` stays
unreported). Snapshot updated. `cargo test -p oxc_linter`, `cargo clippy
-p oxc_linter --all-targets -- -D warnings` and `cargo fmt --check`
pass.
AI disclosure: I used an AI assistant while writing this change. I
reviewed the code and tests myself.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
Closes#28136
Severity for tsgolint diagnostics was looked up in the file's resolved
rules by name only. `unicorn/prefer-string-starts-ends-with` has the
same name as `typescript/prefer-string-starts-ends-with`, so whichever
came first in the list won, and the order differs for files that match
an `overrides` entry. Both lookups (CLI and `lint_source`) now only
match type-aware rules.
Added a CLI fixture with the config from the issue; the snapshot shows a
warning for both files, and it fails on main with an error for `b.tsx`.
`cargo test -p oxlint --lib tsgolint`, `cargo test -p oxc_linter --lib`,
clippy and fmt pass.
AI disclosure: I used Claude Code to help trace the cause and write the
patch, and I reviewed and tested it myself.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
Addresses one problem in #27736.
Clients should respect the the `ServerCapabilities` from the `intialize` response.
When no `diagnosticProvider` is provided, the server says to the client that `textDocument/diagnostic` is not supported.
```
/**
* The server has support for pull model diagnostics.
*
* @since 3.17.0
*/
diagnosticProvider?: DiagnosticOptions | DiagnosticRegistrationOptions;
```
_https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#pull-diagnostics_
Sadly the client does not need to respect them and can requzest `textDocument/diagnostic` even if we told him not to do.
To avoid duplicate linting processes on push mode (server is sending diagnostics to the client on `didOpen/didChange`, we check if we are in the right mode, or else bail out earlier.
> AI disclosure: this PR, the measurements behind it, and this description were produced with Claude Code at a maintainer's request. It should get human review before it leaves draft.
## Summary
This PR does three things:
- **`Cargo.toml`:** `[profile.coverage]` changes from `lto = "thin"` to `lto = false`.
- **`AGENTS.md`:** adds an opt-in note on incremental compilation for this profile, with its measured cost.
- **`AGENTS.md`:** the `cargo coverage -- <tool>` examples become `cargo coverage <tool>`; the old form runs every suite.
## Why
With `lto = "thin"`, the library crates are compiled to bitcode only (`-C linker-plugin-lto`). The final binary (`-C lto=thin`) then re-optimizes and re-codegens the whole program on every rebuild. After a one-line `oxc_minifier` edit, `cargo build --timings` showed where the time went:
| | `oxc_minifier` lib | `oxc_minsize` lib | `oxc_minsize` bin |
|---|---|---|---|
| `lto = "thin"` | 5.2s | 1.9s | **19.4s** (whole-program ThinLTO) |
| `lto = false` | 7.2s | 2.3s | **0.7s** (plain link) |
Every binary built with this profile pays that step separately. That covers the harnesses (`oxc_minsize`, `oxc_track_memory_allocations`, `oxc_coverage`) and the NAPI test builds (`pnpm build-test`). The profile's own comment says `lto = "thin"` was chosen for "faster compile time", and turning LTO off is faster still. The profile exists to run the harnesses with debug assertions, which doesn't need cross-crate LTO.
## Evidence: replaying 11 real `main` commits
Setup:
- **Commits:** the 11 picked from the rust 1.99.1 window. They cover minifier, ecmascript, codegen, parser, transformer and formatter, plus two `oxc_ast`/`oxc_str` refactors that rebuild 66 crates. Four of them change `allocs_*.yaml`, and one also changes `minsize.snap`.
- **Per commit and config:**
1. Build at the commit's parent (untimed).
2. Check out the commit.
3. Time `cargo minsize`, `cargo allocs` and `cargo coverage`, with build and run timed separately.
- **Machine:** 4-core Linux VM; each config used its own target dir.
| config | total edit→result | per commit | harness run (median) | target dir after 11 commits |
|---|---|---|---|---|
| `lto = "thin"` (before) | 1580s | 110–174s | 16.0s | 2.1 GB |
| **`lto = false` (this PR)** | **923s (1.7×)** | 36–140s (1.2× deep → 3.8× leaf) | **16.0s** | **1.8 GB** |
| `lto = false` + `CARGO_PROFILE_COVERAGE_INCREMENTAL=true` | 463s (3.4×) | 23–83s | 16.9s (+6%) | 12 GB (9.9 GB of it the incremental cache) |
**Correctness:** all 33 runs (3 configs × 11 commits) produced outputs byte-identical to the snapshots committed at each commit. That includes the commits whose snapshots changed.
## CI
PR CI on `c6014a89` passed. Several jobs that build with this profile are path-filtered or Windows-only on PRs. So I also triggered `ci.yml` manually on this branch ([run 39706156660](https://github.com/oxc-project/oxc/actions/runs/39706156660)), which runs them regardless of what changed. All 21 jobs that ran passed. The coverage-profile ones:
| job | how it uses the profile | result |
|---|---|---|
| Conformance, Minification Size, Allocations, Linter changes | `cargo coverage` / `minsize` / `allocs` / `lint-timings` | ✅ snapshots unchanged |
| Lexer (generic, AVX2, Neon) | lexer conformance via `oxc_coverage` | ✅ snapshots unchanged |
| Test NAPI Compiler, Test NAPI Oxlint, Test NAPI Oxfmt, Test Codegen | `pnpm build-test` | ✅ |
| Test NAPI Compiler (windows) | `cargo build --profile coverage` of the NAPI crates | ✅ |
**Not run before merge:** Test NAPI Oxlint (windows) and Test NAPI Oxfmt (windows). Their condition is `github.ref_name == 'main'`, with no manual trigger. The risk is low:
- Their Linux versions, which run the same `build-test` scripts, passed.
- Building NAPI libraries with this profile on Windows passed in Test NAPI Compiler (windows).
**Not affected:** the lexer benchmark uses the `bench` profile.
## Incremental: documented as opt-in only
Incremental roughly halves edit→result again, but its cache grew to ~10 GB over 11 commits and harness runs got ~6% slower. So `AGENTS.md` documents it as an opt-in env var with those costs, and it stays out of the profile.
## `cargo coverage -- <tool>` examples
The `coverage` alias already ends in `--`, so `cargo coverage -- parser` passes `-- parser` to the binary. `pico_args::subcommand()` then returns `None`, and the binary runs `run_all()`:
| command | suites run | time |
|---|---|---|
| `cargo coverage parser` | 4 | 5.0s |
| `cargo coverage -- parser` | 28 | 15.3s |
**Not included (possible follow-ups):**
- `just conformance <tool>` has the same problem.
- `oxc_coverage` could skip a leading `--`, so both forms work.
## Verification
- `typos` and the repo's pinned `oxfmt@0.74.0 --check` pass on `AGENTS.md` and `Cargo.toml`.
- `pnpm install` failed in the sandbox with a proxy 403 on a GitHub tarball, so I ran oxfmt directly.
- Before pushing, I also ran the harnesses, lexer conformance (generic and AVX2) and the NAPI crate build locally with LTO off. All matched HEAD.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01GZu5GvJLQYkfYHTAoH2yac
Fast path for short strings.
---
According to local benchmark, it seemed to be effective for the formatter as well, but on CodSpeed, it appears to be effective only for codegen... 🤔
Raise Oxlint's optional `oxlint-tsgolint` peer dependency from `>=7.0.2003` to `>=7.0.2005` and update the lockfile specifier. Version 7.0.2005 is the latest published release and is already pinned by the workspace.
Register `typescript/no-unsafe-enum-assignment` as a type-aware rule backed by oxlint-tsgolint 7.0.2005, following https://github.com/oxc-project/tsgolint/pull/1316. Add rule documentation and regenerate the rule registry, JSON schema, and TypeScript config types.
Extend the existing type-aware CLI fixture with unsafe assignments, arguments, returns, assertions, mutations, and enum-key access, plus valid enum-member and bitwise-combination cases. Reuse the existing tests and update their diagnostic and count snapshots.
Add `allowMixedPromiseArrays` to `typescript/await-thenable`, following https://github.com/oxc-project/tsgolint/pull/1307. Oxlint now validates and forwards the option, and exposes it in the JSON schema and TypeScript config types. It defaults to `false`; when enabled, Promise aggregators accept mixed synchronous and Promise-like inputs while synchronous-only inputs still report errors.
Handle sequence expressions when converting if statements to logical
expression statements:
`if (a(), !b); else c();` -> `a(), b && c();`
`if (a(), !b) c();` -> `a(), b || c();`
this change has been extracted from #27664
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
Async-from-sync `.throw(error)` currently looks up the sync iterator's `return` method. Change that lookup to `throw` in both CommonJS and ESM helpers, matching [Babel's implementation](https://github.com/babel/babel/blob/main/packages/babel-helpers/src/helpers/asyncIterator.ts).
The upstream `yield-star/throw-method-sync-iterator` regression added in #28057 now passes; update its execution snapshot. `just test-transform` reproduces the expected results: 320/418 Babel execution tests and 40/42 local execution tests, with no new failures. Direct delegation checks also pass for both helper formats.
Source imports such as `import source type from "./f.wasm"` were rejected because a shortcut for value imports named `type` skipped the required `from` token.
Remove that shortcut. The ambiguous `import type from "module"` form is already handled before specifier parsing, so source imports now consume their `from` clause normally. The TypeScript `importSource3.ts` fixture added in #28057 covers the regression; the snapshots also record the more precise recovery diagnostic for an invalid deferred import.
The Babel class-field fixtures added in #28057 use escaped `in` and `instanceof` names after an initializer. Oxc consumes these names as binary operators and rejects valid class members.
Stop binary-expression parsing at these escaped names after a newline so ASI can end the initializer and class-member parsing can consume the name. Preserve escaped-keyword diagnostics for same-line operator expressions and after ASI, with diagnostic fixtures for both newline cases.
The Annex B fixtures added in #28057 expose that `let-- > 0` and `let /* comment */ --> 0` are incorrectly parsed as lexical declarations. In sloppy scripts, `let` can be an identifier followed by a postfix update.
Include `++` and `--` in the expression lookahead, preserving normal postfix and line-terminator handling. Regression coverage comes from the upstream fixtures now merged into `main`.
Rebased onto the merged submodule update. `just submodules` and `just coverage` completed on the rebased branch; Babel parser positives improve from 2257/2280 to 2259/2280, and the corresponding semantic failures disappear. The existing HTML-comment formatter failures now report an AST mismatch instead of a parse error; their separate fix is being prepared. Parser lint, formatting, and allocation tracking also completed.
Fixes#27404
```jsx
export function Component() {
let x = 0;
try {
const unused = 1;
} catch {
x = 2;
}
return <div>{x}</div>;
}
```
Before: `assertion failed: Found a block with a single predecessor but where a phi has multiple (2) operands`, which aborts the process in release builds (Vite with `oxc-transform-react`, oxlint `react/*` rules).
After: compiles, with `x` folded to the constant `0`.
## Cause
Once dead-code elimination empties the `try` body, `prune_maybe_throws` removes the edge to the `catch`, and the catch blocks become unreachable. Two references to them were left behind:
- The phi at the join after the `try` kept an operand for the catch path, so `merge_consecutive_blocks` hit its `assert_eq!`.
- The join's `preds` still counted the removed blocks, so it was not merged into its real predecessor. When the `catch` had more than one block, the code after the `try` was silently dropped: `try { const u = 1 } catch { if (c) a(); else b(); } return <div />` compiled to `function Component(props) {}`.
Babel's React Compiler (1.0.0 and upstream main at `ab0d927f`) reports the first case as an invariant error and skips the function, and compiles the second to the same empty function, which renders `null`.
## Fix
- `prune_maybe_throws` recomputes predecessors, drops phi operands from removed blocks, and eliminates phis this makes redundant, as constant propagation already does.
- The `merge_consecutive_blocks` assertion is now an Invariant diagnostic, so any remaining case skips the function instead of crashing.
## Tests
Five new fixtures: the issue repro, an `if` inside the `catch`, a dead `try` in a loop, nested `try`, and a `catch` with no reassignment. On `main`, three panic, one bails with a different invariant and one compiles to an empty function. With this PR, all five compile, and their output renders the same as the original source in React 19 across the fixtures' `sequentialRenders`. No existing snapshot changes. The issue repro is also a `pass` case for `react/invariant`.
AI disclosure: written with Claude Code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bvq2daQaudW29wmuShT7jq
`js/object-prop-break-in/short-keys.js` is in the "Not idempotent" list
of the Prettier conformance report. The cause is how a property key's
width is measured for the short-key check (`width < indent_width +
MIN_OVERLAP_FOR_BREAK`):
- a quoted key goes through `FormatLiteralStringToken::width()`, its
display width (`'古体诗'` -> 6)
- an unquoted key goes through `SourceText::span_width()`, its `char`
count (`古体诗` -> 3)
So the first pass breaks after `'古体诗':` like Prettier, unquotes the key,
and the second pass sees a 3-wide key and joins the line again:
```js
// pass 1 (same as Prettier)
古体诗:
"https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about",
// pass 2
古体诗: "https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about",
```
An unquoted wide key (`古体诗: ...` in the input) never breaks at all,
unlike Prettier, whose `isObjectPropertyWithShortKey` uses
`getStringWidth`.
This makes `span_width` return the display width
(`UnicodeWidthStr::width`). Its only callers are these key-width checks
(object/binding/assignment-target properties, plain and computed), so
nothing else changes.
Tests:
- New fixture `js/unicode/wide-char-short-key.js` (unquoted, quoted and
computed wide keys, destructuring); it fails on `main`, and its output
is identical to Prettier 3.9.10 (the pinned version) at both option sets
and stable on a second pass.
- `short-keys.js` drops out of the "Not idempotent" list in
`conformance__prettier-js.snap`; no other conformance change.
- `cargo test -p oxc_formatter -p oxc_formatter_core --all-features`,
`cargo fmt --check` and `cargo clippy --all-targets --all-features` on
both crates pass.
AI disclosure: this PR (code, tests and description) was written with
Claude Code (Claude Opus 5.5); the verification above is what was run.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Yuji Sugiura <6259812+leaysgur@users.noreply.github.com>
When the operand of an instantiation expression binds looser than the
type arguments, the formatter drops its parentheses:
```ts
// input
x = (c ? a : b)<T>;
x = (y++)<T>;
x = (a as B)<T>;
// before
x = c ? a : b<T>; // instantiates only `b`
x = y++<T>; // does not parse
x = a as B<T>; // `B<T>` is now the type
// after: unchanged from the input
```
The same happens for binary/logical operands, `++y`, unary operators
(`!`, `typeof`, `void`, `delete`, `-`), `satisfies` and `<B>x`. Found by
formatting Biome's `ts/expression/instantiation_expression.ts` fixture
twice: the second pass failed to parse.
`<T>` binds like a non-null `!`, so this adds
`TSInstantiationExpression` next to `TSNonNullExpression` in the three
helpers that decide those parens (`type_cast_like_needs_parens`,
`binary_like_needs_parens`, `update_or_lower_expression_needs_parens`).
Operands that don't need them (`(a.b)<T>`, `(a())<T>`, `` (a`x`)<T> ``)
still lose them.
Prettier (pinned version and 3.9.10) prints the broken form too, so I
extended `DIVERGENCES.md#instantiation-expression-parens` and its pin
fixture instead of adding a new entry.
Verification:
- Parsed each of 33 operand shapes before and after formatting with
TypeScript 5.9.3 and compared the ASTs (parens stripped): on `main` 17
change shape or fail to parse, with this change all 33 match.
- The extended fixture fails on `main` and passes with this change.
`cargo test -p oxc_formatter --all-features` (fixtures + Prettier
conformance, no snapshot changes besides the fixture), `cargo fmt
--check` and `cargo clippy -p oxc_formatter --all-targets
--all-features` pass.
AI disclosure: this PR (code, tests and description) was written with
Claude Code (Claude Opus 5.5); the verification above is what was run.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: leaysgur <6259812+leaysgur@users.noreply.github.com>