From 23d23d69aebb9b945495dc52f8b16c2b8db6847e Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Mon, 26 Oct 2026 03:52:44 +0000 Subject: [PATCH] fix(formatter_markdown): honor range ignore inside directives (#28164) Fixes #28154 Range ignore is supported only at the top level. Prettier does not support directives, so the contents of `:::` end up at the top level and the range works there. We parse `:::` as a container directive, so its contents were not top-level and the range was ignored. This PR treats a `:::` directive with no blockquote / list item / footnote above it as top-level. Supporting ranges everywhere is possible, but it has concerns such as indentation drift in verbatim output. So this starts small. --- .../oxc_formatter_markdown/src/print/block.rs | 26 +++++--- .../src/print/blockquote.rs | 4 +- .../tests/fixtures/markdown/oxfmt-ignore.md | 19 ++++++ .../fixtures/markdown/oxfmt-ignore.md.snap | 61 +++++++++++++++++++ 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/crates/oxc_formatter_markdown/src/print/block.rs b/crates/oxc_formatter_markdown/src/print/block.rs index fc7ce0d682..f3ce6bde4e 100644 --- a/crates/oxc_formatter_markdown/src/print/block.rs +++ b/crates/oxc_formatter_markdown/src/print/block.rs @@ -31,8 +31,18 @@ pub enum Parent { loose: bool, after_checkbox: bool, }, - /// Blockquote or container directive. - Container, + /// Blockquote, footnote definition or container directive. + /// `top_level`: no prefix / indent above (a directive at the root, or in such a directive). + Container { + top_level: bool, + }, +} + +impl Parent { + /// No prefix / indent above: the root, or a `top_level` container. + const fn is_top_level(self) -> bool { + matches!(self, Self::Root | Self::Container { top_level: true }) + } } /// Emits sibling blocks with a blank line between them, except where one line break is the rule @@ -53,8 +63,9 @@ pub fn write_blocks<'a>( } // `` ... ``: the range is source text. - // Only at the document root; nested, the comments are plain HTML. - if parent == Parent::Root + // Only at the document root and in directives with no prefix / indent above them + // (top-level for Prettier, which has no directives); nested elsewhere, the comments are plain HTML. + if parent.is_top_level() && let Some(end) = ignore_range_end(blocks, index, f) { let source = f.context().source_text(); @@ -221,7 +232,7 @@ pub fn write_block<'a>( Block::Paragraph(p) => { let parent = inline::InlineParent { paragraph: true, - first_in_container: parent == Parent::Container && index == 0, + first_in_container: matches!(parent, Parent::Container { .. }) && index == 0, first_in_document: front_matter_risk(parent, siblings, index, f), after_checkbox: matches!(parent, Parent::ListItem { after_checkbox: true, .. }), ..inline::InlineParent::default() @@ -255,7 +266,8 @@ pub fn write_block<'a>( write_verbatim(d.opening, f); if !d.children.is_empty() { super::write_gap(after_opening, f); - write_blocks(&d.children, Parent::Container, f); + let inner = Parent::Container { top_level: parent.is_top_level() }; + write_blocks(&d.children, inner, f); } if let Some(closing) = d.closing { super::write_gap(before_closing, f); @@ -331,7 +343,7 @@ fn write_footnote_definition<'a>( ) { write!(f, [token("[^"), text(f.context().slice(def.label)), token("]:")]); let children = &def.children; - let parent = Parent::Container; + let parent = Parent::Container { top_level: false }; let first = format_with(|f| write_block(&children[0], children, 0, parent, f)).memoized(); let on_label_line = matches!(children.as_slice(), [Block::Paragraph(_)]) && f.options().prose_wrap != ProseWrap::Always diff --git a/crates/oxc_formatter_markdown/src/print/blockquote.rs b/crates/oxc_formatter_markdown/src/print/blockquote.rs index eacf10291a..db20bd0545 100644 --- a/crates/oxc_formatter_markdown/src/print/blockquote.rs +++ b/crates/oxc_formatter_markdown/src/print/blockquote.rs @@ -15,7 +15,9 @@ pub fn write_blockquote<'a>(quote: &'a Blockquote<'a>, f: &mut MarkdownFormatter write!(f, ">"); return; } - let body = format_with(|f| block::write_blocks("e.children, block::Parent::Container, f)); + let body = format_with(|f| { + block::write_blocks("e.children, block::Parent::Container { top_level: false }, f); + }); // The prefixed column is the root that verbatim continuation lines return to write!(f, ["> ", prefix_align(&"> ", &mark_as_root(&body))]); } diff --git a/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md b/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md index 0b7df01fab..952f7e7bd5 100644 --- a/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md +++ b/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md @@ -11,3 +11,22 @@ * also kept + +::: details X + + +| a | b | +| - | - | +* kept + + +::: + +> ::: details Y +> +> +> | a | b | +> | - | - | +> +> +> ::: diff --git a/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md.snap b/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md.snap index 4322fafbbf..c820266a56 100644 --- a/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md.snap +++ b/crates/oxc_formatter_markdown/tests/fixtures/markdown/oxfmt-ignore.md.snap @@ -16,6 +16,25 @@ source: crates/oxc_formatter_markdown/tests/fixtures/mod.rs * also kept +::: details X + + +| a | b | +| - | - | +* kept + + +::: + +> ::: details Y +> +> +> | a | b | +> | - | - | +> +> +> ::: + ==================== Output ==================== ------------------ { printWidth: 80 } @@ -34,6 +53,27 @@ source: crates/oxc_formatter_markdown/tests/fixtures/mod.rs * also kept +::: details X + + +| a | b | +| - | - | +* kept + + +::: + +> ::: details Y +> +> +> +> | a | b | +> | --- | --- | +> +> +> +> ::: + ------------------- { printWidth: 100 } ------------------- @@ -51,4 +91,25 @@ source: crates/oxc_formatter_markdown/tests/fixtures/mod.rs * also kept +::: details X + + +| a | b | +| - | - | +* kept + + +::: + +> ::: details Y +> +> +> +> | a | b | +> | --- | --- | +> +> +> +> ::: + ===================== End =====================