mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
refactor(formatter_core): make embedded dispatch parent-to-child only (#28159)
Remove `child_context`, every usecases are handled only parent -> child direction.
This commit is contained in:
1 parent
014e3d2676
commit
f525b34636
10 files changed
+112
-165
No files matched your search
@@ -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<DispatchResponse<'a>, 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<EmbeddedIr<'a>, 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
|
||||
|
||||
@@ -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<ResolvedDispatchConfig>,
|
||||
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::<HtmlInJsTemplate>()
|
||||
.is_some_and(|c| c.indent_multiple_roots)
|
||||
&& metadata
|
||||
.get("htmlHasMultipleRootElements")
|
||||
.and_then(Value::as_bool),
|
||||
}) as Box<dyn std::any::Any>
|
||||
});
|
||||
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}");
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
@@ -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<bool>,
|
||||
/// 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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::<HtmlEmbedMeta>())
|
||||
.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::<HtmlEmbedMeta>())
|
||||
.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, "`"))]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<EmbeddedIr<'a>> 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<Box<dyn Any>>,
|
||||
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.
|
||||
|
||||
@@ -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<Option<String>, 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())
|
||||
|
||||
Reference in new issue
Block a user