From f525b3463615b4b59f78041f1fd422bcf3aa1af5 Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Mon, 26 Oct 2026 03:52:42 +0000 Subject: [PATCH] refactor(formatter_core): make embedded dispatch parent-to-child only (#28159) Remove `child_context`, every usecases are handled only parent -> child direction. --- apps/oxfmt/src/core/embed/dispatcher.rs | 12 +-- apps/oxfmt/src/core/embed/prettier_doc.rs | 101 ++++++++++-------- apps/oxfmt/src/core/support.rs | 6 -- crates/oxc_formatter/src/embed_context.rs | 10 +- crates/oxc_formatter/src/lib.rs | 2 +- .../src/print/template/embed/html.rs | 80 ++++---------- crates/oxc_formatter_core/AGENTS.md | 5 +- crates/oxc_formatter_core/src/lib.rs | 4 +- .../src/session/embedded.rs | 34 ++---- crates/oxc_formatter_core/src/session/mod.rs | 23 ++-- 10 files changed, 112 insertions(+), 165 deletions(-) diff --git a/apps/oxfmt/src/core/embed/dispatcher.rs b/apps/oxfmt/src/core/embed/dispatcher.rs index f49d3aa36d..9abf6d6119 100644 --- a/apps/oxfmt/src/core/embed/dispatcher.rs +++ b/apps/oxfmt/src/core/embed/dispatcher.rs @@ -226,17 +226,17 @@ impl ResolvedDispatchConfig { } /// Fallback invoked for [`Route::Prettier`] languages. -/// Same shape as `FormatDispatcher` minus the request envelope -/// (the Doc path consumes neither `input_kind` nor `parent_context` today; +/// Same shape as `FormatDispatcher`, plus the routed language +/// (the Doc path consumes `parent_context` only for html(angular)-in-js's [`oxc_formatter::HtmlInJsTemplate`], not `input_kind`; /// re-examine if it ever serves envelope-bearing inputs). /// /// Assembled only in napi builds ([`super::prettier_doc`]); /// the pure Rust build passes `None` and these languages are deliberately preserved as-is. pub type PrettierDocFallback = Arc< - dyn for<'a> Fn( + dyn for<'a, 'r> Fn( &FormatSession<'a>, PrettierLanguage, - &str, + &DispatchRequest<'r>, ) -> Result, String> + Send + Sync, @@ -323,7 +323,7 @@ pub fn build_dispatcher( // deliberate skip otherwise (pure build). Route::Prettier(language) => { if let Some(fallback) = &fallback { - fallback(session, language, text) + fallback(session, language, &request) } else { debug!( "No fallback for Prettier language '{}' in this build, part stays as-is", @@ -349,7 +349,7 @@ fn format_native<'a, E: std::fmt::Display>( format_to_ir: impl FnOnce() -> Result, E>, ) -> DispatchResponse<'a> { debug_span!("oxfmt::embed::format_to_ir", language).in_scope(|| match format_to_ir() { - Ok(embedded) => DispatchResponse::Formatted(embedded.into()), + Ok(embedded) => DispatchResponse::Formatted(embedded), Err(err) => { debug!("native '{language}' format_to_ir failed, part stays as-is: {err}"); DispatchResponse::PreserveOriginal diff --git a/apps/oxfmt/src/core/embed/prettier_doc.rs b/apps/oxfmt/src/core/embed/prettier_doc.rs index 63b26cf69d..12a952258c 100644 --- a/apps/oxfmt/src/core/embed/prettier_doc.rs +++ b/apps/oxfmt/src/core/embed/prettier_doc.rs @@ -11,8 +11,11 @@ use serde::Deserialize; use serde_json::Value; use tracing::{debug, debug_span}; -use oxc_formatter::HtmlEmbedMeta; -use oxc_formatter_core::{DispatchPayload, DispatchResponse, FormatSession}; +use oxc_allocator::ArenaVec; +use oxc_formatter::HtmlInJsTemplate; +use oxc_formatter_core::{ + DispatchRequest, DispatchResponse, EmbeddedIr, FormatElement, FormatSession, Tag, +}; use crate::{ core::{ @@ -31,46 +34,60 @@ pub fn build_prettier_fallback( dispatch_config: Arc, format_embedded_doc: FormatEmbeddedDocWithConfigCallback, ) -> PrettierDocFallback { - Arc::new(move |session: &FormatSession<'_>, language: PrettierLanguage, text: &str| { - let parser_name = language.parser(); - let Some(options) = dispatch_config.prettier_options_for(language) else { - debug!("The plugin for parser '{parser_name}' is not enabled, part stays as-is"); - return Ok(DispatchResponse::PreserveOriginal); - }; - debug_span!("oxfmt::external::format_embedded_doc", parser = parser_name) - .in_scope(|| { - let doc_json_str = (format_embedded_doc)(options, text).map_err(|err| { - format!("Failed to get Doc for embedded code (parser '{parser_name}'): {err}") - })?; - // Prettier's Doc can produce deeply nested arrays - // (e.g., md-in-js with `proseWrap: preserve`, which nests each word in `[[[prev, " "], word], " "]`). - // The default recursion limit of 128 is not enough for long paragraphs. - // This only affects this deserialization call; - // other `serde_json` usage in the codebase keeps the default limit. - let mut de = serde_json::Deserializer::from_str(&doc_json_str); - de.disable_recursion_limit(); - let doc_json = serde_json::Value::deserialize(&mut de) - .map_err(|e| format!("Failed to parse Doc JSON: {e}"))?; + Arc::new( + move |session: &FormatSession<'_>, + language: PrettierLanguage, + request: &DispatchRequest<'_>| { + let text = request.text; + let parser_name = language.parser(); + let Some(options) = dispatch_config.prettier_options_for(language) else { + debug!("The plugin for parser '{parser_name}' is not enabled, part stays as-is"); + return Ok(DispatchResponse::PreserveOriginal); + }; + debug_span!("oxfmt::external::format_embedded_doc", parser = parser_name) + .in_scope(|| { + let doc_json_str = (format_embedded_doc)(options, text).map_err(|err| { + format!( + "Failed to get Doc for embedded code (parser '{parser_name}'): {err}" + ) + })?; + // Prettier's Doc can produce deeply nested arrays + // (e.g., md-in-js with `proseWrap: preserve`, which nests each word in `[[[prev, " "], word], " "]`). + // The default recursion limit of 128 is not enough for long paragraphs. + // This only affects this deserialization call; + // other `serde_json` usage in the codebase keeps the default limit. + let mut de = serde_json::Deserializer::from_str(&doc_json_str); + de.disable_recursion_limit(); + let doc_json = serde_json::Value::deserialize(&mut de) + .map_err(|e| format!("Failed to parse Doc JSON: {e}"))?; - let allocator = session.allocator(); - let (mut ir, metadata) = from_prettier_doc::convert_envelope( - doc_json, - allocator, - session.group_id_builder(), - )?; - from_prettier_doc::postprocess(&mut ir, allocator); - // HTML/Angular additionally surface `HtmlEmbedMeta` to the embed site. - let child_context = language.wants_html_meta().then(|| { - Box::new(HtmlEmbedMeta { - has_multiple_root_elements: metadata + let allocator = session.allocator(); + let (mut ir, metadata) = from_prettier_doc::convert_envelope( + doc_json, + allocator, + session.group_id_builder(), + )?; + from_prettier_doc::postprocess(&mut ir, allocator); + // html(angular)-in-js: indent the content when more than one node prints at the root + let indent = request + .parent_context_as::() + .is_some_and(|c| c.indent_multiple_roots) + && metadata .get("htmlHasMultipleRootElements") - .and_then(Value::as_bool), - }) as Box - }); - Ok(DispatchResponse::Formatted(DispatchPayload { doc: ir, child_context })) - }) - .inspect_err(|err| { - debug!("Failed to format embedded doc for parser '{parser_name}': {err}"); - }) - }) + .and_then(Value::as_bool) + .unwrap_or(true); + if indent { + let mut indented = ArenaVec::with_capacity_in(ir.len() + 2, &allocator); + indented.push(FormatElement::Tag(Tag::StartIndent)); + indented.extend(ir); + indented.push(FormatElement::Tag(Tag::EndIndent)); + ir = indented; + } + Ok(DispatchResponse::Formatted(EmbeddedIr { ir })) + }) + .inspect_err(|err| { + debug!("Failed to format embedded doc for parser '{parser_name}': {err}"); + }) + }, + ) } diff --git a/apps/oxfmt/src/core/support.rs b/apps/oxfmt/src/core/support.rs index a21a15ae84..dc85bce034 100644 --- a/apps/oxfmt/src/core/support.rs +++ b/apps/oxfmt/src/core/support.rs @@ -199,12 +199,6 @@ impl PrettierLanguage { _ => None, } } - - /// Whether the Doc→IR conversion must surface `HtmlEmbedMeta` - /// (`htmlHasMultipleRootElements`) to the embed site. - pub fn wants_html_meta(self) -> bool { - matches!(self, Self::Html | Self::Angular) - } } // --- diff --git a/crates/oxc_formatter/src/embed_context.rs b/crates/oxc_formatter/src/embed_context.rs index e80abdc12c..a15ad27d25 100644 --- a/crates/oxc_formatter/src/embed_context.rs +++ b/crates/oxc_formatter/src/embed_context.rs @@ -23,11 +23,11 @@ pub struct CssInJsTemplate; /// which would need escaping in the template. pub struct MarkdownInJsTemplate; -/// Child→parent pair context for HTML/Angular formatted as an embedded child (html-in-js). -pub struct HtmlEmbedMeta { - /// Whether the parsed HTML has more than one root element. - /// Used to decide whether to `indent` the template content. - pub has_multiple_root_elements: Option, +/// Parent→child context for HTML/Angular dispatched from a JS template literal (html-in-js). +pub struct HtmlInJsTemplate { + /// The backticks hug the content (no whitespace on both ends, whitespace sensitivity not `ignore`): + /// the child indents its content when more than one node prints at the root. + pub indent_multiple_roots: bool, } /// What a dispatched JS/TS program is embedded in, for the print rules that depend on it. diff --git a/crates/oxc_formatter/src/lib.rs b/crates/oxc_formatter/src/lib.rs index 54449a48e7..b71bb44715 100644 --- a/crates/oxc_formatter/src/lib.rs +++ b/crates/oxc_formatter/src/lib.rs @@ -26,7 +26,7 @@ use oxc_span::SourceType; // or the special-purpose AST-in `format_program`. pub(crate) use crate::ast_nodes::{AstNode, AstNodes}; pub use crate::embed_context::{ - CssInJsTemplate, HtmlEmbedMeta, JsEmbeddedIn, MarkdownInJsTemplate, + CssInJsTemplate, HtmlInJsTemplate, JsEmbeddedIn, MarkdownInJsTemplate, }; // `JsFormatContext` is public solely as the type parameter of the returned `Formatted`; // its methods are not part of the public contract. diff --git a/crates/oxc_formatter/src/print/template/embed/html.rs b/crates/oxc_formatter/src/print/template/embed/html.rs index 7f7ded10af..a3cff7e0f6 100644 --- a/crates/oxc_formatter/src/print/template/embed/html.rs +++ b/crates/oxc_formatter/src/print/template/embed/html.rs @@ -1,13 +1,12 @@ use oxc_allocator::ArenaStringBuilder; use oxc_ast::ast::*; use oxc_formatter_core::{ - DispatchRequest, DispatchResponse, FormatElement, InputKind, map_text_in_ir, - push_text_with_literal_lines, + FormatElement, dispatch_fragment_ir, map_text_in_ir, push_text_with_literal_lines, }; use crate::{ ast_nodes::AstNode, - embed_context::HtmlEmbedMeta, + embed_context::HtmlInJsTemplate, format_args, formatter::prelude::*, print::template::{ @@ -52,39 +51,19 @@ pub(super) fn format_html_doc<'a>( let has_leading_ws = cooked.starts_with(|c: char| c.is_ascii_whitespace()); let has_trailing_ws = cooked.ends_with(|c: char| c.is_ascii_whitespace()); + let ws_ignore = f.options().html_whitespace_sensitivity_ignore; - let Ok(DispatchResponse::Formatted(result)) = f.session().dispatch(DispatchRequest { - language: embedded_language, - text: cooked, - input_kind: InputKind::Fragment, - parent_context: None, - }) else { + let context = html_in_js_template(has_leading_ws, has_trailing_ws, ws_ignore); + let Some(ir) = dispatch_fragment_ir(f, embedded_language, cooked, Some(&context)) else { return false; }; - let Some(html_has_multiple_root_elements) = result - .child_context - .as_ref() - .and_then(|context| context.downcast_ref::()) - .map(|meta| meta.has_multiple_root_elements) - else { - return false; - }; - let ir = result.doc; // Re-escape template chars in `Text` runs: // the IR is reinserted into a JS template literal built from `.cooked` values. let ir = super::escape_template_chars_in_ir(ir, f); let content = format_once(|f| f.write_elements(ir)); - let ws_ignore = f.options().html_whitespace_sensitivity_ignore; - write_html_template( - f, - &content, - has_leading_ws, - has_trailing_ws, - html_has_multiple_root_elements.unwrap_or(true), - ws_ignore, - ); + write_html_template(f, &content, has_leading_ws, has_trailing_ws, ws_ignore); return true; } @@ -112,27 +91,14 @@ pub(super) fn format_html_doc<'a>( let has_leading_ws = joined.starts_with(|c: char| c.is_ascii_whitespace()); let has_trailing_ws = joined.ends_with(|c: char| c.is_ascii_whitespace()); + let ws_ignore = f.options().html_whitespace_sensitivity_ignore; // Phase 2: Format via the dispatcher (IR path) - let Ok(DispatchResponse::Formatted(result)) = f.session().dispatch(DispatchRequest { - language: embedded_language, - text: joined, - input_kind: InputKind::Fragment, - parent_context: None, - }) else { + let context = html_in_js_template(has_leading_ws, has_trailing_ws, ws_ignore); + let Some(ir) = dispatch_fragment_ir(f, embedded_language, joined, Some(&context)) else { return false; }; - let Some(html_has_multiple_root_elements) = result - .child_context - .as_ref() - .and_then(|context| context.downcast_ref::()) - .map(|meta| meta.has_multiple_root_elements) - else { - return false; - }; - let ir = result.doc; - // Validate before formatting any expression. // Formatting consumes comment state, // so discovering an unusable placeholder layout afterwards would make the verbatim fallback unsafe. @@ -188,19 +154,19 @@ pub(super) fn format_html_doc<'a>( true }); let format_content = format_once(move |f: &mut JsFormatter<'_, 'a>| f.write_elements(ir)); - - let ws_ignore = f.options().html_whitespace_sensitivity_ignore; - write_html_template( - f, - &format_content, - has_leading_ws, - has_trailing_ws, - html_has_multiple_root_elements.unwrap_or(true), - ws_ignore, - ); + write_html_template(f, &format_content, has_leading_ws, has_trailing_ws, ws_ignore); true } +/// The child indents its content only where the backticks hug it (the last case of [`write_html_template`]). +fn html_in_js_template( + has_leading_ws: bool, + has_trailing_ws: bool, + ws_ignore: bool, +) -> HtmlInJsTemplate { + HtmlInJsTemplate { indent_multiple_roots: !(ws_ignore || has_leading_ws && has_trailing_ws) } +} + // --- /// Write the HTML template with appropriate wrapping based on whitespace and top-level count. @@ -214,12 +180,12 @@ pub(super) fn format_html_doc<'a>( /// - Otherwise: `group(["`", leadingWS?, maybeIndent(group(content)), trailingWS?, "`"])` /// → content hugs the backtick directly /// → multiple root elements wraps with `indent`, single does not +/// → the child does the `indent` on request ([`HtmlInJsTemplate`]) fn write_html_template<'a>( f: &mut JsFormatter<'_, 'a>, content: &impl Format<'a, JsFormatContext<'a>>, has_leading_ws: bool, has_trailing_ws: bool, - has_multiple_root_elements: bool, ws_ignore: bool, ) { if ws_ignore { @@ -249,11 +215,7 @@ fn write_html_template<'a>( // group(["`", leadingWS?, maybeIndent(group(content)), trailingWS?, "`"]) let leading = if has_leading_ws { " " } else { "" }; let trailing = if has_trailing_ws { " " } else { "" }; - if has_multiple_root_elements { - write!(f, [group(&format_args!("`", leading, indent(&group(content)), trailing, "`"))]); - } else { - write!(f, [group(&format_args!("`", leading, group(content), trailing, "`"))]); - } + write!(f, [group(&format_args!("`", leading, group(content), trailing, "`"))]); } } diff --git a/crates/oxc_formatter_core/AGENTS.md b/crates/oxc_formatter_core/AGENTS.md index 78807ac420..3662538a4f 100644 --- a/crates/oxc_formatter_core/AGENTS.md +++ b/crates/oxc_formatter_core/AGENTS.md @@ -78,7 +78,7 @@ The core is parameterized over a consumer-supplied context so it stays language- ### Embedded-language infrastructure (`session/`) -`FormatSession` / `FormatDispatcher` / `DispatchRequest` / `DispatchPayload` let one formatter's IR be built inside another's document (e.g. graphql-in-js): +`FormatSession` / `FormatDispatcher` / `DispatchRequest` / `EmbeddedIr` let one formatter's IR be built inside another's document (e.g. graphql-in-js): - The orchestrator (oxfmt) assembles the dispatcher, mapping language names to formatter implementations (or a Prettier fallback) - Formatter crates only invoke it via `FormatSession::dispatch` @@ -86,8 +86,9 @@ The core is parameterized over a consumer-supplied context so it stays language- - A child adds classes straight into the parent's index space, so its doc is written as-is (no remap) - A root formatter opens a fresh scope (`with_new_tailwind_scope`) and sorts it when finalizing its `Document` (`take_sorted_tailwind_classes`) - A language crate's `format_to_ir` entry returns `EmbeddedIr`, one shape for every child language, no per-crate tuples + - The dispatcher answers with it as-is (`DispatchResponse::Formatted`): data flows parent→child only, a child reports nothing back - Only language-pair specific data crosses as `dyn Any`, core never learns concrete languages: - - When the parent picks the child language, it passes the pair's own data (css-in-js's `CssInJsTemplate`, html-in-js's `has_multiple_root_elements`) + - When the parent picks the child language, it passes the pair's own data (css-in-js's `CssInJsTemplate`, html-in-js's `HtmlInJsTemplate`) - When it cannot (a Markdown code block's name resolves in the host), it passes a fact about itself to every child, and the host translates it into the pair's data once the child language is known (`XxxInMarkdownCodeBlock` → `JsEmbeddedIn`) - `FormatSession` (`session/mod.rs`) is the execution unit: diff --git a/crates/oxc_formatter_core/src/lib.rs b/crates/oxc_formatter_core/src/lib.rs index c2e63c066c..c78a168b71 100644 --- a/crates/oxc_formatter_core/src/lib.rs +++ b/crates/oxc_formatter_core/src/lib.rs @@ -48,8 +48,8 @@ pub use printer::{PrintResult, PrintWidth, Printed, PrinterOptions}; // which is what makes print-without-finalize unrepresentable outside core. pub(crate) use printer::Printer; pub use session::embedded::{ - DispatchPayload, DispatchRequest, DispatchResponse, EmbeddedIr, FormatDispatcher, - dispatch_fragment_ir, dispatch_ir, map_text_in_ir, push_text_with_literal_lines, + DispatchRequest, DispatchResponse, EmbeddedIr, FormatDispatcher, dispatch_fragment_ir, + dispatch_ir, map_text_in_ir, push_text_with_literal_lines, }; pub use session::{FormatSession, InputKind, SessionServices, StringEmbedder, TailwindSorter}; pub use source::{SourceText, SpanCursor, rebase_labels, restore_crlf_labels}; diff --git a/crates/oxc_formatter_core/src/session/embedded.rs b/crates/oxc_formatter_core/src/session/embedded.rs index 09f86c5142..759f1cbde7 100644 --- a/crates/oxc_formatter_core/src/session/embedded.rs +++ b/crates/oxc_formatter_core/src/session/embedded.rs @@ -34,8 +34,7 @@ pub struct DispatchRequest<'r> { pub input_kind: InputKind, /// Parent→child language-pair specific data, /// downcast by the implementation (`None` for most pairs; e.g. JS's `CssInJsTemplate`). - /// The borrowed counterpart of [`DispatchPayload::child_context`] - /// (borrowed because the parent outlives the dispatch call). + /// Only parent→child: a child formats what the parent asks for, it reports nothing back. pub parent_context: Option<&'r dyn Any>, } @@ -55,8 +54,8 @@ impl DispatchRequest<'_> { /// optional-embed callers degrade the same way for both, /// but the two must never be conflated at the source. pub enum DispatchResponse<'a> { - /// The child formatted the request; consume [`DispatchPayload`]. - Formatted(DispatchPayload<'a>), + /// The child formatted the request; the parent writes its IR as-is. + Formatted(EmbeddedIr<'a>), /// Deliberately not formatted; keep the original source untouched. PreserveOriginal, } @@ -77,34 +76,16 @@ pub type FormatDispatcher = Arc< + Sync, >; -/// IR built by a language crate's embedded entry point (`format_to_ir`) for one input text. -/// The orchestrator's dispatcher wraps it into a [`DispatchPayload`]. +/// IR built by a language crate's embedded entry point (`format_to_ir`) for one input text, +/// carried by [`DispatchResponse::Formatted`]. /// /// Every language crate's `format_to_ir` returns this shape, /// so a new child language only has to fill in the fields (no per-crate tuple conventions). pub struct EmbeddedIr<'a> { /// The formatter IR, arena-allocated alongside its elements. - /// Its `FormatElement::TailwindClass` indices point into the session's class scope. - pub ir: ArenaVec<'a, FormatElement<'a>>, -} - -impl<'a> From> for DispatchPayload<'a> { - fn from(embedded: EmbeddedIr<'a>) -> Self { - Self { doc: embedded.ir, child_context: None } - } -} - -/// The child's formatted product, carried by [`DispatchResponse::Formatted`]. -pub struct DispatchPayload<'a> { - /// The formatted IR, arena-allocated alongside its elements. /// Its `FormatElement::TailwindClass` indices already point into the parent's class scope /// (the child session shares it), so the parent writes it as-is. - pub doc: ArenaVec<'a, FormatElement<'a>>, - /// Child→parent language-pair specific data, - /// downcast by the parent (`None` for most pairs; e.g. HTML's `has_multiple_root_elements`). - /// The owned counterpart of [`DispatchRequest::parent_context`] - /// (owned because it outlives the child's stack frame). - pub child_context: Option>, + pub ir: ArenaVec<'a, FormatElement<'a>>, } /// Dispatches one embedded fragment and hands out its doc: @@ -125,7 +106,6 @@ pub fn dispatch_fragment_ir<'a, C: FormatContext>( /// /// `None` covers [`DispatchResponse::PreserveOriginal`] and operational errors alike; /// the caller keeps its original source. -/// Embed sites that must inspect [`DispatchPayload::child_context`] first stay manual. pub fn dispatch_ir<'a, C: FormatContext>( f: &Formatter<'_, 'a, C>, request: DispatchRequest<'_>, @@ -133,7 +113,7 @@ pub fn dispatch_ir<'a, C: FormatContext>( let Ok(DispatchResponse::Formatted(result)) = f.session().dispatch(request) else { return None; }; - Some(result.doc) + Some(result.ir) } /// Rebuild an embedded IR, descending into BestFitting variants and interned content. diff --git a/crates/oxc_formatter_core/src/session/mod.rs b/crates/oxc_formatter_core/src/session/mod.rs index 52d30b95c7..01f2d355fb 100644 --- a/crates/oxc_formatter_core/src/session/mod.rs +++ b/crates/oxc_formatter_core/src/session/mod.rs @@ -7,7 +7,7 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use oxc_allocator::Allocator; use crate::{ - DispatchPayload, DispatchRequest, DispatchResponse, Document, FormatDispatcher, LineEnding, + DispatchRequest, DispatchResponse, Document, EmbeddedIr, FormatDispatcher, LineEnding, PrinterOptions, UniqueGroupIdBuilder, }; @@ -200,8 +200,7 @@ impl<'a> FormatSession<'a> { ) -> Result, String> { let text_len = request.text.len(); let scoped = self.with_new_tailwind_scope(); - let DispatchResponse::Formatted(DispatchPayload { doc, .. }) = scoped.dispatch(request)? - else { + let DispatchResponse::Formatted(EmbeddedIr { ir: doc }) = scoped.dispatch(request)? else { return Ok(None); }; @@ -282,8 +281,8 @@ mod tests { use super::{FormatSession, InputKind, MAX_DISPATCH_DEPTH, SessionServices, TailwindSorter}; use crate::{ - DispatchPayload, DispatchRequest, DispatchResponse, Document, FormatDispatcher, - FormatElement, FormatState, Interned, PrinterOptions, + DispatchRequest, DispatchResponse, Document, EmbeddedIr, FormatDispatcher, FormatElement, + FormatState, Interned, PrinterOptions, }; fn request<'r>() -> DispatchRequest<'r> { @@ -307,10 +306,7 @@ mod tests { fn dispatch_preserves_a_bom_headed_text() { let allocator = Allocator::default(); let dispatcher: FormatDispatcher = Arc::new(|ctx, _request| { - Ok(DispatchResponse::Formatted(DispatchPayload { - doc: ArenaVec::new_in(&ctx.allocator()), - child_context: None, - })) + Ok(DispatchResponse::Formatted(EmbeddedIr { ir: ArenaVec::new_in(&ctx.allocator()) })) }); let session = FormatSession::with_services( &allocator, @@ -329,10 +325,7 @@ mod tests { fn dispatch_stops_at_the_depth_limit() { let allocator = Allocator::default(); let dispatcher: FormatDispatcher = Arc::new(|ctx, _request| { - Ok(DispatchResponse::Formatted(DispatchPayload { - doc: ArenaVec::new_in(&ctx.allocator()), - child_context: None, - })) + Ok(DispatchResponse::Formatted(EmbeddedIr { ir: ArenaVec::new_in(&ctx.allocator()) })) }); let mut session = FormatSession::with_services( &allocator, @@ -405,7 +398,7 @@ mod tests { [FormatElement::Interned(Interned::new(inner))], &ctx.allocator(), ); - Ok(DispatchResponse::Formatted(DispatchPayload { doc, child_context: None })) + Ok(DispatchResponse::Formatted(EmbeddedIr { ir: doc })) }); let session = FormatSession::with_services( &allocator, @@ -426,7 +419,7 @@ mod tests { [FormatElement::TailwindClass(parent_index), FormatElement::Space], &&allocator, ); - elements.extend(payload.doc); + elements.extend(payload.ir); let printed = Document::new(elements, session.take_sorted_tailwind_classes()) .print(0, PrinterOptions::default())