mod arguments;
mod buffer;
mod builders;
pub mod diagnostics;
pub mod format_element;
mod format_extensions;
pub mod formatter;
pub mod group_id;
pub mod macros;
pub mod prelude;
pub mod printer;
mod source_code;
use crate::formatter::Formatter;
use crate::group_id::UniqueGroupIdBuilder;
use crate::prelude::TagKind;
use std::fmt;
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
use std::num::{NonZeroU8, NonZeroU16, TryFromIntError};
use crate::format_element::document::Document;
use crate::printer::{Printer, PrinterOptions};
pub use arguments::{Argument, Arguments};
pub use buffer::{
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
};
pub use builders::BestFitting;
pub use source_code::{SourceCode, SourceCodeSlice};
pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, PrintError};
pub use format_element::{FormatElement, LINE_TERMINATORS, normalize_newlines};
pub use group_id::GroupId;
use ruff_macros::CacheKey;
use ruff_text_size::{TextLen, TextRange, TextSize};
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Default)]
pub enum IndentStyle {
#[default]
Tab,
Space,
}
impl IndentStyle {
pub const fn is_tab(&self) -> bool {
matches!(self, IndentStyle::Tab)
}
pub const fn is_space(&self) -> bool {
matches!(self, IndentStyle::Space)
}
pub const fn as_str(&self) -> &'static str {
match self {
IndentStyle::Tab => "tab",
IndentStyle::Space => "space",
}
}
}
impl std::fmt::Display for IndentStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct IndentWidth(NonZeroU8);
impl IndentWidth {
pub const fn value(&self) -> u32 {
self.0.get() as u32
}
}
impl Default for IndentWidth {
fn default() -> Self {
Self(NonZeroU8::new(2).unwrap())
}
}
impl Display for IndentWidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl TryFrom<u8> for IndentWidth {
type Error = TryFromIntError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
NonZeroU8::try_from(value).map(Self)
}
}
impl From<NonZeroU8> for IndentWidth {
fn from(value: NonZeroU8) -> Self {
Self(value)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, CacheKey)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct LineWidth(NonZeroU16);
impl LineWidth {
pub const fn value(&self) -> u16 {
self.0.get()
}
}
impl Default for LineWidth {
fn default() -> Self {
Self(NonZeroU16::new(80).unwrap())
}
}
impl Display for LineWidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl TryFrom<u16> for LineWidth {
type Error = TryFromIntError;
fn try_from(value: u16) -> Result<LineWidth, Self::Error> {
NonZeroU16::try_from(value).map(LineWidth)
}
}
impl From<LineWidth> for u16 {
fn from(value: LineWidth) -> Self {
value.0.get()
}
}
impl From<LineWidth> for u32 {
fn from(value: LineWidth) -> Self {
u32::from(value.0.get())
}
}
impl From<NonZeroU16> for LineWidth {
fn from(value: NonZeroU16) -> Self {
Self(value)
}
}
pub trait FormatContext {
type Options: FormatOptions;
fn options(&self) -> &Self::Options;
fn source_code(&self) -> SourceCode<'_>;
}
pub trait FormatOptions {
fn indent_style(&self) -> IndentStyle;
fn indent_width(&self) -> IndentWidth;
fn line_width(&self) -> LineWidth;
fn as_print_options(&self) -> PrinterOptions;
}
#[derive(Debug, Default, Eq, PartialEq)]
pub struct SimpleFormatContext {
options: SimpleFormatOptions,
source_code: String,
}
impl SimpleFormatContext {
pub fn new(options: SimpleFormatOptions) -> Self {
Self {
options,
source_code: String::new(),
}
}
#[must_use]
pub fn with_source_code(mut self, code: &str) -> Self {
self.source_code = String::from(code);
self
}
}
impl FormatContext for SimpleFormatContext {
type Options = SimpleFormatOptions;
fn options(&self) -> &Self::Options {
&self.options
}
fn source_code(&self) -> SourceCode<'_> {
SourceCode::new(&self.source_code)
}
}
#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct SimpleFormatOptions {
pub indent_style: IndentStyle,
pub indent_width: IndentWidth,
pub line_width: LineWidth,
}
impl FormatOptions for SimpleFormatOptions {
fn indent_style(&self) -> IndentStyle {
self.indent_style
}
fn indent_width(&self) -> IndentWidth {
self.indent_width
}
fn line_width(&self) -> LineWidth {
self.line_width
}
fn as_print_options(&self) -> PrinterOptions {
PrinterOptions {
line_width: self.line_width,
indent_style: self.indent_style,
indent_width: self.indent_width,
..PrinterOptions::default()
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SourceMarker {
pub source: TextSize,
pub dest: TextSize,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Formatted<Context> {
document: Document,
context: Context,
}
impl<Context> Formatted<Context> {
pub fn new(document: Document, context: Context) -> Self {
Self { document, context }
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn document(&self) -> &Document {
&self.document
}
pub fn into_document(self) -> Document {
self.document
}
}
impl<Context> Formatted<Context>
where
Context: FormatContext,
{
pub fn print(&self) -> PrintResult<Printed> {
let printer = self.create_printer();
printer.print(&self.document)
}
pub fn print_with_indent(&self, indent: u16) -> PrintResult<Printed> {
let printer = self.create_printer();
printer.print_with_indent(&self.document, indent)
}
fn create_printer(&self) -> Printer<'_> {
let source_code = self.context.source_code();
let print_options = self.context.options().as_print_options();
Printer::new(source_code, print_options)
}
}
impl<Context> Display for Formatted<Context>
where
Context: FormatContext,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.document.display(self.context.source_code()), f)
}
}
pub type PrintResult<T> = Result<T, PrintError>;
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Printed {
code: String,
range: Option<TextRange>,
sourcemap: Vec<SourceMarker>,
verbatim_ranges: Vec<TextRange>,
}
impl Printed {
pub fn new(
code: String,
range: Option<TextRange>,
sourcemap: Vec<SourceMarker>,
verbatim_source: Vec<TextRange>,
) -> Self {
Self {
code,
range,
sourcemap,
verbatim_ranges: verbatim_source,
}
}
pub fn new_empty() -> Self {
Self {
code: String::new(),
range: None,
sourcemap: Vec::new(),
verbatim_ranges: Vec::new(),
}
}
pub fn range(&self) -> Option<TextRange> {
self.range
}
pub fn sourcemap(&self) -> &[SourceMarker] {
&self.sourcemap
}
pub fn into_sourcemap(self) -> Vec<SourceMarker> {
self.sourcemap
}
pub fn take_sourcemap(&mut self) -> Vec<SourceMarker> {
std::mem::take(&mut self.sourcemap)
}
pub fn as_code(&self) -> &str {
&self.code
}
pub fn into_code(self) -> String {
self.code
}
pub fn verbatim(&self) -> impl Iterator<Item = (TextRange, &str)> {
self.verbatim_ranges
.iter()
.map(|range| (*range, &self.code[*range]))
}
pub fn verbatim_ranges(&self) -> &[TextRange] {
&self.verbatim_ranges
}
pub fn take_verbatim_ranges(&mut self) -> Vec<TextRange> {
std::mem::take(&mut self.verbatim_ranges)
}
#[must_use]
pub fn slice_range(self, source_range: TextRange, source: &str) -> PrintedRange {
let mut start_marker: Option<SourceMarker> = None;
let mut end_marker: Option<SourceMarker> = None;
for marker in self.sourcemap {
if marker.source <= source_range.start()
&& start_marker.is_none_or(|existing| existing.source < marker.source)
{
start_marker = Some(marker);
}
if marker.source >= source_range.end()
&& end_marker.is_none_or(|existing| existing.source > marker.source)
{
end_marker = Some(marker);
}
}
let (source_start, formatted_start) = start_marker
.map(|marker| (marker.source, marker.dest))
.unwrap_or_default();
let (source_end, formatted_end) = end_marker
.map_or((source.text_len(), self.code.text_len()), |marker| {
(marker.source, marker.dest)
});
let source_range = TextRange::new(source_start, source_end);
let formatted_range = TextRange::new(formatted_start, formatted_end);
let source_range = extend_range_to_include_indent(source_range, source);
let formatted_range = extend_range_to_include_indent(formatted_range, &self.code);
PrintedRange {
code: self.code[formatted_range].to_string(),
source_range,
}
}
}
fn extend_range_to_include_indent(range: TextRange, source: &str) -> TextRange {
let whitespace_len: TextSize = source[..usize::from(range.start())]
.chars()
.rev()
.take_while(|c| matches!(c, ' ' | '\t'))
.map(TextLen::text_len)
.sum();
TextRange::new(range.start() - whitespace_len, range.end())
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PrintedRange {
code: String,
source_range: TextRange,
}
impl PrintedRange {
pub fn new(code: String, source_range: TextRange) -> Self {
Self { code, source_range }
}
pub fn empty() -> Self {
Self {
code: String::new(),
source_range: TextRange::default(),
}
}
pub fn as_code(&self) -> &str {
&self.code
}
pub fn into_code(self) -> String {
self.code
}
pub fn source_range(&self) -> TextRange {
self.source_range
}
}
pub type FormatResult<F> = Result<F, FormatError>;
pub trait Format<Context> {
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()>;
}
impl<T, Context> Format<Context> for &T
where
T: ?Sized + Format<Context>,
{
#[inline]
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
Format::fmt(&**self, f)
}
}
impl<T, Context> Format<Context> for &mut T
where
T: ?Sized + Format<Context>,
{
#[inline]
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
Format::fmt(&**self, f)
}
}
impl<T, Context> Format<Context> for Option<T>
where
T: Format<Context>,
{
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
match self {
Some(value) => value.fmt(f),
None => Ok(()),
}
}
}
impl<Context> Format<Context> for () {
#[inline]
fn fmt(&self, _: &mut Formatter<Context>) -> FormatResult<()> {
Ok(())
}
}
pub trait FormatRule<T, C> {
fn fmt(&self, item: &T, f: &mut Formatter<C>) -> FormatResult<()>;
}
pub trait FormatRuleWithOptions<T, C>: FormatRule<T, C> {
type Options;
#[must_use]
fn with_options(self, options: Self::Options) -> Self;
}
pub trait FormatWithRule<Context>: Format<Context> {
type Item;
fn item(&self) -> &Self::Item;
}
#[derive(Debug, Copy, Clone)]
pub struct FormatRefWithRule<'a, T, R, C>
where
R: FormatRule<T, C>,
{
item: &'a T,
rule: R,
context: PhantomData<C>,
}
impl<'a, T, R, C> FormatRefWithRule<'a, T, R, C>
where
R: FormatRule<T, C>,
{
pub fn new(item: &'a T, rule: R) -> Self {
Self {
item,
rule,
context: PhantomData,
}
}
pub fn rule(&self) -> &R {
&self.rule
}
}
impl<T, R, O, C> FormatRefWithRule<'_, T, R, C>
where
R: FormatRuleWithOptions<T, C, Options = O>,
{
#[must_use]
pub fn with_options(mut self, options: O) -> Self {
self.rule = self.rule.with_options(options);
self
}
}
impl<T, R, C> FormatWithRule<C> for FormatRefWithRule<'_, T, R, C>
where
R: FormatRule<T, C>,
{
type Item = T;
fn item(&self) -> &Self::Item {
self.item
}
}
impl<T, R, C> Format<C> for FormatRefWithRule<'_, T, R, C>
where
R: FormatRule<T, C>,
{
#[inline]
fn fmt(&self, f: &mut Formatter<C>) -> FormatResult<()> {
self.rule.fmt(self.item, f)
}
}
#[derive(Debug, Clone)]
pub struct FormatOwnedWithRule<T, R, C>
where
R: FormatRule<T, C>,
{
item: T,
rule: R,
context: PhantomData<C>,
}
impl<T, R, C> FormatOwnedWithRule<T, R, C>
where
R: FormatRule<T, C>,
{
pub fn new(item: T, rule: R) -> Self {
Self {
item,
rule,
context: PhantomData,
}
}
#[must_use]
pub fn with_item(mut self, item: T) -> Self {
self.item = item;
self
}
}
impl<T, R, C> Format<C> for FormatOwnedWithRule<T, R, C>
where
R: FormatRule<T, C>,
{
#[inline]
fn fmt(&self, f: &mut Formatter<C>) -> FormatResult<()> {
self.rule.fmt(&self.item, f)
}
}
impl<T, R, O, C> FormatOwnedWithRule<T, R, C>
where
R: FormatRuleWithOptions<T, C, Options = O>,
{
#[must_use]
pub fn with_options(mut self, options: O) -> Self {
self.rule = self.rule.with_options(options);
self
}
}
impl<T, R, C> FormatWithRule<C> for FormatOwnedWithRule<T, R, C>
where
R: FormatRule<T, C>,
{
type Item = T;
fn item(&self) -> &Self::Item {
&self.item
}
}
#[inline]
pub fn write<Context>(
output: &mut dyn Buffer<Context = Context>,
args: Arguments<Context>,
) -> FormatResult<()> {
let mut f = Formatter::new(output);
f.write_fmt(args)
}
pub fn format<Context>(
context: Context,
arguments: Arguments<Context>,
) -> FormatResult<Formatted<Context>>
where
Context: FormatContext,
{
let source_length = context.source_code().as_str().len();
let estimated_buffer_size = source_length / 2;
let mut state = FormatState::new(context);
let mut buffer = VecBuffer::with_capacity(estimated_buffer_size, &mut state);
buffer.write_fmt(arguments)?;
let mut document = Document::from(buffer.into_vec());
document.propagate_expand();
Ok(Formatted::new(document, state.into_context()))
}
pub struct FormatState<Context> {
context: Context,
group_id_builder: UniqueGroupIdBuilder,
}
#[expect(clippy::missing_fields_in_debug)]
impl<Context> std::fmt::Debug for FormatState<Context>
where
Context: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("FormatState")
.field("context", &self.context)
.finish()
}
}
impl<Context> FormatState<Context> {
pub fn new(context: Context) -> Self {
Self {
context,
group_id_builder: UniqueGroupIdBuilder::default(),
}
}
pub fn into_context(self) -> Context {
self.context
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn context_mut(&mut self) -> &mut Context {
&mut self.context
}
pub fn group_id(&self, debug_name: &'static str) -> GroupId {
self.group_id_builder.group_id(debug_name)
}
}