fix(formatter,oxfmt): embed only original JSDoc plugin supported languages (#27241)

May support more languages in the future, but for now embed only css / less / scss / json / yaml / html, as original (`parserSynonyms`) does.
https://github.com/fardad-dev/prettier-plugin-jsdoc/blob/75825bae4a9e0f74a0539aeeada777d3e54e38d2/src/descriptionFormatter.ts#L16
This commit is contained in:
leaysgur committed 2026-10-01 05:49:52 +00:00
1 parent 4333fe8da7
commit 267557cfc3
7 files changed
+38 -38

No files matched your search

+2 -2
View File
@@ -107,9 +107,9 @@ Per-language options are NOT built up front: `ResolvedDispatchConfig` maps them
A separate string-out channel (the session's `string_embedder` service, NOT the dispatcher) carries JSDoc's string-in/string-out consumer:
- JSDoc fenced code blocks: routing follows ONE rule, the same `dispatcher::route` table
- JSDoc fenced code blocks: the host (`oxc_formatter`) embeds only the `prettier-plugin-jsdoc` set (css/less/scss/json/yaml/html), then routing follows ONE rule, the same `dispatcher::route` table
- a `Native` fence language formats through `FormatSession::dispatch_to_string` via a thin string adapter (`embed/jsdoc_fence.rs::format_native_fence`, EVERY build, the pure Rust build wires it via `services::for_root`)
- md/html/angular fences stay on the Prettier string path (`embed/prettier_string.rs`, napi only; their Doc→IR conversion has unrepresentable cases);
- html fences stay on the Prettier string path (`embed/prettier_string.rs`, napi only; its Doc→IR conversion has unrepresentable cases);
- everything else stays verbatim
- the embedder carries the caller's effective print width; both branches honor it (native via `PrintWidth` override, Prettier via `printWidth` in the options JSON), so a fence prints at the same width a JS/TS snippet in the same position would (see `upstream-jsdoc-bugs.md` #11 for the deliberate divergence from upstream's flat `printWidth - 4`)
+1 -2
View File
@@ -11,8 +11,7 @@
//! - [`services`] (every build): the root `SessionServices` assembly (`for_root`, one definition per build)
//! - [`jsdoc_fence`] (every build): the JSDoc native-fence string adapter over the registry
//! - [`prettier_doc`] (napi only): Prettier Doc→IR path for the `Route::Prettier` set
//! - [`prettier_string`] (napi only): the Prettier string paths of the string-out channel
//! (md/html/angular JSDoc fences; results re-embed line-by-line)
//! - [`prettier_string`] (napi only): the Prettier string paths of the string-out channel (html JSDoc fences; results re-embed line-by-line)
#[cfg(feature = "napi")]
use std::sync::Arc;
+1 -1
View File
@@ -30,7 +30,7 @@ use crate::core::{
/// Build the napi build's string embedder installed on the session.
///
/// Dispatches by language identifier: the native registry when available, otherwise Prettier via `format_embedded`.
/// The JSDoc fenced consumer reaches every language.
/// The JSDoc fenced consumer sends only its embedded set (`EMBEDDED_FENCE_LANGUAGES` in `oxc_formatter`).
///
/// `sort_tailwind` is the SAME pre-bound sorter as the session's Tailwind service
/// (options JSON already applied by `services::for_root`), for the `@apply` classes a CSS fence collects.
+18 -27
View File
@@ -54,7 +54,7 @@ describe("JSDoc", () => {
);
});
it("should format fenced scss, less and graphql through the native dispatch registry", async () => {
it("should format fenced scss and less through the native dispatch registry, keeping graphql verbatim", async () => {
const source = `
/**
* \`\`\`scss
@@ -91,19 +91,14 @@ describe("JSDoc", () => {
* \`\`\`
*
* \`\`\`graphql
* query Q {
* user {
* id
* name
* }
* }
* query Q { user { id name } }
* \`\`\`
*/
`.trimStart(),
);
});
it("should format fenced json, json5 and yaml via the native registry", async () => {
it("should format fenced json and yaml via the native registry, keeping json5 verbatim", async () => {
const source = `
/**
* \`\`\`json
@@ -132,7 +127,7 @@ describe("JSDoc", () => {
* \`\`\`
*
* \`\`\`json5
* { a: 1 }
* {a:1,}
* \`\`\`
*
* \`\`\`yaml
@@ -148,17 +143,17 @@ describe("JSDoc", () => {
it("should print native fences at the fence's effective width, not the full printWidth", async () => {
// Effective width at top level = printWidth(100) - " * "(3) - 4 = 93,
// the same width JS/TS snippets in the same position already use.
// The 94-char field line must break; the 92-char one must not.
// The 94-char line must break; the 93-char one must not.
// (Upstream `prettier-plugin-jsdoc` uses a flat printWidth - 4, which overflows `printWidth` for indented comments;
// see crates/oxc_formatter/tests/jsdoc/upstream-jsdoc-bugs.md)
const source = `
/**
* \`\`\`graphql
* query { fieldName(argOne: "value1", argTwo: "value2", argThree: "value3", four: 4444, five: 5555577) }
* \`\`\`json
* { "argOne": "value1", "argTwo": "value2", "argThree": "value3", "four": 4444, "five": 555555 }
* \`\`\`
*
* \`\`\`graphql
* query { fieldName(argOne: "value1", argTwo: "value2", argThree: "value3", four: 4444, five: 55555) }
* \`\`\`json
* { "argOne": "value1", "argTwo": "value2", "argThree": "value3", "four": 4444, "five": 55555 }
* \`\`\`
*/
`.trim();
@@ -168,22 +163,18 @@ describe("JSDoc", () => {
expect(result.code).toBe(
`
/**
* \`\`\`graphql
* query {
* fieldName(
* argOne: "value1"
* argTwo: "value2"
* argThree: "value3"
* four: 4444
* five: 5555577
* )
* \`\`\`json
* {
* "argOne": "value1",
* "argTwo": "value2",
* "argThree": "value3",
* "four": 4444,
* "five": 555555
* }
* \`\`\`
*
* \`\`\`graphql
* query {
* fieldName(argOne: "value1", argTwo: "value2", argThree: "value3", four: 4444, five: 55555)
* }
* \`\`\`json
* { "argOne": "value1", "argTwo": "value2", "argThree": "value3", "four": 4444, "five": 55555 }
* \`\`\`
*/
`.trimStart(),
+2 -1
View File
@@ -45,7 +45,8 @@ After changing AST shapes or the generators, regenerate with `just ast`, never h
- See `tests/jsdoc/fixtures` for the covered behavior
- See also `tests/jsdoc/upstream-jsdoc-bugs.md`
- Implemented in Rust as a comment-text rewrite at print time (`formatter/trivia.rs`), not an IR transform
- Non-JS fenced code blocks go through the session's optional `StringEmbedder`; without it (or on failure) they stay verbatim
- Non-JS fenced code blocks of the `prettier-plugin-jsdoc` set go through the session's optional `StringEmbedder`;
without it (or on failure), and for any other language, they stay verbatim
- Covered by plain fixture-pair tests (`--test jsdoc`, committed input/expected pairs);
a mismatch is a failing test, not a tracked conformance-report entry
@@ -25,9 +25,11 @@ fn parse_and_build<'a>(
Some(code)
}
/// Returns `true` if the fenced code block language is JS/TS/JSX/TSX.
/// Returns `true` if the fenced code block language is JS/TS/JSX/TSX (case-insensitive).
pub(super) fn is_js_ts_lang(lang: &str) -> bool {
matches!(lang, "js" | "javascript" | "jsx" | "ts" | "typescript" | "tsx")
["js", "javascript", "jsx", "ts", "typescript", "tsx"]
.iter()
.any(|name| lang.eq_ignore_ascii_case(name))
}
/// Count unescaped backticks on a line and update template literal depth.
@@ -743,12 +743,17 @@ fn serialize_code(
}
}
/// Non-JS/TS fence languages a description embeds: the `prettier-plugin-jsdoc` set (its `parserSynonyms`),
/// matched case-insensitively as there.
/// Any other stays verbatim; add one on demand, as long as the host's dispatcher routes it.
const EMBEDDED_FENCE_LANGUAGES: [&str; 6] = ["css", "less", "scss", "json", "yaml", "html"];
/// Format code block content in a JSDoc Markdown description.
///
/// Routing:
/// - JS/TS code (fenced with a JS/TS lang tag, fenced with no lang tag, or indented blocks):
/// formatted in-process via [`format_jsdoc_js_snippet`] (`oxc_formatter`'s own parser/formatter).
/// - Non-JS/TS fenced code (css, html, graphql, etc.):
/// - Non-JS/TS fenced code of [`EMBEDDED_FENCE_LANGUAGES`]:
/// when the session carries a string embedder, routed through it (the string channel);
/// absent one (Rust-only conformance, or the language has no native branch), preserved verbatim.
///
@@ -764,8 +769,10 @@ fn format_code_value<'a>(
if let Some(l) = lang
&& !is_js_ts_lang(l)
{
if let Some(embed) = opts.session.string_embedder()
&& let Ok(formatted) = embed(l, code, width)
if let Some(language) =
EMBEDDED_FENCE_LANGUAGES.into_iter().find(|name| l.eq_ignore_ascii_case(name))
&& let Some(embed) = opts.session.string_embedder()
&& let Ok(formatted) = embed(language, code, width)
{
return Cow::Owned(formatted);
}