Commit Graph

28 Commits

Author SHA1 Message Date
leaysgur a26f856402 refactor(oxfmt): rename external_formatter to external_services (#25429)
`ExternalFormatter` is merely a transport that calls the JS side via napi; it is not referred to as a formatter.
2026-08-09 15:07:40 +00:00
leaysgur dcc6d38330 refactor(oxlint,oxfmt): Use shared/importJsConfig for js config (#22453)
Consolidate the following parts:

- Using native `import()`s
- Specifying `cacheKey`
- Extracting default exports
- Returning hints for TS incompatibility errors
2026-05-18 08:05:48 +00:00
leaysgur e3081e12a9 feat(oxfmt): Gate vite.config.ts recognition behind VP_VERSION env var (#21295)
Fixes #20416 for Oxfmt

- Gate `vite.config.ts` recognition behind `VP_VERSION` env var
  - With it, only vite.config.ts is recognized
  - Without it, excluded from config candidates (.oxfmtrc.json, oxfmt.config.ts are used)
- Refactor JS config loader into `loadJsConfig` / `loadVitePlusConfig` with shared `importJsConfig` helper
  - `loadVitePlusConfig` should be updated to use `resolveConfig` from `vite-plus` soon
- Split tests into `vite_config` (non VP mode) and `vite_plus` (VP mode)
2026-04-13 01:10:46 +00:00
leaysgur 7ac69fa3f7 refactor(oxfmt): Arrange cli/js_config dir (#21078)
- Add `/js_config/` dir
- Use inline tests
- Always return `Error` instance
2026-04-06 02:27:59 +00:00
leaysgur 89ce30b641 fix(oxlint,oxfmt): Redirect JS stdout to stderr for LSP (#20321)
Fixes #20320

(It's fine to release this as scheduled one)
2026-03-16 10:15:23 +00:00
leaysgur 4ce33e8463 chore(oxfmt): Add comment for setBlocking(true) (#20323)
Follow up #20289 , just adding comments.
2026-03-13 04:45:23 +00:00
camc314 96f761f4bf fix(oxfmt): ensure stdin blocking mode is set for non-TTY environments (#20289) 2026-03-12 13:55:57 +00:00
leaysgur 172fc072f3 feat(oxfmt): .js/.ts config file support (#20135)
Fixes #19711, also resolves some of the demands for nested configs.
2026-03-11 04:34:48 +00:00
leaysgur 5e98677d14 chore(oxfmt): Align the definition order of external APIs (#19896)
Cosmetic changes; Reorder napi callbacks:

- init+dispose
- format file (for non-js)
- embedded (for js)
- sort tailwind (for js)
2026-03-02 01:40:51 +00:00
leaysgur ca68ea6192 feat(oxfmt): Support gql-in-js substitution (#19670)
Part of #15180

gql-in-js is the simplest among other xxx-in-js cases.

It works by:

- splitting the template with `${}`, converting each part into an IR using:
  - `prettier({ parser:"graphql" })` and `oxc_formatter` respectively
- and then interleaving them

However, for this to work, two things are essential:

- Obtaining a formatted `Doc` from Prettier, rather than just a formatted `string`.
- Converting the `Doc` JSON into `oxc_formatter`'s IR.

The diffs primarily arise from the implementation of these two.

### TODOs

- [x] Implement by Claude
- [x] Self review and fix up
- [x] Check bench, ecosystem
2026-02-26 15:24:01 +00:00
Boshen ef5bfab1ef fix(oxfmt): workaround Node.js ThreadsafeFunction cleanup race condition (#18980)
Node.js < 25.4.0 has a race condition with ThreadsafeFunction cleanup
during V8 shutdown that causes crashes on large codebases. This adds a
50ms delay before `process.exit()` to allow pending NAPI operations to
complete.

The crash manifests as `Check failed: node->IsInUse()` or `Check failed:
object_ != kGlobalHandleZapValue` in `napi_reference_unref`, occurring
at ~39% rate on large codebases (12,000+ files) without the workaround.

The issue is fixed in Node.js 25.4.0+, so the workaround is only applied
for earlier versions.

See: https://github.com/nodejs/node/issues/55706

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 20:40:43 +08:00
leaysgur f38986e8a2 refactor(oxfmt): Ensure to destroy worker pool before exit (#18689)
In most cases, it should be fine, but this approach is cleaner.
2026-01-29 08:46:03 +00:00
Luca Fischer ee30de98cf feat(oxfmt): add config migration from biome (#18638)
Added Biome-to-oxfmt configuration migration, based on the Prettier
implementation.

I wasn't sure if I should exclude settings that match oxfmt's defaults
from the generated config, but I'm happy to do so if you’d like
2026-01-28 21:09:13 +09:00
leaysgur 1c1da220ee refactor(oxfmt): Minimize setBlocking(true) workaround scope (#17974)
Follow up #17950
2026-01-14 05:10:28 +00:00
leaysgur 9d96cc6f7a fix(oxfmt): Use std(out/err)._handle.setBlocking(true) to handle WouldBlock error in Rust (#17950)
Fixes #17939, closes #17943 and #17946.
2026-01-13 05:40:13 +00:00
Dunqing 26ed46bc02 feat(formatter): add TailwindCSS support for JS/TS files (#16990)
Based on https://github.com/oxc-project/oxc/pull/16826

## Summary

Add experimental Tailwind CSS class sorting support to Oxfmt via integration with `prettier-plugin-tailwindcss`.

This implementation uses a patch-based approach to export necessary functions from `prettier-plugin-tailwindcss`. We've requested better integration support from the Tailwind team at https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/430, and can improve the integration when that's available.

## Features

- ✅ Sorts Tailwind CSS classes in `className` and `class` attributes
- ✅ Sorts classes in custom JSX attributes (configurable)
- ✅ Sorts classes in function calls like `clsx()`, `cn()`, `tw()` (configurable)
- ✅ Supports template literals with expressions
- ✅ Supports Tailwind v3 config files and v4 stylesheets
- ✅ Preserves whitespace and duplicates (configurable)
- ✅ **Fixes [prettier-plugin-tailwindcss#426](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/426)** - Correctly handles strings in nested non-Tailwind call expressions
- ⏳ Non-JS file support (HTML, Vue, Svelte, etc.) - Coming in follow-up PR
- ⏳ Tailwind CSS config migration in `oxfmt --migrate=prettier` - Coming in follow-up PR

## Example Usage

### Basic Usage

Enable Tailwind CSS sorting with default options by setting `experimentalTailwindcss` to an empty object in your `.oxfmtrc.json`:

```json
{
  "experimentalTailwindcss": {}
}
```

### With Configuration Options

```json
{
  "experimentalTailwindcss": {
    "config": "./tailwind.config.js",
    "functions": ["clsx", "cn", "cva", "tw"],
    "attributes": ["myCustomClass"],
    "preserveWhitespace": false,
    "preserveDuplicates": false
  }
}
```

### Configuration Options

All options from `prettier-plugin-tailwindcss` are supported (with `tailwind` prefix removed):

| Option | Type | Description | Default |
|--------|------|-------------|---------|
| `config` | `string` | Path to your Tailwind CSS v3 configuration file. Paths are resolved relative to the Oxfmt configuration file. | `"./tailwind.config.js"` |
| `stylesheet` | `string` | Path to your Tailwind CSS v4 stylesheet. Paths are resolved relative to the Oxfmt configuration file. | `undefined` |
| `functions` | `string[]` | List of custom function names that contain Tailwind CSS classes (e.g., `["clsx", "cn", "cva", "tw"]`). | `[]` |
| `attributes` | `string[]` | List of custom JSX attributes that should be sorted (e.g., `["myCustomClass"]`). Note: `class` and `className` are always sorted by default. | `[]` |
| `preserveWhitespace` | `boolean` | Preserve whitespace around classes. | `false` |
| `preserveDuplicates` | `boolean` | Preserve duplicate classes. | `false` |

## Migration from prettier-plugin-tailwindcss

If you're currently using `prettier-plugin-tailwindcss`, you can migrate to Oxfmt's built-in support:

### Manual Migration (Current)

#### Before (Prettier)

```json
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindConfig": "./tailwind.config.js",
  "tailwindFunctions": ["clsx", "cn"]
}
```

#### After (Oxfmt)

```json
{
  "experimentalTailwindcss": {
    "config": "./tailwind.config.js",
    "functions": ["clsx", "cn"]
  }
}
```

**Key differences:**
- Remove `tailwind` prefix from all option names (e.g., `tailwindConfig` → `config`)
- No need to install `prettier-plugin-tailwindcss` separately, it is bundled within Oxfmt
### Automated Migration (Follow-up PR)

Tailwind CSS configuration migration will be added to the existing `oxfmt --migrate=prettier` command in a follow-up PR. This will automatically convert your `prettier-plugin-tailwindcss` settings from `.prettierrc` to `.oxfmtrc.json`.

## Implementation Details

### Technical Approach

- Uses `pnpm patch` to export necessary functions from `prettier-plugin-tailwindcss`
- Implements Rust-side detection for Tailwind contexts (JSX attributes, function calls, template literals)
- Passes class strings to the JS plugin for sorting via external callback
- Optimized context tracking to avoid repeated AST traversal

### Improvements Over prettier-plugin-tailwindcss

- **Context-based quasi position tracking** ([tailwindcss.rs:273-276](https://github.com/oxc-project/oxc/blob/main/crates/oxc_formatter/src/utils/tailwindcss.rs#L273-L276)) - Stores template literal position in context stack instead of traversing AST, improving performance
- **Fixes [issue #426](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/426)** - Correctly handles strings in nested non-Tailwind call expressions (e.g., `value.includes("\n")` no longer gets corrupted)
- **Comprehensive edge case testing** - Added 58 tests covering edge cases, nested scenarios, error handling, and performance

## Changes

- Changed `experimentalTailwindcss` option from `boolean` to `TailwindcssOptions` object
- Added all configuration options from `prettier-plugin-tailwindcss` (without `tailwind` prefix)
- Exported necessary functions from `prettier-plugin-tailwindcss` via patch
- Added comprehensive test suite with 58 tests
- Improved documentation for all configuration options

## Test Plan

- [x] `pnpm test` passes (58 tests)
- [x] `cargo clippy` passes
- [x] `just fmt` passes
- [x] Basic Tailwind class sorting works with `class` and `className` attributes
- [x] `attributes` option sorts custom JSX attributes
- [x] `functions` option sorts string arguments in function calls (e.g., `clsx()`, `cn()`)
- [x] Template literals with expressions handle whitespace correctly
- [x] Edge cases: empty strings, Unicode, emoji, special characters
- [x] Nested scenarios: deeply nested template literals, function calls
- [x] Strings in nested non-Tailwind calls are preserved (fixes [#426](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/426))
- [x] Test in https://github.com/tailwindlabs/headlessui and https://github.com/dubinc/dub

## Follow-up Work

- **Non-JS file support** - HTML, Vue, Svelte, etc. by enabling original plugin when `experimentalTailwindcss` is set
- **Migration support** - Add Tailwind CSS config migration to existing `oxfmt --migrate=prettier` command
- **Better integration** - Improve when https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/430 is resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-01-05 10:07:07 +00:00
leaysgur b4e8605a4d refactor(oxfmt): Clean up src-js files and directories (#17040)
```
.
├── bindings.d.ts
├── bindings.js
├── cli
│   ├── migration
│   │   ├── init.ts
│   │   ├── migrate-prettier.ts
│   │   └── shared.ts
│   └── worker-proxy.ts
├── cli-worker.ts    # CLI worker endpoint
├── cli.ts           # CLI endpoint
├── index.ts         # API endpoint
└── libs
    └── prettier.ts  # Core logic shared by index, cli-worker, cli/worker-proxy
```

By doing this, Node.js API `format()` does not use `worker_threads` anymore.
2025-12-18 10:44:28 +00:00
leaysgur 7368d6699a refactor(oxfmt): Resolve options on each path, preparing for .editorconfig support (#16991)
Refactoring for #15850

### Before this PR
- The config file and its option values were resolved only once at the beginning
- They were kept for `OxcFormatter` and also being cached via `setupConfig()` for `ExternalFormatter`
- When calling `SourceFormatter.format()`, it always referenced those values

### After this PR
- The config file and its option values are resolved only once at the beginning
- Those values are still kept as before
  - But, this is for a fast path when there are no per-path overrides in `.editorconfig`
- When calling `SourceFormatter.format()`
  - If `.editorconfig` exists and has per-path overrides, they are re-resolved per path
  - Otherwise, it continues to reference the initially cached values

We only support the root `.editorconfig`, but since glob-based per-path overrides can be written there, we have to resolve options on each path. Some performance impact is unavoidable.

However, in scenarios where `.editorconfig` doesn't exist, or exists but has no per-path overrides, the cost is limited to just cloning the cached values per path.
2025-12-18 10:44:28 +00:00
leaysgur 5e3ceb838e feat(oxfmt): Support oxfmt --stdin-filepath (#16868)
Fixes #14720
2025-12-15 09:01:26 +00:00
leaysgur d4c0bb7688 feat(oxfmt): Support oxfmt --migrate prettier (JS side) (#16773)
Fixes #15849 , closes #16736

Tests and cosmetic changes are planned in next PR.
2025-12-15 02:49:26 +00:00
leaysgur bc2e0f8a12 fix(oxfmt): Report exitCode correctly (#16770)
Previously,

- `oxfmt(napi) not-exists.js` returns `exitCode: 1`
- `oxfmt(rust) not-exists.js` returns `exitCode: 2` = Expected

To fix this, I updated napi callback return value from `bool`.

Additionally, I realized that by returning the processing `Mode` simultaneously, Rust can handle the CLI arguments in better way. Like `--init --help`, which now shows help.

And finally changed napi binding name from `format()` to `runCli()` for the future Node API.
2025-12-12 05:56:10 +00:00
leaysgur bbec437149 refactor(oxfmt): Rewrite --init mode in JS (#16769)
After giving it some thought, I decided to lean these features toward JS after all.

The reasons are:

- I want Rust side to focus on complex formatting tasks, while moving infrequently used commands elsewhere
- Code defining `napi` callbacks tends to become verbose

While `--init` itself worked on the Rust side, it will share some lines with upcoming `--migrate`, so I'm extracting this beforehand too.

Also, We were generating `.jsonc`, but I've switched to creating `.json` for now.

This is because running `oxfmt` immediately after generating `.jsonc` causes trailing comma diffs. 😨
I was hesitant to introduce a jsonc writer, so `.json` will enough for now.
2025-12-12 14:44:27 +09:00
leaysgur a83a2ec613 feat(oxfmt): Expose setupConfig(configJSON: string) napi callback (#16579)
First step for #16530

- Added new napi callback `setupConfig()`
- And call it after resolving config in Rust side

At this time, there is no impact on end users. Passing `{}` is equivalent to passing nothing.
2025-12-08 08:29:23 +00:00
leaysgur 0867d2fda8 feat(oxfmt): Set up JS formatFile() function for Rust via napi (#16415)
- Pass `formatFile()` callback to main `format()` entry
- Refactor `ExternalFormatter` initialization
- Expose `external_formatter.format_file()`

Doesn't affect existing use cases; it just slightly increases the size of the Rust.
2025-12-04 10:03:50 +00:00
leaysgur b6feb6605e feat(oxfmt): Rename embedded.ts with preparing formatFile() function (#16414)
- Rename `embedded.ts` > `prettier-proxy.ts`
- Add `formatFile(fileName, code)` fn to use later

Doesn't affect existing use cases; it just slightly increases the size of the JS.
2025-12-04 10:03:49 +00:00
Boshen 523a30a016 style(all): apply oxfmt default options (#16091)
Embrace the defaults!

---------

Co-authored-by: Yuji Sugiura <y.sugiura.0316@gmail.com>
2025-11-25 15:56:24 +09:00
leaysgur 96eddce53e refactor(oxfmt): Arrange and fix main files (#15758)
- Rename `run.rs` to `main_napi.rs`
  - `main.rs` is the entrypoint for Rust CLI, and `main_napi.rs` is for JS CLI
- Fix up `cli.ts` to use `.exitCode`, rather than `.exit()`
2025-11-17 04:06:51 +00:00
Boshen 898d6fec12 feat(oxfmt): add embedded language formatting with Prettier integration (#14820)
## Summary

Adds support for formatting embedded languages (CSS, GraphQL, HTML, Markdown) in JavaScript/TypeScript template literals using Prettier via NAPI bindings.

## Motivation

JavaScript/TypeScript developers often embed CSS, GraphQL queries, HTML, and Markdown in their code using tagged template literals. Without proper formatting support, these embedded languages remain unformatted, leading to inconsistent code style and reduced readability.

This PR enables oxfmt to format these embedded languages using Prettier, providing a seamless formatting experience similar to Prettier's own embedded language support.

## Changes

### Core Functionality
- **Embedded language detection**: Recognizes `css`, `styled`, `gql`, `graphql`, `html`, `md`, and `markdown` template tags
- **Prettier integration**: Uses `@prettier/sync` for synchronous formatting of embedded code
- **NAPI bridge**: Rust-to-JavaScript interop using NAPI ThreadsafeFunction
- **Graceful fallback**: Returns original code if formatting fails or tag is unsupported

### Implementation Details

**Rust side** (`crates/oxc_formatter/`, `apps/oxfmt/src/prettier_plugins/`):
- New `EmbeddedFormatter` trait with callback-based API
- NAPI bindings using `FnArgs` pattern for clean multi-argument calls
- Template formatter enhancement to detect and format tagged templates
- Proper indentation handling: splits Prettier output into lines, uses `hard_line_break()` separators, wraps in `indent()` for context-aware indentation
- Arena allocator pattern to avoid borrow checker conflicts

**JavaScript side** (`apps/oxfmt/src-js/`):
- TypeScript bindings with clean signatures: `formatEmbeddedCode(tagName: string, code: string)`
- Prettier configuration: 80 char width, 2 space indent, semicolons, double quotes
- Tag-to-parser mapping for supported languages
- Error handling with console logging and fallback

**Testing**:
- E2E test suite using Vitest (8 test cases)
- Test fixtures for each supported language
- Snapshot testing with timing normalization for stability
- All tests passing consistently

## Examples

**Before** (unformatted):
```js
const styles = css`.button { color: red; background: blue; }`;
```

**After** (formatted with proper indentation):
```js
const styles = css`
  .button {
    color: red;
    background: blue;
  }
`;
```

**Supported tags:**
```js
// CSS
const styles = css`...`;
const component = styled`...`;

// GraphQL
const query = gql`...`;
const schema = graphql`...`;

// HTML
const template = html`...`;

// Markdown
const docs = md`...`;
const readme = markdown`...`;
```

## Testing

```bash
# Run oxfmt tests
just oxfmt

# Watch mode for development
just watch-oxfmt
```

All 8 E2E tests pass:
- ✓ embedded_css
- ✓ embedded_graphql
- ✓ embedded_html
- ✓ embedded_markdown
- ✓ mixed_embedded
- ✓ no_embedded
- ✓ unsupported_tag
- ✓ check_mode

## Related

Related to #13427

## Future Work

Potential enhancements:
- Support for additional embedded languages (SQL, YAML, etc.)
- Custom Prettier configuration via oxfmt config
- Performance optimization for large embedded blocks
- Integration with language servers for inline diagnostics
2025-11-03 02:05:35 +00:00