Commit Graph

33 Commits

Author SHA1 Message Date
leaysgur 0f4af5d7df test(formatter_tests): assert fingerprint survives formatting (#26322)
Historically, only JS formatter has had the `detect_code_removal` feature, which uses CI to check for missing nodes or comments.

While this would be useful in other languages (especially CSS), but setting up the mechanism is difficult. For now, just start with something simple that just guarantees the number of comments.

That said, even with a mechanism in place, it can't detect anything unless the cases are fully covered by fixtures, so this only tells us that the current state is problem-free.
2026-09-04 07:14:53 +00:00
leaysgur 54316b239f refactor(formatter_json): hide raw comment printing API, use struct instead (#26314)
#26313 for JSON formatter.
2026-09-04 07:14:51 +00:00
leaysgur 2eaede9b0b fix(formatter_core): unify leading-BOM handlings (#25340)
Follow up on #25333.
2026-08-09 14:32:09 +00:00
leaysgur 567089f3c5 refactor(oxfmt): route JSDoc fence through the dispatcher (#25334)
Markdown code fences can also be written in JSDoc, and other languages can be formatted as inline code within them.
2026-08-09 13:54:12 +00:00
leaysgur 52b146f025 refactor(formatter_core): share split_bom helper (#25333)
In order to standardise the handling of front matter, it was also necessary to standardise the handling of the BOM, which may appear before the FM.
2026-08-09 13:06:35 +00:00
leaysgur 8e779c946b refactor(formatter_core): finalize documents once at print time (#25328)
Call `propagate_expand()` inside `Formatted`, here is the only place to finalize IRs.
2026-08-09 12:33:39 +00:00
leaysgur b91d5a8afe perf(formatter_css,formatter_graphql,formatter_yaml,formatter_json): Pre alloc IR buffers (#25234)
As like `oxc_formatter`(js).
2026-08-03 06:26:00 +00:00
leaysgur 9329862e3d refactor(formatter_core): share comment cursor via SpanCursor (#25228)
Standardising the logic for adjusting comment positions using `Span`.
2026-08-03 02:49:00 +00:00
leaysgur a1842e28b7 docs(formatter_json): fix outdated separeted.rs comments (#25144) 2026-07-31 08:37:30 +00:00
leaysgur acfbf46767 refactor(formatter_core): add CoreFormatOptions and FormatOptions::apply_core (#25138)
These common, core format options:

- LineEnding
- LineWidth
- IndentWidth
- IndentStyle

should be defined in core, and to be shared.
2026-07-31 08:11:23 +00:00
leaysgur f56009a676 fix(oxfmt): correct prose about comment width in fits measurement (#25054)
Audited every formatter crate against the trailing-comment width discipline.

- same-line trailing line comments ride `line_suffix` and are excluded from the fits measurement
- while trailing block comments print inline and count

Corrected the prose that misstated it.
2026-07-29 02:57:17 +00:00
Boshen 3d223072c2 feat(parser): add ParseOptions::enable_ident_hashes (#24491)
Adds `ParseOptions::enable_ident_hashes` (default `true`) and `Ident::new_unhashed` in `oxc_str`, and turns it off in the parse-only paths of the `oxc-parser` napi crate and the JS/JSON formatters.

### Why

`Ident`'s precomputed hash (#19143) is a parse-time investment that pays off in semantic analysis — but parse-only consumers (parse + serialize, formatting) pay it for nothing. Profiling against other native parsers showed identifier hashing at 3.5-4.6% of parse time.

Measured on M-series (yuku-style native harness, 50 warmup + 300 runs, median):

| fixture | default | option off |
|---|---|---|
| react.development.js | 0.156 ms | 0.152 ms (−2.3%) |
| binder.ts | 0.383 ms | 0.370 ms (−3.5%) |
| App.tsx | 1.140 ms | 1.111 ms (−2.5%) |
| checker.ts | 7.862 ms | 7.499 ms (−4.6%) |
| typescript.js | 25.07 ms | 24.16 ms (−3.6%) |

### Contract

Unhashed `Ident`s store hash `0`; `Eq`/`Hash`/`ContentEq` include the stored hash, so unhashed and hashed `Ident`s of the same string do not compare equal. Semantic analysis (or anything relying on `Ident` hashing) must not run on an AST parsed with the option disabled. Default behavior is unchanged.

Every `Ident` the parser creates funnels through the `ParserImpl::ident()` helper, so the option applies uniformly. This covers identifiers lexed directly (`parse_identifier_kind`, `parse_private_identifier`) as well as those reconstructed from a `&str`/`Str` — import/export specifier locals, JSX element/member names, the TS `intrinsic` type name, TS import-type option keys (`with`/`assert`), and the `import x = this` recovery path — which would otherwise always be hashed through `Into<Ident>`. Exhaustively checked by parsing the test262/babel/typescript/misc/prettier corpora (96.5k files, 5.6M identifiers) in both option states and walking every `Ident` in the AST.

On 64-bit platforms `Ident::new_unhashed` compiles to a no-op: an unhashed `Ident` has the same layout and bit representation as `&str`.

### Opt-outs

- **napi `oxc-parser`** ties `enable_ident_hashes` to whether semantic errors are requested. Both parse entry points run `SemanticBuilder` only when `showSemanticErrors` is set, so the common parse-and-serialize path skips hashing while the semantic-errors path keeps it. Serialized output is unchanged — the hash is internal to `Ident` and never emitted.
- **JS and JSON formatters** turn it off: they never run semantic analysis and only read `Ident::as_str` / compare idents against string literals. Exception: the JS formatter keeps hashes when built with the `detect_code_removal` feature, whose check runs `SemanticBuilder` on the formatter-parsed AST.
2026-07-14 15:55:27 +00:00
leaysgur 0a6b16c424 fix(formatter_json): preserve key and literal value for json-stringify (#23996)
Part of #23923

Do not transform object key, string and number values with `json-stringify`.
2026-07-03 07:22:00 +00:00
overlookmotel 5201522945 refactor(allocator): Vec construction methods take &GetAllocator (#23755)
Part of #23043.

`Vec::new_in`, `Vec::with_capacity_in`, `Vec::from_array_in` etc previously took an `&'a Allocator`. Instead make them take an `&A where A: GetAllocator<'a>`.

This makes them more flexible - you can pass a reference to any type which implements `GetAllocator` e.g. `ParserImpl`, `TraverseCtx` etc.

```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let vec = ArenaVec::new_in(ctx.ast.allocator);
// After
let vec = ArenaVec::new_in(ctx);
```

The main motivation is to make them workable replacements for what are currently methods on `AstBuilder`, but will be removed in the new `AstBuilder`:

```rust
// Before (`ctx` here is `&mut TraverseCtx`)
let vec = ctx.ast.vec();
// After
let vec = ArenaVec::new_in(ctx);
// Instead of
let vec = ArenaVec::new_in(ctx.ast.allocator);
```

This is still longer code than the original, which is not ideal, but it's as short as can make them.

The main downside of this change is that when calling these methods with an `&Allocator`, you have to double reference:

```rust
let allocator: &'a Allocator = get_allocator_somehow();
// Passing `&&Allocator`
let vec = ArenaVec::new_in(&allocator);
```

This has no perf impact - these methods are inlined, and compiler collapses the double-reference like it wasn't there, but it's just ugly. Unfortunately, there's no way to implement `GetAllocator` so it'll accept both `&Allocator` and `&mut TraverseCtx`, and the latter is the much more common use case.
2026-06-24 12:13:15 +00:00
overlookmotel d1f18cda21 refactor(all): import arena Box and Vec as ArenaBox / ArenaVec (#23747)
Pure refactor.

Use `ArenaBox` and `ArenaVec` everywhere for the arena-allocated types from `oxc_allocator`, instead of plain `Box` and `Vec`.

This disambiguates when `Vec` is `oxc_allocator::Vec` and when it's `std::vec::Vec`, making code clearer to follow.

Previously it was easy to confuse the two, and such confusion led to UB in `Traverse` (fixed in #23745).

Ditto other arena types - `ArenaHashMap`, `ArenaHashSet`, `ArenaStringBuilder`.

The only exceptions are:

1. Inside `oxc_allocator` crate itself.
2. AST type definitions.
3. Generated code (migrated separately in #23749).
2026-06-24 01:21:41 +00:00
overlookmotel 8c6481bbf9 refactor(allocator): export ArenaBox and ArenaVec aliases (#23746)
Pure refactor.

It's a common pattern in repo to import `Vec` and `Box` etc from `oxc_allocator` and alias them to `ArenaBox` / `ArenaVec`.

```rust
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
```

Make this pattern less verbose by re-exporting `Box` and `Vec` as these aliases from `oxc_allocator`.

Ditto other arena types: `ArenaHashMap`, `ArenaHashSet`, `ArenaStringBuilder`.

Use those aliases in all other crates to shorten code.

```rust
use oxc_allocator::{ArenaBox, ArenaVec};
```
2026-06-24 01:21:40 +00:00
leaysgur f21ed2c112 fix(formatter_json): normalize CRLF for suppressed text (#23702)
The same as #23701, for JSON formatter.
2026-06-22 05:32:31 +00:00
leaysgur 44e4c6c905 refactor(formatter_core): unify arena_cow_str usage (#23700)
Closes #23528.
(I will confirm this (should be lived in `oxc_allocator` side?) later, then refactor all callsites)
2026-06-22 05:24:29 +00:00
leaysgur 8fa7394efa fix(formatter_json): handle wrapped error span (#23472)
- The `formatter_json` uses the `oxc_parser` to parse content
- Because a valid JSON `{}` is considered invalid JS, it is wrapped in `({})` before parsing
- As a result, the spans reported in parsing errors are offset by the width of that opening parenthesis 1
2026-06-16 05:29:06 +00:00
Boshen 777f02ae10 refactor(diagnostics)!: return a Diagnostics type instead of Vec<OxcDiagnostic> (#23217)
## Summary

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

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

## Breaking change

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

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-10 14:46:52 +00:00
leaysgur b0b5d399df fix(formatter_json): support JSON5/JSON6 value in json/jsonc/json5 (#23193)
I wouldn't be surprised by any value that can be formatted as JSON...
2026-06-10 05:41:02 +00:00
leaysgur 8cc82c4cdb feat(formatter_json): implement json-stringify variant (#23192)
Implement last missing JSON family variant: `json-stringify`.

Format output matches `prettier@3.8.4` for now, but we know `prettier@3.9.0` will be coming soon.

> https://github.com/prettier/prettier/blob/main/changelog_unreleased/json/18405.md

So I also implemented this and disabled under `COMPAT_PRE_18405` flag.
2026-06-10 05:41:02 +00:00
leaysgur 3da77e0080 feat(oxfmt): format parser:json5 files by oxc_formatter_json (#22990)
Now, `.json5` is also formatted by `oxc_formatter_json`.
2026-06-08 08:12:35 +00:00
leaysgur 01e087105f fix(formatter,formatter_json): handle PS/LS as line terminator (#22978)
I know this is also a rare, but it's better to be precise.

This also makes that the core no longer needs to be aware of line terminators, which can vary from language to language.
2026-06-05 07:17:39 +00:00
leaysgur 23902d952f fix(formatter_json): handle CR only line breaks (#22977)
I know it's rare, but it should be handled.

Also make it PS/LS aware, since we're using JS parser for JSON formatter.
2026-06-05 07:17:39 +00:00
leaysgur 5778a2dfb5 refactor(formatter_core): purify core and define rules (#22952)
The Printer never consumes these; they belong with each language formatter.

- Evict `Quote` (each formatter owns `QuoteStyle`)
  - `normalize_string` takes a quote byte
- Evict `Expand` / `BracketSpacing` / `TrailingCommas` into oxc_formatter and oxc_formatter_json
- JSON's `TrailingCommas` is now two-variant (`Always`/`Never`)
  - collapse Prettier's `all`/`es5` at each translation layer
- Rename `util/` -> `spec/` (pure shared helpers)
- Split `SourceText`: mechanical access stays in core, JS/TS lexis moves to `SourceTextExt`
- Drop the `oxc_syntax` dependency from core

All behaviros, Prettier conformance are unchanged.
2026-06-04 07:03:56 +00:00
leaysgur 27a6db879b feat(formatter_json): implement jsonc variant (#22912)
Implement `oxc_formatter_json`'s `Jsonc` variant.

The difference between `json` and `jsonc`:

- `jsonc` allows comment only input, `json` does not
- `jsonc` refers `trailingComma` option, `json` does not
2026-06-04 06:45:21 +00:00
leaysgur 136b72b09b fix(formatter_json): use line_suffix for line comment outside array (#22931)
Input:
```json
[1, 2, 3] // if this comment is too long and >= printWidth
```

Before:
```json
[
  1,
  2,
  3
] // if this comment is too long and >= printWidth
```

After:
```json
[1, 2, 3] // if this comment is too long and >= printWidth
```
2026-06-03 04:19:22 +00:00
leaysgur 44e40fa94c fix(formatter_json): expand line comment inside array (#22911)
Fix the bug:

```json
[1,2 // x
]
```

is printed as like:

```json
[1,2 // x]
```
2026-06-03 03:16:08 +00:00
leaysgur 100f2d5d55 refactor(formatter_json): rename argument (#22874)
Just renaming, align with `oxc_formatter`.
2026-06-01 08:25:00 +00:00
leaysgur 5733868456 refactor(formatter_core,formatter,formatter_json): share Expand and BracketSpacing options (#22821)
We chose `core` as the location for duplicate `struct`s, but the final options will continue to be built by each formatter.
2026-05-29 09:13:38 +00:00
leaysgur b006ddf132 refactor(formatter_core,formatter,formatter_json): lift up SourceText to core (#22817)
`oxc_formatter(_js)` and `oxc_formatter_json` are peers, but both need the same source-text scanning. `SourceText` is essentially a `&str` wrapper with a generic `u32` UTF-8 offset API (the oxc-wide convention) and language-neutral, so it belongs in core.

This lets JS and JSON share the code instead of `oxc_formatter_json` hand-rolling its own byte/string slicing.

NOTE:
One method (`get_lines_before`) still encodes JS-specific trivia rules. Moving it out would force its private primitives to become public, so it stays in core in a separate, clearly-marked language-specific impl block.
2026-05-29 09:13:37 +00:00
leaysgur 49db054b6a feat(formatter_json): Implement oxc_formatter_json (json variant only) (#22641)
Part of #19704

In Prettier, there are 4 variants for JSON formatting.

- json 👈🏻 this
- jsonc
- json5
- json-stringify

Although Prettier calls "JSON", it doesn't strictly adhere to the JSON specification; in practice, it behaves more like JS...

To begin with, it uses `babel.parseExpression()`, which makes the input parsing very lenient. The output also doesn't necessarily conform to the specification—for instance, it allows comments and sometimes omits double quotes on keys.

Because of this, we are re-implementing it using `oxc_parser`. Since `parse_expression()` cannot capture comments, I've achieved this by wrapping the input in `()`.

Also considered adding JSON support to `oxc_formatter`, but I have decided NOT to for now. We may reconsider this once we have bothered on various edge cases.
2026-05-29 00:49:50 +00:00