Commit Graph

58 Commits

Author SHA1 Message Date
leaysgur 5ff57f83c0 fix(formatter/sort-imports): handle custom side effect groups (#26217)
Fixes #26165

- `side_effect` is predefined selector, it cannot be used as custom group name
- respect custom group which uses `side_effect` as side-effect-import correctly
- fix metadata detection bug
2026-09-01 03:56:36 +00:00
leaysgur c07fe7c217 feat(oxfmt): support experimentalOperatorPosition (#25643)
Fixes #16366

---

Option name requires `experimental` prefix for now.

Values are:

- `"start"`
- `"end"`(default)

This is also available on playground.
2026-08-14 08:12:16 +00:00
leaysgur fed6681eda docs(oxfmt): skip expanding overrides options (#25572)
Follow up on #25544.

Oxlint also omits `overrides[].rules`.
2026-08-13 02:35:53 +00:00
leaysgur 00f490d7f7 refactor(oxfmt,formatter): split sortImports validation and use type enum (#25432)
Reduce runtime validation.

Also, by using type `enum`, IDE and TS types are also covered.
2026-08-09 15:07:41 +00:00
leaysgur e6e584b9be fix(oxfmt): type jsdoc. enum options (#25008)
Fixes #24999
2026-07-28 01:22:23 +00:00
leaysgur 415fe1e7bb fix(oxfmt): error on ignorePatterns that cannot match files outside the config directory (#24286)
Patterns in `ignorePatterns` use gitignore-style matching rooted at the config file's directory, so a `..` component can never match anything.

Reject such patterns at config load time instead of silently accepting them, and document the scoping.

Refs: #24276
2026-07-08 05:31:23 +00:00
AliceLanniste 9a2788b3ae feat(linter/unicorn): implement prefer-export-from rule (#22935)
## Summary
- Adds Unicorn `prefer-export-from` to report import-then-re-export
patterns and suggest direct `export ... from` declarations.
- Ports upstream coverage for default, named, namespace, TypeScript,
import-attributes, and `checkUsedVariables` cases.
- Exposes the rule option in the oxlint JSON schema and generated
TypeScript config types.

Related #22686

AI-assisted: this PR was prepared with AI assistance; the contributor
remains responsible for reviewing and validating the changes.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
2026-06-11 11:38:35 +01:00
leaysgur f88961ae29 docs(oxfmt): annotate each config option with supported languages (#22953)
Currently, our config file is Prettier-compatible, which is not easy to tell which options apply to which languages.

(I've felt this way for a while, so) I've added some notes to clarify.
2026-06-04 08:28:02 +00:00
leaysgur 43b997847b fix(formatter/sort_imports): Treat subpath imports as internal (#22440)
Closes #22371, follows https://github.com/azat-io/eslint-plugin-perfectionist/pull/732

---

As a baseline, while the result effectively follows the behavior of a perfectionist, there is no fundamental reason why it must be done that way.

Node.js subpaths are more flexible and aren't necessarily "internal", you can also alias external modules.

However, I do think it's consistent in the sense that it treats them the same way as other conventions like `@`.
2026-05-15 00:45:24 +00:00
leaysgur 6e8e8185c0 feat(oxfmt): Experimental .svelte support (#21700)
Part of #19715

Note that:

> Users are required to perform 2 steps below
> 1️⃣ Install svelte/compiler@^5.0.0 and/or @astrojs/compiler@^2.9.1 manually
> 2️⃣ Enable option in config
2026-05-11 01:24:37 +00:00
leaysgur 330fe31f82 refactor(config): Update doc comment for GlobSet (#22197)
Follow up on #22147.
2026-05-07 02:13:27 +00:00
camc314 fb3067ca2b refactor(oxfmt): use shared GlobSet for overrides (#22147)
uses oxlint's `GlobSet` for overrides over `Vec<String>`.

This helpe move out some shared normalization code from oxlint and oxfmt to the shared config crate.

Hopefully this change is ok for you @leaysgur  - I was originally hoping to share somethign else, but it seems trickier than I expected, so I just moved this out instead, which feels to me a good change
2026-05-06 16:14:04 +00:00
leaysgur 50c389b64f fix(oxfmt): Support .editorconfig quote_type (#20989)
Fixes #20563

---

- [x] Merge https://github.com/oxc-project/editorconfig-parser/pull/42
- [x] Release https://github.com/oxc-project/editorconfig-parser/pull/43
- [x] Update crates version
- [x] Implement
- [x] Test
- [x] Update schema, snapshots
2026-04-07 02:03:35 +00:00
leaysgur 4216380bc0 fix(oxfmt): Support .editorconfig tab_width fallback (#20988)
https://github.com/prettier/prettier/blob/90983f40dce5e20beea4e5618b5e0426a6a7f4f0/src/config/editorconfig/editorconfig-to-prettier.js#L25-L30
2026-04-03 02:28:39 +00:00
leaysgur 6ef440af18 feat(oxfmt): Support bool for object style options (#20853)
Fixes #20793
2026-03-30 02:05:35 +00:00
leaysgur 416865ac32 feat(formatter,oxfmt): Add doc comments for JsdocConfig (#20644)
Follow up #19828
2026-03-23 09:00:46 +00:00
Dunqing 4fec907ad5 feat(formatter): add JSDoc comment formatting support (#19828)
## Summary

Implements native JSDoc comment formatting in oxfmt, porting behavior from [prettier-plugin-jsdoc](https://github.com/hosseinmd/prettier-plugin-jsdoc) (v1.8.0). Tracked in #19702.

### Conformance

**145/145 (100%)** compatibility with prettier-plugin-jsdoc test suite.

- 1 fixture intentionally skipped (`descriptions/032-jsx-tsx-css`) — requires embedded CSS/HTML formatter callbacks not available in the standalone conformance runner
- Run with: `cargo run -p oxc_prettier_conformance -- --jsdoc`

Prettier conformance (non-JSDoc):
- **JS**: 746/753 (99.07%)
- **TS**: 591/601 (98.34%)

### Real-World Repository Testing

Validated against 5 real-world repositories (7,542 JSDoc tags total) using Prettier 3.8.1 + prettier-plugin-jsdoc 1.8.0 as baseline:

| Repository | JSDoc Tags | Files with Diffs |
|------------|-----------|-----------------|
| evolu      | 134       | 13              |
| wxt        | 1,183     | 1               |
| typedoc    | 792       | 11              |
| Chart.js   | 1,276     | 4               |
| svelte     | 4,157     | 5               |
| **Total**  | **7,542** | **34**          |

All 34 remaining diffs are **intentional design differences**, not bugs:
- `{@link}` kept atomic (upstream breaks across lines) — ~11 files
- `{@includeCode}` placed on own line (upstream splits tag syntax) — 3 files
- Sub-list continuation indent normalized to 4-space — 5 files
- `?Type` expanded to `Type | null` — 1 file
- Minor line-break position differences — 5 files
- Double-space normalized to single space — 1 file

10 upstream bugs documented in `tasks/prettier_conformance/jsdoc/upstream-jsdoc-bugs.md` — oxfmt intentionally produces correct output where the upstream plugin has known issues.

### Performance

Measured with `hyperfine` using `oxfmt --check` on real-world repos (16 threads):

| Repository | Without JSDoc | With JSDoc | Overhead    |
|------------|--------------|------------|-------------|
| evolu      | 434ms        | 427ms      | ~0% (noise) |
| wxt        | 410ms        | 410ms      | ~0% (noise) |
| typedoc    | 532ms        | 567ms      | ~7%         |
| Chart.js   | 497ms        | 479ms      | ~0% (noise) |
| svelte     | 510ms        | 549ms      | ~8%         |

typedoc and svelte (highest JSDoc density) show measurable ~7-8% wall-clock overhead. The rest are within noise. CPU time overhead is ~1-2% as JSDoc work parallelizes across threads.

### Architecture

The formatting pipeline processes each JSDoc comment through these stages:

1. **Parse** — Extract JSDoc structure via `oxc_jsdoc` (description, tags with type/name/comment)
2. **Normalize** — Canonicalize tag aliases (`@return` → `@returns`), normalize types (`Array.<T>` → `T[]`, `?Type` → `Type | null`), normalize markdown emphasis (`__bold__` → `**bold**`, `*italic*` → `_italic_`)
3. **Sort & Group** — Sort tags by upstream priority weights within `@typedef`/`@callback` groups; reorder `@param` tags to match function signature
4. **Format** — Route each tag to its specific formatter (type+name+comment, type+comment, generic, example); format descriptions via markdown AST with word wrapping; format embedded code blocks
5. **Serialize** — Assemble final comment with `/** ... */` wrapper, `* ` prefixes, and single-line optimization

**Key modules:**
- `serialize.rs` — Main pipeline orchestrator (`JsdocFormatter` struct)
- `tag_formatters.rs` — Tag-specific formatters (type+name+comment, type+comment, generic, example)
- `normalize.rs` — Type/emphasis/capitalization normalization
- `mdast_serialize.rs` — Markdown AST → formatted text serialization with `tag_string_length` offset
- `wrap.rs` — Word wrapping with `first_line_offset`, atomic JSDoc link tokens, table formatting
- `embedded.rs` — Embedded JS/TS/JSX/TSX code formatting in @example blocks
- `imports.rs` — `@import` tag parsing, merging, and sorting
- `param_order.rs` — `@param` tag reordering to match function signature
- `line_buffer.rs` — Single-allocation line accumulator

### Options Support

All 11 options from prettier-plugin-jsdoc are implemented:

| Option | Default | Status |
|--------|---------|--------|
| `capitalizeDescriptions` | `true` | ✅ |
| `commentLineStrategy` | `"singleLine"` | ✅ |
| `separateTagGroups` | `false` | ✅ |
| `separateReturnsFromParam` | `false` | ✅ |
| `bracketSpacing` | `false` | ✅ |
| `descriptionWithDot` | `false` | ✅ |
| `addDefaultToDescription` | `true` | ✅ |
| `preferCodeFences` | `false` | ✅ |
| `lineWrappingStyle` | `"greedy"` | ✅ (greedy + balance) |
| `descriptionTag` | `false` | ✅ |
| `keepUnparsableExampleIndent` | `false` | ✅ |

**Not yet ported** (no conformance fixtures): `jsdocVerticalAlignment`, `tsdoc` mode — can be added when needed.

### Features

- **Description formatting**: Capitalize first letter, normalize markdown emphasis, wrap text to print width
- **Markdown AST processing**: Parse descriptions via mdast for heading normalization, list formatting, reference links, code blocks, blockquotes, tables
- **Tag description wrapping**: Matches upstream's `tagStringLength` architecture — full description passes through markdown AST with first-line offset
- **Tag normalization**: 17 tag synonyms, sort by upstream priority weights within groups
- **Type formatting**: Normalize JSDoc types, format via the formatter's TS parser (simulating upstream's `formatType()`), handle rest params, bracket spacing
- **@example formatting**: Format embedded JS/TS/JSX/TSX code; fenced code blocks with language detection
- **@import handling**: Parse, merge by module path, sort (third-party before relative), format consolidated imports
- **@param reordering**: Reorder tags to match function signature parameter order
- **Default value handling**: Extract `[name=value]` defaults with "Default is \`value\`" suffix

### Upstream Fidelity

Verified against [prettier-plugin-jsdoc](https://github.com/hosseinmd/prettier-plugin-jsdoc) v1.8.0 source:

- **Identical**: Tag synonyms (17), sort priorities (46 tags), grouping logic (`TAGS_GROUP_HEAD`, `TAGS_GROUP_CONDITION`), type normalization pipeline, rest param handling, JSDoc link protection, emphasis normalization, capitalization rules
- **Architecture aligned**: Tag description wrapping uses `tag_string_length` / `first_line_offset` (equivalent to upstream's `!...?` prefix trick), balance mode effective width calculation, greedy fallback
- **10 upstream bugs documented** — oxfmt intentionally produces correct output where the plugin has known issues (HTML comment wrapping, `{@link}` off-by-one, `{@includeCode}` splitting, `@type` capitalization inconsistency, etc.)

### Optimization Techniques

- Single `LineBuffer` allocation per comment (one contiguous `String`, not `Vec<String>`)
- `Cow<'_, str>` throughout normalization — borrows when unchanged, allocates only when modified
- Fast-path skip of mdast parsing for plain-text descriptions (`needs_mdast_parsing()` heuristic)
- Fast-path skip of TS formatter for simple types (`needs_formatter_pass()` check)
- `SmallVec<[usize; 4]>` for import tag indices (stack allocation for common case)

## Test plan

- [x] `cargo test -p oxc_formatter` — 335 tests pass (76 unit + 259 fixture)
- [x] `cargo run -p oxc_prettier_conformance -- --jsdoc` — 145/145 (100%)
- [x] Prettier conformance: JS 746/753 (99.07%), TS 591/601 (98.34%)
- [x] Real-world repos: 34 diffs across 5 repos (7,542 JSDoc tags) — all design differences
- [x] Performance: 0-8% overhead across 5 real-world repos (typedoc ~7%, svelte ~8%, rest within noise)
- [x] `cargo clippy -p oxc_formatter --all-targets` — clean
- [x] `cargo fmt -- --check` — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-23 07:44:47 +00:00
leaysgur bc2021765c fix(oxlint,oxfmt): Omit useless | null for Option<T> field from schema (#20273)
Currently, fields defined as `key: Option<T>` are represented in TS as `key?: T | null`.

However, passing `null` actually just treats it as `None`, which only creates noise.
2026-03-12 09:48:46 +00:00
leaysgur f398bc9318 refactor(formatter,oxfmt): Use Sort prefix for tailwind options (#19568)
Internal changes to ensure consistency.
2026-02-20 08:56:22 +00:00
leaysgur 984dc07f4f feat(oxfmt): Strip "experimental"SortXxx prefix (#19567)
- `experimentalSortImports` 👉🏻 `sortImports`
- `experimentalSortPackageJson` 👉🏻 `sortPackageJson`
- `experimentalTailwindcss` 👉🏻 `sortTailwindcss`

To avoid breaking changes, the old name is retained as an alias.
2026-02-20 08:56:22 +00:00
leaysgur 6c61b7092a fix(oxfmt): Fix outdated sortImports.groups doc comments (#19513) 2026-02-18 04:28:38 +00:00
leaysgur 9c34f7226b feat(formatter/sort_imports)!: Report invalid group name with renaming side-effect > side_effect (#19416)
- Breaking: Rename ambiguous modifier/selector names
  - `side-effect` → `side_effect`
  - `side-effect-style` → `side_effect_style`
  - Now `-` is used only for joiner (`modifier-modifier-selector`)
    - before: `value-side-effect-style` 🆚 after: `value-side_effect_style`
- Breaking: Add config-time validation for invalid group names
  - previously silently ignored...
  - or wrongly applied (and user didn't notice)
2026-02-16 05:05:08 +00:00
leaysgur 4baebefa81 feat(formatter/sort_imports): Support { newlinesBetween: bool } inside groups (#19358)
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`.
2026-02-14 14:12:48 +00:00
leaysgur d1c2fb628a feat(formatter/sort_imports): Support customGroups attributes(selector and modifiers) (#19356)
Fixes #19264

The original issue only mentioned `modifiers`, but I've implemented `selector` as well.
While the original implementation also supports more attributes, I believe this is sufficient for now.
2026-02-13 05:15:00 +00:00
leaysgur 856a01ff40 feat(formatter/sort_imports)!: Replace prefix match with glob pattern in customGroups.elementNamePattern (#19066)
Fixes #18726

Support glob matching instead of prefix matching.

The original used regex, but there was no necessity for it, and this approach offers better performance.

In any case, it will be a braking change.

```js
// before: Matches react, reactify, react-router, etc
"elementNamePattern": ["react"]
// after: Matches react only

// before: Matches loot-core, loot-core/foo, etc
"elementNamePattern": ["loot-core"]
// after: Matches only loot-core, should update to
"elementNamePattern": ["loot-core/*"]
```
2026-02-10 02:14:10 +00:00
leaysgur 6ee2d59cd6 feat(oxfmt): Use oxc_formatter in js-in-xxx part (#18373)
Fixes #16608 partially.

This PR overrides Prettier's default JS/TS formatting process with a custom transform-plugin.
As a result, `oxc_formatter` will be used in the `script` section of Vue, code block of Markdown, etc. And also for example, sort-imports and other features are now available.

After verification, we found that if we override `babel`, it is also applied to some expression parts of Vue.

This was unexpected and more time is needed to do things properly.
Therefore, as a first support, only enables for `markdown` and `mdx`.

Changes:

- Changed `tinypool` mode from `worker_threads` to `child_process`
  - Calling NAPI (API) again via `worker_threads` from NAPI (CLI) causes a hang...
- Implemented `prettier-plugin-oxfmt` using the existing `format()` API
  - What's truly desired is something equivalent to `textToDoc()`, but this is the only option currently we have
- Handling of options corresponding to Prettier's `embed()` processing flow
  - Plugins only support primitive types
  - NAPI also cannot handle options that cannot be serialized to JSON

Following PRs will cover:

- Improvements to option handling
- Tests
2026-02-06 05:50:04 +00:00
leaysgur 4e9a5e5410 refactor(oxfmt): Update TS type and tailwind option docs (#18284)
- Use `Pick<Options, ...>` from Prettier to reuse fully compatible option types
- Fix misleading documentation for Tailwind `attributes` and `functions`:
  - Clarify they use exact match (not prefix)
  - Correct `attributes` default from `["class", "className"]` to `[]`
    - `class`/`className` are hardcoded, option is for additional
2026-01-20 08:58:36 +00:00
leaysgur a95b9bb54e feat(oxfmt): Support oxfmtrc overrides config (#18068)
Fixes #17585
2026-01-19 07:39:43 +00:00
nilptr 984d5c1ca6 feat(oxfmt/sort-imports): support options.customGroups (#17576)
#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>
2026-01-19 10:23:49 +09:00
leaysgur 7c664cfdf0 refactor(apps,website): Use the same generate_schema_json() function (#18025)
The initial goal was to extract the schema generation utility from `oxfmtrc.rs`.

However, I found that `oxc_linter` also defines a similar function, so I unified them into `website_common`.

- Added `generate_schema_json<T>()` to `website_common`
- Removed duplicated implementations from `oxfmtrc.rs` and `oxlintrc.rs`
- Moved `test_schema_json` from `oxc_linter` to `website_linter` too

NOTE: When running `cargo t -p oxc_linter`, it previously generated `npm/oxlint/configuration_schema.json`. Now use `just linter-schema-json` instead.
2026-01-15 07:33:59 +00:00
Yuji Sugiura 0e7176dde4 chore(oxfmt): Fix Oxfmtrc.printWidth typo (#18017)
`’` -> `'` 🙈
2026-01-15 14:19:28 +09:00
leaysgur 48d8dfe3e8 refactor(oxfmt): Generate better JSON schema for sort_imports.groups option (#18014)
Fixes #17651

Stopped manual deserialization and switched to using `enum` to ensure the correct types are generated.
2026-01-15 05:04:21 +00:00
leaysgur cc3e74bdff feat(oxfmt): Add Prettier specific fields in Oxfmtrc (#17981)
Like `vueIndentScriptAndStyle`, fixes #17741
2026-01-14 07:25:21 +00:00
leaysgur 6ffe31503c feat(oxfmt): Add more Oxfmtrc fields description (#17979)
Added more description with consistent notation.
2026-01-14 07:25:20 +00:00
Dunqing 62b7a01728 docs(formatter): clarify experimentalTailwindcss configuration comments (#17898) 2026-01-12 02:25:45 +00:00
leaysgur 1aea211424 refactor(oxfmt): Move Oxfmtrc to apps/oxfmt (#17824)
Before this PR, `Oxfmtrc` was defined in `oxc_formatter` to avoid the circular reference `oxc_language_server` > `oxfmt` > `oxc_language_server`....

But now, `oxc_language_server` does not depend on `Oxfmtrc` since LSP implementation has moved to `apps/oxfmt`.

Therefore, by this PR, `Oxfmtrc` is moved to its proper location, in `apps/oxfmt`.

- Moved `Oxfmtrc` from `oxc_formatter` to `apps/oxfmt`
- Re-exposed from `apps/oxfmt`
- Now only `tasks/website_formatter` depends on it
- Also aligned JSON schema generation with `oxlint` by `just formatter-schema-json`

NOTE: In `oxc_formatter`, there are 2 places (`ir_transform` and `fixtures` tests) where we manually convert options from JSON. I'd like to fix this too in the future.
2026-01-09 15:11:36 +00:00
leaysgur d7b5c0acd3 refactor(formatter)!: Remove Expand::Always variant (#17770)
Fixes #17330
2026-01-08 01:25:32 +00:00
leaysgur 86c016816a feat(oxfmt/sort_package_json): Handle oxfmtrc.sort_scripts option (#17738)
Part of #17387

This PR does not affect existing behavior. The next PR will implement the integration and tests.
2026-01-07 08:03:00 +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 8fd4ea905a feat(oxfmt): options.embeddedLanguageFormatting is now "auto" by default (#17649)
Fixes #16763
2026-01-05 05:15:17 +00:00
leaysgur 0a39cba7ec fix(oxfmt): Update wrong doc comment (#17288) 2025-12-23 01:28:56 +00:00
leaysgur 97a02d1159 feat(oxfmt): Add insertFinalNewline option (#17251)
Fixes #15066 and closes #16757

- Added `insertFinalNewline: bool` option
  - `true` by default = The same behavior with current `oxc_formatter` and Prettier
- Also respect `insert_final_newline` in `.editorconfig`
  - Like others, `.oxfmtrc` takes precedence
2025-12-22 08:55:38 +00:00
leaysgur ca28355fcd refactor(formatter/oxfmtrc): Fix up Oxfmtrc and oxfmtrc.rs (#16933)
This is a refactoring related to the `Oxfmtrc` struct.

- Clearly state that defined in `oxc_formatter` is temporary
  - Also changed export path
- Remove useless `default` attribute from `Oxfmtrc`
  - Defaults are resolved by `into_options()`
- Mark all `SortImportsConfig` fields as `Option`
  - Aligned with `Oxfmtrc`, needed for Node.js API
- Move `from_file()` to `oxc_language_server`, which only used by here
2025-12-16 11:39:42 +00:00
leaysgur afd6c44cd7 feat(oxfmt): Support quoteProps: consistent in Oxfmtrc (#16721)
Follow up #16677, fixes #16247.
2025-12-11 02:03:49 +00:00
leaysgur 88c87a38d0 refactor(formatter/oxfmtrc): Update into_format_options() to into_options() (#16678)
- Update `into_format_options()` to `into_options()`
  - Now it returns `FormatOptions` and `OxfmtOptions`
  - Apply changes
- Also make all `Oxfmtrc` fields as `Option` to distinguish undefined and default
- Update doc comments
2025-12-10 07:42:32 +00:00
connorshea 8babdf9eaa docs(oxfmt): Improve docs for .oxfmtrc.jsonc config fields and add markdownDescription fields to JSON Schema (#16587)
We should probably also improve the `type: string` stuff in the output Markdown to serialize enums with their actual values, but that's a separate problem.

This also updates the logic for generating the oxfmtrc `configuration_schema.json` file so it properly matches the way we generate the configuration_schema.json for oxlint, including with `markdownDescription` fields. See the oxlintrc file [here](https://github.com/oxc-project/oxc/blob/09ca3864227b30fa271d0ff35c5b5aa657f7c575/crates/oxc_linter/src/config/oxlintrc.rs#L205).

Before:

<img width="633" height="417" alt="Screenshot 2025-12-07 at 10 29 13 PM" src="https://github.com/user-attachments/assets/5cb205e3-2180-445b-a255-10b0831ff38a" />

After:

<img width="807" height="213" alt="Screenshot 2025-12-07 at 10 30 07 PM" src="https://github.com/user-attachments/assets/f5b1eb5b-bcb9-4348-8517-12ef3b91b155" />
2025-12-10 02:22:17 +00:00
leaysgur 28e068276d feat(oxfmt): Enable experimental package.json sorting by default (#16593)
Fixes #16525, closes #16541

- Use `sort-package-json: 0.0.2`
- Enable by default, can be disabled by `experimentalSortPackageJson: false`
2025-12-10 01:47:34 +00:00
leaysgur 73748568af feat(formatter/sort-imports): Support options.internalPattern (#16372)
Part of #14253

The original implementation accepts a `RegExp` string, but based on the actual usage, it seems that checking as prefix is sufficient for now.

https://github.com/search?q=%22internalPattern%3A+%5B%22&type=code
2025-12-02 07:37:06 +00:00
leaysgur 9706a1a501 fix(oxfmt): Ignore unsupported options (#16085)
Fixes #15930

Previously `experimentalOperatorPosition` was suggested in `.oxfmtrc` completion. But it is not actually used(not implemented yet).

As a first step, just ignore it. + Report error => #16088
2025-11-25 05:33:43 +00:00
leaysgur a937890b02 feat(formatter)!: Default to lineWidth: 100 (#15933)
Fixes #15900

- Update default 80 > 100
- Set `printWidth: 80` for fixture tests
  - Test diffs are all just adding header part for used options
- Also use `printWidth: 80` for `prettier_conformance`
2025-11-21 07:02:25 +00:00