Commit Graph

14568 Commits

Author SHA1 Message Date
Samuel Attard 9ef028c2ad feat(codegen): add ascii_only option (#25994)
Closes #17068. Supersedes #18095 / #18118.

Adds `CodegenOptions::ascii_only` (and `codegen.asciiOnly` in
`oxc_minify_napi`): escape every non-ASCII character in string literals,
untagged template literals, regexp literals, directives and identifier
names so the output is 7-bit clean — like esbuild `--charset=ascii` /
terser `format.ascii_only`. Off by default; the default path is
untouched (codegen bench and conformance unchanged).

```js
// Input
let café = "naïve 😀";
let re = /[–—]/u;

// Output with ascii_only: true
let caf\u00E9 = "na\u00EFve \u{1F600}";
let re = /[\u2013\u2014]/u;
```

Characters above U+FFFF use ES2015 code-point escapes. Regex patterns
use escaped surrogate pairs instead; RegExp.prototype.source reflects
the escaped spelling.
Tagged template quasis, JSX names/text/attribute strings, hashbangs and
preserved comments may still contain non-ASCII characters. JavaScript
expressions inside tagged templates and JSX are escaped normally.

Performance; with the option disabled, codegen codspeed benchmarks are
withing noise (<1%). With the option on, performance is only affected
for files that contain non ascii characters.

---

Our use case is a large Vite/Rolldown app served through proxies that
mangle non-ASCII JS (previously handled by terser's `ascii_only`);
#17068 also lists deno_core snapshots and Chrome extension content
scripts.

AI-assisted (Claude Code); I reviewed the code, wrote the tests, and
have been running this in production builds via a vendored
`oxc_codegen`.

---------

Co-authored-by: Samuel Attard <6634592+MarshallOfSound@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
Co-authored-by: Dunqing <dengqing0821@gmail.com>
2026-09-10 17:10:02 +01:00
camc314 d7713ad4cb fix(parser): classify Unicode line breaks in block comments (#26536)
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`.
2026-09-10 15:28:49 +00:00
camc314 75cd919ffb fix(transformer): preserve receivers in private optional chains (#26535)
Only bind a private field when it is the result of an optional chain being called. In `(this.#items.get(key)?.find(fn))?.()`, the intermediate `#items` field must not receive `.bind(this)`.

Normalize non-null assertions in the TypeScript transform before other transforms inspect the chain. This preserves the receiver for `(this?.#fn!)()` and `(this?.#fn!!)()` without mixing TypeScript assertion removal into the class transform.

Fixes #26528
2026-09-10 15:09:24 +00:00
camc314 b32d25d418 fix(parser): reject escaped import-phase keywords (#26534)
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"`.
2026-09-10 14:55:08 +00:00
camc314 47b83115f0 fix(parser): recognize contextual binding names in type lookaheads (#26532)
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.
2026-09-10 14:05:25 +00:00
camc314 1d681b0d4c perf(linter/react/jsx-pascal-case): avoid temporary name segment vector (#26531)
Iterate JSX name segments directly instead of collecting them into a temporary vector. Namespaced names still split on `:`, member expressions on `.`, and simple names remain a single segment.
2026-09-10 13:25:00 +00:00
camc314 d6b6705f8f fix(parser): require arrow separator in TypeScript function types (#26529)
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`.
2026-09-10 13:05:18 +00:00
camc314 42315366b1 fix(linter): compare label text in runtime optimization assertion (#26526)
Include label text when sorting diagnostics for the optimized/unoptimized runtime assertion. Diagnostics with identical spans and messages but different labels previously compared equal, so sorting could pair different diagnostics and trigger a false assertion failure.

Fixes the assertion seen in the [Kibana ecosystem CI job](https://github.com/oxc-project/oxc-ecosystem-ci/actions/runs/34459300249/job/102815162719).
2026-09-10 11:05:59 +00:00
camc314 e98beef227 fix(parser): disambiguate await using in for initializers (#26527)
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.
2026-09-10 10:59:58 +00:00
camc314 36d5534689 fix(linter): include file path in diagnostic assertion (#26525) 2026-09-10 10:37:11 +00:00
camc314 92afee6734 fix(parser): allow parenthesized JSX comma expressions with preserve_parens=false (#26524)
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.
2026-09-10 10:30:38 +00:00
camc314 31508b1be5 fix(parser): reject return types on constructor overloads (#26523)
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.
2026-09-10 10:10:39 +00:00
camc314 38533ace80 docs(ast): move type annotation span comment to span field (#26522) 2026-09-10 10:01:29 +00:00
leaysgur 586f27a166 feat(formatter_core): add prefix_align builder and Tag::(Start|End)Prefix (#26520)
Mainly for blockquote for Markdown.

```md
> If this line is long and to be broken, how printer know new line needs `> ` prefix?
```

This will also help JSDoc formatting (`* ` prefix) if we update it via IR, currently just splitting lines.
2026-09-10 09:08:50 +00:00
overlookmotel 35f4ead974 docs(lexer): document chunk functions (#26510)
Docs-only change. Add doc comments to all the primitive functions in `chunk.rs`.
2026-09-10 07:27:19 +00:00
overlookmotel 44bf12d17d refactor(lexer): make veq and mm safe functions (#26509)
Small internal change.

`veq` and `mm` impose no safety preconditions on the caller. Make them safe functions.
2026-09-10 07:27:19 +00:00
overlookmotel f5b0b9ed19 refactor(lexer): rename load8 to load64 (#26508)
Pure refactor - just renaming.

`load8` loads 8 bytes. Rename it to `load64` (64 bits).

This matches `load256`, which loads 256 bits (not bytes).
2026-09-10 07:27:19 +00:00
overlookmotel 795a9fe1bb refactor(lexer): rename finder! macro to define_find_function! (#26507)
Pure refactor - just renaming.

Rename the `finder!` macro to the more descriptive `define_find_function!`.
2026-09-10 07:27:18 +00:00
overlookmotel a15dd9b635 refactor(lexer): split find module into multiple files (#26506)
Pure refactor.

Split `find` module into parts.

- `find` directory contains find primitives (`find1`, `find2` etc) and specialized `find_*` functions.
- `scan` directory contains `scan_*` functions.
- `chunk` module contains primitives for reading and manipulating chunks (`load256`, `mm`, `veq` etc).

In `find` and `scan`, AVX2 and generic code is split into separate files, which both export the same functions.

Each component is now small enough to read as a whole.

This is pure refactor in the strictest sense. The ASM the crate compiles to remains identical to before this PR.
2026-09-10 07:27:18 +00:00
leaysgur 4ae43015c0 refactor(formatter_tests): trim conformance helpers (#26517)
Remove redundant lines.
2026-09-10 04:10:57 +00:00
leaysgur 1c6b1424bf docs(formatter_tests): arrange comments (#26516) 2026-09-10 03:57:54 +00:00
leaysgur 41abcba2cc docs(formatter): note why jsx-test-suite snippets stay off (#26515)
For now, `jsx-test-suite` are off by intension. Almost all cases are already covered by our fixtures.
2026-09-10 03:57:54 +00:00
leaysgur aef8b4acce fix(formatter_css): handle less-test-suites failures (#26513)
Follow up on #26469 for CSS.

Updated `oxc-css-parser` to `v0.0.15`.

---

- Keep parens for `calc((a + b))` by adding `CalcParenthesized` AST
  - Do not follow JS formatter approach (remove all, then add back), only works for CSS, not for SCSS/Less
- Fix Less operand spacing: `2em*1em` -> `2em * 1em`
- Rework `@import` prelude fill
- Leave glued `and(` and `when(` as-is
  - `@media screen and (max-width:1280px) { }` and `@media screen and(max-width:1280px) { }` is different
  - We do not alter behavior
- Format `LessMixinDefinition` and `LessConditionalQualifiedRule`
  - We still have a room to go better than Prettier here, but keep for now
2026-09-10 03:31:42 +00:00
Armano 029c84bcda perf(minfier): update expressions in place when substituting alternate syntax (#26460)
- `substitute_typeof_undefined`: do not recreate binnary expression and
instead just update right/left argument and operator
- `substitute_rotate_binary_expression`: replace `take_in*2` and
reassign with `std::mem::swap`
- `substitute_object_or_array_constructor`,
`substitute_global_new_expression`: introduce new helper
`transform_new_expr_to_call_expr` to convert new to call expr
- `substitute_iife_call`: use array to alloc all items in sequence
without reallocs
2026-09-10 09:44:47 +08:00
overlookmotel 0df2b6c197 refactor(lexer): move KwSet selection out of lex_raw (#26503)
Pure refactor. Small change to `oxc_lexer`.

`coalesce` stage selects a `KwSet` depending on source type - JS or TS. Move making this selection from `lex_raw` into `coalesce` itself. This just removes a little clutter from `lex_raw` and makes it clear that only `coalesce` stage uses it.
2026-09-09 16:47:21 +00:00
camc314 2ad7d498a0 perf(linter/promise/catch-or-return): avoid temporary formatting vectors (#26505)
Join formatted termination methods directly when building diagnostic and help text, avoiding temporary vectors while preserving method order and wording.
2026-09-09 16:42:06 +00:00
camc314 203235baa3 perf(linter/jsx-a11y/no-static-element-interactions): avoid role vector (#26504)
Read the first role token directly instead of collecting every token into a vector. The rule continues to inspect only the first role, preserving its existing behavior for multi-role values.
2026-09-09 16:35:09 +00:00
camc314 2e951679cc perf(linter/jsx-a11y/anchor-is-valid): avoid temporary attribute vector (#26501)
Join the allowed attribute names directly when formatting missing-href help text, avoiding a temporary vector while preserving the diagnostic wording and attribute order.
2026-09-09 15:59:52 +00:00
camc314 deafdba010 perf(linter/react/exhaustive-deps): avoid temporary dependency vector (#26502)
Join formatted dependency names directly when building the missing-dependency diagnostic, avoiding a temporary vector while preserving dependency order and wording.
2026-09-09 15:55:19 +00:00
camc314 4f1627fdc9 perf(linter/oxc/branches-sharing-code): compute deletion spans lazily (#26500)
Compute deletion spans while building shared-code suggestions instead of collecting them into temporary vectors. This also defers span computation until the suggestion closure runs, preserving deletion order and replacement text.
2026-09-09 15:31:59 +00:00
camc314 8296f9eb81 test(codegen): enable number printing cases (#26496)
Enable all 43 commented-out number-printing cases ported from esbuild, with expectations matching Oxc's existing output.

The old expectations preserved the input spelling, but Oxc uses the same value-based number printer with and without whitespace minification. It removes numeric separators and compares decimal, exponent, and hexadecimal spellings by length, keeping the current candidate on ties. For example, `1e-3` becomes `.001` (equal length), while `1000000000001` becomes `0xe8d4a51001` (shorter).

Large integer literals also reflect JavaScript `Number` rounding already applied during parsing. For example, `0xFFFF_FFFF_FFFF_FFFF` rounds to `0x10000000000000000`, so the changed spelling preserves the runtime value.
2026-09-09 15:27:09 +00:00
camc314 a091fc4b42 fix(parser): validate await context for await using declarations (#26495)
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.
2026-09-09 15:21:18 +00:00
camc314 c068fd2f44 perf(linter/react/jsx-curly-brace-presence): avoid newline position allocation (#26499)
Track line offsets while splitting multiline JSX text instead of collecting newline positions in a separate vector. Offsets remain byte-based, preserving fixes for Unicode text, blank lines, and trailing newlines.
2026-09-09 15:09:04 +00:00
camc314 ed4357c620 perf(linter/eslint/no-restricted-properties): skip empty configurations (#26497)
Skip the rule when no property restrictions are configured, avoiding node callbacks and property-access analysis that cannot produce a diagnostic.
2026-09-09 15:02:44 +00:00
camc314 92e41b7a54 perf(linter/oxc/no-barrel-file): iterate module requests directly (#26498)
Iterate matching module requests directly instead of collecting a temporary vector before counting loaded modules. Export traversal order and diagnostics remain unchanged.
2026-09-09 14:55:41 +00:00
camc314 5255ee3982 perf(linter/unicorn/prefer-single-call): avoid temporary argument vector (#26494)
Join source argument slices directly when building the merged-call fix, avoiding a temporary vector while preserving argument text and separators.
2026-09-09 14:48:05 +00:00
camc314 96ef3654ed perf(linter/unicorn/prefer-includes): reduce suggestion allocations (#26492)
Borrow the receiver source text and build argument text inside the suggestion closure. Join argument slices directly instead of collecting a temporary vector, preserving the generated replacement.
2026-09-09 14:11:13 +00:00
camc314 5501e86429 fix(parser): disallow in expressions in using for-loop initializers (#26490)
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.
2026-09-09 14:03:17 +00:00
overlookmotel 5983db126d refactor(lexer): move unicode_ws_len out of classify stage (#26484)
Pure refactor. No code changes, just moving code around.

Move `unicode_ws_len` out of `classify` stage. It's used across `carve`, `misc`, `find`, and `regex_div` - but not `classify` (since #26482).

Move it into `find.rs`, which contains all the other primitives reading from the source buffer.
2026-09-09 13:51:26 +00:00
overlookmotel 8cf13112b5 refactor(lexer): move misc dispatch and early exit out of lex_raw (#26483)
`misc_pre` and `misc_post` both have early exit branches. `misc_pre` also dispatches to 2 different implementations depending on whether UTF-8 validation is required.

This logic was all in `Lexer::lex_raw`, mixed around the calls into other stages. Move it into the `misc` module, simplifying `lex_raw`.

The early exits are handled in the `misc_pre` / `misc_post` entry points (which are marked `#[inline]`), and only pass control into the main implementations `misc_pre_impl` / `misc_post_impl` if the early exit checks pass. ASM after this PR is completely unchanged from before, so this PR is pure refactor in the strictest sense.
2026-09-09 13:51:26 +00:00
overlookmotel 73847e8563 refactor(lexer): move misc_pre and misc_post into separate module (#26482)
Pure refactor. No code changes, just moving code around.

`misc_pre` and `misc_post` were part of `classify` stage. Move them into their own separate stage.

Splitting it up into `pre.rs` and `post.rs` files is likely overkill - they could live within a single file. It's just a bit tidier for each stage to have its own directory (following the pattern of `classify`, `carve`, `coalesce` and `compress`) and it'd be weird if that directory just contained a single `mod.rs` file.

Splitting it does have the advantage of making it clear which primitives each uses from the `use` statements at top of each file.
2026-09-09 13:51:25 +00:00
camc314 a71fd7f1ea perf(linter/unicorn/no-new-buffer): avoid temporary argument vector (#26491)
Join argument source slices directly when building the `Buffer.from` or `Buffer.alloc` suggestion, avoiding an intermediate vector while preserving the replacement text.
2026-09-09 13:46:07 +00:00
camc314 11085e7aee perf(linter/jsdoc/require-yields): short-circuit JSDoc checks (#26488)
Stop checking JSDoc blocks once a non-ignored block is found instead of counting all matching blocks. In the yield-expression path, pass tags directly to the missing-tag check rather than collecting a temporary vector.
2026-09-09 13:31:07 +00:00
camc314 c8e5fa7656 fix(parser): allow escaped type names in import and export specifiers (#26487)
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`.
2026-09-09 13:22:34 +00:00
camc314 2dcee2f216 fix(parser): reject async modifiers on class fields (#26486)
`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.
2026-09-09 13:04:01 +00:00
camc314 9b1aff8c0c perf(linter/unicorn/no-useless-undefined): skip calls without trailing undefined (#26481)
Return early for calls whose last argument is not `undefined`, avoiding unnecessary callee and `bind` checks. Collect trailing undefined spans with pushes followed by one reversal instead of repeated front insertions, preserving diagnostic and fix order.
2026-09-09 12:48:10 +00:00
camc314 973d58e128 fix(parser): require comma after TypeScript this parameter (#26480)
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.
2026-09-09 12:26:51 +00:00
overlookmotel 88872b75ab refactor(lexer): add bm_clear function to clear a bitmap bit (#26478)
Pure refactor.

Add `bm_clear` function that clears a bit in a bitmap at specified index. This is the converse of `bm_set`, and single-bit version of `bm_clear_range`.

This operation is performed in various places across `classify`, `carve`, and `coalesce` stages. Replace the duplicated code with `bm_clear` calls. This makes the logic easier to read.

`bm_clear` is `#[inline(always)]` so this change makes zero difference to codegen. The changes in `carve_jsx` look more substantive, but compiling to ASM and diffing it before/after this PR shows 0 difference. The compiler is able to identify the repeated terms across the 4 calls - there's no need to perform that optimization manually.
2026-09-09 11:50:32 +00:00
overlookmotel 54aab0ce3d refactor(lexer): rename bm_set1 to bm_set (#26477)
Pure refactor. Just rename a function.

The `1` in `bm_set1` was unnecessary - "set" means set bit to 1, "clear" means unset bit to 0.
2026-09-09 11:50:31 +00:00
overlookmotel 3f0318739f refactor(lexer): deduplicate bitmap access code (#26461)
Code to read the nth bit of a bitmap was duplicated in several places across pipeline code:

- `wordbit` in `carve`
- `bit_at` in `regex_div`
- Various places inline in `carve`, `classify`, and `coalesce`

Add a function `bm_get` for this purpose to `bitmap` module (which feels like its natural home) and replace all usages with calls to this function.

This reduces repetition, and makes the call sites easier to read.

`bm_get` is marked `#[inline(always)]`, so extracting it into a function should have no effect on perf.
2026-09-09 08:12:06 +00:00