diff --git a/crates/oxc_formatter/examples/formatter.rs b/crates/oxc_formatter/examples/formatter.rs index f6dc0de2984e7..3cb544337efa5 100644 --- a/crates/oxc_formatter/examples/formatter.rs +++ b/crates/oxc_formatter/examples/formatter.rs @@ -60,17 +60,16 @@ fn main() -> Result<(), String> { }; let formatter = Formatter::new(&allocator, options); + let formatted = formatter.format(&ret.program); if show_ir { - let doc = formatter.doc(&ret.program); - println!("["); - for el in doc.iter() { - println!(" {el:?},"); - } - println!("]"); - } else { - let code = formatter.build(&ret.program); - println!("{code}"); + println!("--- IR ---"); + println!("{}", &formatted.document().to_string()); + println!("--- End IR ---\n"); } + println!("--- Formatted Code ---"); + let code = formatted.print().map_err(|e| e.to_string())?.into_code(); + println!("{code}"); + println!("--- End Formatted Code ---"); Ok(()) } diff --git a/crates/oxc_formatter/src/formatter/context.rs b/crates/oxc_formatter/src/formatter/context.rs index 718d60fba7baa..4104eb9708a76 100644 --- a/crates/oxc_formatter/src/formatter/context.rs +++ b/crates/oxc_formatter/src/formatter/context.rs @@ -42,16 +42,18 @@ impl std::fmt::Debug for FormatContext<'_> { impl<'ast> FormatContext<'ast> { pub fn new( - program: &'ast Program<'ast>, + source_text: &'ast str, + source_type: SourceType, + comments: &'ast [Comment], allocator: &'ast Allocator, options: FormatOptions, ) -> Self { - let source_text = SourceText::new(program.source_text); + let source_text = SourceText::new(source_text); Self { options, source_text, - source_type: program.source_type, - comments: Comments::new(source_text, &program.comments), + source_type, + comments: Comments::new(source_text, comments), allocator, cached_elements: FxHashMap::default(), } diff --git a/crates/oxc_formatter/src/formatter/format_element/document.rs b/crates/oxc_formatter/src/formatter/format_element/document.rs index 5b9dc68af87ec..3e84aa087bfde 100644 --- a/crates/oxc_formatter/src/formatter/format_element/document.rs +++ b/crates/oxc_formatter/src/formatter/format_element/document.rs @@ -1,13 +1,23 @@ #![expect(clippy::mutable_key_type)] +use cow_utils::CowUtils; use std::ops::Deref; +use oxc_allocator::Allocator; +use oxc_ast::Comment; +use oxc_span::SourceType; use rustc_hash::FxHashMap; -use super::{ - super::{FormatElement, prelude::*}, - tag::Tag, +use super::super::prelude::*; +use super::tag::Tag; +use crate::formatter::TextSize; +use crate::formatter::prelude::tag::{DedentMode, GroupMode}; +use crate::{ + Format, FormatOptions, FormatResult, IndentStyle, IndentWidth, LineEnding, LineWidth, + formatter::FormatContext, formatter::Formatter, }; +use crate::{format, write}; + /// A formatted document. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct Document<'a> { @@ -147,430 +157,433 @@ impl<'a> Deref for Document<'a> { impl std::fmt::Display for Document<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() - // let formatted = format!(IrFormatContext::default(), [self.elements.as_slice()]) - // .expect("Formatting not to throw any FormatErrors"); + let allocator = Allocator::default(); + let source_text = allocator.alloc_str(""); + let source_type = SourceType::default(); + let comments: oxc_allocator::Vec = oxc_allocator::Vec::new_in(&allocator); + let options = FormatOptions { + line_width: LineWidth::default(), + indent_style: IndentStyle::Space, + indent_width: IndentWidth::default(), + line_ending: LineEnding::Lf, + ..Default::default() + }; + let context = + FormatContext::new(source_text, source_type, comments.as_ref(), &allocator, options); + + let formatted = format!(context, [self.elements.as_slice()]) + .expect("Formatting not to throw any FormatErrors"); + + f.write_str(formatted.print().expect("Expected a valid document").as_code()) + } +} + +impl<'a> Format<'a> for &[FormatElement<'a>] { + fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { + use Tag::{ + EndAlign, EndConditionalContent, EndDedent, EndEntry, EndFill, EndGroup, EndIndent, + EndIndentIfGroupBreaks, EndLabelled, EndLineSuffix, EndVerbatim, StartAlign, + StartConditionalContent, StartDedent, StartEntry, StartFill, StartGroup, StartIndent, + StartIndentIfGroupBreaks, StartLabelled, StartLineSuffix, StartVerbatim, + }; + + write!(f, [ContentArrayStart])?; + + let mut tag_stack = Vec::new(); + let mut first_element = true; + let mut in_text = false; + + let mut iter = self.iter().peekable(); + + while let Some(element) = iter.next() { + if !first_element && !in_text && !element.is_end_tag() { + // Write a separator between every two elements + write!(f, [text(","), soft_line_break_or_space()])?; + } + + first_element = false; + + match element { + element @ (FormatElement::Space + | FormatElement::HardSpace + | FormatElement::StaticText { .. } + | FormatElement::DynamicText { .. } + | FormatElement::LocatedTokenText { .. }) => { + if !in_text { + write!(f, [text("\"")])?; + } + + in_text = true; + + match element { + FormatElement::Space | FormatElement::HardSpace => { + write!(f, [text(" ")])?; + } + element if element.is_text() => { + // escape quotes + let new_element = match element { + // except for static text because source_position is unknown + FormatElement::StaticText { .. } => element.clone(), + FormatElement::DynamicText { text } => { + let text = text.cow_replace('"', "\\\""); + FormatElement::DynamicText { + text: f.context().allocator().alloc_str(&text), + } + } + FormatElement::LocatedTokenText { slice, source_position } => { + let text = slice.cow_replace('"', "\\\""); + FormatElement::DynamicText { + text: f.context().allocator().alloc_str(&text), + } + } + _ => unreachable!(), + }; + f.write_element(new_element)?; + } + _ => unreachable!(), + } + + let is_next_text = iter.peek().is_some_and(|e| e.is_text() || e.is_space()); + + if !is_next_text { + write!(f, [text("\"")])?; + in_text = false; + } + } + + FormatElement::Line(mode) => match mode { + LineMode::SoftOrSpace => { + write!(f, [text("soft_line_break_or_space")])?; + } + LineMode::Soft => { + write!(f, [text("soft_line_break")])?; + } + LineMode::Hard => { + write!(f, [text("hard_line_break")])?; + } + LineMode::Empty => { + write!(f, [text("empty_line")])?; + } + }, + FormatElement::ExpandParent => { + write!(f, [text("expand_parent")])?; + } + + FormatElement::LineSuffixBoundary => { + write!(f, [text("line_suffix_boundary")])?; + } + + FormatElement::BestFitting(best_fitting) => { + write!(f, [text("best_fitting([")])?; + f.write_elements([ + FormatElement::Tag(StartIndent), + FormatElement::Line(LineMode::Hard), + ])?; + + for variant in best_fitting.variants() { + write!(f, [&**variant, hard_line_break()])?; + } + + f.write_elements([ + FormatElement::Tag(EndIndent), + FormatElement::Line(LineMode::Hard), + ])?; + + write!(f, [text("])")])?; + } + + FormatElement::Interned(interned) => { + let mut interned_elements = f.state_mut().printed_interned_elements(); + match interned_elements.get(interned).copied() { + None => { + let index = interned_elements.len(); + interned_elements.insert(interned.clone(), index); + + write!( + f, + [ + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("")) + ), + space(), + &&**interned, + ] + )?; + } + Some(reference) => { + write!( + f, + [dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("")) + )] + )?; + } + } + } + + FormatElement::Tag(tag) => { + if tag.is_start() { + first_element = true; + tag_stack.push(tag.kind()); + } + // Handle documents with mismatching start/end or superfluous end tags + else { + match tag_stack.pop() { + None => { + // Only write the end tag without any indent to ensure the output document is valid. + write!( + f, + [ + text(">"), + ] + )?; + first_element = false; + continue; + } + Some(start_kind) if start_kind != tag.kind() => { + write!( + f, + [ + ContentArrayEnd, + text(")"), + soft_line_break_or_space(), + text("ERROR>") + ] + )?; + first_element = false; + continue; + } + _ => { + // all ok + } + } + } - // f.write_str(formatted.print().expect("Expected a valid document").as_code()) + match tag { + StartIndent => { + write!(f, [text("indent(")])?; + } + + StartDedent(mode) => { + let label = match mode { + DedentMode::Level => "dedent", + DedentMode::Root => "dedentRoot", + }; + + write!(f, [text(label), text("(")])?; + } + + StartAlign(tag::Align(count)) => { + write!( + f, + [ + text("align("), + dynamic_text( + f.context().allocator().alloc_str(&count.to_string()), + ), + text(","), + space(), + ] + )?; + } + + StartLineSuffix => { + write!(f, [text("line_suffix(")])?; + } + + StartVerbatim(_) => { + write!(f, [text("verbatim(")])?; + } + + StartGroup(group) => { + write!(f, [text("group(")])?; + + if let Some(group_id) = group.id() { + write!( + f, + [ + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("\"{group_id:?}\"")) + ), + text(","), + space(), + ] + )?; + } + + match group.mode() { + GroupMode::Flat => {} + GroupMode::Expand => { + write!(f, [text("expand: true,"), space()])?; + } + GroupMode::Propagated => { + write!(f, [text("expand: propagated,"), space()])?; + } + } + } + + StartIndentIfGroupBreaks(id) => { + write!( + f, + [ + text("indent_if_group_breaks("), + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("\"{id:?}\"")), + ), + text(","), + space(), + ] + )?; + } + + StartConditionalContent(condition) => { + match condition.mode { + PrintMode::Flat => { + write!(f, [text("if_group_fits_on_line(")])?; + } + PrintMode::Expanded => { + write!(f, [text("if_group_breaks(")])?; + } + } + + if let Some(group_id) = condition.group_id { + write!( + f, + [ + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("\"{group_id:?}\"")), + ), + text(","), + space(), + ] + )?; + } + } + + StartLabelled(label_id) => { + write!( + f, + [ + text("label("), + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!("\"{label_id:?}\"")), + ), + text(","), + space(), + ] + )?; + } + + StartFill => { + write!(f, [text("fill(")])?; + } + + StartEntry => { + // handled after the match for all start tags + } + EndEntry => write!(f, [ContentArrayEnd])?, + + EndFill + | EndLabelled + | EndConditionalContent + | EndIndentIfGroupBreaks(_) + | EndAlign + | EndIndent + | EndGroup + | EndLineSuffix + | EndDedent(_) + | EndVerbatim => { + write!(f, [ContentArrayEnd, text(")")])?; + } + } + + if tag.is_start() { + write!(f, [ContentArrayStart])?; + } + } + } + } + + while let Some(top) = tag_stack.pop() { + write!( + f, + [ + ContentArrayEnd, + text(")"), + soft_line_break_or_space(), + dynamic_text( + f.context() + .allocator() + .alloc_str(&std::format!(">")) + ), + ] + )?; + } + + write!(f, [ContentArrayEnd]) + } +} + +struct ContentArrayStart; + +impl<'a> Format<'a> for ContentArrayStart { + fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { + use Tag::{StartGroup, StartIndent}; + + write!(f, [text("[")])?; + + f.write_elements([ + FormatElement::Tag(StartGroup(tag::Group::new())), + FormatElement::Tag(StartIndent), + FormatElement::Line(LineMode::Soft), + ]) } } -// #[derive(Clone, Default, Debug)] -// struct IrFormatContext { -// /// The interned elements that have been printed to this point -// printed_interned_elements: FxHashMap, -// } - -// impl<'a> FormatContext<'a> for IrFormatContext { -// type Options = IrFormatOptions; - -// fn source_text(&self) -> &'a str { -// "" -// } - -// fn options(&self) -> &Self::Options { -// &IrFormatOptions -// } - -// // fn source_map(&self) -> Option<&TransformSourceMap> { -// // None -// // } -// } - -// impl Format for &[FormatElement] { -// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { -// use Tag::{ -// EndAlign, EndConditionalContent, EndDedent, EndEntry, EndFill, EndGroup, EndIndent, -// EndIndentIfGroupBreaks, EndLabelled, EndLineSuffix, EndVerbatim, StartAlign, -// StartConditionalContent, StartDedent, StartEntry, StartFill, StartGroup, StartIndent, -// StartIndentIfGroupBreaks, StartLabelled, StartLineSuffix, StartVerbatim, -// }; - -// write!(f, [ContentArrayStart])?; - -// let mut tag_stack = Vec::new(); -// let mut first_element = true; -// let mut in_text = false; - -// let mut iter = self.iter().peekable(); - -// while let Some(element) = iter.next() { -// if !first_element && !in_text && !element.is_end_tag() { -// // Write a separator between every two elements -// write!(f, [text(","), soft_line_break_or_space()])?; -// } - -// first_element = false; - -// match element { -// element @ (FormatElement::Space -// | FormatElement::HardSpace -// | FormatElement::StaticText { .. } -// | FormatElement::DynamicText { .. } -// | FormatElement::LocatedTokenText { .. }) => { -// if !in_text { -// write!(f, [text("\"")])?; -// } - -// in_text = true; - -// match element { -// FormatElement::Space | FormatElement::HardSpace => { -// write!(f, [text(" ")])?; -// } -// element if element.is_text() => { -// // escape quotes -// let new_element = match element { -// // except for static text because source_position is unknown -// FormatElement::StaticText { .. } => element.clone(), -// FormatElement::DynamicText { text, source_position } => { -// let text = text.to_string().replace('"', "\\\""); -// FormatElement::DynamicText { -// text: text.into(), -// source_position: *source_position, -// } -// } -// FormatElement::LocatedTokenText { slice, source_position } => { -// let text = slice.to_string().replace('"', "\\\""); -// FormatElement::DynamicText { -// text: text.into(), -// source_position: *source_position, -// } -// } -// _ => unreachable!(), -// }; -// f.write_element(new_element)?; -// } -// _ => unreachable!(), -// } - -// let is_next_text = iter.peek().is_some_and(|e| e.is_text() || e.is_space()); - -// if !is_next_text { -// write!(f, [text("\"")])?; -// in_text = false; -// } -// } - -// FormatElement::Line(mode) => match mode { -// LineMode::SoftOrSpace => { -// write!(f, [text("soft_line_break_or_space")])?; -// } -// LineMode::Soft => { -// write!(f, [text("soft_line_break")])?; -// } -// LineMode::Hard => { -// write!(f, [text("hard_line_break")])?; -// } -// LineMode::Empty => { -// write!(f, [text("empty_line")])?; -// } -// }, -// FormatElement::ExpandParent => { -// write!(f, [text("expand_parent")])?; -// } - -// FormatElement::LineSuffixBoundary => { -// write!(f, [text("line_suffix_boundary")])?; -// } - -// FormatElement::BestFitting(best_fitting) => { -// write!(f, [text("best_fitting([")])?; -// f.write_elements([ -// FormatElement::Tag(StartIndent), -// FormatElement::Line(LineMode::Hard), -// ])?; - -// for variant in best_fitting.variants() { -// write!(f, [&**variant, hard_line_break()])?; -// } - -// f.write_elements([ -// FormatElement::Tag(EndIndent), -// FormatElement::Line(LineMode::Hard), -// ])?; - -// write!(f, [text("])")])?; -// } - -// FormatElement::Interned(interned) => { -// let interned_elements = &mut f.context_mut().printed_interned_elements; - -// match interned_elements.get(interned).copied() { -// None => { -// let index = interned_elements.len(); -// interned_elements.insert(interned.clone(), index); - -// write!( -// f, -// [ -// dynamic_text( -// &std::format!(""), -// TextSize::default() -// ), -// space(), -// &&**interned, -// ] -// )?; -// } -// Some(reference) => { -// write!( -// f, -// [dynamic_text( -// &std::format!(""), -// TextSize::default() -// )] -// )?; -// } -// } -// } - -// FormatElement::Tag(tag) => { -// if tag.is_start() { -// first_element = true; -// tag_stack.push(tag.kind()); -// } -// // Handle documents with mismatching start/end or superfluous end tags -// else { -// match tag_stack.pop() { -// None => { -// // Only write the end tag without any indent to ensure the output document is valid. -// write!( -// f, -// [ -// text(">"), -// ] -// )?; -// first_element = false; -// continue; -// } -// Some(start_kind) if start_kind != tag.kind() => { -// write!( -// f, -// [ -// ContentArrayEnd, -// text(")"), -// soft_line_break_or_space(), -// text("ERROR>") -// ] -// )?; -// first_element = false; -// continue; -// } -// _ => { -// // all ok -// } -// } -// } - -// match tag { -// StartIndent => { -// write!(f, [text("indent(")])?; -// } - -// StartDedent(mode) => { -// let label = match mode { -// DedentMode::Level => "dedent", -// DedentMode::Root => "dedentRoot", -// }; - -// write!(f, [text(label), text("(")])?; -// } - -// StartAlign(tag::Align(count)) => { -// write!( -// f, -// [ -// text("align("), -// dynamic_text(&count.to_string(), TextSize::default()), -// text(","), -// space(), -// ] -// )?; -// } - -// StartLineSuffix => { -// write!(f, [text("line_suffix(")])?; -// } - -// StartVerbatim(_) => { -// write!(f, [text("verbatim(")])?; -// } - -// StartGroup(group) => { -// write!(f, [text("group(")])?; - -// if let Some(group_id) = group.id() { -// write!( -// f, -// [ -// dynamic_text( -// &std::format!("\"{group_id:?}\""), -// TextSize::default() -// ), -// text(","), -// space(), -// ] -// )?; -// } - -// match group.mode() { -// GroupMode::Flat => {} -// GroupMode::Expand => { -// write!(f, [text("expand: true,"), space()])?; -// } -// GroupMode::Propagated => { -// write!(f, [text("expand: propagated,"), space()])?; -// } -// } -// } - -// StartIndentIfGroupBreaks(id) => { -// write!( -// f, -// [ -// text("indent_if_group_breaks("), -// dynamic_text(&std::format!("\"{id:?}\""), TextSize::default()), -// text(","), -// space(), -// ] -// )?; -// } - -// StartConditionalContent(condition) => { -// match condition.mode { -// PrintMode::Flat => { -// write!(f, [text("if_group_fits_on_line(")])?; -// } -// PrintMode::Expanded => { -// write!(f, [text("if_group_breaks(")])?; -// } -// } - -// if let Some(group_id) = condition.group_id { -// write!( -// f, -// [ -// dynamic_text( -// &std::format!("\"{group_id:?}\""), -// TextSize::default() -// ), -// text(","), -// space(), -// ] -// )?; -// } -// } - -// StartLabelled(label_id) => { -// write!( -// f, -// [ -// text("label("), -// dynamic_text( -// &std::format!("\"{label_id:?}\""), -// TextSize::default() -// ), -// text(","), -// space(), -// ] -// )?; -// } - -// StartFill => { -// write!(f, [text("fill(")])?; -// } - -// StartEntry => { -// // handled after the match for all start tags -// } -// EndEntry => write!(f, [ContentArrayEnd])?, - -// EndFill -// | EndLabelled -// | EndConditionalContent -// | EndIndentIfGroupBreaks(_) -// | EndAlign -// | EndIndent -// | EndGroup -// | EndLineSuffix -// | EndDedent(_) -// | EndVerbatim => { -// write!(f, [ContentArrayEnd, text(")")])?; -// } -// }; - -// if tag.is_start() { -// write!(f, [ContentArrayStart])?; -// } -// } -// } -// } - -// while let Some(top) = tag_stack.pop() { -// write!( -// f, -// [ -// ContentArrayEnd, -// text(")"), -// soft_line_break_or_space(), -// dynamic_text( -// &std::format!(">"), -// TextSize::default() -// ), -// ] -// )?; -// } - -// write!(f, [ContentArrayEnd]) -// } -// } - -// struct ContentArrayStart; - -// impl Format for ContentArrayStart { -// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { -// use Tag::{StartGroup, StartIndent}; - -// write!(f, [text("[")])?; - -// f.write_elements([ -// FormatElement::Tag(StartGroup(tag::Group::new())), -// FormatElement::Tag(StartIndent), -// FormatElement::Line(LineMode::Soft), -// ]) -// } -// } - -// struct ContentArrayEnd; - -// impl Format for ContentArrayEnd { -// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { -// use Tag::{EndGroup, EndIndent}; -// f.write_elements([ -// FormatElement::Tag(EndIndent), -// FormatElement::Line(LineMode::Soft), -// FormatElement::Tag(EndGroup), -// ])?; - -// write!(f, [text("]")]) -// } -// } +struct ContentArrayEnd; + +impl<'a> Format<'a> for ContentArrayEnd { + fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { + use Tag::{EndGroup, EndIndent}; + f.write_elements([ + FormatElement::Tag(EndIndent), + FormatElement::Line(LineMode::Soft), + FormatElement::Tag(EndGroup), + ])?; + + write!(f, [text("]")]) + } +} impl FormatElements for [FormatElement<'_>] { fn will_break(&self) -> bool { diff --git a/crates/oxc_formatter/src/formatter/mod.rs b/crates/oxc_formatter/src/formatter/mod.rs index 83155fe9a3362..2eead5c2e8522 100644 --- a/crates/oxc_formatter/src/formatter/mod.rs +++ b/crates/oxc_formatter/src/formatter/mod.rs @@ -374,11 +374,10 @@ pub fn write<'ast>(output: &mut dyn Buffer<'ast>, args: Arguments<'_, 'ast>) -> /// # } /// ``` pub fn format<'ast>( - program: &'ast Program<'ast>, context: FormatContext<'ast>, arguments: Arguments<'_, 'ast>, ) -> FormatResult> { - let mut state = FormatState::new(program, context); + let mut state = FormatState::new(context); let mut buffer = VecBuffer::with_capacity(arguments.items().len(), &mut state); buffer.write_fmt(arguments)?; diff --git a/crates/oxc_formatter/src/formatter/state.rs b/crates/oxc_formatter/src/formatter/state.rs index 5a073dcd410e8..9d88577b635c8 100644 --- a/crates/oxc_formatter/src/formatter/state.rs +++ b/crates/oxc_formatter/src/formatter/state.rs @@ -1,8 +1,9 @@ use oxc_ast::{AstKind, ast::Program}; use oxc_data_structures::stack::NonEmptyStack; use oxc_span::Span; +use rustc_hash::FxHashMap; -use super::{FormatContext, GroupId, SyntaxNode, UniqueGroupIdBuilder}; +use super::{FormatContext, GroupId, SyntaxNode, UniqueGroupIdBuilder, prelude::Interned}; /// This structure stores the state that is relevant for the formatting of the whole document. /// @@ -12,6 +13,9 @@ use super::{FormatContext, GroupId, SyntaxNode, UniqueGroupIdBuilder}; pub struct FormatState<'ast> { context: FormatContext<'ast>, group_id_builder: UniqueGroupIdBuilder, + // For the document IR printing process + /// The interned elements that have been printed to this point + printed_interned_elements: FxHashMap, usize>, // This is using a RefCell as it only exists in debug mode, // the Formatter is still completely immutable in release builds // #[cfg(debug_assertions)] @@ -26,10 +30,11 @@ impl std::fmt::Debug for FormatState<'_> { impl<'ast> FormatState<'ast> { /// Creates a new state with the given language specific context - pub fn new(program: &'ast Program<'ast>, context: FormatContext<'ast>) -> Self { + pub fn new(context: FormatContext<'ast>) -> Self { Self { context, group_id_builder: UniqueGroupIdBuilder::default(), + printed_interned_elements: FxHashMap::default(), // #[cfg(debug_assertions)] // printed_tokens: Default::default(), } @@ -56,6 +61,11 @@ impl<'ast> FormatState<'ast> { self.group_id_builder.group_id(debug_name) } + #[expect(clippy::mutable_key_type)] + pub fn printed_interned_elements(&mut self) -> &mut FxHashMap, usize> { + &mut self.printed_interned_elements + } + #[cfg(not(debug_assertions))] #[inline] pub fn track_token(&mut self, span: Span) {} diff --git a/crates/oxc_formatter/src/lib.rs b/crates/oxc_formatter/src/lib.rs index c8c08d87b2459..7d5336f39c636 100644 --- a/crates/oxc_formatter/src/lib.rs +++ b/crates/oxc_formatter/src/lib.rs @@ -67,7 +67,7 @@ impl<'a> Formatter<'a> { formatted.print().unwrap().into_code() } - fn format(mut self, program: &'a Program<'a>) -> Formatted<'a> { + pub fn format(mut self, program: &'a Program<'a>) -> Formatted<'a> { let parent = self.allocator.alloc(AstNodes::Dummy()); let program_node = AstNode::new(program, parent, self.allocator); @@ -76,9 +76,14 @@ impl<'a> Formatter<'a> { let experimental_sort_imports = self.options.experimental_sort_imports; - let context = FormatContext::new(program, self.allocator, self.options); + let context = FormatContext::new( + program.source_text, + program.source_type, + &program.comments, + self.allocator, + self.options, + ); let mut formatted = formatter::format( - program, context, formatter::Arguments::new(&[formatter::Argument::new(&program_node)]), )