From 638ccd66c97ec6d2e34363a4a389732e1d2a179b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 20 May 2026 11:16:24 +0200 Subject: [PATCH] fix: balance indent signals on multi-level jumps in embedded tagged templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the external formatter's output transitions by more than one indent level between adjacent lines, `maybe_gen_tagged_tpl_with_external_formatter` emitted only a single `StartIndent`/`FinishIndent` signal but jumped `current_indent_level` to the new level. The end-of-template cleanup then emitted one `FinishIndent` per level, leaving the IR unbalanced and panicking dprint-core's writer with "finish_indent was called without a corresponding start_indent". For example, `html\`\`` produces lines at indent levels 0 → 2 → 1: the old code pushed one StartIndent and one FinishIndent, but cleanup pushed two more FinishIndents at the end — three finishes against two starts. Replace the single-step `if`/`else if` with `while` loops so multi-level transitions emit one signal per level. Fixes denoland/deno#29963. --- src/generation/generate.rs | 9 ++--- tests/specs/external_formatter/html.txt | 48 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/generation/generate.rs b/src/generation/generate.rs index 47ca3b54..7fb3a332 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -3087,12 +3087,13 @@ fn maybe_gen_tagged_tpl_with_external_formatter<'a>(node: &TaggedTpl<'a>, contex // count indent characters let mut pos = line.chars().take_while(|ch| *ch == indent_char).count(); let indent_level = if indent_width == 0 { 0 } else { pos / indent_width as usize }; - if indent_level > current_indent_level { + while indent_level > current_indent_level { items.push_signal(Signal::StartIndent); - current_indent_level = indent_level; - } else if indent_level < current_indent_level { + current_indent_level += 1; + } + while indent_level < current_indent_level { items.push_signal(Signal::FinishIndent); - current_indent_level = indent_level; + current_indent_level -= 1; } let mut parts = line[pos..].split(placeholder_text).enumerate().peekable(); while let Some((i, part)) = parts.next() { diff --git a/tests/specs/external_formatter/html.txt b/tests/specs/external_formatter/html.txt index 9b076451..ef31cd05 100644 --- a/tests/specs/external_formatter/html.txt +++ b/tests/specs/external_formatter/html.txt @@ -49,3 +49,51 @@ export const Layout = (props: Props) => `; + +== should format html with multi-level indent jumps (regression for deno#29963) == +const a = html` + +`; +[expect] +const a = html` + + + +`; + +== should format html with multi-level indent jumps and an expression placeholder == +const a = html` + +`; +[expect] +const a = html` + + + +`; + +== should format html with three-level indent jump in a single transition == +const a = html` + +`; +[expect] +const a = html` + + + +`; + +== should format html with multiple multi-level indent jumps in the same template == +const a = html` + + + +`; +[expect] +const a = html` + + + + + +`;