use crate::{Goal, Result, format_in};
use tsv_lang::doc::arena::DocArena;
pub struct Formatter {
ast: bumpalo::Bump,
doc: DocArena,
}
impl Formatter {
#[must_use]
pub fn new() -> Self {
Self {
ast: bumpalo::Bump::new(),
doc: DocArena::new(),
}
}
pub fn format(&mut self, source: &str) -> Result<String> {
self.format_with_optional_goal(source, None)
}
pub fn format_with_goal(&mut self, source: &str, goal: Goal) -> Result<String> {
self.format_with_optional_goal(source, Some(goal))
}
fn format_with_optional_goal(&mut self, source: &str, goal: Option<Goal>) -> Result<String> {
self.ast.reset();
self.doc.reset();
let formatted = format_in(source, goal, &self.ast, |_| &self.doc);
self.ast.reset();
formatted
}
}
impl Default for Formatter {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Formatter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Formatter").finish_non_exhaustive()
}
}
const _: () = {
const fn assert_send<T: Send>() {}
assert_send::<Formatter>();
};
#[cfg(test)]
mod tests {
use super::Formatter;
use crate::{Goal, format, format_with_goal};
#[test]
fn a_reused_formatter_matches_the_one_shot_functions() {
let large = "const a = [1, 2, 3];\n".repeat(2_000);
let sources = [
"const x=42;",
"const = ;",
large.as_str(),
"with (a) b;",
"let y\r\n= 1",
"",
];
let mut formatter = Formatter::default();
for source in sources {
let reused = formatter.format(source).map_err(|e| format!("{e:#}"));
let fresh = format(source).map_err(|e| format!("{e:#}"));
assert_eq!(reused, fresh, "{source:?}");
for goal in [Goal::Module, Goal::Script] {
let reused = formatter
.format_with_goal(source, goal)
.map_err(|e| format!("{e:#}"));
let fresh = format_with_goal(source, goal).map_err(|e| format!("{e:#}"));
assert_eq!(reused, fresh, "{source:?} at {goal:?}");
}
}
}
}