Block comments containing U+2028 or U+2029 were marked `SingleLineBlock` unless they also contained LF or CR. Switch to the multiline-comment scanner after either Unicode separator so downstream consumers receive the correct comment kind.
This prevents the formatter from dropping parentheses around a return argument with such a leading comment, which could change the returned value to `undefined`.
The parser consumes `source`, `defer`, and sometimes `from` as binding identifiers before resolving their role in an import declaration. Validate escapes once they are known to be keywords, so declarations such as `import s\u006furce wasm from "m"` and `import source fr\u006fm "m"` report an error.
Escaped binding names remain valid, including `import s\u006furce from "m"` and `import source fr\u006fm from "m"`.
Recognize `await` and `yield` as binding names when looking ahead for TypeScript function types and index signatures. This allows valid syntax such as `(await: number) => void` and `interface I { [await: string]: unknown }`, while leaving contextual legality to actual parsing.
Require `=>` when parsing TypeScript function and constructor types, while retaining `:` for function, method, and arrow return annotations. Previously, any token could be consumed as the separator, accepting invalid types such as `type F = (): number` and `type F = new () + number`.
This also prevents a valid conditional expression from being silently grouped into the wrong arrows:
```ts
const a = true, b = 1, c = 2;
const f = a ? (b) : (): any => b ? c : (x = 1): any => x;
console.log(typeof f);
```
The transformed program now prints `number`; previously it printed `function`.
Allow classic `for` initializers such as `for (await using;;)` when `using` is an ordinary identifier. Reuse the statement parser’s `await using` lookahead so resource declarations require a following binding identifier, preserving the existing line-break restrictions.
Allow parenthesized comma expressions such as `<A>{(a, b)}</A>` and `<A x={(a, b)} />` when `preserve_parens` is false.
Compare the sequence expression's start with the first token's start to retain the grouping distinction after parentheses are omitted from the AST. Ungrouped comma expressions such as `{a, b}` and `{(a, b), c}` remain invalid.
Constructor overloads and ambient signatures previously bypassed the return type check because it required a body. Reject their return annotations with TS1093, matching constructor implementations.
Highlight only the return type itself, excluding the colon and preceding whitespace.
Reject `await using` in non-async functions and ordinary generators, including `for` and `for...of` loop heads. Treat top-level `await using` as module syntax when the source type is unambiguous.
Reject invalid initializers such as `for (using x = a in b;;);` by parsing `using` and `await using` declarations with `Context::In` disabled, matching ordinary for-loop declarations. Restore the outer context afterward so parenthesized `in` expressions and the remaining loop expressions keep their existing behavior.
Allow escaped value names such as `import { t\u0079pe as x } from "m"` and the equivalent named exports. Reject escapes only after resolving `type` as a type-only modifier.
Preserve the decoded identifier name in the ambiguous `type as as` form so its AST name remains `type`.
`class C { async x; }` was accepted in JavaScript and TypeScript, silently discarding `async` from the resulting field. Reject the modifier after parsing a class field, once fields have been distinguished from methods.
Async methods, fields named `async`, and newline-separated fields remain valid.
Require a comma after a TypeScript `this` parameter when the parameter list continues. Previously, malformed syntax such as `function f(this: T x: T) {}` parsed without a diagnostic.
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.
## 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.
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.
## 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.
## 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;;);
```
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.
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.
`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.
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.
`TriviaBuilder::pure_comments` and `no_side_effects_comments` are index ranges into `comments`.
Previously they were stored as `Option<(usize, usize)>` (24 bytes). Instead, store them as `Option<(u32, NonZeroU32)>` (8 bytes, due to `NonZeroU32`'s niche).
`end` is always at least `start + 1`, so it cannot be 0. Comment count cannot exceed `u32::MAX` because of the `MAX_LEN` check in `Source::new`.
`LexerCheckpoint` contains both ranges, so this change shrinks it by 32 bytes.
Micro-optimization to `parse_jsx_element_name`.
`parse_jsx_element_name` inspects the first byte of the identifier name to determine if it should be treated as a reference (`<Foo>` vs `<div>`).
`parse_jsx_identifier` already ensured the identifier is valid, so that first byte can only be one of `[A-Za-z$_]` or the identifier starts with a non-ASCII character. Utilize this constraint to make the "is this a reference" check cheaper.
The new version compiles down to a single `cmp` instruction, shaving off one instruction from the previous version.
It's only 1 instruction, but why not?
Small perf optimization to parser.
Remove the `if self.fatal_error.is_some()` branch from `parse_jsx_element_name`.
The guard existed so that code below it doesn't try to read the first byte of the dummy identifier's empty name after `parse_jsx_identifier` fails. Instead, make the error path return `contains_dash = true`, which skips reading the name in `parse_jsx_element_name`.
In `parse_jsx_member_expression`, a dash is already treated as an error, and its `fatal_error` call is a no-op because a fatal error has already been recorded. The other callers ignore the flag.
This removes a check, but main motivation is preparation for larger changes to how fatal errors are handled in further PRs to follow.
Technically a breaking change, but one that's extremely unlikely to affect any real-world applications.
On 64-bit systems, the maximum source text length was exactly `u32::MAX`.
Lower it by 256 bytes, for 2 reasons:
1. `oxc_lexer` will require source text to be followed by 64 bytes of padding. Reducing max source length ensures _padded_ length can always fit in a `u32`.
2. Counts of tokens, comments and errors are all bounded by source length plus a small constant. By reducing max source length, these can all be stored as `u32`s without any possibility of overflow.
`oxc_lexer` only needs 64 bytes of padding. Have gone with 256 to leave headroom in case later redesigns require more than 64. This avoids another breaking change later. We can always switch to 64 later on if we want, and this later relaxation would not be a breaking change.
All the changes to other crates in this PR are just updating comments which talk about max source length.
Small perf optimization to parser.
`Lexer::token` is the token currently being built. Every method which produces a token ends in `finish_next_inner` or `finish_re_lex`, and both reset it to `Token::default()`. So between lexer calls it is always `Token::default()`, so there is no point in saving and restoring it - it's always a no-op.
Remove the `token` field from `LexerCheckpoint`, which reduces its size by 16 bytes.
Add a debug asserts in `checkpoint` and `checkpoint_with_error_recovery` checking this invariant.
Fix tokens produced when `<<` opens a type argument list. e.g.:
```typescript
type Bar = ReturnType<<T>(x: T) => number>;
```
Previously only 1 `<` token was produced between `ReturnType` and `T`. This PR corrects that so 2 `<` tokens are produced.
TS-ESLint get this wrong too - hence why this wasn't noticed before, as our conformance tests compare to TS-ESLint.
PR submitted to fix TS-ESLint: https://github.com/typescript-eslint/typescript-eslint/pull/12821
In meantime, this PR skips the erroneously failing ESTree tokens conformance tests. We can can re-enable those tests once the fix lands in TS-ESLint.
## Summary
Fixes#26088
`NO_SIDE_EFFECTS` can either be applied to a function:
```rs
/* #__NO_SIDE_EFFECTS__ */ function foo() {}
```
Or be meaningless for its position, since it only works on function
declaration:
```rs
/* #__NO_SIDE_EFFECTS__ */ value;
```
But oxc could not distinguish these two cases.
This PR adds `CommentContent::NoSideEffectsNotApplied`, mirroring the
existing `PureNotApplied`.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
When the parser records a fatal error it fast-forwards the lexer to end of file. `re_lex_template_substitution_tail` did not check for that, so an incomplete template substitution would still re-lex the `}` and derive the token's start from a cursor which had already jumped to the end.
With tokens enabled, that fails the debug assertion in `finish_next_inner`, because the token being replaced is no longer the one on top of the collected token stream. Release builds silently overwrite an unrelated token instead. Tokens are only collected for Oxlint's JS plugins, which bail out if any parsing error, which is why this hadn't been noticed before.
Fix by adding the same `fatal_error` guard, added to other re-lex methods in #11023.
This does make a difference to diagnostics output for invalid code matching this pattern. TypeScript snapshot loses an "Unterminated string" diagnostic on 2 fixtures. Those errors came from the bogus re-lex reading past end of file. Both these fixtures already have an "Unexpected token" error. The additional "Unterminated string" errors were incorrect, and this fix has the side effect of removing them.
`Lexer::finalize_tokens` discards the last collected token - the `Eof` that terminates every token stream - and debug asserted that it was `Eof`.
Turns out that assumption does not always hold. Where the lexer cannot lex any further it emits `Kind::Undetermined`, and no `Eof` ever follows, so the last token is `Undetermined` and the debug assertion fails. This can be triggered by an unterminated string, for example:
```js
foo();
'unterminated
```
Like `Eof`, `Undetermined` is not a "real" token, so either way it's fine to remove it from the token stream. The fix is just to relax the debug assertion to accept either.
Release behaviour is unchanged - it was only a debug assert. Only came across this while running the conformance tests which compare current lexer to the new one (`oxc_lexer`).
## Summary
Fix a parser panic when a string-named export decodes to a literal `\u` sequence.
Lone-surrogate validation now uses the metadata captured by the lexer instead of rescanning decoded string contents. This also prevents false diagnostics for names such as `"\\uD800"`.
## Before:
```
thread 'main' (10990283) panicked at crates/oxc_ast/src/ast_impl/literal.rs:89:42:
end byte index 4 is out of bounds for string of length 0
stack backtrace:
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: core::str::slice_error_fail_rt
3: core::str::slice_error_fail
4: <core::ops::range::RangeTo<usize> as core::slice::index::SliceIndex<str>>::index
5: <str as core::ops::index::Index<core::ops::range::RangeTo<usize>>>::index
6: <oxc_ast::ast::literal::StringLiteral>::is_string_well_formed_unicode
7: <oxc_parser::ParserImpl<oxc_parser::config::NoTokensParserConfig>>::parse_module_export_name
```
## Summary
Comments are now attached to their adjacent tokens using source boundaries.
A leading comment belongs to the token that follows it:
```js
/* leading */ value
^ attached_to
```
A trailing comment belongs to the token that precedes it:
```js
value /* trailing */
^ attached_to
```
`Comment::attached_to` now consistently represents either:
- the start of the following token for leading comments;
- the end of the preceding token for trailing comments.
## Summary
- notify `TriviaBuilder` when the lexer consumes U+2028 or U+2029
- keep Annex B HTML-close comments after irregular line terminators attached as leading trivia
- add a script-mode regression test covering both Unicode separators
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [rust](https://rust-lang.org/)
([source](https://redirect.github.com/rust-lang/rust),
[changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md))
| toolchain | minor | `1.97.1` → `1.98.0` |
---
### Configuration
📅 **Schedule**: (in timezone Asia/Shanghai)
- Branch creation
- "before 10am on monday"
- 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:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
`@__KEY__` and `#__KEY__` are currently treated as normal comments. Codegen can drop them before a later property-mangling pass. The property may then be renamed while the annotated string keeps its old name.
This classifies the marker as a property-key annotation. Codegen keeps it next to string and no-substitution template literals when annotation comments are enabled, and the property mangler reads that classification instead of scanning source text. Final output still follows the normal comment options, so whitespace-minified or annotation-disabled output removes the marker.
## Example
Input:
```js
const object = { _field: 1 };
const key = /* #__KEY__ */ "_field";
object[key];
```
Before, an intermediate codegen pass could remove the marker:
```js
const object = { _field: 1 };
const key = "_field";
object[key];
```
After, the marker survives for the later property-mangling pass.
Verified with the affected crate suites, `just minsize`, workspace lint, docs, AST generation, and independent review. `just ready` is only blocked locally by existing Node-version and terminal-color snapshot differences.
Implemented with AI assistance (OpenAI Codex). I reviewed and understand the changes and remain responsible for this contribution under the repository AI usage policy.
Rejects initialized `let` and `const` declarations in `for...in` during parsing while preserving Annex B's non-strict `var` form.
Adds misc coverage for valid and invalid initializer forms.
Verified with `just ready` and parser conformance.
Closes#25149.
AI-assisted.
Quoted constructor methods bypassed the AST-based constructor validation. Validate type parameters after resolving the method key, preserving the AST shape.
Validation: `just ready`, parser conformance, and allocation checks.
Closes#25692.
AI-assisted with Codex.
Stop parsing delimited-list elements after a separator when EOF or an undetermined token follows, preventing malformed tuple syntax from creating reversed spans during error recovery.
Validated with `cargo coverage -- parser --filter oxc-23670 --detail`.
AI assistance was used to prepare this change.
Closes#23670.
Fixes#23681.
Preserve initial fatal parser errors during top-level `await` reparsing, preventing malformed recovery AST nodes from reaching Oxlint.
Validated with parser unit/conformance suites and allocation snapshots. `just ready` only fails three unrelated ANSI snapshot checks in the current environment.
AI-assisted.