Latest Results
updatebabu-ch:fix/ts-constructor-type-arrow-parens 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) feat(oxfmt): Support html-in-js substitution (#20193)
Part of #15180
The basic flow is the same as css-in-js: replace `${expr}` with placeholder strings, let Prettier format the HTML, then substitute back with formatted `expr`s.
However, there are a few key differences:
### `options.htmlWhitespaceSensitivity`
This HTML-specific option also affects html-in-js (template wrapping strategy, hug detection), so it needed to be held in `oxc_formatter::FormatOptions`.
### `prettier.__internal.formatOptionsHiddenDefaults`
Prettier adds hidden fields to the `options` object during its internal print phase for embedded formatters.
However, `__debug.printToDoc()` runs `normalizeFormatOptions()` which strips unknown keys. Unlike internal `textToDoc()` (which uses `passThrough: true`), `printToDoc()` does not preserve them.
To work around this, we pre-register keys in `formatOptionsHiddenDefaults` so they survive normalization.
### NOTE: Not fully supported cases
#### js-in-html-in-js
Currently, the process of converting Docs to our IR does not support `conditionalGroup`.
Since Prettier only uses this for YAML and JS formatting.
YAML is never embedded, and we would like to use `oxc_formatter` for JS.
However, applying `oxc_formatter` to the **js**-in-html-in-js case was not straightforward. Therefore, I have implemented a fallback to a simpler formatting process for that case for now.
#### `label({ embed, hug })` equivalent
With current implementation, `is_huggable_html_embed_single_arg()` always hugs via early-return, ignoring line width.
Prettier uses `label({ embed, hug })` + `conditionalGroup` to try hugged first and fall back to expanded.
We have the equivalent `BestFitting` 3-variant flow in `write_grouped_arguments`, but `ExpandParent` from HTML template's `hardline` propagates through `will_break` / `propagate_expands` / fit check, forcing the expanded variant to always be chosen.
To fix, we need to add new IR for boundary (prevents `ExpandParent` propagation in `will_break` / `propagate_expands` / fit check), then add HTML embeds to `can_group_expression_argument` so they enter the existing `BestFitting` 3-variant flow.
However, I'm not sure this is worth to add. So just leave it for now. Latest Branches
0%
0%
babu-ch:fix/ts-constructor-type-arrow-parens -3%
feat/jsdoc-comment-formatting Ā© 2026 CodSpeed Technology