- dispatcher
- string_embedder
- tailwind_sorter
Group these 3 together as `SessionServices`, make them available to `FormatSession`, and pass them around.
Align `oxc_formatter` with `oxc_formatter_json`, both now expose `format(...) -> Result<Formatted<'a, _>, OxcDiagnostic>`.
- Add text-in `format` and `format_fragment`, also `format_program`, `parse_for_format` and the `FragmentContext` enum
- Parse options, JSX enabling, js-in-xxx context aware handlings are applied internally
- Make the `Formatter` struct and AST/IR primitives crate-internal
- Fold `get_parse_options` / `enable_jsx_source_type` and the `service` module into `parse_for_format`
Split `oxc_formatter` into:
- `oxc_formatter(_js)`: JS/TS specific formatter, comment handling, sortImports, jsdoc, etc
- `oxc_formatter_core`: Language agnostic IR, printer, etc
The naming convention is different from Biome or Ruff (they do not have `_core`, and `_formatter` is equivalent to the core), but this is because in Oxc, items without `_js` are the default.
As a basic task, I have added `Context` as a generic to all relevant parts to ensure support for multiple languages.
There might still be other components that could be refactored into the core, but I would like to finish them as I implement support for other languages.
Also moved some shared utilities to the core in advance.
This PR contains the following updates:
| Package | Update | Change |
|---|---|---|
| [rust](https://redirect.github.com/rust-lang/rust) | minor | `1.94.1` → `1.95.0` |
---
### Release Notes
<details>
<summary>rust-lang/rust (rust)</summary>
### [`v1.95.0`](https://redirect.github.com/rust-lang/rust/compare/1.94.1...1.95.0)
[Compare Source](https://redirect.github.com/rust-lang/rust/compare/1.94.1...1.95.0)
</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:eyJjcmVhdGVkSW5WZXIiOiI0My4xMjMuOCIsInVwZGF0ZWRJblZlciI6IjQzLjEyMy44IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Fixes#17830
Introduce new variant for `groups` like:
```json
// "newlinesBetween": true, // Default
"groups": [
["value-builtin", "value-external"],
{ "newlinesBetween": false }, // 👈🏻
"value-parent",
"value-sibling"
]
```
This allows you to specify line breaks globally between groups while reverting them at any position.
Conversely, you can also add line breaks at any position while specifying `newlinesBetween: false`.
Pass `--diff` to compare the formatted code to the input, which is very useful when you copy the Prettier output here to check if it is formatted as expected or for idempotency testing.
## Summary
Improves the `print_diff_in_terminal` function with better readability and a simpler API:
**API Changes:**
- `print_diff_in_terminal(expected, result)` - Simple API that takes two strings
- `print_text_diff(diff)` - For when you already have a `TextDiff` (e.g., to use `diff.ratio()`)
**Features:**
- Show 3 lines of context above and below each change
- Add line numbers with gutter separator (`│`)
- Add blank line between adjacent hunks for visual separation
- Add `...` separator when lines are skipped between hunks
- Use single-pass streaming algorithm with small fixed-size buffer
- Colors: red (`-`) for deletions, green (`+`) for insertions, dim for context
**Formatter Example:**
- Added `--diff` flag to show diff between original and formatted code
Example output:
```
1 │class A {
2 │ #x;
3 │}
- 4 │var _x = /* @__PURE__ */ new WeakMap();
- 5 │var _B_brand = /* @__PURE__ */ new WeakSet();
4 │class B {
- 7 │ constructor() {
- 8 │ babelHelpers.classPrivateMethodInitSpec(this, _B_brand);
- 9 │ babelHelpers.classPrivateFieldInitSpec(this, _x, void 0);
+ 5 │ #x;
+ 6 │ #y() {
+ 7 │ this.#x;
8 │ }
- 11 │}
- 12 │function _y() {
- 13 │ babelHelpers.classPrivateFieldGet2(_x, this);
9 │}
```
## Test plan
- [x] `cargo check -p oxc_tasks_common` passes
- [x] Clippy passes
- [x] Verified output with `just test-transform --filter "plugins-integration/class-features-node-12/input.js"`
🤖 Generated with [Claude Code](https://claude.ai/code)
#17076
### Authorship & Use of AI
All code changes in this pull request are completed by a human (myself).
ChatGPT is used **only** to assist with writing comments and this PR
description.
### Commit Structure
The changes are split into small, meaningful commits.
Each commit should be reviewable independently.
## Current State of the Implementation
For easier comparison, I forked
[actual](https://github.com/nilptr/actual) and made a few auxiliary
changes on a separate branch
[nilptr/feat/oxfmt-custom-groups](https://github.com/nilptr/actual/tree/nilptr/feat/oxfmt-custom-groups).
The auxiliary changes include:
- Upgraded `oxfmt` to address the incompatible binding API.
- Updated `eslint.config.mjs` to lint and fix .ts files.
- Updated `.oxfmtrc` to enable `experimentalSortImports`, keeping it
consistent with `eslint.config.mjs`.
### How to Compare?
1. Clone `git@github.com:nilptr/actual.git` and check out
`nilptr/feat/oxfmt-custom-groups` branch.
2. Run `yarn install`
3. Run `NAPI_RS_NATIVE_LIBRARY_PATH=/path/to/binding.node yarn
fmt:oxfmt`
The native binding can be obtained in one of the following ways:
- Download the
[oxfmt.darwin-arm64.node.zip](https://github.com/user-attachments/files/24402116/oxfmt.darwin-arm64.node.zip)
built by me (if you trust it, or scan it with
[VirusTotal](https://www.virustotal.com/gui/home/upload)).
- Build it by yourself: clone my repo, run `pnpm build`, and locate the
.node binding file under `apps/oxfmt/src-js`.
4. Inspect the results with `git diff` — the diff shows the differences
between perfectionist and oxfmt
### Observed Differences
There are 9 differences in total:
- **4 differences** are unrelevant to `experimentalSortImports`. (be the
way, they are introduced after I rebased main branch)
- **5 differences** are relevant to `experimentalSortImports`, but are
not caused by the group-matching logic, which is the focus of this PR.
- 4 of them are blank lines after side-effect imports being removed
- 1 of them is the wrong `ImportLineMetadata.source`
<img width="4064" height="2152" alt="image"
src="https://github.com/user-attachments/assets/8b4cb860-19d6-42b9-a0a4-6db0b3e40194"
/>
<img width="4064" height="2152" alt="image"
src="https://github.com/user-attachments/assets/e2616d7b-b4b2-4dbc-a0d3-ea15d1c0177e"
/>
------
I believe we can start review now.
While the review is ongoing, I will also try to see whether the observed
differences can be fixed in this PR.
---------
Co-authored-by: Yuji Sugiura <y.sugiura.0316@gmail.com>
Part of #14253
Now user can specify own `groups` like:
```js
groups: [
"type",
["external", "builtin"]
]
```
- [x] Parse config
- [x] Implementation
- [x] Test
- [x] Refactor for readability
- [x] Refactor for performance
- [x] ~~Report non-defaults?~~
Sorry for the large diffs. Yes, it's incredibly complicated. 😓
Current implementation was performimg sort based on hard-coded `enum`.
This PR changed it to use group definitions like this.
```js
[
'type-import',
['value-builtin', 'value-external'],
'type-internal',
'value-internal',
['type-parent', 'type-sibling', 'type-index'],
['value-parent', 'value-sibling', 'value-index'],
'ts-equals-import',
'unknown',
]
```
> https://perfectionist.dev/rules/sort-imports#groups
And this will be refactored in the subsequent 2 PRs.
Export `oxc_formatter::get_parse_options` to get this options everywhere.
```rs
ParseOpitons {
parse_regular_expression: false,
// Enable all syntax features
allow_v8_intrinsics: true,
allow_return_outside_function: true,
// `oxc_formatter` expects this to be false
preserve_parens: false,
}
```
As far as I can remember, `doc()` function was only added for the sort-imports example.
Now that we made `format()` as `pub`, there is no reason to use it.
Part of #14253
This is preparation for implementing sort-imports by reordering IR.
While performance is inferior, it is separated from the main formatting process and is completely opt-in.
This should also make it easier to find the cause if any issues arise.
Part of #5068, suggested by @ematipico and @MichaReiser, where @leaysgur
and I explored using biome formatter as the base and made a success. All
credits to the old Rome and the new Biome team.
This PR is in a boilerplate state where some of the functions are marked
as `todo!()`.
I also made the following changes:
* changed the base to be less generic, removed the `Language` and
`Context` generics
* expanded the formatter lifetime to include an `'ast` lifetime
* added access to the source text and comments
* added a mechanism for tracking and accessing parent ast nodes (done by
our codegen)
* removed most of the unit tests because they no longer compile
* removed the `oxc_prettier` crate