Commit Graph

35 Commits

Author SHA1 Message Date
leaysgur 8213e72f37 refactor(formatter,oxfmt): flatten embedded dispatch contract (#25391)
Due to historical reasons, the IR was passed as `[Doc, (Doc, ...Doc)]` only when processing `gql-in-js`, whilst for other cases such as `css-in-js` and `html-in-js`, it was always `[Doc]`.

Now, every result is single `Doc`.
2026-08-09 14:32:11 +00:00
周星星同学 9213560fe7 fix(config): preserve defineConfig field JSDoc (#24326)
## Summary

- contextually type oxlint and oxfmt `defineConfig` object arguments as
their config interfaces
- keep the identity-helper return type so literal inference is preserved
- restore field-level JSDoc hover for config object properties in
editors

## Validation

Manual expansion of `just ready` because `just` is not installed in my
local shell:

- `pnpm install`
- `typos`
- `cargo lintgen`
- `cargo shear --fix --check-test-targets` (returned 0; emitted an
unrelated existing redundant-ignore warning for `oxc_lexer` in workspace
metadata)
- `cargo fmt`
- `node --run fmt`
- `cargo ck`
- `env -u NO_COLOR cargo test --all-features`
- `cargo lint -- --deny warnings`
- `RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
--document-private-items --all-features`
- `cargo run -p oxc_ast_tools`

TypeScript language-service probe using VS Code's bundled TypeScript
SDK:

- old `defineConfig<T extends Config>(config: T): T` returned no docs
for object fields
- new `defineConfig<T extends Config>(config: T & Config): T` returned
JSDoc for `oxlint.ignorePatterns` and `oxfmt.semi`
- invalid property/type probes still reported TS2561 and TS2322
- return literal inference stayed unchanged for representative
oxlint/oxfmt config objects

## AI Assistance

This PR was prepared with AI assistance. I reviewed the change,
validation approach, and PR contents before submission.

Co-authored-by: cixiangtao <cixiangtao@kanyun.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-22 16:41:41 +01:00
leaysgur 1228a0e968 refactor(oxfmt): load native bindings lazily for API (#23144)
Also fixes #23125
2026-06-09 03:47:48 +00:00
leaysgur 2aedd52ce6 fix(oxfmt): avoid JS promise rejects for all TSFN call sites (#23107)
Fixes #20525, (thanks @oxura !)

```sh
# Many files that route to the Prettier `formatFile` callback and fail to parse
mkdir -p /tmp/repro
for i in $(seq 1 500); do printf '<template><div></template>' > /tmp/repro/broken$i.vue; done

# Run repeatedly with default concurrency
for run in $(seq 1 10); do node apps/oxfmt/dist/cli.js --write /tmp/repro; done
```

Within a few runs it crashes non-deterministically (exit codes varied: 133 / 138 / 139, and sometimes 2 = clean error report), with the same fatal as #20525:

```
# Fatal error in , line 0
# Check failed: object_ != kGlobalHandleZapValue.   (or: node->IsInUse())
...
napi_reference_unref
<napi::error::Error as ...Drop>::drop
core::ptr::drop_in_place<napi::error::Error>
oxfmt::core::external_formatter::wrap_format_file::{{closure}}
```

Each broken file makes the `formatFile` callback reject; the resulting `napi::Error` is dropped on the TSFN await path and reaches `napi_reference_unref` during teardown → V8 fatal check.

After this fix (callback resolves errors as data), the same workload runs clean.
2026-06-08 02:54:09 +00:00
leaysgur f6114d3f48 refactor(oxfmt): Drop unused resolvePlugins stub (#22518) 2026-05-18 06:31:22 +00:00
leaysgur 828b56a307 fix(oxfmt): Re-export all nested types (#20636)
Fixes #20493, #20598, closes #20494
2026-03-23 01:31:06 +00:00
leaysgur d22c443045 feat(oxfmt): Export OxfmtConfig type (#20275)
Fixes #20259, also fix up naming conventions.
2026-03-13 00:06:56 +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 ec91dce7b3 refactor(oxfmt): Generate TS types from schema (#19901)
Like oxlint does.

No public definition change.
It also prepares for dynamic configuration files.
2026-03-02 23:51:22 +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
leaysgur 035933c443 feat(formatter,oxfmt): Support js-in-vue (partially) (#19514)
### Overview

Part of #16608

This PR applies `oxc_formatter` to the js-in-vue section.

However, it is not complete and is only applied to the following parts within js-in-vue:

- `script` block
  - and `generic="..."`
- `v-for` LHS
- `v-slot`

In the `script` block, `external_callback` is used like a regular JS file, but it isn't for others.

TL;DR: Now you can use sort-imports for Vue files.

### Review points

The points I'd like to ask your review are the following:

- Addition of `format_node()` to `oxc_formatter`
  - Made it possible to process any type that implements `Format`, not just `Program`
- The implementation of `js-in-vue` fragment was separated as much as possible to avoid impacting existing code
- `oxc_formatter` IR to prettier `Doc`

### TODOs

- [x] Planning
- [x] Implementation
- [x] Check benchmark
- [x] Check coverage
- [x] Understand & refactor
  - [x] oxfmt: JS side
  - [x] oxfmt: Rust side
  - [x] oxfmt: to_doc.rs
  - [x] oxc_formatter: fragment_format
- [x] Check benchmark again
- [x] Check coverage again
- [x] Check ecosystem-ci
- [x] Tests 👉🏻 #19563
2026-02-24 10:17:35 +00:00
leaysgur d7b63a4b05 fix(oxfmt): Update API types for sortPackageJsonOptions (#19569)
It was outdated...
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 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 c4a3cb5fad refactor(oxfmt): Move textToDoc() implementatin to Rust (#19307)
Part of #16608

Prettier's `textToDoc()` context has special handling based on hidden flags.

Initially, for Markdown, I implemented with heuristic string manipulation.

However,

- Similar processing is needed for HTML and Vue
- There are cases that cannot be handled by string manipulation alone
  - Considering adding options to the `oxc_formatter` side

For these reasons, a refactoring was performed as preparation.
2026-02-12 09:05:56 +00:00
leaysgur db8355b80c refactor(oxfmt): Fix all options.filepath usage (#19200)
Fixes #19194

- Changed from passing file name as `filepath` to using absolute path
- For Tailwind's sorter, retrieve it via `options.filepath`
- Ensure `filepath` is passed in all code paths
  - Also, path resolution should be handled at a higher flow
2026-02-10 07:12:18 +00:00
leaysgur d1e3537fb2 refactor(oxfmt): Insert options.(parser|filepath) on Rust side (#19199)
- Reduce work load on JS
- Also simplify `wrap_xxx()` in Rust
2026-02-10 07:12:18 +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 e0e925c3ab refactor(formatter,oxfmt): Move tag-to-parser logic from oxc_formatter to oxfmt (#18277)
Previously, both `oxc_formatter` (SUPPORTED_TAGS) and `oxfmt` JS (TAG_TO_PARSER) had knowledge about supported embedded language tags.

This created duplication and unclear responsibility boundaries.

- Remove `SUPPORTED_TAGS` and `is_supported_tag()` from `oxc_formatter`
- Add `tag_to_prettier_parser()` in `oxfmt` Rust side as single source of truth
- Change JS callback signature from `tagName` to `parserName`
- Simplify JS `formatEmbeddedCode()` to just call Prettier with given parser
2026-01-20 08:58:35 +00:00
leaysgur 90ce4b747f fix(oxfmt): Restore customGroups handling lost (#18243)
Fix for the issue where the code passing settings from `oxfmt` to `oxc_formatter` was missing.

- Also added tests
- Also fixed the TS type that was missing
2026-01-19 14:07:49 +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 d7b5c0acd3 refactor(formatter)!: Remove Expand::Always variant (#17770)
Fixes #17330
2026-01-08 01:25:32 +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 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 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 d340c87f1d fix(oxfmt): Update api FormatOptions type with & Record<string, unknown> (#17036)
Add `& Record<string, unknown>` to `FormatOptions` for `oxfmt.format()`.

This is needed to accept options like `vueIndentScriptAndStyle`, (which are `Oxfmtrc` currently unaware of) and various plugin options.

Also added tests for it and others too.
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 8c33ff4778 feat(oxfmt): Expose Node.js API: format(fileName, sourceText, options?) (#16939)
Fixes #15913
2025-12-16 13:43:16 +00: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 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
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