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